summaryrefslogtreecommitdiffstats
path: root/widget/gtk
diff options
context:
space:
mode:
Diffstat (limited to 'widget/gtk')
-rw-r--r--widget/gtk/AsyncDBus.cpp90
-rw-r--r--widget/gtk/AsyncDBus.h38
-rw-r--r--widget/gtk/AsyncGtkClipboardRequest.cpp120
-rw-r--r--widget/gtk/AsyncGtkClipboardRequest.h61
-rw-r--r--widget/gtk/CompositorWidgetChild.cpp51
-rw-r--r--widget/gtk/CompositorWidgetChild.h41
-rw-r--r--widget/gtk/CompositorWidgetParent.cpp54
-rw-r--r--widget/gtk/CompositorWidgetParent.h42
-rw-r--r--widget/gtk/DMABufLibWrapper.cpp346
-rw-r--r--widget/gtk/DMABufLibWrapper.h231
-rw-r--r--widget/gtk/DMABufSurface.cpp1729
-rw-r--r--widget/gtk/DMABufSurface.h413
-rw-r--r--widget/gtk/GRefPtr.h71
-rw-r--r--widget/gtk/GUniquePtr.h35
-rw-r--r--widget/gtk/GfxInfo.cpp1556
-rw-r--r--widget/gtk/GfxInfo.h137
-rw-r--r--widget/gtk/GfxInfoUtils.h98
-rw-r--r--widget/gtk/GtkCompositorWidget.cpp247
-rw-r--r--widget/gtk/GtkCompositorWidget.h140
-rw-r--r--widget/gtk/IMContextWrapper.cpp3358
-rw-r--r--widget/gtk/IMContextWrapper.h692
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.cpp44
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.h30
-rw-r--r--widget/gtk/MPRISInterfaceDescription.h91
-rw-r--r--widget/gtk/MPRISServiceHandler.cpp909
-rw-r--r--widget/gtk/MPRISServiceHandler.h195
-rw-r--r--widget/gtk/MediaKeysEventSourceFactory.cpp14
-rw-r--r--widget/gtk/MozContainer.cpp402
-rw-r--r--widget/gtk/MozContainer.h97
-rw-r--r--widget/gtk/MozContainerWayland.cpp824
-rw-r--r--widget/gtk/MozContainerWayland.h110
-rw-r--r--widget/gtk/NativeKeyBindings.cpp527
-rw-r--r--widget/gtk/NativeKeyBindings.h62
-rw-r--r--widget/gtk/NativeMenuGtk.cpp424
-rw-r--r--widget/gtk/NativeMenuGtk.h64
-rw-r--r--widget/gtk/NativeMenuSupport.cpp29
-rw-r--r--widget/gtk/PCompositorWidget.ipdl35
-rw-r--r--widget/gtk/PlatformWidgetTypes.ipdlh33
-rw-r--r--widget/gtk/ScreenHelperGTK.cpp306
-rw-r--r--widget/gtk/ScreenHelperGTK.h30
-rw-r--r--widget/gtk/TaskbarProgress.cpp106
-rw-r--r--widget/gtk/TaskbarProgress.h33
-rw-r--r--widget/gtk/WakeLockListener.cpp913
-rw-r--r--widget/gtk/WakeLockListener.h38
-rw-r--r--widget/gtk/WaylandBuffer.cpp224
-rw-r--r--widget/gtk/WaylandBuffer.h140
-rw-r--r--widget/gtk/WaylandVsyncSource.cpp431
-rw-r--r--widget/gtk/WaylandVsyncSource.h99
-rw-r--r--widget/gtk/WidgetStyleCache.cpp1434
-rw-r--r--widget/gtk/WidgetStyleCache.h63
-rw-r--r--widget/gtk/WidgetTraceEvent.cpp68
-rw-r--r--widget/gtk/WidgetUtilsGtk.cpp500
-rw-r--r--widget/gtk/WidgetUtilsGtk.h83
-rw-r--r--widget/gtk/WindowSurface.h41
-rw-r--r--widget/gtk/WindowSurfaceProvider.cpp212
-rw-r--r--widget/gtk/WindowSurfaceProvider.h101
-rw-r--r--widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp418
-rw-r--r--widget/gtk/WindowSurfaceWaylandMultiBuffer.h84
-rw-r--r--widget/gtk/WindowSurfaceX11.cpp47
-rw-r--r--widget/gtk/WindowSurfaceX11.h39
-rw-r--r--widget/gtk/WindowSurfaceX11Image.cpp272
-rw-r--r--widget/gtk/WindowSurfaceX11Image.h49
-rw-r--r--widget/gtk/WindowSurfaceX11SHM.cpp27
-rw-r--r--widget/gtk/WindowSurfaceX11SHM.h36
-rw-r--r--widget/gtk/compat/gdk/gdkdnd.h29
-rw-r--r--widget/gtk/compat/gdk/gdkkeysyms.h266
-rw-r--r--widget/gtk/compat/gdk/gdkvisual.h15
-rw-r--r--widget/gtk/compat/gdk/gdkwindow.h27
-rw-r--r--widget/gtk/compat/gdk/gdkx.h42
-rw-r--r--widget/gtk/compat/glib/gmem.h48
-rw-r--r--widget/gtk/compat/gtk/gtkwidget.h43
-rw-r--r--widget/gtk/compat/gtk/gtkwindow.h26
-rw-r--r--widget/gtk/components.conf151
-rw-r--r--widget/gtk/crashtests/540078-1.xhtml1
-rw-r--r--widget/gtk/crashtests/673390-1.html1
-rw-r--r--widget/gtk/crashtests/crashtests.list2
-rw-r--r--widget/gtk/gbm.h480
-rw-r--r--widget/gtk/gtk3drawing.cpp2179
-rw-r--r--widget/gtk/gtkdrawing.h524
-rw-r--r--widget/gtk/moz.build179
-rw-r--r--widget/gtk/mozgtk/moz.build37
-rw-r--r--widget/gtk/mozgtk/mozgtk.c30
-rw-r--r--widget/gtk/mozwayland/moz.build16
-rw-r--r--widget/gtk/mozwayland/mozwayland.c230
-rw-r--r--widget/gtk/mozwayland/mozwayland.h134
-rw-r--r--widget/gtk/nsAppShell.cpp494
-rw-r--r--widget/gtk/nsAppShell.h69
-rw-r--r--widget/gtk/nsApplicationChooser.cpp132
-rw-r--r--widget/gtk/nsApplicationChooser.h32
-rw-r--r--widget/gtk/nsBidiKeyboard.cpp52
-rw-r--r--widget/gtk/nsBidiKeyboard.h25
-rw-r--r--widget/gtk/nsClipboard.cpp1425
-rw-r--r--widget/gtk/nsClipboard.h177
-rw-r--r--widget/gtk/nsClipboardWayland.cpp75
-rw-r--r--widget/gtk/nsClipboardWayland.h28
-rw-r--r--widget/gtk/nsClipboardX11.cpp173
-rw-r--r--widget/gtk/nsClipboardX11.h32
-rw-r--r--widget/gtk/nsColorPicker.cpp253
-rw-r--r--widget/gtk/nsColorPicker.h71
-rw-r--r--widget/gtk/nsDeviceContextSpecG.cpp422
-rw-r--r--widget/gtk/nsDeviceContextSpecG.h62
-rw-r--r--widget/gtk/nsDragService.cpp2756
-rw-r--r--widget/gtk/nsDragService.h268
-rw-r--r--widget/gtk/nsFilePicker.cpp785
-rw-r--r--widget/gtk/nsFilePicker.h96
-rw-r--r--widget/gtk/nsGTKToolkit.h52
-rw-r--r--widget/gtk/nsGtkCursors.h416
-rw-r--r--widget/gtk/nsGtkKeyUtils.cpp2541
-rw-r--r--widget/gtk/nsGtkKeyUtils.h509
-rw-r--r--widget/gtk/nsGtkUtils.h59
-rw-r--r--widget/gtk/nsImageToPixbuf.cpp121
-rw-r--r--widget/gtk/nsImageToPixbuf.h37
-rw-r--r--widget/gtk/nsLookAndFeel.cpp2329
-rw-r--r--widget/gtk/nsLookAndFeel.h211
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp1369
-rw-r--r--widget/gtk/nsNativeThemeGTK.h119
-rw-r--r--widget/gtk/nsPrintDialogGTK.cpp621
-rw-r--r--widget/gtk/nsPrintDialogGTK.h35
-rw-r--r--widget/gtk/nsPrintSettingsGTK.cpp681
-rw-r--r--widget/gtk/nsPrintSettingsGTK.h147
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.cpp80
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.h33
-rw-r--r--widget/gtk/nsShmImage.cpp326
-rw-r--r--widget/gtk/nsShmImage.h75
-rw-r--r--widget/gtk/nsSound.cpp397
-rw-r--r--widget/gtk/nsSound.h33
-rw-r--r--widget/gtk/nsToolkit.cpp24
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.cpp317
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.h78
-rw-r--r--widget/gtk/nsWaylandDisplay.cpp204
-rw-r--r--widget/gtk/nsWaylandDisplay.h120
-rw-r--r--widget/gtk/nsWidgetFactory.cpp67
-rw-r--r--widget/gtk/nsWidgetFactory.h21
-rw-r--r--widget/gtk/nsWindow.cpp10028
-rw-r--r--widget/gtk/nsWindow.h1016
-rw-r--r--widget/gtk/v4l2test/moz.build17
-rw-r--r--widget/gtk/v4l2test/v4l2test.cpp188
-rw-r--r--widget/gtk/va_drmcommon.h156
-rw-r--r--widget/gtk/vaapitest/moz.build20
-rw-r--r--widget/gtk/vaapitest/vaapitest.cpp255
-rw-r--r--widget/gtk/wayland/fractional-scale-v1-client-protocol.h268
-rw-r--r--widget/gtk/wayland/fractional-scale-v1-protocol.c73
-rw-r--r--widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h228
-rw-r--r--widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c60
-rw-r--r--widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h650
-rw-r--r--widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c81
-rw-r--r--widget/gtk/wayland/moz.build37
-rw-r--r--widget/gtk/wayland/pointer-constraints-unstable-v1-client-protocol.h650
-rw-r--r--widget/gtk/wayland/pointer-constraints-unstable-v1-protocol.c97
-rw-r--r--widget/gtk/wayland/relative-pointer-unstable-v1-client-protocol.h293
-rw-r--r--widget/gtk/wayland/relative-pointer-unstable-v1-protocol.c69
-rw-r--r--widget/gtk/wayland/viewporter-client-protocol.h392
-rw-r--r--widget/gtk/wayland/viewporter-protocol.c56
-rw-r--r--widget/gtk/wayland/xdg-activation-v1-client-protocol.h409
-rw-r--r--widget/gtk/wayland/xdg-activation-v1-protocol.c82
-rw-r--r--widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h392
-rw-r--r--widget/gtk/wayland/xdg-output-unstable-v1-protocol.c74
157 files changed, 57734 insertions, 0 deletions
diff --git a/widget/gtk/AsyncDBus.cpp b/widget/gtk/AsyncDBus.cpp
new file mode 100644
index 0000000000..ca560aca96
--- /dev/null
+++ b/widget/gtk/AsyncDBus.cpp
@@ -0,0 +1,90 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AsyncDBus.h"
+#include "mozilla/UniquePtrExtensions.h"
+
+namespace mozilla::widget {
+
+static void CreateProxyCallback(GObject*, GAsyncResult* aResult,
+ gpointer aUserData) {
+ RefPtr<DBusProxyPromise::Private> promise =
+ dont_AddRef(static_cast<DBusProxyPromise::Private*>(aUserData));
+ GUniquePtr<GError> error;
+ RefPtr<GDBusProxy> proxy = dont_AddRef(
+ g_dbus_proxy_new_for_bus_finish(aResult, getter_Transfers(error)));
+ if (proxy) {
+ promise->Resolve(std::move(proxy), __func__);
+ } else {
+ promise->Reject(std::move(error), __func__);
+ }
+}
+
+RefPtr<DBusProxyPromise> CreateDBusProxyForBus(
+ GBusType aBusType, GDBusProxyFlags aFlags,
+ GDBusInterfaceInfo* aInterfaceInfo, const char* aName,
+ const char* aObjectPath, const char* aInterfaceName,
+ GCancellable* aCancellable) {
+ auto promise = MakeRefPtr<DBusProxyPromise::Private>(__func__);
+ g_dbus_proxy_new_for_bus(aBusType, aFlags, aInterfaceInfo, aName, aObjectPath,
+ aInterfaceName, aCancellable, CreateProxyCallback,
+ do_AddRef(promise).take());
+ return promise.forget();
+}
+
+static void ProxyCallCallback(GObject* aSourceObject, GAsyncResult* aResult,
+ gpointer aUserData) {
+ RefPtr<DBusCallPromise::Private> promise =
+ dont_AddRef(static_cast<DBusCallPromise::Private*>(aUserData));
+ GUniquePtr<GError> error;
+ RefPtr<GVariant> result = dont_AddRef(g_dbus_proxy_call_finish(
+ G_DBUS_PROXY(aSourceObject), aResult, getter_Transfers(error)));
+ if (result) {
+ promise->Resolve(std::move(result), __func__);
+ } else {
+ promise->Reject(std::move(error), __func__);
+ }
+}
+
+RefPtr<DBusCallPromise> DBusProxyCall(GDBusProxy* aProxy, const char* aMethod,
+ GVariant* aArgs, GDBusCallFlags aFlags,
+ gint aTimeout,
+ GCancellable* aCancellable) {
+ auto promise = MakeRefPtr<DBusCallPromise::Private>(__func__);
+ g_dbus_proxy_call(aProxy, aMethod, aArgs, aFlags, aTimeout, aCancellable,
+ ProxyCallCallback, do_AddRef(promise).take());
+ return promise.forget();
+}
+
+static void ProxyCallWithUnixFDListCallback(GObject* aSourceObject,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ RefPtr<DBusCallPromise::Private> promise =
+ dont_AddRef(static_cast<DBusCallPromise::Private*>(aUserData));
+ GUniquePtr<GError> error;
+ GUnixFDList** aFDList = nullptr;
+ RefPtr<GVariant> result =
+ dont_AddRef(g_dbus_proxy_call_with_unix_fd_list_finish(
+ G_DBUS_PROXY(aSourceObject), aFDList, aResult,
+ getter_Transfers(error)));
+ if (result) {
+ promise->Resolve(std::move(result), __func__);
+ } else {
+ promise->Reject(std::move(error), __func__);
+ }
+}
+
+RefPtr<DBusCallPromise> DBusProxyCallWithUnixFDList(
+ GDBusProxy* aProxy, const char* aMethod, GVariant* aArgs,
+ GDBusCallFlags aFlags, gint aTimeout, GUnixFDList* aFDList,
+ GCancellable* aCancellable) {
+ auto promise = MakeRefPtr<DBusCallPromise::Private>(__func__);
+ g_dbus_proxy_call_with_unix_fd_list(
+ aProxy, aMethod, aArgs, aFlags, aTimeout, aFDList, aCancellable,
+ ProxyCallWithUnixFDListCallback, do_AddRef(promise).take());
+ return promise.forget();
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/AsyncDBus.h b/widget/gtk/AsyncDBus.h
new file mode 100644
index 0000000000..b1ac9d9039
--- /dev/null
+++ b/widget/gtk/AsyncDBus.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_AsyncDBus_h
+#define mozilla_widget_AsyncDBus_h
+
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/MozPromise.h"
+
+namespace mozilla::widget {
+
+using DBusProxyPromise = MozPromise<RefPtr<GDBusProxy>, GUniquePtr<GError>,
+ /* IsExclusive = */ true>;
+
+using DBusCallPromise = MozPromise<RefPtr<GVariant>, GUniquePtr<GError>,
+ /* IsExclusive = */ true>;
+
+RefPtr<DBusProxyPromise> CreateDBusProxyForBus(
+ GBusType aBusType, GDBusProxyFlags aFlags,
+ GDBusInterfaceInfo* aInterfaceInfo, const char* aName,
+ const char* aObjectPath, const char* aInterfaceName,
+ GCancellable* aCancellable = nullptr);
+
+RefPtr<DBusCallPromise> DBusProxyCall(GDBusProxy*, const char* aMethod,
+ GVariant* aArgs, GDBusCallFlags,
+ gint aTimeout = -1,
+ GCancellable* = nullptr);
+
+RefPtr<DBusCallPromise> DBusProxyCallWithUnixFDList(
+ GDBusProxy*, const char* aMethod, GVariant* aArgs, GDBusCallFlags,
+ gint aTimeout = -1, GUnixFDList* = nullptr, GCancellable* = nullptr);
+
+} // namespace mozilla::widget
+
+#endif
diff --git a/widget/gtk/AsyncGtkClipboardRequest.cpp b/widget/gtk/AsyncGtkClipboardRequest.cpp
new file mode 100644
index 0000000000..75801c698d
--- /dev/null
+++ b/widget/gtk/AsyncGtkClipboardRequest.cpp
@@ -0,0 +1,120 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "AsyncGtkClipboardRequest.h"
+
+namespace mozilla {
+
+AsyncGtkClipboardRequest::AsyncGtkClipboardRequest(ClipboardDataType aDataType,
+ int32_t aWhichClipboard,
+ const char* aMimeType) {
+ GtkClipboard* clipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+ mRequest = MakeUnique<Request>(aDataType);
+
+ switch (aDataType) {
+ case ClipboardDataType::Data:
+ LOGCLIP(" getting DATA MIME %s\n", aMimeType);
+ gtk_clipboard_request_contents(clipboard,
+ gdk_atom_intern(aMimeType, FALSE),
+ OnDataReceived, mRequest.get());
+ break;
+ case ClipboardDataType::Text:
+ LOGCLIP(" getting TEXT\n");
+ gtk_clipboard_request_text(clipboard, OnTextReceived, mRequest.get());
+ break;
+ case ClipboardDataType::Targets:
+ LOGCLIP(" getting TARGETS\n");
+ gtk_clipboard_request_contents(clipboard,
+ gdk_atom_intern("TARGETS", FALSE),
+ OnDataReceived, mRequest.get());
+ break;
+ }
+}
+
+void AsyncGtkClipboardRequest::OnDataReceived(GtkClipboard* clipboard,
+ GtkSelectionData* selection_data,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP("OnDataReceived(%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ static_cast<Request*>(data)->Complete(selection_data);
+}
+
+void AsyncGtkClipboardRequest::OnTextReceived(GtkClipboard* clipboard,
+ const gchar* text,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP("OnTextReceived(%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ static_cast<Request*>(data)->Complete(text);
+}
+
+void AsyncGtkClipboardRequest::Request::Complete(const void* aData) {
+ LOGCLIP("Request::Complete(), aData = %p, timedOut = %d\n", aData, mTimedOut);
+
+ if (mTimedOut) {
+ delete this;
+ return;
+ }
+
+ mData.emplace();
+
+ gint dataLength = 0;
+ if (mDataType == ClipboardDataType::Targets ||
+ mDataType == ClipboardDataType::Data) {
+ dataLength = gtk_selection_data_get_length((GtkSelectionData*)aData);
+ } else {
+ dataLength = aData ? strlen((const char*)aData) : 0;
+ }
+
+ // Negative size means no data or data error.
+ if (dataLength <= 0) {
+ LOGCLIP(" zero dataLength, quit.\n");
+ return;
+ }
+
+ switch (mDataType) {
+ case ClipboardDataType::Targets: {
+ LOGCLIP(" getting %d bytes of clipboard targets.\n", dataLength);
+ gint n_targets = 0;
+ GdkAtom* targets = nullptr;
+ if (!gtk_selection_data_get_targets((GtkSelectionData*)aData, &targets,
+ &n_targets) ||
+ !n_targets) {
+ // We failed to get targets
+ return;
+ }
+ mData->SetTargets(
+ ClipboardTargets{GUniquePtr<GdkAtom>(targets), uint32_t(n_targets)});
+ break;
+ }
+ case ClipboardDataType::Text: {
+ LOGCLIP(" getting %d bytes of text.\n", dataLength);
+ mData->SetText(Span(static_cast<const char*>(aData), dataLength));
+ LOGCLIP(" done, mClipboardData = %p\n", mData->AsSpan().data());
+ break;
+ }
+ case ClipboardDataType::Data: {
+ LOGCLIP(" getting %d bytes of data.\n", dataLength);
+ mData->SetData(Span(gtk_selection_data_get_data((GtkSelectionData*)aData),
+ dataLength));
+ LOGCLIP(" done, mClipboardData = %p\n", mData->AsSpan().data());
+ break;
+ }
+ }
+}
+
+AsyncGtkClipboardRequest::~AsyncGtkClipboardRequest() {
+ if (mRequest && mRequest->mData.isNothing()) {
+ mRequest->mTimedOut = true;
+ Unused << mRequest.release();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gtk/AsyncGtkClipboardRequest.h b/widget/gtk/AsyncGtkClipboardRequest.h
new file mode 100644
index 0000000000..1d72691560
--- /dev/null
+++ b/widget/gtk/AsyncGtkClipboardRequest.h
@@ -0,0 +1,61 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_AsyncGtkClipboardRequest_h
+#define mozilla_AsyncGtkClipboardRequest_h
+
+#include "nsClipboard.h"
+
+namespace mozilla {
+
+// An asynchronous clipboard request that we wait for synchronously by
+// spinning the event loop.
+class MOZ_STACK_CLASS AsyncGtkClipboardRequest {
+ // Heap-allocated object that we give GTK as a callback.
+ struct Request {
+ explicit Request(ClipboardDataType aDataType) : mDataType(aDataType) {}
+
+ void Complete(const void*);
+
+ const ClipboardDataType mDataType;
+ Maybe<ClipboardData> mData;
+ bool mTimedOut = false;
+ };
+
+ UniquePtr<Request> mRequest;
+
+ static void OnDataReceived(GtkClipboard*, GtkSelectionData*, gpointer);
+ static void OnTextReceived(GtkClipboard*, const gchar*, gpointer);
+
+ public:
+ // Launch a request for a particular GTK clipboard. The current status of the
+ // request can be observed by calling HasCompleted() and TakeResult().
+ AsyncGtkClipboardRequest(ClipboardDataType, int32_t aWhichClipboard,
+ const char* aMimeType = nullptr);
+
+ // Returns whether the request has been answered already.
+ bool HasCompleted() const { return mRequest->mData.isSome(); }
+
+ // Takes the result from the current request if completed, or a
+ // default-constructed data otherwise. The destructor will take care of
+ // flagging the request as timed out in that case.
+ ClipboardData TakeResult() {
+ if (!HasCompleted()) {
+ return {};
+ }
+ auto request = std::move(mRequest);
+ return request->mData.extract();
+ }
+
+ // If completed, frees the request if needed. Otherwise, marks it as a timed
+ // out request so that when it completes the Request object is properly
+ // freed.
+ ~AsyncGtkClipboardRequest();
+};
+
+}; // namespace mozilla
+
+#endif
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..b7908a43d4
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.cpp
@@ -0,0 +1,51 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorWidgetChild.h"
+#include "mozilla/Unused.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+CompositorWidgetChild::CompositorWidgetChild(
+ RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData&)
+ : mVsyncDispatcher(aVsyncDispatcher), mVsyncObserver(aVsyncObserver) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ MOZ_ASSERT(!gfxPlatform::IsHeadless());
+}
+
+CompositorWidgetChild::~CompositorWidgetChild() = default;
+
+bool CompositorWidgetChild::Initialize() { return true; }
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvObserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(mVsyncObserver);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetChild::RecvUnobserveVsync() {
+ mVsyncDispatcher->SetCompositorVsyncObserver(nullptr);
+ return IPC_OK();
+}
+
+void CompositorWidgetChild::NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ Unused << SendNotifyClientSizeChanged(aClientSize);
+}
+
+void CompositorWidgetChild::DisableRendering() {
+ Unused << SendDisableRendering();
+}
+
+void CompositorWidgetChild::EnableRendering(const uintptr_t aXWindow,
+ const bool aShaped) {
+ Unused << SendEnableRendering(aXWindow, aShaped);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetChild.h b/widget/gtk/CompositorWidgetChild.h
new file mode 100644
index 0000000000..b1cad75da3
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef widget_gtk_CompositorWidgetChild_h
+#define widget_gtk_CompositorWidgetChild_h
+
+#include "GtkCompositorWidget.h"
+#include "mozilla/widget/PCompositorWidgetChild.h"
+#include "mozilla/widget/CompositorWidgetVsyncObserver.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetChild final : public PCompositorWidgetChild,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ CompositorWidgetChild(RefPtr<CompositorVsyncDispatcher> aVsyncDispatcher,
+ RefPtr<CompositorWidgetVsyncObserver> aVsyncObserver,
+ const CompositorWidgetInitData&);
+ ~CompositorWidgetChild() override;
+
+ bool Initialize();
+
+ mozilla::ipc::IPCResult RecvObserveVsync() override;
+ mozilla::ipc::IPCResult RecvUnobserveVsync() override;
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+ void DisableRendering() override;
+ void EnableRendering(const uintptr_t aXWindow, const bool aShaped) override;
+
+ private:
+ RefPtr<CompositorVsyncDispatcher> mVsyncDispatcher;
+ RefPtr<CompositorWidgetVsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetChild_h
diff --git a/widget/gtk/CompositorWidgetParent.cpp b/widget/gtk/CompositorWidgetParent.cpp
new file mode 100644
index 0000000000..998614622e
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.cpp
@@ -0,0 +1,54 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "CompositorWidgetParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+
+namespace mozilla::widget {
+
+CompositorWidgetParent::CompositorWidgetParent(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions)
+ : GtkCompositorWidget(aInitData.get_GtkCompositorWidgetInitData(), aOptions,
+ nullptr) {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+}
+
+CompositorWidgetParent::~CompositorWidgetParent() = default;
+
+void CompositorWidgetParent::ObserveVsync(VsyncObserver* aObserver) {
+ if (aObserver) {
+ Unused << SendObserveVsync();
+ } else {
+ Unused << SendUnobserveVsync();
+ }
+ mVsyncObserver = aObserver;
+}
+
+RefPtr<VsyncObserver> CompositorWidgetParent::GetVsyncObserver() const {
+ MOZ_ASSERT(XRE_GetProcessType() == GeckoProcessType_GPU);
+ return mVsyncObserver;
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvNotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ NotifyClientSizeChanged(aClientSize);
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvDisableRendering() {
+ DisableRendering();
+ return IPC_OK();
+}
+
+mozilla::ipc::IPCResult CompositorWidgetParent::RecvEnableRendering(
+ const uintptr_t& aXWindow, const bool& aShaped) {
+ EnableRendering(aXWindow, aShaped);
+ return IPC_OK();
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h
new file mode 100644
index 0000000000..2bbc70af3e
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.h
@@ -0,0 +1,42 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef widget_gtk_CompositorWidgetParent_h
+#define widget_gtk_CompositorWidgetParent_h
+
+#include "GtkCompositorWidget.h"
+#include "mozilla/VsyncDispatcher.h"
+#include "mozilla/widget/PCompositorWidgetParent.h"
+
+namespace mozilla {
+namespace widget {
+
+class CompositorWidgetParent final : public PCompositorWidgetParent,
+ public GtkCompositorWidget {
+ public:
+ explicit CompositorWidgetParent(const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions);
+ ~CompositorWidgetParent() override;
+
+ void ActorDestroy(ActorDestroyReason aWhy) override {}
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+ RefPtr<VsyncObserver> GetVsyncObserver() const override;
+
+ mozilla::ipc::IPCResult RecvNotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) override;
+
+ mozilla::ipc::IPCResult RecvDisableRendering() override;
+ mozilla::ipc::IPCResult RecvEnableRendering(const uintptr_t& aXWindow,
+ const bool& aShaped) override;
+
+ private:
+ RefPtr<VsyncObserver> mVsyncObserver;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_CompositorWidgetParent_h
diff --git a/widget/gtk/DMABufLibWrapper.cpp b/widget/gtk/DMABufLibWrapper.cpp
new file mode 100644
index 0000000000..3db0219f88
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.cpp
@@ -0,0 +1,346 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DMABufLibWrapper.h"
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+#include "base/message_loop.h" // for MessageLoop
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "WidgetUtilsGtk.h"
+#include "gfxConfig.h"
+#include "nsIGfxInfo.h"
+#include "mozilla/Components.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+#include <mutex>
+#include <unistd.h>
+#include "gbm.h"
+
+using namespace mozilla::gfx;
+
+namespace mozilla {
+namespace widget {
+
+bool sUseWebGLDmabufBackend = true;
+
+#define GBMLIB_NAME "libgbm.so.1"
+#define DRMLIB_NAME "libdrm.so.2"
+
+// Use static lock to protect dri operation as
+// gbm_dri.c is not thread safe.
+// https://gitlab.freedesktop.org/mesa/mesa/-/issues/4422
+mozilla::StaticMutex GbmLib::sDRILock MOZ_UNANNOTATED;
+
+bool GbmLib::sLoaded = false;
+void* GbmLib::sGbmLibHandle = nullptr;
+void* GbmLib::sXf86DrmLibHandle = nullptr;
+CreateDeviceFunc GbmLib::sCreateDevice;
+DestroyDeviceFunc GbmLib::sDestroyDevice;
+CreateFunc GbmLib::sCreate;
+CreateWithModifiersFunc GbmLib::sCreateWithModifiers;
+GetModifierFunc GbmLib::sGetModifier;
+GetStrideFunc GbmLib::sGetStride;
+GetFdFunc GbmLib::sGetFd;
+DestroyFunc GbmLib::sDestroy;
+MapFunc GbmLib::sMap;
+UnmapFunc GbmLib::sUnmap;
+GetPlaneCountFunc GbmLib::sGetPlaneCount;
+GetHandleForPlaneFunc GbmLib::sGetHandleForPlane;
+GetStrideForPlaneFunc GbmLib::sGetStrideForPlane;
+GetOffsetFunc GbmLib::sGetOffset;
+DeviceIsFormatSupportedFunc GbmLib::sDeviceIsFormatSupported;
+DrmPrimeHandleToFDFunc GbmLib::sDrmPrimeHandleToFD;
+CreateSurfaceFunc GbmLib::sCreateSurface;
+DestroySurfaceFunc GbmLib::sDestroySurface;
+
+bool GbmLib::IsLoaded() {
+ return sCreateDevice != nullptr && sDestroyDevice != nullptr &&
+ sCreate != nullptr && sCreateWithModifiers != nullptr &&
+ sGetModifier != nullptr && sGetStride != nullptr &&
+ sGetFd != nullptr && sDestroy != nullptr && sMap != nullptr &&
+ sUnmap != nullptr && sGetPlaneCount != nullptr &&
+ sGetHandleForPlane != nullptr && sGetStrideForPlane != nullptr &&
+ sGetOffset != nullptr && sDeviceIsFormatSupported != nullptr &&
+ sDrmPrimeHandleToFD != nullptr && sCreateSurface != nullptr &&
+ sDestroySurface != nullptr;
+}
+
+bool GbmLib::Load() {
+ static bool sTriedToLoad = false;
+ if (sTriedToLoad) {
+ return sLoaded;
+ }
+
+ sTriedToLoad = true;
+
+ MOZ_ASSERT(!sGbmLibHandle);
+ MOZ_ASSERT(!sLoaded);
+
+ LOGDMABUF(("Loading DMABuf system library %s ...\n", GBMLIB_NAME));
+
+ sGbmLibHandle = dlopen(GBMLIB_NAME, RTLD_LAZY | RTLD_LOCAL);
+ if (!sGbmLibHandle) {
+ LOGDMABUF(("Failed to load %s, dmabuf isn't available.\n", GBMLIB_NAME));
+ return false;
+ }
+
+ sCreateDevice = (CreateDeviceFunc)dlsym(sGbmLibHandle, "gbm_create_device");
+ sDestroyDevice =
+ (DestroyDeviceFunc)dlsym(sGbmLibHandle, "gbm_device_destroy");
+ sCreate = (CreateFunc)dlsym(sGbmLibHandle, "gbm_bo_create");
+ sCreateWithModifiers = (CreateWithModifiersFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_create_with_modifiers");
+ sGetModifier = (GetModifierFunc)dlsym(sGbmLibHandle, "gbm_bo_get_modifier");
+ sGetStride = (GetStrideFunc)dlsym(sGbmLibHandle, "gbm_bo_get_stride");
+ sGetFd = (GetFdFunc)dlsym(sGbmLibHandle, "gbm_bo_get_fd");
+ sDestroy = (DestroyFunc)dlsym(sGbmLibHandle, "gbm_bo_destroy");
+ sMap = (MapFunc)dlsym(sGbmLibHandle, "gbm_bo_map");
+ sUnmap = (UnmapFunc)dlsym(sGbmLibHandle, "gbm_bo_unmap");
+ sGetPlaneCount =
+ (GetPlaneCountFunc)dlsym(sGbmLibHandle, "gbm_bo_get_plane_count");
+ sGetHandleForPlane = (GetHandleForPlaneFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_get_handle_for_plane");
+ sGetStrideForPlane = (GetStrideForPlaneFunc)dlsym(
+ sGbmLibHandle, "gbm_bo_get_stride_for_plane");
+ sGetOffset = (GetOffsetFunc)dlsym(sGbmLibHandle, "gbm_bo_get_offset");
+ sDeviceIsFormatSupported = (DeviceIsFormatSupportedFunc)dlsym(
+ sGbmLibHandle, "gbm_device_is_format_supported");
+ sCreateSurface =
+ (CreateSurfaceFunc)dlsym(sGbmLibHandle, "gbm_surface_create");
+ sDestroySurface =
+ (DestroySurfaceFunc)dlsym(sGbmLibHandle, "gbm_surface_destroy");
+
+ sXf86DrmLibHandle = dlopen(DRMLIB_NAME, RTLD_LAZY | RTLD_LOCAL);
+ if (!sXf86DrmLibHandle) {
+ LOGDMABUF(("Failed to load %s, dmabuf isn't available.\n", DRMLIB_NAME));
+ return false;
+ }
+ sDrmPrimeHandleToFD =
+ (DrmPrimeHandleToFDFunc)dlsym(sXf86DrmLibHandle, "drmPrimeHandleToFD");
+ sLoaded = IsLoaded();
+ if (!sLoaded) {
+ LOGDMABUF(("Failed to load all symbols from %s\n", GBMLIB_NAME));
+ }
+ return sLoaded;
+}
+
+int DMABufDevice::GetDmabufFD(uint32_t aGEMHandle) {
+ int fd;
+ return GbmLib::DrmPrimeHandleToFD(mDRMFd, aGEMHandle, 0, &fd) < 0 ? -1 : fd;
+}
+
+gbm_device* DMABufDevice::GetGbmDevice() {
+ std::call_once(mFlagGbmDevice, [&] {
+ mGbmDevice = (mDRMFd != -1) ? GbmLib::CreateDevice(mDRMFd) : nullptr;
+ });
+ return mGbmDevice;
+}
+
+int DMABufDevice::OpenDRMFd() { return open(mDrmRenderNode.get(), O_RDWR); }
+
+bool DMABufDevice::IsEnabled(nsACString& aFailureId) {
+ if (mDRMFd == -1) {
+ aFailureId = mFailureId;
+ }
+ return mDRMFd != -1;
+}
+
+DMABufDevice::DMABufDevice()
+ : mXRGBFormat({true, false, GBM_FORMAT_XRGB8888, {}}),
+ mARGBFormat({true, true, GBM_FORMAT_ARGB8888, {}}) {
+ Configure();
+}
+
+DMABufDevice::~DMABufDevice() {
+ if (mGbmDevice) {
+ GbmLib::DestroyDevice(mGbmDevice);
+ mGbmDevice = nullptr;
+ }
+ if (mDRMFd != -1) {
+ close(mDRMFd);
+ mDRMFd = -1;
+ }
+}
+
+void DMABufDevice::Configure() {
+ LOGDMABUF(("DMABufDevice::Configure()"));
+
+ if (!GbmLib::IsAvailable()) {
+ LOGDMABUF(("GbmLib is not available!"));
+ mFailureId = "FEATURE_FAILURE_NO_LIBGBM";
+ return;
+ }
+
+ mDrmRenderNode = nsAutoCString(getenv("MOZ_DRM_DEVICE"));
+ if (mDrmRenderNode.IsEmpty()) {
+ mDrmRenderNode.Assign(gfx::gfxVars::DrmRenderDevice());
+ }
+ if (mDrmRenderNode.IsEmpty()) {
+ LOGDMABUF(("We're missing DRM render device!\n"));
+ mFailureId = "FEATURE_FAILURE_NO_DRM_DEVICE";
+ return;
+ }
+
+ LOGDMABUF(("Using DRM device %s", mDrmRenderNode.get()));
+ mDRMFd = open(mDrmRenderNode.get(), O_RDWR);
+ if (mDRMFd < 0) {
+ LOGDMABUF(("Failed to open drm render node %s error %s\n",
+ mDrmRenderNode.get(), strerror(errno)));
+ mFailureId = "FEATURE_FAILURE_NO_DRM_DEVICE";
+ return;
+ }
+
+#ifdef MOZ_WAYLAND
+ LoadFormatModifiers();
+#endif
+
+ LOGDMABUF(("DMABuf is enabled"));
+}
+
+#ifdef NIGHTLY_BUILD
+bool DMABufDevice::IsDMABufTexturesEnabled() {
+ return gfx::gfxVars::UseDMABuf() &&
+ StaticPrefs::widget_dmabuf_textures_enabled();
+}
+#else
+bool DMABufDevice::IsDMABufTexturesEnabled() { return false; }
+#endif
+bool DMABufDevice::IsDMABufWebGLEnabled() {
+ LOGDMABUF(
+ ("DMABufDevice::IsDMABufWebGLEnabled: UseDMABuf %d "
+ "sUseWebGLDmabufBackend %d "
+ "widget_dmabuf_webgl_enabled %d\n",
+ gfx::gfxVars::UseDMABuf(), sUseWebGLDmabufBackend,
+ StaticPrefs::widget_dmabuf_webgl_enabled()));
+ return gfx::gfxVars::UseDMABuf() && sUseWebGLDmabufBackend &&
+ StaticPrefs::widget_dmabuf_webgl_enabled();
+}
+
+#ifdef MOZ_WAYLAND
+void DMABufDevice::SetModifiersToGfxVars() {
+ gfxVars::SetDMABufModifiersXRGB(mXRGBFormat.mModifiers);
+ gfxVars::SetDMABufModifiersARGB(mARGBFormat.mModifiers);
+}
+
+void DMABufDevice::GetModifiersFromGfxVars() {
+ mXRGBFormat.mModifiers = gfxVars::DMABufModifiersXRGB().Clone();
+ mARGBFormat.mModifiers = gfxVars::DMABufModifiersARGB().Clone();
+}
+#endif
+
+void DMABufDevice::DisableDMABufWebGL() { sUseWebGLDmabufBackend = false; }
+
+GbmFormat* DMABufDevice::GetGbmFormat(bool aHasAlpha) {
+ GbmFormat* format = aHasAlpha ? &mARGBFormat : &mXRGBFormat;
+ return format->mIsSupported ? format : nullptr;
+}
+
+#ifdef MOZ_WAYLAND
+void DMABufDevice::AddFormatModifier(bool aHasAlpha, int aFormat,
+ uint32_t mModifierHi,
+ uint32_t mModifierLo) {
+ GbmFormat* format = aHasAlpha ? &mARGBFormat : &mXRGBFormat;
+ format->mIsSupported = true;
+ format->mHasAlpha = aHasAlpha;
+ format->mFormat = aFormat;
+ format->mModifiers.AppendElement(((uint64_t)mModifierHi << 32) | mModifierLo);
+}
+
+static void dmabuf_modifiers(void* data,
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf,
+ uint32_t format, uint32_t modifier_hi,
+ uint32_t modifier_lo) {
+ // skip modifiers marked as invalid
+ if (modifier_hi == (DRM_FORMAT_MOD_INVALID >> 32) &&
+ modifier_lo == (DRM_FORMAT_MOD_INVALID & 0xffffffff)) {
+ return;
+ }
+
+ auto* device = static_cast<DMABufDevice*>(data);
+ switch (format) {
+ case GBM_FORMAT_ARGB8888:
+ device->AddFormatModifier(true, format, modifier_hi, modifier_lo);
+ break;
+ case GBM_FORMAT_XRGB8888:
+ device->AddFormatModifier(false, format, modifier_hi, modifier_lo);
+ break;
+ default:
+ break;
+ }
+}
+
+static void dmabuf_format(void* data,
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf,
+ uint32_t format) {
+ // XXX: deprecated
+}
+
+static const struct zwp_linux_dmabuf_v1_listener dmabuf_listener = {
+ dmabuf_format, dmabuf_modifiers};
+
+static void global_registry_handler(void* data, wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ if (strcmp(interface, "zwp_linux_dmabuf_v1") == 0 && version > 2) {
+ auto* dmabuf = WaylandRegistryBind<zwp_linux_dmabuf_v1>(
+ registry, id, &zwp_linux_dmabuf_v1_interface, 3);
+ LOGDMABUF(("zwp_linux_dmabuf_v1 is available."));
+ zwp_linux_dmabuf_v1_add_listener(dmabuf, &dmabuf_listener, data);
+ } else if (strcmp(interface, "wl_drm") == 0) {
+ LOGDMABUF(("wl_drm is available."));
+ }
+}
+
+static void global_registry_remover(void* data, wl_registry* registry,
+ uint32_t id) {}
+
+static const struct wl_registry_listener registry_listener = {
+ global_registry_handler, global_registry_remover};
+
+void DMABufDevice::LoadFormatModifiers() {
+ if (!GdkIsWaylandDisplay()) {
+ return;
+ }
+ if (XRE_IsParentProcess()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ wl_display* display = WaylandDisplayGetWLDisplay();
+ wl_registry* registry = wl_display_get_registry(display);
+ wl_registry_add_listener(registry, &registry_listener, this);
+ wl_display_roundtrip(display);
+ wl_display_roundtrip(display);
+ wl_registry_destroy(registry);
+ SetModifiersToGfxVars();
+ } else {
+ GetModifiersFromGfxVars();
+ }
+}
+#endif
+
+DMABufDevice* GetDMABufDevice() {
+ static StaticAutoPtr<DMABufDevice> sDmaBufDevice;
+ static std::once_flag onceFlag;
+ std::call_once(onceFlag, [] {
+ sDmaBufDevice = new DMABufDevice();
+ if (NS_IsMainThread()) {
+ ClearOnShutdown(&sDmaBufDevice);
+ } else {
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "ClearDmaBufDevice", [] { ClearOnShutdown(&sDmaBufDevice); }));
+ }
+ });
+ return sDmaBufDevice.get();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/DMABufLibWrapper.h b/widget/gtk/DMABufLibWrapper.h
new file mode 100644
index 0000000000..9723083ad8
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.h
@@ -0,0 +1,231 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MOZ_DMABUF_LIB_WRAPPER_H__
+#define __MOZ_DMABUF_LIB_WRAPPER_H__
+
+#include "mozilla/widget/gbm.h"
+#include "mozilla/StaticMutex.h"
+#include <mutex>
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gDmabufLog;
+# define LOGDMABUF(args) MOZ_LOG(gDmabufLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGDMABUF(args)
+#endif /* MOZ_LOGGING */
+
+#ifndef DRM_FORMAT_MOD_INVALID
+# define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
+#endif
+
+namespace mozilla {
+namespace widget {
+
+typedef struct gbm_device* (*CreateDeviceFunc)(int);
+typedef void (*DestroyDeviceFunc)(struct gbm_device*);
+typedef struct gbm_bo* (*CreateFunc)(struct gbm_device*, uint32_t, uint32_t,
+ uint32_t, uint32_t);
+typedef struct gbm_bo* (*CreateWithModifiersFunc)(struct gbm_device*, uint32_t,
+ uint32_t, uint32_t,
+ const uint64_t*,
+ const unsigned int);
+typedef uint64_t (*GetModifierFunc)(struct gbm_bo*);
+typedef uint32_t (*GetStrideFunc)(struct gbm_bo*);
+typedef int (*GetFdFunc)(struct gbm_bo*);
+typedef void (*DestroyFunc)(struct gbm_bo*);
+typedef void* (*MapFunc)(struct gbm_bo*, uint32_t, uint32_t, uint32_t, uint32_t,
+ uint32_t, uint32_t*, void**);
+typedef void (*UnmapFunc)(struct gbm_bo*, void*);
+typedef int (*GetPlaneCountFunc)(struct gbm_bo*);
+typedef union gbm_bo_handle (*GetHandleForPlaneFunc)(struct gbm_bo*, int);
+typedef uint32_t (*GetStrideForPlaneFunc)(struct gbm_bo*, int);
+typedef uint32_t (*GetOffsetFunc)(struct gbm_bo*, int);
+typedef int (*DeviceIsFormatSupportedFunc)(struct gbm_device*, uint32_t,
+ uint32_t);
+typedef int (*DrmPrimeHandleToFDFunc)(int, uint32_t, uint32_t, int*);
+typedef struct gbm_surface* (*CreateSurfaceFunc)(struct gbm_device*, uint32_t,
+ uint32_t, uint32_t, uint32_t);
+typedef void (*DestroySurfaceFunc)(struct gbm_surface*);
+
+class GbmLib {
+ public:
+ static bool IsAvailable() { return sLoaded || Load(); }
+ static bool IsModifierAvailable();
+
+ static struct gbm_device* CreateDevice(int fd) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sCreateDevice(fd);
+ };
+ static void DestroyDevice(struct gbm_device* gdm) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sDestroyDevice(gdm);
+ };
+ static struct gbm_bo* Create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format,
+ uint32_t flags) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sCreate(gbm, width, height, format, flags);
+ }
+ static void Destroy(struct gbm_bo* bo) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ sDestroy(bo);
+ }
+ static uint32_t GetStride(struct gbm_bo* bo) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetStride(bo);
+ }
+ static int GetFd(struct gbm_bo* bo) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetFd(bo);
+ }
+ static void* Map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height, uint32_t flags, uint32_t* stride,
+ void** map_data) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sMap(bo, x, y, width, height, flags, stride, map_data);
+ }
+ static void Unmap(struct gbm_bo* bo, void* map_data) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ sUnmap(bo, map_data);
+ }
+ static struct gbm_bo* CreateWithModifiers(struct gbm_device* gbm,
+ uint32_t width, uint32_t height,
+ uint32_t format,
+ const uint64_t* modifiers,
+ const unsigned int count) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sCreateWithModifiers(gbm, width, height, format, modifiers, count);
+ }
+ static uint64_t GetModifier(struct gbm_bo* bo) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetModifier(bo);
+ }
+ static int GetPlaneCount(struct gbm_bo* bo) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetPlaneCount(bo);
+ }
+ static union gbm_bo_handle GetHandleForPlane(struct gbm_bo* bo, int plane) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetHandleForPlane(bo, plane);
+ }
+ static uint32_t GetStrideForPlane(struct gbm_bo* bo, int plane) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetStrideForPlane(bo, plane);
+ }
+ static uint32_t GetOffset(struct gbm_bo* bo, int plane) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sGetOffset(bo, plane);
+ }
+ static int DeviceIsFormatSupported(struct gbm_device* gbm, uint32_t format,
+ uint32_t usage) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sDeviceIsFormatSupported(gbm, format, usage);
+ }
+ static int DrmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags,
+ int* prime_fd) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sDrmPrimeHandleToFD(fd, handle, flags, prime_fd);
+ }
+ static struct gbm_surface* CreateSurface(struct gbm_device* gbm,
+ uint32_t width, uint32_t height,
+ uint32_t format, uint32_t flags) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sCreateSurface(gbm, width, height, format, flags);
+ }
+ static void DestroySurface(struct gbm_surface* surface) {
+ StaticMutexAutoLock lockDRI(sDRILock);
+ return sDestroySurface(surface);
+ }
+
+ private:
+ static bool Load();
+ static bool IsLoaded();
+
+ static CreateDeviceFunc sCreateDevice;
+ static DestroyDeviceFunc sDestroyDevice;
+ static CreateFunc sCreate;
+ static CreateWithModifiersFunc sCreateWithModifiers;
+ static GetModifierFunc sGetModifier;
+ static GetStrideFunc sGetStride;
+ static GetFdFunc sGetFd;
+ static DestroyFunc sDestroy;
+ static MapFunc sMap;
+ static UnmapFunc sUnmap;
+ static GetPlaneCountFunc sGetPlaneCount;
+ static GetHandleForPlaneFunc sGetHandleForPlane;
+ static GetStrideForPlaneFunc sGetStrideForPlane;
+ static GetOffsetFunc sGetOffset;
+ static DeviceIsFormatSupportedFunc sDeviceIsFormatSupported;
+ static DrmPrimeHandleToFDFunc sDrmPrimeHandleToFD;
+ static CreateSurfaceFunc sCreateSurface;
+ static DestroySurfaceFunc sDestroySurface;
+ static bool sLoaded;
+
+ static void* sGbmLibHandle;
+ static void* sXf86DrmLibHandle;
+ static mozilla::StaticMutex sDRILock MOZ_UNANNOTATED;
+};
+
+struct GbmFormat {
+ bool mIsSupported;
+ bool mHasAlpha;
+ int mFormat;
+ nsTArray<uint64_t> mModifiers;
+};
+
+class DMABufDevice {
+ public:
+ DMABufDevice();
+ ~DMABufDevice();
+
+ int OpenDRMFd();
+ gbm_device* GetGbmDevice();
+ int GetDmabufFD(uint32_t aGEMHandle);
+
+ bool IsEnabled(nsACString& aFailureId);
+
+ // Use dmabuf for WebRender general web content
+ static bool IsDMABufTexturesEnabled();
+ // Use dmabuf for WebGL content
+ static bool IsDMABufWebGLEnabled();
+ static void DisableDMABufWebGL();
+
+#ifdef MOZ_WAYLAND
+ void AddFormatModifier(bool aHasAlpha, int aFormat, uint32_t mModifierHi,
+ uint32_t mModifierLo);
+#endif
+ GbmFormat* GetGbmFormat(bool aHasAlpha);
+
+ private:
+ void Configure();
+#ifdef MOZ_WAYLAND
+ void LoadFormatModifiers();
+ void SetModifiersToGfxVars();
+ void GetModifiersFromGfxVars();
+#endif
+
+ private:
+ GbmFormat mXRGBFormat;
+ GbmFormat mARGBFormat;
+
+ int mDRMFd = -1;
+ std::once_flag mFlagGbmDevice;
+ gbm_device* mGbmDevice = nullptr;
+ const char* mFailureId = nullptr;
+ nsAutoCString mDrmRenderNode;
+};
+
+DMABufDevice* GetDMABufDevice();
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // __MOZ_DMABUF_LIB_WRAPPER_H__
diff --git a/widget/gtk/DMABufSurface.cpp b/widget/gtk/DMABufSurface.cpp
new file mode 100644
index 0000000000..b8ec593e30
--- /dev/null
+++ b/widget/gtk/DMABufSurface.cpp
@@ -0,0 +1,1729 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "DMABufSurface.h"
+#include "DMABufLibWrapper.h"
+
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+#include <fcntl.h>
+#include <getopt.h>
+#include <signal.h>
+#include <stdbool.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/time.h>
+#include <dlfcn.h>
+#include <sys/mman.h>
+#ifdef HAVE_EVENTFD
+# include <sys/eventfd.h>
+#endif
+#include <poll.h>
+#ifdef HAVE_SYSIOCCOM_H
+# include <sys/ioccom.h>
+#endif
+#include <sys/ioctl.h>
+
+#include "mozilla/widget/gbm.h"
+#include "mozilla/widget/va_drmcommon.h"
+#include "YCbCrUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "GLContextTypes.h" // for GLContext, etc
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.h"
+#include "GLBlitHelper.h"
+#include "GLReadTexImageHelper.h"
+#include "nsGtkUtils.h"
+
+#include "mozilla/layers/LayersSurfaces.h"
+#include "mozilla/ScopeExit.h"
+
+/*
+TODO:
+DRM device selection:
+https://lists.freedesktop.org/archives/wayland-devel/2018-November/039660.html
+*/
+
+/* C++ / C typecast macros for special EGL handle values */
+#if defined(__cplusplus)
+# define EGL_CAST(type, value) (static_cast<type>(value))
+#else
+# define EGL_CAST(type, value) ((type)(value))
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using namespace mozilla::gl;
+using namespace mozilla::layers;
+using namespace mozilla::gfx;
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+static LazyLogModule gDmabufRefLog("DmabufRef");
+# define LOGDMABUFREF(args) \
+ MOZ_LOG(gDmabufRefLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGDMABUFREF(args)
+#endif /* MOZ_LOGGING */
+
+#define BUFFER_FLAGS 0
+
+static RefPtr<GLContext> sSnapshotContext;
+static StaticMutex sSnapshotContextMutex MOZ_UNANNOTATED;
+static Atomic<int> gNewSurfaceUID(1);
+
+RefPtr<GLContext> ClaimSnapshotGLContext() {
+ if (!sSnapshotContext) {
+ nsCString discardFailureId;
+ sSnapshotContext = GLContextProvider::CreateHeadless({}, &discardFailureId);
+ if (!sSnapshotContext) {
+ LOGDMABUF(
+ ("ClaimSnapshotGLContext: Failed to create snapshot GLContext."));
+ return nullptr;
+ }
+ sSnapshotContext->mOwningThreadId = Nothing(); // No singular owner.
+ }
+ if (!sSnapshotContext->MakeCurrent()) {
+ LOGDMABUF(("ClaimSnapshotGLContext: Failed to make GLContext current."));
+ return nullptr;
+ }
+ return sSnapshotContext;
+}
+
+void ReturnSnapshotGLContext(RefPtr<GLContext> aGLContext) {
+ // direct eglMakeCurrent() call breaks current context caching so make sure
+ // it's not used.
+ MOZ_ASSERT(!aGLContext->mUseTLSIsCurrent);
+ if (!aGLContext->IsCurrent()) {
+ LOGDMABUF(("ReturnSnapshotGLContext() failed, is not current!"));
+ return;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+ egl->fMakeCurrent(EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT);
+}
+
+bool DMABufSurface::IsGlobalRefSet() const {
+ if (!mGlobalRefCountFd) {
+ return false;
+ }
+ struct pollfd pfd;
+ pfd.fd = mGlobalRefCountFd;
+ pfd.events = POLLIN;
+ return poll(&pfd, 1, 0) == 1;
+}
+
+void DMABufSurface::GlobalRefRelease() {
+#ifdef HAVE_EVENTFD
+ if (!mGlobalRefCountFd) {
+ return;
+ }
+ LOGDMABUFREF(("DMABufSurface::GlobalRefRelease UID %d", mUID));
+ uint64_t counter;
+ if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ if (errno == EAGAIN) {
+ LOGDMABUFREF(
+ (" GlobalRefRelease failed: already zero reference! UID %d", mUID));
+ }
+ // EAGAIN means the refcount is already zero. It happens when we release
+ // last reference to the surface.
+ if (errno != EAGAIN) {
+ NS_WARNING(nsPrintfCString("Failed to unref dmabuf global ref count: %s",
+ strerror(errno))
+ .get());
+ }
+ }
+#endif
+}
+
+void DMABufSurface::GlobalRefAdd() {
+#ifdef HAVE_EVENTFD
+ LOGDMABUFREF(("DMABufSurface::GlobalRefAdd UID %d", mUID));
+ MOZ_DIAGNOSTIC_ASSERT(mGlobalRefCountFd);
+ uint64_t counter = 1;
+ if (write(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ NS_WARNING(nsPrintfCString("Failed to ref dmabuf global ref count: %s",
+ strerror(errno))
+ .get());
+ }
+#endif
+}
+
+void DMABufSurface::GlobalRefCountCreate() {
+#ifdef HAVE_EVENTFD
+ LOGDMABUFREF(("DMABufSurface::GlobalRefCountCreate UID %d", mUID));
+ MOZ_DIAGNOSTIC_ASSERT(!mGlobalRefCountFd);
+ // Create global ref count initialized to 0,
+ // i.e. is not referenced after create.
+ mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);
+ if (mGlobalRefCountFd < 0) {
+ NS_WARNING(nsPrintfCString("Failed to create dmabuf global ref count: %s",
+ strerror(errno))
+ .get());
+ mGlobalRefCountFd = 0;
+ return;
+ }
+#endif
+}
+
+void DMABufSurface::GlobalRefCountImport(int aFd) {
+#ifdef HAVE_EVENTFD
+ mGlobalRefCountFd = aFd;
+ if (mGlobalRefCountFd) {
+ LOGDMABUFREF(("DMABufSurface::GlobalRefCountImport UID %d", mUID));
+ GlobalRefAdd();
+ }
+#endif
+}
+
+int DMABufSurface::GlobalRefCountExport() {
+#ifdef MOZ_LOGGING
+ if (mGlobalRefCountFd) {
+ LOGDMABUFREF(("DMABufSurface::GlobalRefCountExport UID %d", mUID));
+ }
+#endif
+ return mGlobalRefCountFd;
+}
+
+void DMABufSurface::GlobalRefCountDelete() {
+ if (mGlobalRefCountFd) {
+ LOGDMABUFREF(("DMABufSurface::GlobalRefCountDelete UID %d", mUID));
+ close(mGlobalRefCountFd);
+ mGlobalRefCountFd = 0;
+ }
+}
+
+void DMABufSurface::ReleaseDMABuf() {
+ LOGDMABUF(("DMABufSurface::ReleaseDMABuf() UID %d", mUID));
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ Unmap(i);
+ }
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ CloseFileDescriptors(lockFD, /* aForceClose */ true);
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mGbmBufferObject[i]) {
+ GbmLib::Destroy(mGbmBufferObject[i]);
+ mGbmBufferObject[i] = nullptr;
+ }
+ }
+ mBufferPlaneCount = 0;
+}
+
+DMABufSurface::DMABufSurface(SurfaceType aSurfaceType)
+ : mSurfaceType(aSurfaceType),
+ mBufferPlaneCount(0),
+ mDrmFormats(),
+ mStrides(),
+ mOffsets(),
+ mGbmBufferObject(),
+ mMappedRegion(),
+ mMappedRegionStride(),
+ mSyncFd(-1),
+ mSync(nullptr),
+ mGlobalRefCountFd(0),
+ mUID(gNewSurfaceUID++),
+ mSurfaceLock("DMABufSurface") {
+ for (auto& slot : mDmabufFds) {
+ slot = -1;
+ }
+ for (auto& modifier : mBufferModifiers) {
+ modifier = DRM_FORMAT_MOD_INVALID;
+ }
+}
+
+DMABufSurface::~DMABufSurface() {
+ FenceDelete();
+ GlobalRefRelease();
+ GlobalRefCountDelete();
+}
+
+already_AddRefed<DMABufSurface> DMABufSurface::CreateDMABufSurface(
+ const mozilla::layers::SurfaceDescriptor& aDesc) {
+ const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
+ RefPtr<DMABufSurface> surf;
+
+ switch (desc.bufferType()) {
+ case SURFACE_RGBA:
+ surf = new DMABufSurfaceRGBA();
+ break;
+ case SURFACE_NV12:
+ case SURFACE_YUV420:
+ surf = new DMABufSurfaceYUV();
+ break;
+ default:
+ return nullptr;
+ }
+
+ if (!surf->Create(desc)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+void DMABufSurface::FenceDelete() {
+ if (mSyncFd > 0) {
+ close(mSyncFd);
+ mSyncFd = -1;
+ }
+
+ if (!mGL) {
+ return;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mSync) {
+ egl->fDestroySync(mSync);
+ mSync = nullptr;
+ }
+}
+
+void DMABufSurface::FenceSet() {
+ if (!mGL || !mGL->MakeCurrent()) {
+ MOZ_DIAGNOSTIC_ASSERT(mGL,
+ "DMABufSurface::FenceSet(): missing GL context!");
+ return;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (egl->IsExtensionSupported(EGLExtension::KHR_fence_sync) &&
+ egl->IsExtensionSupported(EGLExtension::ANDROID_native_fence_sync)) {
+ FenceDelete();
+
+ mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, nullptr);
+ if (mSync) {
+ mSyncFd = egl->fDupNativeFenceFDANDROID(mSync);
+ mGL->fFlush();
+ return;
+ }
+ }
+
+ // ANDROID_native_fence_sync may not be supported so call glFinish()
+ // as a slow path.
+ mGL->fFinish();
+}
+
+void DMABufSurface::FenceWait() {
+ if (!mGL || mSyncFd < 0) {
+ MOZ_DIAGNOSTIC_ASSERT(mGL,
+ "DMABufSurface::FenceWait() missing GL context!");
+ return;
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ const EGLint attribs[] = {LOCAL_EGL_SYNC_NATIVE_FENCE_FD_ANDROID, mSyncFd,
+ LOCAL_EGL_NONE};
+ EGLSync sync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+ if (!sync) {
+ MOZ_ASSERT(false, "DMABufSurface::FenceWait(): Failed to create GLFence!");
+ // We failed to create GLFence so clear mSyncFd to avoid another try.
+ close(mSyncFd);
+ mSyncFd = -1;
+ return;
+ }
+
+ // mSyncFd is owned by GLFence so clear local reference to avoid double close
+ // at DMABufSurface::FenceDelete().
+ mSyncFd = -1;
+
+ egl->fClientWaitSync(sync, 0, LOCAL_EGL_FOREVER);
+ egl->fDestroySync(sync);
+}
+
+bool DMABufSurface::OpenFileDescriptors(const MutexAutoLock& aProofOfLock) {
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (!OpenFileDescriptorForPlane(aProofOfLock, i)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+// We can safely close DMABuf file descriptors only when we have a valid
+// GbmBufferObject. When we don't have a valid GbmBufferObject and a DMABuf
+// file descriptor is closed, whole surface is released.
+void DMABufSurface::CloseFileDescriptors(const MutexAutoLock& aProofOfLock,
+ bool aForceClose) {
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ CloseFileDescriptorForPlane(aProofOfLock, i, aForceClose);
+ }
+}
+
+DMABufSurfaceRGBA::DMABufSurfaceRGBA()
+ : DMABufSurface(SURFACE_RGBA),
+ mSurfaceFlags(0),
+ mWidth(0),
+ mHeight(0),
+ mGmbFormat(nullptr),
+ mEGLImage(LOCAL_EGL_NO_IMAGE),
+ mTexture(0),
+ mGbmBufferFlags(0) {}
+
+DMABufSurfaceRGBA::~DMABufSurfaceRGBA() {
+#ifdef MOZ_WAYLAND
+ ReleaseWlBuffer();
+#endif
+ ReleaseSurface();
+}
+
+bool DMABufSurfaceRGBA::OpenFileDescriptorForPlane(
+ const MutexAutoLock& aProofOfLock, int aPlane) {
+ if (mDmabufFds[aPlane] >= 0) {
+ return true;
+ }
+ gbm_bo* bo = mGbmBufferObject[0];
+ if (NS_WARN_IF(!bo)) {
+ LOGDMABUF(
+ ("DMABufSurfaceRGBA::OpenFileDescriptorForPlane: Missing "
+ "mGbmBufferObject object!"));
+ return false;
+ }
+
+ if (mBufferPlaneCount == 1) {
+ MOZ_ASSERT(aPlane == 0, "DMABuf: wrong surface plane!");
+ mDmabufFds[0] = GbmLib::GetFd(bo);
+ } else {
+ mDmabufFds[aPlane] = GetDMABufDevice()->GetDmabufFD(
+ GbmLib::GetHandleForPlane(bo, aPlane).u32);
+ }
+
+ if (mDmabufFds[aPlane] < 0) {
+ CloseFileDescriptors(aProofOfLock);
+ return false;
+ }
+
+ return true;
+}
+
+void DMABufSurfaceRGBA::CloseFileDescriptorForPlane(
+ const MutexAutoLock& aProofOfLock, int aPlane, bool aForceClose = false) {
+ if ((aForceClose || mGbmBufferObject[0]) && mDmabufFds[aPlane] >= 0) {
+ close(mDmabufFds[aPlane]);
+ mDmabufFds[aPlane] = -1;
+ }
+}
+
+bool DMABufSurfaceRGBA::Create(int aWidth, int aHeight,
+ int aDMABufSurfaceFlags) {
+ MOZ_ASSERT(mGbmBufferObject[0] == nullptr, "Already created?");
+
+ mSurfaceFlags = aDMABufSurfaceFlags;
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Create() UID %d size %d x %d\n", mUID, mWidth,
+ mHeight));
+
+ if (!GetDMABufDevice()->GetGbmDevice()) {
+ LOGDMABUF((" Missing GbmDevice!"));
+ return false;
+ }
+
+ mGmbFormat = GetDMABufDevice()->GetGbmFormat(mSurfaceFlags & DMABUF_ALPHA);
+ if (!mGmbFormat) {
+ // Requested DRM format is not supported.
+ return false;
+ }
+ mDrmFormats[0] = mGmbFormat->mFormat;
+
+ bool useModifiers = (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS) &&
+ !mGmbFormat->mModifiers.IsEmpty();
+ if (useModifiers) {
+ LOGDMABUF((" Creating with modifiers\n"));
+ mGbmBufferObject[0] = GbmLib::CreateWithModifiers(
+ GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mDrmFormats[0],
+ mGmbFormat->mModifiers.Elements(), mGmbFormat->mModifiers.Length());
+ if (mGbmBufferObject[0]) {
+ mBufferModifiers[0] = GbmLib::GetModifier(mGbmBufferObject[0]);
+ }
+ }
+
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Creating without modifiers\n"));
+ mGbmBufferFlags = GBM_BO_USE_LINEAR;
+ mGbmBufferObject[0] =
+ GbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight,
+ mDrmFormats[0], mGbmBufferFlags);
+ mBufferModifiers[0] = DRM_FORMAT_MOD_INVALID;
+ }
+
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Failed to create GbmBufferObject\n"));
+ return false;
+ }
+
+ if (mBufferModifiers[0] != DRM_FORMAT_MOD_INVALID) {
+ mBufferPlaneCount = GbmLib::GetPlaneCount(mGbmBufferObject[0]);
+ if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
+ LOGDMABUF(
+ (" There's too many dmabuf planes! (%d)", mBufferPlaneCount));
+ mBufferPlaneCount = DMABUF_BUFFER_PLANES;
+ return false;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mStrides[i] = GbmLib::GetStrideForPlane(mGbmBufferObject[0], i);
+ mOffsets[i] = GbmLib::GetOffset(mGbmBufferObject[0], i);
+ }
+ } else {
+ mBufferPlaneCount = 1;
+ mStrides[0] = GbmLib::GetStride(mGbmBufferObject[0]);
+ }
+
+ LOGDMABUF((" Success\n"));
+ return true;
+}
+
+bool DMABufSurfaceRGBA::Create(mozilla::gl::GLContext* aGLContext,
+ const EGLImageKHR aEGLImage, int aWidth,
+ int aHeight) {
+ LOGDMABUF(("DMABufSurfaceRGBA::Create() from EGLImage UID = %d\n", mUID));
+ if (!aGLContext) {
+ return false;
+ }
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+
+ mGL = aGLContext;
+ mWidth = aWidth;
+ mHeight = aHeight;
+ mEGLImage = aEGLImage;
+ if (!egl->fExportDMABUFImageQuery(mEGLImage, mDrmFormats, &mBufferPlaneCount,
+ mBufferModifiers)) {
+ LOGDMABUF((" ExportDMABUFImageQueryMESA failed, quit\n"));
+ return false;
+ }
+ if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
+ LOGDMABUF((" wrong plane count %d, quit\n", mBufferPlaneCount));
+ mBufferPlaneCount = DMABUF_BUFFER_PLANES;
+ return false;
+ }
+ if (!egl->fExportDMABUFImage(mEGLImage, mDmabufFds, mStrides, mOffsets)) {
+ LOGDMABUF((" ExportDMABUFImageMESA failed, quit\n"));
+ return false;
+ }
+
+ // A broken driver can return dmabuf without valid file descriptors
+ // which leads to fails later so quit now.
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mDmabufFds[i] < 0) {
+ LOGDMABUF(
+ (" ExportDMABUFImageMESA failed, mDmabufFds[%d] is invalid, quit",
+ i));
+ return false;
+ }
+ }
+
+ LOGDMABUF((" imported size %d x %d format %x planes %d modifiers %" PRIx64,
+ mWidth, mHeight, mDrmFormats[0], mBufferPlaneCount,
+ mBufferModifiers[0]));
+ return true;
+}
+
+bool DMABufSurfaceRGBA::ImportSurfaceDescriptor(
+ const SurfaceDescriptor& aDesc) {
+ const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
+
+ mWidth = desc.width()[0];
+ mHeight = desc.height()[0];
+ mBufferModifiers[0] = desc.modifier()[0];
+ mDrmFormats[0] = desc.format()[0];
+ mBufferPlaneCount = desc.fds().Length();
+ mGbmBufferFlags = desc.flags();
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ mUID = desc.uid();
+
+ LOGDMABUF(
+ ("DMABufSurfaceRGBA::ImportSurfaceDescriptor() UID %d size %d x %d\n",
+ mUID, mWidth, mHeight));
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = desc.fds()[i].ClonePlatformHandle().release();
+ if (mDmabufFds[i] < 0) {
+ LOGDMABUF(
+ (" failed to get DMABuf file descriptor: %s", strerror(errno)));
+ return false;
+ }
+ mStrides[i] = desc.strides()[i];
+ mOffsets[i] = desc.offsets()[i];
+ }
+
+ if (desc.fence().Length() > 0) {
+ mSyncFd = desc.fence()[0].ClonePlatformHandle().release();
+ if (mSyncFd < 0) {
+ LOGDMABUF(
+ (" failed to get GL fence file descriptor: %s", strerror(errno)));
+ return false;
+ }
+ }
+
+ if (desc.refCount().Length() > 0) {
+ GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release());
+ }
+
+ LOGDMABUF((" imported size %d x %d format %x planes %d", mWidth, mHeight,
+ mDrmFormats[0], mBufferPlaneCount));
+ return true;
+}
+
+bool DMABufSurfaceRGBA::Create(const SurfaceDescriptor& aDesc) {
+ return ImportSurfaceDescriptor(aDesc);
+}
+
+bool DMABufSurfaceRGBA::Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
+ AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
+ AutoTArray<uintptr_t, DMABUF_BUFFER_PLANES> images;
+ AutoTArray<uint64_t, DMABUF_BUFFER_PLANES> modifiers;
+ AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID));
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (!OpenFileDescriptors(lockFD)) {
+ return false;
+ }
+
+ width.AppendElement(mWidth);
+ height.AppendElement(mHeight);
+ format.AppendElement(mDrmFormats[0]);
+ modifiers.AppendElement(mBufferModifiers[0]);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ }
+
+ CloseFileDescriptors(lockFD);
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
+ }
+
+ aOutDescriptor = SurfaceDescriptorDMABuf(
+ mSurfaceType, modifiers, mGbmBufferFlags, fds, width, height, width,
+ height, format, strides, offsets, GetYUVColorSpace(), mColorRange,
+ fenceFDs, mUID, refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) {
+ LOGDMABUF(("DMABufSurfaceRGBA::CreateTexture() UID %d\n", mUID));
+ MOZ_ASSERT(!mEGLImage && !mTexture, "EGLImage is already created!");
+
+ nsTArray<EGLint> attribs;
+ attribs.AppendElement(LOCAL_EGL_WIDTH);
+ attribs.AppendElement(mWidth);
+ attribs.AppendElement(LOCAL_EGL_HEIGHT);
+ attribs.AppendElement(mHeight);
+ attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
+ attribs.AppendElement(mDrmFormats[0]);
+#define ADD_PLANE_ATTRIBS(plane_idx) \
+ { \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
+ attribs.AppendElement(mDmabufFds[plane_idx]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
+ attribs.AppendElement((int)mOffsets[plane_idx]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
+ attribs.AppendElement((int)mStrides[plane_idx]); \
+ if (mBufferModifiers[0] != DRM_FORMAT_MOD_INVALID) { \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
+ attribs.AppendElement(mBufferModifiers[0] & 0xFFFFFFFF); \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
+ attribs.AppendElement(mBufferModifiers[0] >> 32); \
+ } \
+ }
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (!OpenFileDescriptors(lockFD)) {
+ return false;
+ }
+ ADD_PLANE_ATTRIBS(0);
+ if (mBufferPlaneCount > 1) ADD_PLANE_ATTRIBS(1);
+ if (mBufferPlaneCount > 2) ADD_PLANE_ATTRIBS(2);
+ if (mBufferPlaneCount > 3) ADD_PLANE_ATTRIBS(3);
+#undef ADD_PLANE_ATTRIBS
+ attribs.AppendElement(LOCAL_EGL_NONE);
+
+ if (!aGLContext) return false;
+
+ if (!aGLContext->MakeCurrent()) {
+ LOGDMABUF(
+ ("DMABufSurfaceRGBA::CreateTexture(): failed to make GL context "
+ "current"));
+ return false;
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+ mEGLImage =
+ egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
+ nullptr, attribs.Elements());
+
+ CloseFileDescriptors(lockFD);
+
+ if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
+ LOGDMABUF(("EGLImageKHR creation failed"));
+ return false;
+ }
+
+ aGLContext->fGenTextures(1, &mTexture);
+ const ScopedBindTexture savedTex(aGLContext, mTexture);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage);
+ mGL = aGLContext;
+
+ return true;
+}
+
+void DMABufSurfaceRGBA::ReleaseTextures() {
+ LOGDMABUF(("DMABufSurfaceRGBA::ReleaseTextures() UID %d\n", mUID));
+ FenceDelete();
+
+ if (!mTexture && !mEGLImage) {
+ return;
+ }
+
+ if (!mGL) {
+#ifdef NIGHTLY_BUILD
+ MOZ_DIAGNOSTIC_ASSERT(mGL, "Missing GL context!");
+#else
+ NS_WARNING(
+ "DMABufSurfaceRGBA::ReleaseTextures(): Missing GL context! We're "
+ "leaking textures!");
+ return;
+#endif
+ }
+
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mTexture && mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ }
+
+ if (mEGLImage != LOCAL_EGL_NO_IMAGE) {
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = LOCAL_EGL_NO_IMAGE;
+ }
+ mGL = nullptr;
+}
+
+void DMABufSurfaceRGBA::ReleaseSurface() {
+ MOZ_ASSERT(!IsMapped(), "We can't release mapped buffer!");
+
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
+
+#ifdef MOZ_WAYLAND
+bool DMABufSurfaceRGBA::CreateWlBuffer() {
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (!OpenFileDescriptors(lockFD)) {
+ return false;
+ }
+
+ nsWaylandDisplay* waylandDisplay = widget::WaylandDisplayGet();
+ if (!waylandDisplay->GetDmabuf()) {
+ CloseFileDescriptors(lockFD);
+ return false;
+ }
+
+ struct zwp_linux_buffer_params_v1* params =
+ zwp_linux_dmabuf_v1_create_params(waylandDisplay->GetDmabuf());
+ zwp_linux_buffer_params_v1_add(params, mDmabufFds[0], 0, mOffsets[0],
+ mStrides[0], mBufferModifiers[0] >> 32,
+ mBufferModifiers[0] & 0xffffffff);
+
+ mWlBuffer = zwp_linux_buffer_params_v1_create_immed(
+ params, GetWidth(), GetHeight(), mDrmFormats[0], 0);
+
+ CloseFileDescriptors(lockFD);
+
+ return mWlBuffer != nullptr;
+}
+
+void DMABufSurfaceRGBA::ReleaseWlBuffer() {
+ MozClearPointer(mWlBuffer, wl_buffer_destroy);
+}
+#endif
+
+// We should synchronize DMA Buffer object access from CPU to avoid potential
+// cache incoherency and data loss.
+// See
+// https://01.org/linuxgraphics/gfx-docs/drm/driver-api/dma-buf.html#cpu-access-to-dma-buffer-objects
+struct dma_buf_sync {
+ uint64_t flags;
+};
+#define DMA_BUF_SYNC_READ (1 << 0)
+#define DMA_BUF_SYNC_WRITE (2 << 0)
+#define DMA_BUF_SYNC_START (0 << 2)
+#define DMA_BUF_SYNC_END (1 << 2)
+#define DMA_BUF_BASE 'b'
+#define DMA_BUF_IOCTL_SYNC _IOW(DMA_BUF_BASE, 0, struct dma_buf_sync)
+
+static void SyncDmaBuf(int aFd, uint64_t aFlags) {
+ struct dma_buf_sync sync = {0};
+
+ sync.flags = aFlags | DMA_BUF_SYNC_READ | DMA_BUF_SYNC_WRITE;
+ while (true) {
+ int ret;
+ ret = ioctl(aFd, DMA_BUF_IOCTL_SYNC, &sync);
+ if (ret == -1 && errno == EINTR) {
+ continue;
+ } else if (ret == -1) {
+ LOGDMABUF(
+ ("Failed to synchronize DMA buffer: %s FD %d", strerror(errno), aFd));
+ break;
+ } else {
+ break;
+ }
+ }
+}
+
+void* DMABufSurface::MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride,
+ int aGbmFlags, int aPlane) {
+ NS_ASSERTION(!IsMapped(aPlane), "Already mapped!");
+ if (!mGbmBufferObject[aPlane]) {
+ NS_WARNING("We can't map DMABufSurfaceRGBA without mGbmBufferObject");
+ return nullptr;
+ }
+
+ LOGDMABUF(
+ ("DMABufSurfaceRGBA::MapInternal() UID %d plane %d size %d x %d -> %d x "
+ "%d\n",
+ mUID, aPlane, aX, aY, aWidth, aHeight));
+
+ mMappedRegionStride[aPlane] = 0;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegion[aPlane] =
+ GbmLib::Map(mGbmBufferObject[aPlane], aX, aY, aWidth, aHeight, aGbmFlags,
+ &mMappedRegionStride[aPlane], &mMappedRegionData[aPlane]);
+ if (!mMappedRegion[aPlane]) {
+ LOGDMABUF((" Surface mapping failed: %s", strerror(errno)));
+ return nullptr;
+ }
+ if (aStride) {
+ *aStride = mMappedRegionStride[aPlane];
+ }
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (OpenFileDescriptorForPlane(lockFD, aPlane)) {
+ SyncDmaBuf(mDmabufFds[aPlane], DMA_BUF_SYNC_START);
+ CloseFileDescriptorForPlane(lockFD, aPlane);
+ }
+
+ return mMappedRegion[aPlane];
+}
+
+void* DMABufSurfaceRGBA::MapReadOnly(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride) {
+ return MapInternal(aX, aY, aWidth, aHeight, aStride, GBM_BO_TRANSFER_READ);
+}
+
+void* DMABufSurfaceRGBA::MapReadOnly(uint32_t* aStride) {
+ return MapInternal(0, 0, mWidth, mHeight, aStride, GBM_BO_TRANSFER_READ);
+}
+
+void* DMABufSurfaceRGBA::Map(uint32_t aX, uint32_t aY, uint32_t aWidth,
+ uint32_t aHeight, uint32_t* aStride) {
+ return MapInternal(aX, aY, aWidth, aHeight, aStride,
+ GBM_BO_TRANSFER_READ_WRITE);
+}
+
+void* DMABufSurfaceRGBA::Map(uint32_t* aStride) {
+ return MapInternal(0, 0, mWidth, mHeight, aStride,
+ GBM_BO_TRANSFER_READ_WRITE);
+}
+
+void DMABufSurface::Unmap(int aPlane) {
+ if (mMappedRegion[aPlane]) {
+ LOGDMABUF(("DMABufSurface::Unmap() UID %d plane %d\n", mUID, aPlane));
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (OpenFileDescriptorForPlane(lockFD, aPlane)) {
+ SyncDmaBuf(mDmabufFds[aPlane], DMA_BUF_SYNC_END);
+ CloseFileDescriptorForPlane(lockFD, aPlane);
+ }
+ GbmLib::Unmap(mGbmBufferObject[aPlane], mMappedRegionData[aPlane]);
+ mMappedRegion[aPlane] = nullptr;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegionStride[aPlane] = 0;
+ }
+}
+
+nsresult DMABufSurface::BuildSurfaceDescriptorBuffer(
+ SurfaceDescriptorBuffer& aSdBuffer, Image::BuildSdbFlags aFlags,
+ const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+#ifdef DEBUG
+void DMABufSurfaceRGBA::DumpToFile(const char* pFile) {
+ uint32_t stride;
+
+ if (!MapReadOnly(&stride)) {
+ return;
+ }
+ cairo_surface_t* surface = nullptr;
+
+ auto unmap = MakeScopeExit([&] {
+ if (surface) {
+ cairo_surface_destroy(surface);
+ }
+ Unmap();
+ });
+
+ surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mMappedRegion[0], CAIRO_FORMAT_ARGB32, mWidth, mHeight,
+ stride);
+ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+ cairo_surface_write_to_png(surface, pFile);
+ }
+}
+#endif
+
+#if 0
+// Copy from source surface by GL
+# include "GLBlitHelper.h"
+
+bool DMABufSurfaceRGBA::CopyFrom(class DMABufSurface* aSourceSurface,
+ GLContext* aGLContext) {
+ MOZ_ASSERT(aSourceSurface->GetTexture());
+ MOZ_ASSERT(GetTexture());
+
+ gfx::IntSize size(GetWidth(), GetHeight());
+ aGLContext->BlitHelper()->BlitTextureToTexture(aSourceSurface->GetTexture(),
+ GetTexture(), size, size);
+ return true;
+}
+#endif
+
+// TODO - Clear the surface by EGL
+void DMABufSurfaceRGBA::Clear() {
+ uint32_t destStride;
+ void* destData = Map(&destStride);
+ memset(destData, 0, GetHeight() * destStride);
+ Unmap();
+}
+
+bool DMABufSurfaceRGBA::HasAlpha() {
+ return !mGmbFormat || mGmbFormat->mHasAlpha;
+}
+
+gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormat() {
+ return HasAlpha() ? gfx::SurfaceFormat::B8G8R8A8
+ : gfx::SurfaceFormat::B8G8R8X8;
+}
+
+// GL uses swapped R and B components so report accordingly.
+gfx::SurfaceFormat DMABufSurfaceRGBA::GetFormatGL() {
+ return HasAlpha() ? gfx::SurfaceFormat::R8G8B8A8
+ : gfx::SurfaceFormat::R8G8B8X8;
+}
+
+already_AddRefed<DMABufSurfaceRGBA> DMABufSurfaceRGBA::CreateDMABufSurface(
+ int aWidth, int aHeight, int aDMABufSurfaceFlags) {
+ RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
+ if (!surf->Create(aWidth, aHeight, aDMABufSurfaceFlags)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurface> DMABufSurfaceRGBA::CreateDMABufSurface(
+ mozilla::gl::GLContext* aGLContext, const EGLImageKHR aEGLImage, int aWidth,
+ int aHeight) {
+ RefPtr<DMABufSurfaceRGBA> surf = new DMABufSurfaceRGBA();
+ if (!surf->Create(aGLContext, aEGLImage, aWidth, aHeight)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurface() UID %d from desc\n",
+ surf->GetUID()));
+ if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ false)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CopyYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurfaceCopy() UID %d from desc\n",
+ surf->GetUID()));
+ if (!surf->UpdateYUVData(aDesc, aWidth, aHeight, /* aCopy */ true)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ int aWidth, int aHeight, void** aPixelData, int* aLineSizes) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ LOGDMABUF(("DMABufSurfaceYUV::CreateYUVSurface() UID %d %d x %d\n",
+ surf->GetUID(), aWidth, aHeight));
+ if (!surf->Create(aWidth, aHeight, aPixelData, aLineSizes)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+DMABufSurfaceYUV::DMABufSurfaceYUV()
+ : DMABufSurface(SURFACE_NV12),
+ mWidth(),
+ mHeight(),
+ mWidthAligned(),
+ mHeightAligned(),
+ mTexture() {
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
+ }
+}
+
+DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); }
+
+bool DMABufSurfaceYUV::OpenFileDescriptorForPlane(
+ const MutexAutoLock& aProofOfLock, int aPlane) {
+ // The fd is already opened, no need to reopen.
+ // This can happen when we import dmabuf surface from VA-API decoder,
+ // mGbmBufferObject is null and we don't close
+ // file descriptors for surface as they are our only reference to it.
+ if (mDmabufFds[aPlane] >= 0) {
+ return true;
+ }
+
+ if (mGbmBufferObject[aPlane] == nullptr) {
+ LOGDMABUF(
+ ("DMABufSurfaceYUV::OpenFileDescriptorForPlane: Missing "
+ "mGbmBufferObject object!"));
+ return false;
+ }
+
+ mDmabufFds[aPlane] = GbmLib::GetFd(mGbmBufferObject[aPlane]);
+ if (mDmabufFds[aPlane] < 0) {
+ CloseFileDescriptors(aProofOfLock);
+ return false;
+ }
+ return true;
+}
+
+void DMABufSurfaceYUV::CloseFileDescriptorForPlane(
+ const MutexAutoLock& aProofOfLock, int aPlane, bool aForceClose = false) {
+ if ((aForceClose || mGbmBufferObject[aPlane]) && mDmabufFds[aPlane] >= 0) {
+ close(mDmabufFds[aPlane]);
+ mDmabufFds[aPlane] = -1;
+ }
+}
+
+bool DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor(
+ const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight) {
+ LOGDMABUF(("DMABufSurfaceYUV::ImportPRIMESurfaceDescriptor() UID %d", mUID));
+ // Already exists?
+ MOZ_DIAGNOSTIC_ASSERT(mDmabufFds[0] < 0);
+
+ if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
+ aDesc.num_objects > DMABUF_BUFFER_PLANES) {
+ LOGDMABUF((" Can't import, wrong layers/objects number (%d, %d)",
+ aDesc.num_layers, aDesc.num_objects));
+ return false;
+ }
+ if (aDesc.fourcc == VA_FOURCC_NV12) {
+ mSurfaceType = SURFACE_NV12;
+ } else if (aDesc.fourcc == VA_FOURCC_P010) {
+ mSurfaceType = SURFACE_NV12;
+ } else if (aDesc.fourcc == VA_FOURCC_YV12) {
+ mSurfaceType = SURFACE_YUV420;
+ } else {
+ LOGDMABUF((" Can't import surface data of 0x%x format", aDesc.fourcc));
+ return false;
+ }
+
+ mBufferPlaneCount = aDesc.num_layers;
+
+ for (unsigned int i = 0; i < aDesc.num_layers; i++) {
+ // All supported formats have 4:2:0 chroma sub-sampling.
+ unsigned int subsample = i == 0 ? 0 : 1;
+
+ unsigned int object = aDesc.layers[i].object_index[0];
+ mBufferModifiers[i] = aDesc.objects[object].drm_format_modifier;
+ mDrmFormats[i] = aDesc.layers[i].drm_format;
+ mOffsets[i] = aDesc.layers[i].offset[0];
+ mStrides[i] = aDesc.layers[i].pitch[0];
+ mWidthAligned[i] = aDesc.width >> subsample;
+ mHeightAligned[i] = aDesc.height >> subsample;
+ mWidth[i] = aWidth >> subsample;
+ mHeight[i] = aHeight >> subsample;
+ LOGDMABUF((" plane %d size %d x %d format %x", i, mWidth[i], mHeight[i],
+ mDrmFormats[i]));
+ }
+ return true;
+}
+
+bool DMABufSurfaceYUV::MoveYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
+ int aWidth, int aHeight) {
+ if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
+ return false;
+ }
+ for (unsigned int i = 0; i < aDesc.num_layers; i++) {
+ unsigned int object = aDesc.layers[i].object_index[0];
+ // Keep VADRMPRIMESurfaceDescriptor untouched and dup() dmabuf
+ // file descriptors.
+ mDmabufFds[i] = dup(aDesc.objects[object].fd);
+ }
+ return true;
+}
+
+void DMABufSurfaceYUV::ReleaseVADRMPRIMESurfaceDescriptor(
+ VADRMPRIMESurfaceDescriptor& aDesc) {
+ for (unsigned int i = 0; i < aDesc.num_layers; i++) {
+ unsigned int object = aDesc.layers[i].object_index[0];
+ if (aDesc.objects[object].fd != -1) {
+ close(aDesc.objects[object].fd);
+ aDesc.objects[object].fd = -1;
+ }
+ }
+}
+
+bool DMABufSurfaceYUV::CreateYUVPlane(int aPlane) {
+ LOGDMABUF(("DMABufSurfaceYUV::CreateYUVPlane() UID %d size %d x %d", mUID,
+ mWidth[aPlane], mHeight[aPlane]));
+
+ if (!GetDMABufDevice()->GetGbmDevice()) {
+ LOGDMABUF((" Missing GbmDevice!"));
+ return false;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mGbmBufferObject[aPlane] == nullptr);
+ bool useModifiers = (mBufferModifiers[aPlane] != DRM_FORMAT_MOD_INVALID);
+ if (useModifiers) {
+ LOGDMABUF((" Creating with modifiers"));
+ mGbmBufferObject[aPlane] = GbmLib::CreateWithModifiers(
+ GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
+ mDrmFormats[aPlane], mBufferModifiers + aPlane, 1);
+ }
+ if (!mGbmBufferObject[aPlane]) {
+ LOGDMABUF((" Creating without modifiers"));
+ mGbmBufferObject[aPlane] = GbmLib::Create(
+ GetDMABufDevice()->GetGbmDevice(), mWidth[aPlane], mHeight[aPlane],
+ mDrmFormats[aPlane], GBM_BO_USE_RENDERING);
+ mBufferModifiers[aPlane] = DRM_FORMAT_MOD_INVALID;
+ }
+ if (!mGbmBufferObject[aPlane]) {
+ LOGDMABUF((" Failed to create GbmBufferObject: %s", strerror(errno)));
+ return false;
+ }
+
+ mStrides[aPlane] = GbmLib::GetStride(mGbmBufferObject[aPlane]);
+ mOffsets[aPlane] = GbmLib::GetOffset(mGbmBufferObject[aPlane], 0);
+ mWidthAligned[aPlane] = mWidth[aPlane];
+ mHeightAligned[aPlane] = mHeight[aPlane];
+ return true;
+}
+
+bool DMABufSurfaceYUV::CopyYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc,
+ int aWidth, int aHeight) {
+ RefPtr<DMABufSurfaceYUV> tmpSurf = CreateYUVSurface(aDesc, aWidth, aHeight);
+ if (!tmpSurf) {
+ return false;
+ }
+
+ if (!ImportPRIMESurfaceDescriptor(aDesc, aWidth, aHeight)) {
+ return false;
+ }
+
+ StaticMutexAutoLock lock(sSnapshotContextMutex);
+ RefPtr<GLContext> context = ClaimSnapshotGLContext();
+ auto releaseTextures = MakeScopeExit([&] {
+ tmpSurf->ReleaseTextures();
+ ReleaseTextures();
+ ReturnSnapshotGLContext(context);
+ });
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (!tmpSurf->CreateTexture(context, i)) {
+ return false;
+ }
+ if (!CreateYUVPlane(i) || !CreateTexture(context, i)) {
+ return false;
+ }
+ gfx::IntSize size(GetWidth(i), GetHeight(i));
+ context->BlitHelper()->BlitTextureToTexture(
+ tmpSurf->GetTexture(i), GetTexture(i), size, size, LOCAL_GL_TEXTURE_2D,
+ LOCAL_GL_TEXTURE_2D);
+ }
+ return true;
+}
+
+bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc,
+ int aWidth, int aHeight, bool aCopy) {
+ LOGDMABUF(("DMABufSurfaceYUV::UpdateYUVData() UID %d copy %d", mUID, aCopy));
+ return aCopy ? CopyYUVDataImpl(aDesc, aWidth, aHeight)
+ : MoveYUVDataImpl(aDesc, aWidth, aHeight);
+}
+
+bool DMABufSurfaceYUV::CreateLinearYUVPlane(int aPlane, int aWidth, int aHeight,
+ int aDrmFormat) {
+ LOGDMABUF(("DMABufSurfaceYUV::CreateLinearYUVPlane() UID %d size %d x %d",
+ mUID, aWidth, aHeight));
+
+ if (!GetDMABufDevice()->GetGbmDevice()) {
+ LOGDMABUF((" Missing GbmDevice!"));
+ return false;
+ }
+
+ mWidth[aPlane] = aWidth;
+ mHeight[aPlane] = aHeight;
+ mDrmFormats[aPlane] = aDrmFormat;
+
+ mGbmBufferObject[aPlane] =
+ GbmLib::Create(GetDMABufDevice()->GetGbmDevice(), aWidth, aHeight,
+ aDrmFormat, GBM_BO_USE_LINEAR);
+ if (!mGbmBufferObject[aPlane]) {
+ LOGDMABUF((" Failed to create GbmBufferObject: %s", strerror(errno)));
+ return false;
+ }
+
+ mStrides[aPlane] = GbmLib::GetStride(mGbmBufferObject[aPlane]);
+ mDmabufFds[aPlane] = -1;
+
+ return true;
+}
+
+void DMABufSurfaceYUV::UpdateYUVPlane(int aPlane, void* aPixelData,
+ int aLineSize) {
+ LOGDMABUF(
+ ("DMABufSurfaceYUV::UpdateYUVPlane() UID %d plane %d", mUID, aPlane));
+ if (aLineSize == mWidth[aPlane] &&
+ (int)mMappedRegionStride[aPlane] == mWidth[aPlane]) {
+ memcpy(mMappedRegion[aPlane], aPixelData, aLineSize * mHeight[aPlane]);
+ } else {
+ char* src = (char*)aPixelData;
+ char* dest = (char*)mMappedRegion[aPlane];
+ for (int i = 0; i < mHeight[aPlane]; i++) {
+ memcpy(dest, src, mWidth[aPlane]);
+ src += aLineSize;
+ dest += mMappedRegionStride[aPlane];
+ }
+ }
+}
+
+bool DMABufSurfaceYUV::UpdateYUVData(void** aPixelData, int* aLineSizes) {
+ LOGDMABUF(("DMABufSurfaceYUV::UpdateYUVData() UID %d", mUID));
+ if (mSurfaceType != SURFACE_YUV420) {
+ LOGDMABUF((" UpdateYUVData can upload YUV420 surface type only!"));
+ return false;
+ }
+
+ if (mBufferPlaneCount != 3) {
+ LOGDMABUF((" DMABufSurfaceYUV planes does not match!"));
+ return false;
+ }
+
+ auto unmapBuffers = MakeScopeExit([&] {
+ Unmap(0);
+ Unmap(1);
+ Unmap(2);
+ });
+
+ // Map planes
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ MapInternal(0, 0, mWidth[i], mHeight[i], nullptr, GBM_BO_TRANSFER_WRITE, i);
+ if (!mMappedRegion[i]) {
+ LOGDMABUF((" DMABufSurfaceYUV plane can't be mapped!"));
+ return false;
+ }
+ if ((int)mMappedRegionStride[i] < mWidth[i]) {
+ LOGDMABUF((" DMABufSurfaceYUV plane size stride does not match!"));
+ return false;
+ }
+ }
+
+ // Copy planes
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ UpdateYUVPlane(i, aPixelData[i], aLineSizes[i]);
+ }
+
+ return true;
+}
+
+bool DMABufSurfaceYUV::Create(int aWidth, int aHeight, void** aPixelData,
+ int* aLineSizes) {
+ LOGDMABUF(("DMABufSurfaceYUV::Create() UID %d size %d x %d", mUID, aWidth,
+ aHeight));
+
+ mSurfaceType = SURFACE_YUV420;
+ mBufferPlaneCount = 3;
+
+ if (!CreateLinearYUVPlane(0, aWidth, aHeight, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateLinearYUVPlane(1, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateLinearYUVPlane(2, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!aPixelData || !aLineSizes) {
+ return true;
+ }
+ return UpdateYUVData(aPixelData, aLineSizes);
+}
+
+bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) {
+ return ImportSurfaceDescriptor(aDesc);
+}
+
+bool DMABufSurfaceYUV::ImportSurfaceDescriptor(
+ const SurfaceDescriptorDMABuf& aDesc) {
+ mBufferPlaneCount = aDesc.fds().Length();
+ mSurfaceType = (mBufferPlaneCount == 2) ? SURFACE_NV12 : SURFACE_YUV420;
+ mColorSpace = aDesc.yUVColorSpace();
+ mColorRange = aDesc.colorRange();
+ mUID = aDesc.uid();
+
+ LOGDMABUF(("DMABufSurfaceYUV::ImportSurfaceDescriptor() UID %d", mUID));
+
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = aDesc.fds()[i].ClonePlatformHandle().release();
+ if (mDmabufFds[i] < 0) {
+ LOGDMABUF((" failed to get DMABuf plane file descriptor: %s",
+ strerror(errno)));
+ return false;
+ }
+ mWidth[i] = aDesc.width()[i];
+ mHeight[i] = aDesc.height()[i];
+ mWidthAligned[i] = aDesc.widthAligned()[i];
+ mHeightAligned[i] = aDesc.heightAligned()[i];
+ mDrmFormats[i] = aDesc.format()[i];
+ mStrides[i] = aDesc.strides()[i];
+ mOffsets[i] = aDesc.offsets()[i];
+ mBufferModifiers[i] = aDesc.modifier()[i];
+ LOGDMABUF((" plane %d fd %d size %d x %d format %x", i, mDmabufFds[i],
+ mWidth[i], mHeight[i], mDrmFormats[i]));
+ }
+
+ if (aDesc.fence().Length() > 0) {
+ mSyncFd = aDesc.fence()[0].ClonePlatformHandle().release();
+ if (mSyncFd < 0) {
+ LOGDMABUF(
+ (" failed to get GL fence file descriptor: %s", strerror(errno)));
+ return false;
+ }
+ }
+
+ if (aDesc.refCount().Length() > 0) {
+ GlobalRefCountImport(aDesc.refCount()[0].ClonePlatformHandle().release());
+ }
+
+ return true;
+}
+
+bool DMABufSurfaceYUV::Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) {
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> width;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> height;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> widthBytes;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> heightBytes;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> format;
+ AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
+ AutoTArray<uint64_t, DMABUF_BUFFER_PLANES> modifiers;
+ AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ LOGDMABUF(("DMABufSurfaceYUV::Serialize() UID %d", mUID));
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (!OpenFileDescriptors(lockFD)) {
+ return false;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ width.AppendElement(mWidth[i]);
+ height.AppendElement(mHeight[i]);
+ widthBytes.AppendElement(mWidthAligned[i]);
+ heightBytes.AppendElement(mHeightAligned[i]);
+ format.AppendElement(mDrmFormats[i]);
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ modifiers.AppendElement(mBufferModifiers[i]);
+ }
+
+ CloseFileDescriptors(lockFD);
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(GlobalRefCountExport()));
+ }
+
+ aOutDescriptor = SurfaceDescriptorDMABuf(
+ mSurfaceType, modifiers, 0, fds, width, height, widthBytes, heightBytes,
+ format, strides, offsets, GetYUVColorSpace(), mColorRange, fenceFDs, mUID,
+ refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceYUV::CreateEGLImage(GLContext* aGLContext, int aPlane) {
+ LOGDMABUF(
+ ("DMABufSurfaceYUV::CreateEGLImage() UID %d plane %d", mUID, aPlane));
+ MOZ_ASSERT(mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE,
+ "EGLImage is already created!");
+ MOZ_ASSERT(aGLContext, "Missing GLContext!");
+
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+
+ MutexAutoLock lockFD(mSurfaceLock);
+ if (!OpenFileDescriptorForPlane(lockFD, aPlane)) {
+ LOGDMABUF((" failed to open dmabuf file descriptors"));
+ return false;
+ }
+
+ nsTArray<EGLint> attribs;
+ attribs.AppendElement(LOCAL_EGL_WIDTH);
+ attribs.AppendElement(mWidthAligned[aPlane]);
+ attribs.AppendElement(LOCAL_EGL_HEIGHT);
+ attribs.AppendElement(mHeightAligned[aPlane]);
+ attribs.AppendElement(LOCAL_EGL_LINUX_DRM_FOURCC_EXT);
+ attribs.AppendElement(mDrmFormats[aPlane]);
+#define ADD_PLANE_ATTRIBS_NV12(plane_idx) \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_FD_EXT); \
+ attribs.AppendElement(mDmabufFds[aPlane]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_OFFSET_EXT); \
+ attribs.AppendElement((int)mOffsets[aPlane]); \
+ attribs.AppendElement(LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_PITCH_EXT); \
+ attribs.AppendElement((int)mStrides[aPlane]); \
+ if (mBufferModifiers[aPlane] != DRM_FORMAT_MOD_INVALID) { \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
+ attribs.AppendElement(mBufferModifiers[aPlane] & 0xFFFFFFFF); \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
+ attribs.AppendElement(mBufferModifiers[aPlane] >> 32); \
+ }
+ ADD_PLANE_ATTRIBS_NV12(0);
+#undef ADD_PLANE_ATTRIBS_NV12
+ attribs.AppendElement(LOCAL_EGL_NONE);
+
+ mEGLImage[aPlane] =
+ egl->fCreateImage(LOCAL_EGL_NO_CONTEXT, LOCAL_EGL_LINUX_DMA_BUF_EXT,
+ nullptr, attribs.Elements());
+
+ CloseFileDescriptorForPlane(lockFD, aPlane);
+
+ if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
+ LOGDMABUF((" EGLImageKHR creation failed"));
+ return false;
+ }
+
+ LOGDMABUF((" Success."));
+ return true;
+}
+
+void DMABufSurfaceYUV::ReleaseEGLImages(GLContext* aGLContext) {
+ LOGDMABUF(("DMABufSurfaceYUV::ReleaseEGLImages() UID %d", mUID));
+ MOZ_ASSERT(aGLContext, "Missing GLContext!");
+
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mEGLImage[i] != LOCAL_EGL_NO_IMAGE) {
+ egl->fDestroyImage(mEGLImage[i]);
+ mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
+ }
+ }
+}
+
+bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) {
+ LOGDMABUF(
+ ("DMABufSurfaceYUV::CreateTexture() UID %d plane %d", mUID, aPlane));
+ MOZ_ASSERT(!mTexture[aPlane], "Texture is already created!");
+ MOZ_ASSERT(aGLContext, "Missing GLContext!");
+
+ if (!aGLContext->MakeCurrent()) {
+ LOGDMABUF((" Failed to make GL context current."));
+ return false;
+ }
+ if (!CreateEGLImage(aGLContext, aPlane)) {
+ return false;
+ }
+ aGLContext->fGenTextures(1, &mTexture[aPlane]);
+ const ScopedBindTexture savedTex(aGLContext, mTexture[aPlane]);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_S,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_WRAP_T,
+ LOCAL_GL_CLAMP_TO_EDGE);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MAG_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fTexParameteri(LOCAL_GL_TEXTURE_2D, LOCAL_GL_TEXTURE_MIN_FILTER,
+ LOCAL_GL_LINEAR);
+ aGLContext->fEGLImageTargetTexture2D(LOCAL_GL_TEXTURE_2D, mEGLImage[aPlane]);
+ mGL = aGLContext;
+ return true;
+}
+
+void DMABufSurfaceYUV::ReleaseTextures() {
+ LOGDMABUF(("DMABufSurfaceYUV::ReleaseTextures() UID %d", mUID));
+
+ FenceDelete();
+
+ bool textureActive = false;
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mTexture[i] || mEGLImage[i]) {
+ textureActive = true;
+ break;
+ }
+ }
+
+ if (!textureActive) {
+ return;
+ }
+
+ if (!mGL) {
+#ifdef NIGHTLY_BUILD
+ MOZ_DIAGNOSTIC_ASSERT(mGL, "Missing GL context!");
+#else
+ NS_WARNING(
+ "DMABufSurfaceYUV::ReleaseTextures(): Missing GL context! We're "
+ "leaking textures!");
+ return;
+#endif
+ }
+
+ if (!mGL->MakeCurrent()) {
+ NS_WARNING(
+ "DMABufSurfaceYUV::ReleaseTextures(): MakeCurrent failed. We're "
+ "leaking textures!");
+ return;
+ }
+
+ mGL->fDeleteTextures(DMABUF_BUFFER_PLANES, mTexture);
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mTexture[i] = 0;
+ }
+ ReleaseEGLImages(mGL);
+ mGL = nullptr;
+}
+
+bool DMABufSurfaceYUV::VerifyTextureCreation() {
+ LOGDMABUF(("DMABufSurfaceYUV::VerifyTextureCreation() UID %d", mUID));
+
+ StaticMutexAutoLock lock(sSnapshotContextMutex);
+ RefPtr<GLContext> context = ClaimSnapshotGLContext();
+ auto release = MakeScopeExit([&] {
+ ReleaseEGLImages(context);
+ ReturnSnapshotGLContext(context);
+ });
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (!CreateEGLImage(context, i)) {
+ LOGDMABUF((" failed to create EGL image!"));
+ return false;
+ }
+ }
+
+ LOGDMABUF((" success"));
+ return true;
+}
+
+gfx::SurfaceFormat DMABufSurfaceYUV::GetFormat() {
+ switch (mSurfaceType) {
+ case SURFACE_NV12:
+ return gfx::SurfaceFormat::NV12;
+ case SURFACE_YUV420:
+ return gfx::SurfaceFormat::YUV;
+ default:
+ NS_WARNING("DMABufSurfaceYUV::GetFormat(): Wrong surface format!");
+ return gfx::SurfaceFormat::UNKNOWN;
+ }
+}
+
+// GL uses swapped R and B components so report accordingly.
+gfx::SurfaceFormat DMABufSurfaceYUV::GetFormatGL() { return GetFormat(); }
+
+int DMABufSurfaceYUV::GetTextureCount() {
+ switch (mSurfaceType) {
+ case SURFACE_NV12:
+ return 2;
+ case SURFACE_YUV420:
+ return 3;
+ default:
+ NS_WARNING("DMABufSurfaceYUV::GetTextureCount(): Wrong surface format!");
+ return 1;
+ }
+}
+
+void DMABufSurfaceYUV::ReleaseSurface() {
+ LOGDMABUF(("DMABufSurfaceYUV::ReleaseSurface() UID %d", mUID));
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
+
+nsresult DMABufSurfaceYUV::ReadIntoBuffer(uint8_t* aData, int32_t aStride,
+ const gfx::IntSize& aSize,
+ gfx::SurfaceFormat aFormat) {
+ LOGDMABUF(("DMABufSurfaceYUV::ReadIntoBuffer UID %d", mUID));
+
+ MOZ_ASSERT(aSize.width == GetWidth());
+ MOZ_ASSERT(aSize.height == GetHeight());
+
+ StaticMutexAutoLock lock(sSnapshotContextMutex);
+ RefPtr<GLContext> context = ClaimSnapshotGLContext();
+ auto releaseTextures = mozilla::MakeScopeExit([&] {
+ ReleaseTextures();
+ ReturnSnapshotGLContext(context);
+ });
+
+ for (int i = 0; i < GetTextureCount(); i++) {
+ if (!GetTexture(i) && !CreateTexture(context, i)) {
+ LOGDMABUF(("ReadIntoBuffer: Failed to create DMABuf textures."));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ ScopedTexture scopedTex(context);
+ ScopedBindTexture boundTex(context, scopedTex.Texture());
+
+ context->fTexImage2D(LOCAL_GL_TEXTURE_2D, 0, LOCAL_GL_RGBA, aSize.width,
+ aSize.height, 0, LOCAL_GL_RGBA, LOCAL_GL_UNSIGNED_BYTE,
+ nullptr);
+
+ ScopedFramebufferForTexture autoFBForTex(context, scopedTex.Texture());
+ if (!autoFBForTex.IsComplete()) {
+ LOGDMABUF(("ReadIntoBuffer: ScopedFramebufferForTexture failed."));
+ return NS_ERROR_FAILURE;
+ }
+
+ const gl::OriginPos destOrigin = gl::OriginPos::BottomLeft;
+ {
+ const ScopedBindFramebuffer bindFB(context, autoFBForTex.FB());
+ if (!context->BlitHelper()->Blit(this, aSize, destOrigin)) {
+ LOGDMABUF(("ReadIntoBuffer: Blit failed."));
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ ScopedBindFramebuffer bind(context, autoFBForTex.FB());
+ ReadPixelsIntoBuffer(context, aData, aStride, aSize, aFormat);
+ return NS_OK;
+}
+
+already_AddRefed<gfx::DataSourceSurface>
+DMABufSurfaceYUV::GetAsSourceSurface() {
+ LOGDMABUF(("DMABufSurfaceYUV::GetAsSourceSurface UID %d", mUID));
+
+ gfx::IntSize size(GetWidth(), GetHeight());
+ const auto format = gfx::SurfaceFormat::B8G8R8A8;
+ RefPtr<gfx::DataSourceSurface> source =
+ gfx::Factory::CreateDataSourceSurface(size, format);
+ if (NS_WARN_IF(!source)) {
+ LOGDMABUF(("GetAsSourceSurface: CreateDataSourceSurface failed."));
+ return nullptr;
+ }
+
+ gfx::DataSourceSurface::ScopedMap map(source,
+ gfx::DataSourceSurface::READ_WRITE);
+ if (NS_WARN_IF(!map.IsMapped())) {
+ LOGDMABUF(("GetAsSourceSurface: Mapping surface failed."));
+ return nullptr;
+ }
+
+ if (NS_WARN_IF(NS_FAILED(
+ ReadIntoBuffer(map.GetData(), map.GetStride(), size, format)))) {
+ LOGDMABUF(("GetAsSourceSurface: Reading into buffer failed."));
+ return nullptr;
+ }
+
+ return source.forget();
+}
+
+nsresult DMABufSurfaceYUV::BuildSurfaceDescriptorBuffer(
+ SurfaceDescriptorBuffer& aSdBuffer, Image::BuildSdbFlags aFlags,
+ const std::function<MemoryOrShmem(uint32_t)>& aAllocate) {
+ LOGDMABUF(("DMABufSurfaceYUV::BuildSurfaceDescriptorBuffer UID %d", mUID));
+
+ gfx::IntSize size(GetWidth(), GetHeight());
+ const auto format = gfx::SurfaceFormat::B8G8R8A8;
+
+ uint8_t* buffer = nullptr;
+ int32_t stride = 0;
+ nsresult rv = Image::AllocateSurfaceDescriptorBufferRgb(
+ size, format, buffer, aSdBuffer, stride, aAllocate);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ LOGDMABUF(("BuildSurfaceDescriptorBuffer allocate descriptor failed"));
+ return rv;
+ }
+
+ return ReadIntoBuffer(buffer, stride, size, format);
+}
+
+#if 0
+void DMABufSurfaceYUV::ClearPlane(int aPlane) {
+ if (!MapInternal(0, 0, mWidth[aPlane], mHeight[aPlane], nullptr,
+ GBM_BO_TRANSFER_WRITE, aPlane)) {
+ return;
+ }
+ if ((int)mMappedRegionStride[aPlane] < mWidth[aPlane]) {
+ return;
+ }
+ memset((char*)mMappedRegion[aPlane], 0,
+ mMappedRegionStride[aPlane] * mHeight[aPlane]);
+ Unmap(aPlane);
+}
+
+# include "gfxUtils.h"
+
+void DMABufSurfaceYUV::DumpToFile(const char* aFile) {
+ RefPtr<gfx::DataSourceSurface> surf = GetAsSourceSurface();
+ gfxUtils::WriteAsPNG(surf, aFile);
+}
+#endif
diff --git a/widget/gtk/DMABufSurface.h b/widget/gtk/DMABufSurface.h
new file mode 100644
index 0000000000..9bb8928cca
--- /dev/null
+++ b/widget/gtk/DMABufSurface.h
@@ -0,0 +1,413 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef DMABufSurface_h__
+#define DMABufSurface_h__
+
+#include <functional>
+#include <stdint.h>
+#include "mozilla/widget/va_drmcommon.h"
+#include "GLTypes.h"
+#include "ImageContainer.h"
+#include "nsISupportsImpl.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/Mutex.h"
+
+typedef void* EGLImageKHR;
+typedef void* EGLSyncKHR;
+
+#define DMABUF_BUFFER_PLANES 4
+
+#ifndef VA_FOURCC_NV12
+# define VA_FOURCC_NV12 0x3231564E
+#endif
+#ifndef VA_FOURCC_YV12
+# define VA_FOURCC_YV12 0x32315659
+#endif
+#ifndef VA_FOURCC_P010
+# define VA_FOURCC_P010 0x30313050
+#endif
+
+namespace mozilla {
+namespace gfx {
+class DataSourceSurface;
+}
+namespace layers {
+class MemoryOrShmem;
+class SurfaceDescriptor;
+class SurfaceDescriptorBuffer;
+class SurfaceDescriptorDMABuf;
+} // namespace layers
+namespace gl {
+class GLContext;
+}
+} // namespace mozilla
+
+typedef enum {
+ // Use alpha pixel format
+ DMABUF_ALPHA = 1 << 0,
+ // Surface is used as texture and may be also shared
+ DMABUF_TEXTURE = 1 << 1,
+ // Use modifiers. Such dmabuf surface may have more planes
+ // and complex internal structure (tiling/compression/etc.)
+ // so we can't do direct rendering to it.
+ DMABUF_USE_MODIFIERS = 1 << 3,
+} DMABufSurfaceFlags;
+
+class DMABufSurfaceRGBA;
+class DMABufSurfaceYUV;
+struct wl_buffer;
+
+namespace mozilla::widget {
+struct GbmFormat;
+}
+
+class DMABufSurface {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(DMABufSurface)
+
+ enum SurfaceType {
+ SURFACE_RGBA,
+ SURFACE_NV12,
+ SURFACE_YUV420,
+ };
+
+ // Import surface from SurfaceDescriptor. This is usually
+ // used to copy surface from another process over IPC.
+ // When a global reference counter was created for the surface
+ // (see bellow) it's automatically referenced.
+ static already_AddRefed<DMABufSurface> CreateDMABufSurface(
+ const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ // Export surface to another process via. SurfaceDescriptor.
+ virtual bool Serialize(
+ mozilla::layers::SurfaceDescriptor& aOutDescriptor) = 0;
+
+ virtual int GetWidth(int aPlane = 0) = 0;
+ virtual int GetHeight(int aPlane = 0) = 0;
+ virtual mozilla::gfx::SurfaceFormat GetFormat() = 0;
+ virtual mozilla::gfx::SurfaceFormat GetFormatGL() = 0;
+
+ virtual bool CreateTexture(mozilla::gl::GLContext* aGLContext,
+ int aPlane = 0) = 0;
+ virtual void ReleaseTextures() = 0;
+ virtual GLuint GetTexture(int aPlane = 0) = 0;
+ virtual EGLImageKHR GetEGLImage(int aPlane = 0) = 0;
+
+ SurfaceType GetSurfaceType() { return mSurfaceType; };
+ virtual int GetTextureCount() = 0;
+
+ bool IsMapped(int aPlane = 0) { return (mMappedRegion[aPlane] != nullptr); };
+ void Unmap(int aPlane = 0);
+
+ virtual DMABufSurfaceRGBA* GetAsDMABufSurfaceRGBA() { return nullptr; }
+ virtual DMABufSurfaceYUV* GetAsDMABufSurfaceYUV() { return nullptr; }
+ virtual already_AddRefed<mozilla::gfx::DataSourceSurface>
+ GetAsSourceSurface() {
+ return nullptr;
+ }
+
+ virtual nsresult BuildSurfaceDescriptorBuffer(
+ mozilla::layers::SurfaceDescriptorBuffer& aSdBuffer,
+ mozilla::layers::Image::BuildSdbFlags aFlags,
+ const std::function<mozilla::layers::MemoryOrShmem(uint32_t)>& aAllocate);
+
+ virtual mozilla::gfx::YUVColorSpace GetYUVColorSpace() {
+ return mozilla::gfx::YUVColorSpace::Default;
+ };
+
+ bool IsFullRange() { return mColorRange == mozilla::gfx::ColorRange::FULL; };
+ void SetColorRange(mozilla::gfx::ColorRange aColorRange) {
+ mColorRange = aColorRange;
+ };
+
+ void FenceSet();
+ void FenceWait();
+ void FenceDelete();
+
+ // Set and get a global surface UID. The UID is shared across process
+ // and it's used to track surface lifetime in various parts of rendering
+ // engine.
+ uint32_t GetUID() const { return mUID; };
+
+ // Creates a global reference counter objects attached to the surface.
+ // It's created as unreferenced, i.e. IsGlobalRefSet() returns false
+ // right after GlobalRefCountCreate() call.
+ //
+ // The counter is shared by all surface instances across processes
+ // so it tracks global surface usage.
+ //
+ // The counter is automatically referenced when a new surface instance is
+ // created with SurfaceDescriptor (usually copied to another process over IPC)
+ // and it's unreferenced when surface is deleted.
+ //
+ // So without any additional GlobalRefAdd()/GlobalRefRelease() calls
+ // the IsGlobalRefSet() returns true if any other process use the surface.
+ void GlobalRefCountCreate();
+ void GlobalRefCountDelete();
+
+ // If global reference counter was created by GlobalRefCountCreate()
+ // returns true when there's an active surface reference.
+ bool IsGlobalRefSet() const;
+
+ // Add/Remove additional reference to the surface global reference counter.
+ void GlobalRefAdd();
+ void GlobalRefRelease();
+
+ // Release all underlying data.
+ virtual void ReleaseSurface() = 0;
+
+#ifdef DEBUG
+ virtual void DumpToFile(const char* pFile){};
+#endif
+
+ DMABufSurface(SurfaceType aSurfaceType);
+
+ protected:
+ virtual bool Create(const mozilla::layers::SurfaceDescriptor& aDesc) = 0;
+
+ // Import global ref count object from IPC by file descriptor.
+ // This adds global ref count reference to the surface.
+ void GlobalRefCountImport(int aFd);
+ // Export global ref count object by file descriptor.
+ int GlobalRefCountExport();
+
+ void ReleaseDMABuf();
+
+ void* MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride, int aGbmFlags, int aPlane = 0);
+
+ // We want to keep number of opened file descriptors low so open/close
+ // DMABuf file handles only when we need them, i.e. when DMABuf is exported
+ // to another process or to EGL.
+ virtual bool OpenFileDescriptorForPlane(
+ const mozilla::MutexAutoLock& aProofOfLock, int aPlane) = 0;
+ virtual void CloseFileDescriptorForPlane(
+ const mozilla::MutexAutoLock& aProofOfLock, int aPlane,
+ bool aForceClose = false) = 0;
+ bool OpenFileDescriptors(const mozilla::MutexAutoLock& aProofOfLock);
+ void CloseFileDescriptors(const mozilla::MutexAutoLock& aProofOfLock,
+ bool aForceClose = false);
+
+ virtual ~DMABufSurface();
+
+ SurfaceType mSurfaceType;
+ uint64_t mBufferModifiers[DMABUF_BUFFER_PLANES];
+
+ int mBufferPlaneCount;
+ int mDmabufFds[DMABUF_BUFFER_PLANES];
+ int32_t mDrmFormats[DMABUF_BUFFER_PLANES];
+ int32_t mStrides[DMABUF_BUFFER_PLANES];
+ int32_t mOffsets[DMABUF_BUFFER_PLANES];
+
+ struct gbm_bo* mGbmBufferObject[DMABUF_BUFFER_PLANES];
+ void* mMappedRegion[DMABUF_BUFFER_PLANES];
+ void* mMappedRegionData[DMABUF_BUFFER_PLANES];
+ uint32_t mMappedRegionStride[DMABUF_BUFFER_PLANES];
+
+ int mSyncFd;
+ EGLSyncKHR mSync;
+ RefPtr<mozilla::gl::GLContext> mGL;
+
+ int mGlobalRefCountFd;
+ uint32_t mUID;
+ mozilla::Mutex mSurfaceLock MOZ_UNANNOTATED;
+
+ mozilla::gfx::ColorRange mColorRange = mozilla::gfx::ColorRange::LIMITED;
+};
+
+class DMABufSurfaceRGBA final : public DMABufSurface {
+ public:
+ static already_AddRefed<DMABufSurfaceRGBA> CreateDMABufSurface(
+ int aWidth, int aHeight, int aDMABufSurfaceFlags);
+
+ static already_AddRefed<DMABufSurface> CreateDMABufSurface(
+ mozilla::gl::GLContext* aGLContext, const EGLImageKHR aEGLImage,
+ int aWidth, int aHeight);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor) override;
+
+ DMABufSurfaceRGBA* GetAsDMABufSurfaceRGBA() override { return this; }
+
+ void Clear();
+
+ void ReleaseSurface() override;
+
+ bool CopyFrom(class DMABufSurface* aSourceSurface);
+
+ int GetWidth(int aPlane = 0) override { return mWidth; };
+ int GetHeight(int aPlane = 0) override { return mHeight; };
+ mozilla::gfx::SurfaceFormat GetFormat() override;
+ mozilla::gfx::SurfaceFormat GetFormatGL() override;
+ bool HasAlpha();
+
+ void* MapReadOnly(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride = nullptr);
+ void* MapReadOnly(uint32_t* aStride = nullptr);
+ void* Map(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride = nullptr);
+ void* Map(uint32_t* aStride = nullptr);
+ void* GetMappedRegion(int aPlane = 0) { return mMappedRegion[aPlane]; };
+ uint32_t GetMappedRegionStride(int aPlane = 0) {
+ return mMappedRegionStride[aPlane];
+ };
+
+ bool CreateTexture(mozilla::gl::GLContext* aGLContext,
+ int aPlane = 0) override;
+ void ReleaseTextures() override;
+ GLuint GetTexture(int aPlane = 0) override { return mTexture; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) override { return mEGLImage; };
+
+#ifdef MOZ_WAYLAND
+ bool CreateWlBuffer();
+ void ReleaseWlBuffer();
+ wl_buffer* GetWlBuffer() { return mWlBuffer; };
+#endif
+
+ int GetTextureCount() override { return 1; };
+
+#ifdef DEBUG
+ void DumpToFile(const char* pFile) override;
+#endif
+
+ DMABufSurfaceRGBA();
+
+ private:
+ DMABufSurfaceRGBA(const DMABufSurfaceRGBA&) = delete;
+ DMABufSurfaceRGBA& operator=(const DMABufSurfaceRGBA&) = delete;
+ ~DMABufSurfaceRGBA();
+
+ bool Create(int aWidth, int aHeight, int aDMABufSurfaceFlags);
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc) override;
+ bool Create(mozilla::gl::GLContext* aGLContext, const EGLImageKHR aEGLImage,
+ int aWidth, int aHeight);
+
+ bool ImportSurfaceDescriptor(const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ bool OpenFileDescriptorForPlane(const mozilla::MutexAutoLock& aProofOfLock,
+ int aPlane) override;
+ void CloseFileDescriptorForPlane(const mozilla::MutexAutoLock& aProofOfLock,
+ int aPlane, bool aForceClose) override;
+
+ private:
+ int mSurfaceFlags;
+
+ int mWidth;
+ int mHeight;
+ mozilla::widget::GbmFormat* mGmbFormat;
+
+ EGLImageKHR mEGLImage;
+ GLuint mTexture;
+ uint32_t mGbmBufferFlags;
+#ifdef MOZ_WAYLAND
+ wl_buffer* mWlBuffer = nullptr;
+#endif
+};
+
+class DMABufSurfaceYUV final : public DMABufSurface {
+ public:
+ static already_AddRefed<DMABufSurfaceYUV> CreateYUVSurface(
+ int aWidth, int aHeight, void** aPixelData = nullptr,
+ int* aLineSizes = nullptr);
+ static already_AddRefed<DMABufSurfaceYUV> CreateYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth, int aHeight);
+ static already_AddRefed<DMABufSurfaceYUV> CopyYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aVaDesc, int aWidth, int aHeight);
+ static void ReleaseVADRMPRIMESurfaceDescriptor(
+ VADRMPRIMESurfaceDescriptor& aDesc);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor) override;
+
+ DMABufSurfaceYUV* GetAsDMABufSurfaceYUV() override { return this; };
+ already_AddRefed<mozilla::gfx::DataSourceSurface> GetAsSourceSurface()
+ override;
+
+ nsresult BuildSurfaceDescriptorBuffer(
+ mozilla::layers::SurfaceDescriptorBuffer& aSdBuffer,
+ mozilla::layers::Image::BuildSdbFlags aFlags,
+ const std::function<mozilla::layers::MemoryOrShmem(uint32_t)>& aAllocate)
+ override;
+
+ int GetWidth(int aPlane = 0) override { return mWidth[aPlane]; }
+ int GetHeight(int aPlane = 0) override { return mHeight[aPlane]; }
+ mozilla::gfx::SurfaceFormat GetFormat() override;
+ mozilla::gfx::SurfaceFormat GetFormatGL() override;
+
+ bool CreateTexture(mozilla::gl::GLContext* aGLContext,
+ int aPlane = 0) override;
+ void ReleaseTextures() override;
+
+ void ReleaseSurface() override;
+
+ GLuint GetTexture(int aPlane = 0) override { return mTexture[aPlane]; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) override {
+ return mEGLImage[aPlane];
+ };
+
+ int GetTextureCount() override;
+
+ void SetYUVColorSpace(mozilla::gfx::YUVColorSpace aColorSpace) {
+ mColorSpace = aColorSpace;
+ }
+ mozilla::gfx::YUVColorSpace GetYUVColorSpace() override {
+ return mColorSpace;
+ }
+
+ DMABufSurfaceYUV();
+
+ bool UpdateYUVData(void** aPixelData, int* aLineSizes);
+ bool UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth,
+ int aHeight, bool aCopy);
+ bool VerifyTextureCreation();
+
+ private:
+ DMABufSurfaceYUV(const DMABufSurfaceYUV&) = delete;
+ DMABufSurfaceYUV& operator=(const DMABufSurfaceYUV&) = delete;
+ ~DMABufSurfaceYUV();
+
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc) override;
+ bool Create(int aWidth, int aHeight, void** aPixelData, int* aLineSizes);
+ bool CreateYUVPlane(int aPlane);
+ bool CreateLinearYUVPlane(int aPlane, int aWidth, int aHeight,
+ int aDrmFormat);
+ void UpdateYUVPlane(int aPlane, void* aPixelData, int aLineSize);
+
+ bool MoveYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth,
+ int aHeight);
+ bool CopyYUVDataImpl(const VADRMPRIMESurfaceDescriptor& aDesc, int aWidth,
+ int aHeight);
+
+ bool ImportPRIMESurfaceDescriptor(const VADRMPRIMESurfaceDescriptor& aDesc,
+ int aWidth, int aHeight);
+ bool ImportSurfaceDescriptor(
+ const mozilla::layers::SurfaceDescriptorDMABuf& aDesc);
+
+ bool OpenFileDescriptorForPlane(const mozilla::MutexAutoLock& aProofOfLock,
+ int aPlane) override;
+ void CloseFileDescriptorForPlane(const mozilla::MutexAutoLock& aProofOfLock,
+ int aPlane, bool aForceClose) override;
+
+ bool CreateEGLImage(mozilla::gl::GLContext* aGLContext, int aPlane);
+ void ReleaseEGLImages(mozilla::gl::GLContext* aGLContext);
+
+ nsresult ReadIntoBuffer(uint8_t* aData, int32_t aStride,
+ const mozilla::gfx::IntSize& aSize,
+ mozilla::gfx::SurfaceFormat aFormat);
+
+ int mWidth[DMABUF_BUFFER_PLANES];
+ int mHeight[DMABUF_BUFFER_PLANES];
+ // Aligned size of the surface imported from VADRMPRIMESurfaceDescriptor.
+ // It's used only internally to create EGLImage as some GL drivers
+ // needs that (Bug 1724385).
+ int mWidthAligned[DMABUF_BUFFER_PLANES];
+ int mHeightAligned[DMABUF_BUFFER_PLANES];
+ EGLImageKHR mEGLImage[DMABUF_BUFFER_PLANES];
+ GLuint mTexture[DMABUF_BUFFER_PLANES];
+ mozilla::gfx::YUVColorSpace mColorSpace =
+ mozilla::gfx::YUVColorSpace::Default;
+};
+
+#endif
diff --git a/widget/gtk/GRefPtr.h b/widget/gtk/GRefPtr.h
new file mode 100644
index 0000000000..1970008811
--- /dev/null
+++ b/widget/gtk/GRefPtr.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GRefPtr_h_
+#define GRefPtr_h_
+
+// Allows to use RefPtr<T> with various kinds of GObjects
+
+#include <gdk/gdk.h>
+#include <gio/gio.h>
+#include <gtk/gtk.h>
+#include "mozilla/RefPtr.h"
+
+namespace mozilla {
+
+template <typename T>
+struct GObjectRefPtrTraits {
+ static void AddRef(T* aObject) { g_object_ref(aObject); }
+ static void Release(T* aObject) { g_object_unref(aObject); }
+};
+
+#define GOBJECT_TRAITS(type_) \
+ template <> \
+ struct RefPtrTraits<type_> : public GObjectRefPtrTraits<type_> {};
+
+GOBJECT_TRAITS(GtkWidget)
+GOBJECT_TRAITS(GFile)
+GOBJECT_TRAITS(GFileMonitor)
+GOBJECT_TRAITS(GMenu)
+GOBJECT_TRAITS(GMenuItem)
+GOBJECT_TRAITS(GSimpleAction)
+GOBJECT_TRAITS(GSimpleActionGroup)
+GOBJECT_TRAITS(GDBusProxy)
+GOBJECT_TRAITS(GAppInfo)
+GOBJECT_TRAITS(GAppLaunchContext)
+GOBJECT_TRAITS(GdkDragContext)
+GOBJECT_TRAITS(GDBusMessage)
+GOBJECT_TRAITS(GdkPixbuf)
+GOBJECT_TRAITS(GCancellable)
+GOBJECT_TRAITS(GtkIMContext)
+GOBJECT_TRAITS(GUnixFDList)
+GOBJECT_TRAITS(GtkCssProvider)
+GOBJECT_TRAITS(GDBusMethodInvocation)
+
+#undef GOBJECT_TRAITS
+
+template <>
+struct RefPtrTraits<GVariant> {
+ static void AddRef(GVariant* aVariant) { g_variant_ref(aVariant); }
+ static void Release(GVariant* aVariant) { g_variant_unref(aVariant); }
+};
+
+template <>
+struct RefPtrTraits<GHashTable> {
+ static void AddRef(GHashTable* aObject) { g_hash_table_ref(aObject); }
+ static void Release(GHashTable* aObject) { g_hash_table_unref(aObject); }
+};
+
+template <>
+struct RefPtrTraits<GDBusNodeInfo> {
+ static void AddRef(GDBusNodeInfo* aObject) { g_dbus_node_info_ref(aObject); }
+ static void Release(GDBusNodeInfo* aObject) {
+ g_dbus_node_info_unref(aObject);
+ }
+};
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gtk/GUniquePtr.h b/widget/gtk/GUniquePtr.h
new file mode 100644
index 0000000000..0d164f9369
--- /dev/null
+++ b/widget/gtk/GUniquePtr.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GUniquePtr_h_
+#define GUniquePtr_h_
+
+// Provides GUniquePtr to g_free a given pointer.
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+struct GFreeDeleter {
+ constexpr GFreeDeleter() = default;
+ void operator()(GdkEventCrossing* aPtr) const {
+ gdk_event_free((GdkEvent*)aPtr);
+ }
+ void operator()(GdkEventKey* aPtr) const { gdk_event_free((GdkEvent*)aPtr); }
+ void operator()(GdkEvent* aPtr) const { gdk_event_free(aPtr); }
+ void operator()(GError* aPtr) const { g_error_free(aPtr); }
+ void operator()(void* aPtr) const { g_free(aPtr); }
+ void operator()(GtkPaperSize* aPtr) const { gtk_paper_size_free(aPtr); }
+ void operator()(gchar** aPtr) const { g_strfreev(aPtr); }
+};
+
+template <typename T>
+using GUniquePtr = UniquePtr<T, GFreeDeleter>;
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gtk/GfxInfo.cpp b/widget/gtk/GfxInfo.cpp
new file mode 100644
index 0000000000..4dadbc81b5
--- /dev/null
+++ b/widget/gtk/GfxInfo.cpp
@@ -0,0 +1,1556 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GfxInfo.h"
+
+#include <cctype>
+#include <errno.h>
+#include <unistd.h>
+#include <string>
+#include <poll.h>
+#include <sys/types.h>
+#include <sys/utsname.h>
+#include <sys/wait.h>
+#include <glib.h>
+#include <fcntl.h>
+
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/SSE.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "nsCRTGlue.h"
+#include "nsExceptionHandler.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsUnicharUtils.h"
+#include "nsWhitespaceTokenizer.h"
+#include "prenv.h"
+#include "WidgetUtilsGtk.h"
+#include "MediaCodecsSupport.h"
+#include "nsAppRunner.h"
+
+// How long we wait for data from glxtest/vaapi test process in milliseconds.
+#define GFX_TEST_TIMEOUT 4000
+#define VAAPI_TEST_TIMEOUT 2000
+#define V4L2_TEST_TIMEOUT 2000
+
+#define GLX_PROBE_BINARY u"glxtest"_ns
+#define VAAPI_PROBE_BINARY u"vaapitest"_ns
+#define V4L2_PROBE_BINARY u"v4l2test"_ns
+
+namespace mozilla::widget {
+
+#ifdef DEBUG
+NS_IMPL_ISUPPORTS_INHERITED(GfxInfo, GfxInfoBase, nsIGfxInfoDebug)
+#endif
+
+int GfxInfo::sGLXTestPipe = -1;
+pid_t GfxInfo::sGLXTestPID = 0;
+
+// bits to use decoding codec information returned from glxtest
+constexpr int CODEC_HW_H264 = 1 << 4;
+constexpr int CODEC_HW_VP8 = 1 << 5;
+constexpr int CODEC_HW_VP9 = 1 << 6;
+constexpr int CODEC_HW_AV1 = 1 << 7;
+
+nsresult GfxInfo::Init() {
+ mGLMajorVersion = 0;
+ mGLMinorVersion = 0;
+ mHasTextureFromPixmap = false;
+ mIsMesa = false;
+ mIsAccelerated = true;
+ mIsWayland = false;
+ mIsXWayland = false;
+ mHasMultipleGPUs = false;
+ mGlxTestError = false;
+ return GfxInfoBase::Init();
+}
+
+void GfxInfo::AddCrashReportAnnotations() {
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterVendorID,
+ mVendorId);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AdapterDeviceID,
+ mDeviceId);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVendor, mDriverVendor);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::AdapterDriverVersion, mDriverVersion);
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::IsWayland,
+ mIsWayland);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::DesktopEnvironment,
+ GetDesktopEnvironmentIdentifier());
+
+ if (mHasMultipleGPUs) {
+ nsAutoCString note;
+ note.AppendLiteral("Has dual GPUs.");
+ if (!mSecondaryVendorId.IsEmpty()) {
+ note.AppendLiteral(" GPU #2: AdapterVendorID2: ");
+ note.Append(mSecondaryVendorId);
+ note.AppendLiteral(", AdapterDeviceID2: ");
+ note.Append(mSecondaryDeviceId);
+ }
+ CrashReporter::AppendAppNotesToCrashReport(note);
+ }
+}
+
+static bool MakeFdNonBlocking(int fd) {
+ return fcntl(fd, F_SETFL, fcntl(fd, F_GETFL, 0) | O_NONBLOCK) != -1;
+}
+
+static bool ManageChildProcess(const char* aProcessName, int* aPID, int* aPipe,
+ int aTimeout, char** aData) {
+ // Don't try anything if we failed before
+ if (*aPID < 0) {
+ return false;
+ }
+
+ GIOChannel* channel = nullptr;
+ *aData = nullptr;
+
+ auto free = mozilla::MakeScopeExit([&] {
+ if (channel) {
+ g_io_channel_unref(channel);
+ }
+ if (*aPipe >= 0) {
+ close(*aPipe);
+ *aPipe = -1;
+ }
+ });
+
+ const TimeStamp deadline =
+ TimeStamp::Now() + TimeDuration::FromMilliseconds(aTimeout);
+
+ struct pollfd pfd {};
+ pfd.fd = *aPipe;
+ pfd.events = POLLIN;
+
+ while (poll(&pfd, 1, aTimeout) != 1) {
+ if (errno != EAGAIN && errno != EINTR) {
+ gfxCriticalNote << "ManageChildProcess(" << aProcessName
+ << "): poll failed: " << strerror(errno) << "\n";
+ return false;
+ }
+ if (TimeStamp::Now() > deadline) {
+ gfxCriticalNote << "ManageChildProcess(" << aProcessName
+ << "): process hangs\n";
+ return false;
+ }
+ }
+
+ channel = g_io_channel_unix_new(*aPipe);
+ MakeFdNonBlocking(*aPipe);
+
+ GUniquePtr<GError> error;
+ gsize length = 0;
+ int ret;
+ do {
+ error = nullptr;
+ ret = g_io_channel_read_to_end(channel, aData, &length,
+ getter_Transfers(error));
+ } while (ret == G_IO_STATUS_AGAIN && TimeStamp::Now() < deadline);
+ if (error || ret != G_IO_STATUS_NORMAL) {
+ gfxCriticalNote << "ManageChildProcess(" << aProcessName
+ << "): failed to read data from child process: ";
+ if (error) {
+ gfxCriticalNote << error->message;
+ } else {
+ gfxCriticalNote << "timeout";
+ }
+ return false;
+ }
+
+ int status = 0;
+ int pid = *aPID;
+ *aPID = -1;
+
+ while (true) {
+ int ret = waitpid(pid, &status, WNOHANG);
+ if (ret > 0) {
+ break;
+ }
+ if (ret < 0) {
+ if (errno == ECHILD) {
+ // Bug 718629
+ // ECHILD happens when the glxtest process got reaped got reaped after a
+ // PR_CreateProcess as per bug 227246. This shouldn't matter, as we
+ // still seem to get the data from the pipe, and if we didn't, the
+ // outcome would be to blocklist anyway.
+ return true;
+ }
+ if (errno != EAGAIN && errno != EINTR) {
+ gfxCriticalNote << "ManageChildProcess(" << aProcessName
+ << "): waitpid failed: " << strerror(errno) << "\n";
+ return false;
+ }
+ }
+ if (TimeStamp::Now() > deadline) {
+ gfxCriticalNote << "ManageChildProcess(" << aProcessName
+ << "): process hangs\n";
+ return false;
+ }
+ // Wait 50ms to another waitpid() check.
+ usleep(50000);
+ }
+
+ return WIFEXITED(status) && WEXITSTATUS(status) == EXIT_SUCCESS;
+}
+
+// to understand this function, see bug 639842. We retrieve the OpenGL driver
+// information in a separate process to protect against bad drivers.
+void GfxInfo::GetData() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ // In some cases (xpcshell test, Profile manager etc.)
+ // FireGLXTestProcess() is not fired in advance
+ // so we call it here.
+ GfxInfo::FireGLXTestProcess();
+
+ GfxInfoBase::GetData();
+
+ char* glxData = nullptr;
+ auto free = mozilla::MakeScopeExit([&] { g_free((void*)glxData); });
+
+ bool error = !ManageChildProcess("glxtest", &sGLXTestPID, &sGLXTestPipe,
+ GFX_TEST_TIMEOUT, &glxData);
+ if (error) {
+ gfxCriticalNote << "glxtest: ManageChildProcess failed\n";
+ }
+
+ nsCString glVendor;
+ nsCString glRenderer;
+ nsCString glVersion;
+ nsCString textureFromPixmap;
+ nsCString testType;
+
+ // Available if GLX_MESA_query_renderer is supported.
+ nsCString mesaVendor;
+ nsCString mesaDevice;
+ nsCString mesaAccelerated;
+ // Available if using a DRI-based libGL stack.
+ nsCString driDriver;
+ nsCString adapterRam;
+
+ nsCString drmRenderDevice;
+
+ nsCString ddxDriver;
+
+ AutoTArray<nsCString, 2> pciVendors;
+ AutoTArray<nsCString, 2> pciDevices;
+
+ nsCString* stringToFill = nullptr;
+ bool logString = false;
+ bool errorLog = false;
+
+ char* bufptr = glxData;
+
+ while (true) {
+ char* line = NS_strtok("\n", &bufptr);
+ if (!line) break;
+ if (stringToFill) {
+ stringToFill->Assign(line);
+ stringToFill = nullptr;
+ } else if (logString) {
+ gfxCriticalNote << "glxtest: " << line;
+ logString = false;
+ } else if (!strcmp(line, "VENDOR")) {
+ stringToFill = &glVendor;
+ } else if (!strcmp(line, "RENDERER")) {
+ stringToFill = &glRenderer;
+ } else if (!strcmp(line, "VERSION")) {
+ stringToFill = &glVersion;
+ } else if (!strcmp(line, "TFP")) {
+ stringToFill = &textureFromPixmap;
+ } else if (!strcmp(line, "MESA_VENDOR_ID")) {
+ stringToFill = &mesaVendor;
+ } else if (!strcmp(line, "MESA_DEVICE_ID")) {
+ stringToFill = &mesaDevice;
+ } else if (!strcmp(line, "MESA_ACCELERATED")) {
+ stringToFill = &mesaAccelerated;
+ } else if (!strcmp(line, "MESA_VRAM")) {
+ stringToFill = &adapterRam;
+ } else if (!strcmp(line, "DDX_DRIVER")) {
+ stringToFill = &ddxDriver;
+ } else if (!strcmp(line, "DRI_DRIVER")) {
+ stringToFill = &driDriver;
+ } else if (!strcmp(line, "PCI_VENDOR_ID")) {
+ stringToFill = pciVendors.AppendElement();
+ } else if (!strcmp(line, "PCI_DEVICE_ID")) {
+ stringToFill = pciDevices.AppendElement();
+ } else if (!strcmp(line, "DRM_RENDERDEVICE")) {
+ stringToFill = &drmRenderDevice;
+ } else if (!strcmp(line, "TEST_TYPE")) {
+ stringToFill = &testType;
+ } else if (!strcmp(line, "WARNING")) {
+ logString = true;
+ } else if (!strcmp(line, "ERROR")) {
+ logString = true;
+ errorLog = true;
+ }
+ }
+
+ MOZ_ASSERT(pciDevices.Length() == pciVendors.Length(),
+ "Missing PCI vendors/devices");
+
+ size_t pciLen = std::min(pciVendors.Length(), pciDevices.Length());
+ mHasMultipleGPUs = pciLen > 1;
+
+ if (!strcmp(textureFromPixmap.get(), "TRUE")) mHasTextureFromPixmap = true;
+
+ // only useful for Linux kernel version check for FGLRX driver.
+ // assumes X client == X server, which is sad.
+ struct utsname unameobj {};
+ if (uname(&unameobj) >= 0) {
+ mOS.Assign(unameobj.sysname);
+ mOSRelease.Assign(unameobj.release);
+ }
+
+ const char* spoofedVendor = PR_GetEnv("MOZ_GFX_SPOOF_GL_VENDOR");
+ if (spoofedVendor) glVendor.Assign(spoofedVendor);
+ const char* spoofedRenderer = PR_GetEnv("MOZ_GFX_SPOOF_GL_RENDERER");
+ if (spoofedRenderer) glRenderer.Assign(spoofedRenderer);
+ const char* spoofedVersion = PR_GetEnv("MOZ_GFX_SPOOF_GL_VERSION");
+ if (spoofedVersion) glVersion.Assign(spoofedVersion);
+ const char* spoofedOS = PR_GetEnv("MOZ_GFX_SPOOF_OS");
+ if (spoofedOS) mOS.Assign(spoofedOS);
+ const char* spoofedOSRelease = PR_GetEnv("MOZ_GFX_SPOOF_OS_RELEASE");
+ if (spoofedOSRelease) mOSRelease.Assign(spoofedOSRelease);
+
+ // Scan the GL_VERSION string for the GL and driver versions.
+ nsCWhitespaceTokenizer tokenizer(glVersion);
+ while (tokenizer.hasMoreTokens()) {
+ nsCString token(tokenizer.nextToken());
+ unsigned int major = 0, minor = 0, revision = 0, patch = 0;
+ if (sscanf(token.get(), "%u.%u.%u.%u", &major, &minor, &revision, &patch) >=
+ 2) {
+ // A survey of GL_VENDOR strings indicates that the first version is
+ // always the GL version, the second is usually the driver version.
+ if (mGLMajorVersion == 0) {
+ mGLMajorVersion = major;
+ mGLMinorVersion = minor;
+ } else if (mDriverVersion.IsEmpty()) { // Not already spoofed.
+ mDriverVersion =
+ nsPrintfCString("%u.%u.%u.%u", major, minor, revision, patch);
+ }
+ }
+ }
+
+ if (mGLMajorVersion == 0) {
+ NS_WARNING("Failed to parse GL version!");
+ }
+
+ mDrmRenderDevice = std::move(drmRenderDevice);
+ mTestType = std::move(testType);
+
+ // Mesa always exposes itself in the GL_VERSION string, but not always the
+ // GL_VENDOR string.
+ mIsMesa = glVersion.Find("Mesa") != -1;
+
+ // We need to use custom driver vendor IDs for mesa so we can treat them
+ // differently than the proprietary drivers.
+ if (mIsMesa) {
+ mIsAccelerated = !mesaAccelerated.Equals("FALSE");
+ // Process software rasterizers before the DRI driver string; we may be
+ // forcing software rasterization on a DRI-accelerated X server by using
+ // LIBGL_ALWAYS_SOFTWARE or a similar restriction.
+ if (strcasestr(glRenderer.get(), "llvmpipe")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaLLVMPipe),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (strcasestr(glRenderer.get(), "softpipe")) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaSoftPipe),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (strcasestr(glRenderer.get(), "software rasterizer")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverVendor::MesaSWRast),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (strcasestr(driDriver.get(), "vmwgfx")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverVendor::MesaVM),
+ mDriverVendor);
+ mIsAccelerated = false;
+ } else if (!mIsAccelerated) {
+ CopyUTF16toUTF8(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaSWUnknown),
+ mDriverVendor);
+ } else if (!driDriver.IsEmpty()) {
+ mDriverVendor = nsPrintfCString("mesa/%s", driDriver.get());
+ } else {
+ // Some other mesa configuration where we couldn't get enough info.
+ NS_WARNING("Failed to detect Mesa driver being used!");
+ CopyUTF16toUTF8(GfxDriverInfo::GetDriverVendor(DriverVendor::MesaUnknown),
+ mDriverVendor);
+ }
+
+ if (!mesaVendor.IsEmpty()) {
+ mVendorId = mesaVendor;
+ }
+
+ if (!mesaDevice.IsEmpty()) {
+ mDeviceId = mesaDevice;
+ }
+
+ if (!mIsAccelerated && mVendorId.IsEmpty()) {
+ mVendorId.Assign(glVendor.get());
+ }
+
+ if (!mIsAccelerated && mDeviceId.IsEmpty()) {
+ mDeviceId.Assign(glRenderer.get());
+ }
+ } else if (glVendor.EqualsLiteral("NVIDIA Corporation")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ mVendorId);
+ mDriverVendor.AssignLiteral("nvidia/unknown");
+ // TODO: Use NV-CONTROL X11 extension to query Device ID and VRAM.
+ } else if (glVendor.EqualsLiteral("ATI Technologies Inc.")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ mVendorId);
+ mDriverVendor.AssignLiteral("ati/unknown");
+ // TODO: Look into ways to find the device ID on FGLRX.
+ } else {
+ NS_WARNING("Failed to detect GL vendor!");
+ }
+
+ if (!adapterRam.IsEmpty()) {
+ mAdapterRAM = (uint32_t)atoi(adapterRam.get());
+ }
+
+ // If we have the DRI driver, we can derive the vendor ID from that if needed.
+ if (mVendorId.IsEmpty() && !driDriver.IsEmpty()) {
+ const char* nvidiaDrivers[] = {"nouveau", "tegra", nullptr};
+ for (size_t i = 0; nvidiaDrivers[i]; ++i) {
+ if (driDriver.Equals(nvidiaDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::NVIDIA),
+ mVendorId);
+ break;
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ const char* intelDrivers[] = {"iris", "crocus", "i915", "i965",
+ "i810", "intel", nullptr};
+ for (size_t i = 0; intelDrivers[i]; ++i) {
+ if (driDriver.Equals(intelDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::Intel),
+ mVendorId);
+ break;
+ }
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ const char* amdDrivers[] = {"r600", "r200", "r100",
+ "radeon", "radeonsi", nullptr};
+ for (size_t i = 0; amdDrivers[i]; ++i) {
+ if (driDriver.Equals(amdDrivers[i])) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::ATI),
+ mVendorId);
+ break;
+ }
+ }
+ }
+
+ if (mVendorId.IsEmpty()) {
+ if (driDriver.EqualsLiteral("freedreno")) {
+ CopyUTF16toUTF8(GfxDriverInfo::GetDeviceVendor(DeviceVendor::Qualcomm),
+ mVendorId);
+ }
+ }
+ }
+
+ // If we still don't have a vendor ID, we can try the PCI vendor list.
+ if (mVendorId.IsEmpty()) {
+ if (pciVendors.IsEmpty()) {
+ gfxCriticalNote << "No GPUs detected via PCI\n";
+ } else {
+ for (size_t i = 0; i < pciVendors.Length(); ++i) {
+ if (mVendorId.IsEmpty()) {
+ mVendorId = pciVendors[i];
+ } else if (mVendorId != pciVendors[i]) {
+ gfxCriticalNote << "More than 1 GPU vendor detected via PCI, cannot "
+ "deduce vendor\n";
+ mVendorId.Truncate();
+ break;
+ }
+ }
+ }
+ }
+
+ // If we know the vendor ID, but didn't get a device ID, we can guess from the
+ // PCI device list.
+ if (mDeviceId.IsEmpty() && !mVendorId.IsEmpty()) {
+ for (size_t i = 0; i < pciLen; ++i) {
+ if (mVendorId.Equals(pciVendors[i])) {
+ if (mDeviceId.IsEmpty()) {
+ mDeviceId = pciDevices[i];
+ } else if (mDeviceId != pciDevices[i]) {
+ gfxCriticalNote << "More than 1 GPU from same vendor detected via "
+ "PCI, cannot deduce device\n";
+ mDeviceId.Truncate();
+ break;
+ }
+ }
+ }
+ }
+
+ // Assuming we know the vendor, we should check for a secondary card.
+ if (!mVendorId.IsEmpty()) {
+ if (pciLen > 2) {
+ gfxCriticalNote
+ << "More than 2 GPUs detected via PCI, secondary GPU is arbitrary\n";
+ }
+ for (size_t i = 0; i < pciLen; ++i) {
+ if (!mVendorId.Equals(pciVendors[i]) ||
+ (!mDeviceId.IsEmpty() && !mDeviceId.Equals(pciDevices[i]))) {
+ mSecondaryVendorId = pciVendors[i];
+ mSecondaryDeviceId = pciDevices[i];
+ break;
+ }
+ }
+ }
+
+ // If we couldn't choose, log them.
+ if (mVendorId.IsEmpty()) {
+ for (size_t i = 0; i < pciLen; ++i) {
+ gfxCriticalNote << "PCI candidate " << pciVendors[i].get() << "/"
+ << pciDevices[i].get() << "\n";
+ }
+ }
+
+ // Fallback to GL_VENDOR and GL_RENDERER.
+ if (mVendorId.IsEmpty()) {
+ mVendorId.Assign(glVendor.get());
+ }
+ if (mDeviceId.IsEmpty()) {
+ mDeviceId.Assign(glRenderer.get());
+ }
+
+ mAdapterDescription.Assign(glRenderer);
+
+ // Make a best effort guess at whether or not we are using the XWayland compat
+ // layer. For all intents and purposes, we should otherwise believe we are
+ // using X11.
+ mIsWayland = GdkIsWaylandDisplay();
+ mIsXWayland = IsXWaylandProtocol();
+
+ if (!ddxDriver.IsEmpty()) {
+ PRInt32 start = 0;
+ PRInt32 loc = ddxDriver.Find(";", start);
+ while (loc != kNotFound) {
+ nsCString line(ddxDriver.get() + start, loc - start);
+ mDdxDrivers.AppendElement(std::move(line));
+
+ start = loc + 1;
+ loc = ddxDriver.Find(";", start);
+ }
+ }
+
+ if (error || errorLog || mTestType.IsEmpty()) {
+ if (!mAdapterDescription.IsEmpty()) {
+ mAdapterDescription.AppendLiteral(" (See failure log)");
+ } else {
+ mAdapterDescription.AppendLiteral("See failure log");
+ }
+
+ mGlxTestError = true;
+ }
+
+ AddCrashReportAnnotations();
+}
+
+int GfxInfo::FireTestProcess(const nsAString& aBinaryFile, int* aOutPipe,
+ const char** aStringArgs) {
+ nsCOMPtr<nsIFile> appFile;
+ nsresult rv = XRE_GetBinaryPath(getter_AddRefs(appFile));
+ if (NS_FAILED(rv)) {
+ gfxCriticalNote << "Couldn't find application file.\n";
+ return false;
+ }
+ nsCOMPtr<nsIFile> exePath;
+ rv = appFile->GetParent(getter_AddRefs(exePath));
+ if (NS_FAILED(rv)) {
+ gfxCriticalNote << "Couldn't get application directory.\n";
+ return false;
+ }
+ exePath->Append(aBinaryFile);
+
+#define MAX_ARGS 8
+ char* argv[MAX_ARGS + 2];
+
+ argv[0] = strdup(exePath->NativePath().get());
+ for (int i = 0; i < MAX_ARGS; i++) {
+ if (aStringArgs[i]) {
+ argv[i + 1] = strdup(aStringArgs[i]);
+ } else {
+ argv[i + 1] = nullptr;
+ break;
+ }
+ }
+
+ // Use G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD flags
+ // to g_spawn_async_with_pipes() run posix_spawn() directly.
+ int pid;
+ GUniquePtr<GError> err;
+ g_spawn_async_with_pipes(
+ nullptr, argv, nullptr,
+ GSpawnFlags(G_SPAWN_LEAVE_DESCRIPTORS_OPEN | G_SPAWN_DO_NOT_REAP_CHILD),
+ nullptr, nullptr, &pid, nullptr, aOutPipe, nullptr,
+ getter_Transfers(err));
+ if (err) {
+ gfxCriticalNote << "FireTestProcess failed: " << err->message << "\n";
+ pid = 0;
+ }
+ for (auto& arg : argv) {
+ if (!arg) {
+ break;
+ }
+ free(arg);
+ }
+ return pid;
+}
+
+bool GfxInfo::FireGLXTestProcess() {
+ if (sGLXTestPID != 0) {
+ return true;
+ }
+
+ int pfd[2];
+ if (pipe(pfd) == -1) {
+ gfxCriticalNote << "FireGLXTestProcess failed to create pipe\n";
+ return false;
+ }
+ sGLXTestPipe = pfd[0];
+
+ auto pipeID = std::to_string(pfd[1]);
+ const char* args[] = {"-f", pipeID.c_str(),
+ IsWaylandEnabled() ? "-w" : nullptr, nullptr};
+ sGLXTestPID = FireTestProcess(GLX_PROBE_BINARY, nullptr, args);
+ // Set pid to -1 to avoid further test launch.
+ if (!sGLXTestPID) {
+ sGLXTestPID = -1;
+ }
+ close(pfd[1]);
+ return true;
+}
+
+void GfxInfo::GetDataVAAPI() {
+ if (mIsVAAPISupported.isSome()) {
+ return;
+ }
+ mIsVAAPISupported = Some(false);
+
+#ifdef MOZ_ENABLE_VAAPI
+ char* vaapiData = nullptr;
+ auto free = mozilla::MakeScopeExit([&] { g_free((void*)vaapiData); });
+
+ int vaapiPipe = -1;
+ int vaapiPID = 0;
+ const char* args[] = {"-d", mDrmRenderDevice.get(), nullptr};
+ vaapiPID = FireTestProcess(VAAPI_PROBE_BINARY, &vaapiPipe, args);
+ if (!vaapiPID) {
+ return;
+ }
+
+ if (!ManageChildProcess("vaapitest", &vaapiPID, &vaapiPipe,
+ VAAPI_TEST_TIMEOUT, &vaapiData)) {
+ gfxCriticalNote << "vaapitest: ManageChildProcess failed\n";
+ return;
+ }
+
+ char* bufptr = vaapiData;
+ char* line;
+ while ((line = NS_strtok("\n", &bufptr))) {
+ if (!strcmp(line, "VAAPI_SUPPORTED")) {
+ line = NS_strtok("\n", &bufptr);
+ if (!line) {
+ gfxCriticalNote << "vaapitest: Failed to get VAAPI support\n";
+ return;
+ }
+ mIsVAAPISupported = Some(!strcmp(line, "TRUE"));
+ } else if (!strcmp(line, "VAAPI_HWCODECS")) {
+ line = NS_strtok("\n", &bufptr);
+ if (!line) {
+ gfxCriticalNote << "vaapitest: Failed to get VAAPI codecs\n";
+ return;
+ }
+
+ std::istringstream(line) >> mVAAPISupportedCodecs;
+ if (mVAAPISupportedCodecs & CODEC_HW_H264) {
+ media::MCSInfo::AddSupport(
+ media::MediaCodecsSupport::H264HardwareDecode);
+ }
+ if (mVAAPISupportedCodecs & CODEC_HW_VP8) {
+ media::MCSInfo::AddSupport(
+ media::MediaCodecsSupport::VP8HardwareDecode);
+ }
+ if (mVAAPISupportedCodecs & CODEC_HW_VP9) {
+ media::MCSInfo::AddSupport(
+ media::MediaCodecsSupport::VP9HardwareDecode);
+ }
+ if (mVAAPISupportedCodecs & CODEC_HW_AV1) {
+ media::MCSInfo::AddSupport(
+ media::MediaCodecsSupport::AV1HardwareDecode);
+ }
+ } else if (!strcmp(line, "WARNING") || !strcmp(line, "ERROR")) {
+ gfxCriticalNote << "vaapitest: " << line;
+ line = NS_strtok("\n", &bufptr);
+ if (line) {
+ gfxCriticalNote << "vaapitest: " << line << "\n";
+ }
+ return;
+ }
+ }
+#endif
+}
+
+// Probe all V4L2 devices and check their capabilities
+void GfxInfo::GetDataV4L2() {
+ if (mIsV4L2Supported.isSome()) {
+ // We have already probed v4l2 support, no need to do it again.
+ return;
+ }
+ mIsV4L2Supported = Some(false);
+
+#ifdef MOZ_ENABLE_V4L2
+ DIR* dir = opendir("/dev");
+ if (!dir) {
+ gfxCriticalNote << "Could not list /dev\n";
+ return;
+ }
+ struct dirent* dir_entry;
+ while ((dir_entry = readdir(dir))) {
+ if (!strncmp(dir_entry->d_name, "video", 5)) {
+ nsCString path = "/dev/"_ns;
+ path += nsDependentCString(dir_entry->d_name);
+ V4L2ProbeDevice(path);
+ }
+ }
+ closedir(dir);
+#endif // MOZ_ENABLE_V4L2
+}
+
+// Check the capabilities of a single V4L2 device. If the device doesn't work
+// or doesn't support any codecs we recognise, then we just ignore it. If it
+// does support recognised codecs then add these codecs to the supported list
+// and mark V4L2 as supported: We only need a single working device to enable
+// V4L2, when we come to decode FFmpeg will probe all the devices and choose
+// the appropriate one.
+void GfxInfo::V4L2ProbeDevice(nsCString& dev) {
+ char* v4l2Data = nullptr;
+ auto free = mozilla::MakeScopeExit([&] { g_free((void*)v4l2Data); });
+
+ int v4l2Pipe = -1;
+ int v4l2PID = 0;
+ const char* args[] = {"-d", dev.get(), nullptr};
+ v4l2PID = FireTestProcess(V4L2_PROBE_BINARY, &v4l2Pipe, args);
+ if (!v4l2PID) {
+ gfxCriticalNote << "Failed to start v4l2test process\n";
+ return;
+ }
+
+ if (!ManageChildProcess("v4l2test", &v4l2PID, &v4l2Pipe, V4L2_TEST_TIMEOUT,
+ &v4l2Data)) {
+ gfxCriticalNote << "v4l2test: ManageChildProcess failed\n";
+ return;
+ }
+
+ char* bufptr = v4l2Data;
+ char* line;
+ nsTArray<nsCString> capFormats;
+ nsTArray<nsCString> outFormats;
+ bool supported = false;
+ // Use gfxWarning rather than gfxCriticalNote from here on because the
+ // errors/warnings output by v4l2test are generally just caused by devices
+ // which aren't M2M decoders. Set gfx.logging.level=5 to see these messages.
+
+ while ((line = NS_strtok("\n", &bufptr))) {
+ if (!strcmp(line, "V4L2_SUPPORTED")) {
+ line = NS_strtok("\n", &bufptr);
+ if (!line) {
+ gfxWarning() << "v4l2test: Failed to get V4L2 support\n";
+ return;
+ }
+ supported = !strcmp(line, "TRUE");
+ } else if (!strcmp(line, "V4L2_CAPTURE_FMTS")) {
+ line = NS_strtok("\n", &bufptr);
+ if (!line) {
+ gfxWarning() << "v4l2test: Failed to get V4L2 CAPTURE formats\n";
+ return;
+ }
+ char* capture_fmt;
+ while ((capture_fmt = NS_strtok(" ", &line))) {
+ capFormats.AppendElement(capture_fmt);
+ }
+ } else if (!strcmp(line, "V4L2_OUTPUT_FMTS")) {
+ line = NS_strtok("\n", &bufptr);
+ if (!line) {
+ gfxWarning() << "v4l2test: Failed to get V4L2 OUTPUT formats\n";
+ return;
+ }
+ char* output_fmt;
+ while ((output_fmt = NS_strtok(" ", &line))) {
+ outFormats.AppendElement(output_fmt);
+ }
+ } else if (!strcmp(line, "WARNING") || !strcmp(line, "ERROR")) {
+ line = NS_strtok("\n", &bufptr);
+ if (line) {
+ gfxWarning() << "v4l2test: " << line << "\n";
+ }
+ return;
+ }
+ }
+
+ // If overall SUPPORTED flag is not TRUE then stop now
+ if (!supported) {
+ return;
+ }
+
+ // Currently the V4L2 decode platform only supports YUV420 and NV12
+ if (!capFormats.Contains("YV12") && !capFormats.Contains("NV12")) {
+ return;
+ }
+
+ // Supported codecs
+ if (outFormats.Contains("H264")) {
+ mIsV4L2Supported = Some(true);
+ media::MCSInfo::AddSupport(media::MediaCodecsSupport::H264HardwareDecode);
+ mV4L2SupportedCodecs |= CODEC_HW_H264;
+ }
+}
+
+const nsTArray<GfxDriverInfo>& GfxInfo::GetGfxDriverInfo() {
+ if (!sDriverInfo->Length()) {
+ // Mesa 10.0 provides the GLX_MESA_query_renderer extension, which allows us
+ // to query device IDs backing a GL context for blocklisting.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::All,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(10, 0, 0, 0), "FEATURE_FAILURE_OLD_MESA", "Mesa 10.0");
+
+ // NVIDIA Mesa baseline (see bug 1714391).
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaNouveau, DeviceFamily::All,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(11, 0, 0, 0), "FEATURE_FAILURE_OLD_NV_MESA", "Mesa 11.0");
+
+ // NVIDIA baseline (ported from old blocklist)
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(257, 21, 0, 0), "FEATURE_FAILURE_OLD_NVIDIA", "NVIDIA 257.21");
+
+ // fglrx baseline (chosen arbitrarily as 2013-07-22 release).
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::AtiAll,
+ GfxDriverInfo::optionalFeatures,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(13, 15, 100, 1), "FEATURE_FAILURE_OLD_FGLRX", "fglrx 13.15.100.1");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER
+
+ // All Mesa software drivers, they should get Software WebRender instead.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::SoftwareMesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_FAILURE_SOFTWARE_GL",
+ "");
+
+ // Older generation Intel devices do not perform well with WebRender.
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::IntelWebRenderBlocked,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "INTEL_DEVICE_GEN5_OR_OLDER",
+ "");
+
+ // Nvidia Mesa baseline, see bug 1563859.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(18, 2, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 18.2.0.0");
+
+ // Disable on all older Nvidia drivers due to stability issues.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, V(470, 82, 0, 0),
+ "FEATURE_FAILURE_WEBRENDER_OLD_NVIDIA", "470.82.0");
+
+ // Older generation NVIDIA devices do not perform well with WebRender.
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::NvidiaWebRenderBlocked,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "NVIDIA_EARLY_TESLA_AND_C67_C68", "");
+
+ // Mesa baseline, chosen arbitrarily. Linux users are generally good about
+ // updating their Mesa libraries so we don't want to arbitarily support
+ // WebRender on old drivers with outstanding bugs to work around.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(17, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA", "Mesa 17.0.0.0");
+
+ // Mesa baseline for non-Intel/NVIDIA/ATI devices. These other devices will
+ // often have less mature drivers so let's block older Mesa versions.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaNonIntelNvidiaAtiAll,
+ DeviceFamily::All, nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(22, 2, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA_OTHER",
+ "Mesa 22.2.0.0");
+
+ // Bug 1690568 / Bug 1393793 - Require Mesa 17.3.0+ for devices using the
+ // AMD r600 driver to avoid shader compilation issues.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaR600, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(17, 3, 0, 0), "FEATURE_FAILURE_WEBRENDER_OLD_MESA_R600",
+ "Mesa 17.3.0.0");
+
+ // Disable on all ATI devices not using Mesa for now.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_FAILURE_WEBRENDER_NO_LINUX_ATI", "");
+
+ // Disable R600 GPUs with Mesa drivers.
+ // Bug 1673939 - Garbled text on RS880 GPUs with Mesa drivers.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::AmdR600,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_FAILURE_WEBRENDER_BUG_1673939",
+ "https://gitlab.freedesktop.org/mesa/mesa/-/issues/3720");
+
+ // Bug 1635186 - Poor performance with video playing in a background window
+ // on XWayland. Keep in sync with FEATURE_X11_EGL below to only enable them
+ // together by default. Only Mesa and Nvidia binary drivers are expected
+ // on Wayland rigth now.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::XWayland, DriverVendor::MesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(21, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_BUG_1635186",
+ "Mesa 21.0.0.0");
+
+ // Bug 1815481 - Disable mesa drivers in virtual machines.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaVM, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_FAILURE_WEBRENDER_MESA_VM", "");
+ // Disable hardware mesa drivers in virtual machines due to instability.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaVM, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBGL_USE_HARDWARE,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_WEBGL_MESA_VM", "");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_COMPOSITOR
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_WEBRENDER_COMPOSITOR,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_WEBRENDER_COMPOSITOR_DISABLED", "");
+
+ ////////////////////////////////////
+ // FEATURE_X11_EGL
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_X11_EGL, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(17, 0, 0, 0), "FEATURE_X11_EGL_OLD_MESA",
+ "Mesa 17.0.0.0");
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_X11_EGL, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(18, 2, 0, 0), "FEATURE_X11_EGL_OLD_MESA_NOUVEAU",
+ "Mesa 18.2.0.0");
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_X11_EGL, nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION,
+ DRIVER_LESS_THAN, V(470, 82, 0, 0),
+ "FEATURE_ROLLOUT_X11_EGL_NVIDIA_BINARY", "470.82.0");
+
+ // Disable on all AMD devices not using Mesa.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_X11_EGL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_FAILURE_X11_EGL_NO_LINUX_ATI", "");
+
+ ////////////////////////////////////
+ // FEATURE_DMABUF
+#ifdef EARLY_BETA_OR_EARLIER
+ // Disabled due to high volume crash tracked in bug 1788573, fixed in the
+ // 545 driver.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_DMABUF, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, V(545, 23, 6, 0), "FEATURE_FAILURE_BUG_1788573", "");
+#else
+ // Disabled due to high volume crash tracked in bug 1788573.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_DMABUF, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1788573",
+ "");
+#endif
+
+ ////////////////////////////////////
+ // FEATURE_DMABUF_SURFACE_EXPORT
+ // Disabled due to:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/6666
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/6796
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_DMABUF_SURFACE_EXPORT,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BROKEN_DRIVER", "");
+
+ // Disabled due to:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/6688
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::IntelAll,
+ nsIGfxInfo::FEATURE_DMABUF_SURFACE_EXPORT,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BROKEN_DRIVER", "");
+
+ // Disabled due to:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/6988
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::QualcommAll,
+ nsIGfxInfo::FEATURE_DMABUF_SURFACE_EXPORT,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_FAILURE_BROKEN_DRIVER", "");
+
+ ////////////////////////////////////
+ // FEATURE_HARDWARE_VIDEO_DECODING
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DRIVER_VERSION, DRIVER_LESS_THAN,
+ V(21, 0, 0, 0), "FEATURE_HARDWARE_VIDEO_DECODING_MESA",
+ "Mesa 21.0.0.0");
+
+ // Disable on all NVIDIA hardware
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::All, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_HARDWARE_VIDEO_DECODING_NO_LINUX_NVIDIA", "");
+
+ // Disable on all AMD devices not using Mesa.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_HARDWARE_VIDEO_DECODING_NO_LINUX_AMD", "");
+
+ // Disable on r600 driver due to decoding artifacts (Bug 1824307)
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaR600, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_HARDWARE_VIDEO_DECODING_NO_R600", "");
+
+ // Disable on AMD devices using broken Mesa (Bug 1832080).
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, V(23, 1, 1, 0),
+ "FEATURE_HARDWARE_VIDEO_DECODING_AMD_DISABLE", "Mesa 23.1.1.0");
+
+ // Disable on Release/late Beta on AMD
+#if !defined(EARLY_BETA_OR_EARLIER)
+ APPEND_TO_DRIVER_BLOCKLIST(OperatingSystem::Linux, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_HARDWARE_VIDEO_DECODING_DISABLE", "");
+#endif
+ ////////////////////////////////////
+ // FEATURE_HW_DECODED_VIDEO_ZERO_COPY - ALLOWLIST
+ APPEND_TO_DRIVER_BLOCKLIST2(OperatingSystem::Linux, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_ALLOW_ALWAYS,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_ROLLOUT_ALL");
+
+ // Disable on AMD devices using broken Mesa (Bug 1837138).
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaAll, DeviceFamily::AtiAll,
+ nsIGfxInfo::FEATURE_HW_DECODED_VIDEO_ZERO_COPY,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_LESS_THAN, V(23, 1, 1, 0),
+ "FEATURE_HARDWARE_VIDEO_ZERO_COPY_LINUX_AMD_DISABLE", "Mesa 23.1.1.0");
+
+ ////////////////////////////////////
+ // FEATURE_WEBRENDER_PARTIAL_PRESENT
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::X11, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_WEBRENDER_PARTIAL_PRESENT,
+ nsIGfxInfo::FEATURE_BLOCKED_DEVICE, DRIVER_COMPARISON_IGNORED,
+ V(0, 0, 0, 0), "FEATURE_ROLLOUT_WR_PARTIAL_PRESENT_NVIDIA_BINARY", "");
+
+ ////////////////////////////////////
+
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::MesaNouveau, DeviceFamily::All,
+ nsIGfxInfo::FEATURE_THREADSAFE_GL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0),
+ "FEATURE_FAILURE_THREADSAFE_GL_NOUVEAU", "");
+
+#ifdef EARLY_BETA_OR_EARLIER
+ // Disabled due to high volume crash tracked in bug 1788573, fixed in the
+ // 545 driver.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_THREADSAFE_GL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_LESS_THAN, V(545, 23, 6, 0), "FEATURE_FAILURE_BUG_1788573", "");
+#else
+ // Disabled due to high volume crash tracked in bug 1788573.
+ APPEND_TO_DRIVER_BLOCKLIST_EXT(
+ OperatingSystem::Linux, ScreenSizeStatus::All, BatteryStatus::All,
+ WindowProtocol::All, DriverVendor::NonMesaAll, DeviceFamily::NvidiaAll,
+ nsIGfxInfo::FEATURE_THREADSAFE_GL, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "FEATURE_FAILURE_BUG_1788573",
+ "");
+#endif
+
+ // AMD R600 family does not perform well with WebRender.
+ APPEND_TO_DRIVER_BLOCKLIST(
+ OperatingSystem::Linux, DeviceFamily::AmdR600,
+ nsIGfxInfo::FEATURE_WEBRENDER, nsIGfxInfo::FEATURE_BLOCKED_DEVICE,
+ DRIVER_COMPARISON_IGNORED, V(0, 0, 0, 0), "AMD_R600_FAMILY", "");
+ }
+ return *sDriverInfo;
+}
+
+bool GfxInfo::DoesWindowProtocolMatch(const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol) {
+ if (mIsWayland &&
+ aBlocklistWindowProtocol.Equals(
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::WaylandAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (!mIsWayland &&
+ aBlocklistWindowProtocol.Equals(
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::X11All),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ return GfxInfoBase::DoesWindowProtocolMatch(aBlocklistWindowProtocol,
+ aWindowProtocol);
+}
+
+bool GfxInfo::DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor) {
+ if (mIsMesa) {
+ if (aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::MesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (mIsAccelerated &&
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::HardwareMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (!mIsAccelerated &&
+ aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::SoftwareMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ if (aBlocklistVendor.Equals(GfxDriverInfo::GetDriverVendor(
+ DriverVendor::MesaNonIntelNvidiaAtiAll),
+ nsCaseInsensitiveStringComparator)) {
+ return !mVendorId.Equals("0x8086") && !mVendorId.Equals("0x10de") &&
+ !mVendorId.Equals("0x1002");
+ }
+ }
+ if (!mIsMesa && aBlocklistVendor.Equals(
+ GfxDriverInfo::GetDriverVendor(DriverVendor::NonMesaAll),
+ nsCaseInsensitiveStringComparator)) {
+ return true;
+ }
+ return GfxInfoBase::DoesDriverVendorMatch(aBlocklistVendor, aDriverVendor);
+}
+
+nsresult GfxInfo::GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS /* = nullptr */)
+
+{
+ NS_ENSURE_ARG_POINTER(aStatus);
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_UNKNOWN;
+ aSuggestedDriverVersion.SetIsVoid(true);
+ OperatingSystem os = OperatingSystem::Linux;
+ if (aOS) *aOS = os;
+
+ if (sShutdownOccurred) {
+ return NS_OK;
+ }
+
+ GetData();
+
+ if (mGlxTestError) {
+ // If glxtest failed, block most features by default.
+ if (OnlyAllowFeatureOnKnownConfig(aFeature)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_GLXTEST_FAILED";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ if (mGLMajorVersion == 1) {
+ // We're on OpenGL 1. In most cases that indicates really old hardware.
+ // We better block them, rather than rely on them to fail gracefully,
+ // because they don't! see bug 696636
+ if (OnlyAllowFeatureOnKnownConfig(aFeature)) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_OPENGL_1";
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ }
+ return NS_OK;
+ }
+
+ // Blocklist software GL implementations from using layers acceleration.
+ // On the test infrastructure, we'll force-enable layers acceleration.
+ if (aFeature == nsIGfxInfo::FEATURE_OPENGL_LAYERS && !mIsAccelerated &&
+ !PR_GetEnv("MOZ_LAYERS_ALLOW_SOFTWARE_GL")) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_SOFTWARE_GL";
+ return NS_OK;
+ }
+
+ if (aFeature == nsIGfxInfo::FEATURE_WEBRENDER) {
+ // Don't try Webrender on devices where we are guaranteed to fail.
+ if (mGLMajorVersion < 3) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_OPENGL_LESS_THAN_3";
+ return NS_OK;
+ }
+
+ // Bug 1710400: Disable Webrender on the deprecated Intel DDX driver
+ for (const nsCString& driver : mDdxDrivers) {
+ if (strcasestr(driver.get(), "Intel")) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_DEVICE;
+ aFailureId = "FEATURE_FAILURE_DDX_INTEL";
+ return NS_OK;
+ }
+ }
+ }
+
+ const struct {
+ int32_t mFeature;
+ int32_t mCodec;
+ } kFeatureToCodecs[] = {{nsIGfxInfo::FEATURE_H264_HW_DECODE, CODEC_HW_H264},
+ {nsIGfxInfo::FEATURE_VP8_HW_DECODE, CODEC_HW_VP8},
+ {nsIGfxInfo::FEATURE_VP9_HW_DECODE, CODEC_HW_VP9},
+ {nsIGfxInfo::FEATURE_AV1_HW_DECODE, CODEC_HW_AV1}};
+
+ for (const auto& pair : kFeatureToCodecs) {
+ if (aFeature != pair.mFeature) {
+ continue;
+ }
+ if ((mVAAPISupportedCodecs & pair.mCodec) ||
+ (mV4L2SupportedCodecs & pair.mCodec)) {
+ *aStatus = nsIGfxInfo::FEATURE_STATUS_OK;
+ } else {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST;
+ aFailureId = "FEATURE_FAILURE_VIDEO_DECODING_MISSING";
+ }
+ return NS_OK;
+ }
+
+ auto ret = GfxInfoBase::GetFeatureStatusImpl(
+ aFeature, aStatus, aSuggestedDriverVersion, aDriverInfo, aFailureId, &os);
+
+ // Probe VA-API/V4L2 on supported devices only
+ if (aFeature == nsIGfxInfo::FEATURE_HARDWARE_VIDEO_DECODING) {
+ if (!StaticPrefs::media_hardware_video_decoding_enabled_AtStartup()) {
+ return ret;
+ }
+ bool probeHWDecode =
+ mIsAccelerated &&
+ (*aStatus == nsIGfxInfo::FEATURE_STATUS_OK ||
+ StaticPrefs::media_hardware_video_decoding_force_enabled_AtStartup() ||
+ StaticPrefs::media_ffmpeg_vaapi_enabled_AtStartup());
+ if (probeHWDecode) {
+ GetDataVAAPI();
+ GetDataV4L2();
+ } else {
+ mIsVAAPISupported = Some(false);
+ mIsV4L2Supported = Some(false);
+ }
+ if (!mIsVAAPISupported.value() && !mIsV4L2Supported.value()) {
+ *aStatus = nsIGfxInfo::FEATURE_BLOCKED_PLATFORM_TEST;
+ aFailureId = "FEATURE_FAILURE_VIDEO_DECODING_TEST_FAILED";
+ }
+ }
+
+ return ret;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetD2DEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteEnabled(bool* aEnabled) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetDWriteVersion(nsAString& aDwriteVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP GfxInfo::GetHasBattery(bool* aHasBattery) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetEmbeddedInFirefoxReality(bool* aEmbeddedInFirefoxReality) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetCleartypeParameters(nsAString& aCleartypeParams) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetWindowProtocol(nsAString& aWindowProtocol) {
+ GetData();
+ if (mIsWayland) {
+ aWindowProtocol = GfxDriverInfo::GetWindowProtocol(WindowProtocol::Wayland);
+ } else if (mIsXWayland) {
+ aWindowProtocol =
+ GfxDriverInfo::GetWindowProtocol(WindowProtocol::XWayland);
+ } else {
+ aWindowProtocol = GfxDriverInfo::GetWindowProtocol(WindowProtocol::X11);
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::GFX_LINUX_WINDOW_PROTOCOL,
+ aWindowProtocol);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetTestType(nsAString& aTestType) {
+ GetData();
+ AppendASCIItoUTF16(mTestType, aTestType);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription(nsAString& aAdapterDescription) {
+ GetData();
+ AppendASCIItoUTF16(mAdapterDescription, aAdapterDescription);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDescription2(nsAString& aAdapterDescription) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM(uint32_t* aAdapterRAM) {
+ GetData();
+ *aAdapterRAM = mAdapterRAM;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterRAM2(uint32_t* aAdapterRAM) { return NS_ERROR_FAILURE; }
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver(nsAString& aAdapterDriver) {
+ aAdapterDriver.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriver2(nsAString& aAdapterDriver) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) {
+ GetData();
+ CopyASCIItoUTF16(mDriverVendor, aAdapterDriverVendor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) {
+ GetData();
+ CopyASCIItoUTF16(mDriverVersion, aAdapterDriverVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverVersion2(nsAString& aAdapterDriverVersion) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate(nsAString& aAdapterDriverDate) {
+ aAdapterDriverDate.Truncate();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDriverDate2(nsAString& aAdapterDriverDate) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID(nsAString& aAdapterVendorID) {
+ GetData();
+ CopyUTF8toUTF16(mVendorId, aAdapterVendorID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterVendorID2(nsAString& aAdapterVendorID) {
+ GetData();
+ CopyUTF8toUTF16(mSecondaryVendorId, aAdapterVendorID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID(nsAString& aAdapterDeviceID) {
+ GetData();
+ CopyUTF8toUTF16(mDeviceId, aAdapterDeviceID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterDeviceID2(nsAString& aAdapterDeviceID) {
+ GetData();
+ CopyUTF8toUTF16(mSecondaryDeviceId, aAdapterDeviceID);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetAdapterSubsysID2(nsAString& aAdapterSubsysID) {
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetIsGPU2Active(bool* aIsGPU2Active) {
+ // This is never the case, as the active GPU should be the primary GPU.
+ *aIsGPU2Active = false;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+GfxInfo::GetDrmRenderDevice(nsACString& aDrmRenderDevice) {
+ GetData();
+ aDrmRenderDevice.Assign(mDrmRenderDevice);
+ return NS_OK;
+}
+
+#ifdef DEBUG
+
+// Implement nsIGfxInfoDebug
+// We don't support spoofing anything on Linux
+
+NS_IMETHODIMP GfxInfo::SpoofVendorID(const nsAString& aVendorID) {
+ GetData();
+ CopyUTF16toUTF8(aVendorID, mVendorId);
+ mIsAccelerated = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDeviceID(const nsAString& aDeviceID) {
+ GetData();
+ CopyUTF16toUTF8(aDeviceID, mDeviceId);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofDriverVersion(const nsAString& aDriverVersion) {
+ GetData();
+ CopyUTF16toUTF8(aDriverVersion, mDriverVersion);
+ return NS_OK;
+}
+
+NS_IMETHODIMP GfxInfo::SpoofOSVersion(uint32_t aVersion) {
+ // We don't support OS versioning on Linux. There's just "Linux".
+ return NS_OK;
+}
+
+#endif
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/GfxInfo.h b/widget/gtk/GfxInfo.h
new file mode 100644
index 0000000000..26b4554b4a
--- /dev/null
+++ b/widget/gtk/GfxInfo.h
@@ -0,0 +1,137 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WIDGET_GTK_GFXINFO_h__
+#define WIDGET_GTK_GFXINFO_h__
+
+#include "GfxInfoBase.h"
+#include "nsString.h"
+
+namespace mozilla {
+namespace widget {
+
+class GfxInfo final : public GfxInfoBase {
+ public:
+ // We only declare the subset of nsIGfxInfo that we actually implement. The
+ // rest is brought forward from GfxInfoBase.
+ NS_IMETHOD GetD2DEnabled(bool* aD2DEnabled) override;
+ NS_IMETHOD GetDWriteEnabled(bool* aDWriteEnabled) override;
+ NS_IMETHOD GetDWriteVersion(nsAString& aDwriteVersion) override;
+ NS_IMETHOD GetEmbeddedInFirefoxReality(
+ bool* aEmbeddedInFirefoxReality) override;
+ NS_IMETHOD GetHasBattery(bool* aHasBattery) override;
+ NS_IMETHOD GetCleartypeParameters(nsAString& aCleartypeParams) override;
+ NS_IMETHOD GetWindowProtocol(nsAString& aWindowProtocol) override;
+ NS_IMETHOD GetTestType(nsAString& aTestType) override;
+ NS_IMETHOD GetAdapterDescription(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion(nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetAdapterDescription2(nsAString& aAdapterDescription) override;
+ NS_IMETHOD GetAdapterDriver2(nsAString& aAdapterDriver) override;
+ NS_IMETHOD GetAdapterVendorID2(nsAString& aAdapterVendorID) override;
+ NS_IMETHOD GetAdapterDeviceID2(nsAString& aAdapterDeviceID) override;
+ NS_IMETHOD GetAdapterSubsysID2(nsAString& aAdapterSubsysID) override;
+ NS_IMETHOD GetAdapterRAM2(uint32_t* aAdapterRAM) override;
+ NS_IMETHOD GetAdapterDriverVendor2(nsAString& aAdapterDriverVendor) override;
+ NS_IMETHOD GetAdapterDriverVersion2(
+ nsAString& aAdapterDriverVersion) override;
+ NS_IMETHOD GetAdapterDriverDate2(nsAString& aAdapterDriverDate) override;
+ NS_IMETHOD GetIsGPU2Active(bool* aIsGPU2Active) override;
+ NS_IMETHOD GetDrmRenderDevice(nsACString& aDrmRenderDevice) override;
+ using GfxInfoBase::GetFeatureStatus;
+ using GfxInfoBase::GetFeatureSuggestedDriverVersion;
+
+ virtual nsresult Init() override;
+
+ NS_IMETHOD_(void) GetData() override;
+
+ static bool FireGLXTestProcess();
+
+#ifdef DEBUG
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIGFXINFODEBUG
+#endif
+
+ protected:
+ ~GfxInfo() = default;
+
+ OperatingSystem GetOperatingSystem() override {
+ return OperatingSystem::Linux;
+ }
+ virtual nsresult GetFeatureStatusImpl(
+ int32_t aFeature, int32_t* aStatus, nsAString& aSuggestedDriverVersion,
+ const nsTArray<GfxDriverInfo>& aDriverInfo, nsACString& aFailureId,
+ OperatingSystem* aOS = nullptr) override;
+ virtual const nsTArray<GfxDriverInfo>& GetGfxDriverInfo() override;
+
+ virtual bool DoesWindowProtocolMatch(
+ const nsAString& aBlocklistWindowProtocol,
+ const nsAString& aWindowProtocol) override;
+
+ virtual bool DoesDriverVendorMatch(const nsAString& aBlocklistVendor,
+ const nsAString& aDriverVendor) override;
+ static int FireTestProcess(const nsAString& aBinaryFile, int* aOutPipe,
+ const char** aStringArgs);
+
+ private:
+ bool mInitialized = false;
+ nsCString mVendorId;
+ nsCString mDeviceId;
+ nsCString mDriverVendor;
+ nsCString mDriverVersion;
+ nsCString mAdapterDescription;
+ uint32_t mAdapterRAM;
+ nsCString mOS;
+ nsCString mOSRelease;
+ nsCString mTestType;
+
+ nsCString mSecondaryVendorId;
+ nsCString mSecondaryDeviceId;
+
+ nsCString mDrmRenderDevice;
+
+ nsTArray<nsCString> mDdxDrivers;
+
+ struct ScreenInfo {
+ uint32_t mWidth;
+ uint32_t mHeight;
+ bool mIsDefault;
+ };
+
+ nsTArray<ScreenInfo> mScreenInfo;
+ bool mHasTextureFromPixmap;
+ unsigned int mGLMajorVersion, mGLMinorVersion;
+ bool mIsMesa;
+ bool mIsAccelerated;
+ bool mIsWayland;
+ bool mIsXWayland;
+ bool mHasMultipleGPUs;
+ bool mGlxTestError;
+ mozilla::Maybe<bool> mIsVAAPISupported;
+ int mVAAPISupportedCodecs = 0;
+ mozilla::Maybe<bool> mIsV4L2Supported;
+ int mV4L2SupportedCodecs = 0;
+
+ static int sGLXTestPipe;
+ static pid_t sGLXTestPID;
+
+ void GetDataVAAPI();
+ void GetDataV4L2();
+ void V4L2ProbeDevice(nsCString& dev);
+ void AddCrashReportAnnotations();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* WIDGET_GTK_GFXINFO_h__ */
diff --git a/widget/gtk/GfxInfoUtils.h b/widget/gtk/GfxInfoUtils.h
new file mode 100644
index 0000000000..f4d96604e3
--- /dev/null
+++ b/widget/gtk/GfxInfoUtils.h
@@ -0,0 +1,98 @@
+/* vim: se cin sw=2 ts=2 et : */
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WIDGET_GTK_GFXINFO_UTILS_h__
+#define WIDGET_GTK_GFXINFO_UTILS_h__
+
+// An alternative to mozilla::Unused for use in (a) C code and (b) code where
+// linking with unused.o is difficult.
+#define MOZ_UNUSED(expr) \
+ do { \
+ if (expr) { \
+ (void)0; \
+ } \
+ } while (0)
+
+#define LOG_PIPE 2
+
+static bool enable_logging = false;
+static void log(const char* format, ...) {
+ if (!enable_logging) {
+ return;
+ }
+ va_list args;
+ va_start(args, format);
+ vfprintf(stderr, format, args);
+ va_end(args);
+}
+
+static int output_pipe = 1;
+static void close_logging() {
+ // we want to redirect to /dev/null stdout, stderr, and while we're at it,
+ // any PR logging file descriptors. To that effect, we redirect all positive
+ // file descriptors up to what open() returns here. In particular, 1 is stdout
+ // and 2 is stderr.
+ int fd = open("/dev/null", O_WRONLY);
+ for (int i = 1; i < fd; i++) {
+ if (output_pipe != i) {
+ dup2(fd, i);
+ }
+ }
+ close(fd);
+}
+
+// C++ standard collides with C standard in that it doesn't allow casting void*
+// to function pointer types. So the work-around is to convert first to size_t.
+// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/
+template <typename func_ptr_type>
+static func_ptr_type cast(void* ptr) {
+ return reinterpret_cast<func_ptr_type>(reinterpret_cast<size_t>(ptr));
+}
+
+#define BUFFER_SIZE_STEP 4000
+
+static char* test_buf = nullptr;
+static int test_bufsize = 0;
+static int test_length = 0;
+
+static void record_value(const char* format, ...) {
+ if (!test_buf || test_length + BUFFER_SIZE_STEP / 2 > test_bufsize) {
+ test_bufsize += BUFFER_SIZE_STEP;
+ test_buf = (char*)realloc(test_buf, test_bufsize);
+ }
+ int remaining = test_bufsize - test_length;
+
+ // Append the new values to the buffer, not to exceed the remaining space.
+ va_list args;
+ va_start(args, format);
+ int max_added = vsnprintf(test_buf + test_length, remaining, format, args);
+ va_end(args);
+
+ if (max_added >= remaining) {
+ test_length += remaining;
+ } else {
+ test_length += max_added;
+ }
+}
+
+#define record_error(str_, ...) record_value("ERROR\n" str_ "\n", ##__VA_ARGS__)
+#define record_warning(str_, ...) \
+ record_value("WARNING\n" str_ "\n", ##__VA_ARGS__)
+
+static void record_flush() {
+ if (!test_buf) {
+ return;
+ }
+ MOZ_UNUSED(write(output_pipe, test_buf, test_length));
+ if (enable_logging) {
+ MOZ_UNUSED(write(LOG_PIPE, test_buf, test_length));
+ }
+ free(test_buf);
+ test_buf = nullptr;
+}
+
+#endif /* WIDGET_GTK_GFXINFO_h__ */
diff --git a/widget/gtk/GtkCompositorWidget.cpp b/widget/gtk/GtkCompositorWidget.cpp
new file mode 100644
index 0000000000..a3443b9828
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.cpp
@@ -0,0 +1,247 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "GtkCompositorWidget.h"
+
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/widget/InProcessCompositorWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+#endif
+
+#ifdef MOZ_WAYLAND
+# include "mozilla/layers/NativeLayerWayland.h"
+#endif
+
+#ifdef MOZ_LOGGING
+# undef LOG
+# define LOG(str, ...) \
+ MOZ_LOG(IsPopup() ? gWidgetPopupLog : gWidgetLog, \
+ mozilla::LogLevel::Debug, \
+ ("[%p]: " str, mWidget.get(), ##__VA_ARGS__))
+#endif /* MOZ_LOGGING */
+
+namespace mozilla {
+namespace widget {
+
+GtkCompositorWidget::GtkCompositorWidget(
+ const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, RefPtr<nsWindow> aWindow)
+ : CompositorWidget(aOptions),
+ mWidget(std::move(aWindow)),
+ mClientSize(LayoutDeviceIntSize(aInitData.InitialClientSize()),
+ "GtkCompositorWidget::mClientSize") {
+#if defined(MOZ_X11)
+ if (GdkIsX11Display()) {
+ mXWindow = (Window)aInitData.XWindow();
+ ConfigureX11Backend(mXWindow, aInitData.Shaped());
+ LOG("GtkCompositorWidget::GtkCompositorWidget() [%p] mXWindow %p "
+ "mIsRenderingSuspended %d\n",
+ (void*)mWidget.get(), (void*)mXWindow, !!mIsRenderingSuspended);
+ }
+#endif
+#if defined(MOZ_WAYLAND)
+ if (GdkIsWaylandDisplay()) {
+ ConfigureWaylandBackend();
+ LOG("GtkCompositorWidget::GtkCompositorWidget() [%p] mWidget %p "
+ "mIsRenderingSuspended %d\n",
+ (void*)mWidget.get(), (void*)mWidget, !!mIsRenderingSuspended);
+ }
+#endif
+}
+
+GtkCompositorWidget::~GtkCompositorWidget() {
+ LOG("GtkCompositorWidget::~GtkCompositorWidget [%p]\n", (void*)mWidget.get());
+ DisableRendering();
+ RefPtr<nsIWidget> widget = mWidget.forget();
+ NS_ReleaseOnMainThread("GtkCompositorWidget::mWidget", widget.forget());
+}
+
+already_AddRefed<gfx::DrawTarget> GtkCompositorWidget::StartRemoteDrawing() {
+ return nullptr;
+}
+void GtkCompositorWidget::EndRemoteDrawing() {}
+
+already_AddRefed<gfx::DrawTarget>
+GtkCompositorWidget::StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) {
+ return mProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode);
+}
+
+void GtkCompositorWidget::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+nsIWidget* GtkCompositorWidget::RealWidget() { return mWidget; }
+
+void GtkCompositorWidget::NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) {
+ LOG("GtkCompositorWidget::NotifyClientSizeChanged() to %d x %d",
+ aClientSize.width, aClientSize.height);
+
+ auto size = mClientSize.Lock();
+ *size = aClientSize;
+}
+
+LayoutDeviceIntSize GtkCompositorWidget::GetClientSize() {
+ auto size = mClientSize.Lock();
+ return *size;
+}
+
+void GtkCompositorWidget::RemoteLayoutSizeUpdated(
+ const LayoutDeviceRect& aSize) {
+ if (!mWidget || !mWidget->IsWaitingForCompositorResume()) {
+ return;
+ }
+
+ LOG("GtkCompositorWidget::RemoteLayoutSizeUpdated() %d x %d",
+ (int)aSize.width, (int)aSize.height);
+
+ // We're waiting for layout to match widget size.
+ auto clientSize = mClientSize.Lock();
+ if (clientSize->width != (int)aSize.width ||
+ clientSize->height != (int)aSize.height) {
+ LOG("quit, client size doesn't match (%d x %d)", clientSize->width,
+ clientSize->height);
+ return;
+ }
+
+ mWidget->ResumeCompositorFromCompositorThread();
+}
+
+EGLNativeWindowType GtkCompositorWidget::GetEGLNativeWindow() {
+ EGLNativeWindowType window = nullptr;
+ if (mWidget) {
+ window = (EGLNativeWindowType)mWidget->GetNativeData(NS_NATIVE_EGL_WINDOW);
+ }
+#if defined(MOZ_X11)
+ if (mXWindow) {
+ window = (EGLNativeWindowType)mXWindow;
+ }
+#endif
+ LOG("GtkCompositorWidget::GetEGLNativeWindow [%p] window %p\n", mWidget.get(),
+ window);
+ return window;
+}
+
+bool GtkCompositorWidget::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+#if defined(MOZ_WAYLAND)
+ if (mWidget) {
+ return mWidget->SetEGLNativeWindowSize(aEGLWindowSize);
+ }
+#endif
+ return true;
+}
+
+LayoutDeviceIntRegion GtkCompositorWidget::GetTransparentRegion() {
+ // We need to clear target buffer alpha values of popup windows as
+ // SW-WR paints with alpha blending (see Bug 1674473).
+ if (!mWidget || mWidget->IsPopup()) {
+ return LayoutDeviceIntRect(LayoutDeviceIntPoint(0, 0), GetClientSize());
+ }
+
+ // Clear background of titlebar area to render titlebar
+ // transparent corners correctly.
+ return mWidget->GetTitlebarRect();
+}
+
+#ifdef MOZ_WAYLAND
+RefPtr<mozilla::layers::NativeLayerRoot>
+GtkCompositorWidget::GetNativeLayerRoot() {
+ if (gfx::gfxVars::UseWebRenderCompositor()) {
+ if (!mNativeLayerRoot) {
+ MOZ_ASSERT(mWidget && mWidget->GetMozContainer());
+ mNativeLayerRoot = NativeLayerRootWayland::CreateForMozContainer(
+ mWidget->GetMozContainer());
+ }
+ return mNativeLayerRoot;
+ }
+ return nullptr;
+}
+#endif
+
+void GtkCompositorWidget::DisableRendering() {
+ LOG("GtkCompositorWidget::DisableRendering [%p]\n", (void*)mWidget.get());
+ mIsRenderingSuspended = true;
+ mProvider.CleanupResources();
+#if defined(MOZ_X11)
+ mXWindow = {};
+#endif
+}
+
+#if defined(MOZ_WAYLAND)
+bool GtkCompositorWidget::ConfigureWaylandBackend() {
+ mProvider.Initialize(this);
+ return true;
+}
+#endif
+
+#if defined(MOZ_X11)
+bool GtkCompositorWidget::ConfigureX11Backend(Window aXWindow, bool aShaped) {
+ mXWindow = aXWindow;
+
+ // We don't have X window yet.
+ if (!mXWindow) {
+ mIsRenderingSuspended = true;
+ return false;
+ }
+
+ // Grab the window's visual and depth
+ XWindowAttributes windowAttrs;
+ if (!XGetWindowAttributes(DefaultXDisplay(), mXWindow, &windowAttrs)) {
+ NS_WARNING("GtkCompositorWidget(): XGetWindowAttributes() failed!");
+ return false;
+ }
+
+ Visual* visual = windowAttrs.visual;
+ int depth = windowAttrs.depth;
+
+ // Initialize the window surface provider
+ mProvider.Initialize(mXWindow, visual, depth, aShaped);
+ return true;
+}
+#endif
+
+void GtkCompositorWidget::EnableRendering(const uintptr_t aXWindow,
+ const bool aShaped) {
+ LOG("GtkCompositorWidget::EnableRendering() [%p]\n", mWidget.get());
+
+ if (!mIsRenderingSuspended) {
+ LOG(" quit, mIsRenderingSuspended = false\n");
+ return;
+ }
+#if defined(MOZ_WAYLAND)
+ if (GdkIsWaylandDisplay()) {
+ LOG(" configure widget %p\n", mWidget.get());
+ if (!ConfigureWaylandBackend()) {
+ return;
+ }
+ }
+#endif
+#if defined(MOZ_X11)
+ if (GdkIsX11Display()) {
+ LOG(" configure XWindow %p shaped %d\n", (void*)aXWindow, aShaped);
+ if (!ConfigureX11Backend((Window)aXWindow, aShaped)) {
+ return;
+ }
+ }
+#endif
+ mIsRenderingSuspended = false;
+}
+#ifdef MOZ_LOGGING
+bool GtkCompositorWidget::IsPopup() {
+ return mWidget ? mWidget->IsPopup() : false;
+}
+#endif
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/GtkCompositorWidget.h b/widget/gtk/GtkCompositorWidget.h
new file mode 100644
index 0000000000..5bf89835d7
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef widget_gtk_GtkCompositorWidget_h
+#define widget_gtk_GtkCompositorWidget_h
+
+#include "GLDefs.h"
+#include "mozilla/DataMutex.h"
+#include "mozilla/widget/CompositorWidget.h"
+#include "WindowSurfaceProvider.h"
+
+class nsIWidget;
+class nsWindow;
+
+namespace mozilla {
+
+namespace layers {
+class NativeLayerRootWayland;
+} // namespace layers
+
+namespace widget {
+
+class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
+ public:
+ virtual void NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) = 0;
+ virtual GtkCompositorWidget* AsGtkCompositorWidget() { return nullptr; };
+
+ virtual void DisableRendering() = 0;
+ virtual void EnableRendering(const uintptr_t aXWindow,
+ const bool aShaped) = 0;
+
+ // CompositorWidgetDelegate Overrides
+
+ PlatformCompositorWidgetDelegate* AsPlatformSpecificDelegate() override {
+ return this;
+ }
+};
+
+class GtkCompositorWidgetInitData;
+
+class GtkCompositorWidget : public CompositorWidget,
+ public PlatformCompositorWidgetDelegate {
+ public:
+ GtkCompositorWidget(const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ RefPtr<nsWindow> aWindow /* = nullptr*/);
+ ~GtkCompositorWidget();
+
+ // CompositorWidget Overrides
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ LayoutDeviceIntSize GetClientSize() override;
+ void RemoteLayoutSizeUpdated(const LayoutDeviceRect& aSize);
+
+ nsIWidget* RealWidget() override;
+ GtkCompositorWidget* AsGTK() override { return this; }
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ EGLNativeWindowType GetEGLNativeWindow();
+
+ LayoutDeviceIntRegion GetTransparentRegion() override;
+
+ // Suspend rendering of this remote widget and clear all resources.
+ // Can be used when underlying window is hidden/unmapped.
+ void DisableRendering() override;
+
+ // Resume rendering with to given aXWindow (X11) or nsWindow (Wayland).
+ void EnableRendering(const uintptr_t aXWindow, const bool aShaped) override;
+
+ // If we fail to set window size (due to different screen scale or so)
+ // we can't paint the frame by compositor.
+ bool SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+
+#if defined(MOZ_X11)
+ Window XWindow() const { return mXWindow; }
+#endif
+#if defined(MOZ_WAYLAND)
+ RefPtr<mozilla::layers::NativeLayerRoot> GetNativeLayerRoot() override;
+#endif
+
+ bool PreRender(WidgetRenderingContext* aContext) override {
+ return !mIsRenderingSuspended;
+ }
+ bool IsHidden() const override { return mIsRenderingSuspended; }
+
+ // PlatformCompositorWidgetDelegate Overrides
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+ GtkCompositorWidget* AsGtkCompositorWidget() override { return this; }
+
+ private:
+#if defined(MOZ_WAYLAND)
+ bool ConfigureWaylandBackend();
+#endif
+#if defined(MOZ_X11)
+ bool ConfigureX11Backend(Window aXWindow, bool aShaped);
+#endif
+#ifdef MOZ_LOGGING
+ bool IsPopup();
+#endif
+
+ protected:
+ RefPtr<nsWindow> mWidget;
+
+ private:
+ // This field is written to on the main thread and read from on the compositor
+ // or renderer thread. During window resizing, this is subject to a (largely
+ // benign) read/write race, see bug 1665726. The DataMutex doesn't prevent the
+ // read/write race, but it does make it Not Undefined Behaviour, and also
+ // ensures we only ever use the old or new size, and not some weird synthesis
+ // of the two.
+ DataMutex<LayoutDeviceIntSize> mClientSize;
+
+ WindowSurfaceProvider mProvider;
+
+#if defined(MOZ_X11)
+ Window mXWindow = {};
+#endif
+#ifdef MOZ_WAYLAND
+ RefPtr<mozilla::layers::NativeLayerRootWayland> mNativeLayerRoot;
+#endif
+ Atomic<bool> mIsRenderingSuspended{true};
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_GtkCompositorWidget_h
diff --git a/widget/gtk/IMContextWrapper.cpp b/widget/gtk/IMContextWrapper.cpp
new file mode 100644
index 0000000000..fc87acbf86
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -0,0 +1,3358 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+#include "nsString.h"
+#include "prtime.h"
+#include "prenv.h"
+
+#include "IMContextWrapper.h"
+
+#include "GRefPtr.h"
+#include "nsGtkKeyUtils.h"
+#include "nsWindow.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Likely.h"
+#include "mozilla/LookAndFeel.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_intl.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "mozilla/WritingModes.h"
+
+// For collecting other people's log, tell `MOZ_LOG=IMEHandler:4,sync`
+// rather than `MOZ_LOG=IMEHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+mozilla::LazyLogModule gIMELog("IMEHandler");
+
+namespace mozilla {
+namespace widget {
+
+static inline const char* ToChar(bool aBool) {
+ return aBool ? "true" : "false";
+}
+
+static const char* GetEventType(GdkEventKey* aKeyEvent) {
+ switch (aKeyEvent->type) {
+ case GDK_KEY_PRESS:
+ return "GDK_KEY_PRESS";
+ case GDK_KEY_RELEASE:
+ return "GDK_KEY_RELEASE";
+ default:
+ return "Unknown";
+ }
+}
+
+class GetEventStateName : public nsAutoCString {
+ public:
+ explicit GetEventStateName(guint aState,
+ IMContextWrapper::IMContextID aIMContextID =
+ IMContextWrapper::IMContextID::Unknown) {
+ if (aState & GDK_SHIFT_MASK) {
+ AppendModifier("shift");
+ }
+ if (aState & GDK_CONTROL_MASK) {
+ AppendModifier("control");
+ }
+ if (aState & GDK_MOD1_MASK) {
+ AppendModifier("mod1");
+ }
+ if (aState & GDK_MOD2_MASK) {
+ AppendModifier("mod2");
+ }
+ if (aState & GDK_MOD3_MASK) {
+ AppendModifier("mod3");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod4");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod5");
+ }
+ if (aState & GDK_MOD4_MASK) {
+ AppendModifier("mod5");
+ }
+ switch (aIMContextID) {
+ case IMContextWrapper::IMContextID::IBus:
+ static const guint IBUS_HANDLED_MASK = 1 << 24;
+ static const guint IBUS_IGNORED_MASK = 1 << 25;
+ if (aState & IBUS_HANDLED_MASK) {
+ AppendModifier("IBUS_HANDLED_MASK");
+ }
+ if (aState & IBUS_IGNORED_MASK) {
+ AppendModifier("IBUS_IGNORED_MASK");
+ }
+ break;
+ case IMContextWrapper::IMContextID::Fcitx:
+ case IMContextWrapper::IMContextID::Fcitx5:
+ static const guint FcitxKeyState_HandledMask = 1 << 24;
+ static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+ if (aState & FcitxKeyState_HandledMask) {
+ AppendModifier("FcitxKeyState_HandledMask");
+ }
+ if (aState & FcitxKeyState_IgnoredMask) {
+ AppendModifier("FcitxKeyState_IgnoredMask");
+ }
+ break;
+ default:
+ break;
+ }
+ }
+
+ private:
+ void AppendModifier(const char* aModifierName) {
+ if (!IsEmpty()) {
+ AppendLiteral(" + ");
+ }
+ Append(aModifierName);
+ }
+};
+
+class GetTextRangeStyleText final : public nsAutoCString {
+ public:
+ explicit GetTextRangeStyleText(const TextRangeStyle& aStyle) {
+ if (!aStyle.IsDefined()) {
+ AssignLiteral("{ IsDefined()=false }");
+ return;
+ }
+
+ if (aStyle.IsLineStyleDefined()) {
+ AppendLiteral("{ mLineStyle=");
+ AppendLineStyle(aStyle.mLineStyle);
+ if (aStyle.IsUnderlineColorDefined()) {
+ AppendLiteral(", mUnderlineColor=");
+ AppendColor(aStyle.mUnderlineColor);
+ } else {
+ AppendLiteral(", IsUnderlineColorDefined=false");
+ }
+ } else {
+ AppendLiteral("{ IsLineStyleDefined()=false");
+ }
+
+ if (aStyle.IsForegroundColorDefined()) {
+ AppendLiteral(", mForegroundColor=");
+ AppendColor(aStyle.mForegroundColor);
+ } else {
+ AppendLiteral(", IsForegroundColorDefined()=false");
+ }
+
+ if (aStyle.IsBackgroundColorDefined()) {
+ AppendLiteral(", mBackgroundColor=");
+ AppendColor(aStyle.mBackgroundColor);
+ } else {
+ AppendLiteral(", IsBackgroundColorDefined()=false");
+ }
+
+ AppendLiteral(" }");
+ }
+ void AppendLineStyle(TextRangeStyle::LineStyle aLineStyle) {
+ switch (aLineStyle) {
+ case TextRangeStyle::LineStyle::None:
+ AppendLiteral("LineStyle::None");
+ break;
+ case TextRangeStyle::LineStyle::Solid:
+ AppendLiteral("LineStyle::Solid");
+ break;
+ case TextRangeStyle::LineStyle::Dotted:
+ AppendLiteral("LineStyle::Dotted");
+ break;
+ case TextRangeStyle::LineStyle::Dashed:
+ AppendLiteral("LineStyle::Dashed");
+ break;
+ case TextRangeStyle::LineStyle::Double:
+ AppendLiteral("LineStyle::Double");
+ break;
+ case TextRangeStyle::LineStyle::Wavy:
+ AppendLiteral("LineStyle::Wavy");
+ break;
+ default:
+ AppendPrintf("Invalid(0x%02X)",
+ static_cast<TextRangeStyle::LineStyleType>(aLineStyle));
+ break;
+ }
+ }
+ void AppendColor(nscolor aColor) {
+ AppendPrintf("{ R=0x%02X, G=0x%02X, B=0x%02X, A=0x%02X }", NS_GET_R(aColor),
+ NS_GET_G(aColor), NS_GET_B(aColor), NS_GET_A(aColor));
+ }
+ virtual ~GetTextRangeStyleText() = default;
+};
+
+const static bool kUseSimpleContextDefault = false;
+
+/******************************************************************************
+ * SelectionStyleProvider
+ *
+ * IME (e.g., fcitx, ~4.2.8.3) may look up selection colors of widget, which
+ * is related to the window associated with the IM context, to support any
+ * colored widgets. Our editor (like <input type="text">) is rendered as
+ * native GtkTextView as far as possible by default and if editor color is
+ * changed by web apps, nsTextFrame may swap background color of foreground
+ * color of composition string for making composition string is always
+ * visually distinct in normal text.
+ *
+ * So, we would like IME to set style of composition string to good colors
+ * in GtkTextView. Therefore, this class overwrites selection colors of
+ * our widget with selection colors of GtkTextView so that it's possible IME
+ * to refer selection colors of GtkTextView via our widget.
+ ******************************************************************************/
+
+static Maybe<nscolor> GetSystemColor(LookAndFeel::ColorID aId) {
+ return LookAndFeel::GetColor(aId, LookAndFeel::ColorScheme::Light,
+ LookAndFeel::UseStandins::No);
+}
+
+class SelectionStyleProvider final {
+ public:
+ static SelectionStyleProvider* GetExistingInstance() { return sInstance; }
+
+ static SelectionStyleProvider* GetInstance() {
+ if (sHasShutDown) {
+ return nullptr;
+ }
+ if (!sInstance) {
+ sInstance = new SelectionStyleProvider();
+ }
+ return sInstance;
+ }
+
+ static void Shutdown() {
+ if (sInstance) {
+ g_object_unref(sInstance->mProvider);
+ }
+ delete sInstance;
+ sInstance = nullptr;
+ sHasShutDown = true;
+ }
+
+ // mContainer associated with an IM context.
+ void AttachTo(MozContainer* aContainer) {
+ gtk_style_context_add_provider(
+ gtk_widget_get_style_context(GTK_WIDGET(aContainer)),
+ GTK_STYLE_PROVIDER(mProvider), GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+ }
+
+ void OnThemeChanged() {
+ // fcitx refers GtkStyle::text[GTK_STATE_SELECTED] and
+ // GtkStyle::bg[GTK_STATE_SELECTED] (although pair of text and *base*
+ // or *fg* and bg is correct). gtk_style_update_from_context() will
+ // set these colors using the widget's GtkStyleContext and so the
+ // colors can be controlled by a ":selected" CSS rule.
+ nsAutoCString style(":selected{");
+ // FYI: LookAndFeel always returns selection colors of GtkTextView.
+ if (auto selectionForegroundColor =
+ GetSystemColor(LookAndFeel::ColorID::Highlight)) {
+ double alpha =
+ static_cast<double>(NS_GET_A(*selectionForegroundColor)) / 0xFF;
+ style.AppendPrintf("color:rgba(%u,%u,%u,",
+ NS_GET_R(*selectionForegroundColor),
+ NS_GET_G(*selectionForegroundColor),
+ NS_GET_B(*selectionForegroundColor));
+ // We can't use AppendPrintf here, because it does locale-specific
+ // formatting of floating-point values.
+ style.AppendFloat(alpha);
+ style.AppendPrintf(");");
+ }
+ if (auto selectionBackgroundColor =
+ GetSystemColor(LookAndFeel::ColorID::Highlighttext)) {
+ double alpha =
+ static_cast<double>(NS_GET_A(*selectionBackgroundColor)) / 0xFF;
+ style.AppendPrintf("background-color:rgba(%u,%u,%u,",
+ NS_GET_R(*selectionBackgroundColor),
+ NS_GET_G(*selectionBackgroundColor),
+ NS_GET_B(*selectionBackgroundColor));
+ style.AppendFloat(alpha);
+ style.AppendPrintf(");");
+ }
+ style.AppendLiteral("}");
+ gtk_css_provider_load_from_data(mProvider, style.get(), -1, nullptr);
+ }
+
+ private:
+ static SelectionStyleProvider* sInstance;
+ static bool sHasShutDown;
+ GtkCssProvider* const mProvider;
+
+ SelectionStyleProvider() : mProvider(gtk_css_provider_new()) {
+ OnThemeChanged();
+ }
+};
+
+SelectionStyleProvider* SelectionStyleProvider::sInstance = nullptr;
+bool SelectionStyleProvider::sHasShutDown = false;
+
+/******************************************************************************
+ * IMContextWrapper
+ ******************************************************************************/
+
+IMContextWrapper* IMContextWrapper::sLastFocusedContext = nullptr;
+guint16 IMContextWrapper::sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+bool IMContextWrapper::sUseSimpleContext;
+
+NS_IMPL_ISUPPORTS(IMContextWrapper, TextEventDispatcherListener,
+ nsISupportsWeakReference)
+
+IMContextWrapper::IMContextWrapper(nsWindow* aOwnerWindow)
+ : mOwnerWindow(aOwnerWindow),
+ mLastFocusedWindow(nullptr),
+ mContext(nullptr),
+ mSimpleContext(nullptr),
+ mDummyContext(nullptr),
+ mComposingContext(nullptr),
+ mCompositionStart(UINT32_MAX),
+ mProcessingKeyEvent(nullptr),
+ mCompositionState(eCompositionState_NotComposing),
+ mIMContextID(IMContextID::Unknown),
+ mFallbackToKeyEvent(false),
+ mKeyboardEventWasDispatched(false),
+ mKeyboardEventWasConsumed(false),
+ mIsDeletingSurrounding(false),
+ mLayoutChanged(false),
+ mSetCursorPositionOnKeyEvent(true),
+ mPendingResettingIMContext(false),
+ mRetrieveSurroundingSignalReceived(false),
+ mMaybeInDeadKeySequence(false),
+ mIsIMInAsyncKeyHandlingMode(false),
+ mSetInputPurposeAndInputHints(false) {
+ static bool sFirstInstance = true;
+ if (sFirstInstance) {
+ sFirstInstance = false;
+ sUseSimpleContext =
+ Preferences::GetBool("intl.ime.use_simple_context_on_password_field",
+ kUseSimpleContextDefault);
+ }
+ Init();
+}
+
+static bool IsIBusInSyncMode() {
+ // See ibus_im_context_class_init() in client/gtk2/ibusimcontext.c
+ // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L610
+ const char* env = PR_GetEnv("IBUS_ENABLE_SYNC_MODE");
+
+ // See _get_boolean_env() in client/gtk2/ibusimcontext.c
+ // https://github.com/ibus/ibus/blob/86963f2f94d1e4fc213b01c2bc2ba9dcf4b22219/client/gtk2/ibusimcontext.c#L520-L537
+ if (!env) {
+ return false;
+ }
+ nsDependentCString envStr(env);
+ if (envStr.IsEmpty() || envStr.EqualsLiteral("0") ||
+ envStr.EqualsLiteral("false") || envStr.EqualsLiteral("False") ||
+ envStr.EqualsLiteral("FALSE")) {
+ return false;
+ }
+ return true;
+}
+
+static bool GetFcitxBoolEnv(const char* aEnv) {
+ // See fcitx_utils_get_boolean_env in src/lib/fcitx-utils/utils.c
+ // https://github.com/fcitx/fcitx/blob/0c87840dc7d9460c2cb5feaeefec299d0d3d62ec/src/lib/fcitx-utils/utils.c#L721-L736
+ const char* env = PR_GetEnv(aEnv);
+ if (!env) {
+ return false;
+ }
+ nsDependentCString envStr(env);
+ if (envStr.IsEmpty() || envStr.EqualsLiteral("0") ||
+ envStr.EqualsLiteral("false")) {
+ return false;
+ }
+ return true;
+}
+
+static bool IsFcitxInSyncMode() {
+ // See fcitx_im_context_class_init() in src/frontend/gtk2/fcitximcontext.c
+ // https://github.com/fcitx/fcitx/blob/78b98d9230dc9630e99d52e3172bdf440ffd08c4/src/frontend/gtk2/fcitximcontext.c#L395-L398
+ return GetFcitxBoolEnv("IBUS_ENABLE_SYNC_MODE") ||
+ GetFcitxBoolEnv("FCITX_ENABLE_SYNC_MODE");
+}
+
+nsDependentCSubstring IMContextWrapper::GetIMName() const {
+ const char* contextIDChar =
+ gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext));
+ if (!contextIDChar) {
+ return nsDependentCSubstring();
+ }
+
+ nsDependentCSubstring im(contextIDChar, strlen(contextIDChar));
+
+ // If the context is XIM, actual engine must be specified with
+ // |XMODIFIERS=@im=foo|.
+ const char* xmodifiersChar = PR_GetEnv("XMODIFIERS");
+ if (!xmodifiersChar || !im.EqualsLiteral("xim")) {
+ return im;
+ }
+
+ nsDependentCString xmodifiers(xmodifiersChar);
+ int32_t atIMValueStart = xmodifiers.Find("@im=") + 4;
+ if (atIMValueStart < 4 ||
+ xmodifiers.Length() <= static_cast<size_t>(atIMValueStart)) {
+ return im;
+ }
+
+ int32_t atIMValueEnd = xmodifiers.Find("@", atIMValueStart);
+ if (atIMValueEnd > atIMValueStart) {
+ return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
+ atIMValueEnd - atIMValueStart);
+ }
+
+ if (atIMValueEnd == kNotFound) {
+ return nsDependentCSubstring(xmodifiersChar + atIMValueStart,
+ strlen(xmodifiersChar) - atIMValueStart);
+ }
+
+ return im;
+}
+
+void IMContextWrapper::Init() {
+ // Overwrite selection colors of the window before associating the window
+ // with IM context since IME may look up selection colors via IM context
+ // to support any colored widgets.
+ SelectionStyleProvider::GetInstance()->AttachTo(
+ mOwnerWindow->GetMozContainer());
+
+ // NOTE: gtk_im_*_new() abort (kill) the whole process when it fails.
+ // So, we don't need to check the result.
+
+ // Normal context.
+ mContext = gtk_im_multicontext_new();
+ g_signal_connect(mContext, "preedit_changed",
+ G_CALLBACK(IMContextWrapper::OnChangeCompositionCallback),
+ this);
+ g_signal_connect(mContext, "retrieve_surrounding",
+ G_CALLBACK(IMContextWrapper::OnRetrieveSurroundingCallback),
+ this);
+ g_signal_connect(mContext, "delete_surrounding",
+ G_CALLBACK(IMContextWrapper::OnDeleteSurroundingCallback),
+ this);
+ g_signal_connect(mContext, "commit",
+ G_CALLBACK(IMContextWrapper::OnCommitCompositionCallback),
+ this);
+ g_signal_connect(mContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
+ this);
+ g_signal_connect(mContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
+ this);
+ nsDependentCSubstring im = GetIMName();
+ if (im.EqualsLiteral("ibus")) {
+ mIMContextID = IMContextID::IBus;
+ mIsIMInAsyncKeyHandlingMode = !IsIBusInSyncMode();
+ // Although ibus has key snooper mode, it's forcibly disabled on Firefox
+ // in default settings by its whitelist since we always send key events
+ // to IME before handling shortcut keys. The whitelist can be
+ // customized with env, IBUS_NO_SNOOPER_APPS, but we don't need to
+ // support such rare cases for reducing maintenance cost.
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("fcitx")) {
+ mIMContextID = IMContextID::Fcitx;
+ mIsIMInAsyncKeyHandlingMode = !IsFcitxInSyncMode();
+ // Although Fcitx has key snooper mode similar to ibus, it's also
+ // disabled on Firefox in default settings by its whitelist. The
+ // whitelist can be customized with env, IBUS_NO_SNOOPER_APPS or
+ // FCITX_NO_SNOOPER_APPS, but we don't need to support such rare cases
+ // for reducing maintenance cost.
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("fcitx5")) {
+ mIMContextID = IMContextID::Fcitx5;
+ mIsIMInAsyncKeyHandlingMode = true; // does not have sync mode.
+ mIsKeySnooped = false; // never use key snooper.
+ } else if (im.EqualsLiteral("uim")) {
+ mIMContextID = IMContextID::Uim;
+ mIsIMInAsyncKeyHandlingMode = false;
+ // We cannot know if uim uses key snooper since it's build option of
+ // uim. Therefore, we need to retrieve the consideration from the
+ // pref for making users and distributions allowed to choose their
+ // preferred value.
+ mIsKeySnooped =
+ Preferences::GetBool("intl.ime.hack.uim.using_key_snooper", true);
+ } else if (im.EqualsLiteral("scim")) {
+ mIMContextID = IMContextID::Scim;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("iiim")) {
+ mIMContextID = IMContextID::IIIMF;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ } else if (im.EqualsLiteral("wayland")) {
+ mIMContextID = IMContextID::Wayland;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = true;
+ } else {
+ mIMContextID = IMContextID::Unknown;
+ mIsIMInAsyncKeyHandlingMode = false;
+ mIsKeySnooped = false;
+ }
+
+ // Simple context
+ if (sUseSimpleContext) {
+ mSimpleContext = gtk_im_context_simple_new();
+ g_signal_connect(mSimpleContext, "preedit_changed",
+ G_CALLBACK(&IMContextWrapper::OnChangeCompositionCallback),
+ this);
+ g_signal_connect(
+ mSimpleContext, "retrieve_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnRetrieveSurroundingCallback), this);
+ g_signal_connect(mSimpleContext, "delete_surrounding",
+ G_CALLBACK(&IMContextWrapper::OnDeleteSurroundingCallback),
+ this);
+ g_signal_connect(mSimpleContext, "commit",
+ G_CALLBACK(&IMContextWrapper::OnCommitCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_start",
+ G_CALLBACK(IMContextWrapper::OnStartCompositionCallback),
+ this);
+ g_signal_connect(mSimpleContext, "preedit_end",
+ G_CALLBACK(IMContextWrapper::OnEndCompositionCallback),
+ this);
+ }
+
+ // Dummy context
+ mDummyContext = gtk_im_multicontext_new();
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p Init(), mOwnerWindow=%p, mContext=%p (im=\"%s\"), "
+ "mIsIMInAsyncKeyHandlingMode=%s, mIsKeySnooped=%s, "
+ "mSimpleContext=%p, mDummyContext=%p, "
+ "gtk_im_multicontext_get_context_id()=\"%s\", "
+ "PR_GetEnv(\"XMODIFIERS\")=\"%s\"",
+ this, mOwnerWindow, mContext, nsAutoCString(im).get(),
+ ToChar(mIsIMInAsyncKeyHandlingMode), ToChar(mIsKeySnooped),
+ mSimpleContext, mDummyContext,
+ gtk_im_multicontext_get_context_id(GTK_IM_MULTICONTEXT(mContext)),
+ PR_GetEnv("XMODIFIERS")));
+}
+
+/* static */
+void IMContextWrapper::Shutdown() { SelectionStyleProvider::Shutdown(); }
+
+IMContextWrapper::~IMContextWrapper() {
+ MOZ_ASSERT(!mContext);
+ MOZ_ASSERT(!mComposingContext);
+ if (this == sLastFocusedContext) {
+ sLastFocusedContext = nullptr;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info, ("0x%p ~IMContextWrapper()", this));
+}
+
+void IMContextWrapper::SetGdkWindow(GdkWindow* aGdkWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p GdkWindowChanged(%p)", this, aGdkWindow));
+ MOZ_ASSERT(!aGdkWindow || mOwnerWindow->GetGdkWindow() == aGdkWindow);
+ gtk_im_context_set_client_window(mContext, aGdkWindow);
+ if (mSimpleContext) {
+ gtk_im_context_set_client_window(mSimpleContext, aGdkWindow);
+ }
+ gtk_im_context_set_client_window(mDummyContext, aGdkWindow);
+}
+
+NS_IMETHODIMP
+IMContextWrapper::NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) {
+ switch (aNotification.mMessage) {
+ case REQUEST_TO_COMMIT_COMPOSITION:
+ case REQUEST_TO_CANCEL_COMPOSITION: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ return IsComposing() ? EndIMEComposition(window) : NS_OK;
+ }
+ case NOTIFY_IME_OF_FOCUS:
+ OnFocusChangeInGecko(true);
+ return NS_OK;
+ case NOTIFY_IME_OF_BLUR:
+ OnFocusChangeInGecko(false);
+ return NS_OK;
+ case NOTIFY_IME_OF_POSITION_CHANGE:
+ OnLayoutChange();
+ return NS_OK;
+ case NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED:
+ OnUpdateComposition();
+ return NS_OK;
+ case NOTIFY_IME_OF_SELECTION_CHANGE: {
+ nsWindow* window =
+ static_cast<nsWindow*>(aTextEventDispatcher->GetWidget());
+ OnSelectionChange(window, aNotification);
+ return NS_OK;
+ }
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) {
+ // XXX When input transaction is being stolen by add-on, what should we do?
+}
+
+NS_IMETHODIMP_(void)
+IMContextWrapper::WillDispatchKeyboardEvent(
+ TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent, uint32_t aIndexOfKeypress,
+ void* aData) {
+ KeymapWrapper::WillDispatchKeyboardEvent(aKeyboardEvent,
+ static_cast<GdkEventKey*>(aData));
+}
+
+TextEventDispatcher* IMContextWrapper::GetTextEventDispatcher() {
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ return nullptr;
+ }
+ TextEventDispatcher* dispatcher =
+ mLastFocusedWindow->GetTextEventDispatcher();
+ // nsIWidget::GetTextEventDispatcher() shouldn't return nullptr.
+ MOZ_RELEASE_ASSERT(dispatcher);
+ return dispatcher;
+}
+
+NS_IMETHODIMP_(IMENotificationRequests)
+IMContextWrapper::GetIMENotificationRequests() {
+ IMENotificationRequests::Notifications notifications =
+ IMENotificationRequests::NOTIFY_NOTHING;
+ // If it's not enabled, we don't need position change notification.
+ if (IsEnabled()) {
+ notifications |= IMENotificationRequests::NOTIFY_POSITION_CHANGE;
+ }
+ return IMENotificationRequests(notifications);
+}
+
+void IMContextWrapper::OnDestroyWindow(nsWindow* aWindow) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p OnDestroyWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mOwnerWindow=0x%p, mLastFocusedModule=0x%p",
+ this, aWindow, mLastFocusedWindow, mOwnerWindow, sLastFocusedContext));
+
+ MOZ_ASSERT(aWindow, "aWindow must not be null");
+
+ if (mLastFocusedWindow == aWindow) {
+ if (IsComposing()) {
+ EndIMEComposition(aWindow);
+ }
+ NotifyIMEOfFocusChange(IMEFocusState::Blurred);
+ mLastFocusedWindow = nullptr;
+ }
+
+ if (mOwnerWindow != aWindow) {
+ return;
+ }
+
+ if (sLastFocusedContext == this) {
+ sLastFocusedContext = nullptr;
+ }
+
+ /**
+ * NOTE:
+ * The given window is the owner of this, so, we must disconnect from the
+ * contexts now. But that might be referred from other nsWindows
+ * (they are children of this. But we don't know why there are the
+ * cases). So, we need to clear the pointers that refers to contexts
+ * and this if the other referrers are still alive. See bug 349727.
+ */
+ if (mContext) {
+ PrepareToDestroyContext(mContext);
+ gtk_im_context_set_client_window(mContext, nullptr);
+ g_signal_handlers_disconnect_by_data(mContext, this);
+ g_object_unref(mContext);
+ mContext = nullptr;
+ }
+
+ if (mSimpleContext) {
+ gtk_im_context_set_client_window(mSimpleContext, nullptr);
+ g_signal_handlers_disconnect_by_data(mSimpleContext, this);
+ g_object_unref(mSimpleContext);
+ mSimpleContext = nullptr;
+ }
+
+ if (mDummyContext) {
+ // mContext and mDummyContext have the same slaveType and signal_data
+ // so no need for another workaround_gtk_im_display_closed.
+ gtk_im_context_set_client_window(mDummyContext, nullptr);
+ g_object_unref(mDummyContext);
+ mDummyContext = nullptr;
+ }
+
+ if (NS_WARN_IF(mComposingContext)) {
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+ }
+
+ mOwnerWindow = nullptr;
+ mLastFocusedWindow = nullptr;
+ mInputContext.mIMEState.mEnabled = IMEEnabled::Disabled;
+ mPostingKeyEvents.Clear();
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p OnDestroyWindow(), succeeded, Completely destroyed", this));
+}
+
+void IMContextWrapper::PrepareToDestroyContext(GtkIMContext* aContext) {
+ if (mIMContextID == IMContextID::IIIMF) {
+ // IIIM module registers handlers for the "closed" signal on the
+ // display, but the signal handler is not disconnected when the module
+ // is unloaded. To prevent the module from being unloaded, use static
+ // variable to hold reference of slave context class declared by IIIM.
+ // Note that this does not grab any instance, it grabs the "class".
+ static gpointer sGtkIIIMContextClass = nullptr;
+ if (!sGtkIIIMContextClass) {
+ // We retrieved slave context class with g_type_name() and actual
+ // slave context instance when our widget was GTK2. That must be
+ // _GtkIMContext::priv::slave in GTK3. However, _GtkIMContext::priv
+ // is an opacity struct named _GtkIMMulticontextPrivate, i.e., it's
+ // not exposed by GTK3. Therefore, we cannot access the instance
+ // safely. So, we need to retrieve the slave context class with
+ // g_type_from_name("GtkIMContextIIIM") directly (anyway, we needed
+ // to compare the class name with "GtkIMContextIIIM").
+ GType IIMContextType = g_type_from_name("GtkIMContextIIIM");
+ if (IIMContextType) {
+ sGtkIIIMContextClass = g_type_class_ref(IIMContextType);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p PrepareToDestroyContext(), added to reference to "
+ "GtkIMContextIIIM class to prevent it from being unloaded",
+ this));
+ } else {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p PrepareToDestroyContext(), FAILED to prevent the "
+ "IIIM module from being uploaded",
+ this));
+ }
+ }
+ }
+}
+
+void IMContextWrapper::OnFocusWindow(nsWindow* aWindow) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", this,
+ aWindow, mLastFocusedWindow));
+ mLastFocusedWindow = aWindow;
+}
+
+void IMContextWrapper::OnBlurWindow(nsWindow* aWindow) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mIMEFocusState=%s",
+ this, aWindow, mLastFocusedWindow, ToString(mIMEFocusState).c_str()));
+
+ if (mLastFocusedWindow != aWindow) {
+ return;
+ }
+
+ NotifyIMEOfFocusChange(IMEFocusState::Blurred);
+}
+
+KeyHandlingState IMContextWrapper::OnKeyEvent(
+ nsWindow* aCaller, GdkEventKey* aEvent,
+ bool aKeyboardEventWasDispatched /* = false */) {
+ MOZ_ASSERT(aEvent, "aEvent must be non-null");
+
+ if (!mInputContext.mIMEState.IsEditable() || MOZ_UNLIKELY(IsDestroyed())) {
+ return KeyHandlingState::eNotHandled;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info, (">>>>>>>>>>>>>>>>"));
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(aCaller=0x%p, "
+ "aEvent(0x%p): { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+ "time=%u, hardware_keycode=%u, group=%u }, "
+ "aKeyboardEventWasDispatched=%s)",
+ this, aCaller, aEvent, GetEventType(aEvent),
+ gdk_keyval_name(aEvent->keyval), gdk_keyval_to_unicode(aEvent->keyval),
+ GetEventStateName(aEvent->state, mIMContextID).get(), aEvent->time,
+ aEvent->hardware_keycode, aEvent->group,
+ ToChar(aKeyboardEventWasDispatched)));
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), mMaybeInDeadKeySequence=%s, "
+ "mCompositionState=%s, current context=%p, active context=%p, "
+ "mIMContextID=%s, mIsIMInAsyncKeyHandlingMode=%s",
+ this, ToChar(mMaybeInDeadKeySequence), GetCompositionStateName(),
+ GetCurrentContext(), GetActiveContext(), ToString(mIMContextID).c_str(),
+ ToChar(mIsIMInAsyncKeyHandlingMode)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, the caller isn't focused "
+ "window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return KeyHandlingState::eNotHandled;
+ }
+
+ // Even if old IM context has composition, key event should be sent to
+ // current context since the user expects so.
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (MOZ_UNLIKELY(!currentContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnKeyEvent(), FAILED, there are no context", this));
+ return KeyHandlingState::eNotHandled;
+ }
+
+ if (mSetCursorPositionOnKeyEvent) {
+ SetCursorPosition(currentContext);
+ mSetCursorPositionOnKeyEvent = false;
+ }
+
+ // Let's support dead key event even if active keyboard layout also
+ // supports complicated composition like CJK IME.
+ bool isDeadKey =
+ KeymapWrapper::ComputeDOMKeyNameIndex(aEvent) == KEY_NAME_INDEX_Dead;
+ mMaybeInDeadKeySequence |= isDeadKey;
+
+ // If current context is mSimpleContext, both ibus and fcitx handles key
+ // events synchronously. So, only when current context is mContext which
+ // is GtkIMMulticontext, the key event may be handled by IME asynchronously.
+ bool probablyHandledAsynchronously =
+ mIsIMInAsyncKeyHandlingMode && currentContext == mContext;
+
+ // If we're not sure whether the event is handled asynchronously, this is
+ // set to true.
+ bool maybeHandledAsynchronously = false;
+
+ // If aEvent is a synthesized event for async handling, this will be set to
+ // true.
+ bool isHandlingAsyncEvent = false;
+
+ // If we've decided that the event won't be synthesized asyncrhonously
+ // by IME, but actually IME did it, this is set to true.
+ bool isUnexpectedAsyncEvent = false;
+
+ // If IM is ibus or fcitx and it handles key events asynchronously,
+ // they mark aEvent->state as "handled by me" when they post key event
+ // to another process. Unfortunately, we need to check this hacky
+ // flag because it's difficult to store all pending key events by
+ // an array or a hashtable.
+ if (probablyHandledAsynchronously) {
+ switch (mIMContextID) {
+ case IMContextID::IBus: {
+ // See src/ibustypes.h
+ static const guint IBUS_IGNORED_MASK = 1 << 25;
+ // If IBUS_IGNORED_MASK was set to aEvent->state, the event
+ // has already been handled by another process and it wasn't
+ // used by IME.
+ isHandlingAsyncEvent = !!(aEvent->state & IBUS_IGNORED_MASK);
+ if (!isHandlingAsyncEvent) {
+ // On some environments, IBUS_IGNORED_MASK flag is not set as
+ // expected. In such case, we keep pusing all events into the queue.
+ // I.e., that causes eating a lot of memory until it's blurred.
+ // Therefore, we need to check whether there is same timestamp event
+ // in the queue. This redundant cost should be low because in most
+ // causes, key events in the queue should be 2 or 4.
+ isHandlingAsyncEvent =
+ mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state does not have "
+ "IBUS_IGNORED_MASK but "
+ "same event in the queue. So, assuming it's a "
+ "synthesized event",
+ this));
+ }
+ }
+
+ // If it's a synthesized event, let's remove it from the posting
+ // event queue first. Otherwise the following blocks cannot use
+ // `break`.
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state has IBUS_IGNORED_MASK "
+ "or aEvent is in the "
+ "posting event queue, so, it won't be handled "
+ "asynchronously anymore. Removing "
+ "the posted events from the queue",
+ this));
+ probablyHandledAsynchronously = false;
+ mPostingKeyEvents.RemoveEvent(aEvent);
+ }
+
+ // ibus won't send back key press events in a dead key sequcne.
+ if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+ probablyHandledAsynchronously = false;
+ if (isHandlingAsyncEvent) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ // Some keyboard layouts which have dead keys may send
+ // "empty" key event to make us call
+ // gtk_im_context_filter_keypress() to commit composed
+ // character during a GDK_KEY_PRESS event dispatching.
+ if (!gdk_keyval_to_unicode(aEvent->keyval) &&
+ !aEvent->hardware_keycode) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ break;
+ }
+ // ibus may handle key events synchronously if focused editor is
+ // <input type="password"> or |ime-mode: disabled;|. However, in
+ // some environments, not so actually. Therefore, we need to check
+ // the result of gtk_im_context_filter_keypress() later.
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ probablyHandledAsynchronously = false;
+ maybeHandledAsynchronously = !isHandlingAsyncEvent;
+ break;
+ }
+ break;
+ }
+ case IMContextID::Fcitx:
+ case IMContextID::Fcitx5: {
+ // See src/lib/fcitx-utils/keysym.h
+ static const guint FcitxKeyState_IgnoredMask = 1 << 25;
+ // If FcitxKeyState_IgnoredMask was set to aEvent->state,
+ // the event has already been handled by another process and
+ // it wasn't used by IME.
+ isHandlingAsyncEvent = !!(aEvent->state & FcitxKeyState_IgnoredMask);
+ if (!isHandlingAsyncEvent) {
+ // On some environments, FcitxKeyState_IgnoredMask flag *might* be not
+ // set as expected. If there were such cases, we'd keep pusing all
+ // events into the queue. I.e., that would cause eating a lot of
+ // memory until it'd be blurred. Therefore, we should check whether
+ // there is same timestamp event in the queue. This redundant cost
+ // should be low because in most causes, key events in the queue
+ // should be 2 or 4.
+ isHandlingAsyncEvent =
+ mPostingKeyEvents.IndexOf(aEvent) != GdkEventKeyQueue::NoIndex();
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state does not have "
+ "FcitxKeyState_IgnoredMask "
+ "but same event in the queue. So, assuming it's a "
+ "synthesized event",
+ this));
+ }
+ }
+
+ // fcitx won't send back key press events in a dead key sequcne.
+ if (mMaybeInDeadKeySequence && aEvent->type == GDK_KEY_PRESS) {
+ probablyHandledAsynchronously = false;
+ if (isHandlingAsyncEvent) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ // Some keyboard layouts which have dead keys may send
+ // "empty" key event to make us call
+ // gtk_im_context_filter_keypress() to commit composed
+ // character during a GDK_KEY_PRESS event dispatching.
+ if (!gdk_keyval_to_unicode(aEvent->keyval) &&
+ !aEvent->hardware_keycode) {
+ isUnexpectedAsyncEvent = true;
+ break;
+ }
+ }
+
+ // fcitx handles key events asynchronously even if focused
+ // editor cannot use IME actually.
+
+ if (isHandlingAsyncEvent) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), aEvent->state has "
+ "FcitxKeyState_IgnoredMask or aEvent is in "
+ "the posting event queue, so, it won't be handled "
+ "asynchronously anymore. "
+ "Removing the posted events from the queue",
+ this));
+ probablyHandledAsynchronously = false;
+ mPostingKeyEvents.RemoveEvent(aEvent);
+ break;
+ }
+ break;
+ }
+ default:
+ MOZ_ASSERT_UNREACHABLE(
+ "IME may handle key event "
+ "asyncrhonously, but not yet confirmed if it comes agian "
+ "actually");
+ }
+ }
+
+ if (!isUnexpectedAsyncEvent) {
+ mKeyboardEventWasDispatched = aKeyboardEventWasDispatched;
+ mKeyboardEventWasConsumed = false;
+ } else {
+ // If we didn't expect this event, we've alreday dispatched eKeyDown
+ // event or eKeyUp event for that.
+ mKeyboardEventWasDispatched = true;
+ // And in this case, we need to assume that another key event hasn't
+ // been receivied and mKeyboardEventWasConsumed keeps storing the
+ // dispatched eKeyDown or eKeyUp event's state.
+ }
+ mFallbackToKeyEvent = false;
+ mProcessingKeyEvent = aEvent;
+ gboolean isFiltered = gtk_im_context_filter_keypress(currentContext, aEvent);
+
+ // If we're not sure whether the event is handled by IME asynchronously or
+ // synchronously, we need to trust the result of
+ // gtk_im_context_filter_keypress(). If it consumed and but did nothing,
+ // we can assume that another event will be synthesized.
+ if (!isHandlingAsyncEvent && maybeHandledAsynchronously) {
+ probablyHandledAsynchronously |=
+ isFiltered && !mFallbackToKeyEvent && !mKeyboardEventWasDispatched;
+ }
+
+ if (aEvent->type == GDK_KEY_PRESS) {
+ if (isFiltered && probablyHandledAsynchronously) {
+ sWaitingSynthesizedKeyPressHardwareKeyCode = aEvent->hardware_keycode;
+ } else {
+ sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+ }
+ }
+
+ // The caller of this shouldn't handle aEvent anymore if we've dispatched
+ // composition events or modified content with other events.
+ bool filterThisEvent = isFiltered && !mFallbackToKeyEvent;
+
+ if (IsComposingOnCurrentContext() && !isFiltered &&
+ aEvent->type == GDK_KEY_PRESS && mDispatchedCompositionString.IsEmpty()) {
+ // A Hangul input engine for SCIM doesn't emit preedit_end
+ // signal even when composition string becomes empty. On the
+ // other hand, we should allow to make composition with empty
+ // string for other languages because there *might* be such
+ // IM. For compromising this issue, we should dispatch
+ // compositionend event, however, we don't need to reset IM
+ // actually.
+ // NOTE: Don't dispatch key events as "processed by IME" since
+ // we need to dispatch keyboard events as IME wasn't handled it.
+ mProcessingKeyEvent = nullptr;
+ DispatchCompositionCommitEvent(currentContext, &EmptyString());
+ mProcessingKeyEvent = aEvent;
+ // In this case, even though we handle the keyboard event here,
+ // but we should dispatch keydown event as
+ filterThisEvent = false;
+ }
+
+ if (filterThisEvent && !mKeyboardEventWasDispatched) {
+ // If IME handled the key event but we've not dispatched eKeyDown nor
+ // eKeyUp event yet, we need to dispatch here unless the key event is
+ // now being handled by other IME process.
+ if (!probablyHandledAsynchronously) {
+ MaybeDispatchKeyEventAsProcessedByIME(eVoidEvent);
+ // Be aware, the widget might have been gone here.
+ }
+ // If we need to wait reply from IM, IM may send some signals to us
+ // without sending the key event again. In such case, we need to
+ // dispatch keyboard events with a copy of aEvent. Therefore, we
+ // need to use information of this key event to dispatch an KeyDown
+ // or eKeyUp event later.
+ else {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnKeyEvent(), putting aEvent into the queue...", this));
+ mPostingKeyEvents.PutEvent(aEvent);
+ }
+ }
+
+ mProcessingKeyEvent = nullptr;
+
+ if (aEvent->type == GDK_KEY_PRESS && !filterThisEvent) {
+ // If the key event hasn't been handled by active IME nor keyboard
+ // layout, we can assume that the dead key sequence has been or was
+ // ended. Note that we should not reset it when the key event is
+ // GDK_KEY_RELEASE since it may not be filtered by active keyboard
+ // layout even in composition.
+ mMaybeInDeadKeySequence = false;
+ }
+
+ if (aEvent->type == GDK_KEY_RELEASE) {
+ if (const GdkEventKey* pendingKeyPressEvent =
+ mPostingKeyEvents.GetCorrespondingKeyPressEvent(aEvent)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnKeyEvent(), forgetting a pending GDK_KEY_PRESS event "
+ "because GDK_KEY_RELEASE for the event is handled",
+ this));
+ mPostingKeyEvents.RemoveEvent(pendingKeyPressEvent);
+ }
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p OnKeyEvent(), succeeded, filterThisEvent=%s "
+ "(isFiltered=%s, mFallbackToKeyEvent=%s, "
+ "probablyHandledAsynchronously=%s, maybeHandledAsynchronously=%s), "
+ "mPostingKeyEvents.Length()=%zu, mCompositionState=%s, "
+ "mMaybeInDeadKeySequence=%s, mKeyboardEventWasDispatched=%s, "
+ "mKeyboardEventWasConsumed=%s",
+ this, ToChar(filterThisEvent), ToChar(isFiltered),
+ ToChar(mFallbackToKeyEvent), ToChar(probablyHandledAsynchronously),
+ ToChar(maybeHandledAsynchronously), mPostingKeyEvents.Length(),
+ GetCompositionStateName(), ToChar(mMaybeInDeadKeySequence),
+ ToChar(mKeyboardEventWasDispatched), ToChar(mKeyboardEventWasConsumed)));
+ MOZ_LOG(gIMELog, LogLevel::Info, ("<<<<<<<<<<<<<<<<\n\n"));
+
+ if (filterThisEvent) {
+ return KeyHandlingState::eHandled;
+ }
+ // If another call of this method has already dispatched eKeyDown event,
+ // we should return KeyHandlingState::eNotHandledButEventDispatched because
+ // the caller should've stopped handling the event if preceding eKeyDown
+ // event was consumed.
+ if (aKeyboardEventWasDispatched) {
+ return KeyHandlingState::eNotHandledButEventDispatched;
+ }
+ if (!mKeyboardEventWasDispatched) {
+ return KeyHandlingState::eNotHandled;
+ }
+ return mKeyboardEventWasConsumed
+ ? KeyHandlingState::eNotHandledButEventConsumed
+ : KeyHandlingState::eNotHandledButEventDispatched;
+}
+
+void IMContextWrapper::OnFocusChangeInGecko(bool aFocus) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnFocusChangeInGecko(aFocus=%s),mCompositionState=%s, "
+ "mIMEFocusState=%s, mSetInputPurposeAndInputHints=%s",
+ this, ToChar(aFocus), GetCompositionStateName(),
+ ToString(mIMEFocusState).c_str(),
+ ToChar(mSetInputPurposeAndInputHints)));
+
+ // We shouldn't carry over the removed string to another editor.
+ mSelectedStringRemovedByComposition.Truncate();
+ mContentSelection.reset();
+
+ if (aFocus) {
+ if (mSetInputPurposeAndInputHints) {
+ mSetInputPurposeAndInputHints = false;
+ SetInputPurposeAndInputHints();
+ }
+ NotifyIMEOfFocusChange(IMEFocusState::Focused);
+ } else {
+ NotifyIMEOfFocusChange(IMEFocusState::Blurred);
+ }
+
+ // When the focus changes, we need to inform IM about the new cursor
+ // position. Chinese input methods generally rely on this because they
+ // usually don't start composition until a character is picked.
+ if (aFocus && EnsureToCacheContentSelection()) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void IMContextWrapper::ResetIME() {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p ResetIME(), mCompositionState=%s, mIMEFocusState=%s", this,
+ GetCompositionStateName(), ToString(mIMEFocusState).c_str()));
+
+ GtkIMContext* activeContext = GetActiveContext();
+ if (MOZ_UNLIKELY(!activeContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p ResetIME(), FAILED, there are no context", this));
+ return;
+ }
+
+ RefPtr<IMContextWrapper> kungFuDeathGrip(this);
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ mPendingResettingIMContext = false;
+ gtk_im_context_reset(activeContext);
+
+ // The last focused window might have been destroyed by a DOM event handler
+ // which was called by us during a call of gtk_im_context_reset().
+ if (!lastFocusedWindow ||
+ NS_WARN_IF(lastFocusedWindow != mLastFocusedWindow) ||
+ lastFocusedWindow->Destroyed()) {
+ return;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(activeContext, compositionString);
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p ResetIME() called gtk_im_context_reset(), "
+ "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
+ "mIMEFocusState=%s",
+ this, activeContext, GetCompositionStateName(),
+ NS_ConvertUTF16toUTF8(compositionString).get(),
+ ToString(mIMEFocusState).c_str()));
+
+ // XXX IIIMF (ATOK X3 which is one of the Language Engine of it is still
+ // used in Japan!) sends only "preedit_changed" signal with empty
+ // composition string synchronously. Therefore, if composition string
+ // is now empty string, we should assume that the IME won't send
+ // "commit" signal.
+ if (IsComposing() && compositionString.IsEmpty()) {
+ // WARNING: The widget might have been gone after this.
+ DispatchCompositionCommitEvent(activeContext, &EmptyString());
+ }
+}
+
+nsresult IMContextWrapper::EndIMEComposition(nsWindow* aCaller) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return NS_OK;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p EndIMEComposition(aCaller=0x%p), "
+ "mCompositionState=%s",
+ this, aCaller, GetCompositionStateName()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p EndIMEComposition(), FAILED, the caller isn't "
+ "focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return NS_OK;
+ }
+
+ if (!IsComposing()) {
+ return NS_OK;
+ }
+
+ // Currently, GTK has API neither to commit nor to cancel composition
+ // forcibly. Therefore, TextComposition will recompute commit string for
+ // the request even if native IME will cause unexpected commit string.
+ // So, we don't need to emulate commit or cancel composition with
+ // proper composition events.
+ // XXX ResetIME() might not enough for finishing compositoin on some
+ // environments. We should emulate focus change too because some IMEs
+ // may commit or cancel composition at blur.
+ ResetIME();
+
+ return NS_OK;
+}
+
+void IMContextWrapper::OnLayoutChange() {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (IsComposing()) {
+ SetCursorPosition(GetActiveContext());
+ } else {
+ // If not composing, candidate window position is updated before key
+ // down
+ mSetCursorPositionOnKeyEvent = true;
+ }
+ mLayoutChanged = true;
+}
+
+void IMContextWrapper::OnUpdateComposition() {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Composition has been committed. So we need update selection for
+ // caret later
+ mContentSelection.reset();
+ EnsureToCacheContentSelection();
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // If we've already set candidate window position, we don't need to update
+ // the position with update composition notification.
+ if (!mLayoutChanged) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void IMContextWrapper::SetInputContext(nsWindow* aCaller,
+ const InputContext* aContext,
+ const InputContextAction* aAction) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p SetInputContext(aCaller=0x%p, aContext={ mIMEState={ "
+ "mEnabled=%s }, mHTMLInputType=%s })",
+ this, aCaller, ToString(aContext->mIMEState.mEnabled).c_str(),
+ NS_ConvertUTF16toUTF8(aContext->mHTMLInputType).get()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!mContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "there are no context",
+ this));
+ return;
+ }
+
+ if (sLastFocusedContext != this) {
+ mInputContext = *aContext;
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p SetInputContext(), succeeded, "
+ "but we're not active",
+ this));
+ return;
+ }
+
+ const bool changingEnabledState =
+ aContext->IsInputAttributeChanged(mInputContext);
+
+ // Release current IME focus if IME is enabled.
+ if (changingEnabledState && mInputContext.mIMEState.IsEditable()) {
+ if (IsComposing()) {
+ EndIMEComposition(mLastFocusedWindow);
+ }
+ if (mIMEFocusState == IMEFocusState::Focused) {
+ NotifyIMEOfFocusChange(IMEFocusState::BlurredWithoutFocusChange);
+ }
+ }
+
+ mInputContext = *aContext;
+ mSetInputPurposeAndInputHints = false;
+
+ if (!changingEnabledState || !mInputContext.mIMEState.IsEditable()) {
+ return;
+ }
+
+ // If the input context was temporarily disabled without a focus change,
+ // it must be ready to query content even if the focused content is in
+ // a remote process. In this case, we should set IME focus right now.
+ if (mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
+ SetInputPurposeAndInputHints();
+ NotifyIMEOfFocusChange(IMEFocusState::Focused);
+ return;
+ }
+
+ // Otherwise, we cannot set input-purpose and input-hints right now because
+ // setting them may require to set focus immediately for IME own's UI.
+ // However, at this moment, `ContentCacheInParent` does not have content
+ // cache, it'll be available after `NOTIFY_IME_OF_FOCUS` notification.
+ // Therefore, we set them at receiving the notification.
+ mSetInputPurposeAndInputHints = true;
+}
+
+void IMContextWrapper::SetInputPurposeAndInputHints() {
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ return;
+ }
+
+ GtkInputPurpose purpose = GTK_INPUT_PURPOSE_FREE_FORM;
+ const nsString& inputType = mInputContext.mHTMLInputType;
+ // Password case has difficult issue. Desktop IMEs disable composition if
+ // input-purpose is password. For disabling IME on |ime-mode: disabled;|, we
+ // need to check mEnabled value instead of inputType value. This hack also
+ // enables composition on <input type="password" style="ime-mode: enabled;">.
+ // This is right behavior of ime-mode on desktop.
+ //
+ // On the other hand, IME for tablet devices may provide a specific software
+ // keyboard for password field. If so, the behavior might look strange on
+ // both:
+ // <input type="text" style="ime-mode: disabled;">
+ // <input type="password" style="ime-mode: enabled;">
+ //
+ // Temporarily, we should focus on desktop environment for now. I.e., let's
+ // ignore tablet devices for now. When somebody reports actual trouble on
+ // tablet devices, we should try to look for a way to solve actual problem.
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ purpose = GTK_INPUT_PURPOSE_PASSWORD;
+ } else if (inputType.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (inputType.EqualsLiteral("url")) {
+ purpose = GTK_INPUT_PURPOSE_URL;
+ } else if (inputType.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (inputType.EqualsLiteral("number")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ } else if (mInputContext.mHTMLInputMode.EqualsLiteral("decimal")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ } else if (mInputContext.mHTMLInputMode.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (mInputContext.mHTMLInputMode.EqualsLiteral("numeric")) {
+ purpose = GTK_INPUT_PURPOSE_DIGITS;
+ } else if (mInputContext.mHTMLInputMode.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (mInputContext.mHTMLInputMode.EqualsLiteral("url")) {
+ purpose = GTK_INPUT_PURPOSE_URL;
+ }
+ // Search by type and inputmode isn't supported on GTK.
+
+ g_object_set(currentContext, "input-purpose", purpose, nullptr);
+
+ // Although GtkInputHints is enum type, value is bit field.
+ gint hints = GTK_INPUT_HINT_NONE;
+ if (mInputContext.mHTMLInputMode.EqualsLiteral("none")) {
+ hints |= GTK_INPUT_HINT_INHIBIT_OSK;
+ }
+
+ if (mInputContext.mAutocapitalize.EqualsLiteral("characters")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_CHARS;
+ } else if (mInputContext.mAutocapitalize.EqualsLiteral("sentences")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_SENTENCES;
+ } else if (mInputContext.mAutocapitalize.EqualsLiteral("words")) {
+ hints |= GTK_INPUT_HINT_UPPERCASE_WORDS;
+ }
+
+ g_object_set(currentContext, "input-hints", hints, nullptr);
+}
+
+InputContext IMContextWrapper::GetInputContext() {
+ mInputContext.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ return mInputContext;
+}
+
+GtkIMContext* IMContextWrapper::GetCurrentContext() const {
+ if (IsEnabled()) {
+ return mContext;
+ }
+ if (mInputContext.mIMEState.mEnabled == IMEEnabled::Password) {
+ return mSimpleContext;
+ }
+ return mDummyContext;
+}
+
+bool IMContextWrapper::IsValidContext(GtkIMContext* aContext) const {
+ if (!aContext) {
+ return false;
+ }
+ return aContext == mContext || aContext == mSimpleContext ||
+ aContext == mDummyContext;
+}
+
+bool IMContextWrapper::IsEnabled() const {
+ return mInputContext.mIMEState.mEnabled == IMEEnabled::Enabled ||
+ (!sUseSimpleContext &&
+ mInputContext.mIMEState.mEnabled == IMEEnabled::Password);
+}
+
+void IMContextWrapper::NotifyIMEOfFocusChange(IMEFocusState aIMEFocusState) {
+ MOZ_ASSERT_IF(aIMEFocusState == IMEFocusState::BlurredWithoutFocusChange,
+ mIMEFocusState != IMEFocusState::Blurred);
+ if (mIMEFocusState == aIMEFocusState) {
+ return;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p NotifyIMEOfFocusChange(aIMEFocusState=%s), mIMEFocusState=%s, "
+ "sLastFocusedContext=0x%p",
+ this, ToString(aIMEFocusState).c_str(),
+ ToString(mIMEFocusState).c_str(), sLastFocusedContext));
+ MOZ_ASSERT(!mSetInputPurposeAndInputHints);
+
+ // If we've already made IME blurred at setting the input context disabled
+ // and it's now completely blurred by a focus move, we need only to update
+ // mIMEFocusState and when the input context gets enabled, we cannot set
+ // IME focus immediately.
+ if (aIMEFocusState == IMEFocusState::Blurred &&
+ mIMEFocusState == IMEFocusState::BlurredWithoutFocusChange) {
+ mIMEFocusState = IMEFocusState::Blurred;
+ return;
+ }
+
+ auto Blur = [&](IMEFocusState aInternalState) {
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (MOZ_UNLIKELY(!currentContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p NotifyIMEOfFocusChange()::Blur(), FAILED, "
+ "there is no context",
+ this));
+ return;
+ }
+ gtk_im_context_focus_out(currentContext);
+ mIMEFocusState = aInternalState;
+ };
+
+ if (aIMEFocusState != IMEFocusState::Focused) {
+ return Blur(aIMEFocusState);
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (MOZ_UNLIKELY(!currentContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p NotifyIMEOfFocusChange(), FAILED, "
+ "there is no context",
+ this));
+ return;
+ }
+
+ if (sLastFocusedContext && sLastFocusedContext != this) {
+ sLastFocusedContext->NotifyIMEOfFocusChange(IMEFocusState::Blurred);
+ }
+
+ sLastFocusedContext = this;
+
+ // Forget all posted key events when focus is moved since they shouldn't
+ // be fired in different editor.
+ sWaitingSynthesizedKeyPressHardwareKeyCode = 0;
+ mPostingKeyEvents.Clear();
+
+ gtk_im_context_focus_in(currentContext);
+ mIMEFocusState = aIMEFocusState;
+ mSetCursorPositionOnKeyEvent = true;
+
+ if (!IsEnabled()) {
+ // We should release IME focus for uim and scim.
+ // These IMs are using snooper that is released at losing focus.
+ Blur(IMEFocusState::BlurredWithoutFocusChange);
+ }
+}
+
+void IMContextWrapper::OnSelectionChange(
+ nsWindow* aCaller, const IMENotification& aIMENotification) {
+ const bool isSelectionRangeChanged =
+ mContentSelection.isNothing() ||
+ !aIMENotification.mSelectionChangeData.EqualsRange(
+ mContentSelection.ref());
+ mContentSelection =
+ Some(ContentSelection(aIMENotification.mSelectionChangeData));
+ const bool retrievedSurroundingSignalReceived =
+ mRetrieveSurroundingSignalReceived;
+ mRetrieveSurroundingSignalReceived = false;
+
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ const IMENotification::SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
+ "mSelectionChangeData=%s }), "
+ "mCompositionState=%s, mIsDeletingSurrounding=%s, "
+ "mRetrieveSurroundingSignalReceived=%s, isSelectionRangeChanged=%s",
+ this, aCaller, ToString(selectionChangeData).c_str(),
+ GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
+ ToChar(retrievedSurroundingSignalReceived),
+ ToChar(isSelectionRangeChanged)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!IsComposing()) {
+ // Now we have no composition (mostly situation on calling this method)
+ // If we have it, it will set by
+ // NOTIFY_IME_OF_COMPOSITION_EVENT_HANDLED.
+ mSetCursorPositionOnKeyEvent = true;
+ }
+
+ // The focused editor might have placeholder text with normal text node.
+ // In such case, the text node must be removed from a compositionstart
+ // event handler. So, we're dispatching eCompositionStart,
+ // we should ignore selection change notification.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(mContentSelection.isNothing())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "new offset is too large, cannot keep composing",
+ this));
+ } else if (mContentSelection->HasRange()) {
+ // Modify the selection start offset with new offset.
+ mCompositionStart = mContentSelection->OffsetAndDataRef().StartOffset();
+ // XXX We should modify mSelectedStringRemovedByComposition?
+ // But how?
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p OnSelectionChange(), ignored, mCompositionStart "
+ "is updated to %u, the selection change doesn't cause "
+ "resetting IM context",
+ this, mCompositionStart));
+ // And don't reset the IM context.
+ return;
+ } else {
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p OnSelectionChange(), ignored, because of no selection range",
+ this));
+ return;
+ }
+ // Otherwise, reset the IM context due to impossible to keep composing.
+ }
+
+ // If the selection change is caused by deleting surrounding text,
+ // we shouldn't need to notify IME of selection change.
+ if (mIsDeletingSurrounding) {
+ return;
+ }
+
+ bool occurredBeforeComposition =
+ IsComposing() && !selectionChangeData.mOccurredDuringComposition &&
+ !selectionChangeData.mCausedByComposition;
+ if (occurredBeforeComposition) {
+ mPendingResettingIMContext = true;
+ }
+
+ // When the selection change is caused by dispatching composition event,
+ // selection set event and/or occurred before starting current composition,
+ // we shouldn't notify IME of that and commit existing composition.
+ // Don't do this even if selection is not changed actually. For example,
+ // fcitx has direct input mode which does not insert composing string, but
+ // inserts commited text for each key sequence (i.e., there is "invisible"
+ // composition string). In the world after bug 1712269, we don't use a
+ // set of composition events for this kind of IME. Therefore,
+ // SelectionChangeData.mCausedByComposition is not expected value for here
+ // if this call is caused by a preceding commit. And if the preceding commit
+ // is triggered by a key type for next word, resetting IME state makes fcitx
+ // discard the pending input for the next word. Thus, we need to check
+ // whether the selection range is actually changed here.
+ if (!selectionChangeData.mCausedByComposition &&
+ !selectionChangeData.mCausedBySelectionEvent && isSelectionRangeChanged &&
+ !occurredBeforeComposition) {
+ // Hack for ibus-pinyin. ibus-pinyin will synthesize a set of
+ // composition which commits with empty string after calling
+ // gtk_im_context_reset(). Therefore, selecting text causes
+ // unexpectedly removing it. For preventing it but not breaking the
+ // other IMEs which use surrounding text, we should call it only when
+ // surrounding text has been retrieved after last selection range was
+ // set. If it's not retrieved, that means that current IME doesn't
+ // have any content cache, so, it must not need the notification of
+ // selection change.
+ if (IsComposing() || retrievedSurroundingSignalReceived) {
+ ResetIME();
+ }
+ }
+}
+
+/* static */
+void IMContextWrapper::OnThemeChanged() {
+ if (auto* provider = SelectionStyleProvider::GetExistingInstance()) {
+ provider->OnThemeChanged();
+ }
+}
+
+/* static */
+void IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnStartCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
+ // IME may synthesize composition asynchronously after filtering a
+ // GDK_KEY_PRESS event. In that case, we should handle composition with
+ // emulating the usual case, i.e., this is called in the stack of
+ // OnKeyEvent().
+ Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
+ if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
+ GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
+ if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
+ KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
+ KEY_NAME_INDEX_USE_STRING) {
+ maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
+ mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
+ }
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnStartCompositionNative(aContext=0x%p), "
+ "current context=0x%p, mComposingContext=0x%p",
+ this, aContext, GetCurrentContext(), mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnStartCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ if (mComposingContext && aContext != mComposingContext) {
+ // XXX For now, we should ignore this odd case, just logging.
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnStartCompositionNative(), Warning, "
+ "there is already a composing context but starting new "
+ "composition with different context",
+ this));
+ }
+
+ // IME may start composition without "preedit_start" signal. Therefore,
+ // mComposingContext will be initialized in DispatchCompositionStart().
+
+ if (!DispatchCompositionStart(aContext)) {
+ return;
+ }
+ mCompositionTargetRange.mOffset = mCompositionStart;
+ mCompositionTargetRange.mLength = 0;
+}
+
+/* static */
+void IMContextWrapper::OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnEndCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnEndCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnEndCompositionNative(aContext=0x%p), mComposingContext=0x%p",
+ this, aContext, mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnEndCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ // If we've not started composition with aContext, we should ignore it.
+ if (aContext != mComposingContext) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnEndCompositionNative(), Warning, "
+ "given context doesn't match with mComposingContext",
+ this));
+ return;
+ }
+
+ g_object_unref(mComposingContext);
+ mComposingContext = nullptr;
+
+ // If we already handled the commit event, we should do nothing here.
+ if (IsComposing()) {
+ if (!DispatchCompositionCommitEvent(aContext)) {
+ // If the widget is destroyed, we should do nothing anymore.
+ return;
+ }
+ }
+
+ if (mPendingResettingIMContext) {
+ ResetIME();
+ }
+}
+
+/* static */
+void IMContextWrapper::OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ RefPtr module = aModule;
+ module->OnChangeCompositionNative(aContext);
+
+ if (module->IsDestroyed()) {
+ // A strong reference is already held during "preedit-changed" emission,
+ // but _ibus_context_destroy_cb() in ibus 1.5.28 and
+ // _fcitx_im_context_close_im_cb() in fcitx 4.2.9.9 want their
+ // GtkIMContexts to live a little longer. See bug 1824634.
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction(__func__, [context = RefPtr{aContext}]() {}));
+ }
+}
+
+void IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) {
+ // IME may synthesize composition asynchronously after filtering a
+ // GDK_KEY_PRESS event. In that case, we should handle composition with
+ // emulating the usual case, i.e., this is called in the stack of
+ // OnKeyEvent().
+ Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
+ if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
+ GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
+ if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
+ KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
+ KEY_NAME_INDEX_USE_STRING) {
+ maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
+ mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
+ }
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnChangeCompositionNative(aContext=0x%p), "
+ "mComposingContext=0x%p",
+ this, aContext, mComposingContext));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ // Note that if this is called after focus move, the context may different
+ // from any our owning context.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnChangeCompositionNative(), FAILED, "
+ "given context doesn't match with any context",
+ this));
+ return;
+ }
+
+ if (mComposingContext && aContext != mComposingContext) {
+ // XXX For now, we should ignore this odd case, just logging.
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnChangeCompositionNative(), Warning, "
+ "given context doesn't match with composing context",
+ this));
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!IsComposing() && compositionString.IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnChangeCompositionNative(), Warning, does nothing "
+ "because has not started composition and composing string is "
+ "empty",
+ this));
+ mDispatchedCompositionString.Truncate();
+ return; // Don't start the composition with empty string.
+ }
+
+ // Be aware, widget can be gone
+ DispatchCompositionChangeEvent(aContext, compositionString);
+}
+
+/* static */
+gboolean IMContextWrapper::OnRetrieveSurroundingCallback(
+ GtkIMContext* aContext, IMContextWrapper* aModule) {
+ return aModule->OnRetrieveSurroundingNative(aContext);
+}
+
+gboolean IMContextWrapper::OnRetrieveSurroundingNative(GtkIMContext* aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnRetrieveSurroundingNative(aContext=0x%p), "
+ "current context=0x%p",
+ this, aContext, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnRetrieveSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ nsAutoString uniStr;
+ uint32_t cursorPos;
+ if (NS_FAILED(GetCurrentParagraph(uniStr, cursorPos))) {
+ return FALSE;
+ }
+
+ // Despite taking a pointer and a length, IBus wants the string to be
+ // zero-terminated and doesn't like U+0000 within the string.
+ uniStr.ReplaceChar(char16_t(0), char16_t(0xFFFD));
+
+ NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(uniStr, 0, cursorPos));
+ uint32_t cursorPosInUTF8 = utf8Str.Length();
+ AppendUTF16toUTF8(nsDependentSubstring(uniStr, cursorPos), utf8Str);
+ gtk_im_context_set_surrounding(aContext, utf8Str.get(), utf8Str.Length(),
+ cursorPosInUTF8);
+ mRetrieveSurroundingSignalReceived = true;
+ return TRUE;
+}
+
+/* static */
+gboolean IMContextWrapper::OnDeleteSurroundingCallback(
+ GtkIMContext* aContext, gint aOffset, gint aNChars,
+ IMContextWrapper* aModule) {
+ return aModule->OnDeleteSurroundingNative(aContext, aOffset, aNChars);
+}
+
+gboolean IMContextWrapper::OnDeleteSurroundingNative(GtkIMContext* aContext,
+ gint aOffset,
+ gint aNChars) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnDeleteSurroundingNative(aContext=0x%p, aOffset=%d, "
+ "aNChar=%d), current context=0x%p",
+ this, aContext, aOffset, aNChars, GetCurrentContext()));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (GetCurrentContext() != aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return FALSE;
+ }
+
+ AutoRestore<bool> saveDeletingSurrounding(mIsDeletingSurrounding);
+ mIsDeletingSurrounding = true;
+ if (NS_SUCCEEDED(DeleteText(aContext, aOffset, (uint32_t)aNChars))) {
+ return TRUE;
+ }
+
+ // failed
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnDeleteSurroundingNative(), FAILED, "
+ "cannot delete text",
+ this));
+ return FALSE;
+}
+
+/* static */
+void IMContextWrapper::OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule) {
+ aModule->OnCommitCompositionNative(aContext, aString);
+}
+
+void IMContextWrapper::OnCommitCompositionNative(GtkIMContext* aContext,
+ const gchar* aUTF8Char) {
+ const gchar emptyStr = 0;
+ const gchar* commitString = aUTF8Char ? aUTF8Char : &emptyStr;
+ NS_ConvertUTF8toUTF16 utf16CommitString(commitString);
+
+ // IME may synthesize composition asynchronously after filtering a
+ // GDK_KEY_PRESS event. In that case, we should handle composition with
+ // emulating the usual case, i.e., this is called in the stack of
+ // OnKeyEvent().
+ Maybe<AutoRestore<GdkEventKey*>> maybeRestoreProcessingKeyEvent;
+ if (!mProcessingKeyEvent && !mPostingKeyEvents.IsEmpty()) {
+ GdkEventKey* keyEvent = mPostingKeyEvents.GetFirstEvent();
+ if (keyEvent && keyEvent->type == GDK_KEY_PRESS &&
+ KeymapWrapper::ComputeDOMKeyNameIndex(keyEvent) ==
+ KEY_NAME_INDEX_USE_STRING) {
+ maybeRestoreProcessingKeyEvent.emplace(mProcessingKeyEvent);
+ mProcessingKeyEvent = mPostingKeyEvents.GetFirstEvent();
+ }
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(aContext=0x%p), "
+ "current context=0x%p, active context=0x%p, commitString=\"%s\", "
+ "mProcessingKeyEvent=0x%p, IsComposingOn(aContext)=%s",
+ this, aContext, GetCurrentContext(), GetActiveContext(),
+ commitString, mProcessingKeyEvent, ToChar(IsComposingOn(aContext))));
+
+ // See bug 472635, we should do nothing if IM context doesn't match.
+ if (!IsValidContext(aContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnCommitCompositionNative(), FAILED, "
+ "given context doesn't match",
+ this));
+ return;
+ }
+
+ // If we are not in composition and committing with empty string,
+ // we need to do nothing because if we continued to handle this
+ // signal, we would dispatch compositionstart, text, compositionend
+ // events with empty string. Of course, they are unnecessary events
+ // for Web applications and our editor.
+ if (!IsComposingOn(aContext) && utf16CommitString.IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnCommitCompositionNative(), Warning, does nothing "
+ "because has not started composition and commit string is empty",
+ this));
+ return;
+ }
+
+ // If IME doesn't change their keyevent that generated this commit,
+ // we should treat that IME didn't handle the key event because
+ // web applications want to receive "keydown" and "keypress" event
+ // in such case.
+ // NOTE: While a key event is being handled, this might be caused on
+ // current context. Otherwise, this may be caused on active context.
+ if (!IsComposingOn(aContext) && mProcessingKeyEvent &&
+ mProcessingKeyEvent->type == GDK_KEY_PRESS &&
+ aContext == GetCurrentContext()) {
+ char keyval_utf8[8]; /* should have at least 6 bytes of space */
+ gint keyval_utf8_len;
+ guint32 keyval_unicode;
+
+ keyval_unicode = gdk_keyval_to_unicode(mProcessingKeyEvent->keyval);
+ keyval_utf8_len = g_unichar_to_utf8(keyval_unicode, keyval_utf8);
+ keyval_utf8[keyval_utf8_len] = '\0';
+
+ // If committing string is exactly same as a character which is
+ // produced by the key, eKeyDown and eKeyPress event should be
+ // dispatched by the caller of OnKeyEvent() normally. Note that
+ // mMaybeInDeadKeySequence will be set to false by OnKeyEvent()
+ // since we set mFallbackToKeyEvent to true here.
+ if (!strcmp(commitString, keyval_utf8)) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "we'll send normal key event",
+ this));
+ mFallbackToKeyEvent = true;
+ return;
+ }
+
+ // If we're in a dead key sequence, commit string is a character in
+ // the BMP and mProcessingKeyEvent produces some characters but it's
+ // not same as committing string, we should dispatch an eKeyPress
+ // event from here.
+ WidgetKeyboardEvent keyDownEvent(true, eKeyDown, mLastFocusedWindow);
+ KeymapWrapper::InitKeyEvent(keyDownEvent, mProcessingKeyEvent, false);
+ if (mMaybeInDeadKeySequence && utf16CommitString.Length() == 1 &&
+ keyDownEvent.mKeyNameIndex == KEY_NAME_INDEX_USE_STRING) {
+ mKeyboardEventWasDispatched = true;
+ // Anyway, we're not in dead key sequence anymore.
+ mMaybeInDeadKeySequence = false;
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p OnCommitCompositionNative(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return;
+ }
+
+ // First, dispatch eKeyDown event.
+ keyDownEvent.mKeyValue = utf16CommitString;
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool dispatched = dispatcher->DispatchKeyboardEvent(
+ eKeyDown, keyDownEvent, status, mProcessingKeyEvent);
+ if (!dispatched || status == nsEventStatus_eConsumeNoDefault) {
+ mKeyboardEventWasConsumed = true;
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "doesn't dispatch eKeyPress event because the preceding "
+ "eKeyDown event was not dispatched or was consumed",
+ this));
+ return;
+ }
+ if (mLastFocusedWindow != keyDownEvent.mWidget ||
+ mLastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p OnCommitCompositionNative(), Warning, "
+ "stop dispatching eKeyPress event because the preceding "
+ "eKeyDown event caused changing focused widget or "
+ "destroyed",
+ this));
+ return;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "dispatched eKeyDown event for the committed character",
+ this));
+
+ // Next, dispatch eKeyPress event.
+ dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status,
+ mProcessingKeyEvent);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "dispatched eKeyPress event for the committed character",
+ this));
+ return;
+ }
+ }
+
+ NS_ConvertUTF8toUTF16 str(commitString);
+ // Be aware, widget can be gone
+ DispatchCompositionCommitEvent(aContext, &str);
+}
+
+void IMContextWrapper::GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString) {
+ gchar* preedit_string;
+ gint cursor_pos;
+ PangoAttrList* feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
+ &cursor_pos);
+ if (preedit_string && *preedit_string) {
+ CopyUTF8toUTF16(MakeStringSpan(preedit_string), aCompositionString);
+ } else {
+ aCompositionString.Truncate();
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p GetCompositionString(aContext=0x%p), "
+ "aCompositionString=\"%s\"",
+ this, aContext, preedit_string));
+
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+}
+
+bool IMContextWrapper::MaybeDispatchKeyEventAsProcessedByIME(
+ EventMessage aFollowingEvent) {
+ if (!mLastFocusedWindow) {
+ return false;
+ }
+
+ if (!mIsKeySnooped &&
+ ((!mProcessingKeyEvent && mPostingKeyEvents.IsEmpty()) ||
+ (mProcessingKeyEvent && mKeyboardEventWasDispatched))) {
+ return true;
+ }
+
+ // A "keydown" or "keyup" event handler may change focus with the
+ // following event. In such case, we need to cancel this composition.
+ // So, we need to store IM context now because mComposingContext may be
+ // overwritten with different context if calling this method recursively.
+ // Note that we don't need to grab the context here because |context|
+ // will be used only for checking if it's same as mComposingContext.
+ GtkIMContext* oldCurrentContext = GetCurrentContext();
+ GtkIMContext* oldComposingContext = mComposingContext;
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ if (mProcessingKeyEvent || !mPostingKeyEvents.IsEmpty()) {
+ if (mProcessingKeyEvent) {
+ mKeyboardEventWasDispatched = true;
+ }
+ // If we're not handling a key event synchronously, the signal may be
+ // sent by IME without sending key event to us. In such case, we
+ // should dispatch keyboard event for the last key event which was
+ // posted to other IME process.
+ GdkEventKey* sourceEvent = mProcessingKeyEvent
+ ? mProcessingKeyEvent
+ : mPostingKeyEvents.GetFirstEvent();
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch %s %s "
+ "event: { type=%s, keyval=%s, unicode=0x%X, state=%s, "
+ "time=%u, hardware_keycode=%u, group=%u }",
+ this, ToChar(aFollowingEvent),
+ ToChar(sourceEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp),
+ mProcessingKeyEvent ? "processing" : "posted",
+ GetEventType(sourceEvent), gdk_keyval_name(sourceEvent->keyval),
+ gdk_keyval_to_unicode(sourceEvent->keyval),
+ GetEventStateName(sourceEvent->state, mIMContextID).get(),
+ sourceEvent->time, sourceEvent->hardware_keycode, sourceEvent->group));
+
+ // Let's dispatch eKeyDown event or eKeyUp event now. Note that only
+ // when we're not in a dead key composition, we should mark the
+ // eKeyDown and eKeyUp event as "processed by IME" since we should
+ // expose raw keyCode and key value to web apps the key event is a
+ // part of a dead key sequence.
+ // FYI: We should ignore if default of preceding keydown or keyup
+ // event is prevented since even on the other browsers, web
+ // applications cannot cancel the following composition event.
+ // Spec bug: https://github.com/w3c/uievents/issues/180
+ KeymapWrapper::DispatchKeyDownOrKeyUpEvent(lastFocusedWindow, sourceEvent,
+ !mMaybeInDeadKeySequence,
+ &mKeyboardEventWasConsumed);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
+ "event is dispatched",
+ this));
+
+ if (!mProcessingKeyEvent) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), removing first "
+ "event from the queue",
+ this));
+ mPostingKeyEvents.RemoveEvent(sourceEvent);
+ }
+ } else {
+ MOZ_ASSERT(mIsKeySnooped);
+ // Currently, we support key snooper mode of uim and wayland only.
+ MOZ_ASSERT(mIMContextID == IMContextID::Uim ||
+ mIMContextID == IMContextID::Wayland);
+ // uim sends "preedit_start" signal and "preedit_changed" separately
+ // at starting composition, "commit" and "preedit_end" separately at
+ // committing composition.
+
+ // Currently, we should dispatch only fake eKeyDown event because
+ // we cannot decide which is the last signal of each key operation
+ // and Chromium also dispatches only "keydown" event in this case.
+ bool dispatchFakeKeyDown = false;
+ switch (aFollowingEvent) {
+ case eCompositionStart:
+ case eCompositionCommit:
+ case eCompositionCommitAsIs:
+ case eContentCommandInsertText:
+ dispatchFakeKeyDown = true;
+ break;
+ // XXX Unfortunately, I don't have a good idea to prevent to
+ // dispatch redundant eKeyDown event for eCompositionStart
+ // immediately after "delete_surrounding" signal. However,
+ // not dispatching eKeyDown event is worse than dispatching
+ // redundant eKeyDown events.
+ case eContentCommandDelete:
+ dispatchFakeKeyDown = true;
+ break;
+ // We need to prevent to dispatch redundant eKeyDown event for
+ // eCompositionChange immediately after eCompositionStart. So,
+ // We should not dispatch eKeyDown event if dispatched composition
+ // string is still empty string.
+ case eCompositionChange:
+ dispatchFakeKeyDown = !mDispatchedCompositionString.IsEmpty();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Do you forget to handle the case?");
+ break;
+ }
+
+ if (dispatchFakeKeyDown) {
+ WidgetKeyboardEvent fakeKeyDownEvent(true, eKeyDown, lastFocusedWindow);
+ fakeKeyDownEvent.mKeyCode = NS_VK_PROCESSKEY;
+ fakeKeyDownEvent.mKeyNameIndex = KEY_NAME_INDEX_Process;
+ // It's impossible to get physical key information in this case but
+ // this should be okay since web apps shouldn't do anything with
+ // physical key information during composition.
+ fakeKeyDownEvent.mCodeNameIndex = CODE_NAME_INDEX_UNKNOWN;
+
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch fake eKeyDown event",
+ this, ToChar(aFollowingEvent)));
+
+ KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+ lastFocusedWindow, fakeKeyDownEvent, &mKeyboardEventWasConsumed);
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), "
+ "fake keydown event is dispatched",
+ this));
+ }
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the "
+ "focused widget was destroyed/changed by a key event",
+ this));
+ return false;
+ }
+
+ // If the dispatched keydown event caused moving focus and that also
+ // caused changing active context, we need to cancel composition here.
+ if (GetCurrentContext() != oldCurrentContext) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), Warning, the key "
+ "event causes changing active IM context",
+ this));
+ if (mComposingContext == oldComposingContext) {
+ // Only when the context is still composing, we should call
+ // ResetIME() here. Otherwise, it should've already been
+ // cleaned up.
+ ResetIME();
+ }
+ return false;
+ }
+
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionStart(GtkIMContext* aContext) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p DispatchCompositionStart(aContext=0x%p)", this, aContext));
+
+ if (IsComposing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "we're already in composition",
+ this));
+ return true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "cannot query the selection offset",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!mContentSelection->HasRange())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "due to no selection",
+ this));
+ return false;
+ }
+
+ mComposingContext = static_cast<GtkIMContext*>(g_object_ref(aContext));
+ MOZ_ASSERT(mComposingContext);
+
+ // Keep the last focused window alive
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ // XXX The composition start point might be changed by composition events
+ // even though we strongly hope it doesn't happen.
+ // Every composition event should have the start offset for the result
+ // because it may high cost if we query the offset every time.
+ mCompositionStart = mContentSelection->OffsetAndDataRef().StartOffset();
+ mDispatchedCompositionString.Truncate();
+
+ // If this composition is started by a key press, we need to dispatch
+ // eKeyDown or eKeyUp event before dispatching eCompositionStart event.
+ // Note that dispatching a keyboard event which is marked as "processed
+ // by IME" is okay since Chromium also dispatches keyboard event as so.
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionStart)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DispatchCompositionStart(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ static bool sHasSetTelemetry = false;
+ if (!sHasSetTelemetry) {
+ sHasSetTelemetry = true;
+ NS_ConvertUTF8toUTF16 im(GetIMName());
+ // 72 is kMaximumKeyStringLength in TelemetryScalar.cpp
+ if (im.Length() > 72) {
+ if (NS_IS_SURROGATE_PAIR(im[72 - 2], im[72 - 1])) {
+ im.Truncate(72 - 2);
+ } else {
+ im.Truncate(72 - 1);
+ }
+ // U+2026 is "..."
+ im.Append(char16_t(0x2026));
+ }
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_IME_NAME_ON_LINUX, im,
+ true);
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p DispatchCompositionStart(), dispatching "
+ "compositionstart... (mCompositionStart=%u)",
+ this, mCompositionStart));
+ mCompositionState = eCompositionState_CompositionStartDispatched;
+ nsEventStatus status;
+ dispatcher->StartComposition(status);
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, the focused "
+ "widget was destroyed/changed by compositionstart event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionChangeEvent(
+ GtkIMContext* aContext, const nsAString& aCompositionString) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", this, aContext));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (!IsComposing()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p DispatchCompositionChangeEvent(), the composition "
+ "wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+ // If this composition string change caused by a key press, we need to
+ // dispatch eKeyDown or eKeyUp before dispatching eCompositionChange event.
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(eCompositionChange)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DispatchCompositionChangeEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ // Store the selected string which will be removed by following
+ // compositionchange event.
+ if (mCompositionState == eCompositionState_CompositionStartDispatched) {
+ if (NS_WARN_IF(!EnsureToCacheContentSelection(
+ &mSelectedStringRemovedByComposition))) {
+ // XXX How should we behave in this case??
+ } else if (mContentSelection->HasRange()) {
+ // XXX We should assume, for now, any web applications don't change
+ // selection at handling this compositionchange event.
+ mCompositionStart = mContentSelection->OffsetAndDataRef().StartOffset();
+ } else {
+ // If there is no selection range, we should keep previously storing
+ // mCompositionStart.
+ }
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aContext, aCompositionString);
+
+ rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to SetPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ mCompositionState = eCompositionState_CompositionChangeEventDispatched;
+
+ // We cannot call SetCursorPosition for e10s-aware.
+ // DispatchEvent is async on e10s, so composition rect isn't updated now
+ // on tab parent.
+ mDispatchedCompositionString = aCompositionString;
+ mLayoutChanged = false;
+ mCompositionTargetRange.mOffset =
+ mCompositionStart + rangeArray->TargetClauseOffset();
+ mCompositionTargetRange.mLength = rangeArray->TargetClauseLength();
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+ rv = dispatcher->FlushPendingComposition(status);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to FlushPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, the "
+ "focused widget was destroyed/changed by "
+ "compositionchange event",
+ this));
+ return false;
+ }
+ return true;
+}
+
+bool IMContextWrapper::DispatchCompositionCommitEvent(
+ GtkIMContext* aContext, const nsAString* aCommitString) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
+ "aCommitString=0x%p, (\"%s\"))",
+ this, aContext, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ // TODO: We need special care to handle request to commit composition
+ // by content while we're committing composition because we have
+ // commit string information now but IME may not have composition
+ // anymore. Therefore, we may not be able to handle commit as
+ // expected. However, this is rare case because this situation
+ // never occurs with remote content. So, it's okay to fix this
+ // issue later. (Perhaps, TextEventDisptcher should do it for
+ // all platforms. E.g., creating WillCommitComposition()?)
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ RefPtr<TextEventDispatcher> dispatcher;
+ if (!IsComposing() &&
+ !StaticPrefs::intl_ime_use_composition_events_for_insert_text()) {
+ if (!aCommitString || aCommitString->IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "did nothing due to inserting empty string without composition",
+ this));
+ return true;
+ }
+ if (MOZ_UNLIKELY(!EnsureToCacheContentSelection())) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DispatchCompositionCommitEvent(), Warning, "
+ "Failed to cache selection before dispatching "
+ "eContentCommandInsertText event",
+ this));
+ }
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandInsertText)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DispatchCompositionCommitEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return false;
+ }
+ // Emulate selection until receiving actual selection range. This is
+ // important for OnSelectionChange. If selection is not changed by web
+ // apps, i.e., selection range is same as what selection expects, we
+ // shouldn't reset IME because the trigger of causing this commit may be an
+ // input for next composition and we shouldn't cancel it.
+ if (mContentSelection.isSome()) {
+ mContentSelection->Collapse(
+ (mContentSelection->HasRange()
+ ? mContentSelection->OffsetAndDataRef().StartOffset()
+ : mCompositionStart) +
+ aCommitString->Length());
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p DispatchCompositionCommitEvent(), mContentSelection=%s",
+ this, ToString(mContentSelection).c_str()));
+ }
+ MOZ_ASSERT(!dispatcher);
+ } else {
+ if (!IsComposing()) {
+ if (!aCommitString || aCommitString->IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there is no composition and empty commit string",
+ this));
+ return true;
+ }
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p DispatchCompositionCommitEvent(), "
+ "the composition wasn't started, force starting...",
+ this));
+ if (!DispatchCompositionStart(aContext)) {
+ return false;
+ }
+ }
+ // If this commit caused by a key press, we need to dispatch eKeyDown or
+ // eKeyUp before dispatching composition events.
+ else if (!MaybeDispatchKeyEventAsProcessedByIME(
+ aCommitString ? eCompositionCommit : eCompositionCommitAsIs)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DispatchCompositionCommitEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ mCompositionState = eCompositionState_NotComposing;
+ return false;
+ }
+
+ dispatcher = GetTextEventDispatcher();
+ MOZ_ASSERT(dispatcher);
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ // Emulate selection until receiving actual selection range.
+ const uint32_t offsetToPutCaret =
+ mCompositionStart + (aCommitString
+ ? aCommitString->Length()
+ : mDispatchedCompositionString.Length());
+ if (mContentSelection.isSome()) {
+ mContentSelection->Collapse(offsetToPutCaret);
+ } else {
+ // TODO: We should guarantee that there should be at least fake selection
+ // for IME at here. Then, we can keep the last writing mode.
+ mContentSelection.emplace(offsetToPutCaret, WritingMode());
+ }
+ }
+
+ mCompositionState = eCompositionState_NotComposing;
+ // Reset dead key sequence too because GTK doesn't support dead key chain
+ // (i.e., a key press doesn't cause both producing some characters and
+ // restarting new dead key sequence at one time). So, committing
+ // composition means end of a dead key sequence.
+ mMaybeInDeadKeySequence = false;
+ mCompositionStart = UINT32_MAX;
+ mCompositionTargetRange.Clear();
+ mDispatchedCompositionString.Truncate();
+ mSelectedStringRemovedByComposition.Truncate();
+
+ if (!dispatcher) {
+ MOZ_ASSERT(aCommitString);
+ MOZ_ASSERT(!aCommitString->IsEmpty());
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetContentCommandEvent insertTextEvent(true, eContentCommandInsertText,
+ lastFocusedWindow);
+ insertTextEvent.mString.emplace(*aCommitString);
+ lastFocusedWindow->DispatchEvent(&insertTextEvent, status);
+
+ if (!insertTextEvent.mSucceeded) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, inserting "
+ "text failed",
+ this));
+ return false;
+ }
+ } else {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ nsresult rv = dispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to CommitComposition() failure",
+ this));
+ return false;
+ }
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "the focused widget was destroyed/changed by "
+ "compositioncommit event",
+ this));
+ return false;
+ }
+
+ return true;
+}
+
+already_AddRefed<TextRangeArray> IMContextWrapper::CreateTextRangeArray(
+ GtkIMContext* aContext, const nsAString& aCompositionString) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p CreateTextRangeArray(aContext=0x%p, "
+ "aCompositionString=\"%s\" (Length()=%zu))",
+ this, aContext, NS_ConvertUTF16toUTF8(aCompositionString).get(),
+ aCompositionString.Length()));
+
+ RefPtr<TextRangeArray> textRangeArray = new TextRangeArray();
+
+ gchar* preedit_string;
+ gint cursor_pos_in_chars;
+ PangoAttrList* feedback_list;
+ gtk_im_context_get_preedit_string(aContext, &preedit_string, &feedback_list,
+ &cursor_pos_in_chars);
+ if (!preedit_string || !*preedit_string) {
+ if (!aCompositionString.IsEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, due to "
+ "preedit_string is null",
+ this));
+ }
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ // Convert caret offset from offset in characters to offset in UTF-16
+ // string. If we couldn't proper offset in UTF-16 string, we should
+ // assume that the caret is at the end of the composition string.
+ uint32_t caretOffsetInUTF16 = aCompositionString.Length();
+ if (NS_WARN_IF(cursor_pos_in_chars < 0)) {
+ // Note that this case is undocumented. We should assume that the
+ // caret is at the end of the composition string.
+ } else if (cursor_pos_in_chars == 0) {
+ caretOffsetInUTF16 = 0;
+ } else {
+ gchar* charAfterCaret =
+ g_utf8_offset_to_pointer(preedit_string, cursor_pos_in_chars);
+ if (NS_WARN_IF(!charAfterCaret)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), failed to get UTF-8 "
+ "string before the caret (cursor_pos_in_chars=%d)",
+ this, cursor_pos_in_chars));
+ } else {
+ glong caretOffset = 0;
+ gunichar2* utf16StrBeforeCaret =
+ g_utf8_to_utf16(preedit_string, charAfterCaret - preedit_string,
+ nullptr, &caretOffset, nullptr);
+ if (NS_WARN_IF(!utf16StrBeforeCaret) || NS_WARN_IF(caretOffset < 0)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, failed to "
+ "convert to UTF-16 string before the caret "
+ "(cursor_pos_in_chars=%d, caretOffset=%ld)",
+ this, cursor_pos_in_chars, caretOffset));
+ } else {
+ caretOffsetInUTF16 = static_cast<uint32_t>(caretOffset);
+ uint32_t compositionStringLength = aCompositionString.Length();
+ if (NS_WARN_IF(caretOffsetInUTF16 > compositionStringLength)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), WARNING, "
+ "caretOffsetInUTF16=%u is larger than "
+ "compositionStringLength=%u",
+ this, caretOffsetInUTF16, compositionStringLength));
+ caretOffsetInUTF16 = compositionStringLength;
+ }
+ }
+ if (utf16StrBeforeCaret) {
+ g_free(utf16StrBeforeCaret);
+ }
+ }
+ }
+
+ PangoAttrIterator* iter;
+ iter = pango_attr_list_get_iterator(feedback_list);
+ if (!iter) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p CreateTextRangeArray(), FAILED, iterator couldn't "
+ "be allocated",
+ this));
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+ return textRangeArray.forget();
+ }
+
+ uint32_t minOffsetOfClauses = aCompositionString.Length();
+ uint32_t maxOffsetOfClauses = 0;
+ do {
+ TextRange range;
+ if (!SetTextRange(iter, preedit_string, caretOffsetInUTF16, range)) {
+ continue;
+ }
+ MOZ_ASSERT(range.Length());
+ minOffsetOfClauses = std::min(minOffsetOfClauses, range.mStartOffset);
+ maxOffsetOfClauses = std::max(maxOffsetOfClauses, range.mEndOffset);
+ textRangeArray->AppendElement(range);
+ } while (pango_attr_iterator_next(iter));
+
+ // If the IME doesn't define clause from the start of the composition,
+ // we should insert dummy clause information since TextRangeArray assumes
+ // that there must be a clause whose start is 0 when there is one or
+ // more clauses.
+ if (minOffsetOfClauses) {
+ TextRange dummyClause;
+ dummyClause.mStartOffset = 0;
+ dummyClause.mEndOffset = minOffsetOfClauses;
+ dummyClause.mRangeType = TextRangeType::eRawClause;
+ textRangeArray->InsertElementAt(0, dummyClause);
+ maxOffsetOfClauses = std::max(maxOffsetOfClauses, dummyClause.mEndOffset);
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), inserting a dummy clause "
+ "at the beginning of the composition string mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, dummyClause.mStartOffset, dummyClause.mEndOffset,
+ ToChar(dummyClause.mRangeType)));
+ }
+
+ // If the IME doesn't define clause at end of the composition, we should
+ // insert dummy clause information since TextRangeArray assumes that there
+ // must be a clase whose end is the length of the composition string when
+ // there is one or more clauses.
+ if (!textRangeArray->IsEmpty() &&
+ maxOffsetOfClauses < aCompositionString.Length()) {
+ TextRange dummyClause;
+ dummyClause.mStartOffset = maxOffsetOfClauses;
+ dummyClause.mEndOffset = aCompositionString.Length();
+ dummyClause.mRangeType = TextRangeType::eRawClause;
+ textRangeArray->AppendElement(dummyClause);
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p CreateTextRangeArray(), inserting a dummy clause "
+ "at the end of the composition string mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, dummyClause.mStartOffset, dummyClause.mEndOffset,
+ ToChar(dummyClause.mRangeType)));
+ }
+
+ TextRange range;
+ range.mStartOffset = range.mEndOffset = caretOffsetInUTF16;
+ range.mRangeType = TextRangeType::eCaret;
+ textRangeArray->AppendElement(range);
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p CreateTextRangeArray(), mStartOffset=%u, "
+ "mEndOffset=%u, mRangeType=%s",
+ this, range.mStartOffset, range.mEndOffset, ToChar(range.mRangeType)));
+
+ pango_attr_iterator_destroy(iter);
+ pango_attr_list_unref(feedback_list);
+ g_free(preedit_string);
+
+ return textRangeArray.forget();
+}
+
+/* static */
+nscolor IMContextWrapper::ToNscolor(PangoAttrColor* aPangoAttrColor) {
+ PangoColor& pangoColor = aPangoAttrColor->color;
+ uint8_t r = pangoColor.red / 0x100;
+ uint8_t g = pangoColor.green / 0x100;
+ uint8_t b = pangoColor.blue / 0x100;
+ return NS_RGB(r, g, b);
+}
+
+bool IMContextWrapper::SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset,
+ TextRange& aTextRange) const {
+ // Set the range offsets in UTF-16 string.
+ gint utf8ClauseStart, utf8ClauseEnd;
+ pango_attr_iterator_range(aPangoAttrIter, &utf8ClauseStart, &utf8ClauseEnd);
+ if (utf8ClauseStart == utf8ClauseEnd) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to collapsed range", this));
+ return false;
+ }
+
+ if (!utf8ClauseStart) {
+ aTextRange.mStartOffset = 0;
+ } else {
+ glong utf16PreviousClausesLength;
+ gunichar2* utf16PreviousClausesString =
+ g_utf8_to_utf16(aUTF8CompositionString, utf8ClauseStart, nullptr,
+ &utf16PreviousClausesLength, nullptr);
+
+ if (NS_WARN_IF(!utf16PreviousClausesString)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving previous string of current clause)",
+ this));
+ return false;
+ }
+
+ aTextRange.mStartOffset = utf16PreviousClausesLength;
+ g_free(utf16PreviousClausesString);
+ }
+
+ glong utf16CurrentClauseLength;
+ gunichar2* utf16CurrentClauseString = g_utf8_to_utf16(
+ aUTF8CompositionString + utf8ClauseStart, utf8ClauseEnd - utf8ClauseStart,
+ nullptr, &utf16CurrentClauseLength, nullptr);
+
+ if (NS_WARN_IF(!utf16CurrentClauseString)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetTextRange(), FAILED, due to g_utf8_to_utf16() "
+ "failure (retrieving current clause)",
+ this));
+ return false;
+ }
+
+ // iBus Chewing IME tells us that there is an empty clause at the end of
+ // the composition string but we should ignore it since our code doesn't
+ // assume that there is an empty clause.
+ if (!utf16CurrentClauseLength) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p SetTextRange(), FAILED, due to current clause length "
+ "is 0",
+ this));
+ return false;
+ }
+
+ aTextRange.mEndOffset = aTextRange.mStartOffset + utf16CurrentClauseLength;
+ g_free(utf16CurrentClauseString);
+ utf16CurrentClauseString = nullptr;
+
+ // Set styles
+ TextRangeStyle& style = aTextRange.mRangeStyle;
+
+ // Underline
+ PangoAttrInt* attrUnderline = reinterpret_cast<PangoAttrInt*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE));
+ if (attrUnderline) {
+ switch (attrUnderline->value) {
+ case PANGO_UNDERLINE_NONE:
+ style.mLineStyle = TextRangeStyle::LineStyle::None;
+ break;
+ case PANGO_UNDERLINE_DOUBLE:
+ style.mLineStyle = TextRangeStyle::LineStyle::Double;
+ break;
+ case PANGO_UNDERLINE_ERROR:
+ style.mLineStyle = TextRangeStyle::LineStyle::Wavy;
+ break;
+ case PANGO_UNDERLINE_SINGLE:
+ case PANGO_UNDERLINE_LOW:
+ style.mLineStyle = TextRangeStyle::LineStyle::Solid;
+ break;
+ default:
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p SetTextRange(), retrieved unknown underline "
+ "style: %d",
+ this, attrUnderline->value));
+ style.mLineStyle = TextRangeStyle::LineStyle::Solid;
+ break;
+ }
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+
+ // Underline color
+ PangoAttrColor* attrUnderlineColor = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_UNDERLINE_COLOR));
+ if (attrUnderlineColor) {
+ style.mUnderlineColor = ToNscolor(attrUnderlineColor);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_UNDERLINE_COLOR;
+ }
+ } else {
+ style.mLineStyle = TextRangeStyle::LineStyle::None;
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_LINESTYLE;
+ }
+
+ // Don't set colors if they are not specified. They should be computed by
+ // textframe if only one of the colors are specified.
+
+ // Foreground color (text color)
+ PangoAttrColor* attrForeground = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_FOREGROUND));
+ if (attrForeground) {
+ style.mForegroundColor = ToNscolor(attrForeground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_FOREGROUND_COLOR;
+ }
+
+ // Background color
+ PangoAttrColor* attrBackground = reinterpret_cast<PangoAttrColor*>(
+ pango_attr_iterator_get(aPangoAttrIter, PANGO_ATTR_BACKGROUND));
+ if (attrBackground) {
+ style.mBackgroundColor = ToNscolor(attrBackground);
+ style.mDefinedStyles |= TextRangeStyle::DEFINED_BACKGROUND_COLOR;
+ }
+
+ /**
+ * We need to judge the meaning of the clause for a11y. Before we support
+ * IME specific composition string style, we used following rules:
+ *
+ * 1: If attrUnderline and attrForground are specified, we assumed the
+ * clause is TextRangeType::eSelectedClause.
+ * 2: If only attrUnderline is specified, we assumed the clause is
+ * TextRangeType::eConvertedClause.
+ * 3: If only attrForground is specified, we assumed the clause is
+ * TextRangeType::eSelectedRawClause.
+ * 4: If neither attrUnderline nor attrForeground is specified, we assumed
+ * the clause is TextRangeType::eRawClause.
+ *
+ * However, this rules are odd since there can be two or more selected
+ * clauses. Additionally, our old rules caused that IME developers/users
+ * cannot specify composition string style as they want.
+ *
+ * So, we shouldn't guess the meaning from its visual style.
+ */
+
+ // If the range covers whole of composition string and the caret is at
+ // the end of the composition string, the range is probably not converted.
+ if (!utf8ClauseStart &&
+ utf8ClauseEnd == static_cast<gint>(strlen(aUTF8CompositionString)) &&
+ aTextRange.mEndOffset == aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eRawClause;
+ }
+ // Typically, the caret is set at the start of the selected clause.
+ // So, if the caret is in the clause, we can assume that the clause is
+ // selected.
+ else if (aTextRange.mStartOffset <= aUTF16CaretOffset &&
+ aTextRange.mEndOffset > aUTF16CaretOffset) {
+ aTextRange.mRangeType = TextRangeType::eSelectedClause;
+ }
+ // Otherwise, we should assume that the clause is converted but not
+ // selected.
+ else {
+ aTextRange.mRangeType = TextRangeType::eConvertedClause;
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p SetTextRange(), succeeded, aTextRange= { "
+ "mStartOffset=%u, mEndOffset=%u, mRangeType=%s, mRangeStyle=%s }",
+ this, aTextRange.mStartOffset, aTextRange.mEndOffset,
+ ToChar(aTextRange.mRangeType),
+ GetTextRangeStyleText(aTextRange.mRangeStyle).get()));
+
+ return true;
+}
+
+void IMContextWrapper::SetCursorPosition(GtkIMContext* aContext) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Info,
+ ("0x%p SetCursorPosition(aContext=0x%p), "
+ "mCompositionTargetRange={ mOffset=%u, mLength=%u }, "
+ "mContentSelection=%s",
+ this, aContext, mCompositionTargetRange.mOffset,
+ mCompositionTargetRange.mLength, ToString(mContentSelection).c_str()));
+
+ bool useCaret = false;
+ if (!mCompositionTargetRange.IsValid()) {
+ if (mContentSelection.isNothing()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, "
+ "mCompositionTargetRange and mContentSelection are invalid",
+ this));
+ return;
+ }
+ if (!mContentSelection->HasRange()) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p SetCursorPosition(), FAILED, "
+ "mCompositionTargetRange is invalid and there is no selection",
+ this));
+ return;
+ }
+ useCaret = true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no focused "
+ "window",
+ this));
+ return;
+ }
+
+ if (MOZ_UNLIKELY(!aContext)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no context", this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretOrTextRectEvent(
+ true, useCaret ? eQueryCaretRect : eQueryTextRect, mLastFocusedWindow);
+ if (useCaret) {
+ queryCaretOrTextRectEvent.InitForQueryCaretRect(
+ mContentSelection->OffsetAndDataRef().StartOffset());
+ } else {
+ if (mContentSelection->WritingModeRef().IsVertical()) {
+ // For preventing the candidate window to overlap the target
+ // clause, we should set fake (typically, very tall) caret rect.
+ uint32_t length =
+ mCompositionTargetRange.mLength ? mCompositionTargetRange.mLength : 1;
+ queryCaretOrTextRectEvent.InitForQueryTextRect(
+ mCompositionTargetRange.mOffset, length);
+ } else {
+ queryCaretOrTextRectEvent.InitForQueryTextRect(
+ mCompositionTargetRange.mOffset, 1);
+ }
+ }
+ nsEventStatus status;
+ mLastFocusedWindow->DispatchEvent(&queryCaretOrTextRectEvent, status);
+ if (queryCaretOrTextRectEvent.Failed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, %s was failed", this,
+ useCaret ? "eQueryCaretRect" : "eQueryTextRect"));
+ return;
+ }
+
+ nsWindow* rootWindow =
+ static_cast<nsWindow*>(mLastFocusedWindow->GetTopLevelWidget());
+
+ // Get the position of the rootWindow in screen.
+ LayoutDeviceIntPoint root = rootWindow->WidgetToScreenOffset();
+
+ // Get the position of IM context owner window in screen.
+ LayoutDeviceIntPoint owner = mOwnerWindow->WidgetToScreenOffset();
+
+ // Compute the caret position in the IM owner window.
+ LayoutDeviceIntRect rect =
+ queryCaretOrTextRectEvent.mReply->mRect + root - owner;
+ rect.width = 0;
+ GdkRectangle area = rootWindow->DevicePixelsToGdkRectRoundOut(rect);
+
+ gtk_im_context_set_cursor_location(aContext, &area);
+}
+
+nsresult IMContextWrapper::GetCurrentParagraph(nsAString& aText,
+ uint32_t& aCursorPos) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p GetCurrentParagraph(), mCompositionState=%s", this,
+ GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, there are no "
+ "focused window in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ nsEventStatus status;
+
+ uint32_t selOffset = mCompositionStart;
+ uint32_t selLength = mSelectedStringRemovedByComposition.Length();
+
+ // If focused editor doesn't have composition string, we should use
+ // current selection.
+ if (!EditorHasCompositionString()) {
+ // Query cursor position & selection
+ if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, due to no "
+ "valid selection information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (mContentSelection.isSome() && mContentSelection->HasRange()) {
+ selOffset = mContentSelection->OffsetAndDataRef().StartOffset();
+ selLength = mContentSelection->OffsetAndDataRef().Length();
+ } else {
+ // If there is no range, let's get all text instead...
+ selOffset = 0u;
+ selLength = INT32_MAX; // TODO: Change to UINT32_MAX, but see below
+ }
+ }
+
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), selOffset=%u, selLength=%u", this,
+ selOffset, selLength));
+
+ // XXX nsString::Find and nsString::RFind take int32_t for offset, so,
+ // we cannot support this request when the current offset is larger
+ // than INT32_MAX.
+ if (selOffset > INT32_MAX || selLength > INT32_MAX ||
+ selOffset + selLength > INT32_MAX) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "out of range",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (selOffset + selLength > queryTextContentEvent.mReply->DataLength()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, The selection is "
+ "invalid, queryTextContentEvent={ mReply=%s }",
+ this, ToString(queryTextContentEvent.mReply).c_str()));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Remove composing string and restore the selected string because
+ // GtkEntry doesn't remove selected string until committing, however,
+ // our editor does it. We should emulate the behavior for IME.
+ nsAutoString textContent(queryTextContentEvent.mReply->DataRef());
+ if (EditorHasCompositionString() &&
+ mDispatchedCompositionString != mSelectedStringRemovedByComposition) {
+ textContent.Replace(mCompositionStart,
+ mDispatchedCompositionString.Length(),
+ mSelectedStringRemovedByComposition);
+ }
+
+ // Get only the focused paragraph, by looking for newlines
+ int32_t parStart = 0;
+ if (selOffset > 0) {
+ parStart = Substring(textContent, 0, selOffset - 1).RFind(u"\n") + 1;
+ }
+ int32_t parEnd = textContent.Find(u"\n", selOffset + selLength);
+ if (parEnd < 0) {
+ parEnd = textContent.Length();
+ }
+ aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
+ aCursorPos = selOffset - uint32_t(parStart);
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
+ "aText.Length()=%zu, aCursorPos=%u",
+ this, NS_ConvertUTF16toUTF8(aText).get(), aText.Length(), aCursorPos));
+
+ return NS_OK;
+}
+
+nsresult IMContextWrapper::DeleteText(GtkIMContext* aContext, int32_t aOffset,
+ uint32_t aNChars) {
+ MOZ_LOG(gIMELog, LogLevel::Info,
+ ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
+ "mCompositionState=%s",
+ this, aContext, aOffset, aNChars, GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there are no focused window "
+ "in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aNChars) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars must not be zero", this));
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+ nsEventStatus status;
+
+ // First, we should cancel current composition because editor cannot
+ // handle changing selection and deleting text.
+ uint32_t selOffset;
+ bool wasComposing = IsComposing();
+ bool editorHadCompositionString = EditorHasCompositionString();
+ if (wasComposing) {
+ selOffset = mCompositionStart;
+ if (!DispatchCompositionCommitEvent(aContext,
+ &mSelectedStringRemovedByComposition)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, quitting from DeletText", this));
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (NS_WARN_IF(!EnsureToCacheContentSelection())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, due to no valid selection "
+ "information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+ if (!mContentSelection->HasRange()) {
+ MOZ_LOG(gIMELog, LogLevel::Debug,
+ ("0x%p DeleteText(), does nothing, due to no selection range",
+ this));
+ return NS_OK;
+ }
+ selOffset = mContentSelection->OffsetAndDataRef().StartOffset();
+ }
+
+ // Get all text contents of the focused editor
+ WidgetQueryContentEvent queryTextContentEvent(true, eQueryTextContent,
+ mLastFocusedWindow);
+ queryTextContentEvent.InitForQueryTextContent(0, UINT32_MAX);
+ mLastFocusedWindow->DispatchEvent(&queryTextContentEvent, status);
+ if (NS_WARN_IF(queryTextContentEvent.Failed())) {
+ return NS_ERROR_FAILURE;
+ }
+ if (queryTextContentEvent.mReply->IsDataEmpty()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there is no contents", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ NS_ConvertUTF16toUTF8 utf8Str(nsDependentSubstring(
+ queryTextContentEvent.mReply->DataRef(), 0, selOffset));
+ glong offsetInUTF8Characters =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length()) + aOffset;
+ if (offsetInUTF8Characters < 0) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aOffset is too small for "
+ "current cursor pos (computed offset: %ld)",
+ this, offsetInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ AppendUTF16toUTF8(
+ nsDependentSubstring(queryTextContentEvent.mReply->DataRef(), selOffset),
+ utf8Str);
+ glong countOfCharactersInUTF8 =
+ g_utf8_strlen(utf8Str.get(), utf8Str.Length());
+ glong endInUTF8Characters = offsetInUTF8Characters + aNChars;
+ if (countOfCharactersInUTF8 < endInUTF8Characters) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, aNChars is too large for "
+ "current contents (content length: %ld, computed end offset: %ld)",
+ this, countOfCharactersInUTF8, endInUTF8Characters));
+ return NS_ERROR_FAILURE;
+ }
+
+ gchar* charAtOffset =
+ g_utf8_offset_to_pointer(utf8Str.get(), offsetInUTF8Characters);
+ gchar* charAtEnd =
+ g_utf8_offset_to_pointer(utf8Str.get(), endInUTF8Characters);
+
+ // Set selection to delete
+ WidgetSelectionEvent selectionEvent(true, eSetSelection, mLastFocusedWindow);
+
+ nsDependentCSubstring utf8StrBeforeOffset(utf8Str, 0,
+ charAtOffset - utf8Str.get());
+ selectionEvent.mOffset = NS_ConvertUTF8toUTF16(utf8StrBeforeOffset).Length();
+
+ nsDependentCSubstring utf8DeletingStr(utf8Str, utf8StrBeforeOffset.Length(),
+ charAtEnd - charAtOffset);
+ selectionEvent.mLength = NS_ConvertUTF8toUTF16(utf8DeletingStr).Length();
+
+ selectionEvent.mReversed = false;
+ selectionEvent.mExpandToClusterBoundary = false;
+ lastFocusedWindow->DispatchEvent(&selectionEvent, status);
+
+ if (!selectionEvent.mSucceeded || lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, setting selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // If this deleting text caused by a key press, we need to dispatch
+ // eKeyDown or eKeyUp before dispatching eContentCommandDelete event.
+ if (!MaybeDispatchKeyEventAsProcessedByIME(eContentCommandDelete)) {
+ MOZ_LOG(gIMELog, LogLevel::Warning,
+ ("0x%p DeleteText(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ // Delete the selection
+ WidgetContentCommandEvent contentCommandEvent(true, eContentCommandDelete,
+ mLastFocusedWindow);
+ mLastFocusedWindow->DispatchEvent(&contentCommandEvent, status);
+
+ if (!contentCommandEvent.mSucceeded ||
+ lastFocusedWindow != mLastFocusedWindow ||
+ lastFocusedWindow->Destroyed()) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, deleting the selection caused "
+ "focus change or window destroyed",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!wasComposing) {
+ return NS_OK;
+ }
+
+ // Restore the composition at new caret position.
+ if (!DispatchCompositionStart(aContext)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, resterting composition start", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!editorHadCompositionString) {
+ return NS_OK;
+ }
+
+ nsAutoString compositionString;
+ GetCompositionString(aContext, compositionString);
+ if (!DispatchCompositionChangeEvent(aContext, compositionString)) {
+ MOZ_LOG(
+ gIMELog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, restoring composition string", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+bool IMContextWrapper::EnsureToCacheContentSelection(
+ nsAString* aSelectedString) {
+ if (aSelectedString) {
+ aSelectedString->Truncate();
+ }
+
+ if (mContentSelection.isSome()) {
+ if (mContentSelection->HasRange() && aSelectedString) {
+ aSelectedString->Assign(mContentSelection->OffsetAndDataRef().DataRef());
+ }
+ return true;
+ }
+
+ RefPtr<nsWindow> dispatcherWindow =
+ mLastFocusedWindow ? mLastFocusedWindow : mOwnerWindow;
+ if (NS_WARN_IF(!dispatcherWindow)) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p EnsureToCacheContentSelection(), FAILED, due to "
+ "no focused window",
+ this));
+ return false;
+ }
+
+ nsEventStatus status;
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ dispatcherWindow);
+ dispatcherWindow->DispatchEvent(&querySelectedTextEvent, status);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ MOZ_LOG(gIMELog, LogLevel::Error,
+ ("0x%p EnsureToCacheContentSelection(), FAILED, due to "
+ "failure of query selection event",
+ this));
+ return false;
+ }
+
+ mContentSelection = Some(ContentSelection(querySelectedTextEvent));
+ if (mContentSelection->HasRange()) {
+ if (!mContentSelection->OffsetAndDataRef().IsDataEmpty() &&
+ aSelectedString) {
+ aSelectedString->Assign(querySelectedTextEvent.mReply->DataRef());
+ }
+ }
+
+ MOZ_LOG(
+ gIMELog, LogLevel::Debug,
+ ("0x%p EnsureToCacheContentSelection(), Succeeded, mContentSelection=%s",
+ this, ToString(mContentSelection).c_str()));
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 0000000000..213c5ce8d3
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,692 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef IMContextWrapper_h_
+#define IMContextWrapper_h_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "nsString.h"
+#include "nsCOMPtr.h"
+#include "nsTArray.h"
+#include "nsIWidget.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/ContentData.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "mozilla/WritingModes.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/widget/IMEData.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * KeyHandlingState is result of IMContextWrapper::OnKeyEvent().
+ */
+enum class KeyHandlingState {
+ // The native key event has not been handled by IMContextWrapper.
+ eNotHandled,
+ // The native key event was handled by IMContextWrapper.
+ eHandled,
+ // The native key event has not been handled by IMContextWrapper,
+ // but eKeyDown or eKeyUp event has been dispatched.
+ eNotHandledButEventDispatched,
+ // The native key event has not been handled by IMContextWrapper,
+ // but eKeyDown or eKeyUp event has been dispatched and consumed.
+ eNotHandledButEventConsumed,
+};
+
+class IMContextWrapper final : public TextEventDispatcherListener {
+ public:
+ // TextEventDispatcherListener implementation
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD NotifyIME(TextEventDispatcher* aTextEventDispatcher,
+ const IMENotification& aNotification) override;
+ NS_IMETHOD_(IMENotificationRequests) GetIMENotificationRequests() override;
+ NS_IMETHOD_(void)
+ OnRemovedFrom(TextEventDispatcher* aTextEventDispatcher) override;
+ NS_IMETHOD_(void)
+ WillDispatchKeyboardEvent(TextEventDispatcher* aTextEventDispatcher,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ uint32_t aIndexOfKeypress, void* aData) override;
+
+ public:
+ // aOwnerWindow is a pointer of the owner window. When aOwnerWindow is
+ // destroyed, the related IME contexts are released (i.e., IME cannot be
+ // used with the instance after that).
+ explicit IMContextWrapper(nsWindow* aOwnerWindow);
+
+ // Called when the process is being shut down.
+ static void Shutdown();
+
+ // "Enabled" means the users can use all IMEs.
+ // I.e., the focus is in the normal editors.
+ bool IsEnabled() const;
+
+ // OnFocusWindow is a notification that aWindow is going to be focused.
+ void OnFocusWindow(nsWindow* aWindow);
+ // OnBlurWindow is a notification that aWindow is going to be unfocused.
+ void OnBlurWindow(nsWindow* aWindow);
+ // OnDestroyWindow is a notification that aWindow is going to be destroyed.
+ void OnDestroyWindow(nsWindow* aWindow);
+ // OnFocusChangeInGecko is a notification that an editor gets focus.
+ void OnFocusChangeInGecko(bool aFocus);
+ // OnSelectionChange is a notification that selection (caret) is changed
+ // in the focused editor.
+ void OnSelectionChange(nsWindow* aCaller,
+ const IMENotification& aIMENotification);
+ // OnThemeChanged is called when desktop theme is changed.
+ static void OnThemeChanged();
+
+ /**
+ * OnKeyEvent() is called when aWindow gets a native key press event or a
+ * native key release event. If this returns true, the key event was
+ * filtered by IME. Otherwise, this returns false.
+ * NOTE: When the native key press event starts composition, this returns
+ * true but dispatches an eKeyDown event or eKeyUp event before
+ * dispatching composition events or content command event.
+ *
+ * @param aWindow A window on which user operate the
+ * key.
+ * @param aEvent A native key press or release
+ * event.
+ * @param aKeyboardEventWasDispatched true if eKeyDown or eKeyUp event
+ * for aEvent has already been
+ * dispatched. In this case,
+ * this class doesn't dispatch
+ * keyboard event anymore.
+ */
+ KeyHandlingState OnKeyEvent(nsWindow* aWindow, GdkEventKey* aEvent,
+ bool aKeyboardEventWasDispatched = false);
+
+ // IME related nsIWidget methods.
+ nsresult EndIMEComposition(nsWindow* aCaller);
+ void SetInputContext(nsWindow* aCaller, const InputContext* aContext,
+ const InputContextAction* aAction);
+ InputContext GetInputContext();
+ void OnUpdateComposition();
+ void OnLayoutChange();
+
+ // Set GdkWindow associated with IM context.
+ // It can be null which disables context operations.
+ void SetGdkWindow(GdkWindow* aGdkWindow);
+
+ TextEventDispatcher* GetTextEventDispatcher();
+
+ // TODO: Typically, new IM comes every several years. And now, our code
+ // becomes really IM behavior dependent. So, perhaps, we need prefs
+ // to control related flags for IM developers.
+ enum class IMContextID : uint8_t {
+ Fcitx, // 4.x or earlier
+ Fcitx5,
+ IBus,
+ IIIMF,
+ Scim,
+ Uim,
+ Wayland,
+ Unknown,
+ };
+
+ friend std::ostream& operator<<(std::ostream& aStream,
+ const IMContextID& aIMContextID) {
+ switch (aIMContextID) {
+ case IMContextID::Fcitx:
+ return aStream << "Fcitx";
+ case IMContextID::Fcitx5:
+ return aStream << "Fcitx5";
+ case IMContextID::IBus:
+ return aStream << "IBus";
+ case IMContextID::IIIMF:
+ return aStream << "IIIMF";
+ case IMContextID::Scim:
+ return aStream << "Scim";
+ case IMContextID::Uim:
+ return aStream << "Uim";
+ case IMContextID::Wayland:
+ return aStream << "Wayland";
+ case IMContextID::Unknown:
+ return aStream << "Unknown";
+ }
+ MOZ_ASSERT_UNREACHABLE("Add new case for the new IM support");
+ return aStream << "Unknown";
+ }
+
+ /**
+ * GetIMName() returns IM name associated with mContext. If the context is
+ * xim, this look for actual engine from XMODIFIERS environment variable.
+ */
+ nsDependentCSubstring GetIMName() const;
+
+ /**
+ * GetWaitingSynthesizedKeyPressHardwareKeyCode() returns hardware_keycode
+ * value of last handled GDK_KEY_PRESS event which is probable handled by
+ * IME asynchronously and we have not received synthesized GDK_KEY_PRESS
+ * event yet.
+ */
+ static guint16 GetWaitingSynthesizedKeyPressHardwareKeyCode() {
+ return sWaitingSynthesizedKeyPressHardwareKeyCode;
+ }
+
+ protected:
+ ~IMContextWrapper();
+
+ /**
+ * SetInputPurposeAndInputHints() sets input-purpose and input-hints of
+ * current IM context to the values computed with mInputContext.
+ */
+ void SetInputPurposeAndInputHints();
+
+ // Owner of an instance of this class. This should be top level window.
+ // The owner window must release the contexts when it's destroyed because
+ // the IME contexts need the native window. If OnDestroyWindow() is called
+ // with the owner window, it'll release IME contexts. Otherwise, it'll
+ // just clean up any existing composition if it's related to the destroying
+ // child window.
+ nsWindow* mOwnerWindow;
+
+ // A last focused window in this class's context.
+ nsWindow* mLastFocusedWindow;
+
+ // Actual context. This is used for handling the user's input.
+ GtkIMContext* mContext;
+
+ // mSimpleContext is used for the password field and
+ // the |ime-mode: disabled;| editors if sUseSimpleContext is true.
+ // These editors disable IME. But dead keys should work. Fortunately,
+ // the simple IM context of GTK2 support only them.
+ GtkIMContext* mSimpleContext;
+
+ // mDummyContext is a dummy context and will be used in Focus()
+ // when the state of mEnabled means disabled. This context's IME state is
+ // always "closed", so it closes IME forcedly.
+ GtkIMContext* mDummyContext;
+
+ // mComposingContext is not nullptr while one of mContext, mSimpleContext
+ // and mDummyContext has composition.
+ // XXX: We don't assume that two or more context have composition same time.
+ GtkIMContext* mComposingContext;
+
+ // IME enabled state and other things defined in InputContext.
+ // Use following helper methods if you don't need the detail of the status.
+ InputContext mInputContext;
+
+ // mCompositionStart is the start offset of the composition string in the
+ // current content. When <textarea> or <input> have focus, it means offset
+ // from the first character of them. When a HTML editor has focus, it
+ // means offset from the first character of the root element of the editor.
+ uint32_t mCompositionStart;
+
+ // mDispatchedCompositionString is the latest composition string which
+ // was dispatched by compositionupdate event.
+ nsString mDispatchedCompositionString;
+
+ // mSelectedStringRemovedByComposition is the selected string which was
+ // removed by first compositionchange event.
+ nsString mSelectedStringRemovedByComposition;
+
+ // OnKeyEvent() temporarily sets mProcessingKeyEvent to the given native
+ // event.
+ GdkEventKey* mProcessingKeyEvent;
+
+ /**
+ * GdkEventKeyQueue stores *copy* of GdkEventKey instances. However, this
+ * must be safe to our usecase since it has |time| and the value should not
+ * be same as older event.
+ */
+ class GdkEventKeyQueue final {
+ public:
+ ~GdkEventKeyQueue() { Clear(); }
+
+ void Clear() { mEvents.Clear(); }
+
+ /**
+ * PutEvent() puts new event into the queue.
+ */
+ void PutEvent(const GdkEventKey* aEvent) {
+ GdkEventKey* newEvent = reinterpret_cast<GdkEventKey*>(
+ gdk_event_copy(reinterpret_cast<const GdkEvent*>(aEvent)));
+ newEvent->state &= GDK_MODIFIER_MASK;
+ mEvents.AppendElement(newEvent);
+ }
+
+ /**
+ * RemoveEvent() removes oldest same event and its preceding events
+ * from the queue.
+ */
+ void RemoveEvent(const GdkEventKey* aEvent) {
+ size_t index = IndexOf(aEvent);
+ if (NS_WARN_IF(index == GdkEventKeyQueue::NoIndex())) {
+ return;
+ }
+ mEvents.RemoveElementAt(index);
+ }
+
+ /**
+ * Return corresponding GDK_KEY_PRESS event for aEvent. aEvent must be a
+ * GDK_KEY_RELEASE event.
+ */
+ const GdkEventKey* GetCorrespondingKeyPressEvent(
+ const GdkEventKey* aEvent) const {
+ MOZ_ASSERT(aEvent->type == GDK_KEY_RELEASE);
+ for (const GUniquePtr<GdkEventKey>& pendingKeyEvent : mEvents) {
+ if (pendingKeyEvent->type == GDK_KEY_PRESS &&
+ aEvent->hardware_keycode == pendingKeyEvent->hardware_keycode) {
+ return pendingKeyEvent.get();
+ }
+ }
+ return nullptr;
+ }
+
+ /**
+ * FirstEvent() returns oldest event in the queue.
+ */
+ GdkEventKey* GetFirstEvent() const {
+ if (mEvents.IsEmpty()) {
+ return nullptr;
+ }
+ return mEvents[0].get();
+ }
+
+ bool IsEmpty() const { return mEvents.IsEmpty(); }
+
+ static size_t NoIndex() { return nsTArray<GdkEventKey*>::NoIndex; }
+ size_t Length() const { return mEvents.Length(); }
+ size_t IndexOf(const GdkEventKey* aEvent) const {
+ static_assert(!(GDK_MODIFIER_MASK & (1 << 24)),
+ "We assumes 25th bit is used by some IM, but used by GDK");
+ static_assert(!(GDK_MODIFIER_MASK & (1 << 25)),
+ "We assumes 26th bit is used by some IM, but used by GDK");
+ for (size_t i = 0; i < mEvents.Length(); i++) {
+ GdkEventKey* event = mEvents[i].get();
+ // It must be enough to compare only type, time, keyval and
+ // part of state. Note that we cannot compaire two events
+ // simply since IME may have changed unused bits of state.
+ if (event->time == aEvent->time) {
+ if (NS_WARN_IF(event->type != aEvent->type) ||
+ NS_WARN_IF(event->keyval != aEvent->keyval) ||
+ NS_WARN_IF(event->state != (aEvent->state & GDK_MODIFIER_MASK))) {
+ continue;
+ }
+ }
+ return i;
+ }
+ return GdkEventKeyQueue::NoIndex();
+ }
+
+ private:
+ nsTArray<GUniquePtr<GdkEventKey>> mEvents;
+ };
+ // OnKeyEvent() append mPostingKeyEvents when it believes that a key event
+ // is posted to other IME process.
+ GdkEventKeyQueue mPostingKeyEvents;
+
+ static guint16 sWaitingSynthesizedKeyPressHardwareKeyCode;
+
+ struct Range {
+ uint32_t mOffset;
+ uint32_t mLength;
+
+ Range() : mOffset(UINT32_MAX), mLength(UINT32_MAX) {}
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ void Clear() {
+ mOffset = UINT32_MAX;
+ mLength = UINT32_MAX;
+ }
+ };
+
+ // current target offset and length of IME composition
+ Range mCompositionTargetRange;
+
+ // mCompositionState indicates current status of composition.
+ enum eCompositionState : uint8_t {
+ eCompositionState_NotComposing,
+ eCompositionState_CompositionStartDispatched,
+ eCompositionState_CompositionChangeEventDispatched
+ };
+ eCompositionState mCompositionState;
+
+ bool IsComposing() const {
+ return (mCompositionState != eCompositionState_NotComposing);
+ }
+
+ bool IsComposingOn(GtkIMContext* aContext) const {
+ return IsComposing() && mComposingContext == aContext;
+ }
+
+ bool IsComposingOnCurrentContext() const {
+ return IsComposingOn(GetCurrentContext());
+ }
+
+ bool EditorHasCompositionString() {
+ return (mCompositionState ==
+ eCompositionState_CompositionChangeEventDispatched);
+ }
+
+ /**
+ * Checks if aContext is valid context for handling composition.
+ *
+ * @param aContext An IM context which is specified by native
+ * composition events.
+ * @return true if the context is valid context for
+ * handling composition. Otherwise, false.
+ */
+ bool IsValidContext(GtkIMContext* aContext) const;
+
+ const char* GetCompositionStateName() {
+ switch (mCompositionState) {
+ case eCompositionState_NotComposing:
+ return "NotComposing";
+ case eCompositionState_CompositionStartDispatched:
+ return "CompositionStartDispatched";
+ case eCompositionState_CompositionChangeEventDispatched:
+ return "CompositionChangeEventDispatched";
+ default:
+ return "InvaildState";
+ }
+ }
+
+ // mIMContextID indicates the ID of mContext. This is actually indicates
+ // IM which user selected.
+ IMContextID mIMContextID;
+
+ // If mContentSelection is Nothing, it means that
+ // EnsureToCacheContentSelection failed to get selection or just not caching
+ // the selection.
+ Maybe<ContentSelection> mContentSelection;
+
+ /**
+ * Return true if mContentSelection is set to some. Otherwise, false.
+ */
+ bool EnsureToCacheContentSelection(nsAString* aSelectedString = nullptr);
+
+ enum class IMEFocusState : uint8_t {
+ // IME has focus
+ Focused,
+ // IME was blurred
+ Blurred,
+ // IME was blurred without a focus change
+ BlurredWithoutFocusChange,
+ };
+ friend std::ostream& operator<<(std::ostream& aStream, IMEFocusState aState) {
+ switch (aState) {
+ case IMEFocusState::Focused:
+ return aStream << "IMEFocusState::Focused";
+ case IMEFocusState::Blurred:
+ return aStream << "IMEFocusState::Blurred";
+ case IMEFocusState::BlurredWithoutFocusChange:
+ return aStream << "IMEFocusState::BlurredWithoutFocusChange";
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid value");
+ return aStream << "<illegal value>";
+ }
+ }
+ IMEFocusState mIMEFocusState = IMEFocusState::Blurred;
+
+ // mFallbackToKeyEvent is set to false when this class starts to handle
+ // a native key event (at that time, mProcessingKeyEvent is set to the
+ // native event). If active IME just commits composition with a character
+ // which is produced by the key with current keyboard layout, this is set
+ // to true.
+ bool mFallbackToKeyEvent;
+ // mKeyboardEventWasDispatched is used by OnKeyEvent() and
+ // MaybeDispatchKeyEventAsProcessedByIME().
+ // MaybeDispatchKeyEventAsProcessedByIME() dispatches an eKeyDown or
+ // eKeyUp event event if the composition is caused by a native
+ // key press event. If this is true, a keyboard event has been dispatched
+ // for the native event. If so, MaybeDispatchKeyEventAsProcessedByIME()
+ // won't dispatch keyboard event anymore.
+ bool mKeyboardEventWasDispatched;
+ // Whether the keyboard event which as dispatched at setting
+ // mKeyboardEventWasDispatched to true was consumed or not.
+ bool mKeyboardEventWasConsumed;
+ // mIsDeletingSurrounding is true while OnDeleteSurroundingNative() is
+ // trying to delete the surrounding text.
+ bool mIsDeletingSurrounding;
+ // mLayoutChanged is true after OnLayoutChange() is called. This is reset
+ // when eCompositionChange is being dispatched.
+ bool mLayoutChanged;
+ // mSetCursorPositionOnKeyEvent true when caret rect or position is updated
+ // with no composition. If true, we update candidate window position
+ // before key down
+ bool mSetCursorPositionOnKeyEvent;
+ // mPendingResettingIMContext becomes true if selection change notification
+ // is received during composition but the selection change occurred before
+ // starting the composition. In such case, we cannot notify IME of
+ // selection change during composition because we don't want to commit
+ // the composition in such case. However, we should notify IME of the
+ // selection change after the composition is committed.
+ bool mPendingResettingIMContext;
+ // mRetrieveSurroundingSignalReceived is true after "retrieve_surrounding"
+ // signal is received until selection is changed in Gecko.
+ bool mRetrieveSurroundingSignalReceived;
+ // mMaybeInDeadKeySequence is set to true when we detect a dead key press
+ // and set to false when we're sure dead key sequence has been finished.
+ // Note that we cannot detect which key event causes ending a dead key
+ // sequence. For example, when you press dead key grave with ibus Spanish
+ // keyboard layout, it just consumes the key event when we call
+ // gtk_im_context_filter_keypress(). Then, pressing "Escape" key cancels
+ // the dead key sequence but we don't receive any signal and it's consumed
+ // by gtk_im_context_filter_keypress() normally. On the other hand, when
+ // pressing "Shift" key causes exactly same behavior but dead key sequence
+ // isn't finished yet.
+ bool mMaybeInDeadKeySequence;
+ // mIsIMInAsyncKeyHandlingMode is set to true if we know that IM handles
+ // key events asynchronously. I.e., filtered key event may come again
+ // later.
+ bool mIsIMInAsyncKeyHandlingMode;
+ // mIsKeySnooped is set to true if IM uses key snooper to listen key events.
+ // In such case, we won't receive key events if IME consumes the event.
+ bool mIsKeySnooped;
+ // mSetInputPurposeAndInputHints is set if `SetInputContext` wants `Focus`
+ // to set input-purpose and input-hints.
+ bool mSetInputPurposeAndInputHints;
+
+ // sLastFocusedContext is a pointer to the last focused instance of this
+ // class. When a instance is destroyed and sLastFocusedContext refers it,
+ // this is cleared. So, this refers valid pointer always.
+ static IMContextWrapper* sLastFocusedContext;
+
+ // sUseSimpleContext indeicates if password editors and editors with
+ // |ime-mode: disabled;| should use GtkIMContextSimple.
+ // If true, they use GtkIMContextSimple. Otherwise, not.
+ static bool sUseSimpleContext;
+
+ // Callback methods for native IME events. These methods should call
+ // the related instance methods simply.
+ static gboolean OnRetrieveSurroundingCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static gboolean OnDeleteSurroundingCallback(GtkIMContext* aContext,
+ gint aOffset, gint aNChars,
+ IMContextWrapper* aModule);
+ static void OnCommitCompositionCallback(GtkIMContext* aContext,
+ const gchar* aString,
+ IMContextWrapper* aModule);
+ static void OnChangeCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+ static void OnEndCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule);
+
+ // The instance methods for the native IME events.
+ gboolean OnRetrieveSurroundingNative(GtkIMContext* aContext);
+ gboolean OnDeleteSurroundingNative(GtkIMContext* aContext, gint aOffset,
+ gint aNChars);
+ void OnCommitCompositionNative(GtkIMContext* aContext, const gchar* aString);
+ void OnChangeCompositionNative(GtkIMContext* aContext);
+ void OnStartCompositionNative(GtkIMContext* aContext);
+ void OnEndCompositionNative(GtkIMContext* aContext);
+
+ /**
+ * GetCurrentContext() returns current IM context which is chosen with the
+ * enabled state.
+ * WARNING:
+ * When this class receives some signals for a composition after focus
+ * is moved in Gecko, the result of this may be different from given
+ * context by the signals.
+ */
+ GtkIMContext* GetCurrentContext() const;
+
+ /**
+ * GetActiveContext() returns a composing context or current context.
+ */
+ GtkIMContext* GetActiveContext() const {
+ return mComposingContext ? mComposingContext : GetCurrentContext();
+ }
+
+ // If the owner window and IM context have been destroyed, returns TRUE.
+ bool IsDestroyed() { return !mOwnerWindow; }
+
+ void NotifyIMEOfFocusChange(IMEFocusState aIMEFocusState);
+
+ // Initializes the instance.
+ void Init();
+
+ /**
+ * Reset the active context, i.e., if there is mComposingContext, reset it.
+ * Otherwise, reset current context. Note that all native composition
+ * events during calling this will be ignored.
+ */
+ void ResetIME();
+
+ // Gets the current composition string by the native APIs.
+ void GetCompositionString(GtkIMContext* aContext,
+ nsAString& aCompositionString);
+
+ /**
+ * Generates our text range array from current composition string.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString The data to be dispatched with
+ * compositionchange event.
+ */
+ already_AddRefed<TextRangeArray> CreateTextRangeArray(
+ GtkIMContext* aContext, const nsAString& aCompositionString);
+
+ /**
+ * SetTextRange() initializes aTextRange with aPangoAttrIter.
+ *
+ * @param aPangoAttrIter An iter which represents a clause of the
+ * composition string.
+ * @param aUTF8CompositionString The whole composition string (UTF-8).
+ * @param aUTF16CaretOffset The caret offset in the composition
+ * string encoded as UTF-16.
+ * @param aTextRange The result.
+ * @return true if this initializes aTextRange.
+ * Otherwise, false.
+ */
+ bool SetTextRange(PangoAttrIterator* aPangoAttrIter,
+ const gchar* aUTF8CompositionString,
+ uint32_t aUTF16CaretOffset, TextRange& aTextRange) const;
+
+ /**
+ * ToNscolor() converts the PangoColor in aPangoAttrColor to nscolor.
+ */
+ static nscolor ToNscolor(PangoAttrColor* aPangoAttrColor);
+
+ /**
+ * Move the candidate window with "fake" cursor position.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ */
+ void SetCursorPosition(GtkIMContext* aContext);
+
+ // Queries the current selection offset of the window.
+ uint32_t GetSelectionOffset(nsWindow* aWindow);
+
+ // Get current paragraph text content and cursor position
+ nsresult GetCurrentParagraph(nsAString& aText, uint32_t& aCursorPos);
+
+ /**
+ * Delete text portion
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aOffset Start offset of the range to delete.
+ * @param aNChars Count of characters to delete. It depends
+ * on |g_utf8_strlen()| what is one character.
+ */
+ nsresult DeleteText(GtkIMContext* aContext, int32_t aOffset,
+ uint32_t aNChars);
+
+ // Called before destroying the context to work around some platform bugs.
+ void PrepareToDestroyContext(GtkIMContext* aContext);
+
+ /**
+ * WARNING:
+ * Following methods dispatch gecko events. Then, the focused widget
+ * can be destroyed, and also it can be stolen focus. If they returns
+ * FALSE, callers cannot continue the composition.
+ * - MaybeDispatchKeyEventAsProcessedByIME
+ * - DispatchCompositionStart
+ * - DispatchCompositionChangeEvent
+ * - DispatchCompositionCommitEvent
+ */
+
+ /**
+ * Dispatch an eKeyDown or eKeyUp event whose mKeyCode value is
+ * NS_VK_PROCESSKEY and mKeyNameIndex is KEY_NAME_INDEX_Process if
+ * we're not in a dead key sequence, mProcessingKeyEvent is nullptr
+ * but mPostingKeyEvents is not empty or mProcessingKeyEvent is not
+ * nullptr and mKeyboardEventWasDispatched is still false. If this
+ * dispatches a keyboard event, this sets mKeyboardEventWasDispatched
+ * to true.
+ *
+ * @param aFollowingEvent The following event message.
+ * @return If the caller can continue to handle
+ * composition, returns true. Otherwise,
+ * false. For example, if focus is moved
+ * by dispatched keyboard event, returns
+ * false.
+ */
+ bool MaybeDispatchKeyEventAsProcessedByIME(EventMessage aFollowingEvent);
+
+ /**
+ * Dispatches a composition start event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionStart(GtkIMContext* aContext);
+
+ /**
+ * Dispatches a compositionchange event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCompositionString New composition string.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionChangeEvent(GtkIMContext* aContext,
+ const nsAString& aCompositionString);
+
+ /**
+ * Dispatches a compositioncommit event or compositioncommitasis event.
+ *
+ * @param aContext A GtkIMContext which is being handled.
+ * @param aCommitString If this is nullptr, the composition will
+ * be committed with last dispatched data.
+ * Otherwise, the composition will be
+ * committed with this value.
+ * @return true if the focused widget is neither
+ * destroyed nor changed. Otherwise, false.
+ */
+ bool DispatchCompositionCommitEvent(GtkIMContext* aContext,
+ const nsAString* aCommitString = nullptr);
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // #ifndef IMContextWrapper_h_
diff --git a/widget/gtk/InProcessGtkCompositorWidget.cpp b/widget/gtk/InProcessGtkCompositorWidget.cpp
new file mode 100644
index 0000000000..bffd6f5a7f
--- /dev/null
+++ b/widget/gtk/InProcessGtkCompositorWidget.cpp
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "HeadlessCompositorWidget.h"
+#include "HeadlessWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+
+#include "InProcessGtkCompositorWidget.h"
+#include "VsyncDispatcher.h"
+#include "nsWindow.h"
+
+namespace mozilla::widget {
+
+/* static */
+RefPtr<CompositorWidget> CompositorWidget::CreateLocal(
+ const CompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsIWidget* aWidget) {
+ if (aInitData.type() ==
+ CompositorWidgetInitData::THeadlessCompositorWidgetInitData) {
+ return new HeadlessCompositorWidget(
+ aInitData.get_HeadlessCompositorWidgetInitData(), aOptions,
+ static_cast<HeadlessWidget*>(aWidget));
+ } else {
+ return new InProcessGtkCompositorWidget(
+ aInitData.get_GtkCompositorWidgetInitData(), aOptions,
+ static_cast<nsWindow*>(aWidget));
+ }
+}
+
+InProcessGtkCompositorWidget::InProcessGtkCompositorWidget(
+ const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : GtkCompositorWidget(aInitData, aOptions, aWindow) {}
+
+void InProcessGtkCompositorWidget::ObserveVsync(VsyncObserver* aObserver) {
+ if (RefPtr<CompositorVsyncDispatcher> cvd =
+ mWidget->GetCompositorVsyncDispatcher()) {
+ cvd->SetCompositorVsyncObserver(aObserver);
+ }
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/InProcessGtkCompositorWidget.h b/widget/gtk/InProcessGtkCompositorWidget.h
new file mode 100644
index 0000000000..bd7e68d6fb
--- /dev/null
+++ b/widget/gtk/InProcessGtkCompositorWidget.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef widget_gtk_InProcessGtkCompositorWidget_h
+#define widget_gtk_InProcessGtkCompositorWidget_h
+
+#include "GtkCompositorWidget.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class InProcessGtkCompositorWidget final : public GtkCompositorWidget {
+ public:
+ InProcessGtkCompositorWidget(const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions,
+ nsWindow* aWindow);
+
+ // CompositorWidgetDelegate
+
+ void ObserveVsync(VsyncObserver* aObserver) override;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // widget_gtk_InProcessGtkCompositorWidget_h
diff --git a/widget/gtk/MPRISInterfaceDescription.h b/widget/gtk/MPRISInterfaceDescription.h
new file mode 100644
index 0000000000..28e719e651
--- /dev/null
+++ b/widget/gtk/MPRISInterfaceDescription.h
@@ -0,0 +1,91 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
+#define WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
+
+#include <gio/gio.h>
+
+extern const gchar introspection_xml[] =
+ // adopted from https://github.com/freedesktop/mpris-spec/blob/master/spec/org.mpris.MediaPlayer2.xml
+ // everything starting with tp can be removed, as it is used for HTML Spec Documentation Generation
+ "<node>"
+ "<interface name=\"org.mpris.MediaPlayer2\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "<method name=\"Raise\"/>"
+ "<method name=\"Quit\"/>"
+ "<property name=\"CanQuit\" type=\"b\" access=\"read\"/>"
+ "<property name=\"CanRaise\" type=\"b\" access=\"read\"/>"
+ "<property name=\"HasTrackList\" type=\"b\" access=\"read\"/>"
+ "<property name=\"Identity\" type=\"s\" access=\"read\"/>"
+ "<property name=\"DesktopEntry\" type=\"s\" access=\"read\"/>"
+ "<property name=\"SupportedUriSchemes\" type=\"as\" access=\"read\"/>"
+ "<property name=\"SupportedMimeTypes\" type=\"as\" access=\"read\"/>"
+ "</interface>"
+ // Note that every property emits a changed signal (which is default) apart from Position.
+ "<interface name=\"org.mpris.MediaPlayer2.Player\">"
+ "<method name=\"Next\"/>"
+ "<method name=\"Previous\"/>"
+ "<method name=\"Pause\"/>"
+ "<method name=\"PlayPause\"/>"
+ "<method name=\"Stop\"/>"
+ "<method name=\"Play\"/>"
+ "<method name=\"Seek\">"
+ "<arg direction=\"in\" type=\"x\" name=\"Offset\"/>"
+ "</method>"
+ "<method name=\"SetPosition\">"
+ "<arg direction=\"in\" type=\"o\" name=\"TrackId\"/>"
+ "<arg direction=\"in\" type=\"x\" name=\"Position\"/>"
+ "</method>"
+ "<method name=\"OpenUri\">"
+ "<arg direction=\"in\" type=\"s\" name=\"Uri\"/>"
+ "</method>"
+ "<property name=\"PlaybackStatus\" type=\"s\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Rate\" type=\"d\" access=\"readwrite\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Metadata\" type=\"a{sv}\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Volume\" type=\"d\" access=\"readwrite\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"Position\" type=\"x\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
+ "</property>"
+ "<property name=\"MinimumRate\" type=\"d\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"MaximumRate\" type=\"d\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanGoNext\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanGoPrevious\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanPlay\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanPause\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanSeek\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"true\"/>"
+ "</property>"
+ "<property name=\"CanControl\" type=\"b\" access=\"read\">"
+ "<annotation name=\"org.freedesktop.DBus.Property.EmitsChangedSignal\" value=\"false\"/>"
+ "</property>"
+ "<signal name=\"Seeked\">"
+ "<arg name=\"Position\" type=\"x\"/>"
+ "</signal>"
+ "</interface>"
+ "</node>";
+
+#endif // WIDGET_GTK_MPRIS_INTERFACE_DESCRIPTION_H_
diff --git a/widget/gtk/MPRISServiceHandler.cpp b/widget/gtk/MPRISServiceHandler.cpp
new file mode 100644
index 0000000000..ae1ef8654d
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.cpp
@@ -0,0 +1,909 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MPRISServiceHandler.h"
+
+#include <stdint.h>
+#include <inttypes.h>
+#include <unordered_map>
+
+#include "MPRISInterfaceDescription.h"
+#include "mozilla/dom/MediaControlUtils.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "nsXULAppAPI.h"
+#include "nsIXULAppInfo.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+#include "WidgetUtilsGtk.h"
+#include "AsyncDBus.h"
+#include "prio.h"
+
+#define LOGMPRIS(msg, ...) \
+ MOZ_LOG(gMediaControlLog, LogLevel::Debug, \
+ ("MPRISServiceHandler=%p, " msg, this, ##__VA_ARGS__))
+
+namespace mozilla {
+namespace widget {
+
+// A global counter tracking the total images saved in the system and it will be
+// used to form a unique image file name.
+static uint32_t gImageNumber = 0;
+
+static inline Maybe<dom::MediaControlKey> GetMediaControlKey(
+ const gchar* aMethodName) {
+ const std::unordered_map<std::string, dom::MediaControlKey> map = {
+ {"Raise", dom::MediaControlKey::Focus},
+ {"Next", dom::MediaControlKey::Nexttrack},
+ {"Previous", dom::MediaControlKey::Previoustrack},
+ {"Pause", dom::MediaControlKey::Pause},
+ {"PlayPause", dom::MediaControlKey::Playpause},
+ {"Stop", dom::MediaControlKey::Stop},
+ {"Play", dom::MediaControlKey::Play}};
+
+ auto it = map.find(aMethodName);
+ return it == map.end() ? Nothing() : Some(it->second);
+}
+
+static void HandleMethodCall(GDBusConnection* aConnection, const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aMethodName, GVariant* aParameters,
+ GDBusMethodInvocation* aInvocation,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<dom::MediaControlKey> key = GetMediaControlKey(aMethodName);
+ if (key.isNothing()) {
+ g_dbus_method_invocation_return_error(
+ aInvocation, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
+ "Method %s.%s.%s not supported", aObjectPath, aInterfaceName,
+ aMethodName);
+ return;
+ }
+
+ MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
+ if (handler->PressKey(key.value())) {
+ g_dbus_method_invocation_return_value(aInvocation, nullptr);
+ } else {
+ g_dbus_method_invocation_return_error(
+ aInvocation, G_DBUS_ERROR, G_DBUS_ERROR_FAILED,
+ "%s.%s.%s is not available now", aObjectPath, aInterfaceName,
+ aMethodName);
+ }
+}
+
+enum class Property : uint8_t {
+ eIdentity,
+ eDesktopEntry,
+ eHasTrackList,
+ eCanRaise,
+ eCanQuit,
+ eSupportedUriSchemes,
+ eSupportedMimeTypes,
+ eCanGoNext,
+ eCanGoPrevious,
+ eCanPlay,
+ eCanPause,
+ eCanSeek,
+ eCanControl,
+ eGetPlaybackStatus,
+ eGetMetadata,
+};
+
+static inline Maybe<dom::MediaControlKey> GetPairedKey(Property aProperty) {
+ switch (aProperty) {
+ case Property::eCanRaise:
+ return Some(dom::MediaControlKey::Focus);
+ case Property::eCanGoNext:
+ return Some(dom::MediaControlKey::Nexttrack);
+ case Property::eCanGoPrevious:
+ return Some(dom::MediaControlKey::Previoustrack);
+ case Property::eCanPlay:
+ return Some(dom::MediaControlKey::Play);
+ case Property::eCanPause:
+ return Some(dom::MediaControlKey::Pause);
+ default:
+ return Nothing();
+ }
+}
+
+static inline Maybe<Property> GetProperty(const gchar* aPropertyName) {
+ const std::unordered_map<std::string, Property> map = {
+ // org.mpris.MediaPlayer2 properties
+ {"Identity", Property::eIdentity},
+ {"DesktopEntry", Property::eDesktopEntry},
+ {"HasTrackList", Property::eHasTrackList},
+ {"CanRaise", Property::eCanRaise},
+ {"CanQuit", Property::eCanQuit},
+ {"SupportedUriSchemes", Property::eSupportedUriSchemes},
+ {"SupportedMimeTypes", Property::eSupportedMimeTypes},
+ // org.mpris.MediaPlayer2.Player properties
+ {"CanGoNext", Property::eCanGoNext},
+ {"CanGoPrevious", Property::eCanGoPrevious},
+ {"CanPlay", Property::eCanPlay},
+ {"CanPause", Property::eCanPause},
+ {"CanSeek", Property::eCanSeek},
+ {"CanControl", Property::eCanControl},
+ {"PlaybackStatus", Property::eGetPlaybackStatus},
+ {"Metadata", Property::eGetMetadata}};
+
+ auto it = map.find(aPropertyName);
+ return (it == map.end() ? Nothing() : Some(it->second));
+}
+
+static GVariant* HandleGetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GError** aError,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ Maybe<Property> property = GetProperty(aPropertyName);
+ if (property.isNothing()) {
+ g_set_error(aError, G_DBUS_ERROR, G_DBUS_ERROR_NOT_SUPPORTED,
+ "%s.%s %s is not supported", aObjectPath, aInterfaceName,
+ aPropertyName);
+ return nullptr;
+ }
+
+ MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
+ switch (property.value()) {
+ case Property::eSupportedUriSchemes:
+ case Property::eSupportedMimeTypes:
+ // No plan to implement OpenUri for now
+ return g_variant_new_strv(nullptr, 0);
+ case Property::eGetPlaybackStatus:
+ return handler->GetPlaybackStatus();
+ case Property::eGetMetadata:
+ return handler->GetMetadataAsGVariant();
+ case Property::eIdentity:
+ return g_variant_new_string(handler->Identity());
+ case Property::eDesktopEntry:
+ return g_variant_new_string(handler->DesktopEntry());
+ case Property::eHasTrackList:
+ case Property::eCanQuit:
+ case Property::eCanSeek:
+ return g_variant_new_boolean(false);
+ // Play/Pause would be blocked if CanControl is false
+ case Property::eCanControl:
+ return g_variant_new_boolean(true);
+ case Property::eCanRaise:
+ case Property::eCanGoNext:
+ case Property::eCanGoPrevious:
+ case Property::eCanPlay:
+ case Property::eCanPause:
+ Maybe<dom::MediaControlKey> key = GetPairedKey(property.value());
+ MOZ_ASSERT(key.isSome());
+ return g_variant_new_boolean(handler->IsMediaKeySupported(key.value()));
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Switch statement is incomplete");
+ return nullptr;
+}
+
+static gboolean HandleSetProperty(GDBusConnection* aConnection,
+ const gchar* aSender,
+ const gchar* aObjectPath,
+ const gchar* aInterfaceName,
+ const gchar* aPropertyName, GVariant* aValue,
+ GError** aError, gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ MOZ_ASSERT(NS_IsMainThread());
+ g_set_error(aError, G_IO_ERROR, G_IO_ERROR_FAILED,
+ "%s:%s setting is not supported", aInterfaceName, aPropertyName);
+ return false;
+}
+
+static const GDBusInterfaceVTable gInterfaceVTable = {
+ HandleMethodCall, HandleGetProperty, HandleSetProperty};
+
+void MPRISServiceHandler::OnNameAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnNameAcquired(aConnection,
+ aName);
+}
+
+void MPRISServiceHandler::OnNameLostStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnNameLost(aConnection, aName);
+}
+
+void MPRISServiceHandler::OnBusAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName,
+ gpointer aUserData) {
+ MOZ_ASSERT(aUserData);
+ static_cast<MPRISServiceHandler*>(aUserData)->OnBusAcquired(aConnection,
+ aName);
+}
+
+void MPRISServiceHandler::OnNameAcquired(GDBusConnection* aConnection,
+ const gchar* aName) {
+ LOGMPRIS("OnNameAcquired: %s", aName);
+ mConnection = aConnection;
+}
+
+void MPRISServiceHandler::OnNameLost(GDBusConnection* aConnection,
+ const gchar* aName) {
+ LOGMPRIS("OnNameLost: %s", aName);
+ mConnection = nullptr;
+ if (!mRootRegistrationId) {
+ return;
+ }
+
+ if (!aConnection) {
+ return;
+ }
+
+ if (g_dbus_connection_unregister_object(aConnection, mRootRegistrationId)) {
+ mRootRegistrationId = 0;
+ } else {
+ // Note: Most code examples in the internet probably dont't even check the
+ // result here, but
+ // according to the spec it _can_ return false.
+ LOGMPRIS("Unable to unregister root object from within onNameLost!");
+ }
+
+ if (!mPlayerRegistrationId) {
+ return;
+ }
+
+ if (g_dbus_connection_unregister_object(aConnection, mPlayerRegistrationId)) {
+ mPlayerRegistrationId = 0;
+ } else {
+ // Note: Most code examples in the internet probably dont't even check the
+ // result here, but
+ // according to the spec it _can_ return false.
+ LOGMPRIS("Unable to unregister object from within onNameLost!");
+ }
+}
+
+void MPRISServiceHandler::OnBusAcquired(GDBusConnection* aConnection,
+ const gchar* aName) {
+ GUniquePtr<GError> error;
+ LOGMPRIS("OnBusAcquired: %s", aName);
+
+ mRootRegistrationId = g_dbus_connection_register_object(
+ aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[0],
+ &gInterfaceVTable, this, /* user_data */
+ nullptr, /* user_data_free_func */
+ getter_Transfers(error)); /* GError** */
+
+ if (mRootRegistrationId == 0) {
+ LOGMPRIS("Failed at root registration: %s",
+ error ? error->message : "Unknown Error");
+ return;
+ }
+
+ mPlayerRegistrationId = g_dbus_connection_register_object(
+ aConnection, DBUS_MPRIS_OBJECT_PATH, mIntrospectionData->interfaces[1],
+ &gInterfaceVTable, this, /* user_data */
+ nullptr, /* user_data_free_func */
+ getter_Transfers(error)); /* GError** */
+
+ if (mPlayerRegistrationId == 0) {
+ LOGMPRIS("Failed at object registration: %s",
+ error ? error->message : "Unknown Error");
+ }
+}
+
+void MPRISServiceHandler::SetServiceName(const char* aName) {
+ nsCString dbusName(aName);
+ dbusName.ReplaceChar(':', '_');
+ dbusName.ReplaceChar('.', '_');
+ mServiceName =
+ nsCString(DBUS_MPRIS_SERVICE_NAME) + nsCString(".instance") + dbusName;
+}
+
+const char* MPRISServiceHandler::GetServiceName() { return mServiceName.get(); }
+
+/* static */
+void g_bus_get_callback(GObject* aSourceObject, GAsyncResult* aRes,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+
+ GDBusConnection* conn = g_bus_get_finish(aRes, getter_Transfers(error));
+ if (!conn) {
+ if (!IsCancelledGError(error.get())) {
+ NS_WARNING(nsPrintfCString("Failure at g_bus_get_finish: %s",
+ error ? error->message : "Unknown Error")
+ .get());
+ }
+ return;
+ }
+
+ MPRISServiceHandler* handler = static_cast<MPRISServiceHandler*>(aUserData);
+ if (!handler) {
+ NS_WARNING(
+ nsPrintfCString("Failure to get a MPRISServiceHandler*: %p", handler)
+ .get());
+ return;
+ }
+
+ handler->OwnName(conn);
+}
+
+void MPRISServiceHandler::OwnName(GDBusConnection* aConnection) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ SetServiceName(g_dbus_connection_get_unique_name(aConnection));
+
+ GUniquePtr<GError> error;
+
+ InitIdentity();
+ mOwnerId = g_bus_own_name_on_connection(
+ aConnection, GetServiceName(),
+ // Enter a waiting queue until this service name is free
+ // (likely another FF instance is running/has been crashed)
+ G_BUS_NAME_OWNER_FLAGS_NONE, OnNameAcquiredStatic, OnNameLostStatic, this,
+ nullptr);
+
+ /* parse introspection data */
+ mIntrospectionData = dont_AddRef(
+ g_dbus_node_info_new_for_xml(introspection_xml, getter_Transfers(error)));
+
+ if (!mIntrospectionData) {
+ LOGMPRIS("Failed at parsing XML Interface definition: %s",
+ error ? error->message : "Unknown Error");
+ return;
+ }
+
+ OnBusAcquired(aConnection, GetServiceName());
+}
+
+bool MPRISServiceHandler::Open() {
+ MOZ_ASSERT(!mInitialized);
+ MOZ_ASSERT(NS_IsMainThread());
+
+ mDBusGetCancellable = dont_AddRef(g_cancellable_new());
+ g_bus_get(G_BUS_TYPE_SESSION, mDBusGetCancellable, g_bus_get_callback, this);
+
+ mInitialized = true;
+ return true;
+}
+
+MPRISServiceHandler::MPRISServiceHandler() = default;
+MPRISServiceHandler::~MPRISServiceHandler() {
+ MOZ_ASSERT(!mInitialized, "Close hasn't been called!");
+}
+
+void MPRISServiceHandler::Close() {
+ // Reset playback state and metadata before disconnect from dbus.
+ SetPlaybackState(dom::MediaSessionPlaybackState::None);
+ ClearMetadata();
+
+ OnNameLost(mConnection, GetServiceName());
+
+ if (mDBusGetCancellable) {
+ g_cancellable_cancel(mDBusGetCancellable);
+ mDBusGetCancellable = nullptr;
+ }
+
+ if (mOwnerId != 0) {
+ g_bus_unown_name(mOwnerId);
+ }
+
+ mIntrospectionData = nullptr;
+
+ mInitialized = false;
+ MediaControlKeySource::Close();
+}
+
+bool MPRISServiceHandler::IsOpened() const { return mInitialized; }
+
+void MPRISServiceHandler::InitIdentity() {
+ nsresult rv;
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1", &rv);
+
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = appInfo->GetVendor(mIdentity);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ rv = appInfo->GetName(mDesktopEntry);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+
+ mIdentity.Append(' ');
+ mIdentity.Append(mDesktopEntry);
+
+ // Compute the desktop entry name like nsAppRunner does for g_set_prgname
+ ToLowerCase(mDesktopEntry);
+}
+
+const char* MPRISServiceHandler::Identity() const {
+ NS_WARNING_ASSERTION(mInitialized,
+ "MPRISServiceHandler should have been initialized.");
+ return mIdentity.get();
+}
+
+const char* MPRISServiceHandler::DesktopEntry() const {
+ NS_WARNING_ASSERTION(mInitialized,
+ "MPRISServiceHandler should have been initialized.");
+ return mDesktopEntry.get();
+}
+
+bool MPRISServiceHandler::PressKey(dom::MediaControlKey aKey) const {
+ MOZ_ASSERT(mInitialized);
+ if (!IsMediaKeySupported(aKey)) {
+ LOGMPRIS("%s is not supported", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+ LOGMPRIS("Press %s", ToMediaControlKeyStr(aKey));
+ EmitEvent(aKey);
+ return true;
+}
+
+void MPRISServiceHandler::SetPlaybackState(
+ dom::MediaSessionPlaybackState aState) {
+ LOGMPRIS("SetPlaybackState");
+ if (mPlaybackState == aState) {
+ return;
+ }
+
+ MediaControlKeySource::SetPlaybackState(aState);
+
+ GVariant* state = GetPlaybackStatus();
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "PlaybackStatus", state);
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE, &builder, nullptr);
+
+ LOGMPRIS("Emitting MPRIS property changes for 'PlaybackStatus'");
+ Unused << EmitPropertiesChangedSignal(parameters);
+}
+
+GVariant* MPRISServiceHandler::GetPlaybackStatus() const {
+ switch (GetPlaybackState()) {
+ case dom::MediaSessionPlaybackState::Playing:
+ return g_variant_new_string("Playing");
+ case dom::MediaSessionPlaybackState::Paused:
+ return g_variant_new_string("Paused");
+ case dom::MediaSessionPlaybackState::None:
+ return g_variant_new_string("Stopped");
+ default:
+ MOZ_ASSERT_UNREACHABLE("Invalid Playback State");
+ return nullptr;
+ }
+}
+
+void MPRISServiceHandler::SetMediaMetadata(
+ const dom::MediaMetadataBase& aMetadata) {
+ // Reset the index of the next available image to be fetched in the artwork,
+ // before checking the fetching process should be started or not. The image
+ // fetching process could be skipped if the image being fetching currently is
+ // in the artwork. If the current image fetching fails, the next availabe
+ // candidate should be the first image in the latest artwork
+ mNextImageIndex = 0;
+
+ // No need to fetch a MPRIS image if
+ // 1) MPRIS image is being fetched, and the one in fetching is in the artwork
+ // 2) MPRIS image is not being fetched, and the one in use is in the artwork
+ if (!mFetchingUrl.IsEmpty()) {
+ if (dom::IsImageIn(aMetadata.mArtwork, mFetchingUrl)) {
+ LOGMPRIS(
+ "No need to load MPRIS image. The one being processed is in the "
+ "artwork");
+ // Set MPRIS without the image first. The image will be loaded to MPRIS
+ // asynchronously once it's fetched and saved into a local file
+ SetMediaMetadataInternal(aMetadata);
+ return;
+ }
+ } else if (!mCurrentImageUrl.IsEmpty()) {
+ if (dom::IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) {
+ LOGMPRIS("No need to load MPRIS image. The one in use is in the artwork");
+ SetMediaMetadataInternal(aMetadata, false);
+ return;
+ }
+ }
+
+ // Set MPRIS without the image first then load the image to MPRIS
+ // asynchronously
+ SetMediaMetadataInternal(aMetadata);
+ LoadImageAtIndex(mNextImageIndex++);
+}
+
+bool MPRISServiceHandler::EmitMetadataChanged() const {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "Metadata", GetMetadataAsGVariant());
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", DBUS_MPRIS_PLAYER_INTERFACE, &builder, nullptr);
+
+ LOGMPRIS("Emit MPRIS property changes for 'Metadata'");
+ return EmitPropertiesChangedSignal(parameters);
+}
+
+void MPRISServiceHandler::SetMediaMetadataInternal(
+ const dom::MediaMetadataBase& aMetadata, bool aClearArtUrl) {
+ mMPRISMetadata.UpdateFromMetadataBase(aMetadata);
+ if (aClearArtUrl) {
+ mMPRISMetadata.mArtUrl.Truncate();
+ }
+ EmitMetadataChanged();
+}
+
+void MPRISServiceHandler::ClearMetadata() {
+ mMPRISMetadata.Clear();
+ mImageFetchRequest.DisconnectIfExists();
+ RemoveAllLocalImages();
+ mCurrentImageUrl.Truncate();
+ mFetchingUrl.Truncate();
+ mNextImageIndex = 0;
+ mSupportedKeys = 0;
+ EmitMetadataChanged();
+}
+
+void MPRISServiceHandler::LoadImageAtIndex(const size_t aIndex) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (aIndex >= mMPRISMetadata.mArtwork.Length()) {
+ LOGMPRIS("Stop loading image to MPRIS. No available image");
+ mImageFetchRequest.DisconnectIfExists();
+ return;
+ }
+
+ const dom::MediaImage& image = mMPRISMetadata.mArtwork[aIndex];
+
+ if (!dom::IsValidImageUrl(image.mSrc)) {
+ LOGMPRIS("Skip the image with invalid URL. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ mImageFetchRequest.DisconnectIfExists();
+ mFetchingUrl = image.mSrc;
+
+ mImageFetcher = MakeUnique<dom::FetchImageHelper>(image);
+ RefPtr<MPRISServiceHandler> self = this;
+ mImageFetcher->FetchImage()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ [this, self](const nsCOMPtr<imgIContainer>& aImage) {
+ LOGMPRIS("The image is fetched successfully");
+ mImageFetchRequest.Complete();
+
+ uint32_t size = 0;
+ char* data = nullptr;
+ // Only used to hold the image data
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsresult rv = dom::GetEncodedImageBuffer(
+ aImage, mMimeType, getter_AddRefs(inputStream), &size, &data);
+ if (NS_FAILED(rv) || !inputStream || size == 0 || !data) {
+ LOGMPRIS("Failed to get the image buffer info. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ if (SetImageToDisplay(data, size)) {
+ mCurrentImageUrl = mFetchingUrl;
+ LOGMPRIS("The MPRIS image is updated to the image from: %s",
+ NS_ConvertUTF16toUTF8(mCurrentImageUrl).get());
+ } else {
+ LOGMPRIS("Failed to set image to MPRIS");
+ mCurrentImageUrl.Truncate();
+ }
+
+ mFetchingUrl.Truncate();
+ },
+ [this, self](bool) {
+ LOGMPRIS("Failed to fetch image. Try next image");
+ mImageFetchRequest.Complete();
+ mFetchingUrl.Truncate();
+ LoadImageAtIndex(mNextImageIndex++);
+ })
+ ->Track(mImageFetchRequest);
+}
+
+bool MPRISServiceHandler::SetImageToDisplay(const char* aImageData,
+ uint32_t aDataSize) {
+ if (!RenewLocalImageFile(aImageData, aDataSize)) {
+ return false;
+ }
+ MOZ_ASSERT(mLocalImageFile);
+
+ mMPRISMetadata.mArtUrl = nsCString("file://");
+ mMPRISMetadata.mArtUrl.Append(mLocalImageFile->NativePath());
+
+ LOGMPRIS("The image file is created at %s", mMPRISMetadata.mArtUrl.get());
+ return EmitMetadataChanged();
+}
+
+bool MPRISServiceHandler::RenewLocalImageFile(const char* aImageData,
+ uint32_t aDataSize) {
+ MOZ_ASSERT(aImageData);
+ MOZ_ASSERT(aDataSize != 0);
+
+ if (!InitLocalImageFile()) {
+ LOGMPRIS("Failed to create a new image");
+ return false;
+ }
+
+ MOZ_ASSERT(mLocalImageFile);
+ nsCOMPtr<nsIOutputStream> out;
+ NS_NewLocalFileOutputStream(getter_AddRefs(out), mLocalImageFile,
+ PR_RDWR | PR_CREATE_FILE | PR_TRUNCATE);
+ uint32_t written;
+ nsresult rv = out->Write(aImageData, aDataSize, &written);
+ if (NS_FAILED(rv) || written != aDataSize) {
+ LOGMPRIS("Failed to write an image file");
+ RemoveAllLocalImages();
+ return false;
+ }
+
+ return true;
+}
+
+static const char* GetImageFileExtension(const char* aMimeType) {
+ MOZ_ASSERT(strcmp(aMimeType, IMAGE_PNG) == 0);
+ return "png";
+}
+
+bool MPRISServiceHandler::InitLocalImageFile() {
+ RemoveAllLocalImages();
+
+ if (!InitLocalImageFolder()) {
+ return false;
+ }
+
+ MOZ_ASSERT(mLocalImageFolder);
+ MOZ_ASSERT(!mLocalImageFile);
+ nsresult rv = mLocalImageFolder->Clone(getter_AddRefs(mLocalImageFile));
+ if (NS_FAILED(rv)) {
+ LOGMPRIS("Failed to get the image folder");
+ return false;
+ }
+
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<MPRISServiceHandler>(this)] {
+ mLocalImageFile = nullptr;
+ });
+
+ // Create an unique file name to work around the file caching mechanism in the
+ // Ubuntu. Once the image X specified by the filename Y is used in Ubuntu's
+ // MPRIS, this pair will be cached. As long as the filename is same, even the
+ // file content specified by Y is changed to Z, the image will stay unchanged.
+ // The image shown in the Ubuntu's notification is still X instead of Z.
+ // Changing the filename constantly works around this problem
+ char filename[64];
+ SprintfLiteral(filename, "%d_%d.%s", getpid(), gImageNumber++,
+ GetImageFileExtension(mMimeType.get()));
+
+ rv = mLocalImageFile->Append(NS_ConvertUTF8toUTF16(filename));
+ if (NS_FAILED(rv)) {
+ LOGMPRIS("Failed to create an image filename");
+ return false;
+ }
+
+ rv = mLocalImageFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) {
+ LOGMPRIS("Failed to create an image file");
+ return false;
+ }
+
+ cleanup.release();
+ return true;
+}
+
+bool MPRISServiceHandler::InitLocalImageFolder() {
+ if (mLocalImageFolder && LocalImageFolderExists()) {
+ return true;
+ }
+
+ nsresult rv = NS_ERROR_FAILURE;
+ if (IsRunningUnderFlatpak()) {
+ // The XDG_DATA_HOME points to the same location in the host and guest
+ // filesystem.
+ if (const auto* xdgDataHome = g_getenv("XDG_DATA_HOME")) {
+ rv = NS_NewNativeLocalFile(nsDependentCString(xdgDataHome), true,
+ getter_AddRefs(mLocalImageFolder));
+ }
+ } else {
+ rv = NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR,
+ getter_AddRefs(mLocalImageFolder));
+ }
+
+ if (NS_FAILED(rv) || !mLocalImageFolder) {
+ LOGMPRIS("Failed to get the image folder");
+ return false;
+ }
+
+ auto cleanup = MakeScopeExit([&] { mLocalImageFolder = nullptr; });
+
+ rv = mLocalImageFolder->Append(u"firefox-mpris"_ns);
+ if (NS_FAILED(rv)) {
+ LOGMPRIS("Failed to name an image folder");
+ return false;
+ }
+
+ if (!LocalImageFolderExists()) {
+ rv = mLocalImageFolder->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) {
+ LOGMPRIS("Failed to create an image folder");
+ return false;
+ }
+ }
+
+ cleanup.release();
+ return true;
+}
+
+void MPRISServiceHandler::RemoveAllLocalImages() {
+ if (!mLocalImageFolder || !LocalImageFolderExists()) {
+ return;
+ }
+
+ nsresult rv = mLocalImageFolder->Remove(/* aRecursive */ true);
+ if (NS_FAILED(rv)) {
+ // It's ok to fail. The next removal is called when updating the
+ // media-session image, or closing the MPRIS.
+ LOGMPRIS("Failed to remove images");
+ }
+
+ LOGMPRIS("Abandon %s",
+ mLocalImageFile ? mLocalImageFile->NativePath().get() : "nothing");
+ mMPRISMetadata.mArtUrl.Truncate();
+ mLocalImageFile = nullptr;
+ mLocalImageFolder = nullptr;
+}
+
+bool MPRISServiceHandler::LocalImageFolderExists() {
+ MOZ_ASSERT(mLocalImageFolder);
+
+ bool exists;
+ nsresult rv = mLocalImageFolder->Exists(&exists);
+ return NS_SUCCEEDED(rv) && exists;
+}
+
+GVariant* MPRISServiceHandler::GetMetadataAsGVariant() const {
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}", "mpris:trackid",
+ g_variant_new("o", DBUS_MPRIS_TRACK_PATH));
+
+ g_variant_builder_add(
+ &builder, "{sv}", "xesam:title",
+ g_variant_new_string(static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mTitle).get())));
+
+ g_variant_builder_add(
+ &builder, "{sv}", "xesam:album",
+ g_variant_new_string(static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mAlbum).get())));
+
+ GVariantBuilder artistBuilder;
+ g_variant_builder_init(&artistBuilder, G_VARIANT_TYPE("as"));
+ g_variant_builder_add(
+ &artistBuilder, "s",
+ static_cast<const gchar*>(
+ NS_ConvertUTF16toUTF8(mMPRISMetadata.mArtist).get()));
+ g_variant_builder_add(&builder, "{sv}", "xesam:artist",
+ g_variant_builder_end(&artistBuilder));
+
+ if (!mMPRISMetadata.mArtUrl.IsEmpty()) {
+ g_variant_builder_add(&builder, "{sv}", "mpris:artUrl",
+ g_variant_new_string(static_cast<const gchar*>(
+ mMPRISMetadata.mArtUrl.get())));
+ }
+
+ return g_variant_builder_end(&builder);
+}
+
+void MPRISServiceHandler::EmitEvent(dom::MediaControlKey aKey) const {
+ for (const auto& listener : mListeners) {
+ listener->OnActionPerformed(dom::MediaControlAction(aKey));
+ }
+}
+
+struct InterfaceProperty {
+ const char* interface;
+ const char* property;
+};
+static const std::unordered_map<dom::MediaControlKey, InterfaceProperty>
+ gKeyProperty = {
+ {dom::MediaControlKey::Focus, {DBUS_MPRIS_INTERFACE, "CanRaise"}},
+ {dom::MediaControlKey::Nexttrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoNext"}},
+ {dom::MediaControlKey::Previoustrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoPrevious"}},
+ {dom::MediaControlKey::Play, {DBUS_MPRIS_PLAYER_INTERFACE, "CanPlay"}},
+ {dom::MediaControlKey::Pause,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanPause"}}};
+
+void MPRISServiceHandler::SetSupportedMediaKeys(
+ const MediaKeysArray& aSupportedKeys) {
+ uint32_t supportedKeys = 0;
+ for (const dom::MediaControlKey& key : aSupportedKeys) {
+ supportedKeys |= GetMediaKeyMask(key);
+ }
+
+ if (mSupportedKeys == supportedKeys) {
+ LOGMPRIS("Supported keys stay the same");
+ return;
+ }
+
+ uint32_t oldSupportedKeys = mSupportedKeys;
+ mSupportedKeys = supportedKeys;
+
+ // Emit related property changes
+ for (auto it : gKeyProperty) {
+ bool keyWasSupported = oldSupportedKeys & GetMediaKeyMask(it.first);
+ bool keyIsSupported = mSupportedKeys & GetMediaKeyMask(it.first);
+ if (keyWasSupported != keyIsSupported) {
+ LOGMPRIS("Emit PropertiesChanged signal: %s.%s=%s", it.second.interface,
+ it.second.property, keyIsSupported ? "true" : "false");
+ EmitSupportedKeyChanged(it.first, keyIsSupported);
+ }
+ }
+}
+
+bool MPRISServiceHandler::IsMediaKeySupported(dom::MediaControlKey aKey) const {
+ return mSupportedKeys & GetMediaKeyMask(aKey);
+}
+
+bool MPRISServiceHandler::EmitSupportedKeyChanged(dom::MediaControlKey aKey,
+ bool aSupported) const {
+ auto it = gKeyProperty.find(aKey);
+ if (it == gKeyProperty.end()) {
+ LOGMPRIS("No property for %s", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+
+ GVariantBuilder builder;
+ g_variant_builder_init(&builder, G_VARIANT_TYPE("a{sv}"));
+ g_variant_builder_add(&builder, "{sv}",
+ static_cast<const gchar*>(it->second.property),
+ g_variant_new_boolean(aSupported));
+
+ GVariant* parameters = g_variant_new(
+ "(sa{sv}as)", static_cast<const gchar*>(it->second.interface), &builder,
+ nullptr);
+
+ LOGMPRIS("Emit MPRIS property changes for '%s.%s'", it->second.interface,
+ it->second.property);
+ return EmitPropertiesChangedSignal(parameters);
+}
+
+bool MPRISServiceHandler::EmitPropertiesChangedSignal(
+ GVariant* aParameters) const {
+ if (!mConnection) {
+ LOGMPRIS("No D-Bus Connection. Cannot emit properties changed signal");
+ return false;
+ }
+
+ GError* error = nullptr;
+ if (!g_dbus_connection_emit_signal(
+ mConnection, nullptr, DBUS_MPRIS_OBJECT_PATH,
+ "org.freedesktop.DBus.Properties", "PropertiesChanged", aParameters,
+ &error)) {
+ LOGMPRIS("Failed to emit MPRIS property changes: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+#undef LOGMPRIS
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/MPRISServiceHandler.h b/widget/gtk/MPRISServiceHandler.h
new file mode 100644
index 0000000000..469090efc0
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.h
@@ -0,0 +1,195 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
+#define WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
+
+#include <gio/gio.h>
+#include "mozilla/dom/FetchImageHelper.h"
+#include "mozilla/dom/MediaControlKeySource.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIFile.h"
+#include "nsMimeTypes.h"
+#include "nsString.h"
+
+#define DBUS_MPRIS_SERVICE_NAME "org.mpris.MediaPlayer2.firefox"
+#define DBUS_MPRIS_OBJECT_PATH "/org/mpris/MediaPlayer2"
+#define DBUS_MPRIS_INTERFACE "org.mpris.MediaPlayer2"
+#define DBUS_MPRIS_PLAYER_INTERFACE "org.mpris.MediaPlayer2.Player"
+#define DBUS_MPRIS_TRACK_PATH "/org/mpris/MediaPlayer2/firefox"
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * This class implements the "MPRIS" D-Bus Service
+ * (https://specifications.freedesktop.org/mpris-spec/2.2),
+ * which is used to communicate with the Desktop Environment about the
+ * Multimedia playing in Gecko.
+ * Note that this interface requires many methods which may not be supported by
+ * Gecko, the interface
+ * however provides CanXYZ properties for these methods, so the method is
+ * defined but won't be executed.
+ *
+ * Also note that the following defines are for parts that the MPRIS Spec
+ * defines optional. The code won't
+ * compile with any of the defines set, yet, as those aren't implemented yet and
+ * probably never will be of
+ * use for gecko. For sake of completeness, they have been added until the
+ * decision about their implementation
+ * is finally made.
+ *
+ * The constexpr'ed methods are capabilities of the user agent known at compile
+ * time, e.g. we decided at
+ * compile time whether we ever want to support closing the user agent via MPRIS
+ * (Quit() and CanQuit()).
+ *
+ * Other properties like CanPlay() might depend on the runtime state (is there
+ * media available for playback?)
+ * and thus aren't a constexpr but merely a const method.
+ */
+class MPRISServiceHandler final : public dom::MediaControlKeySource {
+ NS_INLINE_DECL_REFCOUNTING(MPRISServiceHandler, override)
+ public:
+ // Note that this constructor does NOT initialize the MPRIS Service but only
+ // this class. The method Open() is responsible for registering and MAY FAIL.
+
+ MPRISServiceHandler();
+ bool Open() override;
+ void Close() override;
+ bool IsOpened() const override;
+
+ // From the EventSource.
+ void SetPlaybackState(dom::MediaSessionPlaybackState aState) override;
+
+ // GetPlaybackState returns dom::PlaybackState. GetPlaybackStatus returns this
+ // state converted into d-bus variants.
+ GVariant* GetPlaybackStatus() const;
+
+ const char* Identity() const;
+ const char* DesktopEntry() const;
+ bool PressKey(dom::MediaControlKey aKey) const;
+
+ void SetMediaMetadata(const dom::MediaMetadataBase& aMetadata) override;
+ GVariant* GetMetadataAsGVariant() const;
+
+ void SetSupportedMediaKeys(const MediaKeysArray& aSupportedKeys) override;
+
+ bool IsMediaKeySupported(dom::MediaControlKey aKey) const;
+
+ void OwnName(GDBusConnection* aConnection);
+
+ private:
+ ~MPRISServiceHandler();
+
+ // Note: Registration Ids for the D-Bus start with 1, so a value of 0
+ // indicates an error (or an object which wasn't initialized yet)
+
+ // a handle to our bus registration/ownership
+ guint mOwnerId = 0;
+ // This is for the interface org.mpris.MediaPlayer2
+ guint mRootRegistrationId = 0;
+ // This is for the interface org.mpris.MediaPlayer2.Player
+ guint mPlayerRegistrationId = 0;
+ RefPtr<GDBusNodeInfo> mIntrospectionData;
+ GDBusConnection* mConnection = nullptr;
+ bool mInitialized = false;
+ nsAutoCString mIdentity;
+ nsAutoCString mDesktopEntry;
+
+ // The image format used in MPRIS is based on the mMimeType here. Although
+ // IMAGE_JPEG or IMAGE_BMP are valid types as well but a png image with
+ // transparent background will be converted into a jpeg/bmp file with a
+ // colored background IMAGE_PNG format seems to be the best choice for now.
+ nsCString mMimeType{IMAGE_PNG};
+
+ // A bitmask indicating what keys are enabled
+ uint32_t mSupportedKeys = 0;
+
+ class MPRISMetadata : public dom::MediaMetadataBase {
+ public:
+ MPRISMetadata() = default;
+ ~MPRISMetadata() = default;
+
+ void UpdateFromMetadataBase(const dom::MediaMetadataBase& aMetadata) {
+ mTitle = aMetadata.mTitle;
+ mArtist = aMetadata.mArtist;
+ mAlbum = aMetadata.mAlbum;
+ mArtwork = aMetadata.mArtwork;
+ }
+ void Clear() {
+ UpdateFromMetadataBase(MediaMetadataBase::EmptyData());
+ mArtUrl.Truncate();
+ }
+
+ nsCString mArtUrl;
+ };
+ MPRISMetadata mMPRISMetadata;
+
+ // The saved image file fetched from the URL
+ nsCOMPtr<nsIFile> mLocalImageFile;
+ nsCOMPtr<nsIFile> mLocalImageFolder;
+
+ UniquePtr<dom::FetchImageHelper> mImageFetcher;
+ MozPromiseRequestHolder<dom::ImagePromise> mImageFetchRequest;
+
+ nsString mFetchingUrl;
+ nsString mCurrentImageUrl;
+
+ size_t mNextImageIndex = 0;
+
+ // Load the image at index aIndex of the metadta's artwork to MPRIS
+ // asynchronously
+ void LoadImageAtIndex(const size_t aIndex);
+ bool SetImageToDisplay(const char* aImageData, uint32_t aDataSize);
+
+ bool RenewLocalImageFile(const char* aImageData, uint32_t aDataSize);
+ bool InitLocalImageFile();
+ bool InitLocalImageFolder();
+ void RemoveAllLocalImages();
+ bool LocalImageFolderExists();
+
+ // Queries nsAppInfo to get the branded browser name and vendor
+ void InitIdentity();
+
+ // non-public API, called from events
+ void OnNameAcquired(GDBusConnection* aConnection, const gchar* aName);
+ void OnNameLost(GDBusConnection* aConnection, const gchar* aName);
+ void OnBusAcquired(GDBusConnection* aConnection, const gchar* aName);
+
+ static void OnNameAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName, gpointer aUserData);
+ static void OnNameLostStatic(GDBusConnection* aConnection, const gchar* aName,
+ gpointer aUserData);
+ static void OnBusAcquiredStatic(GDBusConnection* aConnection,
+ const gchar* aName, gpointer aUserData);
+
+ void EmitEvent(dom::MediaControlKey aKey) const;
+
+ bool EmitMetadataChanged() const;
+
+ void SetMediaMetadataInternal(const dom::MediaMetadataBase& aMetadata,
+ bool aClearArtUrl = true);
+
+ bool EmitSupportedKeyChanged(dom::MediaControlKey aKey,
+ bool aSupported) const;
+
+ bool EmitPropertiesChangedSignal(GVariant* aParameters) const;
+
+ void ClearMetadata();
+
+ RefPtr<GCancellable> mDBusGetCancellable;
+
+ nsCString mServiceName;
+ void SetServiceName(const char* aName);
+ const char* GetServiceName();
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // WIDGET_GTK_MPRIS_SERVICE_HANDLER_H_
diff --git a/widget/gtk/MediaKeysEventSourceFactory.cpp b/widget/gtk/MediaKeysEventSourceFactory.cpp
new file mode 100644
index 0000000000..b9a3dfde41
--- /dev/null
+++ b/widget/gtk/MediaKeysEventSourceFactory.cpp
@@ -0,0 +1,14 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this file,
+ * You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MediaKeysEventSourceFactory.h"
+#include "MPRISServiceHandler.h"
+
+namespace mozilla::widget {
+
+mozilla::dom::MediaControlKeySource* CreateMediaControlKeySource() {
+ return new MPRISServiceHandler();
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/MozContainer.cpp b/widget/gtk/MozContainer.cpp
new file mode 100644
index 0000000000..95d32b57b4
--- /dev/null
+++ b/widget/gtk/MozContainer.cpp
@@ -0,0 +1,402 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "MozContainer.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <stdio.h>
+#include "mozilla/WidgetUtilsGtk.h"
+#include "nsWindow.h"
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOGCONTAINER(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGCONTAINER(args)
+#endif /* MOZ_LOGGING */
+
+/* init methods */
+void moz_container_class_init(MozContainerClass* klass);
+static void moz_container_init(MozContainer* container);
+
+/* widget class methods */
+static void moz_container_map(GtkWidget* widget);
+void moz_container_unmap(GtkWidget* widget);
+static void moz_container_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation);
+static void moz_container_realize(GtkWidget* widget);
+static void moz_container_unrealize(GtkWidget* widget);
+
+/* container class methods */
+static void moz_container_remove(GtkContainer* container,
+ GtkWidget* child_widget);
+static void moz_container_forall(GtkContainer* container,
+ gboolean include_internals,
+ GtkCallback callback, gpointer callback_data);
+static void moz_container_add(GtkContainer* container, GtkWidget* widget);
+
+typedef struct _MozContainerChild MozContainerChild;
+
+struct _MozContainerChild {
+ GtkWidget* widget;
+ gint x;
+ gint y;
+};
+
+static void moz_container_allocate_child(MozContainer* container,
+ MozContainerChild* child);
+static MozContainerChild* moz_container_get_child(MozContainer* container,
+ GtkWidget* child);
+
+/* public methods */
+
+GType moz_container_get_type(void) {
+ static GType moz_container_type = 0;
+
+ if (!moz_container_type) {
+ static GTypeInfo moz_container_info = {
+ sizeof(MozContainerClass), /* class_size */
+ NULL, /* base_init */
+ NULL, /* base_finalize */
+ (GClassInitFunc)moz_container_class_init, /* class_init */
+ NULL, /* class_destroy */
+ NULL, /* class_data */
+ sizeof(MozContainer), /* instance_size */
+ 0, /* n_preallocs */
+ (GInstanceInitFunc)moz_container_init, /* instance_init */
+ NULL, /* value_table */
+ };
+
+ moz_container_type =
+ g_type_register_static(GTK_TYPE_CONTAINER, "MozContainer",
+ &moz_container_info, static_cast<GTypeFlags>(0));
+ }
+
+ return moz_container_type;
+}
+
+GtkWidget* moz_container_new(void) {
+ MozContainer* container;
+
+ container =
+ static_cast<MozContainer*>(g_object_new(MOZ_CONTAINER_TYPE, nullptr));
+
+ return GTK_WIDGET(container);
+}
+
+void moz_container_put(MozContainer* container, GtkWidget* child_widget, gint x,
+ gint y) {
+ MozContainerChild* child;
+
+ child = g_new(MozContainerChild, 1);
+
+ child->widget = child_widget;
+ child->x = x;
+ child->y = y;
+
+ /* printf("moz_container_put %p %p %d %d\n", (void *)container,
+ (void *)child_widget, x, y); */
+
+ container->data.children = g_list_append(container->data.children, child);
+
+ /* we assume that the caller of this function will have already set
+ the parent GdkWindow because we can have many anonymous children. */
+ gtk_widget_set_parent(child_widget, GTK_WIDGET(container));
+}
+
+static void moz_container_destroy(GtkWidget* widget) {
+ auto* container = MOZ_CONTAINER(widget);
+ if (container->destroyed) {
+ return; // The destroy signal may run multiple times.
+ }
+ LOGCONTAINER(("moz_container_destroy() [%p]\n",
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget))));
+ container->destroyed = TRUE;
+ container->data.~Data();
+}
+
+void moz_container_class_init(MozContainerClass* klass) {
+ /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
+ GtkContainerClass* container_class = GTK_CONTAINER_CLASS(klass);
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+
+ widget_class->realize = moz_container_realize;
+ widget_class->unrealize = moz_container_unrealize;
+ widget_class->destroy = moz_container_destroy;
+
+#ifdef MOZ_WAYLAND
+ if (mozilla::widget::GdkIsWaylandDisplay()) {
+ widget_class->map = moz_container_wayland_map;
+ widget_class->size_allocate = moz_container_wayland_size_allocate;
+ widget_class->map_event = moz_container_wayland_map_event;
+ widget_class->unmap = moz_container_wayland_unmap;
+ } else {
+#endif
+ widget_class->map = moz_container_map;
+ widget_class->size_allocate = moz_container_size_allocate;
+ widget_class->unmap = moz_container_unmap;
+#ifdef MOZ_WAYLAND
+ }
+#endif
+
+ container_class->remove = moz_container_remove;
+ container_class->forall = moz_container_forall;
+ container_class->add = moz_container_add;
+}
+
+void moz_container_init(MozContainer* container) {
+ container->destroyed = FALSE;
+ new (&container->data) MozContainer::Data();
+ gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE);
+ LOGCONTAINER(("%s [%p]\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(container)));
+}
+
+void moz_container_map(GtkWidget* widget) {
+ MozContainer* container;
+ GList* tmp_list;
+ GtkWidget* tmp_child;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+ container = MOZ_CONTAINER(widget);
+
+ LOGCONTAINER(("moz_container_map() [%p]",
+ (void*)moz_container_get_nsWindow(container)));
+
+ gtk_widget_set_mapped(widget, TRUE);
+
+ tmp_list = container->data.children;
+ while (tmp_list) {
+ tmp_child = ((MozContainerChild*)tmp_list->data)->widget;
+
+ if (gtk_widget_get_visible(tmp_child)) {
+ if (!gtk_widget_get_mapped(tmp_child)) gtk_widget_map(tmp_child);
+ }
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_show(gtk_widget_get_window(widget));
+ }
+}
+
+void moz_container_unmap(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOGCONTAINER(("moz_container_unmap() [%p]",
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget))));
+
+ // Disable rendering to MozContainer before we unmap it.
+ nsWindow* window = moz_container_get_nsWindow(MOZ_CONTAINER(widget));
+ window->DisableRendering();
+
+ gtk_widget_set_mapped(widget, FALSE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_hide(gtk_widget_get_window(widget));
+ }
+}
+
+void moz_container_realize(GtkWidget* widget) {
+ GdkWindow* parent = gtk_widget_get_parent_window(widget);
+ GdkWindow* window;
+
+ gtk_widget_set_realized(widget, TRUE);
+
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL | GDK_WA_X | GDK_WA_Y;
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation(widget, &allocation);
+ attributes.event_mask = gtk_widget_get_events(widget);
+ attributes.x = allocation.x;
+ attributes.y = allocation.y;
+ attributes.width = allocation.width;
+ attributes.height = allocation.height;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.window_type = GDK_WINDOW_CHILD;
+ MozContainer* container = MOZ_CONTAINER(widget);
+ attributes.visual =
+ container->data.force_default_visual
+ ? gdk_screen_get_system_visual(gtk_widget_get_screen(widget))
+ : gtk_widget_get_visual(widget);
+
+ window = gdk_window_new(parent, &attributes, attributes_mask);
+
+ LOGCONTAINER(("moz_container_realize() [%p] GdkWindow %p\n",
+ (void*)moz_container_get_nsWindow(container), (void*)window));
+
+ gtk_widget_register_window(widget, window);
+ gtk_widget_set_window(widget, window);
+}
+
+void moz_container_unrealize(GtkWidget* widget) {
+ GdkWindow* window = gtk_widget_get_window(widget);
+ LOGCONTAINER(("moz_container_unrealize() [%p] GdkWindow %p\n",
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget)),
+ (void*)window));
+
+ if (gtk_widget_get_mapped(widget)) {
+ gtk_widget_unmap(widget);
+ }
+
+ gtk_widget_unregister_window(widget, window);
+ gtk_widget_set_window(widget, nullptr);
+ gdk_window_destroy(window);
+ gtk_widget_set_realized(widget, false);
+}
+
+void moz_container_size_allocate(GtkWidget* widget, GtkAllocation* allocation) {
+ MozContainer* container;
+ GList* tmp_list;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOGCONTAINER(("moz_container_size_allocate [%p] %d,%d -> %d x %d\n",
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget)),
+ allocation->x, allocation->y, allocation->width,
+ allocation->height));
+
+ /* short circuit if you can */
+ container = MOZ_CONTAINER(widget);
+ gtk_widget_get_allocation(widget, &tmp_allocation);
+ if (!container->data.children && tmp_allocation.x == allocation->x &&
+ tmp_allocation.y == allocation->y &&
+ tmp_allocation.width == allocation->width &&
+ tmp_allocation.height == allocation->height) {
+ return;
+ }
+
+ gtk_widget_set_allocation(widget, allocation);
+
+ tmp_list = container->data.children;
+
+ while (tmp_list) {
+ MozContainerChild* child = static_cast<MozContainerChild*>(tmp_list->data);
+
+ moz_container_allocate_child(container, child);
+
+ tmp_list = tmp_list->next;
+ }
+
+ if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
+ gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
+ allocation->y, allocation->width,
+ allocation->height);
+ }
+}
+
+void moz_container_remove(GtkContainer* container, GtkWidget* child_widget) {
+ MozContainerChild* child;
+ MozContainer* moz_container;
+ GdkWindow* parent_window;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(container));
+ g_return_if_fail(GTK_IS_WIDGET(child_widget));
+
+ moz_container = MOZ_CONTAINER(container);
+
+ child = moz_container_get_child(moz_container, child_widget);
+ g_return_if_fail(child);
+
+ /* gtk_widget_unparent will remove the parent window (as well as the
+ * parent widget), but, in Mozilla's window hierarchy, the parent window
+ * may need to be kept because it may be part of a GdkWindow sub-hierarchy
+ * that is being moved to another MozContainer.
+ *
+ * (In a conventional GtkWidget hierarchy, GdkWindows being reparented
+ * would have their own GtkWidget and that widget would be the one being
+ * reparented. In Mozilla's hierarchy, the parent_window needs to be
+ * retained so that the GdkWindow sub-hierarchy is maintained.)
+ */
+ parent_window = gtk_widget_get_parent_window(child_widget);
+ if (parent_window) g_object_ref(parent_window);
+
+ gtk_widget_unparent(child_widget);
+
+ if (parent_window) {
+ /* The child_widget will always still exist because g_signal_emit,
+ * which invokes this function, holds a reference.
+ *
+ * If parent_window is the container's root window then it will not be
+ * the parent_window if the child_widget is placed in another
+ * container.
+ */
+ if (parent_window != gtk_widget_get_window(GTK_WIDGET(container))) {
+ gtk_widget_set_parent_window(child_widget, parent_window);
+ }
+
+ g_object_unref(parent_window);
+ }
+
+ moz_container->data.children =
+ g_list_remove(moz_container->data.children, child);
+ g_free(child);
+}
+
+void moz_container_forall(GtkContainer* container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data) {
+ g_return_if_fail(IS_MOZ_CONTAINER(container));
+ g_return_if_fail(callback);
+
+ MozContainer* moz_container = MOZ_CONTAINER(container);
+
+ GList* tmp_list = moz_container->data.children;
+ while (tmp_list) {
+ MozContainerChild* child;
+ child = static_cast<MozContainerChild*>(tmp_list->data);
+ tmp_list = tmp_list->next;
+ (*callback)(child->widget, callback_data);
+ }
+}
+
+static void moz_container_allocate_child(MozContainer* container,
+ MozContainerChild* child) {
+ GtkAllocation allocation;
+
+ gtk_widget_get_allocation(child->widget, &allocation);
+ allocation.x = child->x;
+ allocation.y = child->y;
+
+ gtk_widget_size_allocate(child->widget, &allocation);
+}
+
+MozContainerChild* moz_container_get_child(MozContainer* container,
+ GtkWidget* child_widget) {
+ GList* tmp_list = container->data.children;
+ while (tmp_list) {
+ MozContainerChild* child;
+
+ child = static_cast<MozContainerChild*>(tmp_list->data);
+ tmp_list = tmp_list->next;
+
+ if (child->widget == child_widget) return child;
+ }
+ return nullptr;
+}
+
+static void moz_container_add(GtkContainer* container, GtkWidget* widget) {
+ moz_container_put(MOZ_CONTAINER(container), widget, 0, 0);
+}
+
+void moz_container_force_default_visual(MozContainer* container) {
+ container->data.force_default_visual = true;
+}
+
+nsWindow* moz_container_get_nsWindow(MozContainer* container) {
+ gpointer user_data = g_object_get_data(G_OBJECT(container), "nsWindow");
+ return static_cast<nsWindow*>(user_data);
+}
+
+#undef LOGCONTAINER
diff --git a/widget/gtk/MozContainer.h b/widget/gtk/MozContainer.h
new file mode 100644
index 0000000000..27fa2a701f
--- /dev/null
+++ b/widget/gtk/MozContainer.h
@@ -0,0 +1,97 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MOZ_CONTAINER_H__
+#define __MOZ_CONTAINER_H__
+
+#ifdef MOZ_WAYLAND
+# include "MozContainerWayland.h"
+#endif
+
+#include <gtk/gtk.h>
+#include <functional>
+
+/*
+ * MozContainer
+ *
+ * This class serves three purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - It provides GdkWindow to draw content on Wayland or when Gtk+ renders
+ * client side decorations to mShell.
+ *
+ * - It provides a container parent for GtkWidgets. The only GtkWidgets
+ * that need this in Mozilla are the GtkSockets for windowed plugins (Xt
+ * and XEmbed).
+ *
+ * Note that the window hierarchy in Mozilla differs from conventional
+ * GtkWidget hierarchies.
+ *
+ * Mozilla's hierarchy exists through the GdkWindow hierarchy, and all child
+ * GdkWindows (within a child nsIWidget hierarchy) belong to one MozContainer
+ * GtkWidget. If the MozContainer is unrealized or its GdkWindows are
+ * destroyed for some other reason, then the hierarchy no longer exists. (In
+ * conventional GTK clients, the hierarchy is recorded by the GtkWidgets, and
+ * so can be re-established after destruction of the GdkWindows.)
+ *
+ * One consequence of this is that the MozContainer does not know which of its
+ * GdkWindows should parent child GtkWidgets. (Conventional GtkContainers
+ * determine which GdkWindow to assign child GtkWidgets.)
+ *
+ * Therefore, when adding a child GtkWidget to a MozContainer,
+ * gtk_widget_set_parent_window should be called on the child GtkWidget before
+ * it is realized.
+ */
+
+#define MOZ_CONTAINER_TYPE (moz_container_get_type())
+#define MOZ_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_CAST((obj), MOZ_CONTAINER_TYPE, MozContainer))
+#define MOZ_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_CAST((klass), MOZ_CONTAINER_TYPE, MozContainerClass))
+#define IS_MOZ_CONTAINER(obj) \
+ (G_TYPE_CHECK_INSTANCE_TYPE((obj), MOZ_CONTAINER_TYPE))
+#define IS_MOZ_CONTAINER_CLASS(klass) \
+ (G_TYPE_CHECK_CLASS_TYPE((klass), MOZ_CONTAINER_TYPE))
+#define MOZ_CONTAINER_GET_CLASS(obj) \
+ (G_TYPE_INSTANCE_GET_CLASS((obj), MOZ_CONTAINER_TYPE, MozContainerClass))
+#ifdef MOZ_WAYLAND
+# define MOZ_WL_CONTAINER(obj) (&MOZ_CONTAINER(obj)->data.wl_container)
+#endif
+
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+struct _MozContainer {
+ GtkContainer container;
+ gboolean destroyed;
+ struct Data {
+ GList* children = nullptr;
+ gboolean force_default_visual = false;
+#ifdef MOZ_WAYLAND
+ MozContainerWayland wl_container;
+#endif
+ } data;
+};
+
+struct _MozContainerClass {
+ GtkContainerClass parent_class;
+};
+
+GType moz_container_get_type(void);
+GtkWidget* moz_container_new(void);
+void moz_container_unmap(GtkWidget* widget);
+void moz_container_put(MozContainer* container, GtkWidget* child_widget, gint x,
+ gint y);
+void moz_container_force_default_visual(MozContainer* container);
+void moz_container_class_init(MozContainerClass* klass);
+
+class nsWindow;
+nsWindow* moz_container_get_nsWindow(MozContainer* container);
+
+#endif /* __MOZ_CONTAINER_H__ */
diff --git a/widget/gtk/MozContainerWayland.cpp b/widget/gtk/MozContainerWayland.cpp
new file mode 100644
index 0000000000..0e50a3f27c
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.cpp
@@ -0,0 +1,824 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+/*
+ * MozContainerWayland is a wrapper over MozContainer which provides
+ * wl_surface for MozContainer widget.
+ *
+ * The widget scheme looks like:
+ *
+ * ---------------------------------------------------------
+ * | mShell Gtk widget (contains wl_surface owned by Gtk+) |
+ * | |
+ * | --------------------------------------------------- |
+ * | | mContainer (contains wl_surface owned by Gtk+) | |
+ * | | | |
+ * | | --------------------------------------------- | |
+ * | | | wl_subsurface (attached to wl_surface | | |
+ * | | | of mContainer) | | |
+ * | | | | | |
+ * | | | | | |
+ * | | --------------------------------------------- | |
+ * | --------------------------------------------------- |
+ * ---------------------------------------------------------
+ *
+ * We draw to wl_subsurface owned by MozContainerWayland.
+ * We need to wait until wl_surface of mContainer is created
+ * and then we create and attach our wl_subsurface to it.
+ *
+ * First wl_subsurface creation has these steps:
+ *
+ * 1) moz_container_wayland_size_allocate() handler is called when
+ * mContainer size/position is known.
+ * It calls moz_container_wayland_surface_create_locked(), registers
+ * a frame callback handler
+ * moz_container_wayland_frame_callback_handler().
+ *
+ * 2) moz_container_wayland_frame_callback_handler() is called
+ * when wl_surface owned by mozContainer is ready.
+ * We call initial_draw_cbs() handler and we can create our wl_subsurface
+ * on top of wl_surface owned by mozContainer.
+ *
+ * When MozContainer hides/show again, moz_container_wayland_size_allocate()
+ * handler may not be called as MozContainer size is set. So after first
+ * show/hide sequence use moz_container_wayland_map_event() to create
+ * wl_subsurface of MozContainer.
+ */
+
+#include "MozContainer.h"
+
+#include <dlfcn.h>
+#include <glib.h>
+#include <stdio.h>
+#include <wayland-egl.h>
+
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsGtkUtils.h"
+#include "nsWaylandDisplay.h"
+#include "base/task.h"
+
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+# include "nsWindow.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOGWAYLAND(...) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOGCONTAINER(...) \
+ MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOGWAYLAND(...)
+# define LOGCONTAINER(...)
+#endif /* MOZ_LOGGING */
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static bool moz_container_wayland_surface_create_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container);
+static void moz_container_wayland_set_opaque_region_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container);
+
+// Lock mozcontainer and get wayland surface of it. You need to pair with
+// moz_container_wayland_surface_unlock() even
+// if moz_container_wayland_surface_lock() fails and returns nullptr.
+static struct wl_surface* moz_container_wayland_surface_lock(
+ MozContainer* container);
+static void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface);
+
+MozContainerSurfaceLock::MozContainerSurfaceLock(MozContainer* aContainer) {
+ mContainer = aContainer;
+ mSurface = moz_container_wayland_surface_lock(aContainer);
+}
+MozContainerSurfaceLock::~MozContainerSurfaceLock() {
+ moz_container_wayland_surface_unlock(mContainer, &mSurface);
+}
+struct wl_surface* MozContainerSurfaceLock::GetSurface() { return mSurface; }
+
+// Invalidate gtk wl_surface to commit changes to wl_subsurface.
+// wl_subsurface changes are effective when parent surface is commited.
+static void moz_container_wayland_invalidate(MozContainer* container) {
+ LOGWAYLAND("moz_container_wayland_invalidate [%p]\n",
+ (void*)moz_container_get_nsWindow(container));
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ if (!window) {
+ LOGWAYLAND(" Failed - missing GdkWindow!\n");
+ return;
+ }
+ gdk_window_invalidate_rect(window, nullptr, true);
+}
+
+// Route input to parent wl_surface owned by Gtk+ so we get input
+// events from Gtk+.
+static void moz_container_clear_input_region(MozContainer* container) {
+ struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor();
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_surface_set_input_region(wl_container->surface, region);
+ wl_region_destroy(region);
+}
+
+static void moz_container_wayland_move_locked(const MutexAutoLock& aProofOfLock,
+ MozContainer* container, int dx,
+ int dy) {
+ LOGCONTAINER("moz_container_wayland_move [%p] %d,%d\n",
+ (void*)moz_container_get_nsWindow(container), dx, dy);
+
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ if (!wl_container->subsurface || (wl_container->subsurface_dx == dx &&
+ wl_container->subsurface_dy == dy)) {
+ return;
+ }
+
+ wl_container->subsurface_dx = dx;
+ wl_container->subsurface_dy = dy;
+ wl_subsurface_set_position(wl_container->subsurface,
+ wl_container->subsurface_dx,
+ wl_container->subsurface_dy);
+}
+
+// This is called from layout/compositor code only with
+// size equal to GL rendering context.
+
+// Return false if scale factor doesn't match buffer size.
+// We need to skip painting in such case do avoid Wayland compositor freaking.
+bool moz_container_wayland_egl_window_set_size(MozContainer* container,
+ nsIntSize aSize, int aScale) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MutexAutoLock lock(wl_container->container_lock);
+
+ // We may be called after unmap so we're missing egl window completelly.
+ // In such case don't return false which would block compositor.
+ // We return true here and don't block flush WebRender queue.
+ // We'll be repainted if our window become visible again anyway.
+ if (!wl_container->eglwindow) {
+ return true;
+ }
+
+ if (wl_container->buffer_scale != aScale) {
+ moz_container_wayland_set_scale_factor_locked(lock, container, aScale);
+ }
+
+ /* Enable for size changes logging
+ LOGCONTAINER(
+ "moz_container_wayland_egl_window_set_size [%p] %d x %d scale %d "
+ "(unscaled %d x %d)",
+ (void*)moz_container_get_nsWindow(container), aSize.width, aSize.height,
+ aScale, aSize.width / aScale, aSize.height / aScale);
+ */
+ wl_egl_window_resize(wl_container->eglwindow, aSize.width, aSize.height, 0,
+ 0);
+
+ return moz_container_wayland_size_matches_scale_factor_locked(
+ lock, container, aSize.width, aSize.height);
+}
+
+void moz_container_wayland_add_initial_draw_callback_locked(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ if (wl_container->ready_to_draw && !wl_container->surface) {
+ NS_WARNING(
+ "moz_container_wayland_add_or_fire_initial_draw_callback:"
+ " ready to draw without wayland surface!");
+ }
+ MOZ_DIAGNOSTIC_ASSERT(!wl_container->ready_to_draw || !wl_container->surface);
+ wl_container->initial_draw_cbs.push_back(initial_draw_cb);
+}
+
+void moz_container_wayland_add_or_fire_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ {
+ MutexAutoLock lock(wl_container->container_lock);
+ if (wl_container->ready_to_draw && !wl_container->surface) {
+ NS_WARNING(
+ "moz_container_wayland_add_or_fire_initial_draw_callback: ready to "
+ "draw "
+ "without wayland surface!");
+ }
+ if (!wl_container->ready_to_draw || !wl_container->surface) {
+ wl_container->initial_draw_cbs.push_back(initial_draw_cb);
+ return;
+ }
+ }
+
+ // We're ready to draw as
+ // wl_container->ready_to_draw && wl_container->surface
+ // call the callback directly instead of store them.
+ initial_draw_cb();
+}
+
+static void moz_container_wayland_clear_initial_draw_callback_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MozClearPointer(wl_container->frame_callback_handler, wl_callback_destroy);
+ wl_container->initial_draw_cbs.clear();
+}
+
+void moz_container_wayland_clear_initial_draw_callback(
+ MozContainer* container) {
+ MutexAutoLock lock(container->data.wl_container.container_lock);
+ moz_container_wayland_clear_initial_draw_callback_locked(lock, container);
+}
+
+static void moz_container_wayland_frame_callback_handler(
+ void* data, struct wl_callback* callback, uint32_t time) {
+ MozContainerWayland* wl_container = MOZ_WL_CONTAINER(data);
+
+ LOGWAYLAND(
+ "%s [%p] frame_callback_handler %p ready_to_draw %d (set to true)"
+ " initial_draw callback %zd\n",
+ __FUNCTION__, (void*)moz_container_get_nsWindow(MOZ_CONTAINER(data)),
+ (void*)wl_container->frame_callback_handler, wl_container->ready_to_draw,
+ wl_container->initial_draw_cbs.size());
+
+ std::vector<std::function<void(void)>> cbs;
+ {
+ // Protect mozcontainer internals changes by container_lock.
+ MutexAutoLock lock(wl_container->container_lock);
+ MozClearPointer(wl_container->frame_callback_handler, wl_callback_destroy);
+ // It's possible that container is already unmapped so quit in such case.
+ if (!wl_container->surface) {
+ LOGWAYLAND(" container is unmapped, quit.");
+ if (!wl_container->initial_draw_cbs.empty()) {
+ NS_WARNING("Unmapping MozContainer with active draw callback!");
+ wl_container->initial_draw_cbs.clear();
+ }
+ return;
+ }
+ if (wl_container->ready_to_draw) {
+ return;
+ }
+ wl_container->ready_to_draw = true;
+ cbs = std::move(wl_container->initial_draw_cbs);
+ }
+
+ // Call the callbacks registered by
+ // moz_container_wayland_add_or_fire_initial_draw_callback().
+ // and we can't do that under mozcontainer lock.
+ for (auto const& cb : cbs) {
+ cb();
+ }
+}
+
+static const struct wl_callback_listener moz_container_frame_listener = {
+ moz_container_wayland_frame_callback_handler};
+
+static void after_frame_clock_after_paint(GdkFrameClock* clock,
+ MozContainer* container) {
+ MozContainerSurfaceLock lock(container);
+ struct wl_surface* surface = lock.GetSurface();
+ if (surface) {
+ wl_surface_commit(surface);
+ }
+}
+
+static bool moz_gdk_wayland_window_add_frame_callback_surface_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container) {
+ static auto sGdkWaylandWindowAddCallbackSurface =
+ (void (*)(GdkWindow*, struct wl_surface*))dlsym(
+ RTLD_DEFAULT, "gdk_wayland_window_add_frame_callback_surface");
+
+ if (!StaticPrefs::widget_wayland_opaque_region_enabled_AtStartup() ||
+ !sGdkWaylandWindowAddCallbackSurface) {
+ return false;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ sGdkWaylandWindowAddCallbackSurface(window, wl_container->surface);
+
+ GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
+ g_signal_connect_after(frame_clock, "after-paint",
+ G_CALLBACK(after_frame_clock_after_paint), container);
+ return true;
+}
+
+static void moz_gdk_wayland_window_remove_frame_callback_surface_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container) {
+ static auto sGdkWaylandWindowRemoveCallbackSurface =
+ (void (*)(GdkWindow*, struct wl_surface*))dlsym(
+ RTLD_DEFAULT, "gdk_wayland_window_remove_frame_callback_surface");
+
+ if (!sGdkWaylandWindowRemoveCallbackSurface) {
+ return;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ if (wl_container->surface) {
+ sGdkWaylandWindowRemoveCallbackSurface(window, wl_container->surface);
+ }
+
+ GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
+ g_signal_handlers_disconnect_by_func(
+ frame_clock, FuncToGpointer(after_frame_clock_after_paint), container);
+}
+
+void moz_container_wayland_unmap(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ // Unmap MozContainer first so we can remove our resources
+ moz_container_unmap(widget);
+
+ MozContainer* container = MOZ_CONTAINER(widget);
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MutexAutoLock lock(wl_container->container_lock);
+
+ LOGCONTAINER("%s [%p]\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(container));
+
+ moz_container_wayland_clear_initial_draw_callback_locked(lock, container);
+
+ if (wl_container->opaque_region_used) {
+ moz_gdk_wayland_window_remove_frame_callback_surface_locked(lock,
+ container);
+ }
+ if (wl_container->commit_to_parent) {
+ wl_container->surface = nullptr;
+ }
+
+ MozClearPointer(wl_container->eglwindow, wl_egl_window_destroy);
+ MozClearPointer(wl_container->subsurface, wl_subsurface_destroy);
+ MozClearPointer(wl_container->surface, wl_surface_destroy);
+ MozClearPointer(wl_container->viewport, wp_viewport_destroy);
+ MozClearPointer(wl_container->fractional_scale,
+ wp_fractional_scale_v1_destroy);
+
+ wl_container->ready_to_draw = false;
+ wl_container->buffer_scale = 1;
+ wl_container->current_fractional_scale = 0.0;
+}
+
+gboolean moz_container_wayland_map_event(GtkWidget* widget,
+ GdkEventAny* event) {
+ MozContainerWayland* wl_container = &MOZ_CONTAINER(widget)->data.wl_container;
+
+ LOGCONTAINER("%s [%p]\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget)));
+
+ // Return early if we're not mapped. Gtk may send bogus map_event signal
+ // to unmapped widgets (see Bug 1875369).
+ if (!gtk_widget_get_mapped(widget)) {
+ return false;
+ }
+
+ // Make sure we're on main thread as we can't lock mozContainer here
+ // due to moz_container_wayland_add_or_fire_initial_draw_callback() call
+ // below.
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ // Set waiting_to_show flag. It means the mozcontainer is cofigured/mapped
+ // and it's supposed to be visible. *But* it's really visible when we get
+ // moz_container_wayland_add_or_fire_initial_draw_callback() which means
+ // wayland compositor makes it live.
+ wl_container->waiting_to_show = true;
+ MozContainer* container = MOZ_CONTAINER(widget);
+ moz_container_wayland_add_or_fire_initial_draw_callback(
+ container, [container]() -> void {
+ LOGCONTAINER(
+ "[%p] moz_container_wayland_add_or_fire_initial_draw_callback set "
+ "visible",
+ moz_container_get_nsWindow(container));
+ moz_container_wayland_clear_waiting_to_show_flag(container);
+ });
+
+ MutexAutoLock lock(wl_container->container_lock);
+
+ // Don't create wl_subsurface in map_event when it's already created or
+ // if we create it for the first time.
+ if (wl_container->ready_to_draw || wl_container->before_first_size_alloc) {
+ return FALSE;
+ }
+
+ if (!wl_container->surface) {
+ if (!moz_container_wayland_surface_create_locked(lock,
+ MOZ_CONTAINER(widget))) {
+ return FALSE;
+ }
+ }
+
+ nsWindow* window = moz_container_get_nsWindow(MOZ_CONTAINER(widget));
+ moz_container_wayland_set_scale_factor_locked(lock, MOZ_CONTAINER(widget),
+ window->GdkCeiledScaleFactor());
+ moz_container_wayland_set_opaque_region_locked(lock, MOZ_CONTAINER(widget));
+ moz_container_clear_input_region(MOZ_CONTAINER(widget));
+ moz_container_wayland_invalidate(MOZ_CONTAINER(widget));
+ return FALSE;
+}
+
+void moz_container_wayland_map(GtkWidget* widget) {
+ LOGCONTAINER("%s [%p]\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget)));
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ // We need to mark MozContainer as mapped to make sure
+ // moz_container_wayland_unmap() is called on hide/withdraw.
+ gtk_widget_set_mapped(widget, TRUE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_show(gtk_widget_get_window(widget));
+ }
+}
+
+void moz_container_wayland_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ MozContainer* container;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOGCONTAINER("moz_container_wayland_size_allocate [%p] %d,%d -> %d x %d\n",
+ (void*)moz_container_get_nsWindow(MOZ_CONTAINER(widget)),
+ allocation->x, allocation->y, allocation->width,
+ allocation->height);
+
+ /* short circuit if you can */
+ container = MOZ_CONTAINER(widget);
+ gtk_widget_get_allocation(widget, &tmp_allocation);
+ if (!container->data.children && tmp_allocation.x == allocation->x &&
+ tmp_allocation.y == allocation->y &&
+ tmp_allocation.width == allocation->width &&
+ tmp_allocation.height == allocation->height) {
+ return;
+ }
+ gtk_widget_set_allocation(widget, allocation);
+
+ if (gtk_widget_get_has_window(widget) && gtk_widget_get_realized(widget)) {
+ gdk_window_move_resize(gtk_widget_get_window(widget), allocation->x,
+ allocation->y, allocation->width,
+ allocation->height);
+ // We need to position our subsurface according to GdkWindow
+ // when offset changes (GdkWindow is maximized for instance).
+ // see gtk-clutter-embed.c for reference.
+ MutexAutoLock lock(container->data.wl_container.container_lock);
+ if (!container->data.wl_container.surface) {
+ if (!moz_container_wayland_surface_create_locked(lock, container)) {
+ return;
+ }
+ }
+ nsWindow* window = moz_container_get_nsWindow(container);
+ moz_container_wayland_set_scale_factor_locked(
+ lock, container, window->GdkCeiledScaleFactor());
+ moz_container_wayland_set_opaque_region_locked(lock, container);
+ moz_container_wayland_move_locked(lock, container, allocation->x,
+ allocation->y);
+ moz_container_clear_input_region(container);
+ moz_container_wayland_invalidate(MOZ_CONTAINER(widget));
+ container->data.wl_container.before_first_size_alloc = false;
+ }
+}
+
+static wl_region* moz_container_wayland_create_opaque_region(
+ int aX, int aY, int aWidth, int aHeight, int aCornerRadius) {
+ struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor();
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_region_add(region, aX, aY, aWidth, aHeight);
+ if (aCornerRadius) {
+ wl_region_subtract(region, aX, aY, aCornerRadius, aCornerRadius);
+ wl_region_subtract(region, aX + aWidth - aCornerRadius, aY, aCornerRadius,
+ aCornerRadius);
+ wl_region_subtract(region, aX, aY + aHeight - aCornerRadius, aCornerRadius,
+ aCornerRadius);
+ wl_region_subtract(region, aX + aWidth - aCornerRadius,
+ aY + aHeight - aCornerRadius, aCornerRadius,
+ aCornerRadius);
+ }
+ return region;
+}
+
+static void moz_container_wayland_set_opaque_region_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ if (!wl_container->opaque_region_needs_updates) {
+ return;
+ }
+
+ if (!wl_container->opaque_region_used) {
+ wl_container->opaque_region_needs_updates = false;
+ return;
+ }
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(container), &allocation);
+
+ wl_region* region = moz_container_wayland_create_opaque_region(
+ 0, 0, allocation.width, allocation.height,
+ wl_container->opaque_region_corner_radius);
+ wl_surface_set_opaque_region(wl_container->surface, region);
+ wl_region_destroy(region);
+ wl_container->opaque_region_needs_updates = false;
+}
+
+static void moz_container_wayland_set_opaque_region(MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MutexAutoLock lock(wl_container->container_lock);
+ if (wl_container->surface) {
+ moz_container_wayland_set_opaque_region_locked(lock, container);
+ }
+}
+
+static void moz_container_wayland_surface_set_scale_locked(
+ const MutexAutoLock& aProofOfLock, MozContainerWayland* wl_container,
+ int scale) {
+ if (!wl_container->surface) {
+ return;
+ }
+ if (wl_container->buffer_scale == scale) {
+ return;
+ }
+
+ LOGCONTAINER("%s scale %d\n", __FUNCTION__, scale);
+
+ // There is a chance that the attached wl_buffer has not yet been doubled
+ // on the main thread when scale factor changed to 2. This leads to
+ // crash with the following message:
+ // Buffer size (AxB) must be an integer multiple of the buffer_scale (2)
+ // Removing the possibly wrong wl_buffer to prevent that crash:
+ wl_surface_attach(wl_container->surface, nullptr, 0, 0);
+ wl_surface_set_buffer_scale(wl_container->surface, scale);
+ wl_container->buffer_scale = scale;
+}
+
+static void fractional_scale_handle_preferred_scale(
+ void* data, struct wp_fractional_scale_v1* info, uint32_t wire_scale) {
+ MozContainer* container = MOZ_CONTAINER(data);
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ wl_container->current_fractional_scale = wire_scale / 120.0;
+
+ RefPtr<nsWindow> window = moz_container_get_nsWindow(container);
+ LOGWAYLAND("%s [%p] scale: %f\n", __func__, window.get(),
+ wl_container->current_fractional_scale);
+ MOZ_DIAGNOSTIC_ASSERT(window);
+ window->OnScaleChanged(/* aNotify = */ true);
+}
+
+static const struct wp_fractional_scale_v1_listener fractional_scale_listener =
+ {
+ .preferred_scale = fractional_scale_handle_preferred_scale,
+};
+
+void moz_container_wayland_set_scale_factor_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container, int aScale) {
+ if (gfx::gfxVars::UseWebRenderCompositor()) {
+ // the compositor backend handles scaling itself
+ return;
+ }
+
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ wl_container->container_lock.AssertCurrentThreadOwns();
+
+ if (StaticPrefs::widget_wayland_fractional_scale_enabled_AtStartup()) {
+ if (!wl_container->fractional_scale) {
+ if (auto* manager = WaylandDisplayGet()->GetFractionalScaleManager()) {
+ wl_container->fractional_scale =
+ wp_fractional_scale_manager_v1_get_fractional_scale(
+ manager, wl_container->surface);
+ wp_fractional_scale_v1_add_listener(wl_container->fractional_scale,
+ &fractional_scale_listener,
+ container);
+ }
+ }
+
+ if (wl_container->fractional_scale) {
+ if (!wl_container->viewport) {
+ if (auto* viewporter = WaylandDisplayGet()->GetViewporter()) {
+ wl_container->viewport =
+ wp_viewporter_get_viewport(viewporter, wl_container->surface);
+ }
+ }
+ if (wl_container->viewport) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+ wp_viewport_set_destination(wl_container->viewport,
+ gdk_window_get_width(gdkWindow),
+ gdk_window_get_height(gdkWindow));
+ return;
+ }
+ }
+ }
+
+ moz_container_wayland_surface_set_scale_locked(aProofOfLock, wl_container,
+ aScale);
+}
+
+bool moz_container_wayland_size_matches_scale_factor_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container, int aWidth,
+ int aHeight) {
+ return aWidth % container->data.wl_container.buffer_scale == 0 &&
+ aHeight % container->data.wl_container.buffer_scale == 0;
+}
+
+static bool moz_container_wayland_surface_create_locked(
+ const MutexAutoLock& aProofOfLock, MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ LOGWAYLAND("%s [%p]\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(container));
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ MOZ_DIAGNOSTIC_ASSERT(window);
+
+ wl_surface* parent_surface = gdk_wayland_window_get_wl_surface(window);
+ if (!parent_surface) {
+ LOGWAYLAND(" Failed - missing parent surface!");
+ return false;
+ }
+ LOGWAYLAND(" gtk wl_surface %p ID %d\n", (void*)parent_surface,
+ wl_proxy_get_id((struct wl_proxy*)parent_surface));
+
+ if (wl_container->commit_to_parent) {
+ LOGWAYLAND(" commit to parent");
+ wl_container->surface = parent_surface;
+ NS_DispatchToCurrentThread(NewRunnableFunction(
+ "moz_container_wayland_frame_callback_handler",
+ &moz_container_wayland_frame_callback_handler, container, nullptr, 0));
+ return true;
+ }
+
+ // Available as of GTK 3.8+
+ struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor();
+ wl_container->surface = wl_compositor_create_surface(compositor);
+ if (!wl_container->surface) {
+ LOGWAYLAND(" Failed - can't create surface!");
+ return false;
+ }
+
+ wl_container->subsurface =
+ wl_subcompositor_get_subsurface(WaylandDisplayGet()->GetSubcompositor(),
+ wl_container->surface, parent_surface);
+ if (!wl_container->subsurface) {
+ MozClearPointer(wl_container->surface, wl_surface_destroy);
+ LOGWAYLAND(" Failed - can't create sub-surface!");
+ return false;
+ }
+ wl_subsurface_set_desync(wl_container->subsurface);
+
+ // Try to guess subsurface offset to avoid potential flickering.
+ int dx, dy;
+ if (moz_container_get_nsWindow(container)->GetCSDDecorationOffset(&dx, &dy)) {
+ wl_container->subsurface_dx = dx;
+ wl_container->subsurface_dy = dy;
+ wl_subsurface_set_position(wl_container->subsurface, dx, dy);
+ LOGWAYLAND(" guessing subsurface position %d %d\n", dx, dy);
+ }
+
+ // If there's pending frame callback it's for wrong parent surface,
+ // so delete it.
+ if (wl_container->frame_callback_handler) {
+ MozClearPointer(wl_container->frame_callback_handler, wl_callback_destroy);
+ }
+ wl_container->frame_callback_handler = wl_surface_frame(parent_surface);
+ wl_callback_add_listener(wl_container->frame_callback_handler,
+ &moz_container_frame_listener, container);
+ LOGWAYLAND(
+ " created frame callback ID %d\n",
+ wl_proxy_get_id((struct wl_proxy*)wl_container->frame_callback_handler));
+
+ wl_surface_commit(wl_container->surface);
+ wl_display_flush(WaylandDisplayGet()->GetDisplay());
+
+ wl_container->opaque_region_used =
+ moz_gdk_wayland_window_add_frame_callback_surface_locked(aProofOfLock,
+ container);
+
+ LOGWAYLAND(" created surface %p ID %d\n", (void*)wl_container->surface,
+ wl_proxy_get_id((struct wl_proxy*)wl_container->surface));
+ return true;
+}
+
+struct wl_surface* moz_container_wayland_surface_lock(MozContainer* container)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // LOGWAYLAND("%s [%p] surface %p ready_to_draw %d\n", __FUNCTION__,
+ // (void*)container, (void*)container->data.wl_container.surface,
+ // container->data.wl_container.ready_to_draw);
+ container->data.wl_container.container_lock.Lock();
+ if (!container->data.wl_container.surface ||
+ !container->data.wl_container.ready_to_draw) {
+ return nullptr;
+ }
+ return container->data.wl_container.surface;
+}
+
+void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface)
+ MOZ_NO_THREAD_SAFETY_ANALYSIS {
+ // Temporarily disabled to avoid log noise
+ // LOGWAYLAND("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
+ // (void*)container->data.wl_container.surface);
+ if (*surface) {
+ *surface = nullptr;
+ }
+ container->data.wl_container.container_lock.Unlock();
+}
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, double scale) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+
+ LOGCONTAINER("%s [%p] eglwindow %p scale %d\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(container),
+ (void*)wl_container->eglwindow, (int)scale);
+
+ MutexAutoLock lock(wl_container->container_lock);
+ if (!wl_container->surface || !wl_container->ready_to_draw) {
+ LOGCONTAINER(
+ " quit, wl_container->surface %p wl_container->ready_to_draw %d\n",
+ wl_container->surface, wl_container->ready_to_draw);
+ return nullptr;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ nsIntSize requestedSize((int)round(gdk_window_get_width(window) * scale),
+ (int)round(gdk_window_get_height(window) * scale));
+
+ if (!wl_container->eglwindow) {
+ wl_container->eglwindow = wl_egl_window_create(
+ wl_container->surface, requestedSize.width, requestedSize.height);
+
+ LOGCONTAINER("%s [%p] created eglwindow %p size %d x %d (with scale %f)\n",
+ __FUNCTION__, (void*)moz_container_get_nsWindow(container),
+ (void*)wl_container->eglwindow, requestedSize.width,
+ requestedSize.height, scale);
+ } else {
+ nsIntSize recentSize;
+ wl_egl_window_get_attached_size(wl_container->eglwindow, &recentSize.width,
+ &recentSize.height);
+ if (requestedSize != recentSize) {
+ LOGCONTAINER("%s [%p] resized to %d x %d (with scale %f)\n", __FUNCTION__,
+ (void*)moz_container_get_nsWindow(container),
+ requestedSize.width, requestedSize.height, scale);
+ wl_egl_window_resize(wl_container->eglwindow, requestedSize.width,
+ requestedSize.height, 0, 0);
+ }
+ }
+ moz_container_wayland_surface_set_scale_locked(lock, wl_container,
+ static_cast<int>(scale));
+ return wl_container->eglwindow;
+}
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container) {
+ return !!container->data.wl_container.eglwindow;
+}
+
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ int corner_radius) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ wl_container->opaque_region_needs_updates = true;
+ wl_container->opaque_region_corner_radius = corner_radius;
+
+ // When GL compositor / WebRender is used,
+ // moz_container_wayland_get_egl_window() is called only once when window
+ // is created or resized so update opaque region now.
+ if (moz_container_wayland_has_egl_window(container)) {
+ moz_container_wayland_set_opaque_region(container);
+ }
+}
+
+gboolean moz_container_wayland_can_draw(MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MutexAutoLock lock(wl_container->container_lock);
+ return wl_container->ready_to_draw;
+}
+
+double moz_container_wayland_get_fractional_scale(MozContainer* container) {
+ return container->data.wl_container.current_fractional_scale;
+}
+
+double moz_container_wayland_get_scale(MozContainer* container) {
+ nsWindow* window = moz_container_get_nsWindow(container);
+ return window ? window->FractionalScaleFactor() : 1.0;
+}
+
+void moz_container_wayland_set_commit_to_parent(MozContainer* container) {
+ MozContainerWayland* wl_container = &container->data.wl_container;
+ MOZ_DIAGNOSTIC_ASSERT(!wl_container->surface);
+ wl_container->commit_to_parent = true;
+}
+
+bool moz_container_wayland_is_commiting_to_parent(MozContainer* container) {
+ return container->data.wl_container.commit_to_parent;
+}
+
+bool moz_container_wayland_is_waiting_to_show(MozContainer* container) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ return container->data.wl_container.waiting_to_show;
+}
+
+void moz_container_wayland_clear_waiting_to_show_flag(MozContainer* container) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ container->data.wl_container.waiting_to_show = false;
+}
diff --git a/widget/gtk/MozContainerWayland.h b/widget/gtk/MozContainerWayland.h
new file mode 100644
index 0000000000..068c674256
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MOZ_CONTAINER_WAYLAND_H__
+#define __MOZ_CONTAINER_WAYLAND_H__
+
+#include <gtk/gtk.h>
+#include <functional>
+#include <vector>
+#include "mozilla/Mutex.h"
+#include "WindowSurface.h"
+
+/*
+ * MozContainer
+ *
+ * This class serves three purposes in the nsIWidget implementation.
+ *
+ * - It provides objects to receive signals from GTK for events on native
+ * windows.
+ *
+ * - It provides GdkWindow to draw content on Wayland or when Gtk+ renders
+ * client side decorations to mShell.
+ */
+
+/* Workaround for bug at wayland-util.h,
+ * present in wayland-devel < 1.12
+ */
+struct wl_surface;
+struct wl_subsurface;
+
+struct MozContainerWayland {
+ struct wl_surface* surface = nullptr;
+ struct wl_subsurface* subsurface = nullptr;
+ int subsurface_dx = 0;
+ int subsurface_dy = 0;
+ struct wl_egl_window* eglwindow = nullptr;
+ struct wl_callback* frame_callback_handler = nullptr;
+ struct wp_viewport* viewport = nullptr;
+ struct wp_fractional_scale_v1* fractional_scale = nullptr;
+ gboolean opaque_region_needs_updates = false;
+ int opaque_region_corner_radius = 0;
+ gboolean opaque_region_used = false;
+ gboolean ready_to_draw = false;
+ gboolean commit_to_parent = false;
+ gboolean before_first_size_alloc = false;
+ gboolean waiting_to_show = false;
+ // Zero means no fractional scale set.
+ double current_fractional_scale = 0.0;
+ int buffer_scale = 1;
+ std::vector<std::function<void(void)>> initial_draw_cbs;
+ // mozcontainer is used from Compositor and Rendering threads so we need to
+ // control access to mozcontainer where wayland internals are used directly.
+ mozilla::Mutex container_lock{"MozContainerWayland::container_lock"};
+};
+
+struct _MozContainer;
+struct _MozContainerClass;
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+class MozContainerSurfaceLock {
+ MozContainer* mContainer;
+ struct wl_surface* mSurface;
+
+ public:
+ explicit MozContainerSurfaceLock(MozContainer* aContainer);
+ ~MozContainerSurfaceLock();
+ struct wl_surface* GetSurface();
+};
+
+void moz_container_wayland_map(GtkWidget*);
+gboolean moz_container_wayland_map_event(GtkWidget*, GdkEventAny*);
+void moz_container_wayland_size_allocate(GtkWidget*, GtkAllocation*);
+void moz_container_wayland_unmap(GtkWidget*);
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, double scale);
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container);
+bool moz_container_wayland_egl_window_set_size(MozContainer* container,
+ nsIntSize aSize, int aScale);
+void moz_container_wayland_set_scale_factor_locked(
+ const mozilla::MutexAutoLock& aProofOfLock, MozContainer* container,
+ int aScale);
+bool moz_container_wayland_size_matches_scale_factor_locked(
+ const mozilla::MutexAutoLock& aProofOfLock, MozContainer* container,
+ int aWidth, int aHeight);
+
+void moz_container_wayland_add_initial_draw_callback_locked(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb);
+void moz_container_wayland_add_or_fire_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb);
+void moz_container_wayland_clear_initial_draw_callback(MozContainer* container);
+
+wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget);
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ int corner_radius);
+gboolean moz_container_wayland_can_draw(MozContainer* container);
+double moz_container_wayland_get_scale(MozContainer* container);
+double moz_container_wayland_get_fractional_scale(MozContainer* container);
+void moz_container_wayland_set_commit_to_parent(MozContainer* container);
+bool moz_container_wayland_is_commiting_to_parent(MozContainer* container);
+bool moz_container_wayland_is_waiting_to_show(MozContainer* container);
+void moz_container_wayland_clear_waiting_to_show_flag(MozContainer* container);
+
+#endif /* __MOZ_CONTAINER_WAYLAND_H__ */
diff --git a/widget/gtk/NativeKeyBindings.cpp b/widget/gtk/NativeKeyBindings.cpp
new file mode 100644
index 0000000000..e050fd07c0
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.cpp
@@ -0,0 +1,527 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeKeyBindingsType.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/WritingModes.h"
+
+#include "NativeKeyBindings.h"
+#include "nsString.h"
+#include "nsGtkKeyUtils.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.h>
+#include <gdk/gdkkeysyms-compat.h>
+#include <gdk/gdk.h>
+
+namespace mozilla {
+namespace widget {
+
+static nsTArray<CommandInt>* gCurrentCommands = nullptr;
+static bool gHandled = false;
+
+inline void AddCommand(Command aCommand) {
+ MOZ_ASSERT(gCurrentCommands);
+ gCurrentCommands->AppendElement(static_cast<CommandInt>(aCommand));
+}
+
+// Common GtkEntry and GtkTextView signals
+static void copy_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Copy);
+ g_signal_stop_emission_by_name(w, "copy_clipboard");
+ gHandled = true;
+}
+
+static void cut_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Cut);
+ g_signal_stop_emission_by_name(w, "cut_clipboard");
+ gHandled = true;
+}
+
+// GTK distinguishes between display lines (wrapped, as they appear on the
+// screen) and paragraphs, which are runs of text terminated by a newline.
+// We don't have this distinction, so we always use editor's notion of
+// lines, which are newline-terminated.
+
+static const Command sDeleteCommands[][2] = {
+ // backward, forward
+ // CHARS
+ {Command::DeleteCharBackward, Command::DeleteCharForward},
+ // WORD_ENDS
+ {Command::DeleteWordBackward, Command::DeleteWordForward},
+ // WORDS
+ {Command::DeleteWordBackward, Command::DeleteWordForward},
+ // LINES
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // LINE_ENDS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // PARAGRAPH_ENDS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // PARAGRAPHS
+ {Command::DeleteToBeginningOfLine, Command::DeleteToEndOfLine},
+ // This deletes from the end of the previous word to the beginning of the
+ // next word, but only if the caret is not in a word.
+ // XXX need to implement in editor
+ {Command::DoNothing, Command::DoNothing} // WHITESPACE
+};
+
+static void delete_from_cursor_cb(GtkWidget* w, GtkDeleteType del_type,
+ gint count, gpointer user_data) {
+ g_signal_stop_emission_by_name(w, "delete_from_cursor");
+ if (count == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ bool forward = count > 0;
+
+ // Ignore GTK's Ctrl-K keybinding introduced in GTK 3.14 and removed in
+ // 3.18 if the user has custom bindings set. See bug 1176929.
+ if (del_type == GTK_DELETE_PARAGRAPH_ENDS && forward && GTK_IS_ENTRY(w) &&
+ !gtk_check_version(3, 14, 1) && gtk_check_version(3, 17, 9)) {
+ GtkStyleContext* context = gtk_widget_get_style_context(w);
+ GtkStateFlags flags = gtk_widget_get_state_flags(w);
+
+ GPtrArray* array;
+ gtk_style_context_get(context, flags, "gtk-key-bindings", &array, nullptr);
+ if (!array) return;
+ g_ptr_array_unref(array);
+ }
+
+ gHandled = true;
+ if (uint32_t(del_type) >= ArrayLength(sDeleteCommands)) {
+ // unsupported deletion type
+ return;
+ }
+
+ if (del_type == GTK_DELETE_WORDS) {
+ // This works like word_ends, except we first move the caret to the
+ // beginning/end of the current word.
+ if (forward) {
+ AddCommand(Command::WordNext);
+ AddCommand(Command::WordPrevious);
+ } else {
+ AddCommand(Command::WordPrevious);
+ AddCommand(Command::WordNext);
+ }
+ } else if (del_type == GTK_DELETE_DISPLAY_LINES ||
+ del_type == GTK_DELETE_PARAGRAPHS) {
+ // This works like display_line_ends, except we first move the caret to the
+ // beginning/end of the current line.
+ if (forward) {
+ AddCommand(Command::BeginLine);
+ } else {
+ AddCommand(Command::EndLine);
+ }
+ }
+
+ Command command = sDeleteCommands[del_type][forward];
+ if (command == Command::DoNothing) {
+ return;
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ AddCommand(command);
+ }
+}
+
+static const Command sMoveCommands[][2][2] = {
+ // non-extend { backward, forward }, extend { backward, forward }
+ // GTK differentiates between logical position, which is prev/next,
+ // and visual position, which is always left/right.
+ // We should fix this to work the same way for RTL text input.
+ {// LOGICAL_POSITIONS
+ {Command::CharPrevious, Command::CharNext},
+ {Command::SelectCharPrevious, Command::SelectCharNext}},
+ {// VISUAL_POSITIONS
+ {Command::CharPrevious, Command::CharNext},
+ {Command::SelectCharPrevious, Command::SelectCharNext}},
+ {// WORDS
+ {Command::WordPrevious, Command::WordNext},
+ {Command::SelectWordPrevious, Command::SelectWordNext}},
+ {// DISPLAY_LINES
+ {Command::LinePrevious, Command::LineNext},
+ {Command::SelectLinePrevious, Command::SelectLineNext}},
+ {// DISPLAY_LINE_ENDS
+ {Command::BeginLine, Command::EndLine},
+ {Command::SelectBeginLine, Command::SelectEndLine}},
+ {// PARAGRAPHS
+ {Command::LinePrevious, Command::LineNext},
+ {Command::SelectLinePrevious, Command::SelectLineNext}},
+ {// PARAGRAPH_ENDS
+ {Command::BeginLine, Command::EndLine},
+ {Command::SelectBeginLine, Command::SelectEndLine}},
+ {// PAGES
+ {Command::MovePageUp, Command::MovePageDown},
+ {Command::SelectPageUp, Command::SelectPageDown}},
+ {// BUFFER_ENDS
+ {Command::MoveTop, Command::MoveBottom},
+ {Command::SelectTop, Command::SelectBottom}},
+ {// HORIZONTAL_PAGES (unsupported)
+ {Command::DoNothing, Command::DoNothing},
+ {Command::DoNothing, Command::DoNothing}}};
+
+static void move_cursor_cb(GtkWidget* w, GtkMovementStep step, gint count,
+ gboolean extend_selection, gpointer user_data) {
+ g_signal_stop_emission_by_name(w, "move_cursor");
+ if (count == 0) {
+ // Nothing to do.
+ return;
+ }
+
+ gHandled = true;
+ bool forward = count > 0;
+ if (uint32_t(step) >= ArrayLength(sMoveCommands)) {
+ // unsupported movement type
+ return;
+ }
+
+ Command command = sMoveCommands[step][extend_selection][forward];
+ if (command == Command::DoNothing) {
+ return;
+ }
+
+ unsigned int absCount = Abs(count);
+ for (unsigned int i = 0; i < absCount; ++i) {
+ AddCommand(command);
+ }
+}
+
+static void paste_clipboard_cb(GtkWidget* w, gpointer user_data) {
+ AddCommand(Command::Paste);
+ g_signal_stop_emission_by_name(w, "paste_clipboard");
+ gHandled = true;
+}
+
+// GtkTextView-only signals
+static void select_all_cb(GtkWidget* aWidget, gboolean aSelect,
+ gpointer aUserData) {
+ // We don't support "Unselect All" command.
+ // Note that if we'd support it, `Ctrl-Shift-a` will be mapped to it and
+ // overrides open `about:addons` shortcut.
+ if (aSelect) {
+ AddCommand(Command::SelectAll);
+ }
+ g_signal_stop_emission_by_name(aWidget, "select_all");
+ // Although we prevent the default of `GtkTExtView` with
+ // `g_signal_stop_emission_by_name`, but `gHandled` is used for asserting
+ // if it does not match with the emptiness of the command array.
+ // Therefore, we should not set it to `true` if we don't add a command.
+ gHandled |= aSelect;
+}
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings* NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case NativeKeyBindingsType::SingleLineEditor:
+ if (!sInstanceForSingleLineEditor) {
+ sInstanceForSingleLineEditor = new NativeKeyBindings();
+ sInstanceForSingleLineEditor->Init(aType);
+ }
+ return sInstanceForSingleLineEditor;
+
+ default:
+ // fallback to multiline editor case in release build
+ MOZ_FALLTHROUGH_ASSERT("aType is invalid or not yet implemented");
+ case NativeKeyBindingsType::MultiLineEditor:
+ case NativeKeyBindingsType::RichTextEditor:
+ if (!sInstanceForMultiLineEditor) {
+ sInstanceForMultiLineEditor = new NativeKeyBindings();
+ sInstanceForMultiLineEditor->Init(aType);
+ }
+ return sInstanceForMultiLineEditor;
+ }
+}
+
+// static
+void NativeKeyBindings::Shutdown() {
+ delete sInstanceForSingleLineEditor;
+ sInstanceForSingleLineEditor = nullptr;
+ delete sInstanceForMultiLineEditor;
+ sInstanceForMultiLineEditor = nullptr;
+}
+
+void NativeKeyBindings::Init(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case NativeKeyBindingsType::SingleLineEditor:
+ mNativeTarget = gtk_entry_new();
+ break;
+ default:
+ mNativeTarget = gtk_text_view_new();
+ g_signal_connect(mNativeTarget, "select_all", G_CALLBACK(select_all_cb),
+ this);
+ break;
+ }
+
+ g_object_ref_sink(mNativeTarget);
+
+ g_signal_connect(mNativeTarget, "copy_clipboard",
+ G_CALLBACK(copy_clipboard_cb), this);
+ g_signal_connect(mNativeTarget, "cut_clipboard", G_CALLBACK(cut_clipboard_cb),
+ this);
+ g_signal_connect(mNativeTarget, "delete_from_cursor",
+ G_CALLBACK(delete_from_cursor_cb), this);
+ g_signal_connect(mNativeTarget, "move_cursor", G_CALLBACK(move_cursor_cb),
+ this);
+ g_signal_connect(mNativeTarget, "paste_clipboard",
+ G_CALLBACK(paste_clipboard_cb), this);
+}
+
+NativeKeyBindings::~NativeKeyBindings() {
+ gtk_widget_destroy(mNativeTarget);
+ g_object_unref(mNativeTarget);
+}
+
+void NativeKeyBindings::GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ const Maybe<WritingMode>& aWritingMode,
+ nsTArray<CommandInt>& aCommands) {
+ MOZ_ASSERT(!aEvent.mFlags.mIsSynthesizedForTests);
+ MOZ_ASSERT(aCommands.IsEmpty());
+
+ // It must be a DOM event dispached by chrome script.
+ if (!aEvent.mNativeKeyEvent) {
+ return;
+ }
+
+ guint keyval;
+ if (aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(aEvent.mCharCode);
+ } else if (aWritingMode.isSome() && aEvent.NeedsToRemapNavigationKey() &&
+ aWritingMode.ref().IsVertical()) {
+ // TODO: Use KeyNameIndex rather than legacy keyCode.
+ uint32_t remappedGeckoKeyCode =
+ aEvent.GetRemappedKeyCode(aWritingMode.ref());
+ switch (remappedGeckoKeyCode) {
+ case NS_VK_UP:
+ keyval = GDK_Up;
+ break;
+ case NS_VK_DOWN:
+ keyval = GDK_Down;
+ break;
+ case NS_VK_LEFT:
+ keyval = GDK_Left;
+ break;
+ case NS_VK_RIGHT:
+ keyval = GDK_Right;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Add a case for the new remapped key");
+ return;
+ }
+ } else {
+ keyval = static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->keyval;
+ }
+
+ if (GetEditCommandsInternal(aEvent, aCommands, keyval)) {
+ return;
+ }
+
+ for (uint32_t i = 0; i < aEvent.mAlternativeCharCodes.Length(); ++i) {
+ uint32_t ch = aEvent.IsShift()
+ ? aEvent.mAlternativeCharCodes[i].mShiftedCharCode
+ : aEvent.mAlternativeCharCodes[i].mUnshiftedCharCode;
+ if (ch && ch != aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(ch);
+ if (GetEditCommandsInternal(aEvent, aCommands, keyval)) {
+ return;
+ }
+ }
+ }
+
+ // If the key event does not cause any commands, and we're for single line
+ // editor, let's check whether the key combination is for "select-all" in
+ // GtkTextView because the signal is not supported by GtkEntry.
+ if (aCommands.IsEmpty() && this == sInstanceForSingleLineEditor &&
+ StaticPrefs::ui_key_use_select_all_in_single_line_editor()) {
+ if (NativeKeyBindings* bindingsForMultilineEditor =
+ GetInstance(NativeKeyBindingsType::MultiLineEditor)) {
+ bindingsForMultilineEditor->GetEditCommands(aEvent, aWritingMode,
+ aCommands);
+ if (aCommands.Length() == 1u &&
+ aCommands[0u] == static_cast<CommandInt>(Command::SelectAll)) {
+ return;
+ }
+ aCommands.Clear();
+ }
+ }
+
+ /*
+ gtk_bindings_activate_event is preferable, but it has unresolved bug:
+ http://bugzilla.gnome.org/show_bug.cgi?id=162726
+ The bug was already marked as FIXED. However, somebody reports that the
+ bug still exists.
+ Also gtk_bindings_activate may work with some non-shortcuts operations
+ (todo: check it). See bug 411005 and bug 406407.
+
+ Code, which should be used after fixing GNOME bug 162726:
+
+ gtk_bindings_activate_event(GTK_OBJECT(mNativeTarget),
+ static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent));
+ */
+}
+
+bool NativeKeyBindings::GetEditCommandsInternal(
+ const WidgetKeyboardEvent& aEvent, nsTArray<CommandInt>& aCommands,
+ guint aKeyval) {
+ guint modifiers = static_cast<GdkEventKey*>(aEvent.mNativeKeyEvent)->state;
+
+ gCurrentCommands = &aCommands;
+
+ gHandled = false;
+ gtk_bindings_activate(G_OBJECT(mNativeTarget), aKeyval,
+ GdkModifierType(modifiers));
+
+ gCurrentCommands = nullptr;
+
+ return gHandled;
+}
+
+// static
+void NativeKeyBindings::GetEditCommandsForTests(
+ NativeKeyBindingsType aType, const WidgetKeyboardEvent& aEvent,
+ const Maybe<WritingMode>& aWritingMode, nsTArray<CommandInt>& aCommands) {
+ MOZ_DIAGNOSTIC_ASSERT(aEvent.IsTrusted());
+
+ if (aEvent.IsAlt() || aEvent.IsMeta()) {
+ return;
+ }
+
+ static const size_t kBackward = 0;
+ static const size_t kForward = 1;
+ const size_t extentSelection = aEvent.IsShift() ? 1 : 0;
+ // https://github.com/GNOME/gtk/blob/1f141c19533f4b3f397c3959ade673ce243b6138/gtk/gtktext.c#L1289
+ // https://github.com/GNOME/gtk/blob/c5dd34344f0c660ceffffb3bf9da43c263db16e1/gtk/gtktextview.c#L1534
+ Command command = Command::DoNothing;
+ const KeyNameIndex remappedKeyNameIndex =
+ aWritingMode.isSome() ? aEvent.GetRemappedKeyNameIndex(aWritingMode.ref())
+ : aEvent.mKeyNameIndex;
+ switch (remappedKeyNameIndex) {
+ case KEY_NAME_INDEX_USE_STRING:
+ switch (aEvent.PseudoCharCode()) {
+ case 'a':
+ case 'A':
+ if (aEvent.IsControl()) {
+ command = Command::SelectAll;
+ }
+ break;
+ case 'c':
+ case 'C':
+ if (aEvent.IsControl() && !aEvent.IsShift()) {
+ command = Command::Copy;
+ }
+ break;
+ case 'u':
+ case 'U':
+ if (aType == NativeKeyBindingsType::SingleLineEditor &&
+ aEvent.IsControl() && !aEvent.IsShift()) {
+ command = sDeleteCommands[GTK_DELETE_PARAGRAPH_ENDS][kBackward];
+ }
+ break;
+ case 'v':
+ case 'V':
+ if (aEvent.IsControl() && !aEvent.IsShift()) {
+ command = Command::Paste;
+ }
+ break;
+ case 'x':
+ case 'X':
+ if (aEvent.IsControl() && !aEvent.IsShift()) {
+ command = Command::Cut;
+ }
+ break;
+ case '/':
+ if (aEvent.IsControl() && !aEvent.IsShift()) {
+ command = Command::SelectAll;
+ }
+ break;
+ default:
+ break;
+ }
+ break;
+ case KEY_NAME_INDEX_Insert:
+ if (aEvent.IsControl() && !aEvent.IsShift()) {
+ command = Command::Copy;
+ } else if (aEvent.IsShift() && !aEvent.IsControl()) {
+ command = Command::Paste;
+ }
+ break;
+ case KEY_NAME_INDEX_Delete:
+ if (aEvent.IsShift()) {
+ command = Command::Cut;
+ break;
+ }
+ [[fallthrough]];
+ case KEY_NAME_INDEX_Backspace: {
+ const size_t direction =
+ remappedKeyNameIndex == KEY_NAME_INDEX_Delete ? kForward : kBackward;
+ const GtkDeleteType amount =
+ aEvent.IsControl() && aEvent.IsShift()
+ ? GTK_DELETE_PARAGRAPH_ENDS
+ // FYI: Shift key for Backspace is ignored to help mis-typing.
+ : (aEvent.IsControl() ? GTK_DELETE_WORD_ENDS : GTK_DELETE_CHARS);
+ command = sDeleteCommands[amount][direction];
+ break;
+ }
+ case KEY_NAME_INDEX_ArrowLeft:
+ case KEY_NAME_INDEX_ArrowRight: {
+ const size_t direction = remappedKeyNameIndex == KEY_NAME_INDEX_ArrowRight
+ ? kForward
+ : kBackward;
+ const GtkMovementStep amount = aEvent.IsControl()
+ ? GTK_MOVEMENT_WORDS
+ : GTK_MOVEMENT_VISUAL_POSITIONS;
+ command = sMoveCommands[amount][extentSelection][direction];
+ break;
+ }
+ case KEY_NAME_INDEX_ArrowUp:
+ case KEY_NAME_INDEX_ArrowDown: {
+ const size_t direction = remappedKeyNameIndex == KEY_NAME_INDEX_ArrowDown
+ ? kForward
+ : kBackward;
+ const GtkMovementStep amount = aEvent.IsControl()
+ ? GTK_MOVEMENT_PARAGRAPHS
+ : GTK_MOVEMENT_DISPLAY_LINES;
+ command = sMoveCommands[amount][extentSelection][direction];
+ break;
+ }
+ case KEY_NAME_INDEX_Home:
+ case KEY_NAME_INDEX_End: {
+ const size_t direction =
+ remappedKeyNameIndex == KEY_NAME_INDEX_End ? kForward : kBackward;
+ const GtkMovementStep amount = aEvent.IsControl()
+ ? GTK_MOVEMENT_BUFFER_ENDS
+ : GTK_MOVEMENT_DISPLAY_LINE_ENDS;
+ command = sMoveCommands[amount][extentSelection][direction];
+ break;
+ }
+ case KEY_NAME_INDEX_PageUp:
+ case KEY_NAME_INDEX_PageDown: {
+ const size_t direction = remappedKeyNameIndex == KEY_NAME_INDEX_PageDown
+ ? kForward
+ : kBackward;
+ const GtkMovementStep amount = aEvent.IsControl()
+ ? GTK_MOVEMENT_HORIZONTAL_PAGES
+ : GTK_MOVEMENT_PAGES;
+ command = sMoveCommands[amount][extentSelection][direction];
+ break;
+ }
+ default:
+ break;
+ }
+ if (command != Command::DoNothing) {
+ aCommands.AppendElement(static_cast<CommandInt>(command));
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h
new file mode 100644
index 0000000000..1d0c528621
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NativeKeyBindings_h
+#define NativeKeyBindings_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIWidget.h"
+
+#include <glib.h> // for guint
+
+using GtkWidget = struct _GtkWidget;
+
+namespace mozilla {
+enum class NativeKeyBindingsType : uint8_t;
+
+class WritingMode;
+template <typename T>
+class Maybe;
+
+namespace widget {
+
+class NativeKeyBindings final {
+ public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ /**
+ * GetEditCommandsForTests() returns commands performed in native widget
+ * in typical environment. I.e., this does NOT refer customized shortcut
+ * key mappings of the environment.
+ */
+ static void GetEditCommandsForTests(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ const Maybe<WritingMode>& aWritingMode,
+ nsTArray<CommandInt>& aCommands);
+
+ void Init(NativeKeyBindingsType aType);
+
+ void GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ const Maybe<WritingMode>& aWritingMode,
+ nsTArray<CommandInt>& aCommands);
+
+ private:
+ ~NativeKeyBindings();
+
+ bool GetEditCommandsInternal(const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands, guint aKeyval);
+
+ GtkWidget* mNativeTarget;
+
+ static NativeKeyBindings* sInstanceForSingleLineEditor;
+ static NativeKeyBindings* sInstanceForMultiLineEditor;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // NativeKeyBindings_h
diff --git a/widget/gtk/NativeMenuGtk.cpp b/widget/gtk/NativeMenuGtk.cpp
new file mode 100644
index 0000000000..9d413d475e
--- /dev/null
+++ b/widget/gtk/NativeMenuGtk.cpp
@@ -0,0 +1,424 @@
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "NativeMenuGtk.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/DocumentInlines.h"
+#include "mozilla/dom/XULCommandEvent.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "mozilla/EventDispatcher.h"
+#include "nsPresContext.h"
+#include "nsIWidget.h"
+#include "nsWindow.h"
+#include "nsStubMutationObserver.h"
+#include "mozilla/dom/Element.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+
+namespace mozilla::widget {
+
+using GtkMenuPopupAtRect = void (*)(GtkMenu* menu, GdkWindow* rect_window,
+ const GdkRectangle* rect,
+ GdkGravity rect_anchor,
+ GdkGravity menu_anchor,
+ const GdkEvent* trigger_event);
+
+static bool IsDisabled(const dom::Element& aElement) {
+ return aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::disabled,
+ nsGkAtoms::_true, eCaseMatters) ||
+ aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::hidden,
+ nsGkAtoms::_true, eCaseMatters);
+}
+static bool NodeIsRelevant(const nsINode& aNode) {
+ return aNode.IsAnyOfXULElements(nsGkAtoms::menu, nsGkAtoms::menuseparator,
+ nsGkAtoms::menuitem, nsGkAtoms::menugroup);
+}
+
+// If this is a radio / checkbox menuitem, get the current value.
+static Maybe<bool> GetChecked(const dom::Element& aMenuItem) {
+ static dom::Element::AttrValuesArray strings[] = {nsGkAtoms::checkbox,
+ nsGkAtoms::radio, nullptr};
+ switch (aMenuItem.FindAttrValueIn(kNameSpaceID_None, nsGkAtoms::type, strings,
+ eCaseMatters)) {
+ case 0:
+ break;
+ case 1:
+ break;
+ default:
+ return Nothing();
+ }
+
+ return Some(aMenuItem.AttrValueIs(kNameSpaceID_None, nsGkAtoms::checked,
+ nsGkAtoms::_true, eCaseMatters));
+}
+
+struct Actions {
+ RefPtr<GSimpleActionGroup> mGroup;
+ size_t mNextActionIndex = 0;
+
+ nsPrintfCString Register(const dom::Element&, bool aForSubmenu);
+ void Clear();
+};
+
+static MOZ_CAN_RUN_SCRIPT void ActivateItem(dom::Element& aElement) {
+ if (Maybe<bool> checked = GetChecked(aElement)) {
+ if (!aElement.AttrValueIs(kNameSpaceID_None, nsGkAtoms::autocheck,
+ nsGkAtoms::_false, eCaseMatters)) {
+ bool newValue = !*checked;
+ if (newValue) {
+ aElement.SetAttr(kNameSpaceID_None, nsGkAtoms::checked, u"true"_ns,
+ true);
+ } else {
+ aElement.UnsetAttr(kNameSpaceID_None, nsGkAtoms::checked, true);
+ }
+ }
+ }
+
+ RefPtr doc = aElement.OwnerDoc();
+ RefPtr event = new dom::XULCommandEvent(doc, doc->GetPresContext(), nullptr);
+ IgnoredErrorResult rv;
+ event->InitCommandEvent(u"command"_ns, true, true,
+ nsGlobalWindowInner::Cast(doc->GetInnerWindow()), 0,
+ /* ctrlKey = */ false, /* altKey = */ false,
+ /* shiftKey = */ false, /* cmdKey = */ false,
+ /* button = */ MouseButton::ePrimary, nullptr, 0, rv);
+ if (MOZ_UNLIKELY(rv.Failed())) {
+ return;
+ }
+ aElement.DispatchEvent(*event);
+}
+
+static MOZ_CAN_RUN_SCRIPT void ActivateSignal(GSimpleAction* aAction,
+ GVariant* aParam,
+ gpointer aUserData) {
+ RefPtr element = static_cast<dom::Element*>(aUserData);
+ ActivateItem(*element);
+}
+
+static MOZ_CAN_RUN_SCRIPT void FireEvent(dom::Element* aTarget,
+ EventMessage aPopupMessage) {
+ nsEventStatus status = nsEventStatus_eIgnore;
+ WidgetMouseEvent event(true, aPopupMessage, nullptr, WidgetMouseEvent::eReal);
+ EventDispatcher::Dispatch(aTarget, nullptr, &event, nullptr, &status);
+}
+
+static MOZ_CAN_RUN_SCRIPT void ChangeStateSignal(GSimpleAction* aAction,
+ GVariant* aParam,
+ gpointer aUserData) {
+ // TODO: Fire events when safe. These run at a bad time for now.
+ static constexpr bool kEnabled = false;
+ if (!kEnabled) {
+ return;
+ }
+ const bool open = g_variant_get_boolean(aParam);
+ RefPtr popup = static_cast<dom::Element*>(aUserData);
+ if (open) {
+ FireEvent(popup, eXULPopupShowing);
+ FireEvent(popup, eXULPopupShown);
+ } else {
+ FireEvent(popup, eXULPopupHiding);
+ FireEvent(popup, eXULPopupHidden);
+ }
+}
+
+nsPrintfCString Actions::Register(const dom::Element& aMenuItem,
+ bool aForSubmenu) {
+ nsPrintfCString actionName("item-%zu", mNextActionIndex++);
+ Maybe<bool> paramValue = aForSubmenu ? Some(false) : GetChecked(aMenuItem);
+ RefPtr<GSimpleAction> action;
+ if (paramValue) {
+ action = dont_AddRef(g_simple_action_new_stateful(
+ actionName.get(), nullptr, g_variant_new_boolean(*paramValue)));
+ } else {
+ action = dont_AddRef(g_simple_action_new(actionName.get(), nullptr));
+ }
+ if (aForSubmenu) {
+ g_signal_connect(action, "change-state", G_CALLBACK(ChangeStateSignal),
+ gpointer(&aMenuItem));
+ } else {
+ g_signal_connect(action, "activate", G_CALLBACK(ActivateSignal),
+ gpointer(&aMenuItem));
+ }
+ g_action_map_add_action(G_ACTION_MAP(mGroup.get()), G_ACTION(action.get()));
+ return actionName;
+}
+
+void Actions::Clear() {
+ for (size_t i = 0; i < mNextActionIndex; ++i) {
+ g_action_map_remove_action(G_ACTION_MAP(mGroup.get()),
+ nsPrintfCString("item-%zu", i).get());
+ }
+ mNextActionIndex = 0;
+}
+
+class MenuModel final : public nsStubMutationObserver {
+ NS_DECL_ISUPPORTS
+
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTREMOVED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTINSERTED
+ NS_DECL_NSIMUTATIONOBSERVER_CONTENTAPPENDED
+ NS_DECL_NSIMUTATIONOBSERVER_ATTRIBUTECHANGED
+
+ public:
+ explicit MenuModel(dom::Element* aElement) : mElement(aElement) {
+ mElement->AddMutationObserver(this);
+ mGMenu = dont_AddRef(g_menu_new());
+ mActions.mGroup = dont_AddRef(g_simple_action_group_new());
+ }
+
+ GMenuModel* GetModel() { return G_MENU_MODEL(mGMenu.get()); }
+ GActionGroup* GetActionGroup() {
+ return G_ACTION_GROUP(mActions.mGroup.get());
+ }
+
+ dom::Element* Element() { return mElement; }
+
+ void RecomputeModelIfNeeded();
+
+ bool IsShowing() { return mPoppedUp; }
+ void WillShow() {
+ mPoppedUp = true;
+ RecomputeModelIfNeeded();
+ }
+ void DidHide() { mPoppedUp = false; }
+
+ private:
+ virtual ~MenuModel() { mElement->RemoveMutationObserver(this); }
+
+ void DirtyModel() {
+ mDirty = true;
+ if (mPoppedUp) {
+ RecomputeModelIfNeeded();
+ }
+ }
+
+ RefPtr<dom::Element> mElement;
+ RefPtr<GMenu> mGMenu;
+ Actions mActions;
+ bool mDirty = true;
+ bool mPoppedUp = false;
+};
+
+NS_IMPL_ISUPPORTS(MenuModel, nsIMutationObserver)
+
+void MenuModel::ContentRemoved(nsIContent* aChild, nsIContent*) {
+ if (NodeIsRelevant(*aChild)) {
+ DirtyModel();
+ }
+}
+
+void MenuModel::ContentInserted(nsIContent* aChild) {
+ if (NodeIsRelevant(*aChild)) {
+ DirtyModel();
+ }
+}
+
+void MenuModel::ContentAppended(nsIContent* aChild) {
+ if (NodeIsRelevant(*aChild)) {
+ DirtyModel();
+ }
+}
+
+void MenuModel::AttributeChanged(dom::Element* aElement, int32_t aNameSpaceID,
+ nsAtom* aAttribute, int32_t aModType,
+ const nsAttrValue* aOldValue) {
+ if (NodeIsRelevant(*aElement) &&
+ (aAttribute == nsGkAtoms::label || aAttribute == nsGkAtoms::aria_label ||
+ aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::hidden)) {
+ DirtyModel();
+ }
+}
+
+static const dom::Element* GetMenuPopupChild(const dom::Element& aElement) {
+ for (const nsIContent* child = aElement.GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::menupopup)) {
+ return child->AsElement();
+ }
+ }
+ return nullptr;
+}
+
+static void RecomputeModelFor(GMenu* aMenu, Actions& aActions,
+ const dom::Element& aElement) {
+ RefPtr<GMenu> sectionMenu;
+ auto FlushSectionMenu = [&] {
+ if (sectionMenu) {
+ g_menu_append_section(aMenu, nullptr, G_MENU_MODEL(sectionMenu.get()));
+ sectionMenu = nullptr;
+ }
+ };
+
+ for (const nsIContent* child = aElement.GetFirstChild(); child;
+ child = child->GetNextSibling()) {
+ if (child->IsXULElement(nsGkAtoms::menuitem) &&
+ !IsDisabled(*child->AsElement())) {
+ nsAutoString label;
+ child->AsElement()->GetAttr(nsGkAtoms::label, label);
+ if (label.IsEmpty()) {
+ child->AsElement()->GetAttr(nsGkAtoms::aria_label, label);
+ }
+ nsPrintfCString actionName(
+ "menu.%s",
+ aActions.Register(*child->AsElement(), /* aForSubmenu = */ false)
+ .get());
+ g_menu_append(sectionMenu ? sectionMenu.get() : aMenu,
+ NS_ConvertUTF16toUTF8(label).get(), actionName.get());
+ continue;
+ }
+ if (child->IsXULElement(nsGkAtoms::menuseparator)) {
+ FlushSectionMenu();
+ sectionMenu = dont_AddRef(g_menu_new());
+ continue;
+ }
+ if (child->IsXULElement(nsGkAtoms::menugroup)) {
+ FlushSectionMenu();
+ sectionMenu = dont_AddRef(g_menu_new());
+ RecomputeModelFor(sectionMenu, aActions, *child->AsElement());
+ FlushSectionMenu();
+ continue;
+ }
+ if (child->IsXULElement(nsGkAtoms::menu) &&
+ !IsDisabled(*child->AsElement())) {
+ if (const auto* popup = GetMenuPopupChild(*child->AsElement())) {
+ RefPtr<GMenu> submenu = dont_AddRef(g_menu_new());
+ RecomputeModelFor(submenu, aActions, *popup);
+ nsAutoString label;
+ child->AsElement()->GetAttr(nsGkAtoms::label, label);
+ RefPtr<GMenuItem> submenuItem = dont_AddRef(g_menu_item_new_submenu(
+ NS_ConvertUTF16toUTF8(label).get(), G_MENU_MODEL(submenu.get())));
+ nsPrintfCString actionName(
+ "menu.%s",
+ aActions.Register(*popup, /* aForSubmenu = */ true).get());
+ g_menu_item_set_attribute_value(submenuItem.get(), "submenu-action",
+ g_variant_new_string(actionName.get()));
+ g_menu_append_item(sectionMenu ? sectionMenu.get() : aMenu,
+ submenuItem.get());
+ }
+ }
+ }
+
+ FlushSectionMenu();
+}
+
+void MenuModel::RecomputeModelIfNeeded() {
+ if (!mDirty) {
+ return;
+ }
+ mActions.Clear();
+ g_menu_remove_all(mGMenu.get());
+ RecomputeModelFor(mGMenu.get(), mActions, *mElement);
+}
+
+static GtkMenuPopupAtRect GetPopupAtRectFn() {
+ static GtkMenuPopupAtRect sFunc =
+ (GtkMenuPopupAtRect)dlsym(RTLD_DEFAULT, "gtk_menu_popup_at_rect");
+ return sFunc;
+}
+
+bool NativeMenuGtk::CanUse() {
+ return StaticPrefs::widget_gtk_native_context_menus() && GetPopupAtRectFn();
+}
+
+void NativeMenuGtk::FireEvent(EventMessage aPopupMessage) {
+ RefPtr target = Element();
+ widget::FireEvent(target, aPopupMessage);
+}
+
+#define METHOD_SIGNAL(name_) \
+ static MOZ_CAN_RUN_SCRIPT_BOUNDARY void On##name_##Signal( \
+ GtkWidget* widget, gpointer user_data) { \
+ RefPtr menu = static_cast<NativeMenuGtk*>(user_data); \
+ return menu->On##name_(); \
+ }
+
+METHOD_SIGNAL(Unmap);
+
+#undef METHOD_SIGNAL
+
+NativeMenuGtk::NativeMenuGtk(dom::Element* aElement)
+ : mMenuModel(MakeRefPtr<MenuModel>(aElement)) {
+ // Floating, so no need to dont_AddRef.
+ mNativeMenu = gtk_menu_new_from_model(mMenuModel->GetModel());
+ gtk_widget_insert_action_group(mNativeMenu.get(), "menu",
+ mMenuModel->GetActionGroup());
+ g_signal_connect(mNativeMenu, "unmap", G_CALLBACK(OnUnmapSignal), this);
+}
+
+NativeMenuGtk::~NativeMenuGtk() {
+ g_signal_handlers_disconnect_by_data(mNativeMenu, this);
+}
+
+RefPtr<dom::Element> NativeMenuGtk::Element() { return mMenuModel->Element(); }
+
+void NativeMenuGtk::ShowAsContextMenu(nsIFrame* aClickedFrame,
+ const CSSIntPoint& aPosition,
+ bool aIsContextMenu) {
+ if (mMenuModel->IsShowing()) {
+ return;
+ }
+ RefPtr<nsIWidget> widget = aClickedFrame->PresContext()->GetRootWidget();
+ if (NS_WARN_IF(!widget)) {
+ // XXX Do we need to close menus here?
+ return;
+ }
+ auto* win = static_cast<GdkWindow*>(widget->GetNativeData(NS_NATIVE_WINDOW));
+ if (NS_WARN_IF(!win)) {
+ return;
+ }
+
+ auto* geckoWin = static_cast<nsWindow*>(widget.get());
+ // The position needs to be relative to our window.
+ auto pos = (aPosition * aClickedFrame->PresContext()->CSSToDevPixelScale()) -
+ geckoWin->WidgetToScreenOffset();
+ auto gdkPos = geckoWin->DevicePixelsToGdkPointRoundDown(
+ LayoutDeviceIntPoint::Round(pos));
+
+ mMenuModel->WillShow();
+ const GdkRectangle rect = {gdkPos.x, gdkPos.y, 1, 1};
+ auto openFn = GetPopupAtRectFn();
+ openFn(GTK_MENU(mNativeMenu.get()), win, &rect, GDK_GRAVITY_NORTH_WEST,
+ GDK_GRAVITY_NORTH_WEST, GetLastMousePressEvent());
+
+ RefPtr pin{this};
+ FireEvent(eXULPopupShown);
+}
+
+bool NativeMenuGtk::Close() {
+ if (!mMenuModel->IsShowing()) {
+ return false;
+ }
+ gtk_menu_popdown(GTK_MENU(mNativeMenu.get()));
+ return true;
+}
+
+void NativeMenuGtk::OnUnmap() {
+ FireEvent(eXULPopupHiding);
+
+ mMenuModel->DidHide();
+
+ FireEvent(eXULPopupHidden);
+
+ for (NativeMenu::Observer* observer : mObservers.Clone()) {
+ observer->OnNativeMenuClosed();
+ }
+}
+
+void NativeMenuGtk::ActivateItem(dom::Element* aItemElement, Modifiers,
+ int16_t aButton, ErrorResult&) {
+ // TODO: For testing only.
+}
+
+void NativeMenuGtk::OpenSubmenu(dom::Element*) {
+ // TODO: For testing mostly.
+}
+
+void NativeMenuGtk::CloseSubmenu(dom::Element*) {
+ // TODO: For testing mostly.
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/NativeMenuGtk.h b/widget/gtk/NativeMenuGtk.h
new file mode 100644
index 0000000000..3f1f3213c1
--- /dev/null
+++ b/widget/gtk/NativeMenuGtk.h
@@ -0,0 +1,64 @@
+
+/* -*- Mode: c++; tab-width: 2; indent-tabs-mode: nil; -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_NativeMenuGtk_h
+#define mozilla_widget_NativeMenuGtk_h
+
+#include "mozilla/widget/NativeMenu.h"
+#include "mozilla/EventForwards.h"
+#include "GRefPtr.h"
+
+namespace mozilla {
+
+namespace dom {
+class Element;
+}
+
+namespace widget {
+
+class MenuModel;
+
+class NativeMenuGtk : public NativeMenu {
+ public:
+ // Whether we can use native menu popups on GTK.
+ static bool CanUse();
+
+ explicit NativeMenuGtk(dom::Element* aElement);
+
+ // NativeMenu
+ MOZ_CAN_RUN_SCRIPT_BOUNDARY void ShowAsContextMenu(
+ nsIFrame* aClickedFrame, const CSSIntPoint& aPosition,
+ bool aIsContextMenu) override;
+ bool Close() override;
+ void ActivateItem(dom::Element* aItemElement, Modifiers aModifiers,
+ int16_t aButton, ErrorResult& aRv) override;
+ void OpenSubmenu(dom::Element* aMenuElement) override;
+ void CloseSubmenu(dom::Element* aMenuElement) override;
+ RefPtr<dom::Element> Element() override;
+ void AddObserver(NativeMenu::Observer* aObserver) override {
+ mObservers.AppendElement(aObserver);
+ }
+ void RemoveObserver(NativeMenu::Observer* aObserver) override {
+ mObservers.RemoveElement(aObserver);
+ }
+
+ MOZ_CAN_RUN_SCRIPT void OnUnmap();
+
+ protected:
+ virtual ~NativeMenuGtk();
+
+ MOZ_CAN_RUN_SCRIPT void FireEvent(EventMessage);
+
+ bool mPoppedUp = false;
+ RefPtr<GtkWidget> mNativeMenu;
+ RefPtr<MenuModel> mMenuModel;
+ nsTArray<NativeMenu::Observer*> mObservers;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif
diff --git a/widget/gtk/NativeMenuSupport.cpp b/widget/gtk/NativeMenuSupport.cpp
new file mode 100644
index 0000000000..4360867fff
--- /dev/null
+++ b/widget/gtk/NativeMenuSupport.cpp
@@ -0,0 +1,29 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/widget/NativeMenuSupport.h"
+
+#include "MainThreadUtils.h"
+#include "NativeMenuGtk.h"
+
+namespace mozilla::widget {
+
+void NativeMenuSupport::CreateNativeMenuBar(nsIWidget* aParent,
+ dom::Element* aMenuBarElement) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "Attempting to create native menu bar on wrong thread!");
+ // TODO
+}
+
+already_AddRefed<NativeMenu> NativeMenuSupport::CreateNativeContextMenu(
+ dom::Element* aPopup) {
+ return MakeAndAddRef<NativeMenuGtk>(aPopup);
+}
+
+bool NativeMenuSupport::ShouldUseNativeContextMenus() {
+ return NativeMenuGtk::CanUse();
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/PCompositorWidget.ipdl b/widget/gtk/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..d554a33144
--- /dev/null
+++ b/widget/gtk/PCompositorWidget.ipdl
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include protocol PCompositorBridge;
+
+include "mozilla/GfxMessageUtils.h";
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+[ManualDealloc, ChildImpl=virtual, ParentImpl=virtual]
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ async __delete__();
+
+ async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize);
+ async DisableRendering();
+ async EnableRendering(uintptr_t aXWindow, bool aShaped);
+
+child:
+
+ async ObserveVsync();
+ async UnobserveVsync();
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/PlatformWidgetTypes.ipdlh b/widget/gtk/PlatformWidgetTypes.ipdlh
new file mode 100644
index 0000000000..c85da1711e
--- /dev/null
+++ b/widget/gtk/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=99: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+include HeadlessWidgetTypes;
+
+include "mozilla/GfxMessageUtils.h";
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+struct GtkCompositorWidgetInitData
+{
+ uintptr_t XWindow;
+ nsCString XDisplayString;
+ bool Shaped;
+ bool IsX11Display;
+
+ LayoutDeviceIntSize InitialClientSize;
+};
+
+union CompositorWidgetInitData
+{
+ GtkCompositorWidgetInitData;
+ HeadlessCompositorWidgetInitData;
+};
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/ScreenHelperGTK.cpp b/widget/gtk/ScreenHelperGTK.cpp
new file mode 100644
index 0000000000..60be234c60
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.cpp
@@ -0,0 +1,306 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "ScreenHelperGTK.h"
+
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include <X11/Xlib.h>
+# include "X11UndefineNone.h"
+#endif /* MOZ_X11 */
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif /* MOZ_WAYLAND */
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+
+#include "gfxPlatformGtk.h"
+#include "mozilla/dom/DOMTypes.h"
+#include "mozilla/Logging.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "nsGtkUtils.h"
+#include "nsTArray.h"
+#include "nsWindow.h"
+
+struct wl_registry;
+
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+namespace mozilla::widget {
+
+#ifdef MOZ_LOGGING
+static LazyLogModule sScreenLog("WidgetScreen");
+# define LOG_SCREEN(...) MOZ_LOG(sScreenLog, LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOG_SCREEN(...)
+#endif /* MOZ_LOGGING */
+
+using GdkMonitor = struct _GdkMonitor;
+
+class ScreenGetterGtk final {
+ public:
+ ScreenGetterGtk() = default;
+ ~ScreenGetterGtk();
+
+ void Init();
+
+#ifdef MOZ_X11
+ Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
+#endif
+
+ // For internal use from signal callback functions
+ void RefreshScreens();
+
+ private:
+ GdkWindow* mRootWindow = nullptr;
+#ifdef MOZ_X11
+ Atom mNetWorkareaAtom = 0;
+#endif
+};
+
+static GdkMonitor* GdkDisplayGetMonitor(GdkDisplay* aDisplay, int aMonitorNum) {
+ static auto s_gdk_display_get_monitor = (GdkMonitor * (*)(GdkDisplay*, int))
+ dlsym(RTLD_DEFAULT, "gdk_display_get_monitor");
+ if (!s_gdk_display_get_monitor) {
+ return nullptr;
+ }
+ return s_gdk_display_get_monitor(aDisplay, aMonitorNum);
+}
+
+RefPtr<Screen> ScreenHelperGTK::GetScreenForWindow(nsWindow* aWindow) {
+ LOG_SCREEN("GetScreenForWindow() [%p]", aWindow);
+
+ static auto s_gdk_display_get_monitor_at_window =
+ (GdkMonitor * (*)(GdkDisplay*, GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_display_get_monitor_at_window");
+
+ if (!s_gdk_display_get_monitor_at_window) {
+ LOG_SCREEN(" failed, missing Gtk helpers");
+ return nullptr;
+ }
+
+ GdkWindow* gdkWindow = aWindow->GetToplevelGdkWindow();
+ if (!gdkWindow) {
+ LOG_SCREEN(" failed, can't get GdkWindow");
+ return nullptr;
+ }
+
+ GdkDisplay* display = gdk_display_get_default();
+ GdkMonitor* monitor = s_gdk_display_get_monitor_at_window(display, gdkWindow);
+ if (!monitor) {
+ LOG_SCREEN(" failed, can't get monitor for GdkWindow");
+ return nullptr;
+ }
+
+ int index = -1;
+ while (GdkMonitor* m = GdkDisplayGetMonitor(display, ++index)) {
+ if (m == monitor) {
+ return ScreenManager::GetSingleton().CurrentScreenList().SafeElementAt(
+ index);
+ }
+ }
+
+ LOG_SCREEN(" Couldn't find monitor %p", monitor);
+ return nullptr;
+}
+
+static StaticAutoPtr<ScreenGetterGtk> gScreenGetter;
+
+static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
+ LOG_SCREEN("Received monitors-changed event");
+ auto* self = static_cast<ScreenGetterGtk*>(aClosure);
+ self->RefreshScreens();
+}
+
+static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec,
+ ScreenGetterGtk* self) {
+ self->RefreshScreens();
+}
+
+static GdkFilterReturn root_window_event_filter(GdkXEvent* aGdkXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aClosure) {
+#ifdef MOZ_X11
+ ScreenGetterGtk* self = static_cast<ScreenGetterGtk*>(aClosure);
+ XEvent* xevent = static_cast<XEvent*>(aGdkXEvent);
+
+ switch (xevent->type) {
+ case PropertyNotify: {
+ XPropertyEvent* propertyEvent = &xevent->xproperty;
+ if (propertyEvent->atom == self->NetWorkareaAtom()) {
+ LOG_SCREEN("Work area size changed");
+ self->RefreshScreens();
+ }
+ } break;
+ default:
+ break;
+ }
+#endif
+
+ return GDK_FILTER_CONTINUE;
+}
+
+void ScreenGetterGtk::Init() {
+ LOG_SCREEN("ScreenGetterGtk created");
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ if (!defaultScreen) {
+ // Sometimes we don't initial X (e.g., xpcshell)
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("defaultScreen is nullptr, running headless"));
+ return;
+ }
+ mRootWindow = gdk_get_default_root_window();
+ MOZ_ASSERT(mRootWindow);
+
+ g_object_ref(mRootWindow);
+
+ // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
+ gdk_window_set_events(mRootWindow,
+ GdkEventMask(gdk_window_get_events(mRootWindow) |
+ GDK_PROPERTY_CHANGE_MASK));
+
+ g_signal_connect(defaultScreen, "monitors-changed",
+ G_CALLBACK(monitors_changed), this);
+ // Use _after to ensure this callback is run after gfxPlatformGtk.cpp's
+ // handler.
+ g_signal_connect_after(defaultScreen, "notify::resolution",
+ G_CALLBACK(screen_resolution_changed), this);
+#ifdef MOZ_X11
+ gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
+ if (GdkIsX11Display()) {
+ mNetWorkareaAtom = XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow),
+ "_NET_WORKAREA", X11False);
+ }
+#endif
+ RefreshScreens();
+}
+
+ScreenGetterGtk::~ScreenGetterGtk() {
+ if (mRootWindow) {
+ g_signal_handlers_disconnect_by_data(gdk_screen_get_default(), this);
+
+ gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
+ g_object_unref(mRootWindow);
+ mRootWindow = nullptr;
+ }
+}
+
+static uint32_t GetGTKPixelDepth() {
+ GdkVisual* visual = gdk_screen_get_system_visual(gdk_screen_get_default());
+ return gdk_visual_get_depth(visual);
+}
+
+static already_AddRefed<Screen> MakeScreenGtk(GdkScreen* aScreen,
+ gint aMonitorNum) {
+ gint gdkScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor(aMonitorNum);
+
+ // gdk_screen_get_monitor_geometry / workarea returns application pixels
+ // (desktop pixels), so we need to convert it to device pixels with
+ // gdkScaleFactor.
+ gint geometryScaleFactor = gdkScaleFactor;
+
+ gint refreshRate = [&] {
+ // Since gtk 3.22
+ static auto s_gdk_monitor_get_refresh_rate = (int (*)(GdkMonitor*))dlsym(
+ RTLD_DEFAULT, "gdk_monitor_get_refresh_rate");
+
+ if (!s_gdk_monitor_get_refresh_rate) {
+ return 0;
+ }
+ GdkMonitor* monitor =
+ GdkDisplayGetMonitor(gdk_display_get_default(), aMonitorNum);
+ if (!monitor) {
+ return 0;
+ }
+ // Convert to Hz.
+ return NSToIntRound(s_gdk_monitor_get_refresh_rate(monitor) / 1000.0f);
+ }();
+
+ GdkRectangle workarea;
+ gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
+ LayoutDeviceIntRect availRect(workarea.x * geometryScaleFactor,
+ workarea.y * geometryScaleFactor,
+ workarea.width * geometryScaleFactor,
+ workarea.height * geometryScaleFactor);
+ LayoutDeviceIntRect rect;
+ DesktopToLayoutDeviceScale contentsScale(1.0);
+ if (GdkIsX11Display()) {
+ GdkRectangle monitor;
+ gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
+ rect = LayoutDeviceIntRect(monitor.x * geometryScaleFactor,
+ monitor.y * geometryScaleFactor,
+ monitor.width * geometryScaleFactor,
+ monitor.height * geometryScaleFactor);
+ } else {
+ // Don't report screen shift in Wayland, see bug 1795066.
+ availRect.MoveTo(0, 0);
+ // We use Gtk workarea on Wayland as it matches our needs (Bug 1732682).
+ rect = availRect;
+ // Use per-monitor scaling factor in Wayland.
+ contentsScale.scale = gdkScaleFactor;
+ }
+
+ uint32_t pixelDepth = GetGTKPixelDepth();
+ if (pixelDepth == 32) {
+ // If a device uses 32 bits per pixel, it's still only using 8 bits
+ // per color component, which is what our callers want to know.
+ // (Some devices report 32 and some devices report 24.)
+ pixelDepth = 24;
+ }
+
+ CSSToLayoutDeviceScale defaultCssScale(gdkScaleFactor);
+
+ float dpi = 96.0f;
+ gint heightMM = gdk_screen_get_monitor_height_mm(aScreen, aMonitorNum);
+ if (heightMM > 0) {
+ dpi = rect.height / (heightMM / MM_PER_INCH_FLOAT);
+ }
+
+ LOG_SCREEN(
+ "New monitor %d size [%d,%d -> %d x %d] depth %d scale %f CssScale %f "
+ "DPI %f refresh %d ]",
+ aMonitorNum, rect.x, rect.y, rect.width, rect.height, pixelDepth,
+ contentsScale.scale, defaultCssScale.scale, dpi, refreshRate);
+ return MakeAndAddRef<Screen>(rect, availRect, pixelDepth, pixelDepth,
+ refreshRate, contentsScale, defaultCssScale, dpi,
+ Screen::IsPseudoDisplay::No);
+}
+
+void ScreenGetterGtk::RefreshScreens() {
+ LOG_SCREEN("ScreenGetterGtk::RefreshScreens()");
+ AutoTArray<RefPtr<Screen>, 4> screenList;
+
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
+ LOG_SCREEN("GDK reports %d screens", numScreens);
+
+ for (gint i = 0; i < numScreens; i++) {
+ screenList.AppendElement(MakeScreenGtk(defaultScreen, i));
+ }
+
+ ScreenManager::Refresh(std::move(screenList));
+}
+
+gint ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum) {
+ GdkScreen* screen = gdk_screen_get_default();
+ return gdk_screen_get_monitor_scale_factor(screen, aMonitorNum);
+}
+
+ScreenHelperGTK::ScreenHelperGTK() {
+ gScreenGetter = new ScreenGetterGtk();
+ gScreenGetter->Init();
+}
+
+int ScreenHelperGTK::GetMonitorCount() {
+ return gdk_screen_get_n_monitors(gdk_screen_get_default());
+}
+
+ScreenHelperGTK::~ScreenHelperGTK() { gScreenGetter = nullptr; }
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/ScreenHelperGTK.h b/widget/gtk/ScreenHelperGTK.h
new file mode 100644
index 0000000000..b9efb67746
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.h
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_widget_gtk_ScreenHelperGTK_h
+#define mozilla_widget_gtk_ScreenHelperGTK_h
+
+#include "mozilla/widget/ScreenManager.h"
+
+#include "gdk/gdk.h"
+
+class nsWindow;
+
+namespace mozilla::widget {
+
+class ScreenHelperGTK final : public ScreenManager::Helper {
+ public:
+ ScreenHelperGTK();
+ ~ScreenHelperGTK();
+
+ static int GetMonitorCount();
+ static gint GetGTKMonitorScaleFactor(gint aMonitorNum = 0);
+ static RefPtr<widget::Screen> GetScreenForWindow(nsWindow* aWindow);
+};
+
+} // namespace mozilla::widget
+
+#endif // mozilla_widget_gtk_ScreenHelperGTK_h
diff --git a/widget/gtk/TaskbarProgress.cpp b/widget/gtk/TaskbarProgress.cpp
new file mode 100644
index 0000000000..396f39b5e7
--- /dev/null
+++ b/widget/gtk/TaskbarProgress.cpp
@@ -0,0 +1,106 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "TaskbarProgress.h"
+#include "nsWindow.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+using mozilla::LogLevel;
+static mozilla::LazyLogModule gGtkTaskbarProgressLog("nsIGtkTaskbarProgress");
+
+/******************************************************************************
+ * TaskbarProgress
+ ******************************************************************************/
+
+NS_IMPL_ISUPPORTS(TaskbarProgress, nsIGtkTaskbarProgress, nsITaskbarProgress)
+
+TaskbarProgress::TaskbarProgress() : mPrimaryWindow(nullptr) {
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Info,
+ ("%p TaskbarProgress()", this));
+}
+
+TaskbarProgress::~TaskbarProgress() {
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Info,
+ ("%p ~TaskbarProgress()", this));
+}
+
+NS_IMETHODIMP
+TaskbarProgress::SetProgressState(nsTaskbarProgressState aState,
+ uint64_t aCurrentValue, uint64_t aMaxValue) {
+ NS_ENSURE_ARG_RANGE(aState, 0, STATE_PAUSED);
+
+ if (aState == STATE_NO_PROGRESS || aState == STATE_INDETERMINATE) {
+ NS_ENSURE_TRUE(aCurrentValue == 0, NS_ERROR_INVALID_ARG);
+ NS_ENSURE_TRUE(aMaxValue == 0, NS_ERROR_INVALID_ARG);
+ }
+
+ NS_ENSURE_TRUE((aCurrentValue <= aMaxValue), NS_ERROR_ILLEGAL_VALUE);
+
+ // See TaskbarProgress::SetPrimaryWindow: if we're running in headless
+ // mode, mPrimaryWindow will be null.
+ if (!mPrimaryWindow) {
+ return NS_OK;
+ }
+
+ gulong progress;
+
+ if (aMaxValue == 0) {
+ progress = 0;
+ } else {
+ // Rounding down to ensure we don't set to 'full' until the operation
+ // is completely finished.
+ progress = (gulong)(((double)aCurrentValue / aMaxValue) * 100.0);
+ }
+
+ // Check if the resultant value is the same as the previous call, and
+ // ignore this update if it is.
+
+ if (progress == mCurrentProgress) {
+ return NS_OK;
+ }
+
+ mCurrentProgress = progress;
+
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Debug,
+ ("GtkTaskbarProgress::SetProgressState progress: %lu", progress));
+
+ mPrimaryWindow->SetProgress(progress);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+TaskbarProgress::SetPrimaryWindow(mozIDOMWindowProxy* aWindow) {
+ NS_ENSURE_TRUE(aWindow != nullptr, NS_ERROR_ILLEGAL_VALUE);
+
+ auto* parent = nsPIDOMWindowOuter::From(aWindow);
+ RefPtr<nsIWidget> widget =
+ mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+
+ // Only nsWindows have a native window, HeadlessWidgets do not. Stop here if
+ // the window does not have one.
+ if (!widget->GetNativeData(NS_NATIVE_WINDOW)) {
+ return NS_OK;
+ }
+
+ mPrimaryWindow = static_cast<nsWindow*>(widget.get());
+
+ // Clear our current progress. We get a forced update from the
+ // DownloadsTaskbar after returning from this function - zeroing out our
+ // progress will make sure the new window gets the property set on it
+ // immediately, rather than waiting for the progress value to change (which
+ // could be a while depending on size.)
+ mCurrentProgress = 0;
+
+ MOZ_LOG(gGtkTaskbarProgressLog, LogLevel::Debug,
+ ("GtkTaskbarProgress::SetPrimaryWindow window: %p",
+ mPrimaryWindow.get()));
+
+ return NS_OK;
+}
diff --git a/widget/gtk/TaskbarProgress.h b/widget/gtk/TaskbarProgress.h
new file mode 100644
index 0000000000..819df0c089
--- /dev/null
+++ b/widget/gtk/TaskbarProgress.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef TaskbarProgress_h_
+#define TaskbarProgress_h_
+
+#include "nsIGtkTaskbarProgress.h"
+
+class nsWindow;
+
+class TaskbarProgress final : public nsIGtkTaskbarProgress {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIGTKTASKBARPROGRESS
+ NS_DECL_NSITASKBARPROGRESS
+
+ TaskbarProgress();
+
+ protected:
+ ~TaskbarProgress();
+
+ // We track the progress value so we can avoid updating the X window property
+ // unnecessarily.
+ unsigned long mCurrentProgress;
+
+ RefPtr<nsWindow> mPrimaryWindow;
+};
+
+#endif // #ifndef TaskbarProgress_h_
diff --git a/widget/gtk/WakeLockListener.cpp b/widget/gtk/WakeLockListener.cpp
new file mode 100644
index 0000000000..b2c43cb485
--- /dev/null
+++ b/widget/gtk/WakeLockListener.cpp
@@ -0,0 +1,913 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WakeLockListener.h"
+#include "WidgetUtilsGtk.h"
+#include "mozilla/ScopeExit.h"
+
+#ifdef MOZ_ENABLE_DBUS
+# include <gio/gio.h>
+# include "AsyncDBus.h"
+#endif
+
+#if defined(MOZ_X11)
+# include "prlink.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+#endif
+
+#if defined(MOZ_WAYLAND)
+# include "mozilla/widget/nsWaylandDisplay.h"
+# include "nsWindow.h"
+#endif
+
+#ifdef MOZ_ENABLE_DBUS
+# define FREEDESKTOP_PORTAL_DESKTOP_TARGET "org.freedesktop.portal.Desktop"
+# define FREEDESKTOP_PORTAL_DESKTOP_OBJECT "/org/freedesktop/portal/desktop"
+# define FREEDESKTOP_PORTAL_DESKTOP_INTERFACE "org.freedesktop.portal.Inhibit"
+# define FREEDESKTOP_PORTAL_DESKTOP_INHIBIT_IDLE_FLAG 8
+
+# define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
+
+# define FREEDESKTOP_POWER_TARGET "org.freedesktop.PowerManagement"
+# define FREEDESKTOP_POWER_OBJECT "/org/freedesktop/PowerManagement/Inhibit"
+# define FREEDESKTOP_POWER_INTERFACE "org.freedesktop.PowerManagement.Inhibit"
+
+# define SESSION_MANAGER_TARGET "org.gnome.SessionManager"
+# define SESSION_MANAGER_OBJECT "/org/gnome/SessionManager"
+# define SESSION_MANAGER_INTERFACE "org.gnome.SessionManager"
+
+# define DBUS_TIMEOUT (-1)
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+
+#define WAKE_LOCK_LOG(str, ...) \
+ MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, \
+ ("[%p] " str, this, ##__VA_ARGS__))
+static mozilla::LazyLogModule gLinuxWakeLockLog("LinuxWakeLock");
+
+enum WakeLockType {
+ Initial = 0,
+#if defined(MOZ_ENABLE_DBUS)
+ FreeDesktopScreensaver = 1,
+ FreeDesktopPower = 2,
+ FreeDesktopPortal = 3,
+ GNOME = 4,
+#endif
+#if defined(MOZ_X11)
+ XScreenSaver = 5,
+#endif
+#if defined(MOZ_WAYLAND)
+ WaylandIdleInhibit = 6,
+#endif
+ Unsupported = 7,
+};
+
+#if defined(MOZ_ENABLE_DBUS)
+bool IsDBusWakeLock(int aWakeLockType) {
+ switch (aWakeLockType) {
+ case FreeDesktopScreensaver:
+ case FreeDesktopPower:
+ case GNOME:
+ case FreeDesktopPortal:
+ return true;
+ default:
+ return false;
+ }
+}
+#endif
+
+#ifdef MOZ_LOGGING
+const char* WakeLockTypeNames[] = {
+ "Initial",
+ "FreeDesktopScreensaver",
+ "FreeDesktopPower",
+ "FreeDesktopPortal",
+ "GNOME",
+ "XScreenSaver",
+ "WaylandIdleInhibit",
+ "Unsupported",
+};
+#endif
+
+class WakeLockTopic {
+ public:
+ NS_INLINE_DECL_REFCOUNTING(WakeLockTopic)
+
+ explicit WakeLockTopic(const nsAString& aTopic) {
+ CopyUTF16toUTF8(aTopic, mTopic);
+ WAKE_LOCK_LOG("WakeLockTopic::WakeLockTopic() created %s", mTopic.get());
+ if (sWakeLockType == Initial) {
+ SwitchToNextWakeLockType();
+ }
+#ifdef MOZ_ENABLE_DBUS
+ mCancellable = dont_AddRef(g_cancellable_new());
+#endif
+ }
+
+ nsresult InhibitScreensaver();
+ nsresult UninhibitScreensaver();
+
+ void Shutdown();
+
+ private:
+ bool SendInhibit();
+ bool SendUninhibit();
+
+#if defined(MOZ_X11)
+ bool CheckXScreenSaverSupport();
+ bool InhibitXScreenSaver(bool inhibit);
+#endif
+
+#if defined(MOZ_WAYLAND)
+ zwp_idle_inhibitor_v1* mWaylandInhibitor = nullptr;
+ static bool CheckWaylandIdleInhibitSupport();
+ bool InhibitWaylandIdle();
+ bool UninhibitWaylandIdle();
+#endif
+
+ bool IsWakeLockTypeAvailable(int aWakeLockType);
+ bool SwitchToNextWakeLockType();
+
+#ifdef MOZ_ENABLE_DBUS
+ void DBusInhibitScreensaver(const char* aName, const char* aPath,
+ const char* aCall, const char* aMethod,
+ RefPtr<GVariant> aArgs);
+ void DBusUninhibitScreensaver(const char* aName, const char* aPath,
+ const char* aCall, const char* aMethod);
+
+ void InhibitFreeDesktopPortal();
+ void InhibitFreeDesktopScreensaver();
+ void InhibitFreeDesktopPower();
+ void InhibitGNOME();
+
+ void UninhibitFreeDesktopPortal();
+ void UninhibitFreeDesktopScreensaver();
+ void UninhibitFreeDesktopPower();
+ void UninhibitGNOME();
+
+ void DBusInhibitSucceeded(uint32_t aInhibitRequestID);
+ void DBusInhibitFailed(bool aFatal);
+ void DBusUninhibitSucceeded();
+ void DBusUninhibitFailed();
+ void ClearDBusInhibitToken();
+#endif
+ ~WakeLockTopic() = default;
+
+ // Why is screensaver inhibited
+ nsCString mTopic;
+
+ // Our desired state
+ bool mShouldInhibit = false;
+
+ // Our actual sate
+ bool mInhibited = false;
+
+#ifdef MOZ_ENABLE_DBUS
+ // We're waiting for DBus reply (inhibit/uninhibit calls).
+ bool mWaitingForDBusInhibit = false;
+ bool mWaitingForDBusUninhibit = false;
+
+ // mInhibitRequestID is received from success screen saver inhibit call
+ // and it's needed for screen saver enablement.
+ Maybe<uint32_t> mInhibitRequestID;
+
+ RefPtr<GCancellable> mCancellable;
+ // Used to uninhibit org.freedesktop.portal.Inhibit request
+ nsCString mRequestObjectPath;
+#endif
+
+ static int sWakeLockType;
+};
+
+int WakeLockTopic::sWakeLockType = Initial;
+
+#ifdef MOZ_ENABLE_DBUS
+void WakeLockTopic::DBusInhibitSucceeded(uint32_t aInhibitRequestID) {
+ mWaitingForDBusInhibit = false;
+ mInhibitRequestID = Some(aInhibitRequestID);
+ mInhibited = true;
+
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitSucceeded(), mInhibitRequestID %u "
+ "mShouldInhibit %d",
+ *mInhibitRequestID, mShouldInhibit);
+
+ // Uninhibit was requested before inhibit request was finished.
+ // So ask for it now.
+ if (!mShouldInhibit) {
+ UninhibitScreensaver();
+ }
+}
+
+void WakeLockTopic::DBusInhibitFailed(bool aFatal) {
+ WAKE_LOCK_LOG("WakeLockTopic::DBusInhibitFailed(%d)", aFatal);
+
+ mWaitingForDBusInhibit = false;
+ ClearDBusInhibitToken();
+
+ // Non-recoverable DBus error. Switch to another wake lock type.
+ if (aFatal && SwitchToNextWakeLockType()) {
+ SendInhibit();
+ }
+}
+
+void WakeLockTopic::DBusUninhibitSucceeded() {
+ WAKE_LOCK_LOG("WakeLockTopic::DBusUninhibitSucceeded() mShouldInhibit %d",
+ mShouldInhibit);
+
+ mWaitingForDBusUninhibit = false;
+ mInhibited = false;
+ ClearDBusInhibitToken();
+
+ // Inhibit was requested before uninhibit request was finished.
+ // So ask for it now.
+ if (mShouldInhibit) {
+ InhibitScreensaver();
+ }
+}
+
+void WakeLockTopic::DBusUninhibitFailed() {
+ WAKE_LOCK_LOG("WakeLockTopic::DBusUninhibitFailed()");
+ mWaitingForDBusUninhibit = false;
+ mInhibitRequestID = Nothing();
+}
+
+void WakeLockTopic::ClearDBusInhibitToken() {
+ mRequestObjectPath.Truncate();
+ mInhibitRequestID = Nothing();
+}
+
+void WakeLockTopic::DBusInhibitScreensaver(const char* aName, const char* aPath,
+ const char* aCall,
+ const char* aMethod,
+ RefPtr<GVariant> aArgs) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitScreensaver() mWaitingForDBusInhibit %d "
+ "mWaitingForDBusUninhibit %d",
+ mWaitingForDBusInhibit, mWaitingForDBusUninhibit);
+ if (mWaitingForDBusInhibit) {
+ WAKE_LOCK_LOG(" already waiting to inihibit, return");
+ return;
+ }
+ if (mWaitingForDBusUninhibit) {
+ WAKE_LOCK_LOG(" cancel un-inihibit request");
+ g_cancellable_cancel(mCancellable);
+ mWaitingForDBusUninhibit = false;
+ }
+ mWaitingForDBusInhibit = true;
+
+ widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ /* aInterfaceInfo = */ nullptr, aName, aPath, aCall, mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, this, args = RefPtr{aArgs},
+ aMethod](RefPtr<GDBusProxy>&& aProxy) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitScreensaver() proxy created");
+ DBusProxyCall(aProxy.get(), aMethod, args.get(),
+ G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT, mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [s = RefPtr{this}, this](RefPtr<GVariant>&& aResult) {
+ if (!g_variant_is_of_type(aResult.get(),
+ G_VARIANT_TYPE_TUPLE) ||
+ g_variant_n_children(aResult.get()) != 1) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitScreensaver() wrong "
+ "reply type %s\n",
+ g_variant_get_type_string(aResult.get()));
+ DBusInhibitFailed(/* aFatal */ true);
+ return;
+ }
+ RefPtr<GVariant> variant = dont_AddRef(
+ g_variant_get_child_value(aResult.get(), 0));
+ if (!g_variant_is_of_type(variant,
+ G_VARIANT_TYPE_UINT32)) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitScreensaver() wrong "
+ "reply type %s\n",
+ g_variant_get_type_string(aResult.get()));
+ DBusInhibitFailed(/* aFatal */ true);
+ return;
+ }
+ DBusInhibitSucceeded(g_variant_get_uint32(variant));
+ },
+ [s = RefPtr{this}, this,
+ aMethod](GUniquePtr<GError>&& aError) {
+ // Failed to send inhibit request over proxy.
+ // Switch to another wake lock type.
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitFailed() %s call failed : "
+ "%s\n",
+ aMethod, aError->message);
+ DBusInhibitFailed(
+ /* aFatal */ !IsCancelledGError(aError.get()));
+ });
+ },
+ [self = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ // We failed to create DBus proxy. Switch to another
+ // wake lock type.
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusInhibitScreensaver() Proxy creation "
+ "failed: %s\n",
+ aError->message);
+ DBusInhibitFailed(/* aFatal */ !IsCancelledGError(aError.get()));
+ });
+}
+
+void WakeLockTopic::DBusUninhibitScreensaver(const char* aName,
+ const char* aPath,
+ const char* aCall,
+ const char* aMethod) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusUninhibitScreensaver() mWaitingForDBusInhibit %d "
+ "mWaitingForDBusUninhibit %d request id %d",
+ mWaitingForDBusInhibit, mWaitingForDBusUninhibit,
+ mInhibitRequestID ? *mInhibitRequestID : -1);
+
+ if (mWaitingForDBusUninhibit) {
+ WAKE_LOCK_LOG(" already waiting to uninihibit, return");
+ return;
+ }
+
+ if (mWaitingForDBusInhibit) {
+ WAKE_LOCK_LOG(" cancel inihibit request");
+ g_cancellable_cancel(mCancellable);
+ mWaitingForDBusInhibit = false;
+ }
+
+ if (!mInhibitRequestID.isSome()) {
+ WAKE_LOCK_LOG(" missing inihibit token, quit.");
+ // missing uninhibit token, just quit.
+ return;
+ }
+ mWaitingForDBusUninhibit = true;
+
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_ref_sink(g_variant_new("(u)", *mInhibitRequestID)));
+ nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
+ widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ /* aInterfaceInfo = */ nullptr, aName, aPath, aCall, mCancellable)
+ ->Then(
+ target, __func__,
+ [self = RefPtr{this}, this, args = std::move(variant), target,
+ aMethod](RefPtr<GDBusProxy>&& aProxy) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusUninhibitScreensaver() proxy created");
+ DBusProxyCall(aProxy.get(), aMethod, args.get(),
+ G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT, mCancellable)
+ ->Then(
+ target, __func__,
+ [s = RefPtr{this}, this](RefPtr<GVariant>&& aResult) {
+ DBusUninhibitSucceeded();
+ },
+ [s = RefPtr{this}, this,
+ aMethod](GUniquePtr<GError>&& aError) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusUninhibitFailed() %s call failed "
+ ": %s\n",
+ aMethod, aError->message);
+ DBusUninhibitFailed();
+ });
+ },
+ [self = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::DBusUninhibitFailed() Proxy creation failed: "
+ "%s\n",
+ aError->message);
+ DBusUninhibitFailed();
+ });
+}
+
+void WakeLockTopic::InhibitFreeDesktopPortal() {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::InhibitFreeDesktopPortal() mWaitingForDBusInhibit %d "
+ "mWaitingForDBusUninhibit %d",
+ mWaitingForDBusInhibit, mWaitingForDBusUninhibit);
+ if (mWaitingForDBusInhibit) {
+ WAKE_LOCK_LOG(" already waiting to inihibit, return");
+ return;
+ }
+ if (mWaitingForDBusUninhibit) {
+ WAKE_LOCK_LOG(" cancel un-inihibit request");
+ g_cancellable_cancel(mCancellable);
+ mWaitingForDBusUninhibit = false;
+ }
+ mWaitingForDBusInhibit = true;
+
+ CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ nullptr, FREEDESKTOP_PORTAL_DESKTOP_TARGET,
+ FREEDESKTOP_PORTAL_DESKTOP_OBJECT, FREEDESKTOP_PORTAL_DESKTOP_INTERFACE,
+ mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [self = RefPtr{this}, this](RefPtr<GDBusProxy>&& aProxy) {
+ GVariantBuilder b;
+ g_variant_builder_init(&b, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&b, "{sv}", "reason",
+ g_variant_new_string(self->mTopic.get()));
+
+ // From
+ // https://flatpak.github.io/xdg-desktop-portal/docs/#gdbus-org.freedesktop.portal.Inhibit
+ DBusProxyCall(
+ aProxy.get(), "Inhibit",
+ g_variant_new("(sua{sv})", g_get_prgname(),
+ FREEDESKTOP_PORTAL_DESKTOP_INHIBIT_IDLE_FLAG, &b),
+ G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT, mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [s = RefPtr{this}, this](RefPtr<GVariant>&& aResult) {
+ gchar* requestObjectPath = nullptr;
+ g_variant_get(aResult, "(o)", &requestObjectPath);
+ if (!requestObjectPath) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::InhibitFreeDesktopPortal(): Unable "
+ "to get requestObjectPath\n");
+ DBusInhibitFailed(/* aFatal */ true);
+ return;
+ }
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::InhibitFreeDesktopPortal(): "
+ "inhibited, objpath to unihibit: %s\n",
+ requestObjectPath);
+ mRequestObjectPath.Adopt(requestObjectPath);
+ DBusInhibitSucceeded(0);
+ },
+ [s = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ DBusInhibitFailed(
+ /* aFatal */ !IsCancelledGError(aError.get()));
+ WAKE_LOCK_LOG(
+ "Failed to create DBus proxy for "
+ "org.freedesktop.portal.Desktop: %s\n",
+ aError->message);
+ });
+ },
+ [self = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ WAKE_LOCK_LOG(
+ "Failed to create DBus proxy for "
+ "org.freedesktop.portal.Desktop: %s\n",
+ aError->message);
+ DBusInhibitFailed(/* aFatal */ !IsCancelledGError(aError.get()));
+ });
+}
+
+void WakeLockTopic::InhibitFreeDesktopScreensaver() {
+ WAKE_LOCK_LOG("InhibitFreeDesktopScreensaver()");
+ DBusInhibitScreensaver(FREEDESKTOP_SCREENSAVER_TARGET,
+ FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "Inhibit",
+ dont_AddRef(g_variant_ref_sink(g_variant_new(
+ "(ss)", g_get_prgname(), mTopic.get()))));
+}
+
+void WakeLockTopic::InhibitFreeDesktopPower() {
+ WAKE_LOCK_LOG("InhibitFreeDesktopPower()");
+ DBusInhibitScreensaver(FREEDESKTOP_POWER_TARGET, FREEDESKTOP_POWER_OBJECT,
+ FREEDESKTOP_POWER_INTERFACE, "Inhibit",
+ dont_AddRef(g_variant_ref_sink(g_variant_new(
+ "(ss)", g_get_prgname(), mTopic.get()))));
+}
+
+void WakeLockTopic::InhibitGNOME() {
+ WAKE_LOCK_LOG("InhibitGNOME()");
+ static const uint32_t xid = 0;
+ static const uint32_t flags = (1 << 3); // Inhibit idle
+ DBusInhibitScreensaver(
+ SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT, SESSION_MANAGER_INTERFACE,
+ "Inhibit",
+ dont_AddRef(g_variant_ref_sink(
+ g_variant_new("(susu)", g_get_prgname(), xid, mTopic.get(), flags))));
+}
+
+void WakeLockTopic::UninhibitFreeDesktopPortal() {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::UninhibitFreeDesktopPortal() mWaitingForDBusInhibit %d "
+ "mWaitingForDBusUninhibit %d object path: %s",
+ mWaitingForDBusInhibit, mWaitingForDBusUninhibit,
+ mRequestObjectPath.get());
+
+ if (mWaitingForDBusUninhibit) {
+ WAKE_LOCK_LOG(" already waiting to uninihibit, return");
+ return;
+ }
+
+ if (mWaitingForDBusInhibit) {
+ WAKE_LOCK_LOG(" cancel inihibit request");
+ g_cancellable_cancel(mCancellable);
+ mWaitingForDBusInhibit = false;
+ }
+ if (mRequestObjectPath.IsEmpty()) {
+ WAKE_LOCK_LOG("UninhibitFreeDesktopPortal() failed: unknown object path\n");
+ return;
+ }
+ mWaitingForDBusUninhibit = true;
+
+ nsCOMPtr<nsISerialEventTarget> target = GetCurrentSerialEventTarget();
+ CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ nullptr, FREEDESKTOP_PORTAL_DESKTOP_TARGET, mRequestObjectPath.get(),
+ "org.freedesktop.portal.Request", mCancellable)
+ ->Then(
+ target, __func__,
+ [self = RefPtr{this}, target, this](RefPtr<GDBusProxy>&& aProxy) {
+ DBusProxyCall(aProxy.get(), "Close", nullptr,
+ G_DBUS_CALL_FLAGS_NONE, DBUS_TIMEOUT, mCancellable)
+ ->Then(
+ target, __func__,
+ [s = RefPtr{this}, this](RefPtr<GVariant>&& aResult) {
+ DBusUninhibitSucceeded();
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::UninhibitFreeDesktopPortal() Inhibit "
+ "removed\n");
+ },
+ [s = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ DBusUninhibitFailed();
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::UninhibitFreeDesktopPortal() "
+ "Removing inhibit failed: %s\n",
+ aError->message);
+ });
+ },
+ [self = RefPtr{this}, this](GUniquePtr<GError>&& aError) {
+ WAKE_LOCK_LOG(
+ "WakeLockTopic::UninhibitFreeDesktopPortal() Proxy creation "
+ "failed: %s\n",
+ aError->message);
+ DBusUninhibitFailed();
+ });
+}
+
+void WakeLockTopic::UninhibitFreeDesktopScreensaver() {
+ WAKE_LOCK_LOG("UninhibitFreeDesktopScreensaver()");
+ DBusUninhibitScreensaver(FREEDESKTOP_SCREENSAVER_TARGET,
+ FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "UnInhibit");
+}
+
+void WakeLockTopic::UninhibitFreeDesktopPower() {
+ WAKE_LOCK_LOG("UninhibitFreeDesktopPower()");
+ DBusUninhibitScreensaver(FREEDESKTOP_POWER_TARGET, FREEDESKTOP_POWER_OBJECT,
+ FREEDESKTOP_POWER_INTERFACE, "UnInhibit");
+}
+
+void WakeLockTopic::UninhibitGNOME() {
+ WAKE_LOCK_LOG("UninhibitGNOME()");
+ DBusUninhibitScreensaver(SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE, "Uninhibit");
+}
+#endif
+
+#if defined(MOZ_X11)
+// TODO: Merge with Idle service?
+typedef Bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
+ int* error_base);
+typedef Bool (*_XScreenSaverQueryVersion_fn)(Display* dpy, int* major,
+ int* minor);
+typedef void (*_XScreenSaverSuspend_fn)(Display* dpy, Bool suspend);
+
+static PRLibrary* sXssLib = nullptr;
+static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
+static _XScreenSaverQueryVersion_fn _XSSQueryVersion = nullptr;
+static _XScreenSaverSuspend_fn _XSSSuspend = nullptr;
+
+/* static */
+bool WakeLockTopic::CheckXScreenSaverSupport() {
+ if (!sXssLib) {
+ sXssLib = PR_LoadLibrary("libXss.so.1");
+ if (!sXssLib) {
+ return false;
+ }
+ }
+
+ _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverQueryExtension");
+ _XSSQueryVersion = (_XScreenSaverQueryVersion_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverQueryVersion");
+ _XSSSuspend = (_XScreenSaverSuspend_fn)PR_FindFunctionSymbol(
+ sXssLib, "XScreenSaverSuspend");
+ if (!_XSSQueryExtension || !_XSSQueryVersion || !_XSSSuspend) {
+ return false;
+ }
+
+ GdkDisplay* gDisplay = gdk_display_get_default();
+ if (!GdkIsX11Display(gDisplay)) {
+ return false;
+ }
+ Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
+
+ int throwaway;
+ if (!_XSSQueryExtension(display, &throwaway, &throwaway)) return false;
+
+ int major, minor;
+ if (!_XSSQueryVersion(display, &major, &minor)) return false;
+ // Needs to be compatible with version 1.1
+ if (major != 1) return false;
+ if (minor < 1) return false;
+
+ WAKE_LOCK_LOG("XScreenSaver supported.");
+ return true;
+}
+
+/* static */
+bool WakeLockTopic::InhibitXScreenSaver(bool inhibit) {
+ WAKE_LOCK_LOG("InhibitXScreenSaver %d", inhibit);
+
+ // Should only be called if CheckXScreenSaverSupport returns true.
+ // There's a couple of safety checks here nonetheless.
+ if (!_XSSSuspend) {
+ return false;
+ }
+ GdkDisplay* gDisplay = gdk_display_get_default();
+ if (!GdkIsX11Display(gDisplay)) {
+ return false;
+ }
+ Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
+ _XSSSuspend(display, inhibit);
+
+ WAKE_LOCK_LOG("InhibitXScreenSaver %d succeeded", inhibit);
+ mInhibited = inhibit;
+ return true;
+}
+#endif
+
+#if defined(MOZ_WAYLAND)
+/* static */
+bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
+ nsWaylandDisplay* waylandDisplay = WaylandDisplayGet();
+ return waylandDisplay && waylandDisplay->GetIdleInhibitManager() != nullptr;
+}
+
+bool WakeLockTopic::InhibitWaylandIdle() {
+ WAKE_LOCK_LOG("InhibitWaylandIdle()");
+
+ nsWaylandDisplay* waylandDisplay = WaylandDisplayGet();
+ if (!waylandDisplay) {
+ return false;
+ }
+
+ nsWindow* focusedWindow = nsWindow::GetFocusedWindow();
+ if (!focusedWindow) {
+ return false;
+ }
+
+ UninhibitWaylandIdle();
+
+ MozContainerSurfaceLock lock(focusedWindow->GetMozContainer());
+ struct wl_surface* waylandSurface = lock.GetSurface();
+ if (waylandSurface) {
+ mWaylandInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
+ waylandDisplay->GetIdleInhibitManager(), waylandSurface);
+ mInhibited = true;
+ }
+
+ WAKE_LOCK_LOG("InhibitWaylandIdle() %s",
+ !!mWaylandInhibitor ? "succeeded" : "failed");
+ return !!mWaylandInhibitor;
+}
+
+bool WakeLockTopic::UninhibitWaylandIdle() {
+ WAKE_LOCK_LOG("UninhibitWaylandIdle() mWaylandInhibitor %p",
+ mWaylandInhibitor);
+
+ mInhibited = false;
+ if (!mWaylandInhibitor) {
+ return false;
+ }
+ zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor);
+ mWaylandInhibitor = nullptr;
+ return true;
+}
+#endif
+
+bool WakeLockTopic::SendInhibit() {
+ WAKE_LOCK_LOG("WakeLockTopic::SendInhibit() WakeLockType %s",
+ WakeLockTypeNames[sWakeLockType]);
+ MOZ_ASSERT(sWakeLockType != Initial);
+
+ switch (sWakeLockType) {
+#if defined(MOZ_ENABLE_DBUS)
+ case FreeDesktopPortal:
+ InhibitFreeDesktopPortal();
+ break;
+ case FreeDesktopScreensaver:
+ InhibitFreeDesktopScreensaver();
+ break;
+ case FreeDesktopPower:
+ InhibitFreeDesktopPower();
+ break;
+ case GNOME:
+ InhibitGNOME();
+ break;
+#endif
+#if defined(MOZ_X11)
+ case XScreenSaver:
+ return InhibitXScreenSaver(true);
+#endif
+#if defined(MOZ_WAYLAND)
+ case WaylandIdleInhibit:
+ return InhibitWaylandIdle();
+#endif
+ default:
+ return false;
+ }
+ return true;
+}
+
+bool WakeLockTopic::SendUninhibit() {
+ WAKE_LOCK_LOG("WakeLockTopic::SendUninhibit() WakeLockType %s",
+ WakeLockTypeNames[sWakeLockType]);
+ MOZ_ASSERT(sWakeLockType != Initial);
+ switch (sWakeLockType) {
+#if defined(MOZ_ENABLE_DBUS)
+ case FreeDesktopPortal:
+ UninhibitFreeDesktopPortal();
+ break;
+ case FreeDesktopScreensaver:
+ UninhibitFreeDesktopScreensaver();
+ break;
+ case FreeDesktopPower:
+ UninhibitFreeDesktopPower();
+ break;
+ case GNOME:
+ UninhibitGNOME();
+ break;
+#endif
+#if defined(MOZ_X11)
+ case XScreenSaver:
+ return InhibitXScreenSaver(false);
+#endif
+#if defined(MOZ_WAYLAND)
+ case WaylandIdleInhibit:
+ return UninhibitWaylandIdle();
+#endif
+ default:
+ return false;
+ }
+ return true;
+}
+
+nsresult WakeLockTopic::InhibitScreensaver() {
+ WAKE_LOCK_LOG("WakeLockTopic::InhibitScreensaver() Inhibited %d", mInhibited);
+
+ if (mInhibited) {
+ // Screensaver is inhibited. Nothing to do here.
+ return NS_OK;
+ }
+ mShouldInhibit = true;
+
+ // Iterate through wake lock types in case of failure.
+ while (!SendInhibit() && SwitchToNextWakeLockType()) {
+ ;
+ }
+
+ return (sWakeLockType != Unsupported) ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void WakeLockTopic::Shutdown() {
+ WAKE_LOCK_LOG("WakeLockTopic::Shutdown() state %d", mInhibited);
+#ifdef MOZ_ENABLE_DBUS
+ if (mWaitingForDBusUninhibit) {
+ return;
+ }
+ g_cancellable_cancel(mCancellable);
+#endif
+ if (mInhibited) {
+ UninhibitScreensaver();
+ }
+}
+
+nsresult WakeLockTopic::UninhibitScreensaver() {
+ WAKE_LOCK_LOG("WakeLockTopic::UninhibitScreensaver() Inhibited %d",
+ mInhibited);
+
+ if (!mInhibited) {
+ // Screensaver isn't inhibited. Nothing to do here.
+ return NS_OK;
+ }
+ mShouldInhibit = false;
+
+ // Don't switch wake lock type in case of failure.
+ // We need to use the same lock/unlock type.
+ return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+bool WakeLockTopic::IsWakeLockTypeAvailable(int aWakeLockType) {
+ switch (aWakeLockType) {
+#if defined(MOZ_ENABLE_DBUS)
+ case FreeDesktopPortal:
+ case FreeDesktopScreensaver:
+ case FreeDesktopPower:
+ case GNOME:
+ return true;
+#endif
+#if defined(MOZ_X11)
+ case XScreenSaver:
+ if (!GdkIsX11Display()) {
+ return false;
+ }
+ if (!CheckXScreenSaverSupport()) {
+ WAKE_LOCK_LOG(" XScreenSaverSupport is missing!");
+ return false;
+ }
+ return true;
+#endif
+#if defined(MOZ_WAYLAND)
+ case WaylandIdleInhibit:
+ if (!GdkIsWaylandDisplay()) {
+ return false;
+ }
+ if (!CheckWaylandIdleInhibitSupport()) {
+ WAKE_LOCK_LOG(" WaylandIdleInhibitSupport is missing!");
+ return false;
+ }
+ return true;
+#endif
+ default:
+ return false;
+ }
+}
+
+bool WakeLockTopic::SwitchToNextWakeLockType() {
+ WAKE_LOCK_LOG("WakeLockTopic::SwitchToNextWakeLockType() WakeLockType %s",
+ WakeLockTypeNames[sWakeLockType]);
+
+ if (sWakeLockType == Unsupported) {
+ return false;
+ }
+
+#ifdef MOZ_LOGGING
+ auto printWakeLocktype = MakeScopeExit([&] {
+ WAKE_LOCK_LOG(" switched to WakeLockType %s",
+ WakeLockTypeNames[sWakeLockType]);
+ });
+#endif
+
+#if defined(MOZ_ENABLE_DBUS)
+ if (IsDBusWakeLock(sWakeLockType)) {
+ // We're switching out of DBus wakelock - clear our recent DBus states.
+ mWaitingForDBusInhibit = false;
+ mWaitingForDBusUninhibit = false;
+ mInhibited = false;
+ ClearDBusInhibitToken();
+ }
+#endif
+
+ while (sWakeLockType != Unsupported) {
+ sWakeLockType++;
+ if (IsWakeLockTypeAvailable(sWakeLockType)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+WakeLockListener::WakeLockListener() = default;
+
+WakeLockListener::~WakeLockListener() {
+ for (const auto& topic : mTopics.Values()) {
+ topic->Shutdown();
+ }
+}
+
+nsresult WakeLockListener::Callback(const nsAString& topic,
+ const nsAString& state) {
+ if (!topic.Equals(u"screen"_ns) && !topic.Equals(u"video-playing"_ns) &&
+ !topic.Equals(u"autoscroll"_ns)) {
+ return NS_OK;
+ }
+
+ RefPtr<WakeLockTopic> topicLock = mTopics.LookupOrInsertWith(
+ topic, [&] { return MakeRefPtr<WakeLockTopic>(topic); });
+
+ // Treat "locked-background" the same as "unlocked" on desktop linux.
+ bool shouldLock = state.EqualsLiteral("locked-foreground");
+ WAKE_LOCK_LOG("WakeLockListener topic %s state %s request lock %d",
+ NS_ConvertUTF16toUTF8(topic).get(),
+ NS_ConvertUTF16toUTF8(state).get(), shouldLock);
+
+ return shouldLock ? topicLock->InhibitScreensaver()
+ : topicLock->UninhibitScreensaver();
+}
diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h
new file mode 100644
index 0000000000..1c587d2302
--- /dev/null
+++ b/widget/gtk/WakeLockListener.h
@@ -0,0 +1,38 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __WakeLockListener_h__
+#define __WakeLockListener_h__
+
+#include "nsHashKeys.h"
+#include "nsRefPtrHashtable.h"
+
+#include "nsIDOMWakeLockListener.h"
+
+class WakeLockTopic;
+
+/**
+ * Receives WakeLock events and simply passes it on to the right WakeLockTopic
+ * to inhibit the screensaver.
+ */
+class WakeLockListener final : public nsIDOMMozWakeLockListener {
+ public:
+ NS_DECL_ISUPPORTS;
+
+ nsresult Callback(const nsAString& topic, const nsAString& state) override;
+
+ WakeLockListener();
+
+ private:
+ ~WakeLockListener();
+
+ // Map of topic names to |WakeLockTopic|s.
+ // We assume a small, finite-sized set of topics.
+ nsRefPtrHashtable<nsStringHashKey, WakeLockTopic> mTopics;
+};
+
+#endif // __WakeLockListener_h__
diff --git a/widget/gtk/WaylandBuffer.cpp b/widget/gtk/WaylandBuffer.cpp
new file mode 100644
index 0000000000..f3fc409362
--- /dev/null
+++ b/widget/gtk/WaylandBuffer.cpp
@@ -0,0 +1,224 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WaylandBuffer.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "mozilla/gfx/Tools.h"
+#include "nsGtkUtils.h"
+#include "nsPrintfCString.h"
+#include "prenv.h" // For PR_GetEnv
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "mozilla/ScopeExit.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(...) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOGWAYLAND(...)
+#endif /* MOZ_LOGGING */
+
+using namespace mozilla::gl;
+
+namespace mozilla::widget {
+
+#define BUFFER_BPP 4
+gfx::SurfaceFormat WaylandBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
+
+#ifdef MOZ_LOGGING
+int WaylandBufferSHM::mDumpSerial =
+ PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
+char* WaylandBufferSHM::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");
+#endif
+
+/* static */
+RefPtr<WaylandShmPool> WaylandShmPool::Create(nsWaylandDisplay* aWaylandDisplay,
+ int aSize) {
+ if (!aWaylandDisplay->GetShm()) {
+ NS_WARNING("WaylandShmPool: Missing Wayland shm interface!");
+ return nullptr;
+ }
+
+ RefPtr<WaylandShmPool> shmPool = new WaylandShmPool();
+
+ shmPool->mShm = MakeUnique<base::SharedMemory>();
+ if (!shmPool->mShm->Create(aSize)) {
+ NS_WARNING("WaylandShmPool: Unable to allocate shared memory!");
+ return nullptr;
+ }
+
+ shmPool->mSize = aSize;
+ shmPool->mShmPool = wl_shm_create_pool(
+ aWaylandDisplay->GetShm(), shmPool->mShm->CloneHandle().get(), aSize);
+ if (!shmPool->mShmPool) {
+ NS_WARNING("WaylandShmPool: Unable to allocate shared memory pool!");
+ return nullptr;
+ }
+
+ return shmPool;
+}
+
+void* WaylandShmPool::GetImageData() {
+ if (mImageData) {
+ return mImageData;
+ }
+ if (!mShm->Map(mSize)) {
+ NS_WARNING("WaylandShmPool: Failed to map Shm!");
+ return nullptr;
+ }
+ mImageData = mShm->memory();
+ return mImageData;
+}
+
+WaylandShmPool::~WaylandShmPool() {
+ MozClearPointer(mShmPool, wl_shm_pool_destroy);
+}
+
+static const struct wl_buffer_listener sBufferListenerWaylandBuffer = {
+ WaylandBuffer::BufferReleaseCallbackHandler};
+
+WaylandBuffer::WaylandBuffer(const LayoutDeviceIntSize& aSize) : mSize(aSize) {}
+
+void WaylandBuffer::AttachAndCommit(wl_surface* aSurface) {
+ LOGWAYLAND(
+ "WaylandBuffer::AttachAndCommit [%p] wl_surface %p ID %d wl_buffer "
+ "%p ID %d\n",
+ (void*)this, (void*)aSurface,
+ aSurface ? wl_proxy_get_id((struct wl_proxy*)aSurface) : -1,
+ (void*)GetWlBuffer(),
+ GetWlBuffer() ? wl_proxy_get_id((struct wl_proxy*)GetWlBuffer()) : -1);
+
+ wl_buffer* buffer = GetWlBuffer();
+ if (buffer) {
+ mAttached = true;
+ wl_surface_attach(aSurface, buffer, 0, 0);
+ wl_surface_commit(aSurface);
+ }
+}
+
+void WaylandBuffer::BufferReleaseCallbackHandler(wl_buffer* aBuffer) {
+ mAttached = false;
+
+ if (mBufferReleaseFunc) {
+ mBufferReleaseFunc(mBufferReleaseData, aBuffer);
+ }
+}
+
+void WaylandBuffer::BufferReleaseCallbackHandler(void* aData,
+ wl_buffer* aBuffer) {
+ auto* buffer = reinterpret_cast<WaylandBuffer*>(aData);
+ buffer->BufferReleaseCallbackHandler(aBuffer);
+}
+
+/* static */
+RefPtr<WaylandBufferSHM> WaylandBufferSHM::Create(
+ const LayoutDeviceIntSize& aSize) {
+ RefPtr<WaylandBufferSHM> buffer = new WaylandBufferSHM(aSize);
+ nsWaylandDisplay* waylandDisplay = WaylandDisplayGet();
+
+ int size = aSize.width * aSize.height * BUFFER_BPP;
+ buffer->mShmPool = WaylandShmPool::Create(waylandDisplay, size);
+ if (!buffer->mShmPool) {
+ return nullptr;
+ }
+
+ buffer->mWLBuffer = wl_shm_pool_create_buffer(
+ buffer->mShmPool->GetShmPool(), 0, aSize.width, aSize.height,
+ aSize.width * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
+ if (!buffer->mWLBuffer) {
+ return nullptr;
+ }
+
+ wl_buffer_add_listener(buffer->GetWlBuffer(), &sBufferListenerWaylandBuffer,
+ buffer.get());
+
+ LOGWAYLAND("WaylandBufferSHM Created [%p] WaylandDisplay [%p]\n",
+ buffer.get(), waylandDisplay);
+
+ return buffer;
+}
+
+WaylandBufferSHM::WaylandBufferSHM(const LayoutDeviceIntSize& aSize)
+ : WaylandBuffer(aSize) {}
+
+WaylandBufferSHM::~WaylandBufferSHM() {
+ MozClearPointer(mWLBuffer, wl_buffer_destroy);
+}
+
+already_AddRefed<gfx::DrawTarget> WaylandBufferSHM::Lock() {
+ return gfxPlatform::CreateDrawTargetForData(
+ static_cast<unsigned char*>(mShmPool->GetImageData()),
+ mSize.ToUnknownSize(), BUFFER_BPP * mSize.width, GetSurfaceFormat());
+}
+
+void WaylandBufferSHM::Clear() {
+ memset(mShmPool->GetImageData(), 0, mSize.height * mSize.width * BUFFER_BPP);
+}
+
+#ifdef MOZ_LOGGING
+void WaylandBufferSHM::DumpToFile(const char* aHint) {
+ if (!mDumpSerial) {
+ return;
+ }
+
+ cairo_surface_t* surface = nullptr;
+ auto unmap = MakeScopeExit([&] {
+ if (surface) {
+ cairo_surface_destroy(surface);
+ }
+ });
+ surface = cairo_image_surface_create_for_data(
+ (unsigned char*)mShmPool->GetImageData(), CAIRO_FORMAT_ARGB32,
+ mSize.width, mSize.height, BUFFER_BPP * mSize.width);
+ if (cairo_surface_status(surface) == CAIRO_STATUS_SUCCESS) {
+ nsCString filename;
+ if (mDumpDir) {
+ filename.Append(mDumpDir);
+ filename.Append('/');
+ }
+ filename.Append(
+ nsPrintfCString("firefox-wl-buffer-%.5d-%s.png", mDumpSerial++, aHint));
+ cairo_surface_write_to_png(surface, filename.get());
+ LOGWAYLAND("Dumped wl_buffer to %s\n", filename.get());
+ }
+}
+#endif
+
+/* static */
+RefPtr<WaylandBufferDMABUF> WaylandBufferDMABUF::Create(
+ const LayoutDeviceIntSize& aSize, GLContext* aGL) {
+ RefPtr<WaylandBufferDMABUF> buffer = new WaylandBufferDMABUF(aSize);
+
+ const auto flags =
+ static_cast<DMABufSurfaceFlags>(DMABUF_TEXTURE | DMABUF_ALPHA);
+ buffer->mDMABufSurface =
+ DMABufSurfaceRGBA::CreateDMABufSurface(aSize.width, aSize.height, flags);
+ if (!buffer->mDMABufSurface || !buffer->mDMABufSurface->CreateTexture(aGL)) {
+ return nullptr;
+ }
+
+ if (!buffer->mDMABufSurface->CreateWlBuffer()) {
+ return nullptr;
+ }
+
+ wl_buffer_add_listener(buffer->GetWlBuffer(), &sBufferListenerWaylandBuffer,
+ buffer.get());
+
+ return buffer;
+}
+
+WaylandBufferDMABUF::WaylandBufferDMABUF(const LayoutDeviceIntSize& aSize)
+ : WaylandBuffer(aSize) {}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/WaylandBuffer.h b/widget/gtk/WaylandBuffer.h
new file mode 100644
index 0000000000..34bdb87ff5
--- /dev/null
+++ b/widget/gtk/WaylandBuffer.h
@@ -0,0 +1,140 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WAYLAND_BUFFER_H
+#define _MOZILLA_WIDGET_GTK_WAYLAND_BUFFER_H
+
+#include "DMABufSurface.h"
+#include "GLContext.h"
+#include "MozFramebuffer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+#include "nsWaylandDisplay.h"
+#include "base/shared_memory.h"
+
+namespace mozilla::widget {
+
+// Allocates and owns shared memory for Wayland drawing surface
+class WaylandShmPool {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WaylandShmPool);
+
+ static RefPtr<WaylandShmPool> Create(nsWaylandDisplay* aWaylandDisplay,
+ int aSize);
+
+ wl_shm_pool* GetShmPool() { return mShmPool; };
+ void* GetImageData();
+
+ private:
+ WaylandShmPool() = default;
+ ~WaylandShmPool();
+
+ wl_shm_pool* mShmPool = nullptr;
+ void* mImageData = nullptr;
+ UniquePtr<base::SharedMemory> mShm;
+ int mSize = 0;
+};
+
+class WaylandBuffer {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WaylandBuffer);
+
+ void AttachAndCommit(wl_surface* aSurface);
+ virtual wl_buffer* GetWlBuffer() = 0;
+ virtual already_AddRefed<gfx::DrawTarget> Lock() { return nullptr; };
+ virtual void* GetImageData() { return nullptr; }
+ virtual GLuint GetTexture() { return 0; }
+ virtual void DestroyGLResources(){};
+
+ LayoutDeviceIntSize GetSize() { return mSize; };
+ bool IsMatchingSize(const LayoutDeviceIntSize& aSize) {
+ return aSize == mSize;
+ }
+ bool IsAttached() { return mAttached; }
+ static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
+
+ static void BufferReleaseCallbackHandler(void* aData, wl_buffer* aBuffer);
+ void SetBufferReleaseFunc(void (*aBufferReleaseFunc)(void* aData,
+ wl_buffer* aBuffer)) {
+ mBufferReleaseFunc = aBufferReleaseFunc;
+ }
+ void SetBufferReleaseData(void* aBufferReleaseData) {
+ mBufferReleaseData = aBufferReleaseData;
+ }
+
+ protected:
+ explicit WaylandBuffer(const LayoutDeviceIntSize& aSize);
+ virtual ~WaylandBuffer() = default;
+
+ void BufferReleaseCallbackHandler(wl_buffer* aBuffer);
+
+ LayoutDeviceIntSize mSize;
+ bool mAttached = false;
+ void (*mBufferReleaseFunc)(void* aData, wl_buffer* aBuffer) = nullptr;
+ void* mBufferReleaseData = nullptr;
+ static gfx::SurfaceFormat mFormat;
+};
+
+// Holds actual graphics data for wl_surface
+class WaylandBufferSHM final : public WaylandBuffer {
+ public:
+ static RefPtr<WaylandBufferSHM> Create(const LayoutDeviceIntSize& aSize);
+
+ wl_buffer* GetWlBuffer() override { return mWLBuffer; };
+ already_AddRefed<gfx::DrawTarget> Lock() override;
+ void* GetImageData() override { return mShmPool->GetImageData(); }
+
+ void Clear();
+ size_t GetBufferAge() { return mBufferAge; };
+ RefPtr<WaylandShmPool> GetShmPool() { return mShmPool; }
+
+ void IncrementBufferAge() { mBufferAge++; };
+ void ResetBufferAge() { mBufferAge = 0; };
+
+#ifdef MOZ_LOGGING
+ void DumpToFile(const char* aHint);
+#endif
+
+ private:
+ explicit WaylandBufferSHM(const LayoutDeviceIntSize& aSize);
+ ~WaylandBufferSHM() override;
+
+ // WaylandShmPoolMB provides actual shared memory we draw into
+ RefPtr<WaylandShmPool> mShmPool;
+
+ // wl_buffer is a wayland object that encapsulates the shared memory
+ // and passes it to wayland compositor by wl_surface object.
+ wl_buffer* mWLBuffer = nullptr;
+
+ size_t mBufferAge = 0;
+
+#ifdef MOZ_LOGGING
+ static int mDumpSerial;
+ static char* mDumpDir;
+#endif
+};
+
+class WaylandBufferDMABUF final : public WaylandBuffer {
+ public:
+ static RefPtr<WaylandBufferDMABUF> Create(const LayoutDeviceIntSize& aSize,
+ gl::GLContext* aGL);
+
+ wl_buffer* GetWlBuffer() override { return mDMABufSurface->GetWlBuffer(); };
+ GLuint GetTexture() override { return mDMABufSurface->GetTexture(); };
+ void DestroyGLResources() override { mDMABufSurface->ReleaseTextures(); };
+
+ private:
+ explicit WaylandBufferDMABUF(const LayoutDeviceIntSize& aSize);
+ ~WaylandBufferDMABUF() = default;
+
+ RefPtr<DMABufSurfaceRGBA> mDMABufSurface;
+};
+
+} // namespace mozilla::widget
+
+#endif // _MOZILLA_WIDGET_GTK_WAYLAND_BUFFER_H
diff --git a/widget/gtk/WaylandVsyncSource.cpp b/widget/gtk/WaylandVsyncSource.cpp
new file mode 100644
index 0000000000..e5bca04837
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.cpp
@@ -0,0 +1,431 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifdef MOZ_WAYLAND
+
+# include "WaylandVsyncSource.h"
+# include "mozilla/UniquePtr.h"
+# include "nsThreadUtils.h"
+# include "nsISupportsImpl.h"
+# include "MainThreadUtils.h"
+# include "mozilla/ScopeExit.h"
+# include "nsGtkUtils.h"
+# include "mozilla/StaticPrefs_layout.h"
+# include "mozilla/StaticPrefs_widget.h"
+# include "nsWindow.h"
+
+# include <gdk/gdkwayland.h>
+
+# ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetVsync;
+# undef LOG
+# define LOG(str, ...) \
+ MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, \
+ ("[nsWindow %p]: " str, GetWindowForLogging(), ##__VA_ARGS__))
+# else
+# define LOG(...)
+# endif /* MOZ_LOGGING */
+
+using namespace mozilla::widget;
+
+namespace mozilla {
+
+static void WaylandVsyncSourceCallbackHandler(void* aData,
+ struct wl_callback* aCallback,
+ uint32_t aTime) {
+ RefPtr context = static_cast<WaylandVsyncSource*>(aData);
+ context->FrameCallback(aCallback, aTime);
+}
+
+static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
+ WaylandVsyncSourceCallbackHandler};
+
+static void NativeLayerRootWaylandVsyncCallback(void* aData, uint32_t aTime) {
+ RefPtr context = static_cast<WaylandVsyncSource*>(aData);
+ context->FrameCallback(nullptr, aTime);
+}
+
+static float GetFPS(TimeDuration aVsyncRate) {
+ return 1000.0f / float(aVsyncRate.ToMilliseconds());
+}
+
+static nsTArray<WaylandVsyncSource*> gWaylandVsyncSources;
+
+Maybe<TimeDuration> WaylandVsyncSource::GetFastestVsyncRate() {
+ Maybe<TimeDuration> retVal;
+ for (auto* source : gWaylandVsyncSources) {
+ auto rate = source->GetVsyncRateIfEnabled();
+ if (!rate) {
+ continue;
+ }
+ if (!retVal.isSome()) {
+ retVal.emplace(*rate);
+ } else if (*rate < *retVal) {
+ retVal.ref() = *rate;
+ }
+ }
+
+ return retVal;
+}
+
+WaylandVsyncSource::WaylandVsyncSource(nsWindow* aWindow)
+ : mMutex("WaylandVsyncSource"),
+ mVsyncRate(TimeDuration::FromMilliseconds(1000.0 / 60.0)),
+ mLastVsyncTimeStamp(TimeStamp::Now()),
+ mWindow(aWindow),
+ mIdleTimeout(1000 / StaticPrefs::layout_throttled_frame_rate()) {
+ MOZ_ASSERT(NS_IsMainThread());
+ gWaylandVsyncSources.AppendElement(this);
+}
+
+WaylandVsyncSource::~WaylandVsyncSource() {
+ gWaylandVsyncSources.RemoveElement(this);
+}
+
+void WaylandVsyncSource::MaybeUpdateSource(MozContainer* aContainer) {
+ MutexAutoLock lock(mMutex);
+
+ LOG("WaylandVsyncSource::MaybeUpdateSource fps %f", GetFPS(mVsyncRate));
+
+ if (aContainer == mContainer) {
+ LOG(" mContainer is the same, quit.");
+ return;
+ }
+
+ mNativeLayerRoot = nullptr;
+ mContainer = aContainer;
+
+ if (mMonitorEnabled) {
+ LOG(" monitor enabled, ask for Refresh()");
+ mCallbackRequested = false;
+ Refresh(lock);
+ }
+}
+
+void WaylandVsyncSource::MaybeUpdateSource(
+ const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot) {
+ MutexAutoLock lock(mMutex);
+
+ LOG("WaylandVsyncSource::MaybeUpdateSource aNativeLayerRoot fps %f",
+ GetFPS(mVsyncRate));
+
+ if (aNativeLayerRoot == mNativeLayerRoot) {
+ LOG(" mNativeLayerRoot is the same, quit.");
+ return;
+ }
+
+ mNativeLayerRoot = aNativeLayerRoot;
+ mContainer = nullptr;
+
+ if (mMonitorEnabled) {
+ LOG(" monitor enabled, ask for Refresh()");
+ mCallbackRequested = false;
+ Refresh(lock);
+ }
+}
+
+void WaylandVsyncSource::Refresh(const MutexAutoLock& aProofOfLock) {
+ mMutex.AssertCurrentThreadOwns();
+
+ LOG("WaylandVsyncSource::Refresh fps %f\n", GetFPS(mVsyncRate));
+
+ if (!(mContainer || mNativeLayerRoot) || !mMonitorEnabled || !mVsyncEnabled ||
+ mCallbackRequested) {
+ // We don't need to do anything because:
+ // * We are unwanted by our widget or monitor, or
+ // * The last frame callback hasn't yet run to see that it had been shut
+ // down, so we can reuse it after having set mVsyncEnabled to true.
+ LOG(" quit mContainer %d mNativeLayerRoot %d mMonitorEnabled %d "
+ "mVsyncEnabled %d mCallbackRequested %d",
+ !!mContainer, !!mNativeLayerRoot, mMonitorEnabled, mVsyncEnabled,
+ !!mCallbackRequested);
+ return;
+ }
+
+ if (mContainer) {
+ MozContainerSurfaceLock lock(mContainer);
+ struct wl_surface* surface = lock.GetSurface();
+ LOG(" refresh from mContainer, wl_surface %p", surface);
+ if (!surface) {
+ LOG(" we're missing wl_surface, register Refresh() callback");
+ // The surface hasn't been created yet. Try again when the surface is
+ // ready.
+ RefPtr<WaylandVsyncSource> self(this);
+ moz_container_wayland_add_initial_draw_callback_locked(
+ mContainer, [self]() -> void {
+ MutexAutoLock lock(self->mMutex);
+ self->Refresh(lock);
+ });
+ return;
+ }
+ }
+
+ // Vsync is enabled, but we don't have a callback configured. Set one up so
+ // we can get to work.
+ SetupFrameCallback(aProofOfLock);
+ const TimeStamp lastVSync = TimeStamp::Now();
+ mLastVsyncTimeStamp = lastVSync;
+ TimeStamp outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
+
+ {
+ MutexAutoUnlock unlock(mMutex);
+ NotifyVsync(lastVSync, outputTimestamp);
+ }
+}
+
+void WaylandVsyncSource::EnableMonitor() {
+ LOG("WaylandVsyncSource::EnableMonitor");
+ MutexAutoLock lock(mMutex);
+ if (mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = true;
+ Refresh(lock);
+}
+
+void WaylandVsyncSource::DisableMonitor() {
+ LOG("WaylandVsyncSource::DisableMonitor");
+ MutexAutoLock lock(mMutex);
+ if (!mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = false;
+ mCallbackRequested = false;
+}
+
+void WaylandVsyncSource::SetupFrameCallback(const MutexAutoLock& aProofOfLock) {
+ mMutex.AssertCurrentThreadOwns();
+ MOZ_ASSERT(!mCallbackRequested);
+
+ LOG("WaylandVsyncSource::SetupFrameCallback");
+
+ if (mNativeLayerRoot) {
+ LOG(" use mNativeLayerRoot");
+ mNativeLayerRoot->RequestFrameCallback(&NativeLayerRootWaylandVsyncCallback,
+ this);
+ } else {
+ MozContainerSurfaceLock lock(mContainer);
+ struct wl_surface* surface = lock.GetSurface();
+ LOG(" use mContainer, wl_surface %p", surface);
+ if (!surface) {
+ // We don't have a surface, either due to being called before it was made
+ // available in the mozcontainer, or after it was destroyed. We're all
+ // done regardless.
+ LOG(" missing wl_surface, quit.");
+ return;
+ }
+
+ LOG(" register frame callback");
+ MozClearPointer(mCallback, wl_callback_destroy);
+ mCallback = wl_surface_frame(surface);
+ wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
+ this);
+ wl_surface_commit(surface);
+ wl_display_flush(WaylandDisplayGet()->GetDisplay());
+
+ if (!mIdleTimerID) {
+ mIdleTimerID = g_timeout_add(
+ mIdleTimeout,
+ [](void* data) -> gint {
+ RefPtr vsync = static_cast<WaylandVsyncSource*>(data);
+ if (vsync->IdleCallback()) {
+ // We want to fire again, so don't clear mIdleTimerID
+ return G_SOURCE_CONTINUE;
+ }
+ // No need for g_source_remove, caller does it for us.
+ vsync->mIdleTimerID = 0;
+ return G_SOURCE_REMOVE;
+ },
+ this);
+ }
+ }
+
+ mCallbackRequested = true;
+}
+
+bool WaylandVsyncSource::IdleCallback() {
+ LOG("WaylandVsyncSource::IdleCallback");
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ RefPtr<nsWindow> window;
+ TimeStamp lastVSync;
+ TimeStamp outputTimestamp;
+ {
+ MutexAutoLock lock(mMutex);
+
+ if (!mVsyncEnabled || !mMonitorEnabled) {
+ // We are unwanted by either our creator or our consumer, so we just stop
+ // here without setting up a new frame callback.
+ LOG(" quit, mVsyncEnabled %d mMonitorEnabled %d", mVsyncEnabled,
+ mMonitorEnabled);
+ return false;
+ }
+
+ const auto now = TimeStamp::Now();
+ const auto timeSinceLastVSync = now - mLastVsyncTimeStamp;
+ if (timeSinceLastVSync.ToMilliseconds() < mIdleTimeout) {
+ // We're not idle, we want to fire the timer again.
+ return true;
+ }
+
+ LOG(" fire idle vsync");
+ CalculateVsyncRate(lock, now);
+ mLastVsyncTimeStamp = lastVSync = now;
+
+ outputTimestamp = mLastVsyncTimeStamp + mVsyncRate;
+ window = mWindow;
+ }
+
+ // This could disable vsync.
+ window->NotifyOcclusionState(OcclusionState::OCCLUDED);
+
+ if (window->IsDestroyed()) {
+ return false;
+ }
+ // Make sure to fire vsync now even if we get disabled afterwards.
+ // This gives an opportunity to clean up after the visibility state change.
+ // FIXME: Do we really need to do this?
+ NotifyVsync(lastVSync, outputTimestamp);
+ return StaticPrefs::widget_wayland_vsync_keep_firing_at_idle();
+}
+
+void WaylandVsyncSource::FrameCallback(wl_callback* aCallback, uint32_t aTime) {
+ LOG("WaylandVsyncSource::FrameCallback");
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ {
+ // This might enable vsync.
+ RefPtr window = mWindow;
+ window->NotifyOcclusionState(OcclusionState::VISIBLE);
+ // NotifyOcclusionState can destroy us.
+ if (window->IsDestroyed()) {
+ return;
+ }
+ }
+
+ MutexAutoLock lock(mMutex);
+ mCallbackRequested = false;
+
+ // NotifyOcclusionState() can clear and create new mCallback by
+ // EnableVsync()/Refresh(). So don't delete newly created frame callback.
+ if (aCallback && aCallback == mCallback) {
+ MozClearPointer(mCallback, wl_callback_destroy);
+ }
+
+ if (!mVsyncEnabled || !mMonitorEnabled) {
+ // We are unwanted by either our creator or our consumer, so we just stop
+ // here without setting up a new frame callback.
+ LOG(" quit, mVsyncEnabled %d mMonitorEnabled %d", mVsyncEnabled,
+ mMonitorEnabled);
+ return;
+ }
+
+ // Configure our next frame callback.
+ SetupFrameCallback(lock);
+
+ int64_t tick = BaseTimeDurationPlatformUtils::TicksFromMilliseconds(aTime);
+ const auto callbackTimeStamp = TimeStamp::FromSystemTime(tick);
+ const auto now = TimeStamp::Now();
+
+ // If the callback timestamp is close enough to our timestamp, use it,
+ // otherwise use the current time.
+ const TimeStamp& vsyncTimestamp =
+ std::abs((now - callbackTimeStamp).ToMilliseconds()) < 50.0
+ ? callbackTimeStamp
+ : now;
+
+ CalculateVsyncRate(lock, vsyncTimestamp);
+ mLastVsyncTimeStamp = vsyncTimestamp;
+ const TimeStamp outputTimestamp = vsyncTimestamp + mVsyncRate;
+
+ {
+ MutexAutoUnlock unlock(mMutex);
+ NotifyVsync(vsyncTimestamp, outputTimestamp);
+ }
+}
+
+TimeDuration WaylandVsyncSource::GetVsyncRate() {
+ MutexAutoLock lock(mMutex);
+ return mVsyncRate;
+}
+
+Maybe<TimeDuration> WaylandVsyncSource::GetVsyncRateIfEnabled() {
+ MutexAutoLock lock(mMutex);
+ if (!mVsyncEnabled) {
+ return Nothing();
+ }
+ return Some(mVsyncRate);
+}
+
+void WaylandVsyncSource::CalculateVsyncRate(const MutexAutoLock& aProofOfLock,
+ TimeStamp aVsyncTimestamp) {
+ mMutex.AssertCurrentThreadOwns();
+
+ double duration = (aVsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
+ double curVsyncRate = mVsyncRate.ToMilliseconds();
+
+ LOG("WaylandVsyncSource::CalculateVsyncRate start fps %f\n",
+ GetFPS(mVsyncRate));
+
+ double correction;
+ if (duration > curVsyncRate) {
+ correction = fmin(curVsyncRate, (duration - curVsyncRate) / 10);
+ mVsyncRate += TimeDuration::FromMilliseconds(correction);
+ } else {
+ correction = fmin(curVsyncRate / 2, (curVsyncRate - duration) / 10);
+ mVsyncRate -= TimeDuration::FromMilliseconds(correction);
+ }
+
+ LOG(" new fps %f correction %f\n", GetFPS(mVsyncRate), correction);
+}
+
+void WaylandVsyncSource::EnableVsync() {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ MutexAutoLock lock(mMutex);
+
+ LOG("WaylandVsyncSource::EnableVsync fps %f\n", GetFPS(mVsyncRate));
+ if (mVsyncEnabled || mIsShutdown) {
+ LOG(" early quit");
+ return;
+ }
+ mVsyncEnabled = true;
+ Refresh(lock);
+}
+
+void WaylandVsyncSource::DisableVsync() {
+ MutexAutoLock lock(mMutex);
+
+ LOG("WaylandVsyncSource::DisableVsync fps %f\n", GetFPS(mVsyncRate));
+ mVsyncEnabled = false;
+ mCallbackRequested = false;
+}
+
+bool WaylandVsyncSource::IsVsyncEnabled() {
+ MutexAutoLock lock(mMutex);
+ return mVsyncEnabled;
+}
+
+void WaylandVsyncSource::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mMutex);
+
+ LOG("WaylandVsyncSource::Shutdown fps %f\n", GetFPS(mVsyncRate));
+ mContainer = nullptr;
+ mNativeLayerRoot = nullptr;
+ mIsShutdown = true;
+ mVsyncEnabled = false;
+ mCallbackRequested = false;
+ MozClearHandleID(mIdleTimerID, g_source_remove);
+ MozClearPointer(mCallback, wl_callback_destroy);
+}
+
+} // namespace mozilla
+
+#endif // MOZ_WAYLAND
diff --git a/widget/gtk/WaylandVsyncSource.h b/widget/gtk/WaylandVsyncSource.h
new file mode 100644
index 0000000000..78fe72e445
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.h
@@ -0,0 +1,99 @@
+/* -*- Mode: C++; tab-width: 20; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _WaylandVsyncSource_h_
+#define _WaylandVsyncSource_h_
+
+#include "base/thread.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Monitor.h"
+#include "mozilla/layers/NativeLayerWayland.h"
+#include "MozContainer.h"
+#include "nsWaylandDisplay.h"
+#include "VsyncSource.h"
+
+namespace mozilla {
+
+using layers::NativeLayerRootWayland;
+
+/*
+ * WaylandVsyncSource
+ *
+ * This class provides a per-widget VsyncSource under Wayland, emulated using
+ * frame callbacks on the widget surface with empty surface commits.
+ *
+ * Wayland does not expose vsync/vblank, as it considers that an implementation
+ * detail the clients should not concern themselves with. Instead, frame
+ * callbacks are provided whenever the compositor believes it is a good time to
+ * start drawing the next frame for a particular surface, giving us as much
+ * time as possible to do so.
+ *
+ * Note that the compositor sends frame callbacks only when it sees fit, and
+ * when that may be is entirely up to the compositor. One cannot expect a
+ * certain rate of callbacks, or any callbacks at all. Examples of common
+ * variations would be surfaces moved between outputs with different refresh
+ * rates, and surfaces that are hidden and therefore do not receieve any
+ * callbacks at all. Other hypothetical scenarios of variation could be
+ * throttling to conserve power, or because a user has requested it.
+ *
+ */
+class WaylandVsyncSource final : public gfx::VsyncSource {
+ public:
+ explicit WaylandVsyncSource(nsWindow* aWindow);
+ virtual ~WaylandVsyncSource();
+
+ static Maybe<TimeDuration> GetFastestVsyncRate();
+
+ void MaybeUpdateSource(MozContainer* aContainer);
+ void MaybeUpdateSource(
+ const RefPtr<NativeLayerRootWayland>& aNativeLayerRoot);
+
+ void EnableMonitor();
+ void DisableMonitor();
+
+ void FrameCallback(wl_callback* aCallback, uint32_t aTime);
+ // Returns whether we should keep firing.
+ bool IdleCallback();
+
+ TimeDuration GetVsyncRate() override;
+
+ void EnableVsync() override;
+
+ void DisableVsync() override;
+
+ bool IsVsyncEnabled() override;
+
+ void Shutdown() override;
+
+ private:
+ Maybe<TimeDuration> GetVsyncRateIfEnabled();
+
+ void Refresh(const MutexAutoLock& aProofOfLock);
+ void SetupFrameCallback(const MutexAutoLock& aProofOfLock);
+ void CalculateVsyncRate(const MutexAutoLock& aProofOfLock,
+ TimeStamp aVsyncTimestamp);
+ void* GetWindowForLogging() { return mWindow; };
+
+ Mutex mMutex;
+ bool mIsShutdown MOZ_GUARDED_BY(mMutex) = false;
+ bool mVsyncEnabled MOZ_GUARDED_BY(mMutex) = false;
+ bool mMonitorEnabled MOZ_GUARDED_BY(mMutex) = false;
+ bool mCallbackRequested MOZ_GUARDED_BY(mMutex) = false;
+ MozContainer* mContainer MOZ_GUARDED_BY(mMutex) = nullptr;
+ RefPtr<NativeLayerRootWayland> mNativeLayerRoot MOZ_GUARDED_BY(mMutex);
+ TimeDuration mVsyncRate MOZ_GUARDED_BY(mMutex);
+ TimeStamp mLastVsyncTimeStamp MOZ_GUARDED_BY(mMutex);
+ wl_callback* mCallback MOZ_GUARDED_BY(mMutex) = nullptr;
+
+ guint mIdleTimerID = 0; // Main thread only.
+ nsWindow* const mWindow; // Main thread only, except for logging.
+ const guint mIdleTimeout;
+};
+
+} // namespace mozilla
+
+#endif // _WaylandVsyncSource_h_
diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp
new file mode 100644
index 0000000000..b7c1cf8a9f
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -0,0 +1,1434 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include "WidgetStyleCache.h"
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/PodOperations.h"
+#include "mozilla/ScopeExit.h"
+#include "nsDebug.h"
+#include "nsPrintfCString.h"
+#include "nsString.h"
+
+#define STATE_FLAG_DIR_LTR (1U << 7)
+#define STATE_FLAG_DIR_RTL (1U << 8)
+static_assert(GTK_STATE_FLAG_DIR_LTR == STATE_FLAG_DIR_LTR &&
+ GTK_STATE_FLAG_DIR_RTL == STATE_FLAG_DIR_RTL,
+ "incorrect direction state flags");
+
+enum class CSDStyle {
+ Unknown,
+ Solid,
+ Normal,
+};
+
+static bool gHeaderBarShouldDrawContainer = false;
+static bool gMaximizedHeaderBarShouldDrawContainer = false;
+static CSDStyle gCSDStyle = CSDStyle::Unknown;
+static GtkWidget* sWidgetStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+static GtkStyleContext* sStyleStorage[MOZ_GTK_WIDGET_NODE_COUNT];
+
+static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType);
+static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType);
+
+static GtkWidget* CreateWindowWidget() {
+ GtkWidget* widget = gtk_window_new(GTK_WINDOW_POPUP);
+ MOZ_RELEASE_ASSERT(widget, "We're missing GtkWindow widget!");
+ gtk_widget_set_name(widget, "MozillaGtkWidget");
+ return widget;
+}
+
+static GtkWidget* CreateWindowContainerWidget() {
+ GtkWidget* widget = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW)), widget);
+ return widget;
+}
+
+static void AddToWindowContainer(GtkWidget* widget) {
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_WINDOW_CONTAINER)), widget);
+}
+
+static GtkWidget* CreateScrollbarWidget(WidgetNodeType aAppearance,
+ GtkOrientation aOrientation) {
+ GtkWidget* widget = gtk_scrollbar_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateCheckboxWidget() {
+ GtkWidget* widget = gtk_check_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateRadiobuttonWidget() {
+ GtkWidget* widget = gtk_radio_button_new_with_label(nullptr, "M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateMenuPopupWidget() {
+ GtkWidget* widget = gtk_menu_new();
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_POPUP);
+ gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
+ nullptr);
+ return widget;
+}
+
+static GtkWidget* CreateMenuBarWidget() {
+ GtkWidget* widget = gtk_menu_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateProgressWidget() {
+ GtkWidget* widget = gtk_progress_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateTooltipWidget() {
+ MOZ_ASSERT(gtk_check_version(3, 20, 0) != nullptr,
+ "CreateTooltipWidget should be used for Gtk < 3.20 only.");
+ GtkWidget* widget = CreateWindowWidget();
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOOLTIP);
+ return widget;
+}
+
+static GtkWidget* CreateExpanderWidget() {
+ GtkWidget* widget = gtk_expander_new("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateFrameWidget() {
+ GtkWidget* widget = gtk_frame_new(nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateButtonWidget() {
+ GtkWidget* widget = gtk_button_new_with_label("M");
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateToggleButtonWidget() {
+ GtkWidget* widget = gtk_toggle_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateButtonArrowWidget() {
+ GtkWidget* widget = gtk_arrow_new(GTK_ARROW_DOWN, GTK_SHADOW_OUT);
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_TOGGLE_BUTTON)), widget);
+ gtk_widget_show(widget);
+ return widget;
+}
+
+static GtkWidget* CreateSpinWidget() {
+ GtkWidget* widget = gtk_spin_button_new(nullptr, 1, 0);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateEntryWidget() {
+ GtkWidget* widget = gtk_entry_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateComboBoxWidget() {
+ GtkWidget* widget = gtk_combo_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+typedef struct {
+ GType type;
+ GtkWidget** widget;
+} GtkInnerWidgetInfo;
+
+static void GetInnerWidget(GtkWidget* widget, gpointer client_data) {
+ auto info = static_cast<GtkInnerWidgetInfo*>(client_data);
+
+ if (G_TYPE_CHECK_INSTANCE_TYPE(widget, info->type)) {
+ *info->widget = widget;
+ }
+}
+
+static GtkWidget* CreateComboBoxButtonWidget() {
+ GtkWidget* comboBox = GetWidget(MOZ_GTK_COMBOBOX);
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
+ gtk_container_forall(GTK_CONTAINER(comboBox), GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ /* Shouldn't be reached with current internal gtk implementation; we
+ * use a generic toggle button as last resort fallback to avoid
+ * crashing. */
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ /* We need to have pointers to the inner widgets (button, separator, arrow)
+ * of the ComboBox to get the correct rendering from theme engines which
+ * special cases their look. Since the inner layout can change, we ask GTK
+ * to NULL our pointers when they are about to become invalid because the
+ * corresponding widgets don't exist anymore. It's the role of
+ * g_object_add_weak_pointer().
+ * Note that if we don't find the inner widgets (which shouldn't happen), we
+ * fallback to use generic "non-inner" widgets, and they don't need that
+ * kind of weak pointer since they are explicit children of gProtoLayout and
+ * as such GTK holds a strong reference to them. */
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget* CreateComboBoxArrowWidget() {
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* buttonChild = gtk_bin_get_child(GTK_BIN(comboBoxButton));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget* CreateComboBoxSeparatorWidget() {
+ // Ensure to search for separator only once as it can fail
+ // TODO - it won't initialize after ResetWidgetCache() call
+ static bool isMissingSeparator = false;
+ if (isMissingSeparator) return nullptr;
+
+ /* Get the widgets inside the Button */
+ GtkWidget* comboBoxSeparator = nullptr;
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_SEPARATOR, &comboBoxSeparator};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ }
+
+ if (comboBoxSeparator) {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxSeparator),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_SEPARATOR);
+ } else {
+ /* comboBoxSeparator may be NULL
+ * when "appears-as-list" = TRUE or "cell-view" = FALSE;
+ * if there is no separator, then we just won't paint it. */
+ isMissingSeparator = true;
+ }
+
+ return comboBoxSeparator;
+}
+
+static GtkWidget* CreateComboBoxEntryWidget() {
+ GtkWidget* widget = gtk_combo_box_new_with_entry();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateComboBoxEntryTextareaWidget() {
+ GtkWidget* comboBoxTextarea = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ENTRY, &comboBoxTextarea};
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxTextarea) {
+ comboBoxTextarea = GetWidget(MOZ_GTK_ENTRY);
+ } else {
+ g_object_add_weak_pointer(
+ G_OBJECT(comboBoxTextarea),
+ reinterpret_cast<gpointer*>(sWidgetStorage) + MOZ_GTK_COMBOBOX_ENTRY);
+ }
+
+ return comboBoxTextarea;
+}
+
+static GtkWidget* CreateComboBoxEntryButtonWidget() {
+ GtkWidget* comboBoxButton = nullptr;
+
+ /* Get its inner Entry and Button */
+ GtkInnerWidgetInfo info = {GTK_TYPE_TOGGLE_BUTTON, &comboBoxButton};
+ gtk_container_forall(GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_ENTRY)),
+ GetInnerWidget, &info);
+
+ if (!comboBoxButton) {
+ comboBoxButton = GetWidget(MOZ_GTK_TOGGLE_BUTTON);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxButton),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ }
+
+ return comboBoxButton;
+}
+
+static GtkWidget* CreateComboBoxEntryArrowWidget() {
+ GtkWidget* comboBoxArrow = nullptr;
+
+ /* Get the Arrow inside the Button */
+ GtkWidget* buttonChild =
+ gtk_bin_get_child(GTK_BIN(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON)));
+
+ if (GTK_IS_BOX(buttonChild)) {
+ /* appears-as-list = FALSE, cell-view = TRUE; the button
+ * contains an hbox. This hbox is there because the ComboBox
+ * needs to place a cell renderer, a separator, and an arrow in
+ * the button when appears-as-list is FALSE. */
+ GtkInnerWidgetInfo info = {GTK_TYPE_ARROW, &comboBoxArrow};
+ gtk_container_forall(GTK_CONTAINER(buttonChild), GetInnerWidget, &info);
+ } else if (GTK_IS_ARROW(buttonChild)) {
+ /* appears-as-list = TRUE, or cell-view = FALSE;
+ * the button only contains an arrow */
+ comboBoxArrow = buttonChild;
+ }
+
+ if (!comboBoxArrow) {
+ /* Shouldn't be reached with current internal gtk implementation;
+ * we gButtonArrowWidget as last resort fallback to avoid
+ * crashing. */
+ comboBoxArrow = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ } else {
+ g_object_add_weak_pointer(G_OBJECT(comboBoxArrow),
+ reinterpret_cast<gpointer*>(sWidgetStorage) +
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW);
+ }
+
+ return comboBoxArrow;
+}
+
+static GtkWidget* CreateScrolledWindowWidget() {
+ GtkWidget* widget = gtk_scrolled_window_new(nullptr, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateTreeViewWidget() {
+ GtkWidget* widget = gtk_tree_view_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateTreeHeaderCellWidget() {
+ /*
+ * Some GTK engines paint the first and last cell
+ * of a TreeView header with a highlight.
+ * Since we do not know where our widget will be relative
+ * to the other buttons in the TreeView header, we must
+ * paint it as a button that is between two others,
+ * thus ensuring it is neither the first or last button
+ * in the header.
+ * GTK doesn't give us a way to do this explicitly,
+ * so we must paint with a button that is between two
+ * others.
+ */
+ GtkTreeViewColumn* firstTreeViewColumn;
+ GtkTreeViewColumn* middleTreeViewColumn;
+ GtkTreeViewColumn* lastTreeViewColumn;
+
+ GtkWidget* treeView = GetWidget(MOZ_GTK_TREEVIEW);
+
+ /* Create and append our three columns */
+ firstTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(firstTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), firstTreeViewColumn);
+
+ middleTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(middleTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), middleTreeViewColumn);
+
+ lastTreeViewColumn = gtk_tree_view_column_new();
+ gtk_tree_view_column_set_title(lastTreeViewColumn, "M");
+ gtk_tree_view_append_column(GTK_TREE_VIEW(treeView), lastTreeViewColumn);
+
+ /* Use the middle column's header for our button */
+ return gtk_tree_view_column_get_button(middleTreeViewColumn);
+}
+
+static GtkWidget* CreateHPanedWidget() {
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_HORIZONTAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateVPanedWidget() {
+ GtkWidget* widget = gtk_paned_new(GTK_ORIENTATION_VERTICAL);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateScaleWidget(GtkOrientation aOrientation) {
+ GtkWidget* widget = gtk_scale_new(aOrientation, nullptr);
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateNotebookWidget() {
+ GtkWidget* widget = gtk_notebook_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static bool HasBackground(GtkStyleContext* aStyle) {
+ GdkRGBA gdkColor;
+ gtk_style_context_get_background_color(aStyle, GTK_STATE_FLAG_NORMAL,
+ &gdkColor);
+ if (gdkColor.alpha != 0.0) {
+ return true;
+ }
+
+ GValue value = G_VALUE_INIT;
+ gtk_style_context_get_property(aStyle, "background-image",
+ GTK_STATE_FLAG_NORMAL, &value);
+ auto cleanup = mozilla::MakeScopeExit([&] { g_value_unset(&value); });
+ return g_value_get_boxed(&value);
+}
+
+static void CreateHeaderBarWidget(WidgetNodeType aAppearance) {
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
+
+ // Headerbar has to be placed to window with csd or solid-csd style
+ // to properly draw the decorated.
+ gtk_style_context_add_class(windowStyle,
+ IsSolidCSDStyleUsed() ? "solid-csd" : "csd");
+
+ GtkWidget* fixed = gtk_fixed_new();
+ GtkStyleContext* fixedStyle = gtk_widget_get_style_context(fixed);
+ gtk_style_context_add_class(fixedStyle, "titlebar");
+
+ GtkWidget* headerBar = gtk_header_bar_new();
+
+ // Emulate what create_titlebar() at gtkwindow.c does.
+ GtkStyleContext* headerBarStyle = gtk_widget_get_style_context(headerBar);
+ gtk_style_context_add_class(headerBarStyle, "titlebar");
+
+ // TODO: Define default-decoration titlebar style as workaround
+ // to ensure the titlebar buttons does not overflow outside.
+ // Recently the titlebar size is calculated as
+ // tab size + titlebar border/padding (default-decoration has 6px padding
+ // at default Adwaita theme).
+ // We need to fix titlebar size calculation to also include
+ // titlebar button sizes. (Bug 1419442)
+ gtk_style_context_add_class(headerBarStyle, "default-decoration");
+
+ sWidgetStorage[aAppearance] = headerBar;
+ if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
+ MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED],
+ "Window widget is already created!");
+ MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED],
+ "Fixed widget is already created!");
+
+ gtk_style_context_add_class(windowStyle, "maximized");
+
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
+ sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED] = fixed;
+ } else {
+ MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW],
+ "Window widget is already created!");
+ MOZ_ASSERT(!sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED],
+ "Fixed widget is already created!");
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
+ sWidgetStorage[MOZ_GTK_HEADERBAR_FIXED] = fixed;
+ }
+
+ gtk_container_add(GTK_CONTAINER(window), fixed);
+ gtk_container_add(GTK_CONTAINER(fixed), headerBar);
+
+ gtk_style_context_invalidate(headerBarStyle);
+ gtk_style_context_invalidate(fixedStyle);
+
+ // Some themes like Elementary's style the container of the headerbar rather
+ // than the header bar itself.
+ bool& shouldDrawContainer = aAppearance == MOZ_GTK_HEADER_BAR
+ ? gHeaderBarShouldDrawContainer
+ : gMaximizedHeaderBarShouldDrawContainer;
+ shouldDrawContainer = [&] {
+ const bool headerBarHasBackground = HasBackground(headerBarStyle);
+ if (headerBarHasBackground && GetBorderRadius(headerBarStyle)) {
+ return false;
+ }
+ if (HasBackground(fixedStyle) &&
+ (GetBorderRadius(fixedStyle) || !headerBarHasBackground)) {
+ return true;
+ }
+ return false;
+ }();
+}
+
+#define ICON_SCALE_VARIANTS 2
+
+static void LoadWidgetIconPixbuf(GtkWidget* aWidgetIcon) {
+ GtkStyleContext* style = gtk_widget_get_style_context(aWidgetIcon);
+
+ const gchar* iconName;
+ GtkIconSize gtkIconSize;
+ gtk_image_get_icon_name(GTK_IMAGE(aWidgetIcon), &iconName, &gtkIconSize);
+
+ gint iconWidth, iconHeight;
+ gtk_icon_size_lookup(gtkIconSize, &iconWidth, &iconHeight);
+
+ /* Those are available since Gtk+ 3.10 as well as GtkHeaderBar */
+ for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
+ GtkIconInfo* gtkIconInfo = gtk_icon_theme_lookup_icon_for_scale(
+ gtk_icon_theme_get_default(), iconName, iconWidth, scale,
+ (GtkIconLookupFlags)0);
+
+ if (!gtkIconInfo) {
+ // We miss the icon, nothing to do here.
+ return;
+ }
+
+ gboolean unused;
+ GdkPixbuf* iconPixbuf = gtk_icon_info_load_symbolic_for_context(
+ gtkIconInfo, style, &unused, nullptr);
+ g_object_unref(G_OBJECT(gtkIconInfo));
+
+ cairo_surface_t* iconSurface =
+ gdk_cairo_surface_create_from_pixbuf(iconPixbuf, scale, nullptr);
+ g_object_unref(iconPixbuf);
+
+ nsPrintfCString surfaceName("MozillaIconSurface%d", scale);
+ g_object_set_data_full(G_OBJECT(aWidgetIcon), surfaceName.get(),
+ iconSurface, (GDestroyNotify)cairo_surface_destroy);
+ }
+}
+
+cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale) {
+ if (aScale > ICON_SCALE_VARIANTS) {
+ aScale = ICON_SCALE_VARIANTS;
+ }
+
+ nsPrintfCString surfaceName("MozillaIconSurface%d", aScale);
+ return (cairo_surface_t*)g_object_get_data(G_OBJECT(aWidgetIcon),
+ surfaceName.get());
+}
+
+static void CreateHeaderBarButton(GtkWidget* aParentWidget,
+ WidgetNodeType aAppearance) {
+ GtkWidget* widget = gtk_button_new();
+
+ // We have to add button to widget hierarchy now to pick
+ // right icon style at LoadWidgetIconPixbuf().
+ if (GTK_IS_BOX(aParentWidget)) {
+ gtk_box_pack_start(GTK_BOX(aParentWidget), widget, FALSE, FALSE, 0);
+ } else {
+ gtk_container_add(GTK_CONTAINER(aParentWidget), widget);
+ }
+
+ // We bypass GetWidget() here because we create all titlebar
+ // buttons at once when a first one is requested.
+ NS_ASSERTION(!sWidgetStorage[aAppearance],
+ "Titlebar button is already created!");
+ sWidgetStorage[aAppearance] = widget;
+
+ // We need to show the button widget now as GtkBox does not
+ // place invisible widgets and we'll miss first-child/last-child
+ // css selectors at the buttons otherwise.
+ gtk_widget_show(widget);
+
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_add_class(style, "titlebutton");
+
+ GtkWidget* image = nullptr;
+ switch (aAppearance) {
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ gtk_style_context_add_class(style, "close");
+ image = gtk_image_new_from_icon_name("window-close-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ gtk_style_context_add_class(style, "minimize");
+ image = gtk_image_new_from_icon_name("window-minimize-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ gtk_style_context_add_class(style, "maximize");
+ image = gtk_image_new_from_icon_name("window-maximize-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ gtk_style_context_add_class(style, "maximize");
+ image = gtk_image_new_from_icon_name("window-restore-symbolic",
+ GTK_ICON_SIZE_MENU);
+ break;
+ default:
+ break;
+ }
+
+ gtk_widget_set_valign(widget, GTK_ALIGN_CENTER);
+ g_object_set(image, "use-fallback", TRUE, NULL);
+ gtk_container_add(GTK_CONTAINER(widget), image);
+
+ // We bypass GetWidget() here by explicit sWidgetStorage[] update so
+ // invalidate the style as well as GetWidget() does.
+ style = gtk_widget_get_style_context(image);
+ gtk_style_context_invalidate(style);
+
+ LoadWidgetIconPixbuf(image);
+}
+
+static bool IsToolbarButtonEnabled(ButtonLayout* aButtonLayout,
+ size_t aButtonNums,
+ WidgetNodeType aAppearance) {
+ for (size_t i = 0; i < aButtonNums; i++) {
+ if (aButtonLayout[i].mType == aAppearance) {
+ return true;
+ }
+ }
+ return false;
+}
+
+bool IsSolidCSDStyleUsed() {
+ if (gCSDStyle == CSDStyle::Unknown) {
+ bool solid;
+ {
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ gtk_window_set_titlebar(GTK_WINDOW(window), gtk_header_bar_new());
+ gtk_widget_realize(window);
+ GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
+ solid = gtk_style_context_has_class(windowStyle, "solid-csd");
+ gtk_widget_destroy(window);
+ }
+ gCSDStyle = solid ? CSDStyle::Solid : CSDStyle::Normal;
+ }
+ return gCSDStyle == CSDStyle::Solid;
+}
+
+static void CreateHeaderBarButtons() {
+ GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
+ MOZ_ASSERT(headerBar, "We're missing header bar widget!");
+
+ gint buttonSpacing = 6;
+ g_object_get(headerBar, "spacing", &buttonSpacing, nullptr);
+
+ GtkWidget* buttonBox = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, buttonSpacing);
+ gtk_container_add(GTK_CONTAINER(headerBar), buttonBox);
+ // We support only LTR headerbar layout for now.
+ gtk_style_context_add_class(gtk_widget_get_style_context(buttonBox),
+ GTK_STYLE_CLASS_LEFT);
+
+ ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
+
+ size_t activeButtons =
+ GetGtkHeaderBarButtonLayout(mozilla::Span(buttonLayout), nullptr);
+
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+ }
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
+ // We don't pack "restore" headerbar button to box as it's an icon
+ // placeholder. Pack it only to header bar to get correct style.
+ CreateHeaderBarButton(GetWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED),
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE);
+ }
+ if (IsToolbarButtonEnabled(buttonLayout, activeButtons,
+ MOZ_GTK_HEADER_BAR_BUTTON_CLOSE)) {
+ CreateHeaderBarButton(buttonBox, MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ }
+}
+
+static void CreateHeaderBar() {
+ CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR);
+ CreateHeaderBarWidget(MOZ_GTK_HEADER_BAR_MAXIMIZED);
+ CreateHeaderBarButtons();
+}
+
+static GtkWidget* CreateWidget(WidgetNodeType aAppearance) {
+ switch (aAppearance) {
+ case MOZ_GTK_WINDOW:
+ return CreateWindowWidget();
+ case MOZ_GTK_WINDOW_CONTAINER:
+ return CreateWindowContainerWidget();
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ return CreateCheckboxWidget();
+ case MOZ_GTK_PROGRESSBAR:
+ return CreateProgressWidget();
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return CreateRadiobuttonWidget();
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_MENUPOPUP:
+ return CreateMenuPopupWidget();
+ case MOZ_GTK_MENUBAR:
+ return CreateMenuBarWidget();
+ case MOZ_GTK_EXPANDER:
+ return CreateExpanderWidget();
+ case MOZ_GTK_FRAME:
+ return CreateFrameWidget();
+ case MOZ_GTK_SPINBUTTON:
+ return CreateSpinWidget();
+ case MOZ_GTK_BUTTON:
+ return CreateButtonWidget();
+ case MOZ_GTK_TOGGLE_BUTTON:
+ return CreateToggleButtonWidget();
+ case MOZ_GTK_BUTTON_ARROW:
+ return CreateButtonArrowWidget();
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY:
+ return CreateEntryWidget();
+ case MOZ_GTK_SCROLLED_WINDOW:
+ return CreateScrolledWindowWidget();
+ case MOZ_GTK_TREEVIEW:
+ return CreateTreeViewWidget();
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return CreateTreeHeaderCellWidget();
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return CreateHPanedWidget();
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return CreateVPanedWidget();
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ return CreateScaleWidget(GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCALE_VERTICAL:
+ return CreateScaleWidget(GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_NOTEBOOK:
+ return CreateNotebookWidget();
+ case MOZ_GTK_COMBOBOX:
+ return CreateComboBoxWidget();
+ case MOZ_GTK_COMBOBOX_BUTTON:
+ return CreateComboBoxButtonWidget();
+ case MOZ_GTK_COMBOBOX_ARROW:
+ return CreateComboBoxArrowWidget();
+ case MOZ_GTK_COMBOBOX_SEPARATOR:
+ return CreateComboBoxSeparatorWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY:
+ return CreateComboBoxEntryWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA:
+ return CreateComboBoxEntryTextareaWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_BUTTON:
+ return CreateComboBoxEntryButtonWidget();
+ case MOZ_GTK_COMBOBOX_ENTRY_ARROW:
+ return CreateComboBoxEntryArrowWidget();
+ case MOZ_GTK_HEADERBAR_WINDOW:
+ case MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED:
+ case MOZ_GTK_HEADERBAR_FIXED:
+ case MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ /* Create header bar widgets once and fill with child elements as we need
+ the header bar fully configured to get a correct style */
+ CreateHeaderBar();
+ return sWidgetStorage[aAppearance];
+ default:
+ /* Not implemented */
+ return nullptr;
+ }
+}
+
+GtkWidget* GetWidget(WidgetNodeType aAppearance) {
+ GtkWidget* widget = sWidgetStorage[aAppearance];
+ if (!widget) {
+ widget = CreateWidget(aAppearance);
+ // Some widgets (MOZ_GTK_COMBOBOX_SEPARATOR for instance) may not be
+ // available or implemented.
+ if (!widget) {
+ return nullptr;
+ }
+ // In GTK versions prior to 3.18, automatic invalidation of style contexts
+ // for widgets was delayed until the next resize event. Gecko however,
+ // typically uses the style context before the resize event runs and so an
+ // explicit invalidation may be required. This is necessary if a style
+ // property was retrieved before all changes were made to the style
+ // context. One such situation is where gtk_button_construct_child()
+ // retrieves the style property "image-spacing" during construction of the
+ // GtkButton, before its parent is set to provide inheritance of ancestor
+ // properties. More recent GTK versions do not need this, but do not
+ // re-resolve until required and so invalidation does not trigger
+ // unnecessary resolution in general.
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gtk_style_context_invalidate(style);
+
+ sWidgetStorage[aAppearance] = widget;
+ }
+ return widget;
+}
+
+static void AddStyleClassesFromStyle(GtkStyleContext* aDest,
+ GtkStyleContext* aSrc) {
+ GList* classes = gtk_style_context_list_classes(aSrc);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_style_context_add_class(aDest, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+}
+
+GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ GtkStyleContext* aParentStyle) {
+ static auto sGtkWidgetClassGetCSSName =
+ reinterpret_cast<const char* (*)(GtkWidgetClass*)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_class_get_css_name"));
+
+ GtkWidgetClass* widgetClass = GTK_WIDGET_GET_CLASS(aWidget);
+ const gchar* name = sGtkWidgetClassGetCSSName
+ ? sGtkWidgetClassGetCSSName(widgetClass)
+ : nullptr;
+
+ GtkStyleContext* context =
+ CreateCSSNode(name, aParentStyle, G_TYPE_FROM_CLASS(widgetClass));
+
+ // Classes are stored on the style context instead of the path so that any
+ // future gtk_style_context_save() will inherit classes on the head CSS
+ // node, in the same way as happens when called on a style context owned by
+ // a widget.
+ //
+ // Classes can be stored on a GtkCssNodeDeclaration and/or the path.
+ // gtk_style_context_save() reuses the GtkCssNodeDeclaration, and appends a
+ // new object to the path, without copying the classes from the old path
+ // head. The new head picks up classes from the GtkCssNodeDeclaration, but
+ // not the path. GtkWidgets store their classes on the
+ // GtkCssNodeDeclaration, so make sure to add classes there.
+ //
+ // Picking up classes from the style context also means that
+ // https://bugzilla.gnome.org/show_bug.cgi?id=767312, which can stop
+ // gtk_widget_path_append_for_widget() from finding classes in GTK 3.20,
+ // is not a problem.
+ GtkStyleContext* widgetStyle = gtk_widget_get_style_context(aWidget);
+ AddStyleClassesFromStyle(context, widgetStyle);
+
+ // Release any floating reference on aWidget.
+ g_object_ref_sink(aWidget);
+ g_object_unref(aWidget);
+
+ return context;
+}
+
+static GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ WidgetNodeType aParentType) {
+ return CreateStyleForWidget(aWidget, GetWidgetRootStyle(aParentType));
+}
+
+GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
+ GType aType) {
+ static auto sGtkWidgetPathIterSetObjectName =
+ reinterpret_cast<void (*)(GtkWidgetPath*, gint, const char*)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_set_object_name"));
+
+ GtkWidgetPath* path;
+ if (aParentStyle) {
+ path = gtk_widget_path_copy(gtk_style_context_get_path(aParentStyle));
+ // Copy classes from the parent style context to its corresponding node in
+ // the path, because GTK will only match against ancestor classes if they
+ // are on the path.
+ GList* classes = gtk_style_context_list_classes(aParentStyle);
+ for (GList* link = classes; link; link = link->next) {
+ gtk_widget_path_iter_add_class(path, -1, static_cast<gchar*>(link->data));
+ }
+ g_list_free(classes);
+ } else {
+ path = gtk_widget_path_new();
+ }
+
+ gtk_widget_path_append_type(path, aType);
+
+ if (sGtkWidgetPathIterSetObjectName) {
+ (*sGtkWidgetPathIterSetObjectName)(path, -1, aName);
+ }
+
+ GtkStyleContext* context = gtk_style_context_new();
+ gtk_style_context_set_path(context, path);
+ gtk_style_context_set_parent(context, aParentStyle);
+ gtk_widget_path_unref(path);
+
+ // In GTK 3.4, gtk_render_* functions use |theming_engine| on the style
+ // context without ensuring any style resolution sets it appropriately
+ // in style_data_lookup(). e.g.
+ // https://git.gnome.org/browse/gtk+/tree/gtk/gtkstylecontext.c?h=3.4.4#n3847
+ //
+ // That can result in incorrect drawing on first draw. To work around this,
+ // force a style look-up to set |theming_engine|. It is sufficient to do
+ // this only on context creation, instead of after every modification to the
+ // context, because themes typically (Ambiance and oxygen-gtk, at least) set
+ // the "engine" property with the '*' selector.
+ if (GTK_MAJOR_VERSION == 3 && gtk_get_minor_version() < 6) {
+ GdkRGBA unused;
+ gtk_style_context_get_color(context, GTK_STATE_FLAG_NORMAL, &unused);
+ }
+
+ return context;
+}
+
+// Return a style context matching that of the root CSS node of a widget.
+// This is used by all GTK versions.
+static GtkStyleContext* GetWidgetRootStyle(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_MENUITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
+ break;
+ case MOZ_GTK_TEXT_VIEW:
+ style =
+ CreateStyleForWidget(gtk_text_view_new(), MOZ_GTK_SCROLLED_WINDOW);
+ break;
+ case MOZ_GTK_TOOLTIP:
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ // The tooltip style class is added first in CreateTooltipWidget()
+ // and transfered to style in CreateStyleForWidget().
+ GtkWidget* tooltipWindow = CreateTooltipWidget();
+ style = CreateStyleForWidget(tooltipWindow, nullptr);
+ gtk_widget_destroy(tooltipWindow); // Release GtkWindow self-reference.
+ } else {
+ // We create this from the path because GtkTooltipWindow is not public.
+ style = CreateCSSNode("tooltip", nullptr, GTK_TYPE_TOOLTIP);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ }
+ break;
+ case MOZ_GTK_TOOLTIP_BOX:
+ style = CreateStyleForWidget(gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 0),
+ MOZ_GTK_TOOLTIP);
+ break;
+ case MOZ_GTK_TOOLTIP_BOX_LABEL:
+ style = CreateStyleForWidget(gtk_label_new(nullptr), MOZ_GTK_TOOLTIP_BOX);
+ break;
+ default:
+ GtkWidget* widget = GetWidget(aNodeType);
+ MOZ_ASSERT(widget);
+ return gtk_widget_get_style_context(widget);
+ }
+
+ MOZ_ASSERT(style);
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+static GtkStyleContext* CreateChildCSSNode(const char* aName,
+ WidgetNodeType aParentNodeType) {
+ return CreateCSSNode(aName, GetCssNodeStyleInternal(aParentNodeType));
+}
+
+// Create a style context equivalent to a saved root style context of
+// |aAppearance| with |aStyleClass| as an additional class. This is used to
+// produce a context equivalent to what GTK versions < 3.20 use for many
+// internal parts of widgets.
+static GtkStyleContext* CreateSubStyleWithClass(WidgetNodeType aAppearance,
+ const gchar* aStyleClass) {
+ static auto sGtkWidgetPathIterGetObjectName =
+ reinterpret_cast<const char* (*)(const GtkWidgetPath*, gint)>(
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_iter_get_object_name"));
+
+ GtkStyleContext* parentStyle = GetWidgetRootStyle(aAppearance);
+
+ // Create a new context that behaves like |parentStyle| would after
+ // gtk_style_context_save(parentStyle).
+ //
+ // Avoiding gtk_style_context_save() avoids the need to manage the
+ // restore, and a new context permits caching style resolution.
+ //
+ // gtk_style_context_save(context) changes the node hierarchy of |context|
+ // to add a new GtkCssNodeDeclaration that is a copy of its original node.
+ // The new node is a child of the original node, and so the new heirarchy is
+ // one level deeper. The new node receives the same classes as the
+ // original, but any changes to the classes on |context| will change only
+ // the new node. The new node inherits properties from the original node
+ // (which retains the original heirarchy and classes) and matches CSS rules
+ // with the new heirarchy and any changes to the classes.
+ //
+ // The change in hierarchy can produce some surprises in matching theme CSS
+ // rules (e.g. https://bugzilla.gnome.org/show_bug.cgi?id=761870#c2), but it
+ // is important here to produce the same behavior so that rules match the
+ // same widget parts in Gecko as they do in GTK.
+ //
+ // When using public GTK API to construct style contexts, a widget path is
+ // required. CSS rules are not matched against the style context heirarchy
+ // but according to the heirarchy in the widget path. The path that matches
+ // the same CSS rules as a saved context is like the path of |parentStyle|
+ // but with an extra copy of the head (last) object appended. Setting
+ // |parentStyle| as the parent context provides the same inheritance of
+ // properties from the widget root node.
+ const GtkWidgetPath* parentPath = gtk_style_context_get_path(parentStyle);
+ const gchar* name = sGtkWidgetPathIterGetObjectName
+ ? sGtkWidgetPathIterGetObjectName(parentPath, -1)
+ : nullptr;
+ GType objectType = gtk_widget_path_get_object_type(parentPath);
+
+ GtkStyleContext* style = CreateCSSNode(name, parentStyle, objectType);
+
+ // Start with the same classes on the new node as were on |parentStyle|.
+ // GTK puts no regions or junction_sides on widget root nodes, and so there
+ // is no need to copy these.
+ AddStyleClassesFromStyle(style, parentStyle);
+
+ gtk_style_context_add_class(style, aStyleClass);
+ return style;
+}
+
+/* GetCssNodeStyleInternal is used by Gtk >= 3.20 */
+static GtkStyleContext* GetCssNodeStyleInternal(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_RADIOBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO,
+ MOZ_GTK_RADIOBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK,
+ MOZ_GTK_CHECKBUTTON_CONTAINER);
+ break;
+ case MOZ_GTK_PROGRESS_TROUGH:
+ /* Progress bar background (trough) */
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH, MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ style = CreateChildCSSNode("progress", MOZ_GTK_PROGRESS_TROUGH);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ // TODO - create from CSS node
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
+ break;
+ case MOZ_GTK_SCROLLED_WINDOW:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT_SELECTION:
+ style = CreateChildCSSNode("selection", MOZ_GTK_TEXT_VIEW_TEXT);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT:
+ case MOZ_GTK_RESIZER:
+ style = CreateChildCSSNode("text", MOZ_GTK_TEXT_VIEW);
+ if (aNodeType == MOZ_GTK_RESIZER) {
+ // The "grip" class provides the correct builtin icon from
+ // gtk_render_handle(). The icon is drawn with shaded variants of
+ // the background color, and so a transparent background would lead to
+ // a transparent resizer. gtk_render_handle() also uses the
+ // background color to draw a background, and so this style otherwise
+ // matches what is used in GtkTextView to match the background with
+ // textarea elements.
+ GdkRGBA color;
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &color);
+ if (color.alpha == 0.0) {
+ g_object_unref(style);
+ style = CreateStyleForWidget(gtk_text_view_new(),
+ MOZ_GTK_SCROLLED_WINDOW);
+ }
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+ }
+ break;
+ case MOZ_GTK_FRAME_BORDER:
+ style = CreateChildCSSNode("border", MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_TREEVIEW_VIEW:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ // TODO - create from CSS node
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_HORIZONTAL);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ style = CreateChildCSSNode("separator", MOZ_GTK_SPLITTER_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_HORIZONTAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_CONTENTS_VERTICAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCALE_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL);
+ break;
+ case MOZ_GTK_TAB_TOP: {
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ }
+ case MOZ_GTK_TAB_BOTTOM: {
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ }
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW: {
+ // TODO - create from CSS node
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ case MOZ_GTK_WINDOW_DECORATION: {
+ GtkStyleContext* parentStyle =
+ CreateSubStyleWithClass(MOZ_GTK_WINDOW, "csd");
+ style = CreateCSSNode("decoration", parentStyle);
+ g_object_unref(parentStyle);
+ break;
+ }
+ case MOZ_GTK_WINDOW_DECORATION_SOLID: {
+ GtkStyleContext* parentStyle =
+ CreateSubStyleWithClass(MOZ_GTK_WINDOW, "solid-csd");
+ style = CreateCSSNode("decoration", parentStyle);
+ g_object_unref(parentStyle);
+ break;
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+
+ MOZ_ASSERT(style, "missing style context for node type");
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+/* GetWidgetStyleInternal is used by Gtk < 3.20 */
+static GtkStyleContext* GetWidgetStyleInternal(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = sStyleStorage[aNodeType];
+ if (style) return style;
+
+ switch (aNodeType) {
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_RADIOBUTTON:
+ style = CreateSubStyleWithClass(MOZ_GTK_RADIOBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_RADIO);
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ style = CreateSubStyleWithClass(MOZ_GTK_CHECKBUTTON_CONTAINER,
+ GTK_STYLE_CLASS_CHECK);
+ break;
+ case MOZ_GTK_PROGRESS_TROUGH:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR, GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_PROGRESS_CHUNK:
+ style = CreateSubStyleWithClass(MOZ_GTK_PROGRESSBAR,
+ GTK_STYLE_CLASS_PROGRESSBAR);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_SPINBUTTON, GTK_STYLE_CLASS_ENTRY);
+ break;
+ case MOZ_GTK_SCROLLED_WINDOW:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLED_WINDOW,
+ GTK_STYLE_CLASS_FRAME);
+ break;
+ case MOZ_GTK_TEXT_VIEW_TEXT:
+ case MOZ_GTK_RESIZER:
+ // GTK versions prior to 3.20 do not have the view class on the root
+ // node, but add this to determine the background for the text window.
+ style = CreateSubStyleWithClass(MOZ_GTK_TEXT_VIEW, GTK_STYLE_CLASS_VIEW);
+ if (aNodeType == MOZ_GTK_RESIZER) {
+ // The "grip" class provides the correct builtin icon from
+ // gtk_render_handle(). The icon is drawn with shaded variants of
+ // the background color, and so a transparent background would lead to
+ // a transparent resizer. gtk_render_handle() also uses the
+ // background color to draw a background, and so this style otherwise
+ // matches MOZ_GTK_TEXT_VIEW_TEXT to match the background with
+ // textarea elements. GtkTextView creates a separate text window and
+ // so the background should not be transparent.
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_GRIP);
+ }
+ break;
+ case MOZ_GTK_FRAME_BORDER:
+ return GetWidgetRootStyle(MOZ_GTK_FRAME);
+ case MOZ_GTK_TREEVIEW_VIEW:
+ style = CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_VIEW);
+ break;
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_TREEVIEW, GTK_STYLE_CLASS_EXPANDER);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_HORIZONTAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ break;
+ case MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SPLITTER_VERTICAL,
+ GTK_STYLE_CLASS_PANE_SEPARATOR);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCALE_TROUGH_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCALE_VERTICAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ case MOZ_GTK_TAB_TOP:
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_TOP);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ case MOZ_GTK_TAB_BOTTOM:
+ style = CreateSubStyleWithClass(MOZ_GTK_NOTEBOOK, GTK_STYLE_CLASS_BOTTOM);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_TAB,
+ static_cast<GtkRegionFlags>(0));
+ break;
+ case MOZ_GTK_NOTEBOOK:
+ case MOZ_GTK_NOTEBOOK_HEADER:
+ case MOZ_GTK_TABPANELS:
+ case MOZ_GTK_TAB_SCROLLARROW: {
+ GtkWidget* widget = GetWidget(MOZ_GTK_NOTEBOOK);
+ return gtk_widget_get_style_context(widget);
+ }
+ default:
+ return GetWidgetRootStyle(aNodeType);
+ }
+
+ MOZ_ASSERT(style);
+ sStyleStorage[aNodeType] = style;
+ return style;
+}
+
+void ResetWidgetCache() {
+ for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
+ if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
+ }
+ mozilla::PodArrayZero(sStyleStorage);
+
+ gCSDStyle = CSDStyle::Unknown;
+
+ /* This will destroy all of our widgets */
+ if (sWidgetStorage[MOZ_GTK_WINDOW]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_WINDOW]);
+ }
+ if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW]);
+ }
+ if (sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]) {
+ gtk_widget_destroy(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED]);
+ }
+
+ /* Clear already freed arrays */
+ mozilla::PodArrayZero(sWidgetStorage);
+}
+
+GtkStyleContext* GetStyleContext(WidgetNodeType aNodeType, int aScale,
+ GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags) {
+ GtkStyleContext* style;
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ style = GetWidgetStyleInternal(aNodeType);
+ } else {
+ style = GetCssNodeStyleInternal(aNodeType);
+ StyleContextSetScale(style, aScale);
+ }
+ bool stateChanged = false;
+ bool stateHasDirection = gtk_get_minor_version() >= 8;
+ GtkStateFlags oldState = gtk_style_context_get_state(style);
+ MOZ_ASSERT(!(aStateFlags & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL)));
+ unsigned newState = aStateFlags;
+ if (stateHasDirection) {
+ switch (aDirection) {
+ case GTK_TEXT_DIR_LTR:
+ newState |= STATE_FLAG_DIR_LTR;
+ break;
+ case GTK_TEXT_DIR_RTL:
+ newState |= STATE_FLAG_DIR_RTL;
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Bad GtkTextDirection");
+ case GTK_TEXT_DIR_NONE:
+ // GtkWidget uses a default direction if neither is explicitly
+ // specified, but here DIR_NONE is interpreted as meaning the
+ // direction is not important, so don't change the direction
+ // unnecessarily.
+ newState |= oldState & (STATE_FLAG_DIR_LTR | STATE_FLAG_DIR_RTL);
+ }
+ } else if (aDirection != GTK_TEXT_DIR_NONE) {
+ GtkTextDirection oldDirection = gtk_style_context_get_direction(style);
+ if (aDirection != oldDirection) {
+ gtk_style_context_set_direction(style, aDirection);
+ stateChanged = true;
+ }
+ }
+ if (oldState != newState) {
+ gtk_style_context_set_state(style, static_cast<GtkStateFlags>(newState));
+ stateChanged = true;
+ }
+ // This invalidate is necessary for unsaved style contexts from GtkWidgets
+ // in pre-3.18 GTK, because automatic invalidation of such contexts
+ // was delayed until a resize event runs.
+ //
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1272194#c7
+ //
+ // Avoid calling invalidate on contexts that are not owned and constructed
+ // by widgets to avoid performing build_properties() (in 3.16 stylecontext.c)
+ // unnecessarily early.
+ if (stateChanged && sWidgetStorage[aNodeType]) {
+ gtk_style_context_invalidate(style);
+ }
+ return style;
+}
+
+GtkStyleContext* CreateStyleContextWithStates(WidgetNodeType aNodeType,
+ int aScale,
+ GtkTextDirection aDirection,
+ GtkStateFlags aStateFlags) {
+ GtkStyleContext* style =
+ GetStyleContext(aNodeType, aScale, aDirection, aStateFlags);
+ GtkWidgetPath* path = gtk_widget_path_copy(gtk_style_context_get_path(style));
+
+ static auto sGtkWidgetPathIterGetState =
+ (GtkStateFlags(*)(const GtkWidgetPath*, gint))dlsym(
+ RTLD_DEFAULT, "gtk_widget_path_iter_get_state");
+ static auto sGtkWidgetPathIterSetState =
+ (void (*)(GtkWidgetPath*, gint, GtkStateFlags))dlsym(
+ RTLD_DEFAULT, "gtk_widget_path_iter_set_state");
+
+ int pathLength = gtk_widget_path_length(path);
+ for (int i = 0; i < pathLength; i++) {
+ unsigned state = aStateFlags | sGtkWidgetPathIterGetState(path, i);
+ sGtkWidgetPathIterSetState(path, i, GtkStateFlags(state));
+ }
+
+ style = gtk_style_context_new();
+ gtk_style_context_set_path(style, path);
+ gtk_widget_path_unref(path);
+
+ return style;
+}
+
+void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor) {
+ // Support HiDPI styles on Gtk 3.20+
+ static auto sGtkStyleContextSetScalePtr =
+ (void (*)(GtkStyleContext*, gint))dlsym(RTLD_DEFAULT,
+ "gtk_style_context_set_scale");
+ if (sGtkStyleContextSetScalePtr && style) {
+ sGtkStyleContextSetScalePtr(style, aScaleFactor);
+ }
+}
+
+bool HeaderBarShouldDrawContainer(WidgetNodeType aNodeType) {
+ MOZ_ASSERT(aNodeType == MOZ_GTK_HEADER_BAR ||
+ aNodeType == MOZ_GTK_HEADER_BAR_MAXIMIZED);
+ mozilla::Unused << GetWidget(aNodeType);
+ return aNodeType == MOZ_GTK_HEADER_BAR
+ ? gHeaderBarShouldDrawContainer
+ : gMaximizedHeaderBarShouldDrawContainer;
+}
+
+gint GetBorderRadius(GtkStyleContext* aStyle) {
+ GValue value = G_VALUE_INIT;
+ // NOTE(emilio): In an ideal world, we'd query the two longhands
+ // (border-top-left-radius and border-top-right-radius) separately. However,
+ // that doesn't work (GTK rejects the query with:
+ //
+ // Style property "border-top-left-radius" is not gettable
+ //
+ // However! Getting border-radius does work, and it does return the
+ // border-top-left-radius as a gint:
+ //
+ // https://docs.gtk.org/gtk3/const.STYLE_PROPERTY_BORDER_RADIUS.html
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-20/gtk/gtkcssshorthandpropertyimpl.c#L961-977
+ //
+ // So we abuse this fact, and make the assumption here that the
+ // border-top-{left,right}-radius are the same, and roll with it.
+ gtk_style_context_get_property(aStyle, "border-radius", GTK_STATE_FLAG_NORMAL,
+ &value);
+ gint result = 0;
+ auto type = G_VALUE_TYPE(&value);
+ if (type == G_TYPE_INT) {
+ result = g_value_get_int(&value);
+ } else {
+ NS_WARNING(nsPrintfCString("Unknown value type %lu for border-radius", type)
+ .get());
+ }
+ g_value_unset(&value);
+ return result;
+}
diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h
new file mode 100644
index 0000000000..f38b75fae6
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.h
@@ -0,0 +1,63 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidgetStyleCache_h
+#define WidgetStyleCache_h
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+GtkWidget* GetWidget(WidgetNodeType aNodeType);
+
+cairo_surface_t* GetWidgetIconSurface(GtkWidget* aWidgetIcon, int aScale);
+
+/*
+ * Return a new style context based on aWidget, as a child of aParentStyle.
+ * If aWidget still has a floating reference, then it is sunk and released.
+ */
+GtkStyleContext* CreateStyleForWidget(GtkWidget* aWidget,
+ GtkStyleContext* aParentStyle);
+
+GtkStyleContext* CreateCSSNode(const char* aName, GtkStyleContext* aParentStyle,
+ GType aType = G_TYPE_NONE);
+
+/*
+ * Returns a pointer to a style context for the specified node and state.
+ * aStateFlags is applied only to last widget in css style path,
+ * for instance GetStyleContext(MOZ_GTK_BUTTON, .., GTK_STATE_FLAG_HOVER)
+ * you get "window button:hover" css selector.
+ * If you want aStateFlags applied to all path elements use
+ * CreateStyleContextWithStates().
+ *
+ * The context is owned by WidgetStyleCache. Do not unref.
+ */
+GtkStyleContext* GetStyleContext(
+ WidgetNodeType aNodeType, int aScale = 1,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL);
+
+/*
+ * Returns a pointer to a style context for the specified node
+ * and state applied to all elements at widget style path.
+ *
+ * The context is owned by caller and must be released by g_object_unref().
+ */
+GtkStyleContext* CreateStyleContextWithStates(
+ WidgetNodeType aNodeType, int aScale = 1,
+ GtkTextDirection aDirection = GTK_TEXT_DIR_NONE,
+ GtkStateFlags aStateFlags = GTK_STATE_FLAG_NORMAL);
+
+void ResetWidgetCache();
+
+bool IsSolidCSDStyleUsed();
+
+void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor);
+
+gint GetBorderRadius(GtkStyleContext* aStyle);
+
+bool HeaderBarShouldDrawContainer(WidgetNodeType);
+
+#endif // WidgetStyleCache_h
diff --git a/widget/gtk/WidgetTraceEvent.cpp b/widget/gtk/WidgetTraceEvent.cpp
new file mode 100644
index 0000000000..161e5302cc
--- /dev/null
+++ b/widget/gtk/WidgetTraceEvent.cpp
@@ -0,0 +1,68 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/WidgetTraceEvent.h"
+
+#include <glib.h>
+#include <mozilla/CondVar.h>
+#include <mozilla/Mutex.h>
+#include <stdio.h>
+
+using mozilla::CondVar;
+using mozilla::Mutex;
+using mozilla::MutexAutoLock;
+
+namespace {
+
+Mutex* sMutex = nullptr;
+CondVar* sCondVar = nullptr;
+bool sTracerProcessed = false;
+
+// This function is called from the main (UI) thread.
+gboolean TracerCallback(gpointer data) {
+ mozilla::SignalTracerThread();
+ return FALSE;
+}
+
+} // namespace
+
+namespace mozilla {
+
+bool InitWidgetTracing() {
+ sMutex = new Mutex("Event tracer thread mutex");
+ sCondVar = new CondVar(*sMutex, "Event tracer thread condvar");
+ return true;
+}
+
+void CleanUpWidgetTracing() {
+ delete sMutex;
+ delete sCondVar;
+ sMutex = nullptr;
+ sCondVar = nullptr;
+}
+
+// This function is called from the background tracer thread.
+bool FireAndWaitForTracerEvent() {
+ MOZ_ASSERT(sMutex && sCondVar, "Tracing not initialized!");
+
+ // Send a default-priority idle event through the
+ // event loop, and wait for it to finish.
+ MutexAutoLock lock(*sMutex);
+ MOZ_ASSERT(!sTracerProcessed, "Tracer synchronization state is wrong");
+ g_idle_add_full(G_PRIORITY_DEFAULT, TracerCallback, nullptr, nullptr);
+ while (!sTracerProcessed) sCondVar->Wait();
+ sTracerProcessed = false;
+ return true;
+}
+
+void SignalTracerThread() {
+ if (!sMutex || !sCondVar) return;
+ MutexAutoLock lock(*sMutex);
+ if (!sTracerProcessed) {
+ sTracerProcessed = true;
+ sCondVar->Notify();
+ }
+}
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.cpp b/widget/gtk/WidgetUtilsGtk.cpp
new file mode 100644
index 0000000000..2ce81dedb7
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.cpp
@@ -0,0 +1,500 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WidgetUtilsGtk.h"
+
+#include "MainThreadUtils.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/UniquePtr.h"
+#include "nsReadableUtils.h"
+#include "nsString.h"
+#include "nsStringFwd.h"
+#include "nsWindow.h"
+#include "nsIGfxInfo.h"
+#include "mozilla/Components.h"
+#include "nsCOMPtr.h"
+#include "nsIProperties.h"
+#include "nsIFile.h"
+#include "nsXULAppAPI.h"
+#include "nsXPCOMCID.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsGtkKeyUtils.h"
+#include "nsGtkUtils.h"
+
+#include <gtk/gtk.h>
+#include <dlfcn.h>
+#include <glib.h>
+
+#undef LOGW
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOGW(...) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOGW(...)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla::widget {
+
+int32_t WidgetUtilsGTK::IsTouchDeviceSupportPresent() {
+ int32_t result = 0;
+ GdkDisplay* display = gdk_display_get_default();
+ if (!display) {
+ return 0;
+ }
+
+ GdkDeviceManager* manager = gdk_display_get_device_manager(display);
+ if (!manager) {
+ return 0;
+ }
+
+ GList* devices =
+ gdk_device_manager_list_devices(manager, GDK_DEVICE_TYPE_SLAVE);
+ GList* list = devices;
+
+ while (devices) {
+ GdkDevice* device = static_cast<GdkDevice*>(devices->data);
+ if (gdk_device_get_source(device) == GDK_SOURCE_TOUCHSCREEN) {
+ result = 1;
+ break;
+ }
+ devices = devices->next;
+ }
+
+ if (list) {
+ g_list_free(list);
+ }
+
+ return result;
+}
+
+bool IsMainWindowTransparent() {
+ return nsWindow::IsToplevelWindowTransparent();
+}
+
+// We avoid linking gdk_*_display_get_type directly in order to avoid a runtime
+// dependency on GTK built with both backends. Other X11- and Wayland-specific
+// functions get stubbed out by libmozgtk and crash when called, but those
+// should only be called when the matching backend is already in use.
+
+bool GdkIsWaylandDisplay(GdkDisplay* display) {
+ static auto sGdkWaylandDisplayGetType =
+ (GType(*)())dlsym(RTLD_DEFAULT, "gdk_wayland_display_get_type");
+ return sGdkWaylandDisplayGetType &&
+ G_TYPE_CHECK_INSTANCE_TYPE(display, sGdkWaylandDisplayGetType());
+}
+
+bool GdkIsX11Display(GdkDisplay* display) {
+ static auto sGdkX11DisplayGetType =
+ (GType(*)())dlsym(RTLD_DEFAULT, "gdk_x11_display_get_type");
+ return sGdkX11DisplayGetType &&
+ G_TYPE_CHECK_INSTANCE_TYPE(display, sGdkX11DisplayGetType());
+}
+
+bool IsXWaylandProtocol() {
+ static bool isXwayland = [] {
+ return !GdkIsWaylandDisplay() && !!getenv("WAYLAND_DISPLAY");
+ }();
+ return isXwayland;
+}
+
+bool GdkIsWaylandDisplay() {
+ static bool isWaylandDisplay = gdk_display_get_default() &&
+ GdkIsWaylandDisplay(gdk_display_get_default());
+ return isWaylandDisplay;
+}
+
+bool GdkIsX11Display() {
+ static bool isX11Display = gdk_display_get_default()
+ ? GdkIsX11Display(gdk_display_get_default())
+ : false;
+ return isX11Display;
+}
+
+GdkDevice* GdkGetPointer() {
+ GdkDisplay* display = gdk_display_get_default();
+ GdkDeviceManager* deviceManager = gdk_display_get_device_manager(display);
+ return gdk_device_manager_get_client_pointer(deviceManager);
+}
+
+static GdkEvent* sLastMousePressEvent = nullptr;
+GdkEvent* GetLastMousePressEvent() { return sLastMousePressEvent; }
+
+void SetLastMousePressEvent(GdkEvent* aEvent) {
+ if (sLastMousePressEvent) {
+ GUniquePtr<GdkEvent> event(sLastMousePressEvent);
+ sLastMousePressEvent = nullptr;
+ }
+ if (!aEvent) {
+ return;
+ }
+ GUniquePtr<GdkEvent> event(gdk_event_copy(aEvent));
+ sLastMousePressEvent = event.release();
+}
+
+bool IsRunningUnderSnap() { return !!GetSnapInstanceName(); }
+
+bool IsRunningUnderFlatpak() {
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/4300a5c609306ce77cbc8a3580c19201dccd8d13/gdk/gdk.c#L472
+ static bool sRunning = [] {
+ return g_file_test("/.flatpak-info", G_FILE_TEST_EXISTS);
+ }();
+ return sRunning;
+}
+
+bool IsPackagedAppFileExists() {
+ static bool sRunning = [] {
+ nsresult rv;
+ nsCString path;
+ nsCOMPtr<nsIFile> file;
+ nsCOMPtr<nsIProperties> directoryService;
+
+ directoryService = do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(directoryService, FALSE);
+
+ rv = directoryService->Get(NS_GRE_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ rv = file->AppendNative("is-packaged-app"_ns);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ rv = file->GetNativePath(path);
+ NS_ENSURE_SUCCESS(rv, FALSE);
+
+ return g_file_test(path.get(), G_FILE_TEST_EXISTS);
+ }();
+ return sRunning;
+}
+
+const char* GetSnapInstanceName() {
+ static const char* sInstanceName = []() -> const char* {
+ const char* snapName = g_getenv("SNAP_NAME");
+ if (!snapName) {
+ return nullptr;
+ }
+ if (g_strcmp0(snapName, MOZ_APP_NAME)) {
+ return nullptr;
+ }
+ // Intentionally leaked, as keeping a pointer to the environment forever
+ // is a bit suspicious.
+ if (const char* instanceName = g_getenv("SNAP_INSTANCE_NAME")) {
+ return g_strdup(instanceName);
+ }
+ // Instance name didn't exist for snapd <= 2.35:
+ return g_strdup(snapName);
+ }();
+ return sInstanceName;
+}
+
+bool ShouldUsePortal(PortalKind aPortalKind) {
+ static bool sPortalEnv = [] {
+ if (IsRunningUnderFlatpakOrSnap()) {
+ return true;
+ }
+ const char* portalEnvString = g_getenv("GTK_USE_PORTAL");
+ return portalEnvString && atoi(portalEnvString) != 0;
+ }();
+
+ bool autoBehavior = sPortalEnv;
+ const int32_t pref = [&] {
+ switch (aPortalKind) {
+ case PortalKind::FilePicker:
+ return StaticPrefs::widget_use_xdg_desktop_portal_file_picker();
+ case PortalKind::MimeHandler:
+ // Mime portal breaks default browser handling, see bug 1516290.
+ autoBehavior = IsRunningUnderFlatpakOrSnap();
+ return StaticPrefs::widget_use_xdg_desktop_portal_mime_handler();
+ case PortalKind::Settings:
+ autoBehavior = true;
+ return StaticPrefs::widget_use_xdg_desktop_portal_settings();
+ case PortalKind::Location:
+ return StaticPrefs::widget_use_xdg_desktop_portal_location();
+ case PortalKind::OpenUri:
+ return StaticPrefs::widget_use_xdg_desktop_portal_open_uri();
+ }
+ return 2;
+ }();
+
+ switch (pref) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return autoBehavior;
+ }
+}
+
+nsTArray<nsCString> ParseTextURIList(const nsACString& aData) {
+ UniquePtr<char[]> data(ToNewCString(aData));
+ gchar** uris = g_uri_list_extract_uris(data.get());
+
+ nsTArray<nsCString> result;
+ for (size_t i = 0; i < g_strv_length(uris); i++) {
+ result.AppendElement(nsCString(uris[i]));
+ }
+
+ g_strfreev(uris);
+ return result;
+}
+
+#ifdef MOZ_WAYLAND
+static gboolean token_failed(gpointer aData);
+
+class XDGTokenRequest {
+ public:
+ void SetTokenID(const char* aTokenID) {
+ LOGW("RequestWaylandFocusPromise() SetTokenID %s", aTokenID);
+ mTransferPromise->Resolve(aTokenID, __func__);
+ }
+ void Cancel() {
+ LOGW("RequestWaylandFocusPromise() canceled");
+ mTransferPromise->Reject(false, __func__);
+ mActivationTimeoutID = 0;
+ }
+
+ XDGTokenRequest(xdg_activation_token_v1* aXdgToken,
+ RefPtr<FocusRequestPromise::Private> aTransferPromise)
+ : mXdgToken(aXdgToken), mTransferPromise(std::move(aTransferPromise)) {
+ mActivationTimeoutID =
+ g_timeout_add(sActivationTimeout, token_failed, this);
+ }
+ ~XDGTokenRequest() {
+ MozClearPointer(mXdgToken, xdg_activation_token_v1_destroy);
+ if (mActivationTimeoutID) {
+ g_source_remove(mActivationTimeoutID);
+ }
+ }
+
+ private:
+ xdg_activation_token_v1* mXdgToken;
+ RefPtr<FocusRequestPromise::Private> mTransferPromise;
+ guint mActivationTimeoutID;
+ // Reject FocusRequestPromise if we don't get XDG token in 0.5 sec.
+ static constexpr int sActivationTimeout = 500;
+};
+
+// Failed to get token in time
+static gboolean token_failed(gpointer data) {
+ UniquePtr<XDGTokenRequest> request(static_cast<XDGTokenRequest*>(data));
+ request->Cancel();
+ return false;
+}
+
+// We've got activation token from Wayland compositor so it's time to use it.
+static void token_done(gpointer data, struct xdg_activation_token_v1* provider,
+ const char* tokenID) {
+ UniquePtr<XDGTokenRequest> request(static_cast<XDGTokenRequest*>(data));
+ request->SetTokenID(tokenID);
+}
+
+static const struct xdg_activation_token_v1_listener token_listener = {
+ token_done,
+};
+#endif
+
+RefPtr<FocusRequestPromise> RequestWaylandFocusPromise() {
+#ifdef MOZ_WAYLAND
+ if (!GdkIsWaylandDisplay() || !KeymapWrapper::GetSeat()) {
+ LOGW("RequestWaylandFocusPromise() failed.");
+ return nullptr;
+ }
+
+ RefPtr<nsWindow> sourceWindow = nsWindow::GetFocusedWindow();
+ if (!sourceWindow || sourceWindow->IsDestroyed()) {
+ LOGW("RequestWaylandFocusPromise() missing source window");
+ return nullptr;
+ }
+
+ xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
+ if (!xdg_activation) {
+ LOGW("RequestWaylandFocusPromise() missing xdg_activation");
+ return nullptr;
+ }
+
+ wl_surface* focusSurface;
+ uint32_t focusSerial;
+ KeymapWrapper::GetFocusInfo(&focusSurface, &focusSerial);
+ if (!focusSurface) {
+ LOGW("RequestWaylandFocusPromise() missing focusSurface");
+ return nullptr;
+ }
+
+ GdkWindow* gdkWindow = sourceWindow->GetToplevelGdkWindow();
+ if (!gdkWindow) {
+ return nullptr;
+ }
+ wl_surface* surface = gdk_wayland_window_get_wl_surface(gdkWindow);
+ if (focusSurface != surface) {
+ LOGW("RequestWaylandFocusPromise() missing wl_surface");
+ return nullptr;
+ }
+
+ RefPtr<FocusRequestPromise::Private> transferPromise =
+ new FocusRequestPromise::Private(__func__);
+
+ xdg_activation_token_v1* aXdgToken =
+ xdg_activation_v1_get_activation_token(xdg_activation);
+ xdg_activation_token_v1_add_listener(
+ aXdgToken, &token_listener,
+ new XDGTokenRequest(aXdgToken, transferPromise));
+ xdg_activation_token_v1_set_serial(aXdgToken, focusSerial,
+ KeymapWrapper::GetSeat());
+ xdg_activation_token_v1_set_surface(aXdgToken, focusSurface);
+ xdg_activation_token_v1_commit(aXdgToken);
+
+ LOGW("RequestWaylandFocusPromise() XDG Token sent");
+
+ return transferPromise.forget();
+#else // !defined(MOZ_WAYLAND)
+ return nullptr;
+#endif
+}
+
+// https://specifications.freedesktop.org/wm-spec/1.3/ar01s05.html
+static nsCString GetWindowManagerName() {
+ if (!GdkIsX11Display()) {
+ return {};
+ }
+
+#ifdef MOZ_X11
+ Display* xdisplay = gdk_x11_get_default_xdisplay();
+ Window root_win =
+ GDK_WINDOW_XID(gdk_screen_get_root_window(gdk_screen_get_default()));
+
+ int actual_format_return;
+ Atom actual_type_return;
+ unsigned long nitems_return;
+ unsigned long bytes_after_return;
+ unsigned char* prop_return = nullptr;
+ auto releaseXProperty = MakeScopeExit([&] {
+ if (prop_return) {
+ XFree(prop_return);
+ }
+ });
+
+ Atom property = XInternAtom(xdisplay, "_NET_SUPPORTING_WM_CHECK", true);
+ Atom req_type = XInternAtom(xdisplay, "WINDOW", true);
+ if (!property || !req_type) {
+ return {};
+ }
+ int result =
+ XGetWindowProperty(xdisplay, root_win, property,
+ 0L, // offset
+ sizeof(Window) / 4, // length
+ false, // delete
+ req_type, &actual_type_return, &actual_format_return,
+ &nitems_return, &bytes_after_return, &prop_return);
+
+ if (result != Success || bytes_after_return != 0 || nitems_return != 1) {
+ return {};
+ }
+
+ Window wmWindow = reinterpret_cast<Window*>(prop_return)[0];
+ if (!wmWindow) {
+ return {};
+ }
+
+ XFree(prop_return);
+ prop_return = nullptr;
+
+ property = XInternAtom(xdisplay, "_NET_WM_NAME", true);
+ req_type = XInternAtom(xdisplay, "UTF8_STRING", true);
+ if (!property || !req_type) {
+ return {};
+ }
+ result =
+ XGetWindowProperty(xdisplay, wmWindow, property,
+ 0L, // offset
+ INT32_MAX, // length
+ false, // delete
+ req_type, &actual_type_return, &actual_format_return,
+ &nitems_return, &bytes_after_return, &prop_return);
+ if (result != Success || bytes_after_return != 0) {
+ return {};
+ }
+
+ return nsCString(reinterpret_cast<const char*>(prop_return));
+#else
+ return {};
+#endif
+}
+
+// Getting a reliable identifier is quite tricky. We try to use the standard
+// XDG_CURRENT_DESKTOP environment first, _NET_WM_NAME later, and a set of
+// legacy / non-standard environment variables otherwise.
+//
+// Documentation for some of those can be found in:
+//
+// https://wiki.archlinux.org/title/Environment_variables#Examples
+// https://wiki.archlinux.org/title/Xdg-utils#Environment_variables
+const nsCString& GetDesktopEnvironmentIdentifier() {
+ MOZ_ASSERT(NS_IsMainThread());
+ static const nsDependentCString sIdentifier = [] {
+ nsCString ident = [] {
+ auto Env = [](const char* aKey) -> const char* {
+ const char* v = getenv(aKey);
+ return v && *v ? v : nullptr;
+ };
+ if (const char* currentDesktop = Env("XDG_CURRENT_DESKTOP")) {
+ return nsCString(currentDesktop);
+ }
+ if (auto wm = GetWindowManagerName(); !wm.IsEmpty()) {
+ return wm;
+ }
+ if (const char* sessionDesktop = Env("XDG_SESSION_DESKTOP")) {
+ // This is not really standardized in freedesktop.org, but it is
+ // documented here, and should be set in systemd systems.
+ // https://www.freedesktop.org/software/systemd/man/pam_systemd.html#%24XDG_SESSION_DESKTOP
+ return nsCString(sessionDesktop);
+ }
+ // We try first the DE-specific variables, then SESSION_DESKTOP, to match
+ // the documented order in:
+ // https://wiki.archlinux.org/title/Xdg-utils#Environment_variables
+ if (getenv("GNOME_DESKTOP_SESSION_ID")) {
+ return nsCString("gnome"_ns);
+ }
+ if (getenv("KDE_FULL_SESSION")) {
+ return nsCString("kde"_ns);
+ }
+ if (getenv("MATE_DESKTOP_SESSION_ID")) {
+ return nsCString("mate"_ns);
+ }
+ if (getenv("LXQT_SESSION_CONFIG")) {
+ return nsCString("lxqt"_ns);
+ }
+ if (const char* desktopSession = Env("DESKTOP_SESSION")) {
+ // Try the legacy DESKTOP_SESSION as a last resort.
+ return nsCString(desktopSession);
+ }
+ return nsCString();
+ }();
+ ToLowerCase(ident);
+ // Intentionally put into a ToNewCString copy, rather than just making a
+ // static nsCString to avoid leakchecking errors, since we really want to
+ // leak this string.
+ return nsDependentCString(ToNewCString(ident), ident.Length());
+ }();
+ return sIdentifier;
+}
+
+bool IsGnomeDesktopEnvironment() {
+ static bool sIsGnome =
+ !!FindInReadable("gnome"_ns, GetDesktopEnvironmentIdentifier());
+ return sIsGnome;
+}
+
+bool IsKdeDesktopEnvironment() {
+ static bool sIsKde = GetDesktopEnvironmentIdentifier().EqualsLiteral("kde");
+ return sIsKde;
+}
+
+bool IsCancelledGError(GError* aGError) {
+ return g_error_matches(aGError, G_IO_ERROR, G_IO_ERROR_CANCELLED);
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h
new file mode 100644
index 0000000000..ffd7425ae5
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef WidgetUtilsGtk_h__
+#define WidgetUtilsGtk_h__
+
+#include "nsString.h"
+#include "nsTArray.h"
+#include "mozilla/MozPromise.h"
+
+#include <stdint.h>
+
+typedef struct _GdkDisplay GdkDisplay;
+typedef struct _GdkDevice GdkDevice;
+typedef struct _GError GError;
+typedef union _GdkEvent GdkEvent;
+class nsWindow;
+
+namespace mozilla::widget {
+
+class WidgetUtilsGTK {
+ public:
+ /* See WidgetUtils::IsTouchDeviceSupportPresent(). */
+ static int32_t IsTouchDeviceSupportPresent();
+};
+
+bool IsMainWindowTransparent();
+
+bool GdkIsWaylandDisplay(GdkDisplay* display);
+bool GdkIsX11Display(GdkDisplay* display);
+
+bool GdkIsWaylandDisplay();
+bool GdkIsX11Display();
+
+bool IsXWaylandProtocol();
+
+GdkDevice* GdkGetPointer();
+
+// Sets / returns the last mouse press event we processed.
+void SetLastMousePressEvent(GdkEvent*);
+GdkEvent* GetLastMousePressEvent();
+
+// Return the snap's instance name, or null when not running as a snap.
+const char* GetSnapInstanceName();
+bool IsRunningUnderSnap();
+bool IsRunningUnderFlatpak();
+bool IsPackagedAppFileExists();
+inline bool IsRunningUnderFlatpakOrSnap() {
+ return IsRunningUnderFlatpak() || IsRunningUnderSnap();
+}
+
+enum class PortalKind {
+ FilePicker,
+ MimeHandler,
+ Settings,
+ Location,
+ OpenUri,
+};
+bool ShouldUsePortal(PortalKind);
+
+// Tries to get a descriptive identifier for the desktop environment that the
+// program is running under. Always normalized to lowercase.
+// See the implementation for the different environment variables and desktop
+// information we try to use.
+//
+// If we can't find a reasonable environment, the empty string is returned.
+const nsCString& GetDesktopEnvironmentIdentifier();
+bool IsGnomeDesktopEnvironment();
+bool IsKdeDesktopEnvironment();
+
+// Parse text/uri-list
+nsTArray<nsCString> ParseTextURIList(const nsACString& data);
+
+using FocusRequestPromise = MozPromise<nsCString, bool, false>;
+RefPtr<FocusRequestPromise> RequestWaylandFocusPromise();
+
+bool IsCancelledGError(GError* aGError);
+
+} // namespace mozilla::widget
+
+#endif // WidgetUtilsGtk_h__
diff --git a/widget/gtk/WindowSurface.h b/widget/gtk/WindowSurface.h
new file mode 100644
index 0000000000..858ddde59e
--- /dev/null
+++ b/widget/gtk/WindowSurface.h
@@ -0,0 +1,41 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_WINDOW_SURFACE_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_H
+
+#include "mozilla/gfx/2D.h"
+#include "Units.h"
+
+namespace mozilla {
+namespace widget {
+
+// A class for drawing to double-buffered windows.
+class WindowSurface {
+ public:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowSurface);
+
+ // Locks a region of the window for drawing, returning a draw target
+ // capturing the bounds of the provided region.
+ // Implementations must permit invocation from any thread.
+ virtual already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) = 0;
+
+ // Swaps the provided invalid region from the back buffer to the window.
+ // Implementations must permit invocation from any thread.
+ virtual void Commit(const LayoutDeviceIntRegion& aInvalidRegion) = 0;
+
+ // Whether the window surface represents a fallback method.
+ virtual bool IsFallback() const { return false; }
+
+ protected:
+ virtual ~WindowSurface() = default;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_H
diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp
new file mode 100644
index 0000000000..b346f0783d
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceProvider.h"
+
+#include "gfxPlatformGtk.h"
+#include "GtkCompositorWidget.h"
+#include "mozilla/gfx/Logging.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsWindow.h"
+
+#ifdef MOZ_WAYLAND
+# include "mozilla/StaticPrefs_widget.h"
+# include "WindowSurfaceWaylandMultiBuffer.h"
+#endif
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+# include "WindowSurfaceX11Image.h"
+# include "WindowSurfaceX11SHM.h"
+#endif
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOG(args)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::layers;
+
+WindowSurfaceProvider::WindowSurfaceProvider()
+ : mWindowSurface(nullptr),
+ mMutex("WindowSurfaceProvider"),
+ mWindowSurfaceValid(false)
+#ifdef MOZ_X11
+ ,
+ mIsShaped(false),
+ mXDepth(0),
+ mXWindow(0),
+ mXVisual(nullptr)
+#endif
+{
+}
+
+WindowSurfaceProvider::~WindowSurfaceProvider() {
+#ifdef MOZ_WAYLAND
+ MOZ_DIAGNOSTIC_ASSERT(!mWidget,
+ "nsWindow reference is still live, we're leaking it!");
+#endif
+#ifdef MOZ_X11
+ MOZ_DIAGNOSTIC_ASSERT(!mXWindow, "mXWindow should be released on quit!");
+#endif
+}
+
+#ifdef MOZ_WAYLAND
+void WindowSurfaceProvider::Initialize(RefPtr<nsWindow> aWidget) {
+ mWindowSurfaceValid = false;
+ mWidget = std::move(aWidget);
+}
+void WindowSurfaceProvider::Initialize(GtkCompositorWidget* aCompositorWidget) {
+ mWindowSurfaceValid = false;
+ mCompositorWidget = aCompositorWidget;
+ mWidget = static_cast<nsWindow*>(aCompositorWidget->RealWidget());
+}
+#endif
+#ifdef MOZ_X11
+void WindowSurfaceProvider::Initialize(Window aWindow, Visual* aVisual,
+ int aDepth, bool aIsShaped) {
+ mWindowSurfaceValid = false;
+ mXWindow = aWindow;
+ mXVisual = aVisual;
+ mXDepth = aDepth;
+ mIsShaped = aIsShaped;
+}
+#endif
+
+void WindowSurfaceProvider::CleanupResources() {
+ MutexAutoLock lock(mMutex);
+ mWindowSurfaceValid = false;
+#ifdef MOZ_WAYLAND
+ mWidget = nullptr;
+#endif
+#ifdef MOZ_X11
+ mXWindow = 0;
+ mXVisual = 0;
+ mXDepth = 0;
+ mIsShaped = false;
+#endif
+}
+
+RefPtr<WindowSurface> WindowSurfaceProvider::CreateWindowSurface() {
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ // We're called too early or we're unmapped.
+ if (!mWidget) {
+ return nullptr;
+ }
+ return MakeRefPtr<WindowSurfaceWaylandMB>(mWidget, mCompositorWidget);
+ }
+#endif
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // We're called too early or we're unmapped.
+ if (!mXWindow) {
+ return nullptr;
+ }
+ // Blit to the window with the following priority:
+ // 1. MIT-SHM
+ // 2. XPutImage
+# ifdef MOZ_HAVE_SHMIMAGE
+ if (!mIsShaped && nsShmImage::UseShm()) {
+ LOG(("Drawing to Window 0x%lx will use MIT-SHM\n", mXWindow));
+ return MakeRefPtr<WindowSurfaceX11SHM>(DefaultXDisplay(), mXWindow,
+ mXVisual, mXDepth);
+ }
+# endif // MOZ_HAVE_SHMIMAGE
+
+ LOG(("Drawing to Window 0x%lx will use XPutImage\n", mXWindow));
+ return MakeRefPtr<WindowSurfaceX11Image>(DefaultXDisplay(), mXWindow,
+ mXVisual, mXDepth, mIsShaped);
+ }
+#endif
+ MOZ_RELEASE_ASSERT(false);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceProvider::StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) {
+ if (aInvalidRegion.IsEmpty()) {
+ return nullptr;
+ }
+
+ MutexAutoLock lock(mMutex);
+
+ if (!mWindowSurfaceValid) {
+ mWindowSurface = nullptr;
+ mWindowSurfaceValid = true;
+ }
+
+ if (!mWindowSurface) {
+ mWindowSurface = CreateWindowSurface();
+ if (!mWindowSurface) {
+ return nullptr;
+ }
+ }
+
+ *aBufferMode = BufferMode::BUFFER_NONE;
+ RefPtr<gfx::DrawTarget> dt = mWindowSurface->Lock(aInvalidRegion);
+#ifdef MOZ_X11
+ if (!dt && GdkIsX11Display() && !mWindowSurface->IsFallback()) {
+ // We can't use WindowSurfaceX11Image fallback on Wayland but
+ // Lock() call on WindowSurfaceWayland should never fail.
+ gfxWarningOnce()
+ << "Failed to lock WindowSurface, falling back to XPutImage backend.";
+ mWindowSurface = MakeRefPtr<WindowSurfaceX11Image>(
+ DefaultXDisplay(), mXWindow, mXVisual, mXDepth, mIsShaped);
+ dt = mWindowSurface->Lock(aInvalidRegion);
+ }
+#endif
+ return dt.forget();
+}
+
+void WindowSurfaceProvider::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ MutexAutoLock lock(mMutex);
+ // Commit to mWindowSurface only if we have a valid one.
+ if (!mWindowSurface || !mWindowSurfaceValid) {
+ return;
+ }
+#if defined(MOZ_WAYLAND)
+ if (GdkIsWaylandDisplay()) {
+ // We're called too early or we're unmapped.
+ // Don't draw anything.
+ if (!mWidget || !mWidget->IsMapped()) {
+ return;
+ }
+ if (moz_container_wayland_is_commiting_to_parent(
+ mWidget->GetMozContainer())) {
+ // If we're drawing directly to wl_surface owned by Gtk we need to use it
+ // in main thread to sync with Gtk access to it.
+ NS_DispatchToMainThread(NS_NewRunnableFunction(
+ "WindowSurfaceProvider::EndRemoteDrawingInRegion",
+ [widget = RefPtr{mWidget}, this, aInvalidRegion]() {
+ if (!widget->IsMapped()) {
+ return;
+ }
+ MutexAutoLock lock(mMutex);
+ // Commit to mWindowSurface only when we have a valid one.
+ if (mWindowSurface && mWindowSurfaceValid) {
+ mWindowSurface->Commit(aInvalidRegion);
+ }
+ }));
+ return;
+ }
+ }
+#endif
+ mWindowSurface->Commit(aInvalidRegion);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceProvider.h b/widget/gtk/WindowSurfaceProvider.h
new file mode 100644
index 0000000000..a040bbe395
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -0,0 +1,101 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
+
+#include <gdk/gdk.h>
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "Units.h"
+#include "mozilla/ScopeExit.h"
+
+#ifdef MOZ_X11
+# include <X11/Xlib.h> // for Window, Display, Visual, etc.
+# include "X11UndefineNone.h"
+#endif
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+class GtkCompositorWidget;
+
+/*
+ * Holds the logic for creating WindowSurface's for a GTK nsWindow.
+ * The main purpose of this class is to allow sharing of logic between
+ * nsWindow and GtkCompositorWidget, for when OMTC is enabled or disabled.
+ */
+class WindowSurfaceProvider final {
+ public:
+ WindowSurfaceProvider();
+ ~WindowSurfaceProvider();
+
+ /**
+ * Initializes the WindowSurfaceProvider by giving it the window
+ * handle and display to attach to. WindowSurfaceProvider doesn't
+ * own the Display, Window, etc, and they must continue to exist
+ * while WindowSurfaceProvider is used.
+ */
+#ifdef MOZ_WAYLAND
+ void Initialize(RefPtr<nsWindow> aWidget);
+ void Initialize(GtkCompositorWidget* aCompositorWidget);
+#endif
+#ifdef MOZ_X11
+ void Initialize(Window aWindow, Visual* aVisual, int aDepth, bool aIsShaped);
+#endif
+
+ /**
+ * Releases any surfaces created by this provider.
+ * This is used by GtkCompositorWidget to get rid
+ * of resources.
+ */
+ void CleanupResources();
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode);
+ void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion);
+
+ private:
+ RefPtr<WindowSurface> CreateWindowSurface();
+ void CleanupWindowSurface();
+
+ RefPtr<WindowSurface> mWindowSurface;
+
+ /* While CleanupResources() can be called from Main thread when nsWindow is
+ * destroyed/hidden, StartRemoteDrawingInRegion()/EndRemoteDrawingInRegion()
+ * is called from Compositor thread during rendering.
+ *
+ * As nsWindow CleanupResources() call comes from Gtk/X11 we can't synchronize
+ * that with WebRender so we use lock to synchronize the access.
+ */
+ mozilla::Mutex mMutex MOZ_UNANNOTATED;
+ // WindowSurface needs to be re-created as underlying window was changed.
+ mozilla::Atomic<bool> mWindowSurfaceValid;
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWindow> mWidget;
+ // WindowSurfaceProvider is owned by GtkCompositorWidget so we don't need
+ // to reference it.
+ GtkCompositorWidget* mCompositorWidget = nullptr;
+#endif
+#ifdef MOZ_X11
+ bool mIsShaped;
+ int mXDepth;
+ Window mXWindow;
+ Visual* mXVisual;
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
diff --git a/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
new file mode 100644
index 0000000000..96f478b726
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWaylandMultiBuffer.cpp
@@ -0,0 +1,418 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceWaylandMultiBuffer.h"
+
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <prenv.h>
+
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "GtkCompositorWidget.h"
+#include "mozilla/gfx/DataSurfaceHelpers.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/WidgetUtils.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(...) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+#else
+# define LOGWAYLAND(...)
+#endif /* MOZ_LOGGING */
+
+namespace mozilla::widget {
+
+/*
+ Wayland multi-thread rendering scheme
+
+ Every rendering thread (main thread, compositor thread) contains its own
+ nsWaylandDisplay object connected to Wayland compositor (Mutter, Weston, etc.)
+
+ WindowSurfaceWayland implements WindowSurface class and draws nsWindow by
+ WindowSurface interface (Lock, Commit) to screen through nsWaylandDisplay.
+
+ ----------------------
+ | Wayland compositor |
+ ----------------------
+ ^
+ |
+ ----------------------
+ | nsWaylandDisplay |
+ ----------------------
+ ^ ^
+ | |
+ | |
+ | --------------------------------- ------------------
+ | | WindowSurfaceWayland |<------>| nsWindow |
+ | | | ------------------
+ | | ----------------------- |
+ | | | WaylandBufferSHM | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | | |
+ | | ----------------------- |
+ | | | WaylandBufferSHM | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | ---------------------------------
+ |
+ |
+ --------------------------------- ------------------
+ | WindowSurfaceWayland |<------>| nsWindow |
+ | | ------------------
+ | ----------------------- |
+ | | WaylandBufferSHM | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ | |
+ | ----------------------- |
+ | | WaylandBufferSHM | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ ---------------------------------
+
+
+nsWaylandDisplay
+
+Is our connection to Wayland display server,
+holds our display connection (wl_display) and event queue (wl_event_queue).
+
+nsWaylandDisplay is created for every thread which sends data to Wayland
+compositor. Wayland events for main thread is served by default Gtk+ loop,
+for other threads (compositor) we must create wl_event_queue and run event loop.
+
+
+WindowSurfaceWayland
+
+Is a Wayland implementation of WindowSurface class for WindowSurfaceProvider,
+we implement Lock() and Commit() interfaces from WindowSurface
+for actual drawing.
+
+One WindowSurfaceWayland draws one nsWindow so those are tied 1:1.
+At Wayland level it holds one wl_surface object.
+
+To perform visualiation of nsWindow, WindowSurfaceWayland contains one
+wl_surface and two wl_buffer objects (owned by WaylandBufferSHM)
+as we use double buffering. When nsWindow drawing is finished to wl_buffer,
+the wl_buffer is attached to wl_surface and it's sent to Wayland compositor.
+
+When there's no wl_buffer available for drawing (all wl_buffers are locked in
+compositor for instance) we store the drawing to WindowImageSurface object
+and draw later when wl_buffer becomes available or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WaylandBufferSHM
+
+Is a class which provides a wl_buffer for drawing.
+Wl_buffer is a main Wayland object with actual graphics data.
+Wl_buffer basically represent one complete window screen.
+When double buffering is involved every window (GdkWindow for instance)
+utilises two wl_buffers which are cycled. One is filed with data by application
+and one is rendered by compositor.
+
+WaylandBufferSHM is implemented by shared memory (shm).
+It owns wl_buffer object, owns WaylandShmPool
+(which provides the shared memory) and ties them together.
+
+WaylandShmPool
+
+WaylandShmPool acts as a manager of shared memory for WaylandBufferSHM.
+Allocates it, holds reference to it and releases it.
+
+We allocate shared memory (shm) by mmap(..., MAP_SHARED,...) as an interface
+between us and wayland compositor. We draw our graphics data to the shm and
+handle to wayland compositor by WaylandBufferSHM/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+using gfx::DataSourceSurface;
+
+#define BACK_BUFFER_NUM 3
+
+WindowSurfaceWaylandMB::WindowSurfaceWaylandMB(
+ RefPtr<nsWindow> aWindow, GtkCompositorWidget* aCompositorWidget)
+ : mSurfaceLock("WindowSurfaceWayland lock"),
+ mWindow(std::move(aWindow)),
+ mCompositorWidget(aCompositorWidget),
+ mFrameInProcess(false),
+ mCallbackRequested(false) {}
+
+bool WindowSurfaceWaylandMB::MaybeUpdateWindowSize() {
+ // We want to get window size from compositor widget as it matches window
+ // size used by parent RenderCompositorSWGL rendrer.
+ // For main thread rendering mCompositorWidget is not available so get
+ // window size directly from nsWindow.
+ LayoutDeviceIntSize newWindowSize = mCompositorWidget
+ ? mCompositorWidget->GetClientSize()
+ : mWindow->GetClientSize();
+ if (mWindowSize != newWindowSize) {
+ mWindowSize = newWindowSize;
+ return true;
+ }
+ return false;
+}
+
+already_AddRefed<DrawTarget> WindowSurfaceWaylandMB::Lock(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ MutexAutoLock lock(mSurfaceLock);
+
+#ifdef MOZ_LOGGING
+ gfx::IntRect lockRect = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND("WindowSurfaceWaylandMB::Lock [%p] [%d,%d] -> [%d x %d] rects %d",
+ (void*)mWindow.get(), lockRect.x, lockRect.y, lockRect.width,
+ lockRect.height, aInvalidRegion.GetNumRects());
+#endif
+
+ if (mWindow->GetWindowType() == WindowType::Invisible) {
+ return nullptr;
+ }
+ mFrameInProcess = true;
+
+ CollectPendingSurfaces(lock);
+
+ if (MaybeUpdateWindowSize()) {
+ LOGWAYLAND(" new window size [%d x %d]", mWindowSize.width,
+ mWindowSize.height);
+ if (mInProgressBuffer) {
+ ReturnBufferToPool(lock, mInProgressBuffer);
+ mInProgressBuffer = nullptr;
+ }
+ if (mFrontBuffer) {
+ ReturnBufferToPool(lock, mFrontBuffer);
+ mFrontBuffer = nullptr;
+ }
+ mAvailableBuffers.Clear();
+ }
+
+ if (!mInProgressBuffer) {
+ if (mFrontBuffer && !mFrontBuffer->IsAttached()) {
+ mInProgressBuffer = mFrontBuffer;
+ } else {
+ mInProgressBuffer = ObtainBufferFromPool(lock, mWindowSize);
+ if (!mInProgressBuffer) {
+ return nullptr;
+ }
+ if (mFrontBuffer) {
+ HandlePartialUpdate(lock, aInvalidRegion);
+ ReturnBufferToPool(lock, mFrontBuffer);
+ }
+ }
+ mFrontBuffer = nullptr;
+ mFrontBufferInvalidRegion.SetEmpty();
+ }
+
+ RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
+ return dt.forget();
+}
+
+void WindowSurfaceWaylandMB::HandlePartialUpdate(
+ const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ LayoutDeviceIntRegion copyRegion;
+ if (mInProgressBuffer->GetBufferAge() == 2) {
+ copyRegion.Sub(mFrontBufferInvalidRegion, aInvalidRegion);
+ } else {
+ LayoutDeviceIntSize frontBufferSize = mFrontBuffer->GetSize();
+ copyRegion = LayoutDeviceIntRegion(LayoutDeviceIntRect(
+ 0, 0, frontBufferSize.width, frontBufferSize.height));
+ copyRegion.SubOut(aInvalidRegion);
+ }
+
+ if (!copyRegion.IsEmpty()) {
+ RefPtr<DataSourceSurface> dataSourceSurface =
+ mozilla::gfx::CreateDataSourceSurfaceFromData(
+ mFrontBuffer->GetSize().ToUnknownSize(),
+ mFrontBuffer->GetSurfaceFormat(),
+ (const uint8_t*)mFrontBuffer->GetShmPool()->GetImageData(),
+ mFrontBuffer->GetSize().width *
+ BytesPerPixel(mFrontBuffer->GetSurfaceFormat()));
+ RefPtr<DrawTarget> dt = mInProgressBuffer->Lock();
+
+ for (auto iter = copyRegion.RectIter(); !iter.Done(); iter.Next()) {
+ LayoutDeviceIntRect r = iter.Get();
+ dt->CopySurface(dataSourceSurface, r.ToUnknownRect(),
+ gfx::IntPoint(r.x, r.y));
+ }
+ }
+}
+
+void WindowSurfaceWaylandMB::Commit(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ MutexAutoLock lock(mSurfaceLock);
+ Commit(lock, aInvalidRegion);
+}
+
+void WindowSurfaceWaylandMB::Commit(
+ const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect invalidRect = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(
+ "WindowSurfaceWaylandMB::Commit [%p] damage rect [%d, %d] -> [%d x %d] "
+ "Window [%d x %d]\n",
+ (void*)mWindow.get(), invalidRect.x, invalidRect.y, invalidRect.width,
+ invalidRect.height, mWindowSize.width, mWindowSize.height);
+#endif
+
+ if (!mInProgressBuffer) {
+ // invisible window
+ return;
+ }
+ mFrameInProcess = false;
+
+ MozContainer* container = mWindow->GetMozContainer();
+ MozContainerSurfaceLock MozContainerLock(container);
+ struct wl_surface* waylandSurface = MozContainerLock.GetSurface();
+ if (!waylandSurface) {
+ LOGWAYLAND(
+ "WindowSurfaceWaylandMB::Commit [%p] frame queued: can't lock "
+ "wl_surface\n",
+ (void*)mWindow.get());
+ if (!mCallbackRequested) {
+ RefPtr<WindowSurfaceWaylandMB> self(this);
+ moz_container_wayland_add_initial_draw_callback_locked(
+ container, [self, aInvalidRegion]() -> void {
+ MutexAutoLock lock(self->mSurfaceLock);
+ if (!self->mFrameInProcess) {
+ self->Commit(lock, aInvalidRegion);
+ }
+ self->mCallbackRequested = false;
+ });
+ mCallbackRequested = true;
+ }
+ return;
+ }
+
+ if (moz_container_wayland_is_commiting_to_parent(container)) {
+ // When committing to parent surface we must use wl_surface_damage().
+ // A parent surface is created as v.3 object which does not support
+ // wl_surface_damage_buffer().
+ wl_surface_damage(waylandSurface, 0, 0, INT32_MAX, INT32_MAX);
+ } else {
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ LayoutDeviceIntRect r = iter.Get();
+ wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+ // aProofOfLock is a kind of substitution of MozContainerSurfaceLock.
+ // MozContainer is locked but MozContainerSurfaceLock doen't convert to
+ // MutexAutoLock& so we use aProofOfLock here.
+ moz_container_wayland_set_scale_factor_locked(
+ aProofOfLock, container, mWindow->GdkCeiledScaleFactor());
+
+ // It's possible that scale factor changed between Lock() and Commit()
+ // but window size is the same.
+ // Don't attach such buffer as it may have incorrect size,
+ // we'll paint new content soon.
+ if (moz_container_wayland_size_matches_scale_factor_locked(
+ aProofOfLock, container, mWindowSize.width, mWindowSize.height)) {
+ mInProgressBuffer->AttachAndCommit(waylandSurface);
+ }
+
+ mInProgressBuffer->ResetBufferAge();
+ mFrontBuffer = mInProgressBuffer;
+ mFrontBufferInvalidRegion = aInvalidRegion;
+ mInProgressBuffer = nullptr;
+
+ EnforcePoolSizeLimit(aProofOfLock);
+ IncrementBufferAge(aProofOfLock);
+
+ if (wl_display_flush(WaylandDisplayGet()->GetDisplay()) == -1) {
+ LOGWAYLAND("WindowSurfaceWaylandMB::Commit [%p] flush failed\n",
+ (void*)mWindow.get());
+ }
+}
+
+RefPtr<WaylandBufferSHM> WindowSurfaceWaylandMB::ObtainBufferFromPool(
+ const MutexAutoLock& aProofOfLock, const LayoutDeviceIntSize& aSize) {
+ if (!mAvailableBuffers.IsEmpty()) {
+ RefPtr<WaylandBufferSHM> buffer = mAvailableBuffers.PopLastElement();
+ mInUseBuffers.AppendElement(buffer);
+ return buffer;
+ }
+
+ RefPtr<WaylandBufferSHM> buffer = WaylandBufferSHM::Create(aSize);
+ if (buffer) {
+ mInUseBuffers.AppendElement(buffer);
+ }
+
+ return buffer;
+}
+
+void WindowSurfaceWaylandMB::ReturnBufferToPool(
+ const MutexAutoLock& aProofOfLock,
+ const RefPtr<WaylandBufferSHM>& aBuffer) {
+ if (aBuffer->IsAttached()) {
+ mPendingBuffers.AppendElement(aBuffer);
+ } else if (aBuffer->IsMatchingSize(mWindowSize)) {
+ mAvailableBuffers.AppendElement(aBuffer);
+ }
+ mInUseBuffers.RemoveElement(aBuffer);
+}
+
+void WindowSurfaceWaylandMB::EnforcePoolSizeLimit(
+ const MutexAutoLock& aProofOfLock) {
+ // Enforce the pool size limit, removing least-recently-used entries as
+ // necessary.
+ while (mAvailableBuffers.Length() > BACK_BUFFER_NUM) {
+ mAvailableBuffers.RemoveElementAt(0);
+ }
+
+ NS_WARNING_ASSERTION(mPendingBuffers.Length() < BACK_BUFFER_NUM,
+ "Are we leaking pending buffers?");
+ NS_WARNING_ASSERTION(mInUseBuffers.Length() < BACK_BUFFER_NUM,
+ "Are we leaking in-use buffers?");
+}
+
+void WindowSurfaceWaylandMB::CollectPendingSurfaces(
+ const MutexAutoLock& aProofOfLock) {
+ mPendingBuffers.RemoveElementsBy([&](auto& buffer) {
+ if (!buffer->IsAttached()) {
+ if (buffer->IsMatchingSize(mWindowSize)) {
+ mAvailableBuffers.AppendElement(std::move(buffer));
+ }
+ return true;
+ }
+ return false;
+ });
+}
+
+void WindowSurfaceWaylandMB::IncrementBufferAge(
+ const MutexAutoLock& aProofOfLock) {
+ for (const RefPtr<WaylandBufferSHM>& buffer : mInUseBuffers) {
+ buffer->IncrementBufferAge();
+ }
+ for (const RefPtr<WaylandBufferSHM>& buffer : mPendingBuffers) {
+ buffer->IncrementBufferAge();
+ }
+ for (const RefPtr<WaylandBufferSHM>& buffer : mAvailableBuffers) {
+ buffer->IncrementBufferAge();
+ }
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/WindowSurfaceWaylandMultiBuffer.h b/widget/gtk/WindowSurfaceWaylandMultiBuffer.h
new file mode 100644
index 0000000000..e14a626de0
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWaylandMultiBuffer.h
@@ -0,0 +1,84 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_MULTI_BUFFER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_MULTI_BUFFER_H
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/Mutex.h"
+#include "nsTArray.h"
+#include "nsWaylandDisplay.h"
+#include "nsWindow.h"
+#include "WaylandBuffer.h"
+#include "WindowSurface.h"
+
+namespace mozilla::widget {
+
+using gfx::DrawTarget;
+
+// WindowSurfaceWaylandMB is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWaylandMB : public WindowSurface {
+ public:
+ WindowSurfaceWaylandMB(RefPtr<nsWindow> aWindow,
+ GtkCompositorWidget* aCompositorWidget);
+ ~WindowSurfaceWaylandMB() = default;
+
+ // Lock() / Commit() are called by gecko when Firefox
+ // wants to display something. Lock() returns a DrawTarget
+ // where gecko paints. When gecko is done it calls Commit()
+ // and we try to send the DrawTarget (backed by wl_buffer)
+ // to wayland compositor.
+ //
+ // If we fail (wayland compositor is busy,
+ // wl_surface is not created yet) we queue the painting
+ // and we send it to wayland compositor in FrameCallbackHandler()/
+ // FlushPendingCommits().
+ already_AddRefed<DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+
+ private:
+ void Commit(const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion);
+ RefPtr<WaylandBufferSHM> ObtainBufferFromPool(
+ const MutexAutoLock& aProofOfLock, const LayoutDeviceIntSize& aSize);
+ void ReturnBufferToPool(const MutexAutoLock& aProofOfLock,
+ const RefPtr<WaylandBufferSHM>& aBuffer);
+ void EnforcePoolSizeLimit(const MutexAutoLock& aProofOfLock);
+ void CollectPendingSurfaces(const MutexAutoLock& aProofOfLock);
+ void HandlePartialUpdate(const MutexAutoLock& aProofOfLock,
+ const LayoutDeviceIntRegion& aInvalidRegion);
+ void IncrementBufferAge(const MutexAutoLock& aProofOfLock);
+ // Return true if window size was updated.
+ bool MaybeUpdateWindowSize();
+
+ mozilla::Mutex mSurfaceLock MOZ_UNANNOTATED;
+
+ RefPtr<nsWindow> mWindow;
+ // WindowSurfaceWaylandMB is owned by GtkCompositorWidget so we can't
+ // reference it.
+ GtkCompositorWidget* mCompositorWidget;
+ LayoutDeviceIntSize mWindowSize;
+
+ RefPtr<WaylandBufferSHM> mInProgressBuffer;
+ RefPtr<WaylandBufferSHM> mFrontBuffer;
+ LayoutDeviceIntRegion mFrontBufferInvalidRegion;
+
+ // buffer pool
+ nsTArray<RefPtr<WaylandBufferSHM>> mInUseBuffers;
+ nsTArray<RefPtr<WaylandBufferSHM>> mPendingBuffers;
+ nsTArray<RefPtr<WaylandBufferSHM>> mAvailableBuffers;
+
+ // delayed commits
+ bool mFrameInProcess;
+ bool mCallbackRequested;
+};
+
+} // namespace mozilla::widget
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_MULTI_BUFFER_H
diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp
new file mode 100644
index 0000000000..36b238a98b
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,47 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceX11.h"
+#include "gfxPlatform.h"
+
+namespace mozilla::widget {
+
+WindowSurfaceX11::WindowSurfaceX11(Display* aDisplay, Window aWindow,
+ Visual* aVisual, unsigned int aDepth)
+ : mDisplay(aDisplay),
+ mWindow(aWindow),
+ mVisual(aVisual),
+ mDepth(aDepth),
+ mFormat(GetVisualFormat(aVisual, aDepth)) {}
+
+/* static */
+gfx::SurfaceFormat WindowSurfaceX11::GetVisualFormat(const Visual* aVisual,
+ unsigned int aDepth) {
+ switch (aDepth) {
+ case 32:
+ if (aVisual->red_mask == 0xff0000 && aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ return gfx::SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ if (aVisual->red_mask == 0xff0000 && aVisual->green_mask == 0xff00 &&
+ aVisual->blue_mask == 0xff) {
+ return gfx::SurfaceFormat::B8G8R8X8;
+ }
+ break;
+ case 16:
+ if (aVisual->red_mask == 0xf800 && aVisual->green_mask == 0x07e0 &&
+ aVisual->blue_mask == 0x1f) {
+ return gfx::SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ return gfx::SurfaceFormat::UNKNOWN;
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/WindowSurfaceX11.h b/widget/gtk/WindowSurfaceX11.h
new file mode 100644
index 0000000000..dda17bf0d0
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
+
+#ifdef MOZ_X11
+
+# include "mozilla/widget/WindowSurface.h"
+# include "mozilla/gfx/Types.h"
+
+# include <X11/Xlib.h>
+# include "X11UndefineNone.h"
+
+namespace mozilla::widget {
+
+class WindowSurfaceX11 : public WindowSurface {
+ public:
+ WindowSurfaceX11(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ protected:
+ static gfx::SurfaceFormat GetVisualFormat(const Visual* aVisual,
+ unsigned int aDepth);
+
+ Display* const mDisplay;
+ const Window mWindow;
+ Visual* const mVisual;
+ const unsigned int mDepth;
+ const gfx::SurfaceFormat mFormat;
+};
+
+} // namespace mozilla::widget
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_H
diff --git a/widget/gtk/WindowSurfaceX11Image.cpp b/widget/gtk/WindowSurfaceX11Image.cpp
new file mode 100644
index 0000000000..f797bfafad
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,272 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceX11Image.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "gfxPlatform.h"
+#include "gfx2DGlue.h"
+
+#include <X11/extensions/shape.h>
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::gfx;
+
+// gfxImageSurface pixel format configuration.
+#define SHAPED_IMAGE_SURFACE_BPP 4
+#ifdef IS_BIG_ENDIAN
+# define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 0
+#else
+# define SHAPED_IMAGE_SURFACE_ALPHA_INDEX 3
+#endif
+
+WindowSurfaceX11Image::WindowSurfaceX11Image(Display* aDisplay, Window aWindow,
+ Visual* aVisual,
+ unsigned int aDepth,
+ bool aIsShaped)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth),
+ mTransparencyBitmap(nullptr),
+ mTransparencyBitmapWidth(0),
+ mTransparencyBitmapHeight(0),
+ mIsShaped(aIsShaped),
+ mWindowParent(0) {
+ if (!mIsShaped) {
+ return;
+ }
+
+ Window root, *children = nullptr;
+ unsigned int childrenNum;
+ if (XQueryTree(mDisplay, mWindow, &root, &mWindowParent, &children,
+ &childrenNum)) {
+ if (children) {
+ XFree((char*)children);
+ }
+ }
+}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image() {
+ if (mTransparencyBitmap) {
+ delete[] mTransparencyBitmap;
+ }
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceX11Image::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+
+ if (!mWindowSurface || mWindowSurface->CairoStatus() ||
+ !(size <= mWindowSurface->GetSize())) {
+ mWindowSurface = new gfxXlibSurface(mDisplay, mWindow, mVisual, size);
+ }
+ if (mWindowSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ if (!mImageSurface || mImageSurface->CairoStatus() ||
+ !(size <= mImageSurface->GetSize())) {
+ gfxImageFormat format = SurfaceFormatToImageFormat(mFormat);
+ if (format == gfx::SurfaceFormat::UNKNOWN) {
+ format = mDepth == 32 ? gfx::SurfaceFormat::A8R8G8B8_UINT32
+ : gfx::SurfaceFormat::X8R8G8B8_UINT32;
+ }
+
+ // Use alpha image format for shaped window as we derive
+ // the shape bitmap from alpha channel. Must match SHAPED_IMAGE_SURFACE_BPP
+ // and SHAPED_IMAGE_SURFACE_ALPHA_INDEX.
+ if (mIsShaped) {
+ format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
+ }
+
+ mImageSurface = new gfxImageSurface(size, format);
+ if (mImageSurface->CairoStatus()) {
+ return nullptr;
+ }
+ }
+
+ gfxImageFormat format = mImageSurface->Format();
+ // Cairo prefers compositing to BGRX instead of BGRA where possible.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ // Otherwise, for Skia, report it as BGRA to the compositor. The alpha
+ // channel will be discarded when we put the image.
+ if (format == gfx::SurfaceFormat::X8R8G8B8_UINT32) {
+ gfx::BackendType backend = gfxVars::ContentBackend();
+ if (!gfx::Factory::DoesBackendSupportDataDrawtarget(backend)) {
+ backend = gfx::BackendType::SKIA;
+ }
+ if (backend != gfx::BackendType::CAIRO) {
+ format = gfx::SurfaceFormat::A8R8G8B8_UINT32;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(
+ mImageSurface->Data(), mImageSurface->GetSize(), mImageSurface->Stride(),
+ ImageFormatToSurfaceFormat(format));
+}
+
+// The transparency bitmap routines are derived form the ones at nsWindow.cpp.
+// The difference here is that we compose to RGBA image and then create
+// the shape mask from final image alpha channel.
+static inline int32_t GetBitmapStride(int32_t width) { return (width + 7) / 8; }
+
+static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aImageData) {
+ int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aImageData;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
+ alphas += SHAPED_IMAGE_SURFACE_BPP;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aImageData += stride;
+ }
+
+ return false;
+}
+
+static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aImageData) {
+ int32_t stride = aMaskWidth * SHAPED_IMAGE_SURFACE_BPP;
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aImageData;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *(alphas + SHAPED_IMAGE_SURFACE_ALPHA_INDEX) > 0x7f;
+ alphas += SHAPED_IMAGE_SURFACE_BPP;
+
+ gchar mask = 1 << (x & 7);
+ gchar maskByte = maskBytes[x >> 3];
+ // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
+ maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
+ }
+ aImageData += stride;
+ }
+}
+
+void WindowSurfaceX11Image::ResizeTransparencyBitmap(int aWidth, int aHeight) {
+ int32_t actualSize =
+ GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
+ int32_t newSize = GetBitmapStride(aWidth) * aHeight;
+
+ if (actualSize < newSize) {
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = new gchar[newSize];
+ }
+
+ mTransparencyBitmapWidth = aWidth;
+ mTransparencyBitmapHeight = aHeight;
+}
+
+void WindowSurfaceX11Image::ApplyTransparencyBitmap() {
+ gfx::IntSize size = mWindowSurface->GetSize();
+ bool maskChanged = true;
+
+ if (!mTransparencyBitmap) {
+ mTransparencyBitmapWidth = size.width;
+ mTransparencyBitmapHeight = size.height;
+
+ int32_t byteSize =
+ GetBitmapStride(mTransparencyBitmapWidth) * mTransparencyBitmapHeight;
+ mTransparencyBitmap = new gchar[byteSize];
+ } else {
+ bool sizeChanged = (size.width != mTransparencyBitmapWidth ||
+ size.height != mTransparencyBitmapHeight);
+
+ if (sizeChanged) {
+ ResizeTransparencyBitmap(size.width, size.height);
+ } else {
+ maskChanged = ChangedMaskBits(
+ mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight, nsIntRect(0, 0, size.width, size.height),
+ (uint8_t*)mImageSurface->Data());
+ }
+ }
+
+ if (maskChanged) {
+ UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight,
+ nsIntRect(0, 0, size.width, size.height),
+ (uint8_t*)mImageSurface->Data());
+
+ // We use X11 calls where possible, because GDK handles expose events
+ // for shaped windows in a way that's incompatible with us (Bug 635903).
+ // It doesn't occur when the shapes are set through X.
+ Display* xDisplay = mWindowSurface->XDisplay();
+ Window xDrawable = mWindowSurface->XDrawable();
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+ if (mWindowParent) {
+ XShapeCombineMask(mDisplay, mWindowParent, ShapeBounding, 0, 0,
+ maskPixmap, ShapeSet);
+ }
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+}
+
+void WindowSurfaceX11Image::Commit(
+ const LayoutDeviceIntRegion& aInvalidRegion) {
+ RefPtr<gfx::DrawTarget> dt = gfx::Factory::CreateDrawTargetForCairoSurface(
+ mWindowSurface->CairoSurface(), mWindowSurface->GetSize());
+ RefPtr<gfx::SourceSurface> surf =
+ gfx::Factory::CreateSourceSurfaceForCairoSurface(
+ mImageSurface->CairoSurface(), mImageSurface->GetSize(),
+ mImageSurface->Format());
+ if (!dt || !surf) {
+ return;
+ }
+
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ if (bounds.IsEmpty()) {
+ return;
+ }
+
+ if (mIsShaped) {
+ ApplyTransparencyBitmap();
+ }
+
+ uint32_t numRects = aInvalidRegion.GetNumRects();
+ if (numRects == 1) {
+ dt->CopySurface(surf, bounds, bounds.TopLeft());
+ } else {
+ AutoTArray<IntRect, 32> rects;
+ rects.SetCapacity(numRects);
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ rects.AppendElement(iter.Get().ToUnknownRect());
+ }
+ dt->PushDeviceSpaceClipRects(rects.Elements(), rects.Length());
+
+ dt->DrawSurface(surf, gfx::Rect(bounds), gfx::Rect(bounds),
+ DrawSurfaceOptions(),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+
+ dt->PopClip();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11Image.h b/widget/gtk/WindowSurfaceX11Image.h
new file mode 100644
index 0000000000..281199b5dd
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,49 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
+
+#ifdef MOZ_X11
+
+# include <glib.h>
+# include "WindowSurfaceX11.h"
+# include "gfxXlibSurface.h"
+# include "gfxImageSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11Image : public WindowSurfaceX11 {
+ public:
+ WindowSurfaceX11Image(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth, bool aIsShaped);
+ ~WindowSurfaceX11Image();
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+ bool IsFallback() const override { return true; }
+
+ private:
+ void ResizeTransparencyBitmap(int aWidth, int aHeight);
+ void ApplyTransparencyBitmap();
+
+ RefPtr<gfxXlibSurface> mWindowSurface;
+ RefPtr<gfxImageSurface> mImageSurface;
+
+ gchar* mTransparencyBitmap;
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+ bool mIsShaped;
+ Window mWindowParent;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
diff --git a/widget/gtk/WindowSurfaceX11SHM.cpp b/widget/gtk/WindowSurfaceX11SHM.cpp
new file mode 100644
index 0000000000..889881d22d
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11SHM.cpp
@@ -0,0 +1,27 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "WindowSurfaceX11SHM.h"
+
+namespace mozilla::widget {
+
+WindowSurfaceX11SHM::WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow,
+ Visual* aVisual, unsigned int aDepth) {
+ mFrontImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+ mBackImage = new nsShmImage(aDisplay, aWindow, aVisual, aDepth);
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceX11SHM::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ mBackImage.swap(mFrontImage);
+ return mBackImage->CreateDrawTarget(aRegion);
+}
+
+void WindowSurfaceX11SHM::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+ mBackImage->Put(aInvalidRegion);
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/WindowSurfaceX11SHM.h b/widget/gtk/WindowSurfaceX11SHM.h
new file mode 100644
index 0000000000..5d12137f7b
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11SHM.h
@@ -0,0 +1,36 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
+#define _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
+
+#ifdef MOZ_X11
+
+# include "mozilla/widget/WindowSurface.h"
+# include "nsShmImage.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceX11SHM : public WindowSurface {
+ public:
+ WindowSurfaceX11SHM(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ private:
+ RefPtr<nsShmImage> mFrontImage;
+ RefPtr<nsShmImage> mBackImage;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_WINDOW_SURFACE_X11_SHM_H
diff --git a/widget/gtk/compat/gdk/gdkdnd.h b/widget/gtk/compat/gdk/gdkdnd.h
new file mode 100644
index 0000000000..bf9888d84f
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkdnd.h
@@ -0,0 +1,29 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKDND_WRAPPER_H
+#define GDKDND_WRAPPER_H
+
+#define gdk_drag_context_get_actions gdk_drag_context_get_actions_
+#define gdk_drag_context_list_targets gdk_drag_context_list_targets_
+#define gdk_drag_context_get_dest_window gdk_drag_context_get_dest_window_
+#include_next <gdk/gdkdnd.h>
+#undef gdk_drag_context_get_actions
+#undef gdk_drag_context_list_targets
+#undef gdk_drag_context_get_dest_window
+
+static inline GdkDragAction gdk_drag_context_get_actions(
+ GdkDragContext* context) {
+ return context->actions;
+}
+
+static inline GList* gdk_drag_context_list_targets(GdkDragContext* context) {
+ return context->targets;
+}
+
+static inline GdkWindow* gdk_drag_context_get_dest_window(
+ GdkDragContext* context) {
+ return context->dest_window;
+}
+#endif /* GDKDND_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkkeysyms.h b/widget/gtk/compat/gdk/gdkkeysyms.h
new file mode 100644
index 0000000000..2f48d61fcc
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkkeysyms.h
@@ -0,0 +1,266 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKKEYSYMS_WRAPPER_H
+#define GDKKEYSYMS_WRAPPER_H
+
+#include_next <gdk/gdkkeysyms.h>
+
+#ifndef GDK_ISO_Level5_Shift
+# define GDK_ISO_Level5_Shift 0xFE11
+#endif
+
+#ifndef GDK_ISO_Level5_Latch
+# define GDK_ISO_Level5_Latch 0xFE12
+#endif
+
+#ifndef GDK_ISO_Level5_Lock
+# define GDK_ISO_Level5_Lock 0xFE13
+#endif
+
+#ifndef GDK_dead_greek
+# define GDK_dead_greek 0xFE8C
+#endif
+
+#ifndef GDK_ch
+# define GDK_ch 0xFEA0
+#endif
+
+#ifndef GDK_Ch
+# define GDK_Ch 0xFEA1
+#endif
+
+#ifndef GDK_CH
+# define GDK_CH 0xFEA2
+#endif
+
+#ifndef GDK_c_h
+# define GDK_c_h 0xFEA3
+#endif
+
+#ifndef GDK_C_h
+# define GDK_C_h 0xFEA4
+#endif
+
+#ifndef GDK_C_H
+# define GDK_C_H 0xFEA5
+#endif
+
+#ifndef GDK_MonBrightnessUp
+# define GDK_MonBrightnessUp 0x1008FF02
+#endif
+
+#ifndef GDK_MonBrightnessDown
+# define GDK_MonBrightnessDown 0x1008FF03
+#endif
+
+#ifndef GDK_AudioLowerVolume
+# define GDK_AudioLowerVolume 0x1008FF11
+#endif
+
+#ifndef GDK_AudioMute
+# define GDK_AudioMute 0x1008FF12
+#endif
+
+#ifndef GDK_AudioRaiseVolume
+# define GDK_AudioRaiseVolume 0x1008FF13
+#endif
+
+#ifndef GDK_AudioPlay
+# define GDK_AudioPlay 0x1008FF14
+#endif
+
+#ifndef GDK_AudioStop
+# define GDK_AudioStop 0x1008FF15
+#endif
+
+#ifndef GDK_AudioPrev
+# define GDK_AudioPrev 0x1008FF16
+#endif
+
+#ifndef GDK_AudioNext
+# define GDK_AudioNext 0x1008FF17
+#endif
+
+#ifndef GDK_HomePage
+# define GDK_HomePage 0x1008FF18
+#endif
+
+#ifndef GDK_Mail
+# define GDK_Mail 0x1008FF19
+#endif
+
+#ifndef GDK_Search
+# define GDK_Search 0x1008FF1B
+#endif
+
+#ifndef GDK_AudioRecord
+# define GDK_AudioRecord 0x1008FF1C
+#endif
+
+#ifndef GDK_Back
+# define GDK_Back 0x1008FF26
+#endif
+
+#ifndef GDK_Forward
+# define GDK_Forward 0x1008FF27
+#endif
+
+#ifndef GDK_Stop
+# define GDK_Stop 0x1008FF28
+#endif
+
+#ifndef GDK_Refresh
+# define GDK_Refresh 0x1008FF29
+#endif
+
+#ifndef GDK_PowerOff
+# define GDK_PowerOff 0x1008FF2A
+#endif
+
+#ifndef GDK_Eject
+# define GDK_Eject 0x1008FF2C
+#endif
+
+#ifndef GDK_AudioPause
+# define GDK_AudioPause 0x1008FF31
+#endif
+
+#ifndef GDK_BrightnessAdjust
+# define GDK_BrightnessAdjust 0x1008FF3B
+#endif
+
+#ifndef GDK_AudioRewind
+# define GDK_AudioRewind 0x1008FF3E
+#endif
+
+#ifndef GDK_Launch0
+# define GDK_Launch0 0x1008FF40
+#endif
+
+#ifndef GDK_Launch1
+# define GDK_Launch1 0x1008FF41
+#endif
+
+#ifndef GDK_Launch2
+# define GDK_Launch2 0x1008FF42
+#endif
+
+#ifndef GDK_Launch3
+# define GDK_Launch3 0x1008FF43
+#endif
+
+#ifndef GDK_Launch4
+# define GDK_Launch4 0x1008FF44
+#endif
+
+#ifndef GDK_Launch5
+# define GDK_Launch5 0x1008FF45
+#endif
+
+#ifndef GDK_Launch6
+# define GDK_Launch6 0x1008FF46
+#endif
+
+#ifndef GDK_Launch7
+# define GDK_Launch7 0x1008FF47
+#endif
+
+#ifndef GDK_Launch8
+# define GDK_Launch8 0x1008FF48
+#endif
+
+#ifndef GDK_Launch9
+# define GDK_Launch9 0x1008FF49
+#endif
+
+#ifndef GDK_LaunchA
+# define GDK_LaunchA 0x1008FF4A
+#endif
+
+#ifndef GDK_LaunchB
+# define GDK_LaunchB 0x1008FF4B
+#endif
+
+#ifndef GDK_LaunchC
+# define GDK_LaunchC 0x1008FF4C
+#endif
+
+#ifndef GDK_LaunchD
+# define GDK_LaunchD 0x1008FF4D
+#endif
+
+#ifndef GDK_LaunchE
+# define GDK_LaunchE 0x1008FF4E
+#endif
+
+#ifndef GDK_LaunchF
+# define GDK_LaunchF 0x1008FF4F
+#endif
+
+#ifndef GDK_Copy
+# define GDK_Copy 0x1008FF57
+#endif
+
+#ifndef GDK_Cut
+# define GDK_Cut 0x1008FF58
+#endif
+
+#ifndef GDK_Paste
+# define GDK_Paste 0x1008FF6D
+#endif
+
+#ifndef GDK_Reload
+# define GDK_Reload 0x1008FF73
+#endif
+
+#ifndef GDK_AudioRandomPlay
+# define GDK_AudioRandomPlay 0x1008FF99
+#endif
+
+#ifndef GDK_Subtitle
+# define GDK_Subtitle 0x1008FF9A
+#endif
+
+#ifndef GDK_Red
+# define GDK_Red 0x1008FFA3
+#endif
+
+#ifndef GDK_Green
+# define GDK_Green 0x1008FFA4
+#endif
+
+#ifndef GDK_Yellow
+# define GDK_Yellow 0x1008FFA5
+#endif
+
+#ifndef GDK_Blue
+# define GDK_Blue 0x1008FFA6
+#endif
+
+#ifndef GDK_TouchpadToggle
+# define GDK_TouchpadToggle 0x1008FFA9
+#endif
+
+#ifndef GDK_TouchpadOn
+# define GDK_TouchpadOn 0x1008FFB0
+#endif
+
+#ifndef GDK_TouchpadOff
+# define GDK_TouchpadOff 0x1008ffb1
+#endif
+
+#ifndef GDK_LogWindowTree
+# define GDK_LogWindowTree 0x1008FE24
+#endif
+
+#ifndef GDK_LogGrabInfo
+# define GDK_LogGrabInfo 0x1008FE25
+#endif
+
+#ifndef GDK_Sleep
+# define GDK_Sleep 0x1008FF2F
+#endif
+
+#endif /* GDKKEYSYMS_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkvisual.h b/widget/gtk/compat/gdk/gdkvisual.h
new file mode 100644
index 0000000000..e5187a78da
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkvisual.h
@@ -0,0 +1,15 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKVISUAL_WRAPPER_H
+#define GDKVISUAL_WRAPPER_H
+
+#define gdk_visual_get_depth gdk_visual_get_depth_
+#include_next <gdk/gdkvisual.h>
+#undef gdk_visual_get_depth
+
+static inline gint gdk_visual_get_depth(GdkVisual* visual) {
+ return visual->depth;
+}
+#endif /* GDKVISUAL_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkwindow.h b/widget/gtk/compat/gdk/gdkwindow.h
new file mode 100644
index 0000000000..a4d2efbc89
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkwindow.h
@@ -0,0 +1,27 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKWINDOW_WRAPPER_H
+#define GDKWINDOW_WRAPPER_H
+
+#define gdk_window_get_display gdk_window_get_display_
+#define gdk_window_get_screen gdk_window_get_screen_
+#include_next <gdk/gdkwindow.h>
+#undef gdk_window_get_display
+#undef gdk_window_get_screen
+
+static inline GdkDisplay* gdk_window_get_display(GdkWindow* window) {
+ return gdk_drawable_get_display(GDK_DRAWABLE(window));
+}
+
+static inline GdkScreen* gdk_window_get_screen(GdkWindow* window) {
+ return gdk_drawable_get_screen(window);
+}
+
+#if GDK_PIXBUF_MAJOR == 2 && GDK_PIXBUF_MINOR < 18
+static inline gboolean gdk_window_is_destroyed(GdkWindow* window) {
+ return GDK_WINDOW_OBJECT(window)->destroyed;
+}
+#endif
+#endif /* GDKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/compat/gdk/gdkx.h b/widget/gtk/compat/gdk/gdkx.h
new file mode 100644
index 0000000000..7b0718f3cb
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkx.h
@@ -0,0 +1,42 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GDKX_WRAPPER_H
+#define GDKX_WRAPPER_H
+
+#include <gtk/gtkversion.h>
+
+#define gdk_x11_window_foreign_new_for_display \
+ gdk_x11_window_foreign_new_for_display_
+#define gdk_x11_window_lookup_for_display gdk_x11_window_lookup_for_display_
+#define gdk_x11_window_get_xid gdk_x11_window_get_xid_
+#if !GTK_CHECK_VERSION(2, 24, 0)
+# define gdk_x11_set_sm_client_id gdk_x11_set_sm_client_id_
+#endif
+#include_next <gdk/gdkx.h>
+#undef gdk_x11_window_foreign_new_for_display
+#undef gdk_x11_window_lookup_for_display
+#undef gdk_x11_window_get_xid
+
+static inline GdkWindow* gdk_x11_window_foreign_new_for_display(
+ GdkDisplay* display, Window window) {
+ return gdk_window_foreign_new_for_display(display, window);
+}
+
+static inline GdkWindow* gdk_x11_window_lookup_for_display(GdkDisplay* display,
+ Window window) {
+ return gdk_window_lookup_for_display(display, window);
+}
+
+static inline Window gdk_x11_window_get_xid(GdkWindow* window) {
+ return (GDK_WINDOW_XWINDOW(window));
+}
+
+#if !GTK_CHECK_VERSION(2, 24, 0)
+# undef gdk_x11_set_sm_client_id
+static inline void gdk_x11_set_sm_client_id(const gchar* sm_client_id) {
+ gdk_set_sm_client_id(sm_client_id);
+}
+#endif
+#endif /* GDKX_WRAPPER_H */
diff --git a/widget/gtk/compat/glib/gmem.h b/widget/gtk/compat/glib/gmem.h
new file mode 100644
index 0000000000..78f24bbd8d
--- /dev/null
+++ b/widget/gtk/compat/glib/gmem.h
@@ -0,0 +1,48 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GMEM_WRAPPER_H
+#define GMEM_WRAPPER_H
+
+#define g_malloc_n g_malloc_n_
+#define g_malloc0_n g_malloc0_n_
+#define g_realloc_n g_realloc_n_
+#include_next <glib/gmem.h>
+#undef g_malloc_n
+#undef g_malloc0_n
+#undef g_realloc_n
+
+#include <glib/gmessages.h>
+
+#undef g_new
+#define g_new(type, num) ((type*)g_malloc_n((num), sizeof(type)))
+
+#undef g_new0
+#define g_new0(type, num) ((type*)g_malloc0_n((num), sizeof(type)))
+
+#undef g_renew
+#define g_renew(type, ptr, num) ((type*)g_realloc_n(ptr, (num), sizeof(type)))
+
+#define _CHECK_OVERFLOW(num, type_size) \
+ if (G_UNLIKELY(type_size > 0 && num > G_MAXSIZE / type_size)) { \
+ g_error("%s: overflow allocating %" G_GSIZE_FORMAT "*%" G_GSIZE_FORMAT \
+ " bytes", \
+ G_STRLOC, num, type_size); \
+ }
+
+static inline gpointer g_malloc_n(gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc(num * type_size);
+}
+
+static inline gpointer g_malloc0_n(gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_malloc0(num * type_size);
+}
+
+static inline gpointer g_realloc_n(gpointer ptr, gsize num, gsize type_size) {
+ _CHECK_OVERFLOW(num, type_size)
+ return g_realloc(ptr, num * type_size);
+}
+#endif /* GMEM_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwidget.h b/widget/gtk/compat/gtk/gtkwidget.h
new file mode 100644
index 0000000000..21165d61fa
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwidget.h
@@ -0,0 +1,43 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKWIDGET_WRAPPER_H
+#define GTKWIDGET_WRAPPER_H
+
+#define gtk_widget_set_mapped gtk_widget_set_mapped_
+#define gtk_widget_get_mapped gtk_widget_get_mapped_
+#define gtk_widget_set_realized gtk_widget_set_realized_
+#define gtk_widget_get_realized gtk_widget_get_realized_
+#include_next <gtk/gtkwidget.h>
+#undef gtk_widget_set_mapped
+#undef gtk_widget_get_mapped
+#undef gtk_widget_set_realized
+#undef gtk_widget_get_realized
+
+#include <gtk/gtkversion.h>
+
+static inline void gtk_widget_set_mapped(GtkWidget* widget, gboolean mapped) {
+ if (mapped)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_MAPPED);
+ else
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_MAPPED);
+}
+
+static inline gboolean gtk_widget_get_mapped(GtkWidget* widget) {
+ return GTK_WIDGET_MAPPED(widget);
+}
+
+static inline void gtk_widget_set_realized(GtkWidget* widget,
+ gboolean realized) {
+ if (realized)
+ GTK_WIDGET_SET_FLAGS(widget, GTK_REALIZED);
+ else
+ GTK_WIDGET_UNSET_FLAGS(widget, GTK_REALIZED);
+}
+
+static inline gboolean gtk_widget_get_realized(GtkWidget* widget) {
+ return GTK_WIDGET_REALIZED(widget);
+}
+
+#endif /* GTKWIDGET_WRAPPER_H */
diff --git a/widget/gtk/compat/gtk/gtkwindow.h b/widget/gtk/compat/gtk/gtkwindow.h
new file mode 100644
index 0000000000..7c3d5873bd
--- /dev/null
+++ b/widget/gtk/compat/gtk/gtkwindow.h
@@ -0,0 +1,26 @@
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKWINDOW_WRAPPER_H
+#define GTKWINDOW_WRAPPER_H
+
+#define gtk_window_group_get_current_grab gtk_window_group_get_current_grab_
+#define gtk_window_get_window_type gtk_window_get_window_type_
+#include_next <gtk/gtkwindow.h>
+#undef gtk_window_group_get_current_grab
+#undef gtk_window_get_window_type
+
+static inline GtkWidget* gtk_window_group_get_current_grab(
+ GtkWindowGroup* window_group) {
+ if (!window_group->grabs) return NULL;
+
+ return GTK_WIDGET(window_group->grabs->data);
+}
+
+static inline GtkWindowType gtk_window_get_window_type(GtkWindow* window) {
+ gint type;
+ g_object_get(window, "type", &type, (void*)NULL);
+ return (GtkWindowType)type;
+}
+#endif /* GTKWINDOW_WRAPPER_H */
diff --git a/widget/gtk/components.conf b/widget/gtk/components.conf
new file mode 100644
index 0000000000..541df93378
--- /dev/null
+++ b/widget/gtk/components.conf
@@ -0,0 +1,151 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+Headers = [
+ '/widget/gtk/nsWidgetFactory.h',
+]
+
+InitFunc = 'nsWidgetGtk2ModuleCtor'
+UnloadFunc = 'nsWidgetGtk2ModuleDtor'
+
+Classes = [
+ {
+ 'cid': '{2d96b3df-c051-11d1-a827-0040959a28c9}',
+ 'contract_ids': ['@mozilla.org/widget/appshell/gtk;1'],
+ 'legacy_constructor': 'nsAppShellConstructor',
+ 'headers': ['/widget/gtk/nsWidgetFactory.h'],
+ 'processes': ProcessSelector.ALLOW_IN_GPU_RDD_VR_SOCKET_UTILITY_AND_GMPLUGIN_PROCESS,
+ },
+ {
+ 'cid': '{e9537f8f-c07e-4435-8ab3-83f1ad6e3bbf}',
+ 'contract_ids': ['@mozilla.org/gfx/parent/screenmanager;1'],
+ 'singleton': True,
+ 'type': 'mozilla::widget::ScreenManager',
+ 'headers': ['mozilla/StaticPtr.h', 'mozilla/widget/ScreenManager.h'],
+ 'constructor': 'mozilla::widget::ScreenManager::GetAddRefedSingleton',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_AND_MAIN_PROCESS,
+ },
+ {
+ 'cid': '{a9339876-0027-430f-b953-84c9c11c2da3}',
+ 'contract_ids': ['@mozilla.org/widget/taskbarprogress/gtk;1'],
+ 'type': 'TaskbarProgress',
+ 'headers': ['/widget/gtk/TaskbarProgress.h'],
+ },
+ {
+ 'cid': '{4364de1a-798e-419c-a6f5-ca28866b6d5f}',
+ 'contract_ids': ['@mozilla.org/parent/colorpicker;1'],
+ 'type': 'nsColorPicker',
+ 'headers': ['/widget/gtk/nsColorPicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{1940fed5-7d02-4122-8acf-7abaac698983}',
+ 'contract_ids': ['@mozilla.org/parent/filepicker;1'],
+ 'type': 'nsFilePicker',
+ 'headers': ['/widget/gtk/nsFilePicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{948a0023-e3a7-11d2-96cf-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/htmlformatconverter;1'],
+ 'type': 'nsHTMLFormatConverter',
+ 'headers': ['/widget/nsHTMLFormatConverter.h'],
+ },
+ {
+ 'cid': '{b148eed2-236d-11d3-b35c-00a0cc3c1cde}',
+ 'contract_ids': ['@mozilla.org/sound;1'],
+ 'singleton': True,
+ 'type': 'nsISound',
+ 'constructor': 'nsSound::GetInstance',
+ 'headers': ['/widget/gtk/nsSound.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{8b5314bc-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/transferable;1'],
+ 'type': 'nsTransferable',
+ 'headers': ['/widget/nsTransferable.h'],
+ },
+ {
+ 'cid': '{e221df9b-3d66-4045-9a66-5720949f8d10}',
+ 'contract_ids': ['@mozilla.org/applicationchooser;1'],
+ 'type': 'nsApplicationChooser',
+ 'headers': ['/widget/gtk/nsApplicationChooser.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{f55f5d31-dbb7-4d0d-9f6f-a4f4cd8e8ef1}',
+ 'contract_ids': ['@mozilla.org/widget/parent/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{0ba77e04-2adb-422f-af01-5a57b8013100}',
+ 'contract_ids': ['@mozilla.org/widget/parent/dragservice;1'],
+ 'singleton': True,
+ 'type': 'nsDragService',
+ 'headers': ['/widget/gtk/nsDragService.h'],
+ 'constructor': 'nsDragService::GetInstance',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'name': 'GfxInfo',
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/gtk/GfxInfo.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.ALLOW_IN_GPU_PROCESS,
+ },
+ {
+ 'cid': '{6987230e-0098-4e78-bc5f-1493ee7519fa}',
+ 'contract_ids': ['@mozilla.org/widget/useridleservice;1'],
+ 'singleton': True,
+ 'type': 'nsUserIdleService',
+ 'headers': ['/widget/gtk/nsUserIdleServiceGTK.h'],
+ 'constructor': 'nsUserIdleServiceGTK::GetInstance',
+ },
+]
+
+if defined('NS_PRINTING'):
+ Classes += [
+ {
+ 'cid': '{d3f69889-e13a-4321-980c-a39332e21f34}',
+ 'contract_ids': ['@mozilla.org/gfx/devicecontextspec;1'],
+ 'type': 'nsDeviceContextSpecGTK',
+ 'headers': ['/widget/gtk/nsDeviceContextSpecG.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}',
+ 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'],
+ 'type': 'nsPrintDialogServiceGTK',
+ 'headers': ['/widget/gtk/nsPrintDialogGTK.h'],
+ 'init_method': 'Init',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{841387c8-72e6-484b-9296-bf6eea80d58a}',
+ 'contract_ids': ['@mozilla.org/gfx/printsettings-service;1'],
+ 'type': 'nsPrintSettingsServiceGTK',
+ 'headers': ['/widget/gtk/nsPrintSettingsServiceGTK.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{a6cf9129-15b3-11d2-932e-00805f8add32}',
+ 'contract_ids': ['@mozilla.org/gfx/printerlist;1'],
+ 'type': 'nsPrinterListCUPS',
+ 'headers': ['/widget/nsPrinterListCUPS.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ ]
diff --git a/widget/gtk/crashtests/540078-1.xhtml b/widget/gtk/crashtests/540078-1.xhtml
new file mode 100644
index 0000000000..f5de1b9aee
--- /dev/null
+++ b/widget/gtk/crashtests/540078-1.xhtml
@@ -0,0 +1 @@
+<html xmlns="http://www.w3.org/1999/xhtml"><hbox xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"><scrollcorner class="zebra"/></hbox><style style="display: none;">.zebra { -moz-appearance: checkbox; }</style></html>
diff --git a/widget/gtk/crashtests/673390-1.html b/widget/gtk/crashtests/673390-1.html
new file mode 100644
index 0000000000..8463f67f05
--- /dev/null
+++ b/widget/gtk/crashtests/673390-1.html
@@ -0,0 +1 @@
+<div style="-moz-appearance: progresschunk; position: fixed"></div>
diff --git a/widget/gtk/crashtests/crashtests.list b/widget/gtk/crashtests/crashtests.list
new file mode 100644
index 0000000000..9ae47c2233
--- /dev/null
+++ b/widget/gtk/crashtests/crashtests.list
@@ -0,0 +1,2 @@
+load 540078-1.xhtml
+load 673390-1.html
diff --git a/widget/gtk/gbm.h b/widget/gtk/gbm.h
new file mode 100644
index 0000000000..bd94fa8967
--- /dev/null
+++ b/widget/gtk/gbm.h
@@ -0,0 +1,480 @@
+/*
+ * Copyright © 2011 Intel Corporation
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+ * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+ * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
+ * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
+ * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ *
+ * Authors:
+ * Benjamin Franzke <benjaminfranzke@googlemail.com>
+ */
+
+#ifndef _GBM_H_
+#define _GBM_H_
+
+#define __GBM__ 1
+
+#include <stddef.h>
+#include <stdint.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * \file gbm.h
+ * \brief Generic Buffer Manager
+ */
+
+struct gbm_device;
+struct gbm_bo;
+struct gbm_surface;
+
+/**
+ * \mainpage The Generic Buffer Manager
+ *
+ * This module provides an abstraction that the caller can use to request a
+ * buffer from the underlying memory management system for the platform.
+ *
+ * This allows the creation of portable code whilst still allowing access to
+ * the underlying memory manager.
+ */
+
+/**
+ * Abstraction representing the handle to a buffer allocated by the
+ * manager
+ */
+union gbm_bo_handle {
+ void* ptr;
+ int32_t s32;
+ uint32_t u32;
+ int64_t s64;
+ uint64_t u64;
+};
+
+/** Format of the allocated buffer */
+enum gbm_bo_format {
+ /** RGB with 8 bits per channel in a 32 bit value */
+ GBM_BO_FORMAT_XRGB8888,
+ /** ARGB with 8 bits per channel in a 32 bit value */
+ GBM_BO_FORMAT_ARGB8888
+};
+
+/**
+ * The FourCC format codes are taken from the drm_fourcc.h definition, and
+ * re-namespaced. New GBM formats must not be added, unless they are
+ * identical ports from drm_fourcc.
+ */
+#define __gbm_fourcc_code(a, b, c, d) \
+ ((uint32_t)(a) | ((uint32_t)(b) << 8) | ((uint32_t)(c) << 16) | \
+ ((uint32_t)(d) << 24))
+
+#define GBM_FORMAT_BIG_ENDIAN \
+ (1 << 31) /* format is big endian instead of little endian */
+
+/* color index */
+#define GBM_FORMAT_C8 __gbm_fourcc_code('C', '8', ' ', ' ') /* [7:0] C */
+
+/* 8 bpp Red */
+#define GBM_FORMAT_R8 __gbm_fourcc_code('R', '8', ' ', ' ') /* [7:0] R */
+
+/* 16 bpp RG */
+#define GBM_FORMAT_GR88 \
+ __gbm_fourcc_code('G', 'R', '8', '8') /* [15:0] G:R 8:8 little endian */
+
+/* 8 bpp RGB */
+#define GBM_FORMAT_RGB332 \
+ __gbm_fourcc_code('R', 'G', 'B', '8') /* [7:0] R:G:B 3:3:2 */
+#define GBM_FORMAT_BGR233 \
+ __gbm_fourcc_code('B', 'G', 'R', '8') /* [7:0] B:G:R 2:3:3 */
+
+/* 16 bpp RGB */
+#define GBM_FORMAT_XRGB4444 \
+ __gbm_fourcc_code('X', 'R', '1', \
+ '2') /* [15:0] x:R:G:B 4:4:4:4 little endian */
+#define GBM_FORMAT_XBGR4444 \
+ __gbm_fourcc_code('X', 'B', '1', \
+ '2') /* [15:0] x:B:G:R 4:4:4:4 little endian */
+#define GBM_FORMAT_RGBX4444 \
+ __gbm_fourcc_code('R', 'X', '1', \
+ '2') /* [15:0] R:G:B:x 4:4:4:4 little endian */
+#define GBM_FORMAT_BGRX4444 \
+ __gbm_fourcc_code('B', 'X', '1', \
+ '2') /* [15:0] B:G:R:x 4:4:4:4 little endian */
+
+#define GBM_FORMAT_ARGB4444 \
+ __gbm_fourcc_code('A', 'R', '1', \
+ '2') /* [15:0] A:R:G:B 4:4:4:4 little endian */
+#define GBM_FORMAT_ABGR4444 \
+ __gbm_fourcc_code('A', 'B', '1', \
+ '2') /* [15:0] A:B:G:R 4:4:4:4 little endian */
+#define GBM_FORMAT_RGBA4444 \
+ __gbm_fourcc_code('R', 'A', '1', \
+ '2') /* [15:0] R:G:B:A 4:4:4:4 little endian */
+#define GBM_FORMAT_BGRA4444 \
+ __gbm_fourcc_code('B', 'A', '1', \
+ '2') /* [15:0] B:G:R:A 4:4:4:4 little endian */
+
+#define GBM_FORMAT_XRGB1555 \
+ __gbm_fourcc_code('X', 'R', '1', \
+ '5') /* [15:0] x:R:G:B 1:5:5:5 little endian */
+#define GBM_FORMAT_XBGR1555 \
+ __gbm_fourcc_code('X', 'B', '1', \
+ '5') /* [15:0] x:B:G:R 1:5:5:5 little endian */
+#define GBM_FORMAT_RGBX5551 \
+ __gbm_fourcc_code('R', 'X', '1', \
+ '5') /* [15:0] R:G:B:x 5:5:5:1 little endian */
+#define GBM_FORMAT_BGRX5551 \
+ __gbm_fourcc_code('B', 'X', '1', \
+ '5') /* [15:0] B:G:R:x 5:5:5:1 little endian */
+
+#define GBM_FORMAT_ARGB1555 \
+ __gbm_fourcc_code('A', 'R', '1', \
+ '5') /* [15:0] A:R:G:B 1:5:5:5 little endian */
+#define GBM_FORMAT_ABGR1555 \
+ __gbm_fourcc_code('A', 'B', '1', \
+ '5') /* [15:0] A:B:G:R 1:5:5:5 little endian */
+#define GBM_FORMAT_RGBA5551 \
+ __gbm_fourcc_code('R', 'A', '1', \
+ '5') /* [15:0] R:G:B:A 5:5:5:1 little endian */
+#define GBM_FORMAT_BGRA5551 \
+ __gbm_fourcc_code('B', 'A', '1', \
+ '5') /* [15:0] B:G:R:A 5:5:5:1 little endian */
+
+#define GBM_FORMAT_RGB565 \
+ __gbm_fourcc_code('R', 'G', '1', '6') /* [15:0] R:G:B 5:6:5 little endian */
+#define GBM_FORMAT_BGR565 \
+ __gbm_fourcc_code('B', 'G', '1', '6') /* [15:0] B:G:R 5:6:5 little endian */
+
+/* 24 bpp RGB */
+#define GBM_FORMAT_RGB888 \
+ __gbm_fourcc_code('R', 'G', '2', '4') /* [23:0] R:G:B little endian */
+#define GBM_FORMAT_BGR888 \
+ __gbm_fourcc_code('B', 'G', '2', '4') /* [23:0] B:G:R little endian */
+
+/* 32 bpp RGB */
+#define GBM_FORMAT_XRGB8888 \
+ __gbm_fourcc_code('X', 'R', '2', \
+ '4') /* [31:0] x:R:G:B 8:8:8:8 little endian */
+#define GBM_FORMAT_XBGR8888 \
+ __gbm_fourcc_code('X', 'B', '2', \
+ '4') /* [31:0] x:B:G:R 8:8:8:8 little endian */
+#define GBM_FORMAT_RGBX8888 \
+ __gbm_fourcc_code('R', 'X', '2', \
+ '4') /* [31:0] R:G:B:x 8:8:8:8 little endian */
+#define GBM_FORMAT_BGRX8888 \
+ __gbm_fourcc_code('B', 'X', '2', \
+ '4') /* [31:0] B:G:R:x 8:8:8:8 little endian */
+
+#define GBM_FORMAT_ARGB8888 \
+ __gbm_fourcc_code('A', 'R', '2', \
+ '4') /* [31:0] A:R:G:B 8:8:8:8 little endian */
+#define GBM_FORMAT_ABGR8888 \
+ __gbm_fourcc_code('A', 'B', '2', \
+ '4') /* [31:0] A:B:G:R 8:8:8:8 little endian */
+#define GBM_FORMAT_RGBA8888 \
+ __gbm_fourcc_code('R', 'A', '2', \
+ '4') /* [31:0] R:G:B:A 8:8:8:8 little endian */
+#define GBM_FORMAT_BGRA8888 \
+ __gbm_fourcc_code('B', 'A', '2', \
+ '4') /* [31:0] B:G:R:A 8:8:8:8 little endian */
+
+#define GBM_FORMAT_XRGB2101010 \
+ __gbm_fourcc_code('X', 'R', '3', \
+ '0') /* [31:0] x:R:G:B 2:10:10:10 little endian */
+#define GBM_FORMAT_XBGR2101010 \
+ __gbm_fourcc_code('X', 'B', '3', \
+ '0') /* [31:0] x:B:G:R 2:10:10:10 little endian */
+#define GBM_FORMAT_RGBX1010102 \
+ __gbm_fourcc_code('R', 'X', '3', \
+ '0') /* [31:0] R:G:B:x 10:10:10:2 little endian */
+#define GBM_FORMAT_BGRX1010102 \
+ __gbm_fourcc_code('B', 'X', '3', \
+ '0') /* [31:0] B:G:R:x 10:10:10:2 little endian */
+
+#define GBM_FORMAT_ARGB2101010 \
+ __gbm_fourcc_code('A', 'R', '3', \
+ '0') /* [31:0] A:R:G:B 2:10:10:10 little endian */
+#define GBM_FORMAT_ABGR2101010 \
+ __gbm_fourcc_code('A', 'B', '3', \
+ '0') /* [31:0] A:B:G:R 2:10:10:10 little endian */
+#define GBM_FORMAT_RGBA1010102 \
+ __gbm_fourcc_code('R', 'A', '3', \
+ '0') /* [31:0] R:G:B:A 10:10:10:2 little endian */
+#define GBM_FORMAT_BGRA1010102 \
+ __gbm_fourcc_code('B', 'A', '3', \
+ '0') /* [31:0] B:G:R:A 10:10:10:2 little endian */
+
+/* packed YCbCr */
+#define GBM_FORMAT_YUYV \
+ __gbm_fourcc_code('Y', 'U', 'Y', \
+ 'V') /* [31:0] Cr0:Y1:Cb0:Y0 8:8:8:8 little endian */
+#define GBM_FORMAT_YVYU \
+ __gbm_fourcc_code('Y', 'V', 'Y', \
+ 'U') /* [31:0] Cb0:Y1:Cr0:Y0 8:8:8:8 little endian */
+#define GBM_FORMAT_UYVY \
+ __gbm_fourcc_code('U', 'Y', 'V', \
+ 'Y') /* [31:0] Y1:Cr0:Y0:Cb0 8:8:8:8 little endian */
+#define GBM_FORMAT_VYUY \
+ __gbm_fourcc_code('V', 'Y', 'U', \
+ 'Y') /* [31:0] Y1:Cb0:Y0:Cr0 8:8:8:8 little endian */
+
+#define GBM_FORMAT_AYUV \
+ __gbm_fourcc_code('A', 'Y', 'U', \
+ 'V') /* [31:0] A:Y:Cb:Cr 8:8:8:8 little endian */
+
+/*
+ * 2 plane YCbCr
+ * index 0 = Y plane, [7:0] Y
+ * index 1 = Cr:Cb plane, [15:0] Cr:Cb little endian
+ * or
+ * index 1 = Cb:Cr plane, [15:0] Cb:Cr little endian
+ */
+#define GBM_FORMAT_NV12 \
+ __gbm_fourcc_code('N', 'V', '1', '2') /* 2x2 subsampled Cr:Cb plane */
+#define GBM_FORMAT_NV21 \
+ __gbm_fourcc_code('N', 'V', '2', '1') /* 2x2 subsampled Cb:Cr plane */
+#define GBM_FORMAT_NV16 \
+ __gbm_fourcc_code('N', 'V', '1', '6') /* 2x1 subsampled Cr:Cb plane */
+#define GBM_FORMAT_NV61 \
+ __gbm_fourcc_code('N', 'V', '6', '1') /* 2x1 subsampled Cb:Cr plane */
+
+/*
+ * 3 plane YCbCr
+ * index 0: Y plane, [7:0] Y
+ * index 1: Cb plane, [7:0] Cb
+ * index 2: Cr plane, [7:0] Cr
+ * or
+ * index 1: Cr plane, [7:0] Cr
+ * index 2: Cb plane, [7:0] Cb
+ */
+#define GBM_FORMAT_YUV410 \
+ __gbm_fourcc_code('Y', 'U', 'V', \
+ '9') /* 4x4 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU410 \
+ __gbm_fourcc_code('Y', 'V', 'U', \
+ '9') /* 4x4 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV411 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '1') /* 4x1 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU411 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '1') /* 4x1 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV420 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '2') /* 2x2 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU420 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '2') /* 2x2 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV422 \
+ __gbm_fourcc_code('Y', 'U', '1', \
+ '6') /* 2x1 subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU422 \
+ __gbm_fourcc_code('Y', 'V', '1', \
+ '6') /* 2x1 subsampled Cr (1) and Cb (2) planes */
+#define GBM_FORMAT_YUV444 \
+ __gbm_fourcc_code('Y', 'U', '2', \
+ '4') /* non-subsampled Cb (1) and Cr (2) planes */
+#define GBM_FORMAT_YVU444 \
+ __gbm_fourcc_code('Y', 'V', '2', \
+ '4') /* non-subsampled Cr (1) and Cb (2) planes */
+
+struct gbm_format_name_desc {
+ char name[5];
+};
+
+/**
+ * Flags to indicate the intended use for the buffer - these are passed into
+ * gbm_bo_create(). The caller must set the union of all the flags that are
+ * appropriate
+ *
+ * \sa Use gbm_device_is_format_supported() to check if the combination of
+ * format and use flags are supported
+ */
+enum gbm_bo_flags {
+ /**
+ * Buffer is going to be presented to the screen using an API such as KMS
+ */
+ GBM_BO_USE_SCANOUT = (1 << 0),
+ /**
+ * Buffer is going to be used as cursor
+ */
+ GBM_BO_USE_CURSOR = (1 << 1),
+ /**
+ * Deprecated
+ */
+ GBM_BO_USE_CURSOR_64X64 = GBM_BO_USE_CURSOR,
+ /**
+ * Buffer is to be used for rendering - for example it is going to be used
+ * as the storage for a color buffer
+ */
+ GBM_BO_USE_RENDERING = (1 << 2),
+ /**
+ * Buffer can be used for gbm_bo_write. This is guaranteed to work
+ * with GBM_BO_USE_CURSOR, but may not work for other combinations.
+ */
+ GBM_BO_USE_WRITE = (1 << 3),
+ /**
+ * Buffer is linear, i.e. not tiled.
+ */
+ GBM_BO_USE_LINEAR = (1 << 4),
+};
+
+int gbm_device_get_fd(struct gbm_device* gbm);
+
+const char* gbm_device_get_backend_name(struct gbm_device* gbm);
+
+int gbm_device_is_format_supported(struct gbm_device* gbm, uint32_t format,
+ uint32_t usage);
+
+int gbm_device_get_format_modifier_plane_count(struct gbm_device* gbm,
+ uint32_t format,
+ uint64_t modifier);
+
+void gbm_device_destroy(struct gbm_device* gbm);
+
+struct gbm_device* gbm_create_device(int fd);
+
+struct gbm_bo* gbm_bo_create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format, uint32_t flags);
+
+struct gbm_bo* gbm_bo_create_with_modifiers(struct gbm_device* gbm,
+ uint32_t width, uint32_t height,
+ uint32_t format,
+ const uint64_t* modifiers,
+ const unsigned int count);
+#define GBM_BO_IMPORT_WL_BUFFER 0x5501
+#define GBM_BO_IMPORT_EGL_IMAGE 0x5502
+#define GBM_BO_IMPORT_FD 0x5503
+#define GBM_BO_IMPORT_FD_MODIFIER 0x5504
+
+struct gbm_import_fd_data {
+ int fd;
+ uint32_t width;
+ uint32_t height;
+ uint32_t stride;
+ uint32_t format;
+};
+
+struct gbm_import_fd_modifier_data {
+ uint32_t width;
+ uint32_t height;
+ uint32_t format;
+ uint32_t num_fds;
+ int fds[4];
+ int strides[4];
+ int offsets[4];
+ uint64_t modifier;
+};
+
+struct gbm_bo* gbm_bo_import(struct gbm_device* gbm, uint32_t type,
+ void* buffer, uint32_t usage);
+
+/**
+ * Flags to indicate the type of mapping for the buffer - these are
+ * passed into gbm_bo_map(). The caller must set the union of all the
+ * flags that are appropriate.
+ *
+ * These flags are independent of the GBM_BO_USE_* creation flags. However,
+ * mapping the buffer may require copying to/from a staging buffer.
+ *
+ * See also: pipe_transfer_usage
+ */
+enum gbm_bo_transfer_flags {
+ /**
+ * Buffer contents read back (or accessed directly) at transfer
+ * create time.
+ */
+ GBM_BO_TRANSFER_READ = (1 << 0),
+ /**
+ * Buffer contents will be written back at unmap time
+ * (or modified as a result of being accessed directly).
+ */
+ GBM_BO_TRANSFER_WRITE = (1 << 1),
+ /**
+ * Read/modify/write
+ */
+ GBM_BO_TRANSFER_READ_WRITE = (GBM_BO_TRANSFER_READ | GBM_BO_TRANSFER_WRITE),
+};
+
+void* gbm_bo_map(struct gbm_bo* bo, uint32_t x, uint32_t y, uint32_t width,
+ uint32_t height, uint32_t flags, uint32_t* stride,
+ void** map_data);
+
+void gbm_bo_unmap(struct gbm_bo* bo, void* map_data);
+
+uint32_t gbm_bo_get_width(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_height(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_stride(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_stride_for_plane(struct gbm_bo* bo, int plane);
+
+uint32_t gbm_bo_get_format(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_bpp(struct gbm_bo* bo);
+
+uint32_t gbm_bo_get_offset(struct gbm_bo* bo, int plane);
+
+struct gbm_device* gbm_bo_get_device(struct gbm_bo* bo);
+
+union gbm_bo_handle gbm_bo_get_handle(struct gbm_bo* bo);
+
+int gbm_bo_get_fd(struct gbm_bo* bo);
+
+uint64_t gbm_bo_get_modifier(struct gbm_bo* bo);
+
+int gbm_bo_get_plane_count(struct gbm_bo* bo);
+
+union gbm_bo_handle gbm_bo_get_handle_for_plane(struct gbm_bo* bo, int plane);
+
+int gbm_bo_write(struct gbm_bo* bo, const void* buf, size_t count);
+
+void gbm_bo_set_user_data(struct gbm_bo* bo, void* data,
+ void (*destroy_user_data)(struct gbm_bo*, void*));
+
+void* gbm_bo_get_user_data(struct gbm_bo* bo);
+
+void gbm_bo_destroy(struct gbm_bo* bo);
+
+struct gbm_surface* gbm_surface_create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format,
+ uint32_t flags);
+
+struct gbm_surface* gbm_surface_create_with_modifiers(
+ struct gbm_device* gbm, uint32_t width, uint32_t height, uint32_t format,
+ const uint64_t* modifiers, const unsigned int count);
+
+struct gbm_bo* gbm_surface_lock_front_buffer(struct gbm_surface* surface);
+
+void gbm_surface_release_buffer(struct gbm_surface* surface, struct gbm_bo* bo);
+
+int gbm_surface_has_free_buffers(struct gbm_surface* surface);
+
+void gbm_surface_destroy(struct gbm_surface* surface);
+
+char* gbm_format_get_name(uint32_t gbm_format,
+ struct gbm_format_name_desc* desc);
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp
new file mode 100644
index 0000000000..1fa8b95606
--- /dev/null
+++ b/widget/gtk/gtk3drawing.cpp
@@ -0,0 +1,2179 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/*
+ * This file contains painting functions for each of the gtk2 widgets.
+ * Adapted from the gtkdrawing.c, and gtk+2.0 source.
+ */
+
+#include <gtk/gtk.h>
+#include <gdk/gdkprivate.h>
+#include <string.h>
+#include "gtkdrawing.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/ScopeExit.h"
+#include "prinrval.h"
+#include "WidgetStyleCache.h"
+#include "nsString.h"
+#include "nsDebug.h"
+#include "WidgetUtilsGtk.h"
+
+#include <math.h>
+#include <dlfcn.h>
+
+static gboolean checkbox_check_state;
+static gboolean notebook_has_tab_gap;
+
+static ToggleGTKMetrics sCheckboxMetrics;
+static ToggleGTKMetrics sRadioMetrics;
+static ToolbarGTKMetrics sToolbarMetrics;
+static CSDWindowDecorationSize sToplevelWindowDecorationSize;
+static CSDWindowDecorationSize sPopupWindowDecorationSize;
+
+using mozilla::Span;
+
+#define ARROW_UP 0
+#define ARROW_DOWN G_PI
+#define ARROW_RIGHT G_PI_2
+#define ARROW_LEFT (G_PI + G_PI_2)
+
+#if 0
+// It's used for debugging only to compare Gecko widget style with
+// the ones used by Gtk+ applications.
+static void
+style_path_print(GtkStyleContext *context)
+{
+ const GtkWidgetPath* path = gtk_style_context_get_path(context);
+
+ static auto sGtkWidgetPathToStringPtr =
+ (char * (*)(const GtkWidgetPath *))
+ dlsym(RTLD_DEFAULT, "gtk_widget_path_to_string");
+
+ fprintf(stderr, "Style path:\n%s\n\n", sGtkWidgetPathToStringPtr(path));
+}
+#endif
+
+static GtkBorder operator+=(GtkBorder& first, const GtkBorder& second) {
+ first.left += second.left;
+ first.right += second.right;
+ first.top += second.top;
+ first.bottom += second.bottom;
+ return first;
+}
+
+static gint moz_gtk_get_tab_thickness(GtkStyleContext* style);
+
+static void Inset(GdkRectangle*, const GtkBorder&);
+
+static void InsetByMargin(GdkRectangle*, GtkStyleContext* style);
+
+static void moz_gtk_add_style_margin(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+ *top += margin.top;
+ *bottom += margin.bottom;
+}
+
+static void moz_gtk_add_style_border(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder border;
+
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+
+ *left += border.left;
+ *right += border.right;
+ *top += border.top;
+ *bottom += border.bottom;
+}
+
+static void moz_gtk_add_style_padding(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ GtkBorder padding;
+
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ *left += padding.left;
+ *right += padding.right;
+ *top += padding.top;
+ *bottom += padding.bottom;
+}
+
+static void moz_gtk_add_margin_border_padding(GtkStyleContext* style,
+ gint* left, gint* top,
+ gint* right, gint* bottom) {
+ moz_gtk_add_style_margin(style, left, top, right, bottom);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+}
+
+static void moz_gtk_add_border_padding(GtkStyleContext* style, gint* left,
+ gint* top, gint* right, gint* bottom) {
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+}
+
+// In case there's an error in Gtk theme and preferred size is zero,
+// return some sane values to pass mozilla automation tests.
+// It should not happen in real-life.
+#define MIN_WIDGET_SIZE 10
+static void moz_gtk_sanity_preferred_size(GtkRequisition* requisition) {
+ if (requisition->width <= 0) {
+ requisition->width = MIN_WIDGET_SIZE;
+ }
+ if (requisition->height <= 0) {
+ requisition->height = MIN_WIDGET_SIZE;
+ }
+}
+
+// GetStateFlagsFromGtkWidgetState() can be safely used for the specific
+// GtkWidgets that set both prelight and active flags. For other widgets,
+// either the GtkStateFlags or Gecko's GtkWidgetState need to be carefully
+// adjusted to match GTK behavior. Although GTK sets insensitive and focus
+// flags in the generic GtkWidget base class, GTK adds prelight and active
+// flags only to widgets that are expected to demonstrate prelight or active
+// states. This contrasts with HTML where any element may have :active and
+// :hover states, and so Gecko's GtkStateFlags do not necessarily map to GTK
+// flags. Failure to restrict the flags in the same way as GTK can cause
+// generic CSS selectors from some themes to unintentionally match elements
+// that are not expected to change appearance on hover or mouse-down.
+static GtkStateFlags GetStateFlagsFromGtkWidgetState(GtkWidgetState* state) {
+ GtkStateFlags stateFlags = GTK_STATE_FLAG_NORMAL;
+
+ if (state->disabled)
+ stateFlags = GTK_STATE_FLAG_INSENSITIVE;
+ else {
+ if (state->depressed || state->active)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_ACTIVE);
+ if (state->inHover)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_PRELIGHT);
+ if (state->focused)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_FOCUSED);
+ if (state->backdrop)
+ stateFlags =
+ static_cast<GtkStateFlags>(stateFlags | GTK_STATE_FLAG_BACKDROP);
+ }
+
+ return stateFlags;
+}
+
+static GtkStateFlags GetStateFlagsFromGtkTabFlags(GtkTabFlags flags) {
+ return ((flags & MOZ_GTK_TAB_SELECTED) == 0) ? GTK_STATE_FLAG_NORMAL
+ : GTK_STATE_FLAG_ACTIVE;
+}
+
+gint moz_gtk_init() {
+ if (gtk_major_version > 3 ||
+ (gtk_major_version == 3 && gtk_minor_version >= 14))
+ checkbox_check_state = GTK_STATE_FLAG_CHECKED;
+ else
+ checkbox_check_state = GTK_STATE_FLAG_ACTIVE;
+
+ moz_gtk_refresh();
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_refresh() {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ // Deprecated for Gtk >= 3.20+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TAB_TOP);
+ gtk_style_context_get_style(style, "has-tab-gap", &notebook_has_tab_gap,
+ NULL);
+ } else {
+ notebook_has_tab_gap = true;
+ }
+
+ sCheckboxMetrics.initialized = false;
+ sRadioMetrics.initialized = false;
+ sToolbarMetrics.initialized = false;
+ sToplevelWindowDecorationSize.initialized = false;
+ sPopupWindowDecorationSize.initialized = false;
+
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+}
+
+gint moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom,
+ gint* border_right) {
+ GtkBorder* default_outside_border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style, "default-outside-border",
+ &default_outside_border, NULL);
+
+ if (default_outside_border) {
+ *border_top = default_outside_border->top;
+ *border_left = default_outside_border->left;
+ *border_bottom = default_outside_border->bottom;
+ *border_right = default_outside_border->right;
+ gtk_border_free(default_outside_border);
+ } else {
+ *border_top = *border_left = *border_bottom = *border_right = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_button_get_default_border(gint* border_top,
+ gint* border_left,
+ gint* border_bottom,
+ gint* border_right) {
+ GtkBorder* default_border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON);
+ gtk_style_context_get_style(style, "default-border", &default_border, NULL);
+
+ if (default_border) {
+ *border_top = default_border->top;
+ *border_left = default_border->left;
+ *border_bottom = default_border->bottom;
+ *border_right = default_border->right;
+ gtk_border_free(default_border);
+ } else {
+ /* see gtkbutton.c */
+ *border_top = *border_left = *border_bottom = *border_right = 1;
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_splitter_get_metrics(gint orientation, gint* size) {
+ GtkStyleContext* style;
+ if (orientation == GTK_ORIENTATION_HORIZONTAL) {
+ style = GetStyleContext(MOZ_GTK_SPLITTER_HORIZONTAL);
+ } else {
+ style = GetStyleContext(MOZ_GTK_SPLITTER_VERTICAL);
+ }
+ gtk_style_context_get_style(style, "handle_size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+static void CalculateToolbarButtonMetrics(WidgetNodeType aAppearance,
+ ToolbarButtonGTKMetrics* aMetrics) {
+ gint iconWidth, iconHeight;
+ if (!gtk_icon_size_lookup(GTK_ICON_SIZE_MENU, &iconWidth, &iconHeight)) {
+ NS_WARNING("Failed to get Gtk+ icon size for titlebar button!");
+
+ // Use some reasonable fallback size
+ iconWidth = 16;
+ iconHeight = 16;
+ }
+
+ GtkStyleContext* style = GetStyleContext(aAppearance);
+ gint width = 0, height = 0;
+ if (!gtk_check_version(3, 20, 0)) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-width", &width, "min-height", &height, NULL);
+ }
+
+ // Cover cases when min-width/min-height is not set, it's invalid
+ // or we're running on Gtk+ < 3.20.
+ if (width < iconWidth) width = iconWidth;
+ if (height < iconHeight) height = iconHeight;
+
+ gint left = 0, top = 0, right = 0, bottom = 0;
+ moz_gtk_add_border_padding(style, &left, &top, &right, &bottom);
+
+ // Button size is calculated as min-width/height + border/padding.
+ width += left + right;
+ height += top + bottom;
+
+ // Place icon at button center.
+ aMetrics->iconXPosition = (width - iconWidth) / 2;
+ aMetrics->iconYPosition = (height - iconHeight) / 2;
+
+ aMetrics->minSizeWithBorderMargin.width = width;
+ aMetrics->minSizeWithBorderMargin.height = height;
+}
+
+// We support LTR layout only here for now.
+static void CalculateToolbarButtonSpacing(WidgetNodeType aAppearance,
+ ToolbarButtonGTKMetrics* aMetrics) {
+ GtkStyleContext* style = GetStyleContext(aAppearance);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &aMetrics->buttonMargin);
+
+ // Get titlebar spacing, a default one is 6 pixels (gtk/gtkheaderbar.c)
+ gint buttonSpacing = 6;
+ g_object_get(GetWidget(MOZ_GTK_HEADER_BAR), "spacing", &buttonSpacing,
+ nullptr);
+
+ // We apply spacing as a margin equally to both adjacent buttons.
+ buttonSpacing /= 2;
+
+ if (!aMetrics->firstButton) {
+ aMetrics->buttonMargin.left += buttonSpacing;
+ }
+ if (!aMetrics->lastButton) {
+ aMetrics->buttonMargin.right += buttonSpacing;
+ }
+
+ aMetrics->minSizeWithBorderMargin.width +=
+ aMetrics->buttonMargin.right + aMetrics->buttonMargin.left;
+ aMetrics->minSizeWithBorderMargin.height +=
+ aMetrics->buttonMargin.top + aMetrics->buttonMargin.bottom;
+}
+
+size_t GetGtkHeaderBarButtonLayout(Span<ButtonLayout> aButtonLayout,
+ bool* aReversedButtonsPlacement) {
+ gchar* decorationLayoutSetting = nullptr;
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-decoration-layout", &decorationLayoutSetting,
+ nullptr);
+ auto free = mozilla::MakeScopeExit([&] { g_free(decorationLayoutSetting); });
+
+ // Use a default layout
+ const gchar* decorationLayout = "menu:minimize,maximize,close";
+ if (decorationLayoutSetting) {
+ decorationLayout = decorationLayoutSetting;
+ }
+
+ // "minimize,maximize,close:" layout means buttons are on the opposite
+ // titlebar side. close button is always there.
+ if (aReversedButtonsPlacement) {
+ const char* closeButton = strstr(decorationLayout, "close");
+ const char* separator = strchr(decorationLayout, ':');
+ *aReversedButtonsPlacement =
+ closeButton && separator && closeButton < separator;
+ }
+
+ // We check what position a button string is stored in decorationLayout.
+ //
+ // decorationLayout gets its value from the GNOME preference:
+ // org.gnome.desktop.vm.preferences.button-layout via the
+ // gtk-decoration-layout property.
+ //
+ // Documentation of the gtk-decoration-layout property can be found here:
+ // https://developer.gnome.org/gtk3/stable/GtkSettings.html#GtkSettings--gtk-decoration-layout
+ if (aButtonLayout.IsEmpty()) {
+ return 0;
+ }
+
+ nsDependentCSubstring layout(decorationLayout, strlen(decorationLayout));
+
+ size_t activeButtons = 0;
+ for (const auto& part : layout.Split(':')) {
+ for (const auto& button : part.Split(',')) {
+ if (button.EqualsLiteral("close")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_CLOSE};
+ } else if (button.EqualsLiteral("minimize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE};
+ } else if (button.EqualsLiteral("maximize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE};
+ }
+ if (activeButtons == aButtonLayout.Length()) {
+ return activeButtons;
+ }
+ }
+ }
+ return activeButtons;
+}
+
+static void EnsureToolbarMetrics() {
+ if (sToolbarMetrics.initialized) {
+ return;
+ }
+ // Make sure we have clean cache after theme reset, etc.
+ memset(&sToolbarMetrics, 0, sizeof(sToolbarMetrics));
+
+ // Calculate titlebar button visibility and positions.
+ ButtonLayout aButtonLayout[TOOLBAR_BUTTONS];
+ size_t activeButtonNums =
+ GetGtkHeaderBarButtonLayout(Span(aButtonLayout), nullptr);
+
+ for (size_t i = 0; i < activeButtonNums; i++) {
+ int buttonIndex =
+ (aButtonLayout[i].mType - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ ToolbarButtonGTKMetrics* metrics = sToolbarMetrics.button + buttonIndex;
+ metrics->visible = true;
+ // Mark first button
+ if (!i) {
+ metrics->firstButton = true;
+ }
+ // Mark last button.
+ if (i == (activeButtonNums - 1)) {
+ metrics->lastButton = true;
+ }
+
+ CalculateToolbarButtonMetrics(aButtonLayout[i].mType, metrics);
+ CalculateToolbarButtonSpacing(aButtonLayout[i].mType, metrics);
+ }
+
+ sToolbarMetrics.initialized = true;
+}
+
+const ToolbarButtonGTKMetrics* GetToolbarButtonMetrics(
+ WidgetNodeType aAppearance) {
+ EnsureToolbarMetrics();
+
+ int buttonIndex = (aAppearance - MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ NS_ASSERTION(buttonIndex >= 0 && buttonIndex <= TOOLBAR_BUTTONS,
+ "GetToolbarButtonMetrics(): Wrong titlebar button!");
+ return sToolbarMetrics.button + buttonIndex;
+}
+
+static gint moz_gtk_window_decoration_paint(cairo_t* cr,
+ const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ if (mozilla::widget::GdkIsWaylandDisplay()) {
+ // Doesn't seem to be needed.
+ return MOZ_GTK_SUCCESS;
+ }
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* windowStyle =
+ GetStyleContext(MOZ_GTK_HEADERBAR_WINDOW, state->image_scale);
+ const bool solidDecorations =
+ gtk_style_context_has_class(windowStyle, "solid-csd");
+ GtkStyleContext* decorationStyle =
+ GetStyleContext(solidDecorations ? MOZ_GTK_WINDOW_DECORATION_SOLID
+ : MOZ_GTK_WINDOW_DECORATION,
+ state->image_scale, GTK_TEXT_DIR_LTR, state_flags);
+
+ gtk_render_background(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ gtk_render_frame(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_button_paint(cairo_t* cr, const GdkRectangle* rect,
+ GtkWidgetState* state, GtkReliefStyle relief,
+ GtkWidget* widget,
+ GtkTextDirection direction) {
+ if (!widget) {
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = gtk_widget_get_style_context(widget);
+ gint x = rect->x, y = rect->y, width = rect->width, height = rect->height;
+
+ gtk_widget_set_direction(widget, direction);
+
+ gtk_style_context_save(style);
+ StyleContextSetScale(style, state->image_scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ if (state->isDefault && relief == GTK_RELIEF_NORMAL && !state->focused &&
+ !(state_flags & GTK_STATE_FLAG_PRELIGHT)) {
+ /* handle default borders both outside and inside the button */
+ gint default_top, default_left, default_bottom, default_right;
+ moz_gtk_button_get_default_overflow(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x -= default_left;
+ y -= default_top;
+ width += default_left + default_right;
+ height += default_top + default_bottom;
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ moz_gtk_button_get_default_border(&default_top, &default_left,
+ &default_bottom, &default_right);
+ x += default_left;
+ y += default_top;
+ width -= (default_left + default_right);
+ height -= (default_top + default_bottom);
+ } else if (relief != GTK_RELIEF_NONE || state->depressed ||
+ (state_flags & GTK_STATE_FLAG_PRELIGHT)) {
+ /* the following line can trigger an assertion (Crux theme)
+ file ../../gdk/gdkwindow.c: line 1846 (gdk_window_clear_area):
+ assertion `GDK_IS_WINDOW (window)' failed */
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ }
+
+ if (state->focused) {
+ GtkBorder border;
+ gtk_style_context_get_border(style, state_flags, &border);
+ x += border.left;
+ y += border.top;
+ width -= (border.left + border.right);
+ height -= (border.top + border.bottom);
+ gtk_render_focus(style, cr, x, y, width, height);
+ }
+ gtk_style_context_restore(style);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_header_bar_button_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkReliefStyle relief,
+ WidgetNodeType aIconWidgetType,
+ GtkTextDirection direction) {
+ GdkRectangle rect = *aRect;
+ // We need to inset our calculated margin because it also
+ // contains titlebar button spacing.
+ const ToolbarButtonGTKMetrics* metrics = GetToolbarButtonMetrics(
+ aIconWidgetType == MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE
+ ? MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
+ : aIconWidgetType);
+ Inset(&rect, metrics->buttonMargin);
+
+ GtkWidget* buttonWidget = GetWidget(aIconWidgetType);
+ if (!buttonWidget) {
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ moz_gtk_button_paint(cr, &rect, state, relief, buttonWidget, direction);
+
+ GtkWidget* iconWidget =
+ gtk_bin_get_child(GTK_BIN(GetWidget(aIconWidgetType)));
+ if (!iconWidget) {
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ cairo_surface_t* surface =
+ GetWidgetIconSurface(iconWidget, state->image_scale);
+
+ if (surface) {
+ GtkStyleContext* style = gtk_widget_get_style_context(buttonWidget);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ gtk_style_context_save(style);
+ StyleContextSetScale(style, state->image_scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ /* This is available since Gtk+ 3.10 as well as GtkHeaderBar */
+ gtk_render_icon_surface(style, cr, surface, rect.x + metrics->iconXPosition,
+ rect.y + metrics->iconYPosition);
+ gtk_style_context_restore(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toggle_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, gboolean selected,
+ gboolean inconsistent, gboolean isradio,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint x, y, width, height;
+ GtkStyleContext* style;
+
+ // We need to call this before GetStyleContext, because otherwise we would
+ // reset state flags
+ const ToggleGTKMetrics* metrics =
+ GetToggleMetrics(isradio ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON);
+ // Clamp the rect and paint it center aligned in the rect.
+ x = rect->x;
+ y = rect->y;
+ width = rect->width;
+ height = rect->height;
+
+ if (rect->width < rect->height) {
+ y = rect->y + (rect->height - rect->width) / 2;
+ height = rect->width;
+ }
+
+ if (rect->height < rect->width) {
+ x = rect->x + (rect->width - rect->height) / 2;
+ width = rect->height;
+ }
+
+ if (selected)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+
+ if (inconsistent)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_INCONSISTENT);
+
+ style = GetStyleContext(isradio ? MOZ_GTK_RADIOBUTTON : MOZ_GTK_CHECKBUTTON,
+ state->image_scale, direction, state_flags);
+
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+ // Indicator is inset by the toggle's padding and border.
+ gint indicator_x = x + metrics->borderAndPadding.left;
+ gint indicator_y = y + metrics->borderAndPadding.top;
+ gint indicator_width = metrics->minSizeWithBorder.width -
+ metrics->borderAndPadding.left -
+ metrics->borderAndPadding.right;
+ gint indicator_height = metrics->minSizeWithBorder.height -
+ metrics->borderAndPadding.top -
+ metrics->borderAndPadding.bottom;
+ if (isradio) {
+ gtk_render_option(style, cr, indicator_x, indicator_y, indicator_width,
+ indicator_height);
+ } else {
+ gtk_render_check(style, cr, indicator_x, indicator_y, indicator_width,
+ indicator_height);
+ }
+ } else {
+ if (isradio) {
+ gtk_render_option(style, cr, x, y, width, height);
+ } else {
+ gtk_render_check(style, cr, x, y, width, height);
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint calculate_button_inner_rect(GtkWidget* button,
+ const GdkRectangle* rect,
+ GdkRectangle* inner_rect,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+ GtkBorder border;
+ GtkBorder padding = {0, 0, 0, 0};
+
+ style = gtk_widget_get_style_context(button);
+
+ /* This mirrors gtkbutton's child positioning */
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ inner_rect->x = rect->x + border.left + padding.left;
+ inner_rect->y = rect->y + padding.top + border.top;
+ inner_rect->width =
+ MAX(1, rect->width - padding.left - padding.right - border.left * 2);
+ inner_rect->height =
+ MAX(1, rect->height - padding.top - padding.bottom - border.top * 2);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint calculate_arrow_rect(GtkWidget* arrow, GdkRectangle* rect,
+ GdkRectangle* arrow_rect,
+ GtkTextDirection direction) {
+ /* defined in gtkarrow.c */
+ gfloat arrow_scaling = 0.7;
+ gfloat xalign, xpad;
+ gint extent;
+ gint mxpad, mypad;
+ gfloat mxalign, myalign;
+ GtkMisc* misc = GTK_MISC(arrow);
+
+ gtk_style_context_get_style(gtk_widget_get_style_context(arrow),
+ "arrow_scaling", &arrow_scaling, NULL);
+
+ gtk_misc_get_padding(misc, &mxpad, &mypad);
+ extent = MIN((rect->width - mxpad * 2), (rect->height - mypad * 2)) *
+ arrow_scaling;
+
+ gtk_misc_get_alignment(misc, &mxalign, &myalign);
+
+ xalign = direction == GTK_TEXT_DIR_LTR ? mxalign : 1.0 - mxalign;
+ xpad = mxpad + (rect->width - extent) * xalign;
+
+ arrow_rect->x = direction == GTK_TEXT_DIR_LTR ? floor(rect->x + xpad)
+ : ceil(rect->x + xpad);
+ arrow_rect->y = floor(rect->y + mypad + ((rect->height - extent) * myalign));
+
+ arrow_rect->width = arrow_rect->height = extent;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/**
+ * Get minimum widget size as sum of margin, padding, border and
+ * min-width/min-height.
+ */
+static void moz_gtk_get_widget_min_size(GtkStyleContext* style, int* width,
+ int* height) {
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state_flags, "min-height", height, "min-width",
+ width, nullptr);
+
+ GtkBorder border, padding, margin;
+ gtk_style_context_get_border(style, state_flags, &border);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+
+ *width += border.left + border.right + margin.left + margin.right +
+ padding.left + padding.right;
+ *height += border.top + border.bottom + margin.top + margin.bottom +
+ padding.top + padding.bottom;
+}
+
+static void Inset(GdkRectangle* rect, const GtkBorder& aBorder) {
+ rect->x += aBorder.left;
+ rect->y += aBorder.top;
+ rect->width -= aBorder.left + aBorder.right;
+ rect->height -= aBorder.top + aBorder.bottom;
+}
+
+// Inset a rectangle by the margins specified in a style context.
+static void InsetByMargin(GdkRectangle* rect, GtkStyleContext* style) {
+ GtkBorder margin;
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ Inset(rect, margin);
+}
+
+// Inset a rectangle by the border and padding specified in a style context.
+static void InsetByBorderPadding(GdkRectangle* rect, GtkStyleContext* style) {
+ GtkStateFlags state = gtk_style_context_get_state(style);
+ GtkBorder padding, border;
+
+ gtk_style_context_get_padding(style, state, &padding);
+ Inset(rect, padding);
+ gtk_style_context_get_border(style, state, &border);
+ Inset(rect, border);
+}
+
+static void moz_gtk_draw_styled_frame(GtkStyleContext* style, cairo_t* cr,
+ const GdkRectangle* aRect,
+ bool drawFocus) {
+ GdkRectangle rect = *aRect;
+
+ InsetByMargin(&rect, style);
+
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+ if (drawFocus) {
+ gtk_render_focus(style, cr, rect.x, rect.y, rect.width, rect.height);
+ }
+}
+
+static gint moz_gtk_inner_spin_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->image_scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ GdkRectangle arrow_rect;
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+
+ // align spin to the left
+ arrow_rect.x = rect->x;
+
+ // up button
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2 - 3;
+ gtk_render_arrow(style, cr, ARROW_UP, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+
+ // down button
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2 + 3;
+ gtk_render_arrow(style, cr, ARROW_DOWN, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_spin_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->image_scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_spin_updown_paint(cairo_t* cr, GdkRectangle* rect,
+ gboolean isDown, GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->image_scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ /* hard code these values */
+ GdkRectangle arrow_rect;
+ arrow_rect.width = 6;
+ arrow_rect.height = 6;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ arrow_rect.y += isDown ? -1 : 1;
+
+ gtk_render_arrow(style, cr, isDown ? ARROW_DOWN : ARROW_UP, arrow_rect.x,
+ arrow_rect.y, arrow_rect.width);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_range_draw() for reference.
+ */
+static gint moz_gtk_scale_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkOrientation flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gint x, y, width, height, min_width, min_height;
+ GtkStyleContext* style;
+ GtkBorder margin;
+
+ moz_gtk_get_scale_metrics(flags, &min_width, &min_height);
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCALE_TROUGH_VERTICAL;
+ style = GetStyleContext(widget, state->image_scale, direction, state_flags);
+ gtk_style_context_get_margin(style, state_flags, &margin);
+
+ // Clamp the dimension perpendicular to the direction that the slider crosses
+ // to the minimum size.
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ width = rect->width - (margin.left + margin.right);
+ height = min_height - (margin.top + margin.bottom);
+ x = rect->x + margin.left;
+ y = rect->y + (rect->height - height) / 2;
+ } else {
+ width = min_width - (margin.left + margin.right);
+ height = rect->height - (margin.top + margin.bottom);
+ x = rect->x + (rect->width - width) / 2;
+ y = rect->y + margin.top;
+ }
+
+ gtk_render_background(style, cr, x, y, width, height);
+ gtk_render_frame(style, cr, x, y, width, height);
+
+ if (state->focused)
+ gtk_render_focus(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scale_thumb_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkOrientation flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ gint thumb_width, thumb_height, x, y;
+
+ /* determine the thumb size, and position the thumb in the center in the
+ * opposite axis
+ */
+ if (flags == GTK_ORIENTATION_HORIZONTAL) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL, &thumb_width,
+ &thumb_height);
+ x = rect->x;
+ y = rect->y + (rect->height - thumb_height) / 2;
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
+ &thumb_width);
+ x = rect->x + (rect->width - thumb_width) / 2;
+ y = rect->y;
+ }
+
+ WidgetNodeType widget = (flags == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_THUMB_HORIZONTAL
+ : MOZ_GTK_SCALE_THUMB_VERTICAL;
+ style = GetStyleContext(widget, state->image_scale, direction, state_flags);
+ gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_hpaned_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL, state->image_scale,
+ GTK_TEXT_DIR_LTR, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_vpaned_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL, state->image_scale,
+ GTK_TEXT_DIR_LTR, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_entry_draw() for reference.
+static gint moz_gtk_entry_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state, GtkStyleContext* style,
+ WidgetNodeType widget) {
+ GdkRectangle rect = *aRect;
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Paint the border, except for 'menulist-textfield' that isn't focused:
+ if (widget != MOZ_GTK_DROPDOWN_ENTRY || state->focused) {
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_text_view_paint(cairo_t* cr, GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ // GtkTextView and GtkScrolledWindow do not set active and prelight flags.
+ // The use of focus with MOZ_GTK_SCROLLED_WINDOW here is questionable
+ // because a parent widget will not have focus when its child GtkTextView
+ // has focus, but perhaps this may help identify a focused textarea with
+ // some themes as GtkTextView backgrounds do not typically render
+ // differently with focus.
+ GtkStateFlags state_flags = state->disabled ? GTK_STATE_FLAG_INSENSITIVE
+ : state->focused ? GTK_STATE_FLAG_FOCUSED
+ : GTK_STATE_FLAG_NORMAL;
+
+ GtkStyleContext* style_frame = GetStyleContext(
+ MOZ_GTK_SCROLLED_WINDOW, state->image_scale, direction, state_flags);
+ gtk_render_frame(style_frame, cr, aRect->x, aRect->y, aRect->width,
+ aRect->height);
+
+ GdkRectangle rect = *aRect;
+ InsetByBorderPadding(&rect, style_frame);
+
+ GtkStyleContext* style = GetStyleContext(
+ MOZ_GTK_TEXT_VIEW, state->image_scale, direction, state_flags);
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ // There is a separate "text" window, which usually provides the
+ // background behind the text. However, this is transparent in Ambiance
+ // for GTK 3.20, in which case the MOZ_GTK_TEXT_VIEW background is
+ // visible.
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT, state->image_scale, direction,
+ state_flags);
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_treeview_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint xthickness, ythickness;
+ GtkStyleContext* style;
+ GtkStyleContext* style_tree;
+ GtkStateFlags state_flags;
+ GtkBorder border;
+
+ /* only handle disabled and normal states, otherwise the whole background
+ * area will be painted differently with other states */
+ state_flags =
+ state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+
+ style =
+ GetStyleContext(MOZ_GTK_SCROLLED_WINDOW, state->image_scale, direction);
+ gtk_style_context_get_border(style, state_flags, &border);
+ xthickness = border.left;
+ ythickness = border.top;
+
+ style_tree =
+ GetStyleContext(MOZ_GTK_TREEVIEW_VIEW, state->image_scale, direction);
+ gtk_render_background(style_tree, cr, rect->x + xthickness,
+ rect->y + ythickness, rect->width - 2 * xthickness,
+ rect->height - 2 * ythickness);
+
+ style =
+ GetStyleContext(MOZ_GTK_SCROLLED_WINDOW, state->image_scale, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tree_header_cell_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ gboolean isSorted,
+ GtkTextDirection direction) {
+ moz_gtk_button_paint(cr, aRect, state, GTK_RELIEF_NORMAL,
+ GetWidget(MOZ_GTK_TREE_HEADER_CELL), direction);
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_expander_paint() for reference.
+ */
+static gint moz_gtk_treeview_expander_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkExpanderStyle expander_state,
+ GtkTextDirection direction) {
+ /* Because the frame we get is of the entire treeview, we can't get the
+ * precise event state of one expander, thus rendering hover and active
+ * feedback useless. */
+ GtkStateFlags state_flags =
+ state->disabled ? GTK_STATE_FLAG_INSENSITIVE : GTK_STATE_FLAG_NORMAL;
+
+ if (state->inHover)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_PRELIGHT);
+ if (state->selected)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | GTK_STATE_FLAG_SELECTED);
+
+ /* GTK_STATE_FLAG_ACTIVE controls expanded/colapsed state rendering
+ * in gtk_render_expander()
+ */
+ if (expander_state == GTK_EXPANDER_EXPANDED)
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+ else
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags & ~(checkbox_check_state));
+
+ GtkStyleContext* style = GetStyleContext(
+ MOZ_GTK_TREEVIEW_EXPANDER, state->image_scale, direction, state_flags);
+ gtk_render_expander(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* See gtk_separator_draw() for reference.
+ */
+static gint moz_gtk_combo_box_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect, real_arrow_rect;
+ gint separator_width;
+ gboolean wide_separators;
+ GtkStyleContext* style;
+ GtkRequisition arrow_req;
+
+ GtkWidget* comboBoxButton = GetWidget(MOZ_GTK_COMBOBOX_BUTTON);
+ GtkWidget* comboBoxArrow = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+ if (!comboBoxButton || !comboBoxArrow) {
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+
+ /* Also sets the direction on gComboBoxButtonWidget, which is then
+ * inherited by the separator and arrow */
+ moz_gtk_button_paint(cr, aRect, state, GTK_RELIEF_NORMAL, comboBoxButton,
+ direction);
+
+ calculate_button_inner_rect(comboBoxButton, aRect, &arrow_rect, direction);
+ /* Now arrow_rect contains the inner rect ; we want to correct the width
+ * to what the arrow needs (see gtk_combo_box_size_allocate) */
+ gtk_widget_get_preferred_size(comboBoxArrow, NULL, &arrow_req);
+ moz_gtk_sanity_preferred_size(&arrow_req);
+
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x += arrow_rect.width - arrow_req.width;
+ arrow_rect.width = arrow_req.width;
+
+ calculate_arrow_rect(comboBoxArrow, &arrow_rect, &real_arrow_rect, direction);
+
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ARROW, state->image_scale);
+ gtk_render_arrow(style, cr, ARROW_DOWN, real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+
+ /* If there is no separator in the theme, there's nothing left to do. */
+ GtkWidget* widget = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (!widget) {
+ return MOZ_GTK_SUCCESS;
+ }
+ style = gtk_widget_get_style_context(widget);
+ StyleContextSetScale(style, state->image_scale);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ if (wide_separators) {
+ if (direction == GTK_TEXT_DIR_LTR)
+ arrow_rect.x -= separator_width;
+ else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_frame(style, cr, arrow_rect.x, arrow_rect.y, separator_width,
+ arrow_rect.height);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ GtkBorder padding;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ arrow_rect.x -= padding.left;
+ } else
+ arrow_rect.x += arrow_rect.width;
+
+ gtk_render_line(style, cr, arrow_rect.x, arrow_rect.y, arrow_rect.x,
+ arrow_rect.y + arrow_rect.height);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ if (arrow_type == GTK_ARROW_LEFT) {
+ arrow_type = GTK_ARROW_RIGHT;
+ } else if (arrow_type == GTK_ARROW_RIGHT) {
+ arrow_type = GTK_ARROW_LEFT;
+ }
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type == GTK_ARROW_NONE) return MOZ_GTK_SUCCESS;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ if (!widget) {
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ calculate_arrow_rect(widget, rect, &arrow_rect, direction);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = GetStyleContext(
+ MOZ_GTK_BUTTON_ARROW, state->image_scale, direction, state_flags);
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tooltip_paint(cairo_t* cr, const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ // Tooltip widget is made in GTK3 as following tree:
+ // Tooltip window
+ // Horizontal Box
+ // Icon (not supported by Firefox)
+ // Label
+ // Each element can be fully styled by CSS of GTK theme.
+ // We have to draw all elements with appropriate offset and right dimensions.
+
+ // Tooltip drawing
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TOOLTIP, state->image_scale, direction);
+ GdkRectangle rect = *aRect;
+ gtk_render_background(style, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(style, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Horizontal Box drawing
+ //
+ // The box element has hard-coded 6px margin-* GtkWidget properties, which
+ // are added between the window dimensions and the CSS margin box of the
+ // horizontal box. The frame of the tooltip window is drawn in the
+ // 6px margin.
+ // For drawing Horizontal Box we have to inset drawing area by that 6px
+ // plus its CSS margin.
+ GtkStyleContext* boxStyle =
+ GetStyleContext(MOZ_GTK_TOOLTIP_BOX, state->image_scale, direction);
+
+ rect.x += 6;
+ rect.y += 6;
+ rect.width -= 12;
+ rect.height -= 12;
+
+ InsetByMargin(&rect, boxStyle);
+ gtk_render_background(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+ gtk_render_frame(boxStyle, cr, rect.x, rect.y, rect.width, rect.height);
+
+ // Label drawing
+ InsetByBorderPadding(&rect, boxStyle);
+
+ GtkStyleContext* labelStyle =
+ GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL, state->image_scale, direction);
+ moz_gtk_draw_styled_frame(labelStyle, cr, &rect, false);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_resizer_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_RESIZER, state->image_scale, GTK_TEXT_DIR_LTR,
+ GetStateFlagsFromGtkWidgetState(state));
+
+ // Workaround unico not respecting the text direction for resizers.
+ // See bug 1174248.
+ cairo_save(cr);
+ if (direction == GTK_TEXT_DIR_RTL) {
+ cairo_matrix_t mat;
+ cairo_matrix_init_translate(&mat, 2 * rect->x + rect->width, 0);
+ cairo_matrix_scale(&mat, -1, 1);
+ cairo_transform(cr, &mat);
+ }
+
+ gtk_render_handle(style, cr, rect->x, rect->y, rect->width, rect->height);
+ cairo_restore(cr);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_frame_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_FRAME, state->image_scale, direction);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_progressbar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_PROGRESS_TROUGH, state->image_scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_progress_chunk_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction,
+ WidgetNodeType widget) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_PROGRESS_CHUNK, state->image_scale, direction);
+
+ if (widget == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ /**
+ * The bar's size and the bar speed are set depending of the progress'
+ * size. These could also be constant for all progress bars easily.
+ */
+ gboolean vertical =
+ (widget == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE);
+
+ /* The size of the dimension we are going to use for the animation. */
+ const gint progressSize = vertical ? rect->height : rect->width;
+
+ /* The bar is using a fifth of the element size, based on GtkProgressBar
+ * activity-blocks property. */
+ const gint barSize = MAX(1, progressSize / 5);
+
+ /* Represents the travel that has to be done for a complete cycle. */
+ const gint travel = 2 * (progressSize - barSize);
+
+ /* period equals to travel / pixelsPerMillisecond
+ * where pixelsPerMillisecond equals progressSize / 1000.0.
+ * This is equivalent to 1600. */
+ static const guint period = 1600;
+ const gint t = PR_IntervalToMilliseconds(PR_IntervalNow()) % period;
+ const gint dx = travel * t / period;
+
+ if (vertical) {
+ rect->y += (dx < travel / 2) ? dx : travel - dx;
+ rect->height = barSize;
+ } else {
+ rect->x += (dx < travel / 2) ? dx : travel - dx;
+ rect->width = barSize;
+ }
+ }
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_get_tab_thickness(GtkStyleContext* style) {
+ if (!notebook_has_tab_gap)
+ return 0; /* tabs do not overdraw the tabpanel border with "no gap" style */
+
+ GtkBorder border;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ if (border.top < 2) return 2; /* some themes don't set ythickness correctly */
+
+ return border.top;
+}
+
+gint moz_gtk_get_tab_thickness(WidgetNodeType aNodeType) {
+ GtkStyleContext* style = GetStyleContext(aNodeType);
+ int thickness = moz_gtk_get_tab_thickness(style);
+ return thickness;
+}
+
+/* actual small tabs */
+static gint moz_gtk_tab_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, GtkTabFlags flags,
+ GtkTextDirection direction,
+ WidgetNodeType widget) {
+ /* When the tab isn't selected, we just draw a notebook extension.
+ * When it is selected, we overwrite the adjacent border of the tabpanel
+ * touching the tab with a pierced border (called "the gap") to make the
+ * tab appear physically attached to the tabpanel; see details below. */
+
+ GtkStyleContext* style;
+ GdkRectangle tabRect;
+ GdkRectangle focusRect;
+ GdkRectangle backRect;
+ int initial_gap = 0;
+ bool isBottomTab = (widget == MOZ_GTK_TAB_BOTTOM);
+
+ style = GetStyleContext(widget, state->image_scale, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+ tabRect = *rect;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ tabRect.width -= initial_gap;
+
+ if (direction != GTK_TEXT_DIR_RTL) {
+ tabRect.x += initial_gap;
+ }
+ }
+
+ focusRect = backRect = tabRect;
+
+ if (notebook_has_tab_gap) {
+ if ((flags & MOZ_GTK_TAB_SELECTED) == 0) {
+ /* Only draw the tab */
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height,
+ isBottomTab ? GTK_POS_TOP : GTK_POS_BOTTOM);
+ } else {
+ /* Draw the tab and the gap
+ * We want the gap to be positioned exactly on the tabpanel top
+ * border; since tabbox.css may set a negative margin so that the tab
+ * frame rect already overlaps the tabpanel frame rect, we need to take
+ * that into account when drawing. To that effect, nsNativeThemeGTK
+ * passes us this negative margin (bmargin in the graphic below) in the
+ * lowest bits of |flags|. We use it to set gap_voffset, the distance
+ * between the top of the gap and the bottom of the tab (resp. the
+ * bottom of the gap and the top of the tab when we draw a bottom tab),
+ * while ensuring that the gap always touches the border of the tab,
+ * i.e. 0 <= gap_voffset <= gap_height, to avoid surprinsing results
+ * with big negative or positive margins.
+ * Here is a graphical explanation in the case of top tabs:
+ * ___________________________
+ * / \
+ * | T A B |
+ * ----------|. . . . . . . . . . . . . . .|----- top of tabpanel
+ * : ^ bmargin : ^
+ * : | (-negative margin, : |
+ * bottom : v passed in flags) : | gap_height
+ * of -> :.............................: | (the size of the
+ * the tab . part of the gap . | tabpanel top border)
+ * . outside of the tab . v
+ * ----------------------------------------------
+ *
+ * To draw the gap, we use gtk_render_frame_gap(), see comment in
+ * moz_gtk_tabpanels_paint(). This gap is made 3 * gap_height tall,
+ * which should suffice to ensure that the only visible border is the
+ * pierced one. If the tab is in the middle, we make the box_gap begin
+ * a bit to the left of the tab and end a bit to the right, adjusting
+ * the gap position so it still is under the tab, because we want the
+ * rendering of a gap in the middle of a tabpanel. This is the role of
+ * the gints gap_{l,r}_offset. On the contrary, if the tab is the
+ * first, we align the start border of the box_gap with the start
+ * border of the tab (left if LTR, right if RTL), by setting the
+ * appropriate offset to 0.*/
+ gint gap_loffset, gap_roffset, gap_voffset, gap_height;
+
+ /* Get height needed by the gap */
+ gap_height = moz_gtk_get_tab_thickness(style);
+
+ /* Extract gap_voffset from the first bits of flags */
+ gap_voffset = flags & MOZ_GTK_TAB_MARGIN_MASK;
+ if (gap_voffset > gap_height) gap_voffset = gap_height;
+
+ /* Set gap_{l,r}_offset to appropriate values */
+ gap_loffset = gap_roffset = 20; /* should be enough */
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ if (direction == GTK_TEXT_DIR_RTL)
+ gap_roffset = initial_gap;
+ else
+ gap_loffset = initial_gap;
+ }
+
+ GtkStyleContext* panelStyle =
+ GetStyleContext(MOZ_GTK_TABPANELS, state->image_scale, direction);
+
+ if (isBottomTab) {
+ /* Draw the tab on bottom */
+ focusRect.y += gap_voffset;
+ focusRect.height -= gap_voffset;
+
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y + gap_voffset,
+ tabRect.width, tabRect.height - gap_voffset,
+ GTK_POS_TOP);
+
+ backRect.y += (gap_voffset - gap_height);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(panelStyle, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width,
+ backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(panelStyle, cr, tabRect.x - gap_loffset,
+ tabRect.y + gap_voffset - 3 * gap_height,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_BOTTOM, gap_loffset,
+ gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ } else {
+ /* Draw the tab on top */
+ focusRect.height -= gap_voffset;
+ gtk_render_extension(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height - gap_voffset, GTK_POS_BOTTOM);
+
+ backRect.y += (tabRect.height - gap_voffset);
+ backRect.height = gap_height;
+
+ /* Draw the gap; erase with background color before painting in
+ * case theme does not */
+ gtk_render_background(panelStyle, cr, backRect.x, backRect.y,
+ backRect.width, backRect.height);
+
+ cairo_save(cr);
+ cairo_rectangle(cr, backRect.x, backRect.y, backRect.width,
+ backRect.height);
+ cairo_clip(cr);
+
+ gtk_render_frame_gap(panelStyle, cr, tabRect.x - gap_loffset,
+ tabRect.y + tabRect.height - gap_voffset,
+ tabRect.width + gap_loffset + gap_roffset,
+ 3 * gap_height, GTK_POS_TOP, gap_loffset,
+ gap_loffset + tabRect.width);
+ cairo_restore(cr);
+ }
+ }
+ } else {
+ gtk_render_background(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height);
+ gtk_render_frame(style, cr, tabRect.x, tabRect.y, tabRect.width,
+ tabRect.height);
+ }
+
+ if (state->focused) {
+ /* Paint the focus ring */
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, GetStateFlagsFromGtkWidgetState(state),
+ &padding);
+
+ focusRect.x += padding.left;
+ focusRect.width -= (padding.left + padding.right);
+ focusRect.y += padding.top;
+ focusRect.height -= (padding.top + padding.bottom);
+
+ gtk_render_focus(style, cr, focusRect.x, focusRect.y, focusRect.width,
+ focusRect.height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+/* tab area*/
+static gint moz_gtk_tabpanels_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TABPANELS, state->image_scale, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ /*
+ * The gap size is not needed in moz_gtk_tabpanels_paint because
+ * the gap will be painted with the foreground tab in moz_gtk_tab_paint.
+ *
+ * However, if moz_gtk_tabpanels_paint just uses gtk_render_frame(),
+ * the theme will think that there are no tabs and may draw something
+ * different.Hence the trick of using two clip regions, and drawing the
+ * gap outside each clip region, to get the correct frame for
+ * a tabpanel with tabs.
+ */
+ /* left side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x, rect->y, rect->x + rect->width / 2,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr, rect->x, rect->y, rect->width, rect->height,
+ GTK_POS_TOP, rect->width - 1, rect->width);
+ cairo_restore(cr);
+
+ /* right side */
+ cairo_save(cr);
+ cairo_rectangle(cr, rect->x + rect->width / 2, rect->y, rect->x + rect->width,
+ rect->y + rect->height);
+ cairo_clip(cr);
+ gtk_render_frame_gap(style, cr, rect->x, rect->y, rect->width, rect->height,
+ GTK_POS_TOP, 0, 1);
+ cairo_restore(cr);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_tab_scroll_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+ gdouble arrow_angle;
+ gint arrow_size = MIN(rect->width, rect->height);
+ gint x = rect->x + (rect->width - arrow_size) / 2;
+ gint y = rect->y + (rect->height - arrow_size) / 2;
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ arrow_type =
+ (arrow_type == GTK_ARROW_LEFT) ? GTK_ARROW_RIGHT : GTK_ARROW_LEFT;
+ }
+ switch (arrow_type) {
+ case GTK_ARROW_LEFT:
+ arrow_angle = ARROW_LEFT;
+ break;
+ case GTK_ARROW_RIGHT:
+ arrow_angle = ARROW_RIGHT;
+ break;
+ case GTK_ARROW_DOWN:
+ arrow_angle = ARROW_DOWN;
+ break;
+ default:
+ arrow_angle = ARROW_UP;
+ break;
+ }
+ if (arrow_type != GTK_ARROW_NONE) {
+ style = GetStyleContext(MOZ_GTK_TAB_SCROLLARROW, state->image_scale,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_arrow(style, cr, arrow_angle, x, y, arrow_size);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_header_bar_paint(WidgetNodeType widgetType, cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = GetStyleContext(widgetType, state->image_scale,
+ GTK_TEXT_DIR_NONE, state_flags);
+
+ // Some themes like Elementary's style the container of the headerbar rather
+ // than the header bar itself.
+ if (HeaderBarShouldDrawContainer(widgetType)) {
+ auto containerType = widgetType == MOZ_GTK_HEADER_BAR
+ ? MOZ_GTK_HEADERBAR_FIXED
+ : MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED;
+ style = GetStyleContext(containerType, state->image_scale,
+ GTK_TEXT_DIR_NONE, state_flags);
+ }
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom,
+ // NOTE: callers depend on direction being used
+ // only for MOZ_GTK_DROPDOWN widgets.
+ GtkTextDirection direction) {
+ GtkWidget* w;
+ GtkStyleContext* style;
+ *left = *top = *right = *bottom = 0;
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON: {
+ style = GetStyleContext(MOZ_GTK_BUTTON);
+
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_BUTTON)));
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON) {
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, "image-button");
+ }
+
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ if (widget == MOZ_GTK_TOOLBAR_BUTTON) gtk_style_context_restore(style);
+
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY: {
+ style = GetStyleContext(widget);
+
+ // XXX: Subtract 1 pixel from the padding to account for the default
+ // padding in forms.css. See bug 1187385.
+ *left = *top = *right = *bottom = -1;
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ case MOZ_GTK_TREEVIEW: {
+ style = GetStyleContext(MOZ_GTK_SCROLLED_WINDOW);
+ moz_gtk_add_style_border(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TREE_HEADER_CELL: {
+ /* A Tree Header in GTK is just a different styled button
+ * It must be placed in a TreeView for getting the correct style
+ * assigned.
+ * That is why the following code is the same as for MOZ_GTK_BUTTON.
+ * */
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_TREE_HEADER_CELL)));
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_DROPDOWN: {
+ /* We need to account for the arrow on the dropdown, so text
+ * doesn't come too close to the arrow, or in some cases spill
+ * into the arrow. */
+ gboolean wide_separators;
+ gint separator_width;
+ GtkRequisition arrow_req;
+ GtkBorder border;
+
+ *left = *top = *right = *bottom = gtk_container_get_border_width(
+ GTK_CONTAINER(GetWidget(MOZ_GTK_COMBOBOX_BUTTON)));
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_BUTTON);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+
+ /* If there is no separator, don't try to count its width. */
+ separator_width = 0;
+ GtkWidget* comboBoxSeparator = GetWidget(MOZ_GTK_COMBOBOX_SEPARATOR);
+ if (comboBoxSeparator) {
+ style = gtk_widget_get_style_context(comboBoxSeparator);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ if (!wide_separators) {
+ gtk_style_context_get_border(
+ style, gtk_style_context_get_state(style), &border);
+ separator_width = border.left;
+ }
+ }
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ARROW), NULL,
+ &arrow_req);
+ moz_gtk_sanity_preferred_size(&arrow_req);
+
+ if (direction == GTK_TEXT_DIR_RTL)
+ *left += separator_width + arrow_req.width;
+ else
+ *right += separator_width + arrow_req.width;
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_TABPANELS:
+ w = GetWidget(MOZ_GTK_TABPANELS);
+ break;
+ case MOZ_GTK_PROGRESSBAR:
+ w = GetWidget(MOZ_GTK_PROGRESSBAR);
+ break;
+ case MOZ_GTK_SPINBUTTON_ENTRY:
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ w = GetWidget(MOZ_GTK_SPINBUTTON);
+ break;
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ w = GetWidget(widget);
+ break;
+ case MOZ_GTK_FRAME:
+ w = GetWidget(MOZ_GTK_FRAME);
+ break;
+ case MOZ_GTK_TOOLTIP: {
+ // In GTK 3 there are 6 pixels of additional margin around the box.
+ // See details there:
+ // https://github.com/GNOME/gtk/blob/5ea69a136bd7e4970b3a800390e20314665aaed2/gtk/ui/gtktooltipwindow.ui#L11
+ *left = *right = *top = *bottom = 6;
+
+ // We also need to add margin/padding/borders from Tooltip content.
+ // Tooltip contains horizontal box, where icon and label is put.
+ // We ignore icon as long as we don't have support for it.
+ GtkStyleContext* boxStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX);
+ moz_gtk_add_margin_border_padding(boxStyle, left, top, right, bottom);
+
+ GtkStyleContext* labelStyle = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
+ moz_gtk_add_margin_border_padding(labelStyle, left, top, right, bottom);
+
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_HEADER_BAR_BUTTON_BOX: {
+ style = GetStyleContext(MOZ_GTK_HEADER_BAR);
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ *top = *bottom = 0;
+ bool leftButtonsPlacement = false;
+ GetGtkHeaderBarButtonLayout({}, &leftButtonsPlacement);
+ if (direction == GTK_TEXT_DIR_RTL) {
+ leftButtonsPlacement = !leftButtonsPlacement;
+ }
+ if (leftButtonsPlacement) {
+ *right = 0;
+ } else {
+ *left = 0;
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ /* These widgets have no borders, since they are not containers. */
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ /* These widgets have no borders.*/
+ case MOZ_GTK_INNER_SPIN_BUTTON:
+ case MOZ_GTK_SPINBUTTON:
+ case MOZ_GTK_WINDOW_DECORATION:
+ case MOZ_GTK_WINDOW_DECORATION_SOLID:
+ case MOZ_GTK_RESIZER:
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return MOZ_GTK_SUCCESS;
+ default:
+ g_warning("Unsupported widget type: %d", widget);
+ return MOZ_GTK_UNKNOWN_WIDGET;
+ }
+ /* TODO - we're still missing some widget implementations */
+ if (w) {
+ moz_gtk_add_style_border(gtk_widget_get_style_context(w), left, top, right,
+ bottom);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget) {
+ GtkStyleContext* style = GetStyleContext(widget, 1, direction,
+ GetStateFlagsFromGtkTabFlags(flags));
+
+ *left = *top = *right = *bottom = 0;
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+
+ // Gtk >= 3.20 does not use those styles
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ int tab_curvature;
+
+ gtk_style_context_get_style(style, "tab-curvature", &tab_curvature, NULL);
+ *left += tab_curvature;
+ *right += tab_curvature;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ int initial_gap = 0;
+ gtk_style_context_get_style(style, "initial-gap", &initial_gap, NULL);
+ if (direction == GTK_TEXT_DIR_RTL)
+ *right += initial_gap;
+ else
+ *left += initial_gap;
+ }
+ } else {
+ GtkBorder margin;
+
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+
+ if (flags & MOZ_GTK_TAB_FIRST) {
+ style = GetStyleContext(MOZ_GTK_NOTEBOOK_HEADER, direction);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &margin);
+ *left += margin.left;
+ *right += margin.right;
+ }
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height) {
+ gint arrow_size;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TABPANELS);
+ gtk_style_context_get_style(style, "scroll-arrow-hlength", &arrow_size, NULL);
+
+ *height = *width = arrow_size;
+
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width,
+ gint* height) {
+ GtkWidget* widget;
+ switch (widgetType) {
+ case MOZ_GTK_DROPDOWN:
+ widget = GetWidget(MOZ_GTK_COMBOBOX_ARROW);
+ break;
+ default:
+ widget = GetWidget(MOZ_GTK_BUTTON_ARROW);
+ break;
+ }
+ if (widget) {
+ GtkRequisition requisition;
+ gtk_widget_get_preferred_size(widget, NULL, &requisition);
+ moz_gtk_sanity_preferred_size(&requisition);
+
+ *width = requisition.width;
+ *height = requisition.height;
+ } else {
+ *width = 0;
+ *height = 0;
+ }
+}
+
+gint moz_gtk_get_expander_size(gint* size) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_EXPANDER);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_treeview_expander_size(gint* size) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TREEVIEW);
+ gtk_style_context_get_style(style, "expander-size", size, NULL);
+ return MOZ_GTK_SUCCESS;
+}
+
+void moz_gtk_get_entry_min_height(gint* min_content_height,
+ gint* border_padding_height) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_ENTRY);
+ if (!gtk_check_version(3, 20, 0)) {
+ gtk_style_context_get(style, gtk_style_context_get_state(style),
+ "min-height", min_content_height, nullptr);
+ } else {
+ *min_content_height = 0;
+ }
+
+ GtkBorder border;
+ GtkBorder padding;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ *border_padding_height =
+ (border.top + border.bottom + padding.top + padding.bottom);
+}
+
+void moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height) {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_HORIZONTAL
+ : MOZ_GTK_SCALE_VERTICAL;
+
+ gint thumb_length, thumb_height, trough_border;
+ moz_gtk_get_scalethumb_metrics(orient, &thumb_length, &thumb_height);
+
+ GtkStyleContext* style = GetStyleContext(widget);
+ gtk_style_context_get_style(style, "trough-border", &trough_border, NULL);
+
+ if (orient == GTK_ORIENTATION_HORIZONTAL) {
+ *scale_width = thumb_length + trough_border * 2;
+ *scale_height = thumb_height + trough_border * 2;
+ } else {
+ *scale_width = thumb_height + trough_border * 2;
+ *scale_height = thumb_length + trough_border * 2;
+ }
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCALE_TROUGH_VERTICAL;
+ moz_gtk_get_widget_min_size(GetStyleContext(widget), scale_width,
+ scale_height);
+ }
+}
+
+gint moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length,
+ gint* thumb_height) {
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_HORIZONTAL
+ : MOZ_GTK_SCALE_VERTICAL;
+ GtkStyleContext* style = GetStyleContext(widget);
+ gtk_style_context_get_style(style, "slider_length", thumb_length,
+ "slider_width", thumb_height, NULL);
+ } else {
+ WidgetNodeType widget = (orient == GTK_ORIENTATION_HORIZONTAL)
+ ? MOZ_GTK_SCALE_THUMB_HORIZONTAL
+ : MOZ_GTK_SCALE_THUMB_VERTICAL;
+ GtkStyleContext* style = GetStyleContext(widget);
+
+ gint min_width, min_height;
+ GtkStateFlags state = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state, "min-width", &min_width, "min-height",
+ &min_height, nullptr);
+ GtkBorder margin;
+ gtk_style_context_get_margin(style, state, &margin);
+ gint margin_width = margin.left + margin.right;
+ gint margin_height = margin.top + margin.bottom;
+
+ // Negative margin of slider element also determines its minimal size
+ // so use bigger of those two values.
+ if (min_width < -margin_width) min_width = -margin_width;
+ if (min_height < -margin_height) min_height = -margin_height;
+
+ *thumb_length = min_width;
+ *thumb_height = min_height;
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+const ToggleGTKMetrics* GetToggleMetrics(WidgetNodeType aWidgetType) {
+ ToggleGTKMetrics* metrics;
+
+ switch (aWidgetType) {
+ case MOZ_GTK_RADIOBUTTON:
+ metrics = &sRadioMetrics;
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ metrics = &sCheckboxMetrics;
+ break;
+ default:
+ MOZ_CRASH("Unsupported widget type for getting metrics");
+ return nullptr;
+ }
+
+ metrics->initialized = true;
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ GtkStyleContext* style = GetStyleContext(aWidgetType);
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gtk_style_context_get(style, state_flags, "min-height",
+ &(metrics->minSizeWithBorder.height), "min-width",
+ &(metrics->minSizeWithBorder.width), nullptr);
+ // Fallback to indicator size if min dimensions are zero
+ if (metrics->minSizeWithBorder.height == 0 ||
+ metrics->minSizeWithBorder.width == 0) {
+ gint indicator_size = 0;
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", &indicator_size, nullptr);
+ if (metrics->minSizeWithBorder.height == 0) {
+ metrics->minSizeWithBorder.height = indicator_size;
+ }
+ if (metrics->minSizeWithBorder.width == 0) {
+ metrics->minSizeWithBorder.width = indicator_size;
+ }
+ }
+
+ GtkBorder border, padding;
+ gtk_style_context_get_border(style, state_flags, &border);
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ metrics->borderAndPadding.left = border.left + padding.left;
+ metrics->borderAndPadding.right = border.right + padding.right;
+ metrics->borderAndPadding.top = border.top + padding.top;
+ metrics->borderAndPadding.bottom = border.bottom + padding.bottom;
+ metrics->minSizeWithBorder.width +=
+ metrics->borderAndPadding.left + metrics->borderAndPadding.right;
+ metrics->minSizeWithBorder.height +=
+ metrics->borderAndPadding.top + metrics->borderAndPadding.bottom;
+ } else {
+ gint indicator_size = 0, indicator_spacing = 0;
+ gtk_widget_style_get(GetWidget(MOZ_GTK_CHECKBUTTON_CONTAINER),
+ "indicator_size", &indicator_size, "indicator_spacing",
+ &indicator_spacing, nullptr);
+ metrics->minSizeWithBorder.width = metrics->minSizeWithBorder.height =
+ indicator_size;
+ }
+ return metrics;
+}
+
+/*
+ * get_shadow_width() from gtkwindow.c is not public so we need
+ * to implement it.
+ */
+void InitWindowDecorationSize(CSDWindowDecorationSize* sWindowDecorationSize,
+ bool aPopupWindow) {
+ bool solidDecorations = gtk_style_context_has_class(
+ GetStyleContext(MOZ_GTK_HEADERBAR_WINDOW, 1), "solid-csd");
+ // solid-csd does not use frame extents, quit now.
+ if (solidDecorations) {
+ sWindowDecorationSize->decorationSize = {0, 0, 0, 0};
+ return;
+ }
+
+ // Scale factor is applied later when decoration size is used for actual
+ // gtk windows.
+ GtkStyleContext* context = GetStyleContext(MOZ_GTK_WINDOW_DECORATION);
+
+ /* Always sum border + padding */
+ GtkBorder padding;
+ GtkStateFlags state = gtk_style_context_get_state(context);
+ gtk_style_context_get_border(context, state,
+ &sWindowDecorationSize->decorationSize);
+ gtk_style_context_get_padding(context, state, &padding);
+ sWindowDecorationSize->decorationSize += padding;
+
+ // Available on GTK 3.20+.
+ static auto sGtkRenderBackgroundGetClip = (void (*)(
+ GtkStyleContext*, gdouble, gdouble, gdouble, gdouble,
+ GdkRectangle*))dlsym(RTLD_DEFAULT, "gtk_render_background_get_clip");
+
+ if (!sGtkRenderBackgroundGetClip) {
+ return;
+ }
+
+ GdkRectangle clip;
+ sGtkRenderBackgroundGetClip(context, 0, 0, 0, 0, &clip);
+
+ GtkBorder extents;
+ extents.top = -clip.y;
+ extents.right = clip.width + clip.x;
+ extents.bottom = clip.height + clip.y;
+ extents.left = -clip.x;
+
+ // Get shadow extents but combine with style margin; use the bigger value.
+ // Margin is used for resize grip size - it's not present on
+ // popup windows.
+ if (!aPopupWindow) {
+ GtkBorder margin;
+ gtk_style_context_get_margin(context, state, &margin);
+
+ extents.top = MAX(extents.top, margin.top);
+ extents.right = MAX(extents.right, margin.right);
+ extents.bottom = MAX(extents.bottom, margin.bottom);
+ extents.left = MAX(extents.left, margin.left);
+ }
+
+ sWindowDecorationSize->decorationSize += extents;
+}
+
+GtkBorder GetCSDDecorationSize(bool aIsPopup) {
+ auto metrics =
+ aIsPopup ? &sPopupWindowDecorationSize : &sToplevelWindowDecorationSize;
+ if (!metrics->initialized) {
+ InitWindowDecorationSize(metrics, aIsPopup);
+ metrics->initialized = true;
+ }
+ return metrics->decorationSize;
+}
+
+/* cairo_t *cr argument has to be a system-cairo. */
+gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state, gint flags,
+ GtkTextDirection direction) {
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=694086
+ */
+ cairo_new_path(cr);
+
+ switch (widget) {
+ case MOZ_GTK_BUTTON:
+ case MOZ_GTK_TOOLBAR_BUTTON:
+ if (state->depressed) {
+ return moz_gtk_button_paint(cr, rect, state, (GtkReliefStyle)flags,
+ GetWidget(MOZ_GTK_TOGGLE_BUTTON),
+ direction);
+ }
+ return moz_gtk_button_paint(cr, rect, state, (GtkReliefStyle)flags,
+ GetWidget(MOZ_GTK_BUTTON), direction);
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE:
+ return moz_gtk_header_bar_button_paint(
+ cr, rect, state, (GtkReliefStyle)flags, widget, direction);
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ return moz_gtk_toggle_paint(cr, rect, state,
+ !!(flags & MOZ_GTK_WIDGET_CHECKED),
+ !!(flags & MOZ_GTK_WIDGET_INCONSISTENT),
+ (widget == MOZ_GTK_RADIOBUTTON), direction);
+ case MOZ_GTK_SCALE_HORIZONTAL:
+ case MOZ_GTK_SCALE_VERTICAL:
+ return moz_gtk_scale_paint(cr, rect, state, (GtkOrientation)flags,
+ direction);
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ return moz_gtk_scale_thumb_paint(cr, rect, state, (GtkOrientation)flags,
+ direction);
+ case MOZ_GTK_INNER_SPIN_BUTTON:
+ return moz_gtk_inner_spin_paint(cr, rect, state, direction);
+ case MOZ_GTK_SPINBUTTON:
+ return moz_gtk_spin_paint(cr, rect, state, direction);
+ case MOZ_GTK_SPINBUTTON_UP:
+ case MOZ_GTK_SPINBUTTON_DOWN:
+ return moz_gtk_spin_updown_paint(
+ cr, rect, (widget == MOZ_GTK_SPINBUTTON_DOWN), state, direction);
+ case MOZ_GTK_SPINBUTTON_ENTRY: {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON_ENTRY, state->image_scale,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ return moz_gtk_entry_paint(cr, rect, state, style, widget);
+ }
+ case MOZ_GTK_TREEVIEW:
+ return moz_gtk_treeview_paint(cr, rect, state, direction);
+ case MOZ_GTK_TREE_HEADER_CELL:
+ return moz_gtk_tree_header_cell_paint(cr, rect, state, flags, direction);
+ case MOZ_GTK_TREEVIEW_EXPANDER:
+ return moz_gtk_treeview_expander_paint(
+ cr, rect, state, (GtkExpanderStyle)flags, direction);
+ case MOZ_GTK_ENTRY:
+ case MOZ_GTK_DROPDOWN_ENTRY: {
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->image_scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gint ret = moz_gtk_entry_paint(cr, rect, state, style, widget);
+ return ret;
+ }
+ case MOZ_GTK_TEXT_VIEW:
+ return moz_gtk_text_view_paint(cr, rect, state, direction);
+ case MOZ_GTK_DROPDOWN:
+ return moz_gtk_combo_box_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLTIP:
+ return moz_gtk_tooltip_paint(cr, rect, state, direction);
+ case MOZ_GTK_FRAME:
+ return moz_gtk_frame_paint(cr, rect, state, direction);
+ case MOZ_GTK_RESIZER:
+ return moz_gtk_resizer_paint(cr, rect, state, direction);
+ case MOZ_GTK_PROGRESSBAR:
+ return moz_gtk_progressbar_paint(cr, rect, state, direction);
+ case MOZ_GTK_PROGRESS_CHUNK:
+ case MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE:
+ case MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE:
+ return moz_gtk_progress_chunk_paint(cr, rect, state, direction, widget);
+ case MOZ_GTK_TAB_TOP:
+ case MOZ_GTK_TAB_BOTTOM:
+ return moz_gtk_tab_paint(cr, rect, state, (GtkTabFlags)flags, direction,
+ widget);
+ case MOZ_GTK_TABPANELS:
+ return moz_gtk_tabpanels_paint(cr, rect, state, direction);
+ case MOZ_GTK_TAB_SCROLLARROW:
+ return moz_gtk_tab_scroll_arrow_paint(cr, rect, state,
+ (GtkArrowType)flags, direction);
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(cr, rect, state, (GtkArrowType)flags,
+ direction);
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ return moz_gtk_vpaned_paint(cr, rect, state);
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ return moz_gtk_hpaned_paint(cr, rect, state);
+ case MOZ_GTK_WINDOW_DECORATION:
+ return moz_gtk_window_decoration_paint(cr, rect, state, direction);
+ case MOZ_GTK_HEADER_BAR:
+ case MOZ_GTK_HEADER_BAR_MAXIMIZED:
+ return moz_gtk_header_bar_paint(widget, cr, rect, state);
+ default:
+ g_warning("Unknown widget type: %d", widget);
+ }
+
+ return MOZ_GTK_UNKNOWN_WIDGET;
+}
+
+gint moz_gtk_shutdown() {
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+
+ return MOZ_GTK_SUCCESS;
+}
diff --git a/widget/gtk/gtkdrawing.h b/widget/gtk/gtkdrawing.h
new file mode 100644
index 0000000000..e751dc38c9
--- /dev/null
+++ b/widget/gtk/gtkdrawing.h
@@ -0,0 +1,524 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/**
+ * gtkdrawing.h: GTK widget rendering utilities
+ *
+ * gtkdrawing provides an API for rendering GTK widgets in the
+ * current theme to a pixmap or window, without requiring an actual
+ * widget instantiation, similar to the Macintosh Appearance Manager
+ * or Windows XP's DrawThemeBackground() API.
+ */
+
+#ifndef _GTK_DRAWING_H_
+#define _GTK_DRAWING_H_
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#include <algorithm>
+#include "mozilla/Span.h"
+
+/*** type definitions ***/
+typedef struct {
+ guint8 active;
+ guint8 focused;
+ guint8 selected;
+ guint8 inHover;
+ guint8 disabled;
+ guint8 isDefault;
+ guint8 canDefault;
+ /* The depressed state is for buttons which remain active for a longer period:
+ * activated toggle buttons or buttons showing a popup menu. */
+ guint8 depressed;
+ guint8 backdrop;
+ gint32 curpos; /* curpos and maxpos are used for scrollbars */
+ gint32 maxpos;
+ gint32 image_scale; /* image scale */
+} GtkWidgetState;
+
+/**
+ * A size in the same GTK pixel units as GtkBorder and GdkRectangle.
+ */
+struct MozGtkSize {
+ gint width;
+ gint height;
+
+ MozGtkSize& operator+=(const GtkBorder& aBorder) {
+ width += aBorder.left + aBorder.right;
+ height += aBorder.top + aBorder.bottom;
+ return *this;
+ }
+ MozGtkSize operator+(const GtkBorder& aBorder) const {
+ MozGtkSize result = *this;
+ return result += aBorder;
+ }
+ bool operator<(const MozGtkSize& aOther) const {
+ return (width < aOther.width && height <= aOther.height) ||
+ (width <= aOther.width && height < aOther.height);
+ }
+ void Include(MozGtkSize aOther) {
+ width = std::max(width, aOther.width);
+ height = std::max(height, aOther.height);
+ }
+ void Rotate() {
+ gint tmp = width;
+ width = height;
+ height = tmp;
+ }
+};
+
+typedef struct {
+ bool initialized;
+ MozGtkSize minSizeWithBorder;
+ GtkBorder borderAndPadding;
+} ToggleGTKMetrics;
+
+typedef struct {
+ MozGtkSize minSizeWithBorderMargin;
+ GtkBorder buttonMargin;
+ gint iconXPosition;
+ gint iconYPosition;
+ bool visible;
+ bool firstButton;
+ bool lastButton;
+} ToolbarButtonGTKMetrics;
+
+#define TOOLBAR_BUTTONS 3
+typedef struct {
+ bool initialized;
+ ToolbarButtonGTKMetrics button[TOOLBAR_BUTTONS];
+} ToolbarGTKMetrics;
+
+typedef struct {
+ bool initialized;
+ GtkBorder decorationSize;
+} CSDWindowDecorationSize;
+
+/** flags for tab state **/
+typedef enum {
+ /* first eight bits are used to pass a margin */
+ MOZ_GTK_TAB_MARGIN_MASK = 0xFF,
+ /* the first tab in the group */
+ MOZ_GTK_TAB_FIRST = 1 << 9,
+ /* the selected tab */
+ MOZ_GTK_TAB_SELECTED = 1 << 10
+} GtkTabFlags;
+
+/*** result/error codes ***/
+#define MOZ_GTK_SUCCESS 0
+#define MOZ_GTK_UNKNOWN_WIDGET -1
+#define MOZ_GTK_UNSAFE_THEME -2
+
+/*** checkbox/radio flags ***/
+#define MOZ_GTK_WIDGET_CHECKED 1
+#define MOZ_GTK_WIDGET_INCONSISTENT (1 << 1)
+
+/*** widget type constants ***/
+enum WidgetNodeType : int {
+ /* Paints a GtkButton. flags is a GtkReliefStyle. */
+ MOZ_GTK_BUTTON,
+ /* Paints a button with image and no text */
+ MOZ_GTK_TOOLBAR_BUTTON,
+ /* Paints a toggle button */
+ MOZ_GTK_TOGGLE_BUTTON,
+ /* Paints a button arrow */
+ MOZ_GTK_BUTTON_ARROW,
+
+ /* Paints the container part of a GtkCheckButton. */
+ MOZ_GTK_CHECKBUTTON_CONTAINER,
+ /* Paints a GtkCheckButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_CHECKBUTTON,
+
+ /* Paints the container part of a GtkRadioButton. */
+ MOZ_GTK_RADIOBUTTON_CONTAINER,
+ /* Paints a GtkRadioButton. flags is a boolean, 1=checked, 0=not checked. */
+ MOZ_GTK_RADIOBUTTON,
+ /* Vertical GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_VERTICAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL,
+ MOZ_GTK_SCROLLBAR_THUMB_VERTICAL,
+
+ /* Paints a GtkScale. */
+ MOZ_GTK_SCALE_HORIZONTAL,
+ MOZ_GTK_SCALE_VERTICAL,
+ /* Paints a GtkScale trough. */
+ MOZ_GTK_SCALE_CONTENTS_HORIZONTAL,
+ MOZ_GTK_SCALE_CONTENTS_VERTICAL,
+ MOZ_GTK_SCALE_TROUGH_HORIZONTAL,
+ MOZ_GTK_SCALE_TROUGH_VERTICAL,
+ /* Paints a GtkScale thumb. */
+ MOZ_GTK_SCALE_THUMB_HORIZONTAL,
+ MOZ_GTK_SCALE_THUMB_VERTICAL,
+ /* Paints a GtkSpinButton */
+ MOZ_GTK_INNER_SPIN_BUTTON,
+ MOZ_GTK_SPINBUTTON,
+ MOZ_GTK_SPINBUTTON_UP,
+ MOZ_GTK_SPINBUTTON_DOWN,
+ MOZ_GTK_SPINBUTTON_ENTRY,
+ /* Paints a GtkEntry. */
+ MOZ_GTK_ENTRY,
+ /* Paints a GtkExpander. */
+ MOZ_GTK_EXPANDER,
+ /* Paints a GtkTextView or gets the style context corresponding to the
+ root node of a GtkTextView. */
+ MOZ_GTK_TEXT_VIEW,
+ /* The "text" window or node of a GtkTextView */
+ MOZ_GTK_TEXT_VIEW_TEXT,
+ /* The "selection" node of a GtkTextView.text */
+ MOZ_GTK_TEXT_VIEW_TEXT_SELECTION,
+ /* Paints a GtkOptionMenu. */
+ MOZ_GTK_DROPDOWN,
+ /* Paints an entry in an editable option menu */
+ MOZ_GTK_DROPDOWN_ENTRY,
+
+ /* Paints a GtkToolTip */
+ MOZ_GTK_TOOLTIP,
+ /* Paints a GtkBox from GtkToolTip */
+ MOZ_GTK_TOOLTIP_BOX,
+ /* Paints a GtkLabel of GtkToolTip */
+ MOZ_GTK_TOOLTIP_BOX_LABEL,
+ /* Paints a GtkFrame (e.g. a status bar panel). */
+ MOZ_GTK_FRAME,
+ /* Paints the border of a GtkFrame */
+ MOZ_GTK_FRAME_BORDER,
+ /* Paints a resize grip for a GtkTextView */
+ MOZ_GTK_RESIZER,
+ /* Paints a GtkProgressBar. */
+ MOZ_GTK_PROGRESSBAR,
+ /* Paints a trough (track) of a GtkProgressBar */
+ MOZ_GTK_PROGRESS_TROUGH,
+ /* Paints a progress chunk of a GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK,
+ /* Paints a progress chunk of an indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE,
+ /* Paints a progress chunk of a vertical indeterminated GtkProgressBar. */
+ MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE,
+ /* Used as root style of whole GtkNotebook widget */
+ MOZ_GTK_NOTEBOOK,
+ /* Used as root style of active GtkNotebook area which contains tabs and
+ arrows. */
+ MOZ_GTK_NOTEBOOK_HEADER,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_TOP,
+ /* Paints a tab of a GtkNotebook. flags is a GtkTabFlags, defined above. */
+ MOZ_GTK_TAB_BOTTOM,
+ /* Paints the background and border of a GtkNotebook. */
+ MOZ_GTK_TABPANELS,
+ /* Paints a GtkArrow for a GtkNotebook. flags is a GtkArrowType. */
+ MOZ_GTK_TAB_SCROLLARROW,
+ /* Paints the expander and border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW,
+ /* Paints the border of a GtkTreeView */
+ MOZ_GTK_TREEVIEW_VIEW,
+ /* Paints treeheader cells */
+ MOZ_GTK_TREE_HEADER_CELL,
+ /* Paints an expander for a GtkTreeView */
+ MOZ_GTK_TREEVIEW_EXPANDER,
+ /* Paints the background of menus, context menus. */
+ MOZ_GTK_MENUPOPUP,
+ /* Menubar for -moz-headerbar colors */
+ MOZ_GTK_MENUBAR,
+ /* Paints an arrow in a toolbar button. flags is a GtkArrowType. */
+ MOZ_GTK_TOOLBARBUTTON_ARROW,
+ /* Paints items of popup menus. */
+ MOZ_GTK_MENUITEM,
+ /* Menubar menuitem for foreground colors. */
+ MOZ_GTK_MENUBARITEM,
+ /* GtkVPaned base class */
+ MOZ_GTK_SPLITTER_HORIZONTAL,
+ /* GtkHPaned base class */
+ MOZ_GTK_SPLITTER_VERTICAL,
+ /* Paints a GtkVPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_HORIZONTAL,
+ /* Paints a GtkHPaned separator */
+ MOZ_GTK_SPLITTER_SEPARATOR_VERTICAL,
+ /* Paints the background of a window, dialog or page. */
+ MOZ_GTK_WINDOW,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR. */
+ MOZ_GTK_HEADERBAR_WINDOW,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR_MAXIMIZED. */
+ MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR. */
+ MOZ_GTK_HEADERBAR_FIXED,
+ /* Used only as a container for MOZ_GTK_HEADER_BAR_MAXIMIZED. */
+ MOZ_GTK_HEADERBAR_FIXED_MAXIMIZED,
+ /* Window container for all widgets */
+ MOZ_GTK_WINDOW_CONTAINER,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX,
+ /* Paints a GtkComboBox button widget. */
+ MOZ_GTK_COMBOBOX_BUTTON,
+ /* Paints a GtkComboBox arrow widget. */
+ MOZ_GTK_COMBOBOX_ARROW,
+ /* Paints a GtkComboBox separator widget. */
+ MOZ_GTK_COMBOBOX_SEPARATOR,
+ /* Used for widget tree construction. */
+ MOZ_GTK_COMBOBOX_ENTRY,
+ /* Paints a GtkComboBox entry widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA,
+ /* Paints a GtkComboBox entry button widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_BUTTON,
+ /* Paints a GtkComboBox entry arrow widget. */
+ MOZ_GTK_COMBOBOX_ENTRY_ARROW,
+ /* Used for scrolled window shell. */
+ MOZ_GTK_SCROLLED_WINDOW,
+ /* Paints a GtkHeaderBar */
+ MOZ_GTK_HEADER_BAR,
+ /* Paints a GtkHeaderBar in maximized state */
+ MOZ_GTK_HEADER_BAR_MAXIMIZED,
+ /* Container for GtkHeaderBar buttons */
+ MOZ_GTK_HEADER_BAR_BUTTON_BOX,
+ /* Paints GtkHeaderBar title buttons.
+ * Keep the order here as MOZ_GTK_HEADER_BAR_BUTTON_* are processed
+ * as an array from MOZ_GTK_HEADER_BAR_BUTTON_CLOSE to the last one.
+ */
+ MOZ_GTK_HEADER_BAR_BUTTON_CLOSE,
+ MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE,
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE,
+
+ /* MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE is a state of
+ * MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE button and it's used as
+ * an icon placeholder only.
+ */
+ MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE,
+
+ /* Client-side window decoration node. Available on GTK 3.20+. */
+ MOZ_GTK_WINDOW_DECORATION,
+ MOZ_GTK_WINDOW_DECORATION_SOLID,
+
+ MOZ_GTK_WIDGET_NODE_COUNT
+};
+
+/* ButtonLayout represents a GTK CSD button and whether its on the left or
+ * right side of the tab bar */
+struct ButtonLayout {
+ WidgetNodeType mType;
+};
+
+/*** General library functions ***/
+/**
+ * Initializes the drawing library. You must call this function
+ * prior to using any other functionality.
+ * returns: MOZ_GTK_SUCCESS if there were no errors
+ * MOZ_GTK_UNSAFE_THEME if the current theme engine is known
+ * to crash with gtkdrawing.
+ */
+gint moz_gtk_init();
+
+/**
+ * Updates the drawing library when the theme changes.
+ */
+void moz_gtk_refresh();
+
+/**
+ * Perform cleanup of the drawing library. You should call this function
+ * when your program exits, or you no longer need the library.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_shutdown();
+
+/*** Widget drawing ***/
+/**
+ * Paint a widget in the current theme.
+ * widget: a constant giving the widget to paint
+ * drawable: the drawable to paint to;
+ * it's colormap must be moz_gtk_widget_get_colormap().
+ * rect: the bounding rectangle for the widget
+ * state: the state of the widget. ignored for some widgets.
+ * flags: widget-dependant flags; see the WidgetNodeType definition.
+ * direction: the text direction, to draw the widget correctly LTR and RTL.
+ */
+gint moz_gtk_widget_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state, gint flags,
+ GtkTextDirection direction);
+
+/*** Widget metrics ***/
+/**
+ * Get the border size of a widget
+ * left/right: [OUT] the widget's left/right border
+ * top/bottom: [OUT] the widget's top/bottom border
+ * direction: the text direction for the widget. Callers depend on this
+ * being used only for MOZ_GTK_DROPDOWN widgets, and cache
+ * results for other widget types across direction values.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_widget_border(WidgetNodeType widget, gint* left, gint* top,
+ gint* right, gint* bottom,
+ GtkTextDirection direction);
+
+/**
+ * Get the border size of a notebook tab
+ * left/right: [OUT] the tab's left/right border
+ * top/bottom: [OUT] the tab's top/bottom border
+ * direction: the text direction for the widget
+ * flags: tab-dependant flags; see the GtkTabFlags definition.
+ * widget: tab widget
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_tab_border(gint* left, gint* top, gint* right, gint* bottom,
+ GtkTextDirection direction, GtkTabFlags flags,
+ WidgetNodeType widget);
+
+/**
+ * Get the desired size of a GtkCheckButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_checkbox_get_metrics(gint* indicator_size,
+ gint* indicator_spacing);
+
+/**
+ * Get metrics of the toggle (radio or checkbox)
+ * isRadio: [IN] true when requesting metrics for the radio button
+ * returns: pointer to ToggleGTKMetrics struct
+ */
+const ToggleGTKMetrics* GetToggleMetrics(WidgetNodeType aWidgetType);
+
+/**
+ * Get the desired size of a GtkRadioButton
+ * indicator_size: [OUT] the indicator size
+ * indicator_spacing: [OUT] the spacing between the indicator and its
+ * container
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_radio_get_metrics(gint* indicator_size, gint* indicator_spacing);
+
+/**
+ * Some GTK themes draw their indication for the default button outside
+ * the button (e.g. the glow in New Wave). This gets the extra space necessary.
+ *
+ * border_top: [OUT] extra space to add above
+ * border_left: [OUT] extra space to add to the left
+ * border_bottom: [OUT] extra space to add underneath
+ * border_right: [OUT] extra space to add to the right
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_button_get_default_overflow(gint* border_top, gint* border_left,
+ gint* border_bottom,
+ gint* border_right);
+
+/**
+ * Gets the minimum size of a GtkScale.
+ * orient: [IN] the scale orientation
+ * scale_width: [OUT] the width of the scale
+ * scale_height: [OUT] the height of the scale
+ */
+void moz_gtk_get_scale_metrics(GtkOrientation orient, gint* scale_width,
+ gint* scale_height);
+
+/**
+ * Get the desired size of a GtkScale thumb
+ * orient: [IN] the scale orientation
+ * thumb_length: [OUT] the length of the thumb
+ * thumb_height: [OUT] the height of the thumb
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_scalethumb_metrics(GtkOrientation orient, gint* thumb_length,
+ gint* thumb_height);
+
+/**
+ * Get the desired size of a scroll arrow widget
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_tab_scroll_arrow_size(gint* width, gint* height);
+
+/**
+ * Get the desired size of an arrow in a button
+ *
+ * widgetType: [IN] the widget for which to get the arrow size
+ * width: [OUT] the desired width
+ * height: [OUT] the desired height
+ */
+void moz_gtk_get_arrow_size(WidgetNodeType widgetType, gint* width,
+ gint* height);
+
+/**
+ * Get the minimum height of a entry widget
+ * min_content_height: [OUT] the minimum height of the content box.
+ * border_padding_height: [OUT] the size of borders and paddings.
+ */
+void moz_gtk_get_entry_min_height(gint* min_content_height,
+ gint* border_padding_height);
+
+/**
+ * Get the desired size of a toolbar separator
+ * size: [OUT] the desired width
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_toolbar_separator_width(gint* size);
+
+/**
+ * Get the size of a regular GTK expander that shows/hides content
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_expander_size(gint* size);
+
+/**
+ * Get the size of a treeview's expander (we call them twisties)
+ * size: [OUT] the size of the GTK expander, size = width = height.
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_treeview_expander_size(gint* size);
+
+/**
+ * Get the desired size of a splitter
+ * orientation: [IN] GTK_ORIENTATION_HORIZONTAL or GTK_ORIENTATION_VERTICAL
+ * size: [OUT] width or height of the splitter handle
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_splitter_get_metrics(gint orientation, gint* size);
+
+/**
+ * Get the YTHICKNESS of a tab (notebook extension).
+ */
+gint moz_gtk_get_tab_thickness(WidgetNodeType aNodeType);
+
+/**
+ * Get ToolbarButtonGTKMetrics for recent theme.
+ */
+const ToolbarButtonGTKMetrics* GetToolbarButtonMetrics(
+ WidgetNodeType aAppearance);
+
+/**
+ * Get toolbar button layout.
+ * aButtonLayout: [OUT] An array which will be filled by ButtonLayout
+ * references to visible titlebar buttons. Must contain at
+ * least TOOLBAR_BUTTONS entries if non-empty.
+ * aReversedButtonsPlacement: [OUT] True if the buttons are placed in opposite
+ * titlebar corner.
+ *
+ * returns: Number of returned entries at aButtonLayout.
+ */
+size_t GetGtkHeaderBarButtonLayout(mozilla::Span<ButtonLayout>,
+ bool* aReversedButtonsPlacement);
+
+/**
+ * Get size of CSD window extents.
+ *
+ * aIsPopup: [IN] Get decoration size for popup or toplevel window.
+ *
+ * returns: Calculated (or estimated) decoration size of given aGtkWindow.
+ */
+GtkBorder GetCSDDecorationSize(bool aIsPopup);
+
+#endif
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
new file mode 100644
index 0000000000..0d3916853c
--- /dev/null
+++ b/widget/gtk/moz.build
@@ -0,0 +1,179 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+with Files("*CompositorWidget*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*WindowSurface*"):
+ BUG_COMPONENT = ("Core", "Graphics")
+
+with Files("*IMContextWrapper*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+with Files("*nsGtkKeyUtils*"):
+ BUG_COMPONENT = ("Core", "DOM: UI Events & Focus Handling")
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ DIRS += ["mozgtk"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ DIRS += ["wayland", "mozwayland", "../../third_party/wayland-proxy"]
+
+if CONFIG["MOZ_ENABLE_VAAPI"]:
+ DIRS += ["vaapitest"]
+
+if CONFIG["MOZ_ENABLE_V4L2"]:
+ DIRS += ["v4l2test"]
+
+EXPORTS += [
+ "MozContainer.h",
+ "nsGTKToolkit.h",
+ "nsGtkUtils.h",
+ "nsImageToPixbuf.h",
+]
+
+EXPORTS.mozilla += [
+ "GfxInfo.h",
+ "GfxInfoUtils.h",
+ "GRefPtr.h",
+ "GUniquePtr.h",
+ "WidgetUtilsGtk.h",
+]
+
+EXPORTS.mozilla.widget += [
+ "CompositorWidgetChild.h",
+ "CompositorWidgetParent.h",
+ "DMABufLibWrapper.h",
+ "DMABufSurface.h",
+ "gbm.h",
+ "GtkCompositorWidget.h",
+ "InProcessGtkCompositorWidget.h",
+ "va_drmcommon.h",
+ "WindowSurface.h",
+ "WindowSurfaceProvider.h",
+]
+
+UNIFIED_SOURCES += [
+ "AsyncGtkClipboardRequest.cpp",
+ "CompositorWidgetChild.cpp",
+ "CompositorWidgetParent.cpp",
+ "DMABufLibWrapper.cpp",
+ "DMABufSurface.cpp",
+ "GfxInfo.cpp",
+ "gtk3drawing.cpp",
+ "GtkCompositorWidget.cpp",
+ "IMContextWrapper.cpp",
+ "InProcessGtkCompositorWidget.cpp",
+ "MozContainer.cpp",
+ "MPRISServiceHandler.cpp",
+ "NativeKeyBindings.cpp",
+ "NativeMenuGtk.cpp",
+ "NativeMenuSupport.cpp",
+ "nsApplicationChooser.cpp",
+ "nsAppShell.cpp",
+ "nsBidiKeyboard.cpp",
+ "nsClipboard.cpp",
+ "nsColorPicker.cpp",
+ "nsDragService.cpp",
+ "nsFilePicker.cpp",
+ "nsGtkKeyUtils.cpp",
+ "nsImageToPixbuf.cpp",
+ "nsLookAndFeel.cpp",
+ "nsSound.cpp",
+ "nsToolkit.cpp",
+ "nsUserIdleServiceGTK.cpp",
+ "nsWidgetFactory.cpp",
+ "ScreenHelperGTK.cpp",
+ "TaskbarProgress.cpp",
+ "WakeLockListener.cpp",
+ "WidgetStyleCache.cpp",
+ "WidgetTraceEvent.cpp",
+ "WidgetUtilsGtk.cpp",
+ "WindowSurfaceProvider.cpp",
+]
+
+SOURCES += [
+ "MediaKeysEventSourceFactory.cpp",
+ "nsNativeThemeGTK.cpp", # conflicts with X11 headers
+ "nsWindow.cpp", # conflicts with X11 headers
+ "WaylandVsyncSource.cpp", # conflicts with X11 headers
+]
+
+if CONFIG["MOZ_WAYLAND"]:
+ UNIFIED_SOURCES += [
+ "MozContainerWayland.cpp",
+ "nsClipboardWayland.cpp",
+ "nsWaylandDisplay.cpp",
+ "WaylandBuffer.cpp",
+ "WindowSurfaceWaylandMultiBuffer.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "MozContainerWayland.h",
+ "nsWaylandDisplay.h",
+ "WaylandBuffer.h",
+ ]
+
+if CONFIG["MOZ_X11"]:
+ UNIFIED_SOURCES += [
+ "nsClipboardX11.cpp",
+ "nsShmImage.cpp",
+ "WindowSurfaceX11.cpp",
+ "WindowSurfaceX11Image.cpp",
+ "WindowSurfaceX11SHM.cpp",
+ ]
+
+if CONFIG["NS_PRINTING"]:
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecG.cpp",
+ "nsPrintDialogGTK.cpp",
+ "nsPrintSettingsGTK.cpp",
+ "nsPrintSettingsServiceGTK.cpp",
+ ]
+
+XPCOM_MANIFESTS += [
+ "components.conf",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+LOCAL_INCLUDES += [
+ "/layout/base",
+ "/layout/forms",
+ "/layout/generic",
+ "/layout/xul",
+ "/other-licenses/atk-1.0",
+ "/third_party/cups/include",
+ "/widget",
+ "/widget/headless",
+ "/widget/x11",
+]
+
+DEFINES["CAIRO_GFX"] = True
+
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+
+CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ CFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+
+if CONFIG["MOZ_ENABLE_DBUS"]:
+ EXPORTS.mozilla.widget += [
+ "AsyncDBus.h",
+ ]
+ UNIFIED_SOURCES += [
+ "AsyncDBus.cpp",
+ ]
+ CXXFLAGS += CONFIG["MOZ_DBUS_CFLAGS"]
+
+CXXFLAGS += ["-Werror=switch"]
diff --git a/widget/gtk/mozgtk/moz.build b/widget/gtk/mozgtk/moz.build
new file mode 100644
index 0000000000..d5e78d0032
--- /dev/null
+++ b/widget/gtk/mozgtk/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SharedLibrary("mozgtk")
+
+SOURCES += [
+ "mozgtk.c",
+]
+
+CFLAGS += CONFIG["MOZ_X11_CFLAGS"]
+CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+
+# If LDFLAGS contains -Wl,--as-needed or if it's the default for the toolchain,
+# we need to add -Wl,--no-as-needed before the gtk libraries, otherwise the
+# linker will drop those dependencies because no symbols are used from them.
+# But those dependencies need to be kept for things to work properly.
+# Ideally, we'd only add -Wl,--no-as-needed if necessary, but it's just simpler
+# to add it unconditionally. This library is also simple enough that forcing
+# -Wl,--as-needed after the gtk libraries is not going to make a significant
+# difference.
+if CONFIG["GCC_USE_GNU_LD"]:
+ no_as_needed = ["-Wl,--no-as-needed"]
+ as_needed = ["-Wl,--as-needed"]
+else:
+ no_as_needed = []
+ as_needed = []
+
+OS_LIBS += [f for f in CONFIG["MOZ_GTK3_LIBS"] if f.startswith("-L")]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ "gtk-3",
+ "gdk-3",
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c
new file mode 100644
index 0000000000..d95746fc0b
--- /dev/null
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -0,0 +1,30 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Types.h"
+
+#include <gdk/gdk.h>
+
+// Dummy call to gtk3 library to prevent the linker from removing
+// the gtk3 dependency with --as-needed.
+// see toolkit/library/moz.build for details.
+MOZ_EXPORT void mozgtk_linker_holder() { gdk_display_get_default(); }
+
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+// Bug 1271100
+// We need to trick system Cairo into not using the XShm extension due to
+// a race condition in it that results in frequent BadAccess errors. Cairo
+// relies upon XShmQueryExtension to initially detect if XShm is available.
+// So we define our own stub that always indicates XShm not being present.
+// mozgtk loads before libXext/libcairo and so this stub will take priority.
+// Our tree usage goes through xcb and remains unaffected by this.
+//
+// This is also used to force libxul to depend on the mozgtk library. If we
+// ever can remove this workaround for system Cairo, we'll need something
+// to replace it for that purpose.
+MOZ_EXPORT Bool XShmQueryExtension(Display* aDisplay) { return False; }
+#endif
diff --git a/widget/gtk/mozwayland/moz.build b/widget/gtk/mozwayland/moz.build
new file mode 100644
index 0000000000..0df3f0f685
--- /dev/null
+++ b/widget/gtk/mozwayland/moz.build
@@ -0,0 +1,16 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+SOURCES += [
+ "mozwayland.c",
+]
+EXPORTS.mozilla.widget += [
+ "mozwayland.h",
+]
+
+SharedLibrary("mozwayland")
+
+CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/widget/gtk/mozwayland/mozwayland.c b/widget/gtk/mozwayland/mozwayland.c
new file mode 100644
index 0000000000..eb17d3227e
--- /dev/null
+++ b/widget/gtk/mozwayland/mozwayland.c
@@ -0,0 +1,230 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdlib.h>
+#include "mozilla/Types.h"
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+#include <gdk/gdkwayland.h>
+
+union wl_argument;
+
+/* Those structures are just placeholders and will be replaced by
+ * real symbols from libwayland during run-time linking. We need to make
+ * them explicitly visible.
+ */
+#pragma GCC visibility push(default)
+const struct wl_interface wl_buffer_interface;
+const struct wl_interface wl_callback_interface;
+const struct wl_interface wl_data_device_interface;
+const struct wl_interface wl_data_device_manager_interface;
+const struct wl_interface wl_keyboard_interface;
+const struct wl_interface wl_pointer_interface;
+const struct wl_interface wl_region_interface;
+const struct wl_interface wl_registry_interface;
+const struct wl_interface wl_shm_interface;
+const struct wl_interface wl_shm_pool_interface;
+const struct wl_interface wl_seat_interface;
+const struct wl_interface wl_surface_interface;
+const struct wl_interface wl_subsurface_interface;
+const struct wl_interface wl_compositor_interface;
+const struct wl_interface wl_subcompositor_interface;
+const struct wl_interface wl_output_interface;
+#pragma GCC visibility pop
+
+MOZ_EXPORT void wl_event_queue_destroy(struct wl_event_queue* queue) {}
+
+MOZ_EXPORT void wl_proxy_marshal(struct wl_proxy* p, uint32_t opcode, ...) {}
+
+MOZ_EXPORT void wl_proxy_marshal_array(struct wl_proxy* p, uint32_t opcode,
+ union wl_argument* args) {}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_create(
+ struct wl_proxy* factory, const struct wl_interface* interface) {
+ return NULL;
+}
+
+MOZ_EXPORT void* wl_proxy_create_wrapper(void* proxy) { return NULL; }
+
+MOZ_EXPORT void wl_proxy_wrapper_destroy(void* proxy_wrapper) {}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, ...) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, uint32_t version, ...) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_array_constructor(
+ struct wl_proxy* proxy, uint32_t opcode, union wl_argument* args,
+ const struct wl_interface* interface) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_array_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode, union wl_argument* args,
+ const struct wl_interface* interface, uint32_t version) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_proxy_destroy(struct wl_proxy* proxy) {}
+
+MOZ_EXPORT int wl_proxy_add_listener(struct wl_proxy* proxy,
+ void (**implementation)(void),
+ void* data) {
+ return -1;
+}
+
+MOZ_EXPORT const void* wl_proxy_get_listener(struct wl_proxy* proxy) {
+ return NULL;
+}
+
+typedef int (*wl_dispatcher_func_t)(const void*, void*, uint32_t,
+ const struct wl_message*,
+ union wl_argument*);
+
+MOZ_EXPORT int wl_proxy_add_dispatcher(struct wl_proxy* proxy,
+ wl_dispatcher_func_t dispatcher_func,
+ const void* dispatcher_data,
+ void* data) {
+ return -1;
+}
+
+MOZ_EXPORT void wl_proxy_set_user_data(struct wl_proxy* proxy,
+ void* user_data) {}
+
+MOZ_EXPORT void* wl_proxy_get_user_data(struct wl_proxy* proxy) { return NULL; }
+
+MOZ_EXPORT uint32_t wl_proxy_get_version(struct wl_proxy* proxy) { return -1; }
+
+MOZ_EXPORT uint32_t wl_proxy_get_id(struct wl_proxy* proxy) { return -1; }
+
+MOZ_EXPORT const char* wl_proxy_get_class(struct wl_proxy* proxy) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_proxy_set_queue(struct wl_proxy* proxy,
+ struct wl_event_queue* queue) {}
+
+MOZ_EXPORT struct wl_display* wl_display_connect(const char* name) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_display* wl_display_connect_to_fd(int fd) { return NULL; }
+
+MOZ_EXPORT void wl_display_disconnect(struct wl_display* display) {}
+
+MOZ_EXPORT int wl_display_get_fd(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_dispatch(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_dispatch_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_dispatch_queue_pending(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_dispatch_pending(struct wl_display* display) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_get_error(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT uint32_t wl_display_get_protocol_error(
+ struct wl_display* display, const struct wl_interface** interface,
+ uint32_t* id) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_flush(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT int wl_display_roundtrip_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_roundtrip(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT struct wl_event_queue* wl_display_create_queue(
+ struct wl_display* display) {
+ return NULL;
+}
+
+MOZ_EXPORT int wl_display_prepare_read_queue(struct wl_display* display,
+ struct wl_event_queue* queue) {
+ return -1;
+}
+
+MOZ_EXPORT int wl_display_prepare_read(struct wl_display* display) {
+ return -1;
+}
+
+MOZ_EXPORT void wl_display_cancel_read(struct wl_display* display) {}
+
+MOZ_EXPORT int wl_display_read_events(struct wl_display* display) { return -1; }
+
+MOZ_EXPORT void wl_log_set_handler_client(wl_log_func_t handler) {}
+
+MOZ_EXPORT struct wl_egl_window* wl_egl_window_create(
+ struct wl_surface* surface, int width, int height) {
+ return NULL;
+}
+
+MOZ_EXPORT void wl_egl_window_destroy(struct wl_egl_window* egl_window) {}
+
+MOZ_EXPORT void wl_egl_window_resize(struct wl_egl_window* egl_window,
+ int width, int height, int dx, int dy) {}
+
+MOZ_EXPORT void wl_egl_window_get_attached_size(
+ struct wl_egl_window* egl_window, int* width, int* height) {}
+
+MOZ_EXPORT void wl_list_init(struct wl_list* list) {}
+
+MOZ_EXPORT void wl_list_insert(struct wl_list* list, struct wl_list* elm) {}
+
+MOZ_EXPORT void wl_list_remove(struct wl_list* elm) {}
+
+MOZ_EXPORT int wl_list_length(const struct wl_list* list) { return -1; }
+
+MOZ_EXPORT int wl_list_empty(const struct wl_list* list) { return -1; }
+
+MOZ_EXPORT void wl_list_insert_list(struct wl_list* list,
+ struct wl_list* other) {}
+
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_flags(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, uint32_t version, uint32_t flags,
+ ...) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_compositor* gdk_wayland_display_get_wl_compositor(
+ GdkDisplay* display) {
+ return NULL;
+}
+MOZ_EXPORT struct wl_surface* gdk_wayland_window_get_wl_surface(
+ GdkWindow* window) {
+ return NULL;
+}
+
+MOZ_EXPORT struct wl_pointer* gdk_wayland_device_get_wl_pointer(
+ GdkDevice* device) {
+ return NULL;
+}
+MOZ_EXPORT struct wl_display* gdk_wayland_display_get_wl_display(
+ GdkDisplay* display) {
+ return NULL;
+}
diff --git a/widget/gtk/mozwayland/mozwayland.h b/widget/gtk/mozwayland/mozwayland.h
new file mode 100644
index 0000000000..22f80d6315
--- /dev/null
+++ b/widget/gtk/mozwayland/mozwayland.h
@@ -0,0 +1,134 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+/* Wayland compatibility header, it makes Firefox build with
+ wayland-1.2 and Gtk+ 3.10.
+*/
+
+#ifndef __MozWayland_h_
+#define __MozWayland_h_
+
+#include "mozilla/Types.h"
+#include <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+MOZ_EXPORT struct wl_display* wl_display_connect(const char* name);
+MOZ_EXPORT int wl_display_roundtrip_queue(struct wl_display* display,
+ struct wl_event_queue* queue);
+MOZ_EXPORT uint32_t wl_proxy_get_version(struct wl_proxy* proxy);
+MOZ_EXPORT void wl_proxy_marshal(struct wl_proxy* p, uint32_t opcode, ...);
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, ...);
+MOZ_EXPORT struct wl_proxy* wl_proxy_marshal_constructor_versioned(
+ struct wl_proxy* proxy, uint32_t opcode,
+ const struct wl_interface* interface, uint32_t version, ...);
+MOZ_EXPORT void wl_proxy_destroy(struct wl_proxy* proxy);
+MOZ_EXPORT void* wl_proxy_create_wrapper(void* proxy);
+MOZ_EXPORT void wl_proxy_wrapper_destroy(void* proxy_wrapper);
+
+/* We need implement some missing functions from wayland-client-protocol.h
+ */
+#ifndef WL_DATA_DEVICE_MANAGER_DND_ACTION_ENUM
+enum wl_data_device_manager_dnd_action {
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE = 0,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY = 1,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE = 2,
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_ASK = 4
+};
+#endif
+
+#ifndef WL_DATA_OFFER_SET_ACTIONS
+# define WL_DATA_OFFER_SET_ACTIONS 4
+
+struct moz_wl_data_offer_listener {
+ void (*offer)(void* data, struct wl_data_offer* wl_data_offer,
+ const char* mime_type);
+ void (*source_actions)(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t source_actions);
+ void (*action)(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t dnd_action);
+};
+
+static inline void wl_data_offer_set_actions(
+ struct wl_data_offer* wl_data_offer, uint32_t dnd_actions,
+ uint32_t preferred_action) {
+ wl_proxy_marshal((struct wl_proxy*)wl_data_offer, WL_DATA_OFFER_SET_ACTIONS,
+ dnd_actions, preferred_action);
+}
+#else
+typedef struct wl_data_offer_listener moz_wl_data_offer_listener;
+#endif
+
+#ifndef WL_SUBCOMPOSITOR_GET_SUBSURFACE
+# define WL_SUBCOMPOSITOR_GET_SUBSURFACE 1
+struct wl_subcompositor;
+
+// Emulate what mozilla header wrapper does - make the
+// wl_subcompositor_interface always visible.
+# pragma GCC visibility push(default)
+extern const struct wl_interface wl_subsurface_interface;
+extern const struct wl_interface wl_subcompositor_interface;
+# pragma GCC visibility pop
+
+# define WL_SUBSURFACE_DESTROY 0
+# define WL_SUBSURFACE_SET_POSITION 1
+# define WL_SUBSURFACE_PLACE_ABOVE 2
+# define WL_SUBSURFACE_PLACE_BELOW 3
+# define WL_SUBSURFACE_SET_SYNC 4
+# define WL_SUBSURFACE_SET_DESYNC 5
+
+static inline struct wl_subsurface* wl_subcompositor_get_subsurface(
+ struct wl_subcompositor* wl_subcompositor, struct wl_surface* surface,
+ struct wl_surface* parent) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)wl_subcompositor, WL_SUBCOMPOSITOR_GET_SUBSURFACE,
+ &wl_subsurface_interface, NULL, surface, parent);
+
+ return (struct wl_subsurface*)id;
+}
+
+static inline void wl_subsurface_set_position(
+ struct wl_subsurface* wl_subsurface, int32_t x, int32_t y) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_SET_POSITION,
+ x, y);
+}
+
+static inline void wl_subsurface_set_desync(
+ struct wl_subsurface* wl_subsurface) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_SET_DESYNC);
+}
+
+static inline void wl_subsurface_destroy(struct wl_subsurface* wl_subsurface) {
+ wl_proxy_marshal((struct wl_proxy*)wl_subsurface, WL_SUBSURFACE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wl_subsurface);
+}
+#endif
+
+#ifndef WL_SURFACE_DAMAGE_BUFFER
+# define WL_SURFACE_DAMAGE_BUFFER 9
+
+static inline void wl_surface_damage_buffer(struct wl_surface* wl_surface,
+ int32_t x, int32_t y, int32_t width,
+ int32_t height) {
+ wl_proxy_marshal((struct wl_proxy*)wl_surface, WL_SURFACE_DAMAGE_BUFFER, x, y,
+ width, height);
+}
+#endif
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif /* __MozWayland_h_ */
diff --git a/widget/gtk/nsAppShell.cpp b/widget/gtk/nsAppShell.cpp
new file mode 100644
index 0000000000..d26b43737d
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,494 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "nsAppShell.h"
+#include "nsBaseAppShell.h"
+#include "nsWindow.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ProfilerThreadSleep.h"
+#include "mozilla/Unused.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+# include <gio/gio.h>
+# include "nsIObserverService.h"
+# include "WidgetUtilsGtk.h"
+#endif
+#include "WakeLockListener.h"
+#include "gfxPlatform.h"
+#include "nsAppRunner.h"
+#include "mozilla/XREAppData.h"
+#include "ScreenHelperGTK.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::widget;
+using mozilla::widget::HeadlessScreenHelper;
+using mozilla::widget::ScreenHelperGTK;
+using mozilla::widget::ScreenManager;
+
+#define NOTIFY_TOKEN 0xFA
+#define QUIT_TOKEN 0xFB
+
+LazyLogModule gWidgetLog("Widget");
+LazyLogModule gWidgetDragLog("WidgetDrag");
+LazyLogModule gWidgetWaylandLog("WidgetWayland");
+LazyLogModule gWidgetPopupLog("WidgetPopup");
+LazyLogModule gWidgetVsync("WidgetVsync");
+LazyLogModule gDmabufLog("Dmabuf");
+LazyLogModule gClipboardLog("WidgetClipboard");
+
+static GPollFunc sPollFunc;
+
+nsAppShell* sAppShell = nullptr;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint PollWrapper(GPollFD* aUfds, guint aNfsd, gint aTimeout) {
+ if (aTimeout == 0) {
+ // When the timeout is 0, there is no wait, so no point in notifying
+ // the BackgroundHangMonitor and the profiler.
+ return (*sPollFunc)(aUfds, aNfsd, aTimeout);
+ }
+
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ gint result;
+ {
+ gint timeout = aTimeout;
+ gint64 begin = 0;
+ if (aTimeout != -1) {
+ begin = g_get_monotonic_time();
+ }
+
+ AUTO_PROFILER_LABEL("PollWrapper", IDLE);
+ AUTO_PROFILER_THREAD_SLEEP;
+ do {
+ result = (*sPollFunc)(aUfds, aNfsd, timeout);
+
+ // The result will be -1 with the EINTR error if the poll was interrupted
+ // by a signal, typically the signal sent by the profiler to sample the
+ // process. We are only done waiting if we are not in that case.
+ if (result != -1 || errno != EINTR) {
+ break;
+ }
+
+ if (aTimeout != -1) {
+ // Adjust the timeout to account for the time already spent waiting.
+ gint elapsedSinceBegin = (g_get_monotonic_time() - begin) / 1000;
+ if (elapsedSinceBegin < aTimeout) {
+ timeout = aTimeout - elapsedSinceBegin;
+ } else {
+ // poll returns 0 to indicate the call timed out before any fd
+ // became ready.
+ result = 0;
+ break;
+ }
+ }
+ } while (true);
+ }
+ mozilla::BackgroundHangMonitor().NotifyActivity();
+ return result;
+}
+
+// Emit resume-events on GdkFrameClock if flush-events has not been
+// balanced by resume-events at dispose.
+// For https://bugzilla.gnome.org/show_bug.cgi?id=742636
+static decltype(GObjectClass::constructed) sRealGdkFrameClockConstructed;
+static decltype(GObjectClass::dispose) sRealGdkFrameClockDispose;
+static GQuark sPendingResumeQuark;
+
+static void OnFlushEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, GUINT_TO_POINTER(1));
+}
+
+static void OnResumeEvents(GObject* clock, gpointer) {
+ g_object_set_qdata(clock, sPendingResumeQuark, nullptr);
+}
+
+static void WrapGdkFrameClockConstructed(GObject* object) {
+ sRealGdkFrameClockConstructed(object);
+
+ g_signal_connect(object, "flush-events", G_CALLBACK(OnFlushEvents), nullptr);
+ g_signal_connect(object, "resume-events", G_CALLBACK(OnResumeEvents),
+ nullptr);
+}
+
+static void WrapGdkFrameClockDispose(GObject* object) {
+ if (g_object_get_qdata(object, sPendingResumeQuark)) {
+ g_signal_emit_by_name(object, "resume-events");
+ }
+
+ sRealGdkFrameClockDispose(object);
+}
+
+/*static*/
+gboolean nsAppShell::EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition,
+ gpointer data) {
+ nsAppShell* self = static_cast<nsAppShell*>(data);
+
+ unsigned char c;
+ Unused << read(self->mPipeFDs[0], &c, 1);
+ switch (c) {
+ case NOTIFY_TOKEN:
+ self->NativeEventCallback();
+ break;
+ case QUIT_TOKEN:
+ self->Exit();
+ break;
+ default:
+ NS_ASSERTION(false, "wrong token");
+ break;
+ }
+ return TRUE;
+}
+
+nsAppShell::~nsAppShell() {
+ sAppShell = nullptr;
+
+#ifdef MOZ_ENABLE_DBUS
+ StopDBusListening();
+#endif
+ mozilla::hal::Shutdown();
+
+ if (mTag) g_source_remove(mTag);
+ if (mPipeFDs[0]) close(mPipeFDs[0]);
+ if (mPipeFDs[1]) close(mPipeFDs[1]);
+}
+
+mozilla::StaticRefPtr<WakeLockListener> sWakeLockListener;
+static void AddScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> powerManager =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (powerManager) {
+ sWakeLockListener = new WakeLockListener();
+ powerManager->AddWakeLockListener(sWakeLockListener);
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+}
+
+static void RemoveScreenWakeLockListener() {
+ nsCOMPtr<nsIPowerManagerService> powerManager =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ if (powerManager) {
+ powerManager->RemoveWakeLockListener(sWakeLockListener);
+ sWakeLockListener = nullptr;
+ }
+}
+
+#ifdef MOZ_ENABLE_DBUS
+void nsAppShell::DBusSessionSleepCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData) {
+ if (g_strcmp0(aSignalName, "PrepareForSleep")) {
+ return;
+ }
+ nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService();
+ if (!observerService) {
+ return;
+ }
+ if (!g_variant_is_of_type(aParameters, G_VARIANT_TYPE_TUPLE) ||
+ g_variant_n_children(aParameters) != 1) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected location updated signal params type: %s\n",
+ g_variant_get_type_string(aParameters))
+ .get());
+ return;
+ }
+
+ RefPtr<GVariant> variant =
+ dont_AddRef(g_variant_get_child_value(aParameters, 0));
+ if (!g_variant_is_of_type(variant, G_VARIANT_TYPE_BOOLEAN)) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected location updated signal params type: %s\n",
+ g_variant_get_type_string(aParameters))
+ .get());
+ return;
+ }
+
+ gboolean suspend = g_variant_get_boolean(variant);
+ if (suspend) {
+ // Post sleep_notification
+ observerService->NotifyObservers(nullptr, NS_WIDGET_SLEEP_OBSERVER_TOPIC,
+ nullptr);
+ } else {
+ // Post wake_notification
+ observerService->NotifyObservers(nullptr, NS_WIDGET_WAKE_OBSERVER_TOPIC,
+ nullptr);
+ }
+}
+
+void nsAppShell::DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData) {
+ if (g_strcmp0(aSignalName, "PropertiesChanged")) {
+ return;
+ }
+ nsBaseAppShell::OnSystemTimezoneChange();
+}
+
+void nsAppShell::DBusConnectClientResponse(GObject* aObject,
+ GAsyncResult* aResult,
+ gpointer aUserData) {
+ GUniquePtr<GError> error;
+ RefPtr<GDBusProxy> proxyClient =
+ dont_AddRef(g_dbus_proxy_new_finish(aResult, getter_Transfers(error)));
+ if (!proxyClient) {
+ if (!IsCancelledGError(error.get())) {
+ NS_WARNING(
+ nsPrintfCString("Failed to connect to client: %s\n", error->message)
+ .get());
+ }
+ return;
+ }
+
+ RefPtr self = static_cast<nsAppShell*>(aUserData);
+ if (!strcmp(g_dbus_proxy_get_name(proxyClient), "org.freedesktop.login1")) {
+ self->mLogin1Proxy = std::move(proxyClient);
+ g_signal_connect(self->mLogin1Proxy, "g-signal",
+ G_CALLBACK(DBusSessionSleepCallback), self);
+ } else {
+ self->mTimedate1Proxy = std::move(proxyClient);
+ g_signal_connect(self->mTimedate1Proxy, "g-signal",
+ G_CALLBACK(DBusTimedatePropertiesChangedCallback), self);
+ }
+}
+
+// Based on
+// https://github.com/lcp/NetworkManager/blob/240f47c892b4e935a3e92fc09eb15163d1fa28d8/src/nm-sleep-monitor-systemd.c
+// Use login1 to signal sleep and wake notifications.
+void nsAppShell::StartDBusListening() {
+ MOZ_DIAGNOSTIC_ASSERT(!mLogin1Proxy, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mTimedate1Proxy, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mLogin1ProxyCancellable, "Already configured?");
+ MOZ_DIAGNOSTIC_ASSERT(!mTimedate1ProxyCancellable, "Already configured?");
+
+ mLogin1ProxyCancellable = dont_AddRef(g_cancellable_new());
+ mTimedate1ProxyCancellable = dont_AddRef(g_cancellable_new());
+
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.login1", "/org/freedesktop/login1",
+ "org.freedesktop.login1.Manager", mLogin1ProxyCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
+
+ g_dbus_proxy_new_for_bus(
+ G_BUS_TYPE_SYSTEM, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.timedate1", "/org/freedesktop/timedate1",
+ "org.freedesktop.DBus.Properties", mTimedate1ProxyCancellable,
+ reinterpret_cast<GAsyncReadyCallback>(DBusConnectClientResponse), this);
+}
+
+void nsAppShell::StopDBusListening() {
+ if (mLogin1Proxy) {
+ g_signal_handlers_disconnect_matched(mLogin1Proxy, G_SIGNAL_MATCH_DATA, 0,
+ 0, nullptr, nullptr, this);
+ }
+ if (mLogin1ProxyCancellable) {
+ g_cancellable_cancel(mLogin1ProxyCancellable);
+ mLogin1ProxyCancellable = nullptr;
+ }
+ mLogin1Proxy = nullptr;
+
+ if (mTimedate1Proxy) {
+ g_signal_handlers_disconnect_matched(mTimedate1Proxy, G_SIGNAL_MATCH_DATA,
+ 0, 0, nullptr, nullptr, this);
+ }
+ if (mTimedate1ProxyCancellable) {
+ g_cancellable_cancel(mTimedate1ProxyCancellable);
+ mTimedate1ProxyCancellable = nullptr;
+ }
+ mTimedate1Proxy = nullptr;
+}
+#endif
+
+void nsAppShell::TermSignalHandler(int signo) {
+ if (signo != SIGTERM) {
+ NS_WARNING("Wrong signal!");
+ return;
+ }
+ sAppShell->ScheduleQuitEvent();
+}
+
+void nsAppShell::InstallTermSignalHandler() {
+ if (!XRE_IsParentProcess() || PR_GetEnv("MOZ_DISABLE_SIG_HANDLER") ||
+ !sAppShell) {
+ return;
+ }
+
+ struct sigaction act = {}, oldact;
+ act.sa_handler = TermSignalHandler;
+ sigfillset(&act.sa_mask);
+
+ if (NS_WARN_IF(sigaction(SIGTERM, nullptr, &oldact) != 0)) {
+ return;
+ }
+ if (oldact.sa_handler != SIG_DFL) {
+ NS_WARNING("SIGTERM signal handler is already set?");
+ }
+
+ sigaction(SIGTERM, &act, nullptr);
+}
+
+nsresult nsAppShell::Init() {
+ mozilla::hal::Init();
+
+#ifdef MOZ_ENABLE_DBUS
+ if (XRE_IsParentProcess()) {
+ StartDBusListening();
+ }
+#endif
+
+ if (!sPollFunc) {
+ sPollFunc = g_main_context_get_poll_func(nullptr);
+ g_main_context_set_poll_func(nullptr, &PollWrapper);
+ }
+
+ if (XRE_IsParentProcess()) {
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ if (gfxPlatform::IsHeadless()) {
+ screenManager.SetHelper(mozilla::MakeUnique<HeadlessScreenHelper>());
+ } else {
+ screenManager.SetHelper(mozilla::MakeUnique<ScreenHelperGTK>());
+ }
+
+ if (gtk_check_version(3, 16, 3) == nullptr) {
+ // Before 3.16.3, GDK cannot override classname by --class command line
+ // option when program uses gdk_set_program_class().
+ //
+ // See https://bugzilla.gnome.org/show_bug.cgi?id=747634
+ //
+ // Only bother doing this for the parent process, since it's the one
+ // creating top-level windows.
+ if (gAppData) {
+ gdk_set_program_class(gAppData->remotingName);
+ }
+ }
+ }
+
+ if (!sPendingResumeQuark &&
+ gtk_check_version(3, 14, 7) != nullptr) { // GTK 3.0 to GTK 3.14.7.
+ // GTK 3.8 - 3.14 registered this type when creating the frame clock
+ // for the root window of the display when the display was opened.
+ GType gdkFrameClockIdleType = g_type_from_name("GdkFrameClockIdle");
+ if (gdkFrameClockIdleType) { // not in versions prior to 3.8
+ sPendingResumeQuark = g_quark_from_string("moz-resume-is-pending");
+ auto gdk_frame_clock_idle_class =
+ G_OBJECT_CLASS(g_type_class_peek_static(gdkFrameClockIdleType));
+ auto constructed = &gdk_frame_clock_idle_class->constructed;
+ sRealGdkFrameClockConstructed = *constructed;
+ *constructed = WrapGdkFrameClockConstructed;
+ auto dispose = &gdk_frame_clock_idle_class->dispose;
+ sRealGdkFrameClockDispose = *dispose;
+ *dispose = WrapGdkFrameClockDispose;
+ }
+ }
+
+ // Workaround for bug 1209659 which is fixed by Gtk3.20
+ if (gtk_check_version(3, 20, 0) != nullptr) {
+ unsetenv("GTK_CSD");
+ }
+
+ // Whitelist of only common, stable formats - see bugs 1197059 and 1203078
+ GSList* pixbufFormats = gdk_pixbuf_get_formats();
+ for (GSList* iter = pixbufFormats; iter; iter = iter->next) {
+ GdkPixbufFormat* format = static_cast<GdkPixbufFormat*>(iter->data);
+ gchar* name = gdk_pixbuf_format_get_name(format);
+ if (strcmp(name, "jpeg") && strcmp(name, "png") && strcmp(name, "gif") &&
+ strcmp(name, "bmp") && strcmp(name, "ico") && strcmp(name, "xpm") &&
+ strcmp(name, "svg") && strcmp(name, "webp") && strcmp(name, "avif")) {
+ gdk_pixbuf_format_set_disabled(format, TRUE);
+ }
+ g_free(name);
+ }
+ g_slist_free(pixbufFormats);
+
+ int err = pipe(mPipeFDs);
+ if (err) return NS_ERROR_OUT_OF_MEMORY;
+
+ GIOChannel* ioc;
+ GSource* source;
+
+ // make the pipe nonblocking
+
+ int flags = fcntl(mPipeFDs[0], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[0], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+ flags = fcntl(mPipeFDs[1], F_GETFL, 0);
+ if (flags == -1) goto failed;
+ err = fcntl(mPipeFDs[1], F_SETFL, flags | O_NONBLOCK);
+ if (err == -1) goto failed;
+
+ ioc = g_io_channel_unix_new(mPipeFDs[0]);
+ source = g_io_create_watch(ioc, G_IO_IN);
+ g_io_channel_unref(ioc);
+ g_source_set_callback(source, (GSourceFunc)EventProcessorCallback, this,
+ nullptr);
+ g_source_set_can_recurse(source, TRUE);
+ mTag = g_source_attach(source, nullptr);
+ g_source_unref(source);
+
+ sAppShell = this;
+
+ return nsBaseAppShell::Init();
+failed:
+ close(mPipeFDs[0]);
+ close(mPipeFDs[1]);
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP nsAppShell::Run() {
+ if (XRE_IsParentProcess()) {
+ AddScreenWakeLockListener();
+ }
+
+ nsresult rv = nsBaseAppShell::Run();
+
+ if (XRE_IsParentProcess()) {
+ RemoveScreenWakeLockListener();
+ }
+ return rv;
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ unsigned char buf[] = {NOTIFY_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+void nsAppShell::ScheduleQuitEvent() {
+ unsigned char buf[] = {QUIT_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ if (mSuspendNativeCount) {
+ return false;
+ }
+ bool didProcessEvent = g_main_context_iteration(nullptr, mayWait);
+ return didProcessEvent;
+}
diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h
new file mode 100644
index 0000000000..0b628033cc
--- /dev/null
+++ b/widget/gtk/nsAppShell.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsAppShell_h__
+#define nsAppShell_h__
+
+#ifdef MOZ_ENABLE_DBUS
+# include <gio/gio.h>
+# include "mozilla/RefPtr.h"
+# include "mozilla/GRefPtr.h"
+#endif
+#include <glib.h>
+#include "nsBaseAppShell.h"
+
+class nsAppShell : public nsBaseAppShell {
+ public:
+ nsAppShell() = default;
+
+ // nsBaseAppShell overrides:
+ nsresult Init();
+ NS_IMETHOD Run() override;
+
+ void ScheduleNativeEventCallback() override;
+ bool ProcessNextNativeEvent(bool mayWait) override;
+
+#ifdef MOZ_ENABLE_DBUS
+ void StartDBusListening();
+ void StopDBusListening();
+
+ static void DBusSessionSleepCallback(GDBusProxy* aProxy, gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData);
+ static void DBusTimedatePropertiesChangedCallback(GDBusProxy* aProxy,
+ gchar* aSenderName,
+ gchar* aSignalName,
+ GVariant* aParameters,
+ gpointer aUserData);
+ static void DBusConnectClientResponse(GObject* aObject, GAsyncResult* aResult,
+ gpointer aUserData);
+#endif
+
+ static void InstallTermSignalHandler();
+
+ private:
+ virtual ~nsAppShell();
+
+ static gboolean EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition, gpointer data);
+ static void TermSignalHandler(int signo);
+
+ void ScheduleQuitEvent();
+
+ int mPipeFDs[2] = {0, 0};
+ unsigned mTag = 0;
+
+#ifdef MOZ_ENABLE_DBUS
+ RefPtr<GDBusProxy> mLogin1Proxy;
+ RefPtr<GCancellable> mLogin1ProxyCancellable;
+ RefPtr<GDBusProxy> mTimedate1Proxy;
+ RefPtr<GCancellable> mTimedate1ProxyCancellable;
+#endif
+};
+
+#endif /* nsAppShell_h__ */
diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp
new file mode 100644
index 0000000000..6752c52caf
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.cpp
@@ -0,0 +1,132 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Types.h"
+
+#include <gtk/gtk.h>
+
+#include "nsApplicationChooser.h"
+#include "WidgetUtils.h"
+#include "nsIMIMEInfo.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "nsCExternalHandlerService.h"
+#include "nsComponentManagerUtils.h"
+#include "nsGtkUtils.h"
+#include "nsPIDOMWindow.h"
+
+using namespace mozilla;
+
+NS_IMPL_ISUPPORTS(nsApplicationChooser, nsIApplicationChooser)
+
+nsApplicationChooser::nsApplicationChooser() = default;
+
+nsApplicationChooser::~nsApplicationChooser() = default;
+
+NS_IMETHODIMP
+nsApplicationChooser::Init(mozIDOMWindowProxy* aParent,
+ const nsACString& aTitle) {
+ NS_ENSURE_TRUE(aParent, NS_ERROR_FAILURE);
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = widget::WidgetUtils::DOMWindowToWidget(parent);
+ mWindowTitle.Assign(aTitle);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsApplicationChooser::Open(const nsACString& aContentType,
+ nsIApplicationChooserFinishedCallback* aCallback) {
+ MOZ_ASSERT(aCallback);
+ if (mCallback) {
+ NS_WARNING("Chooser is already in progress.");
+ return NS_ERROR_ALREADY_INITIALIZED;
+ }
+ mCallback = aCallback;
+ NS_ENSURE_TRUE(mParentWidget, NS_ERROR_FAILURE);
+ GtkWindow* parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkWidget* chooser = gtk_app_chooser_dialog_new_for_content_type(
+ parent_widget,
+ (GtkDialogFlags)(GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT),
+ PromiseFlatCString(aContentType).get());
+ gtk_app_chooser_dialog_set_heading(GTK_APP_CHOOSER_DIALOG(chooser),
+ mWindowTitle.BeginReading());
+ NS_ADDREF_THIS();
+ g_signal_connect(chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(chooser);
+ return NS_OK;
+}
+
+/* static */
+void nsApplicationChooser::OnResponse(GtkWidget* chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser, response_id);
+}
+
+/* static */
+void nsApplicationChooser::OnDestroy(GtkWidget* chooser, gpointer user_data) {
+ static_cast<nsApplicationChooser*>(user_data)->Done(chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+void nsApplicationChooser::Done(GtkWidget* chooser, gint response) {
+ nsCOMPtr<nsILocalHandlerApp> localHandler;
+ nsresult rv;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT: {
+ localHandler = do_CreateInstance(NS_LOCALHANDLERAPP_CONTRACTID, &rv);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Out of memory.");
+ break;
+ }
+ GAppInfo* app_info =
+ gtk_app_chooser_get_app_info(GTK_APP_CHOOSER(chooser));
+
+ nsCOMPtr<nsIFile> localExecutable;
+ gchar* fileWithFullPath =
+ g_find_program_in_path(g_app_info_get_executable(app_info));
+ if (!fileWithFullPath) {
+ g_object_unref(app_info);
+ NS_WARNING("Cannot find program in path.");
+ break;
+ }
+ rv = NS_NewNativeLocalFile(nsDependentCString(fileWithFullPath), false,
+ getter_AddRefs(localExecutable));
+ g_free(fileWithFullPath);
+ if (NS_FAILED(rv)) {
+ NS_WARNING("Cannot create local filename.");
+ localHandler = nullptr;
+ } else {
+ localHandler->SetExecutable(localExecutable);
+ localHandler->SetName(
+ NS_ConvertUTF8toUTF16(g_app_info_get_display_name(app_info)));
+ }
+ g_object_unref(app_info);
+ }
+
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(chooser, FuncToGpointer(OnDestroy),
+ this);
+ gtk_widget_destroy(chooser);
+
+ if (mCallback) {
+ mCallback->Done(localHandler);
+ mCallback = nullptr;
+ }
+ NS_RELEASE_THIS();
+}
diff --git a/widget/gtk/nsApplicationChooser.h b/widget/gtk/nsApplicationChooser.h
new file mode 100644
index 0000000000..22f9a808c0
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsApplicationChooser_h__
+#define nsApplicationChooser_h__
+
+#include <gtk/gtk.h>
+#include "nsCOMPtr.h"
+#include "nsIApplicationChooser.h"
+#include "nsString.h"
+
+class nsIWidget;
+
+class nsApplicationChooser final : public nsIApplicationChooser {
+ public:
+ nsApplicationChooser();
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIAPPLICATIONCHOOSER
+ void Done(GtkWidget* chooser, gint response);
+
+ private:
+ ~nsApplicationChooser();
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCString mWindowTitle;
+ nsCOMPtr<nsIApplicationChooserFinishedCallback> mCallback;
+ static void OnResponse(GtkWidget* chooser, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* chooser, gpointer user_data);
+};
+#endif
diff --git a/widget/gtk/nsBidiKeyboard.cpp b/widget/gtk/nsBidiKeyboard.cpp
new file mode 100644
index 0000000000..a57235195b
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.cpp
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "prlink.h"
+
+#include "nsBidiKeyboard.h"
+#include "nsIWidget.h"
+#include <gtk/gtk.h>
+
+NS_IMPL_ISUPPORTS(nsBidiKeyboard, nsIBidiKeyboard)
+
+nsBidiKeyboard::nsBidiKeyboard() { Reset(); }
+
+NS_IMETHODIMP
+nsBidiKeyboard::Reset() {
+ // NB: The default keymap can be null (e.g. in xpcshell). In that case,
+ // simply assume that we don't have bidi keyboards.
+ mHaveBidiKeyboards = false;
+
+ GdkDisplay* display = gdk_display_get_default();
+ if (!display) return NS_OK;
+
+ GdkKeymap* keymap = gdk_keymap_get_for_display(display);
+ mHaveBidiKeyboards = keymap && gdk_keymap_have_bidi_layouts(keymap);
+ return NS_OK;
+}
+
+nsBidiKeyboard::~nsBidiKeyboard() = default;
+
+NS_IMETHODIMP
+nsBidiKeyboard::IsLangRTL(bool* aIsRTL) {
+ if (!mHaveBidiKeyboards) return NS_ERROR_FAILURE;
+
+ *aIsRTL = (gdk_keymap_get_direction(gdk_keymap_get_default()) ==
+ PANGO_DIRECTION_RTL);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsBidiKeyboard::GetHaveBidiKeyboards(bool* aResult) {
+ // not implemented yet
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+
+// static
+already_AddRefed<nsIBidiKeyboard> nsIWidget::CreateBidiKeyboardInner() {
+ return do_AddRef(new nsBidiKeyboard());
+}
diff --git a/widget/gtk/nsBidiKeyboard.h b/widget/gtk/nsBidiKeyboard.h
new file mode 100644
index 0000000000..1bc1c7e0a2
--- /dev/null
+++ b/widget/gtk/nsBidiKeyboard.h
@@ -0,0 +1,25 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsBidiKeyboard
+#define __nsBidiKeyboard
+#include "nsIBidiKeyboard.h"
+
+class nsBidiKeyboard : public nsIBidiKeyboard {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIBIDIKEYBOARD
+
+ nsBidiKeyboard();
+
+ protected:
+ virtual ~nsBidiKeyboard();
+
+ bool mHaveBidiKeyboards;
+};
+
+#endif // __nsBidiKeyboard
diff --git a/widget/gtk/nsClipboard.cpp b/widget/gtk/nsClipboard.cpp
new file mode 100644
index 0000000000..571b43f1cc
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,1425 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#if defined(MOZ_X11)
+# include "nsClipboardX11.h"
+#endif
+#if defined(MOZ_WAYLAND)
+# include "nsClipboardWayland.h"
+# include "nsWaylandDisplay.h"
+#endif
+#include "nsGtkUtils.h"
+#include "nsIURI.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include "nsContentUtils.h"
+#include "HeadlessClipboard.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "nsIFileURL.h"
+#include "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/TimeStamp.h"
+#include "GRefPtr.h"
+#include "WidgetUtilsGtk.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+#if defined(MOZ_X11)
+# include <gtk/gtkx.h>
+#endif
+
+#include "mozilla/Encoding.h"
+
+using namespace mozilla;
+
+// Idle timeout for receiving selection and property notify events (microsec)
+// Right now it's set to 1 sec.
+const int kClipboardTimeout = 1000000;
+
+// Defines how many event loop iterations will be done without sleep.
+// We ususally get data in first 2-3 iterations unless some large object
+// (an image for instance) is transferred through clipboard.
+const int kClipboardFastIterationNum = 3;
+
+// We add this prefix to HTML markup, so that GetHTMLCharset can correctly
+// detect the HTML as UTF-8 encoded.
+static const char kHTMLMarkupPrefix[] =
+ R"(<meta http-equiv="content-type" content="text/html; charset=utf-8">)";
+
+static const char kURIListMime[] = "text/uri-list";
+
+ClipboardTargets nsRetrievalContext::sClipboardTargets;
+ClipboardTargets nsRetrievalContext::sPrimaryTargets;
+
+// Callback when someone asks us for the data
+static void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+static void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
+
+// Callback when owner of clipboard data is changed
+static void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent,
+ gpointer aUserData);
+
+static bool GetHTMLCharset(Span<const char> aData, nsCString& str);
+
+static void SetTransferableData(nsITransferable* aTransferable,
+ const nsACString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength) {
+ LOGCLIP("SetTransferableData MIME %s\n", PromiseFlatCString(aFlavor).get());
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(PromiseFlatCString(aFlavor).get(), wrapper);
+}
+
+ClipboardTargets ClipboardTargets::Clone() {
+ ClipboardTargets ret;
+ ret.mCount = mCount;
+ if (mCount) {
+ ret.mTargets.reset(
+ reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * mCount)));
+ memcpy(ret.mTargets.get(), mTargets.get(), sizeof(GdkAtom) * mCount);
+ }
+ return ret;
+}
+
+void ClipboardTargets::Set(ClipboardTargets aTargets) {
+ mCount = aTargets.mCount;
+ mTargets = std::move(aTargets.mTargets);
+}
+
+void ClipboardData::SetData(Span<const uint8_t> aData) {
+ mData = nullptr;
+ mLength = aData.Length();
+ if (mLength) {
+ mData.reset(reinterpret_cast<char*>(g_malloc(sizeof(char) * mLength)));
+ memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
+ }
+}
+
+void ClipboardData::SetText(Span<const char> aData) {
+ mData = nullptr;
+ mLength = aData.Length();
+ if (mLength) {
+ mData.reset(
+ reinterpret_cast<char*>(g_malloc(sizeof(char) * (mLength + 1))));
+ memcpy(mData.get(), aData.data(), sizeof(char) * mLength);
+ mData.get()[mLength] = '\0';
+ }
+}
+
+void ClipboardData::SetTargets(ClipboardTargets aTargets) {
+ mLength = aTargets.mCount;
+ mData.reset(reinterpret_cast<char*>(aTargets.mTargets.release()));
+}
+
+ClipboardTargets ClipboardData::ExtractTargets() {
+ GUniquePtr<GdkAtom> targets(reinterpret_cast<GdkAtom*>(mData.release()));
+ uint32_t length = std::exchange(mLength, 0);
+ return ClipboardTargets{std::move(targets), length};
+}
+
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard) {
+ if (aWhichClipboard == nsIClipboard::kGlobalClipboard)
+ return GDK_SELECTION_CLIPBOARD;
+
+ return GDK_SELECTION_PRIMARY;
+}
+
+int GetGeckoClipboardType(GtkClipboard* aGtkClipboard) {
+ if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_PRIMARY))
+ return nsClipboard::kSelectionClipboard;
+ else if (aGtkClipboard == gtk_clipboard_get(GDK_SELECTION_CLIPBOARD))
+ return nsClipboard::kGlobalClipboard;
+
+ return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+}
+
+void nsRetrievalContext::ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
+ GdkEvent* aEvent,
+ gpointer data) {
+ LOGCLIP("nsRetrievalContext::ClearCachedTargetsClipboard()");
+ sClipboardTargets.Clear();
+}
+
+void nsRetrievalContext::ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
+ GdkEvent* aEvent,
+ gpointer data) {
+ LOGCLIP("nsRetrievalContext::ClearCachedTargetsPrimary()");
+ sPrimaryTargets.Clear();
+}
+
+ClipboardTargets nsRetrievalContext::GetTargets(int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContext::GetTargets(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ ClipboardTargets& storedTargets =
+ (aWhichClipboard == nsClipboard::kSelectionClipboard) ? sPrimaryTargets
+ : sClipboardTargets;
+ if (!storedTargets) {
+ LOGCLIP(" getting targets from system");
+ storedTargets.Set(GetTargetsImpl(aWhichClipboard));
+ } else {
+ LOGCLIP(" using cached targets");
+ }
+ return storedTargets.Clone();
+}
+
+nsRetrievalContext::~nsRetrievalContext() {
+ sClipboardTargets.Clear();
+ sPrimaryTargets.Clear();
+}
+
+nsClipboard::nsClipboard()
+ : nsBaseClipboard(mozilla::dom::ClipboardCapabilities(
+#ifdef MOZ_WAYLAND
+ widget::GdkIsWaylandDisplay()
+ ? widget::WaylandDisplayGet()->IsPrimarySelectionEnabled()
+ : true,
+#else
+ true, /* supportsSelectionClipboard */
+#endif
+ false /* supportsFindClipboard */,
+ false /* supportsSelectionCache */)) {
+ g_signal_connect(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD), "owner-change",
+ G_CALLBACK(clipboard_owner_change_cb), this);
+ g_signal_connect(gtk_clipboard_get(GDK_SELECTION_PRIMARY), "owner-change",
+ G_CALLBACK(clipboard_owner_change_cb), this);
+}
+
+nsClipboard::~nsClipboard() {
+ g_signal_handlers_disconnect_by_func(
+ gtk_clipboard_get(GDK_SELECTION_CLIPBOARD),
+ FuncToGpointer(clipboard_owner_change_cb), this);
+ g_signal_handlers_disconnect_by_func(
+ gtk_clipboard_get(GDK_SELECTION_PRIMARY),
+ FuncToGpointer(clipboard_owner_change_cb), this);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsClipboard, nsBaseClipboard, nsIObserver)
+
+nsresult nsClipboard::Init(void) {
+#if defined(MOZ_X11)
+ if (widget::GdkIsX11Display()) {
+ mContext = new nsRetrievalContextX11();
+ }
+#endif
+#if defined(MOZ_WAYLAND)
+ if (widget::GdkIsWaylandDisplay()) {
+ mContext = new nsRetrievalContextWayland();
+ }
+#endif
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ if (os) {
+ os->AddObserver(this, "xpcom-shutdown", false);
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ // Save global clipboard content to CLIPBOARD_MANAGER.
+ // gtk_clipboard_store() can run an event loop, so call from a dedicated
+ // runnable.
+ return SchedulerGroup::Dispatch(
+ NS_NewRunnableFunction("gtk_clipboard_store()", []() {
+ LOGCLIP("nsClipboard storing clipboard content\n");
+ gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }));
+}
+
+NS_IMETHODIMP
+nsClipboard::SetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get())) {
+ return NS_OK;
+ }
+
+ LOGCLIP("nsClipboard::SetNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ // List of suported targets
+ GtkTargetList* list = gtk_target_list_new(nullptr, 0);
+
+ // Get the types of supported flavors
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanExport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" FlavorsTransferableCanExport failed!\n");
+ // Fall through. |gtkTargets| will be null below.
+ }
+
+ // Add all the flavors to this widget's supported type.
+ bool imagesAdded = false;
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+ LOGCLIP(" processing target %s\n", flavorStr.get());
+
+ // Special case text/plain since we can handle all of the string types.
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" adding TEXT targets\n");
+ gtk_target_list_add_text_targets(list, 0);
+ continue;
+ }
+
+ if (nsContentUtils::IsFlavorImage(flavorStr)) {
+ // Don't bother adding image targets twice
+ if (!imagesAdded) {
+ // accept any writable image type
+ LOGCLIP(" adding IMAGE targets\n");
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" adding text/uri-list target\n");
+ GdkAtom atom = gdk_atom_intern(kURIListMime, FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ LOGCLIP(" adding OTHER target %s\n", flavorStr.get());
+ GdkAtom atom = gdk_atom_intern(flavorStr.get(), FALSE);
+ gtk_target_list_add(list, atom, 0, 0);
+ }
+
+ // Get GTK clipboard (CLIPBOARD or PRIMARY)
+ GtkClipboard* gtkClipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ gint numTargets = 0;
+ GtkTargetEntry* gtkTargets =
+ gtk_target_table_new_from_list(list, &numTargets);
+ if (!gtkTargets || numTargets == 0) {
+ LOGCLIP(
+ " gtk_target_table_new_from_list() failed or empty list of "
+ "targets!\n");
+ // Clear references to the any old data and let GTK know that it is no
+ // longer available.
+ EmptyNativeClipboardData(aWhichClipboard);
+ return NS_ERROR_FAILURE;
+ }
+
+ ClearCachedTargets(aWhichClipboard);
+
+ // Set getcallback and request to store data after an application exit
+ if (gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this)) {
+ // We managed to set-up the clipboard so update internal state
+ // We have to set it now because gtk_clipboard_set_with_data() calls
+ // clipboard_clear_cb() which reset our internal state
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ mSelectionTransferable = aTransferable;
+ } else {
+ mGlobalSequenceNumber++;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ } else {
+ LOGCLIP(" gtk_clipboard_set_with_data() failed!\n");
+ EmptyNativeClipboardData(aWhichClipboard);
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+mozilla::Result<int32_t, nsresult>
+nsClipboard::GetNativeClipboardSequenceNumber(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+ return aWhichClipboard == kSelectionClipboard ? mSelectionSequenceNumber
+ : mGlobalSequenceNumber;
+}
+
+static bool IsMIMEAtFlavourList(const nsTArray<nsCString>& aFlavourList,
+ const char* aMime) {
+ for (const auto& flavorStr : aFlavourList) {
+ if (flavorStr.Equals(aMime)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+// When clipboard contains only images, X11/Gtk tries to convert them
+// to text when we request text instead of just fail to provide the data.
+// So if clipboard contains images only remove text MIME offer.
+bool nsClipboard::FilterImportedFlavors(int32_t aWhichClipboard,
+ nsTArray<nsCString>& aFlavors) {
+ LOGCLIP("nsClipboard::FilterImportedFlavors");
+
+ auto targets = mContext->GetTargets(aWhichClipboard);
+ if (!targets) {
+ LOGCLIP(" X11: no targes at clipboard (null), quit.\n");
+ return true;
+ }
+
+ for (const auto& atom : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
+ if (!atom_name) {
+ continue;
+ }
+ // Filter out system MIME types.
+ if (strcmp(atom_name.get(), "TARGETS") == 0 ||
+ strcmp(atom_name.get(), "TIMESTAMP") == 0 ||
+ strcmp(atom_name.get(), "SAVE_TARGETS") == 0 ||
+ strcmp(atom_name.get(), "MULTIPLE") == 0) {
+ continue;
+ }
+ // Filter out types which can't be converted to text.
+ if (strncmp(atom_name.get(), "image/", 6) == 0 ||
+ strncmp(atom_name.get(), "application/", 12) == 0 ||
+ strncmp(atom_name.get(), "audio/", 6) == 0 ||
+ strncmp(atom_name.get(), "video/", 6) == 0) {
+ continue;
+ }
+ // We have some other MIME type on clipboard which can be hopefully
+ // converted to text without any problem.
+ LOGCLIP(" X11: text types in clipboard, no need to filter them.\n");
+ return true;
+ }
+
+ // So make sure we offer only types we have at clipboard.
+ nsTArray<nsCString> clipboardFlavors;
+ for (const auto& atom : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(atom));
+ if (!atom_name) {
+ continue;
+ }
+ if (IsMIMEAtFlavourList(aFlavors, atom_name.get())) {
+ clipboardFlavors.AppendElement(nsCString(atom_name.get()));
+ }
+ }
+ aFlavors.SwapElements(clipboardFlavors);
+#ifdef MOZ_LOGGING
+ LOGCLIP(" X11: Flavors which match clipboard content:\n");
+ for (uint32_t i = 0; i < aFlavors.Length(); i++) {
+ LOGCLIP(" %s\n", aFlavors[i].get());
+ }
+#endif
+ return true;
+}
+
+static nsresult GetTransferableFlavors(nsITransferable* aTransferable,
+ nsTArray<nsCString>& aFlavors) {
+ if (!aTransferable) {
+ return NS_ERROR_FAILURE;
+ }
+ // Get a list of flavors this transferable can import
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(aFlavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" FlavorsTransferableCanImport falied!\n");
+ return rv;
+ }
+#ifdef MOZ_LOGGING
+ LOGCLIP(" Flavors which can be imported:");
+ for (const auto& flavor : aFlavors) {
+ LOGCLIP(" %s", flavor.get());
+ }
+#endif
+ return NS_OK;
+}
+
+static bool TransferableSetFile(nsITransferable* aTransferable,
+ const nsACString& aURIList) {
+ nsresult rv;
+ nsTArray<nsCString> uris = mozilla::widget::ParseTextURIList(aURIList);
+ if (!uris.IsEmpty()) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewURI(getter_AddRefs(fileURI), uris[0]);
+ if (nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ aTransferable->SetTransferData(kFileMime, file);
+ LOGCLIP(" successfully set file to clipboard\n");
+ return true;
+ }
+ }
+ }
+ return false;
+}
+
+static bool TransferableSetHTML(nsITransferable* aTransferable,
+ Span<const char> aData) {
+ nsLiteralCString mimeType(kHTMLMime);
+
+ // Convert text/html into our text format
+ nsAutoCString charset;
+ if (!GetHTMLCharset(aData, charset)) {
+ // Fall back to utf-8 in case html/data is missing kHTMLMarkupPrefix.
+ LOGCLIP("Failed to get html/text encoding, fall back to utf-8.\n");
+ charset.AssignLiteral("utf-8");
+ }
+
+ LOGCLIP("TransferableSetHTML: HTML detected charset %s", charset.get());
+ // app which use "text/html" to copy&paste
+ // get the decoder
+ auto encoding = Encoding::ForLabelNoReplacement(charset);
+ if (!encoding) {
+ LOGCLIP("TransferableSetHTML: get unicode decoder error (charset: %s)",
+ charset.get());
+ return false;
+ }
+
+ // According to spec html UTF-16BE/LE should be switched to UTF-8
+ // https://html.spec.whatwg.org/#determining-the-character-encoding:utf-16-encoding-2
+ if (encoding == UTF_16LE_ENCODING || encoding == UTF_16BE_ENCODING) {
+ encoding = UTF_8_ENCODING;
+ }
+
+ // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
+ // issues, but might confuse other users.
+ const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
+ if (aData.Length() >= prefixLen && nsDependentCSubstring(aData.To(prefixLen))
+ .EqualsLiteral(kHTMLMarkupPrefix)) {
+ aData = aData.From(prefixLen);
+ }
+
+ nsAutoString unicodeData;
+ auto [rv, enc] = encoding->Decode(AsBytes(aData), unicodeData);
+#if MOZ_LOGGING
+ if (enc != UTF_8_ENCODING &&
+ MOZ_LOG_TEST(gClipboardLog, mozilla::LogLevel::Debug)) {
+ nsCString decoderName;
+ enc->Name(decoderName);
+ LOGCLIP("TransferableSetHTML: expected UTF-8 decoder but got %s",
+ decoderName.get());
+ }
+#endif
+ if (NS_FAILED(rv)) {
+ LOGCLIP("TransferableSetHTML: failed to decode HTML");
+ return false;
+ }
+ SetTransferableData(aTransferable, mimeType,
+ (const char*)unicodeData.BeginReading(),
+ unicodeData.Length() * sizeof(char16_t));
+ return true;
+}
+
+NS_IMETHODIMP
+nsClipboard::GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::GetNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ // TODO: Ensure we don't re-enter here.
+ if (!mContext) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = GetTransferableFlavors(aTransferable, flavors);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Filter out MIME types on X11 to prevent unwanted conversions,
+ // see Bug 1611407
+ if (widget::GdkIsX11Display() &&
+ !FilterImportedFlavors(aWhichClipboard, flavors)) {
+ LOGCLIP(" Missing suitable clipboard data, quit.");
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ nsCString& flavorStr = flavors[i];
+
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (flavorStr.EqualsLiteral(kJPGImageMime)) {
+ flavorStr.Assign(kJPEGImageMime);
+ }
+
+ LOGCLIP(" Getting image %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" %s type is missing\n", flavorStr.get());
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream), clipboardData.AsSpan(),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ LOGCLIP(" got %s MIME data\n", flavorStr.get());
+ return NS_OK;
+ }
+
+ // Special case text/plain since we can convert any
+ // string into text/plain
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" Getting text %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData = mContext->GetClipboardText(aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" failed to get text data\n");
+ // If the type was text/plain and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // Convert utf-8 into our text format.
+ NS_ConvertUTF8toUTF16 ucs2string(clipboardData.get());
+ SetTransferableData(aTransferable, flavorStr,
+ (const char*)ucs2string.BeginReading(),
+ ucs2string.Length() * 2);
+
+ LOGCLIP(" got text data, length %zd\n", ucs2string.Length());
+ return NS_OK;
+ }
+
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" Getting %s file clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(kURIListMime, aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP(" text/uri-list type is missing\n");
+ continue;
+ }
+
+ nsDependentCSubstring fileName(clipboardData.AsSpan());
+ if (!TransferableSetFile(aTransferable, fileName)) {
+ continue;
+ }
+ return NS_OK;
+ }
+
+ LOGCLIP(" Getting %s MIME clipboard data\n", flavorStr.get());
+
+ auto clipboardData =
+ mContext->GetClipboardData(flavorStr.get(), aWhichClipboard);
+
+#ifdef MOZ_LOGGING
+ if (!clipboardData) {
+ LOGCLIP(" %s type is missing\n", flavorStr.get());
+ }
+#endif
+
+ if (clipboardData) {
+ LOGCLIP(" got %s mime type data.\n", flavorStr.get());
+
+ // Special case text/html since we can convert into UCS2
+ if (flavorStr.EqualsLiteral(kHTMLMime)) {
+ if (!TransferableSetHTML(aTransferable, clipboardData.AsSpan())) {
+ continue;
+ }
+ } else {
+ auto span = clipboardData.AsSpan();
+ SetTransferableData(aTransferable, flavorStr, span.data(),
+ span.Length());
+ }
+ return NS_OK;
+ }
+ }
+
+ LOGCLIP(" failed to get clipboard content.\n");
+ return NS_OK;
+}
+
+enum DataType {
+ DATATYPE_IMAGE,
+ DATATYPE_FILE,
+ DATATYPE_HTML,
+ DATATYPE_RAW,
+};
+
+struct DataCallbackHandler {
+ RefPtr<nsITransferable> mTransferable;
+ nsBaseClipboard::GetDataCallback mDataCallback;
+ nsCString mMimeType;
+ DataType mDataType;
+
+ explicit DataCallbackHandler(RefPtr<nsITransferable> aTransferable,
+ nsBaseClipboard::GetDataCallback&& aDataCallback,
+ const char* aMimeType,
+ DataType aDataType = DATATYPE_RAW)
+ : mTransferable(std::move(aTransferable)),
+ mDataCallback(std::move(aDataCallback)),
+ mMimeType(aMimeType),
+ mDataType(aDataType) {
+ MOZ_COUNT_CTOR(DataCallbackHandler);
+ LOGCLIP("DataCallbackHandler created [%p] MIME %s type %d", this,
+ mMimeType.get(), mDataType);
+ }
+ ~DataCallbackHandler() {
+ LOGCLIP("DataCallbackHandler deleted [%p]", this);
+ MOZ_COUNT_DTOR(DataCallbackHandler);
+ }
+};
+
+static void AsyncGetTextImpl(nsITransferable* aTransferable,
+ int32_t aWhichClipboard,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ LOGCLIP("AsyncGetText() type '%s'",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+
+ gtk_clipboard_request_text(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ [](GtkClipboard* aClipboard, const gchar* aText, gpointer aData) -> void {
+ UniquePtr<DataCallbackHandler> ref(
+ static_cast<DataCallbackHandler*>(aData));
+ LOGCLIP("AsyncGetText async handler of [%p]", aData);
+
+ size_t dataLength = aText ? strlen(aText) : 0;
+ if (dataLength <= 0) {
+ LOGCLIP(" quit, text is not available");
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 utf16string(aText, dataLength);
+ nsLiteralCString flavor(kTextMime);
+ SetTransferableData(ref->mTransferable, flavor,
+ (const char*)utf16string.BeginReading(),
+ utf16string.Length() * 2);
+ LOGCLIP(" text is set, length = %d", (int)dataLength);
+ ref->mDataCallback(NS_OK);
+ },
+ new DataCallbackHandler(aTransferable, std::move(aCallback), kTextMime));
+}
+
+static void AsyncGetDataImpl(nsITransferable* aTransferable,
+ int32_t aWhichClipboard, const char* aMimeType,
+ DataType aDataType,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ LOGCLIP("AsyncGetData() type '%s'",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+
+ const char* gtkMIMEType = nullptr;
+ switch (aDataType) {
+ case DATATYPE_FILE:
+ // Don't ask Gtk for application/x-moz-file
+ gtkMIMEType = kURIListMime;
+ break;
+ case DATATYPE_IMAGE:
+ case DATATYPE_HTML:
+ case DATATYPE_RAW:
+ gtkMIMEType = aMimeType;
+ break;
+ }
+
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ gdk_atom_intern(gtkMIMEType, FALSE),
+ [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
+ gpointer aData) -> void {
+ UniquePtr<DataCallbackHandler> ref(
+ static_cast<DataCallbackHandler*>(aData));
+ LOGCLIP("AsyncGetData async handler [%p] MIME %s type %d", aData,
+ ref->mMimeType.get(), ref->mDataType);
+
+ int dataLength = gtk_selection_data_get_length(aSelection);
+ if (dataLength <= 0) {
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+ const char* data = (const char*)gtk_selection_data_get_data(aSelection);
+ if (!data) {
+ ref->mDataCallback(NS_OK);
+ return;
+ }
+ switch (ref->mDataType) {
+ case DATATYPE_IMAGE: {
+ LOGCLIP(" set image clipboard data");
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ Span(data, dataLength), NS_ASSIGNMENT_COPY);
+ ref->mTransferable->SetTransferData(ref->mMimeType.get(),
+ byteStream);
+ break;
+ }
+ case DATATYPE_FILE: {
+ LOGCLIP(" set file clipboard data");
+ nsDependentCSubstring file(data, dataLength);
+ TransferableSetFile(ref->mTransferable, file);
+ break;
+ }
+ case DATATYPE_HTML: {
+ LOGCLIP(" html clipboard data");
+ Span dataSpan(data, dataLength);
+ TransferableSetHTML(ref->mTransferable, dataSpan);
+ break;
+ }
+ case DATATYPE_RAW: {
+ LOGCLIP(" raw clipboard data %s", ref->mMimeType.get());
+ SetTransferableData(ref->mTransferable, ref->mMimeType, data,
+ dataLength);
+ break;
+ }
+ }
+ ref->mDataCallback(NS_OK);
+ },
+ new DataCallbackHandler(aTransferable, std::move(aCallback), aMimeType,
+ aDataType));
+}
+
+static void AsyncGetDataFlavor(nsITransferable* aTransferable,
+ int32_t aWhichClipboard, nsCString& aFlavorStr,
+ nsBaseClipboard::GetDataCallback&& aCallback) {
+ if (aFlavorStr.EqualsLiteral(kJPEGImageMime) ||
+ aFlavorStr.EqualsLiteral(kJPGImageMime) ||
+ aFlavorStr.EqualsLiteral(kPNGImageMime) ||
+ aFlavorStr.EqualsLiteral(kGIFImageMime)) {
+ // Emulate support for image/jpg
+ if (aFlavorStr.EqualsLiteral(kJPGImageMime)) {
+ aFlavorStr.Assign(kJPEGImageMime);
+ }
+ LOGCLIP(" Getting image %s MIME clipboard data", aFlavorStr.get());
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_IMAGE, std::move(aCallback));
+ return;
+ }
+ // Special case text/plain since we can convert any
+ // string into text/plain
+ if (aFlavorStr.EqualsLiteral(kTextMime)) {
+ LOGCLIP(" Getting unicode clipboard data");
+ AsyncGetTextImpl(aTransferable, aWhichClipboard, std::move(aCallback));
+ return;
+ }
+ if (aFlavorStr.EqualsLiteral(kFileMime)) {
+ LOGCLIP(" Getting file clipboard data\n");
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_FILE, std::move(aCallback));
+ return;
+ }
+ if (aFlavorStr.EqualsLiteral(kHTMLMime)) {
+ LOGCLIP(" Getting HTML clipboard data");
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_HTML, std::move(aCallback));
+ return;
+ }
+ LOGCLIP(" Getting raw %s MIME clipboard data\n", aFlavorStr.get());
+ AsyncGetDataImpl(aTransferable, aWhichClipboard, aFlavorStr.get(),
+ DATATYPE_RAW, std::move(aCallback));
+}
+
+void nsClipboard::AsyncGetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard,
+ GetDataCallback&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(aTransferable);
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::AsyncGetNativeClipboardData (%s)",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ nsTArray<nsCString> importedFlavors;
+ nsresult rv = GetTransferableFlavors(aTransferable, importedFlavors);
+ if (NS_FAILED(rv)) {
+ aCallback(rv);
+ return;
+ }
+
+ auto flavorsNum = importedFlavors.Length();
+ if (!flavorsNum) {
+ aCallback(NS_OK);
+ return;
+ }
+#ifdef MOZ_LOGGING
+ if (flavorsNum > 1) {
+ LOGCLIP(" Only first MIME type (%s) will be imported from clipboard!",
+ importedFlavors[0].get());
+ }
+#endif
+
+ // Filter out MIME types on X11 to prevent unwanted conversions,
+ // see Bug 1611407
+ if (widget::GdkIsX11Display()) {
+ AsyncHasNativeClipboardDataMatchingFlavors(
+ importedFlavors, aWhichClipboard,
+ [aWhichClipboard, transferable = nsCOMPtr{aTransferable},
+ callback = std::move(aCallback)](auto aResultOrError) mutable {
+ if (aResultOrError.isErr()) {
+ callback(aResultOrError.unwrapErr());
+ return;
+ }
+
+ nsTArray<nsCString> clipboardFlavors =
+ std::move(aResultOrError.unwrap());
+ if (!clipboardFlavors.Length()) {
+ LOGCLIP(" no flavors in clipboard, quit.");
+ callback(NS_OK);
+ return;
+ }
+
+ AsyncGetDataFlavor(transferable, aWhichClipboard, clipboardFlavors[0],
+ std::move(callback));
+ });
+ return;
+ }
+
+ // Read clipboard directly on Wayland
+ AsyncGetDataFlavor(aTransferable, aWhichClipboard, importedFlavors[0],
+ std::move(aCallback));
+}
+
+nsresult nsClipboard::EmptyNativeClipboardData(int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::EmptyNativeClipboardData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ MOZ_ASSERT(!mSelectionTransferable);
+ }
+ } else {
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ MOZ_ASSERT(!mGlobalTransferable);
+ }
+ }
+ ClearCachedTargets(aWhichClipboard);
+ return NS_OK;
+}
+
+void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ mSelectionTransferable = nullptr;
+ } else {
+ mGlobalSequenceNumber++;
+ mGlobalTransferable = nullptr;
+ }
+}
+
+static bool FlavorMatchesTarget(const nsACString& aFlavor, GdkAtom aTarget) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(aTarget));
+ if (!atom_name) {
+ return false;
+ }
+ if (aFlavor.Equals(atom_name.get())) {
+ LOGCLIP(" has %s\n", atom_name.get());
+ return true;
+ }
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ if (aFlavor.EqualsLiteral(kJPGImageMime) &&
+ !strcmp(atom_name.get(), kJPEGImageMime)) {
+ LOGCLIP(" has image/jpg\n");
+ return true;
+ }
+ // application/x-moz-file should be treated like text/uri-list
+ if (aFlavor.EqualsLiteral(kFileMime) &&
+ !strcmp(atom_name.get(), kURIListMime)) {
+ LOGCLIP(" has text/uri-list treating as application/x-moz-file");
+ return true;
+ }
+ return false;
+}
+
+mozilla::Result<bool, nsresult>
+nsClipboard::HasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::HasNativeClipboardDataMatchingFlavors (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ if (!mContext) {
+ return Err(NS_ERROR_FAILURE);
+ }
+
+ auto targets = mContext->GetTargets(aWhichClipboard);
+ if (!targets) {
+ LOGCLIP(" no targes at clipboard (null)\n");
+ return false;
+ }
+
+#ifdef MOZ_LOGGING
+ if (LOGCLIP_ENABLED()) {
+ LOGCLIP(" Asking for content:\n");
+ for (auto& flavor : aFlavorList) {
+ LOGCLIP(" MIME %s\n", flavor.get());
+ }
+ LOGCLIP(" Clipboard content (target nums %zu):\n",
+ targets.AsSpan().Length());
+ for (const auto& target : targets.AsSpan()) {
+ GUniquePtr<gchar> atom_name(gdk_atom_name(target));
+ if (!atom_name) {
+ LOGCLIP(" failed to get MIME\n");
+ continue;
+ }
+ LOGCLIP(" MIME %s\n", atom_name.get());
+ }
+ }
+#endif
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (auto& flavor : aFlavorList) {
+ // We special case text/plain here.
+ if (flavor.EqualsLiteral(kTextMime) &&
+ gtk_targets_include_text(targets.AsSpan().data(),
+ targets.AsSpan().Length())) {
+ LOGCLIP(" has kTextMime\n");
+ return true;
+ }
+ for (const auto& target : targets.AsSpan()) {
+ if (FlavorMatchesTarget(flavor, target)) {
+ return true;
+ }
+ }
+ }
+
+ LOGCLIP(" no targes at clipboard (bad match)\n");
+ return false;
+}
+
+struct TragetCallbackHandler {
+ TragetCallbackHandler(const nsTArray<nsCString>& aAcceptedFlavorList,
+ nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback)
+ : mAcceptedFlavorList(aAcceptedFlavorList.Clone()),
+ mCallback(std::move(aCallback)) {
+ LOGCLIP("TragetCallbackHandler(%p) created", this);
+ }
+ ~TragetCallbackHandler() {
+ LOGCLIP("TragetCallbackHandler(%p) deleted", this);
+ }
+ nsTArray<nsCString> mAcceptedFlavorList;
+ nsBaseClipboard::HasMatchingFlavorsCallback mCallback;
+};
+
+void nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ nsBaseClipboard::HasMatchingFlavorsCallback&& aCallback) {
+ MOZ_DIAGNOSTIC_ASSERT(
+ nsIClipboard::IsClipboardTypeSupported(aWhichClipboard));
+
+ LOGCLIP("nsClipboard::AsyncHasNativeClipboardDataMatchingFlavors (%s)",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard)),
+ gdk_atom_intern("TARGETS", FALSE),
+ [](GtkClipboard* aClipboard, GtkSelectionData* aSelection,
+ gpointer aData) -> void {
+ LOGCLIP("gtk_clipboard_request_contents async handler (%p)", aData);
+ UniquePtr<TragetCallbackHandler> handler(
+ static_cast<TragetCallbackHandler*>(aData));
+
+ GdkAtom* targets = nullptr;
+ gint targetsNum = 0;
+ if (gtk_selection_data_get_length(aSelection) > 0) {
+ gtk_selection_data_get_targets(aSelection, &targets, &targetsNum);
+ }
+ nsTArray<nsCString> results;
+ if (targetsNum) {
+ for (auto& flavor : handler->mAcceptedFlavorList) {
+ LOGCLIP(" looking for %s", flavor.get());
+ if (flavor.EqualsLiteral(kTextMime) &&
+ gtk_targets_include_text(targets, targetsNum)) {
+ results.AppendElement(flavor);
+ LOGCLIP(" has kTextMime\n");
+ continue;
+ }
+ for (int i = 0; i < targetsNum; i++) {
+ if (FlavorMatchesTarget(flavor, targets[i])) {
+ results.AppendElement(flavor);
+ }
+ }
+ }
+ }
+ handler->mCallback(std::move(results));
+ },
+ new TragetCallbackHandler(aFlavorList, std::move(aCallback)));
+}
+
+nsITransferable* nsClipboard::GetTransferable(int32_t aWhichClipboard) {
+ nsITransferable* retval;
+
+ if (aWhichClipboard == kSelectionClipboard)
+ retval = mSelectionTransferable.get();
+ else
+ retval = mGlobalTransferable.get();
+
+ return retval;
+}
+
+void nsClipboard::SelectionGetEvent(GtkClipboard* aClipboard,
+ GtkSelectionData* aSelectionData) {
+ // Someone has asked us to hand them something. The first thing
+ // that we want to do is see if that something includes text. If
+ // it does, try to give it text/plain after converting it to
+ // utf-8.
+
+ int32_t whichClipboard;
+
+ // which clipboard?
+ GdkAtom selection = gtk_selection_data_get_selection(aSelectionData);
+ if (selection == GDK_SELECTION_PRIMARY)
+ whichClipboard = kSelectionClipboard;
+ else if (selection == GDK_SELECTION_CLIPBOARD)
+ whichClipboard = kGlobalClipboard;
+ else
+ return; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+
+ LOGCLIP("nsClipboard::SelectionGetEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+
+ nsCOMPtr<nsITransferable> trans = GetTransferable(whichClipboard);
+ if (!trans) {
+ // We have nothing to serve
+ LOGCLIP("nsClipboard::SelectionGetEvent() - %s clipboard is empty!\n",
+ whichClipboard == kSelectionClipboard ? "Primary" : "Clipboard");
+ return;
+ }
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> item;
+
+ GdkAtom selectionTarget = gtk_selection_data_get_target(aSelectionData);
+ LOGCLIP(" selection target %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+
+ // Check to see if the selection data is some text type.
+ if (gtk_targets_include_text(&selectionTarget, 1)) {
+ LOGCLIP(" providing text/plain data\n");
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/plain type for it.
+ rv = trans->GetTransferData("text/plain", getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" GetTransferData() failed to get text/plain!\n");
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ NS_ConvertUTF16toUTF8 utf8string(ucs2string);
+
+ LOGCLIP(" sent %zd bytes of utf-8 data\n", utf8string.Length());
+ if (selectionTarget == gdk_atom_intern("text/plain;charset=utf-8", FALSE)) {
+ LOGCLIP(
+ " using gtk_selection_data_set for 'text/plain;charset=utf-8'\n");
+ // Bypass gtk_selection_data_set_text, which will convert \n to \r\n
+ // in some versions of GTK.
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ reinterpret_cast<const guchar*>(utf8string.get()),
+ utf8string.Length());
+ } else {
+ gtk_selection_data_set_text(aSelectionData, utf8string.get(),
+ utf8string.Length());
+ }
+ return;
+ }
+
+ // Check to see if the selection data is an image type
+ if (gtk_targets_include_image(&selectionTarget, 1, TRUE)) {
+ LOGCLIP(" providing image data\n");
+ // Look through our transfer data for the image
+ static const char* const imageMimeTypes[] = {kNativeImageMime,
+ kPNGImageMime, kJPEGImageMime,
+ kJPGImageMime, kGIFImageMime};
+ nsCOMPtr<nsISupports> imageItem;
+ nsCOMPtr<imgIContainer> image;
+ for (uint32_t i = 0; i < ArrayLength(imageMimeTypes); i++) {
+ rv = trans->GetTransferData(imageMimeTypes[i], getter_AddRefs(imageItem));
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" %s is missing at GetTransferData()\n", imageMimeTypes[i]);
+ continue;
+ }
+
+ image = do_QueryInterface(imageItem);
+ if (image) {
+ LOGCLIP(" %s is available at GetTransferData()\n",
+ imageMimeTypes[i]);
+ break;
+ }
+ }
+
+ if (!image) { // Not getting an image for an image mime type!?
+ LOGCLIP(" Failed to get any image mime from GetTransferData()!\n");
+ return;
+ }
+
+ RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf) {
+ LOGCLIP(" nsImageToPixbuf::ImageToPixbuf() failed!\n");
+ return;
+ }
+
+ LOGCLIP(" Setting pixbuf image data as %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ return;
+ }
+
+ if (selectionTarget == gdk_atom_intern(kHTMLMime, FALSE)) {
+ LOGCLIP(" providing %s data\n", kHTMLMime);
+ rv = trans->GetTransferData(kHTMLMime, getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" failed to get %s data by GetTransferData()!\n", kHTMLMime);
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) {
+ LOGCLIP(" failed to get wideString interface!");
+ return;
+ }
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+
+ nsAutoCString html;
+ // Add the prefix so the encoding is correctly detected.
+ html.AppendLiteral(kHTMLMarkupPrefix);
+ AppendUTF16toUTF8(ucs2string, html);
+
+ LOGCLIP(" Setting %zd bytes of %s data\n", html.Length(),
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)html.get(), html.Length());
+ return;
+ }
+
+ // We put kFileMime onto the clipboard as kURIListMime.
+ if (selectionTarget == gdk_atom_intern(kURIListMime, FALSE)) {
+ LOGCLIP(" providing %s data\n", kURIListMime);
+ rv = trans->GetTransferData(kFileMime, getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" failed to get %s data by GetTransferData()!\n", kFileMime);
+ return;
+ }
+
+ nsCOMPtr<nsIFile> file = do_QueryInterface(item);
+ if (!file) {
+ LOGCLIP(" failed to get nsIFile interface!");
+ return;
+ }
+
+ nsCOMPtr<nsIURI> fileURI;
+ rv = NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (NS_FAILED(rv)) {
+ LOGCLIP(" failed to get fileURI\n");
+ return;
+ }
+
+ nsAutoCString uri;
+ if (NS_FAILED(fileURI->GetSpec(uri))) {
+ LOGCLIP(" failed to get fileURI spec\n");
+ return;
+ }
+
+ LOGCLIP(" Setting %zd bytes of data\n", uri.Length());
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)uri.get(), uri.Length());
+ return;
+ }
+
+ LOGCLIP(" Try if we have anything at GetTransferData() for %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(selectionTarget)).get());
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ GUniquePtr<gchar> target_name(gdk_atom_name(selectionTarget));
+ if (!target_name) {
+ LOGCLIP(" Failed to get target name!\n");
+ return;
+ }
+
+ rv = trans->GetTransferData(target_name.get(), getter_AddRefs(item));
+ // nothing found?
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP(" Failed to get anything from GetTransferData()!\n");
+ return;
+ }
+
+ void* primitive_data = nullptr;
+ uint32_t dataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(target_name.get()), item, &primitive_data, &dataLen);
+ if (!primitive_data) {
+ LOGCLIP(" Failed to get primitive data!\n");
+ return;
+ }
+
+ LOGCLIP(" Setting %s as a primitive data type, %d bytes\n",
+ target_name.get(), dataLen);
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar*)primitive_data, dataLen);
+ free(primitive_data);
+}
+
+void nsClipboard::ClearCachedTargets(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ nsRetrievalContext::ClearCachedTargetsPrimary(nullptr, nullptr, nullptr);
+ } else {
+ nsRetrievalContext::ClearCachedTargetsClipboard(nullptr, nullptr, nullptr);
+ }
+}
+
+void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+ LOGCLIP("nsClipboard::SelectionClearEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ ClearCachedTargets(whichClipboard);
+ ClearTransferable(whichClipboard);
+ ClearClipboardCache(whichClipboard);
+}
+
+void nsClipboard::OwnerChangedEvent(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+ LOGCLIP("nsClipboard::OwnerChangedEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard");
+ GtkWidget* gtkWidget = [aEvent]() -> GtkWidget* {
+ if (!aEvent->owner) {
+ return nullptr;
+ }
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(aEvent->owner, &user_data);
+ return GTK_WIDGET(user_data);
+ }();
+ // If we can get GtkWidget from the current clipboard owner, this
+ // owner-changed event must be triggered by ourself via calling
+ // gtk_clipboard_set_with_data, the sequence number should already be handled.
+ if (!gtkWidget) {
+ if (whichClipboard == kSelectionClipboard) {
+ mSelectionSequenceNumber++;
+ } else {
+ mGlobalSequenceNumber++;
+ }
+ }
+
+ ClearCachedTargets(whichClipboard);
+}
+
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data) {
+ LOGCLIP("clipboard_get_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
+ clipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
+ LOGCLIP("clipboard_clear_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(user_data);
+ clipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+void clipboard_owner_change_cb(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent,
+ gpointer aUserData) {
+ LOGCLIP("clipboard_owner_change_cb() callback\n");
+ nsClipboard* clipboard = static_cast<nsClipboard*>(aUserData);
+ clipboard->OwnerChangedEvent(aGtkClipboard, aEvent);
+}
+
+/*
+ * This function extracts the encoding label from the subset of HTML internal
+ * encoding declaration syntax that uses the old long form with double quotes
+ * and without spaces around the equals sign between the "content" attribute
+ * name and the attribute value.
+ *
+ * This was added for the sake of an ancient version of StarOffice
+ * in the pre-UTF-8 era in bug 123389. It is unclear if supporting
+ * non-UTF-8 encodings is still necessary and if this function
+ * still needs to exist.
+ *
+ * As of December 2022, both Gecko and LibreOffice emit an UTF-8
+ * declaration that this function successfully extracts "UTF-8" from,
+ * but that's also the default that we fall back on if this function
+ * fails to extract a label.
+ */
+bool GetHTMLCharset(Span<const char> aData, nsCString& aFoundCharset) {
+ // Assume ASCII first to find "charset" info
+ const nsDependentCSubstring htmlStr(aData);
+ nsACString::const_iterator start, end;
+ htmlStr.BeginReading(start);
+ htmlStr.EndReading(end);
+ nsACString::const_iterator valueStart(start), valueEnd(start);
+
+ if (CaseInsensitiveFindInReadable("CONTENT=\"text/html;"_ns, start, end)) {
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (CaseInsensitiveFindInReadable("charset="_ns, start, end)) {
+ valueStart = end;
+ start = end;
+ htmlStr.EndReading(end);
+
+ if (FindCharInReadable('"', start, end)) valueEnd = start;
+ }
+ }
+ // find "charset" in HTML
+ if (valueStart != valueEnd) {
+ aFoundCharset = Substring(valueStart, valueEnd);
+ ToUpperCase(aFoundCharset);
+ return true;
+ }
+ return false;
+}
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
new file mode 100644
index 0000000000..0ddbb59827
--- /dev/null
+++ b/widget/gtk/nsClipboard.h
@@ -0,0 +1,177 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsClipboard_h_
+#define __nsClipboard_h_
+
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Span.h"
+#include "nsBaseClipboard.h"
+#include "nsIClipboard.h"
+#include "nsIObserver.h"
+#include "nsCOMPtr.h"
+#include "GUniquePtr.h"
+#include <gtk/gtk.h>
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gClipboardLog;
+# define LOGCLIP(...) \
+ MOZ_LOG(gClipboardLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOGCLIP_ENABLED() \
+ MOZ_LOG_TEST(gClipboardLog, mozilla::LogLevel::Debug)
+#else
+# define LOGCLIP(...)
+# define LOGCLIP_ENABLED() false
+#endif /* MOZ_LOGGING */
+
+class ClipboardTargets {
+ friend class ClipboardData;
+
+ mozilla::GUniquePtr<GdkAtom> mTargets;
+ uint32_t mCount = 0;
+
+ public:
+ ClipboardTargets() = default;
+ ClipboardTargets(mozilla::GUniquePtr<GdkAtom> aTargets, uint32_t aCount)
+ : mTargets(std::move(aTargets)), mCount(aCount) {}
+
+ void Set(ClipboardTargets);
+ ClipboardTargets Clone();
+ void Clear() {
+ mTargets = nullptr;
+ mCount = 0;
+ };
+
+ mozilla::Span<GdkAtom> AsSpan() const { return {mTargets.get(), mCount}; }
+ explicit operator bool() const { return bool(mTargets); }
+};
+
+class ClipboardData {
+ mozilla::GUniquePtr<char> mData;
+ uint32_t mLength = 0;
+
+ public:
+ ClipboardData() = default;
+
+ void SetData(mozilla::Span<const uint8_t>);
+ void SetText(mozilla::Span<const char>);
+ void SetTargets(ClipboardTargets);
+
+ ClipboardTargets ExtractTargets();
+ mozilla::GUniquePtr<char> ExtractText() {
+ mLength = 0;
+ return std::move(mData);
+ }
+
+ mozilla::Span<char> AsSpan() const { return {mData.get(), mLength}; }
+ explicit operator bool() const { return bool(mData); }
+};
+
+enum class ClipboardDataType { Data, Text, Targets };
+
+class nsRetrievalContext {
+ public:
+ // We intentionally use unsafe thread refcount as clipboard is used in
+ // main thread only.
+ NS_INLINE_DECL_REFCOUNTING(nsRetrievalContext)
+
+ // Get actual clipboard content (GetClipboardData/GetClipboardText).
+ virtual ClipboardData GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard) = 0;
+ virtual mozilla::GUniquePtr<char> GetClipboardText(
+ int32_t aWhichClipboard) = 0;
+
+ // Get data mime types which can be obtained from clipboard.
+ ClipboardTargets GetTargets(int32_t aWhichClipboard);
+
+ // Clipboard/Primary selection owner changed. Clear internal cached data.
+ static void ClearCachedTargetsClipboard(GtkClipboard* aClipboard,
+ GdkEvent* aEvent, gpointer data);
+ static void ClearCachedTargetsPrimary(GtkClipboard* aClipboard,
+ GdkEvent* aEvent, gpointer data);
+
+ nsRetrievalContext() = default;
+
+ protected:
+ virtual ClipboardTargets GetTargetsImpl(int32_t aWhichClipboard) = 0;
+ virtual ~nsRetrievalContext();
+
+ static ClipboardTargets sClipboardTargets;
+ static ClipboardTargets sPrimaryTargets;
+};
+
+class nsClipboard : public nsBaseClipboard, public nsIObserver {
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ // Make sure we are initialized, called from the factory
+ // constructor
+ nsresult Init(void);
+
+ // Someone requested the selection
+ void SelectionGetEvent(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData);
+ void SelectionClearEvent(GtkClipboard* aGtkClipboard);
+
+ // Clipboard owner changed
+ void OwnerChangedEvent(GtkClipboard* aGtkClipboard,
+ GdkEventOwnerChange* aEvent);
+
+ protected:
+ // Implement the native clipboard behavior.
+ NS_IMETHOD SetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) override;
+ NS_IMETHOD GetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard) override;
+ void AsyncGetNativeClipboardData(nsITransferable* aTransferable,
+ int32_t aWhichClipboard,
+ GetDataCallback&& aCallback) override;
+ nsresult EmptyNativeClipboardData(int32_t aWhichClipboard) override;
+ mozilla::Result<int32_t, nsresult> GetNativeClipboardSequenceNumber(
+ int32_t aWhichClipboard) override;
+ mozilla::Result<bool, nsresult> HasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard) override;
+ void AsyncHasNativeClipboardDataMatchingFlavors(
+ const nsTArray<nsCString>& aFlavorList, int32_t aWhichClipboard,
+ HasMatchingFlavorsCallback&& aCallback) override;
+
+ private:
+ virtual ~nsClipboard();
+
+ // Get our hands on the correct transferable, given a specific
+ // clipboard
+ nsITransferable* GetTransferable(int32_t aWhichClipboard);
+
+ void ClearTransferable(int32_t aWhichClipboard);
+ void ClearCachedTargets(int32_t aWhichClipboard);
+
+ bool FilterImportedFlavors(int32_t aWhichClipboard,
+ nsTArray<nsCString>& aFlavors);
+
+ // Hang on to our transferables so we can transfer data when asked.
+ nsCOMPtr<nsITransferable> mSelectionTransferable;
+ nsCOMPtr<nsITransferable> mGlobalTransferable;
+ RefPtr<nsRetrievalContext> mContext;
+
+ // Sequence number of the system clipboard data.
+ int32_t mSelectionSequenceNumber = 0;
+ int32_t mGlobalSequenceNumber = 0;
+};
+
+extern const int kClipboardTimeout;
+extern const int kClipboardFastIterationNum;
+
+GdkAtom GetSelectionAtom(int32_t aWhichClipboard);
+int GetGeckoClipboardType(GtkClipboard* aGtkClipboard);
+
+#endif /* __nsClipboard_h_ */
diff --git a/widget/gtk/nsClipboardWayland.cpp b/widget/gtk/nsClipboardWayland.cpp
new file mode 100644
index 0000000000..d179f242dd
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.cpp
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsClipboardWayland.h"
+
+#include "AsyncGtkClipboardRequest.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/ScopeExit.h"
+#include "prtime.h"
+
+#include <gtk/gtk.h>
+#include <stdlib.h>
+#include <string.h>
+
+using namespace mozilla;
+
+nsRetrievalContextWayland::nsRetrievalContextWayland() = default;
+
+ClipboardTargets nsRetrievalContextWayland::GetTargetsImpl(
+ int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContextWayland::GetTargetsImpl()\n");
+
+ return WaitForClipboardData(ClipboardDataType::Targets, aWhichClipboard)
+ .ExtractTargets();
+}
+
+ClipboardData nsRetrievalContextWayland::GetClipboardData(
+ const char* aMimeType, int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContextWayland::GetClipboardData() mime %s\n", aMimeType);
+
+ return WaitForClipboardData(ClipboardDataType::Data, aWhichClipboard,
+ aMimeType);
+}
+
+GUniquePtr<char> nsRetrievalContextWayland::GetClipboardText(
+ int32_t aWhichClipboard) {
+ GdkAtom selection = GetSelectionAtom(aWhichClipboard);
+
+ LOGCLIP("nsRetrievalContextWayland::GetClipboardText(), clipboard %s\n",
+ (selection == GDK_SELECTION_PRIMARY) ? "Primary" : "Selection");
+
+ return WaitForClipboardData(ClipboardDataType::Text, aWhichClipboard)
+ .ExtractText();
+}
+
+ClipboardData nsRetrievalContextWayland::WaitForClipboardData(
+ ClipboardDataType aDataType, int32_t aWhichClipboard,
+ const char* aMimeType) {
+ LOGCLIP("nsRetrievalContextWayland::WaitForClipboardData, MIME %s\n",
+ aMimeType);
+
+ AsyncGtkClipboardRequest request(aDataType, aWhichClipboard, aMimeType);
+ int iteration = 1;
+
+ PRTime entryTime = PR_Now();
+ while (!request.HasCompleted()) {
+ if (iteration++ > kClipboardFastIterationNum) {
+ /* sleep for 10 ms/iteration */
+ PR_Sleep(PR_MillisecondsToInterval(10));
+ if (PR_Now() - entryTime > kClipboardTimeout) {
+ LOGCLIP(" failed to get async clipboard data in time limit\n");
+ break;
+ }
+ }
+ LOGCLIP("doing iteration %d msec %ld ...\n", (iteration - 1),
+ (long)((PR_Now() - entryTime) / 1000));
+ gtk_main_iteration();
+ }
+
+ return request.TakeResult();
+}
diff --git a/widget/gtk/nsClipboardWayland.h b/widget/gtk/nsClipboardWayland.h
new file mode 100644
index 0000000000..cd8dda0772
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsClipboardWayland_h_
+#define __nsClipboardWayland_h_
+
+#include "mozilla/Mutex.h"
+#include "nsClipboard.h"
+
+class nsRetrievalContextWayland final : public nsRetrievalContext {
+ public:
+ nsRetrievalContextWayland();
+
+ ClipboardData GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard) override;
+ mozilla::GUniquePtr<char> GetClipboardText(int32_t aWhichClipboard) override;
+ ClipboardTargets GetTargetsImpl(int32_t aWhichClipboard) override;
+
+ private:
+ ClipboardData WaitForClipboardData(ClipboardDataType, int32_t aWhichClipboard,
+ const char* aMimeType = nullptr);
+};
+
+#endif /* __nsClipboardWayland_h_ */
diff --git a/widget/gtk/nsClipboardX11.cpp b/widget/gtk/nsClipboardX11.cpp
new file mode 100644
index 0000000000..db12d1e69c
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -0,0 +1,173 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/ArrayUtils.h"
+
+#include "AsyncGtkClipboardRequest.h"
+#include "nsClipboardX11.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/WidgetUtilsGtk.h"
+
+#include <gtk/gtk.h>
+
+// For manipulation of the X event queue
+#include <X11/Xlib.h>
+#include <poll.h>
+#include <gdk/gdkx.h>
+#include <sys/time.h>
+#include <sys/types.h>
+#include <errno.h>
+#include <unistd.h>
+#include "X11UndefineNone.h"
+
+using namespace mozilla;
+
+nsRetrievalContextX11::nsRetrievalContextX11() = default;
+
+static void DispatchSelectionNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkWindow* window = gtk_widget_get_window(widget);
+ if (window) {
+ GdkEvent event = {};
+ event.selection.type = GDK_SELECTION_NOTIFY;
+ event.selection.window = window;
+ event.selection.selection =
+ gdk_x11_xatom_to_atom(xevent->xselection.selection);
+ event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+ event.selection.property =
+ gdk_x11_xatom_to_atom(xevent->xselection.property);
+ event.selection.time = xevent->xselection.time;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+static void DispatchPropertyNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkWindow* window = gtk_widget_get_window(widget);
+ if (window && ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK)) {
+ GdkEvent event = {};
+ event.property.type = GDK_PROPERTY_NOTIFY;
+ event.property.window = window;
+ event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+ event.property.time = xevent->xproperty.time;
+ event.property.state = xevent->xproperty.state;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+struct checkEventContext {
+ GtkWidget* cbWidget;
+ Atom selAtom;
+};
+
+static Bool checkEventProc(Display* display, XEvent* event, XPointer arg) {
+ checkEventContext* context = (checkEventContext*)arg;
+
+ if (event->xany.type == SelectionNotify ||
+ (event->xany.type == PropertyNotify &&
+ event->xproperty.atom == context->selAtom)) {
+ GdkWindow* cbWindow = gdk_x11_window_lookup_for_display(
+ gdk_x11_lookup_xdisplay(display), event->xany.window);
+ if (cbWindow) {
+ GtkWidget* cbWidget = nullptr;
+ gdk_window_get_user_data(cbWindow, (gpointer*)&cbWidget);
+ if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+ context->cbWidget = cbWidget;
+ return X11True;
+ }
+ }
+ }
+
+ return X11False;
+}
+
+ClipboardData nsRetrievalContextX11::WaitForClipboardData(
+ ClipboardDataType aDataType, int32_t aWhichClipboard,
+ const char* aMimeType) {
+ AsyncGtkClipboardRequest request(aDataType, aWhichClipboard, aMimeType);
+ if (request.HasCompleted()) {
+ // the request completed synchronously
+ return request.TakeResult();
+ }
+
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ // gdk_display_get_default() returns null on headless
+ if (widget::GdkIsX11Display(gdkDisplay)) {
+ Display* xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ checkEventContext context;
+ context.cbWidget = nullptr;
+ context.selAtom =
+ gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", FALSE));
+
+ // Send X events which are relevant to the ongoing selection retrieval
+ // to the clipboard widget. Wait until either the operation completes, or
+ // we hit our timeout. All other X events remain queued.
+
+ int poll_result;
+
+ struct pollfd pfd;
+ pfd.fd = ConnectionNumber(xDisplay);
+ pfd.events = POLLIN;
+ TimeStamp start = TimeStamp::Now();
+
+ do {
+ XEvent xevent;
+
+ while (XCheckIfEvent(xDisplay, &xevent, checkEventProc,
+ (XPointer)&context)) {
+ if (xevent.xany.type == SelectionNotify)
+ DispatchSelectionNotifyEvent(context.cbWidget, &xevent);
+ else
+ DispatchPropertyNotifyEvent(context.cbWidget, &xevent);
+
+ if (request.HasCompleted()) {
+ return request.TakeResult();
+ }
+ }
+
+ TimeStamp now = TimeStamp::Now();
+ int timeout = std::max<int>(
+ 0, kClipboardTimeout / 1000 - (now - start).ToMilliseconds());
+ poll_result = poll(&pfd, 1, timeout);
+ } while ((poll_result == 1 && (pfd.revents & (POLLHUP | POLLERR)) == 0) ||
+ (poll_result == -1 && errno == EINTR));
+ }
+
+ LOGCLIP("exceeded clipboard timeout");
+ return {};
+}
+
+ClipboardTargets nsRetrievalContextX11::GetTargetsImpl(
+ int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContextX11::GetTargetsImpl(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+ return WaitForClipboardData(ClipboardDataType::Targets, aWhichClipboard)
+ .ExtractTargets();
+}
+
+ClipboardData nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContextX11::GetClipboardData(%s) MIME %s\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard",
+ aMimeType);
+
+ return WaitForClipboardData(ClipboardDataType::Data, aWhichClipboard,
+ aMimeType);
+}
+
+GUniquePtr<char> nsRetrievalContextX11::GetClipboardText(
+ int32_t aWhichClipboard) {
+ LOGCLIP("nsRetrievalContextX11::GetClipboardText(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard");
+
+ return WaitForClipboardData(ClipboardDataType::Text, aWhichClipboard)
+ .ExtractText();
+}
diff --git a/widget/gtk/nsClipboardX11.h b/widget/gtk/nsClipboardX11.h
new file mode 100644
index 0000000000..c143222d3a
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsClipboardX11_h_
+#define __nsClipboardX11_h_
+
+#include <gtk/gtk.h>
+
+#include "mozilla/Maybe.h"
+#include "nsClipboard.h"
+
+class nsRetrievalContextX11 : public nsRetrievalContext {
+ public:
+ ClipboardData GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard) override;
+ mozilla::GUniquePtr<char> GetClipboardText(int32_t aWhichClipboard) override;
+ ClipboardTargets GetTargetsImpl(int32_t aWhichClipboard) override;
+
+ nsRetrievalContextX11();
+
+ private:
+ // Spins X event loop until timing out or being completed.
+ ClipboardData WaitForClipboardData(ClipboardDataType aDataType,
+ int32_t aWhichClipboard,
+ const char* aMimeType = nullptr);
+};
+
+#endif /* __nsClipboardX11_h_ */
diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp
new file mode 100644
index 0000000000..4719710d9e
--- /dev/null
+++ b/widget/gtk/nsColorPicker.cpp
@@ -0,0 +1,253 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gtk/gtk.h>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/dom/HTMLInputElement.h"
+#include "nsColor.h"
+#include "nsColorPicker.h"
+#include "nsGtkUtils.h"
+#include "nsIWidget.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+using mozilla::dom::HTMLInputElement;
+
+NS_IMPL_ISUPPORTS(nsColorPicker, nsIColorPicker)
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+int nsColorPicker::convertGdkRgbaComponent(gdouble color_component) {
+ // GdkRGBA value is in range [0.0..1.0]. We need something in range [0..255]
+ return color_component * 255 + 0.5;
+}
+
+gdouble nsColorPicker::convertToGdkRgbaComponent(int color_component) {
+ return color_component / 255.0;
+}
+
+GdkRGBA nsColorPicker::convertToRgbaColor(nscolor color) {
+ GdkRGBA result = {convertToGdkRgbaComponent(NS_GET_R(color)),
+ convertToGdkRgbaComponent(NS_GET_G(color)),
+ convertToGdkRgbaComponent(NS_GET_B(color)),
+ convertToGdkRgbaComponent(NS_GET_A(color))};
+
+ return result;
+}
+#else
+int nsColorPicker::convertGdkColorComponent(guint16 color_component) {
+ // GdkColor value is in range [0..65535]. We need something in range [0..255]
+ return (color_component * 255 + 127) / 65535;
+}
+
+guint16 nsColorPicker::convertToGdkColorComponent(int color_component) {
+ return color_component * 65535 / 255;
+}
+
+GdkColor nsColorPicker::convertToGdkColor(nscolor color) {
+ GdkColor result = {0 /* obsolete, unused 'pixel' value */,
+ convertToGdkColorComponent(NS_GET_R(color)),
+ convertToGdkColorComponent(NS_GET_G(color)),
+ convertToGdkColorComponent(NS_GET_B(color))};
+
+ return result;
+}
+
+GtkColorSelection* nsColorPicker::WidgetGetColorSelection(GtkWidget* widget) {
+ return GTK_COLOR_SELECTION(gtk_color_selection_dialog_get_color_selection(
+ GTK_COLOR_SELECTION_DIALOG(widget)));
+}
+#endif
+
+NS_IMETHODIMP nsColorPicker::Init(mozIDOMWindowProxy* aParent,
+ const nsAString& title,
+ const nsAString& initialColor,
+ const nsTArray<nsString>& aDefaultColors) {
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+ mTitle = title;
+ mInitialColor = initialColor;
+ mDefaultColors.Assign(aDefaultColors);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsColorPicker::Open(
+ nsIColorPickerShownCallback* aColorPickerShownCallback) {
+ auto maybeColor = HTMLInputElement::ParseSimpleColor(mInitialColor);
+ if (maybeColor.isNothing()) {
+ return NS_ERROR_FAILURE;
+ }
+ nscolor color = maybeColor.value();
+
+ if (mCallback) {
+ // It means Open has already been called: this is not allowed
+ NS_WARNING("mCallback is already set. Open called twice?");
+ return NS_ERROR_FAILURE;
+ }
+ mCallback = aColorPickerShownCallback;
+
+ NS_ConvertUTF16toUTF8 title(mTitle);
+ GtkWindow* parent_window =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ GtkWidget* color_chooser =
+ gtk_color_chooser_dialog_new(title.get(), parent_window);
+
+ if (parent_window) {
+ GtkWindow* window = GTK_WINDOW(color_chooser);
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ if (gtk_window_get_modal(parent_window)) {
+ gtk_window_set_modal(window, TRUE);
+ }
+ }
+
+ gtk_color_chooser_set_use_alpha(GTK_COLOR_CHOOSER(color_chooser), FALSE);
+
+ // Setting the default colors will put them into "Custom" colors list.
+ for (const nsString& defaultColor : mDefaultColors) {
+ if (auto color = HTMLInputElement::ParseSimpleColor(defaultColor)) {
+ GdkRGBA color_rgba = convertToRgbaColor(*color);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);
+ }
+ }
+
+ // The initial color needs to be set last.
+ GdkRGBA color_rgba = convertToRgbaColor(color);
+ gtk_color_chooser_set_rgba(GTK_COLOR_CHOOSER(color_chooser), &color_rgba);
+
+ g_signal_connect(GTK_COLOR_CHOOSER(color_chooser), "color-activated",
+ G_CALLBACK(OnColorChanged), this);
+#else
+ GtkWidget* color_chooser = gtk_color_selection_dialog_new(title.get());
+
+ if (parent_window) {
+ GtkWindow* window = GTK_WINDOW(color_chooser);
+ gtk_window_set_transient_for(window, parent_window);
+ gtk_window_set_destroy_with_parent(window, TRUE);
+ if (gtk_window_get_modal(parent_window)) {
+ gtk_window_set_modal(window, TRUE);
+ }
+ }
+
+ GdkColor color_gdk = convertToGdkColor(color);
+ gtk_color_selection_set_current_color(WidgetGetColorSelection(color_chooser),
+ &color_gdk);
+
+ g_signal_connect(WidgetGetColorSelection(color_chooser), "color-changed",
+ G_CALLBACK(OnColorChanged), this);
+#endif
+
+ NS_ADDREF_THIS();
+
+ g_signal_connect(color_chooser, "response", G_CALLBACK(OnResponse), this);
+ g_signal_connect(color_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(color_chooser);
+
+ return NS_OK;
+}
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+/* static */
+void nsColorPicker::OnColorChanged(GtkColorChooser* color_chooser,
+ GdkRGBA* color, gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Update(color);
+}
+
+void nsColorPicker::Update(GdkRGBA* color) {
+ SetColor(color);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::SetColor(const GdkRGBA* color) {
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkRgbaComponent(color->red));
+ mColor += ToHexString(convertGdkRgbaComponent(color->green));
+ mColor += ToHexString(convertGdkRgbaComponent(color->blue));
+}
+#else
+/* static */
+void nsColorPicker::OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Update(colorselection);
+}
+
+void nsColorPicker::Update(GtkColorSelection* colorselection) {
+ ReadValueFromColorSelection(colorselection);
+ if (mCallback) {
+ mCallback->Update(mColor);
+ }
+}
+
+void nsColorPicker::ReadValueFromColorSelection(
+ GtkColorSelection* colorselection) {
+ GdkColor rgba;
+ gtk_color_selection_get_current_color(colorselection, &rgba);
+
+ mColor.Assign('#');
+ mColor += ToHexString(convertGdkColorComponent(rgba.red));
+ mColor += ToHexString(convertGdkColorComponent(rgba.green));
+ mColor += ToHexString(convertGdkColorComponent(rgba.blue));
+}
+#endif
+
+/* static */
+void nsColorPicker::OnResponse(GtkWidget* color_chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Done(color_chooser, response_id);
+}
+
+/* static */
+void nsColorPicker::OnDestroy(GtkWidget* color_chooser, gpointer user_data) {
+ static_cast<nsColorPicker*>(user_data)->Done(color_chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+void nsColorPicker::Done(GtkWidget* color_chooser, gint response) {
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ GdkRGBA color;
+ gtk_color_chooser_get_rgba(GTK_COLOR_CHOOSER(color_chooser), &color);
+ SetColor(&color);
+#else
+ ReadValueFromColorSelection(WidgetGetColorSelection(color_chooser));
+#endif
+ break;
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ mColor = mInitialColor;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(color_chooser, FuncToGpointer(OnDestroy),
+ this);
+
+ gtk_widget_destroy(color_chooser);
+ if (mCallback) {
+ mCallback->Done(mColor);
+ mCallback = nullptr;
+ }
+
+ NS_RELEASE_THIS();
+}
+
+nsString nsColorPicker::ToHexString(int n) {
+ nsString result;
+ if (n <= 0x0F) {
+ result.Append('0');
+ }
+ result.AppendInt(n, 16);
+ return result;
+}
diff --git a/widget/gtk/nsColorPicker.h b/widget/gtk/nsColorPicker.h
new file mode 100644
index 0000000000..d693175717
--- /dev/null
+++ b/widget/gtk/nsColorPicker.h
@@ -0,0 +1,71 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsColorPicker_h__
+#define nsColorPicker_h__
+
+#include <gtk/gtk.h>
+
+#include "nsCOMPtr.h"
+#include "nsIColorPicker.h"
+#include "nsString.h"
+
+// Enable Gtk3 system color picker.
+#define ACTIVATE_GTK3_COLOR_PICKER 1
+
+class nsIWidget;
+
+class nsColorPicker final : public nsIColorPicker {
+ public:
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSICOLORPICKER
+
+ nsColorPicker() = default;
+
+ private:
+ ~nsColorPicker() = default;
+
+ static nsString ToHexString(int n);
+
+ static void OnResponse(GtkWidget* dialog, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* dialog, gpointer user_data);
+
+#if defined(ACTIVATE_GTK3_COLOR_PICKER)
+ static void OnColorChanged(GtkColorChooser* color_chooser, GdkRGBA* color,
+ gpointer user_data);
+
+ static int convertGdkRgbaComponent(gdouble color_component);
+ static gdouble convertToGdkRgbaComponent(int color_component);
+ static GdkRGBA convertToRgbaColor(nscolor color);
+
+ void Update(GdkRGBA* color);
+ void SetColor(const GdkRGBA* color);
+#else
+ static void OnColorChanged(GtkColorSelection* colorselection,
+ gpointer user_data);
+
+ // Conversion functions for color
+ static int convertGdkColorComponent(guint16 color_component);
+ static guint16 convertToGdkColorComponent(int color_component);
+ static GdkColor convertToGdkColor(nscolor color);
+
+ static GtkColorSelection* WidgetGetColorSelection(GtkWidget* widget);
+
+ void Update(GtkColorSelection* colorselection);
+ void ReadValueFromColorSelection(GtkColorSelection* colorselection);
+#endif
+
+ void Done(GtkWidget* dialog, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIColorPickerShownCallback> mCallback;
+ nsString mTitle;
+ nsString mColor;
+ nsString mInitialColor;
+ nsTArray<nsString> mDefaultColors;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/gtk/nsDeviceContextSpecG.cpp b/widget/gtk/nsDeviceContextSpecG.cpp
new file mode 100644
index 0000000000..3dfa816408
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -0,0 +1,422 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDeviceContextSpecG.h"
+
+#include "mozilla/gfx/PrintPromise.h"
+#include "mozilla/gfx/PrintTargetPDF.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+#include "mozilla/GUniquePtr.h"
+#include "mozilla/WidgetUtilsGtk.h"
+
+#include "prenv.h" /* for PR_GetEnv */
+
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.h"
+#include "nsQueryObject.h"
+#include "nsReadableUtils.h"
+#include "nsThreadUtils.h"
+
+#include "nsCUPSShim.h"
+#include "nsPrinterCUPS.h"
+
+#include "nsPrintSettingsGTK.h"
+
+#include "nsIFileStreams.h"
+#include "nsIFile.h"
+#include "nsTArray.h"
+#include "nsThreadUtils.h"
+
+#include "mozilla/Preferences.h"
+#include "mozilla/StaticPrefs_print.h"
+
+#include <dlfcn.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+// To check if we need to use flatpak portal for printing
+#include "nsIGIOService.h"
+
+using namespace mozilla;
+
+using mozilla::gfx::IntSize;
+using mozilla::gfx::PrintEndDocumentPromise;
+using mozilla::gfx::PrintTarget;
+using mozilla::gfx::PrintTargetPDF;
+
+nsDeviceContextSpecGTK::nsDeviceContextSpecGTK()
+ : mGtkPrintSettings(nullptr), mGtkPageSetup(nullptr) {}
+
+nsDeviceContextSpecGTK::~nsDeviceContextSpecGTK() {
+ if (mGtkPageSetup) {
+ g_object_unref(mGtkPageSetup);
+ }
+
+ if (mGtkPrintSettings) {
+ g_object_unref(mGtkPrintSettings);
+ }
+
+ if (mSpoolFile) {
+ mSpoolFile->Remove(false);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsDeviceContextSpecGTK, nsIDeviceContextSpec)
+
+already_AddRefed<PrintTarget> nsDeviceContextSpecGTK::MakePrintTarget() {
+ double width, height;
+ mPrintSettings->GetEffectiveSheetSize(&width, &height);
+
+ // convert twips to points
+ width /= TWIPS_PER_POINT_FLOAT;
+ height /= TWIPS_PER_POINT_FLOAT;
+
+ // We shouldn't be attempting to get a surface if we've already got a spool
+ // file.
+ MOZ_ASSERT(!mSpoolFile);
+
+ auto stream = [&]() -> nsCOMPtr<nsIOutputStream> {
+ if (mPrintSettings->GetOutputDestination() ==
+ nsIPrintSettings::kOutputDestinationStream) {
+ nsCOMPtr<nsIOutputStream> out;
+ mPrintSettings->GetOutputStream(getter_AddRefs(out));
+ return out;
+ }
+ // Spool file. Use Glib's temporary file function since we're
+ // already dependent on the gtk software stack.
+ gchar* buf;
+ gint fd = g_file_open_tmp("XXXXXX.tmp", &buf, nullptr);
+ if (-1 == fd) {
+ return nullptr;
+ }
+ close(fd);
+ if (NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(buf), false,
+ getter_AddRefs(mSpoolFile)))) {
+ unlink(buf);
+ g_free(buf);
+ return nullptr;
+ }
+ mSpoolName = buf;
+ g_free(buf);
+ mSpoolFile->SetPermissions(0600);
+ nsCOMPtr<nsIFileOutputStream> stream =
+ do_CreateInstance("@mozilla.org/network/file-output-stream;1");
+ if (NS_FAILED(stream->Init(mSpoolFile, -1, -1, 0))) {
+ return nullptr;
+ }
+ return stream;
+ }();
+
+ return PrintTargetPDF::CreateOrNull(stream, IntSize::Ceil(width, height));
+}
+
+#define DECLARE_KNOWN_MONOCHROME_SETTING(key_, value_) {"cups-" key_, value_},
+
+struct {
+ const char* mKey;
+ const char* mValue;
+} kKnownMonochromeSettings[] = {
+ CUPS_EACH_MONOCHROME_PRINTER_SETTING(DECLARE_KNOWN_MONOCHROME_SETTING)};
+
+#undef DECLARE_KNOWN_MONOCHROME_SETTING
+
+// https://developer.gnome.org/gtk3/stable/GtkPaperSize.html#gtk-paper-size-new-from-ipp
+static GUniquePtr<GtkPaperSize> GtkPaperSizeFromIpp(const gchar* aIppName,
+ gdouble aWidth,
+ gdouble aHeight) {
+ static auto sPtr = (GtkPaperSize * (*)(const gchar*, gdouble, gdouble))
+ dlsym(RTLD_DEFAULT, "gtk_paper_size_new_from_ipp");
+ if (gtk_check_version(3, 16, 0)) {
+ return nullptr;
+ }
+ return GUniquePtr<GtkPaperSize>(sPtr(aIppName, aWidth, aHeight));
+}
+
+static bool PaperSizeAlmostEquals(GtkPaperSize* aSize,
+ GtkPaperSize* aOtherSize) {
+ const double kEpsilon = 1.0; // millimetres
+ // GTK stores sizes internally in millimetres so just use that.
+ if (fabs(gtk_paper_size_get_height(aSize, GTK_UNIT_MM) -
+ gtk_paper_size_get_height(aOtherSize, GTK_UNIT_MM)) > kEpsilon) {
+ return false;
+ }
+ if (fabs(gtk_paper_size_get_width(aSize, GTK_UNIT_MM) -
+ gtk_paper_size_get_width(aOtherSize, GTK_UNIT_MM)) > kEpsilon) {
+ return false;
+ }
+ return true;
+}
+
+// Prefer the ppd name because some printers don't deal well even with standard
+// ipp names.
+static GUniquePtr<GtkPaperSize> PpdSizeFromIppName(const gchar* aIppName) {
+ static constexpr struct {
+ const char* mCups;
+ const char* mGtk;
+ } kMap[] = {
+ {CUPS_MEDIA_A3, GTK_PAPER_NAME_A3},
+ {CUPS_MEDIA_A4, GTK_PAPER_NAME_A4},
+ {CUPS_MEDIA_A5, GTK_PAPER_NAME_A5},
+ {CUPS_MEDIA_LETTER, GTK_PAPER_NAME_LETTER},
+ {CUPS_MEDIA_LEGAL, GTK_PAPER_NAME_LEGAL},
+ // Other gtk sizes with no standard CUPS constant: _EXECUTIVE and _B5
+ };
+
+ for (const auto& entry : kMap) {
+ if (!strcmp(entry.mCups, aIppName)) {
+ return GUniquePtr<GtkPaperSize>(gtk_paper_size_new(entry.mGtk));
+ }
+ }
+
+ return nullptr;
+}
+
+// This is a horrible workaround for some printer driver bugs that treat custom
+// page sizes different to standard ones. If our paper object matches one of a
+// standard one, use a standard paper size object instead.
+//
+// We prefer ppd to ipp to custom sizes.
+//
+// See bug 414314, bug 1691798, and bug 1717292 for more info.
+static GUniquePtr<GtkPaperSize> GetStandardGtkPaperSize(
+ GtkPaperSize* aGeckoPaperSize) {
+ // We should get an ipp name from cups, try to get a ppd from that first.
+ const gchar* geckoName = gtk_paper_size_get_name(aGeckoPaperSize);
+ if (auto ppd = PpdSizeFromIppName(geckoName)) {
+ return ppd;
+ }
+
+ // We try gtk_paper_size_new_from_ipp next, because even though
+ // gtk_paper_size_new tries to deal with ipp, it has some rounding issues that
+ // the ipp equivalent doesn't have, see
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/3685.
+ if (auto ipp = GtkPaperSizeFromIpp(
+ geckoName, gtk_paper_size_get_width(aGeckoPaperSize, GTK_UNIT_POINTS),
+ gtk_paper_size_get_height(aGeckoPaperSize, GTK_UNIT_POINTS))) {
+ if (!gtk_paper_size_is_custom(ipp.get())) {
+ if (auto ppd = PpdSizeFromIppName(gtk_paper_size_get_name(ipp.get()))) {
+ return ppd;
+ }
+ return ipp;
+ }
+ }
+
+ GUniquePtr<GtkPaperSize> size(gtk_paper_size_new(geckoName));
+ // gtk_paper_size_is_equal compares just paper names. The name in Gecko
+ // might come from CUPS, which is an ipp size, and gets normalized by gtk.
+ // So check also for the same actual paper size.
+ if (gtk_paper_size_is_equal(size.get(), aGeckoPaperSize) ||
+ PaperSizeAlmostEquals(aGeckoPaperSize, size.get())) {
+ return size;
+ }
+
+ // Not the same after all, so use our custom paper sizes instead.
+ return nullptr;
+}
+
+/** -------------------------------------------------------
+ * Initialize the nsDeviceContextSpecGTK
+ * @update dc 2/15/98
+ * @update syd 3/2/99
+ */
+NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIPrintSettings* aPS,
+ bool aIsPrintPreview) {
+ RefPtr<nsPrintSettingsGTK> settings = do_QueryObject(aPS);
+ if (!settings) {
+ return NS_ERROR_NO_INTERFACE;
+ }
+ mPrintSettings = aPS;
+
+ mGtkPrintSettings = settings->GetGtkPrintSettings();
+ mGtkPageSetup = settings->GetGtkPageSetup();
+
+ GtkPaperSize* geckoPaperSize = gtk_page_setup_get_paper_size(mGtkPageSetup);
+ GUniquePtr<GtkPaperSize> gtkPaperSize =
+ GetStandardGtkPaperSize(geckoPaperSize);
+
+ mGtkPageSetup = gtk_page_setup_copy(mGtkPageSetup);
+ mGtkPrintSettings = gtk_print_settings_copy(mGtkPrintSettings);
+
+ if (!aPS->GetPrintInColor() && StaticPrefs::print_cups_monochrome_enabled()) {
+ for (const auto& setting : kKnownMonochromeSettings) {
+ gtk_print_settings_set(mGtkPrintSettings, setting.mKey, setting.mValue);
+ }
+ auto applySetting = [&](const nsACString& aKey, const nsACString& aVal) {
+ nsAutoCString extra;
+ extra.AppendASCII("cups-");
+ extra.Append(aKey);
+ gtk_print_settings_set(mGtkPrintSettings, extra.get(),
+ nsAutoCString(aVal).get());
+ };
+ nsPrinterCUPS::ForEachExtraMonochromeSetting(applySetting);
+ }
+
+ GtkPaperSize* properPaperSize =
+ gtkPaperSize ? gtkPaperSize.get() : geckoPaperSize;
+ gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize);
+ gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup,
+ properPaperSize);
+ return NS_OK;
+}
+
+static void print_callback(GtkPrintJob* aJob, gpointer aData,
+ const GError* aError) {
+ g_object_unref(aJob);
+ ((nsIFile*)aData)->Remove(false);
+}
+
+/* static */
+gboolean nsDeviceContextSpecGTK::PrinterEnumerator(GtkPrinter* aPrinter,
+ gpointer aData) {
+ nsDeviceContextSpecGTK* spec = (nsDeviceContextSpecGTK*)aData;
+
+ if (spec->mHasEnumerationFoundAMatch) {
+ // We're already done, but we're letting the enumeration run its course,
+ // to avoid a GTK bug.
+ return FALSE;
+ }
+
+ // Find the printer whose name matches the one inside the settings.
+ nsString printerName;
+ nsresult rv = spec->mPrintSettings->GetPrinterName(printerName);
+ if (NS_SUCCEEDED(rv) && !printerName.IsVoid()) {
+ NS_ConvertUTF16toUTF8 requestedName(printerName);
+ const char* currentName = gtk_printer_get_name(aPrinter);
+ if (requestedName.Equals(currentName)) {
+ nsPrintSettingsGTK::From(spec->mPrintSettings)->SetGtkPrinter(aPrinter);
+
+ // Bug 1145916 - attempting to kick off a print job for this printer
+ // during this tick of the event loop will result in the printer backend
+ // misunderstanding what the capabilities of the printer are due to a
+ // GTK bug (https://bugzilla.gnome.org/show_bug.cgi?id=753041). We
+ // sidestep this by deferring the print to the next tick.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsDeviceContextSpecGTK::StartPrintJob", spec,
+ &nsDeviceContextSpecGTK::StartPrintJob));
+
+ // We're already done, but we need to let the enumeration run its course,
+ // to avoid a GTK bug. So we record that we've found a match and
+ // then return FALSE.
+ // TODO: If/when we can be sure that GTK handles this OK, we could
+ // return TRUE to avoid some needless enumeration.
+ spec->mHasEnumerationFoundAMatch = true;
+ return FALSE;
+ }
+ }
+
+ // We haven't found it yet - keep searching...
+ return FALSE;
+}
+
+void nsDeviceContextSpecGTK::StartPrintJob() {
+ GtkPrintJob* job = gtk_print_job_new(
+ mTitle.get(), nsPrintSettingsGTK::From(mPrintSettings)->GetGtkPrinter(),
+ mGtkPrintSettings, mGtkPageSetup);
+
+ if (!gtk_print_job_set_source_file(job, mSpoolName.get(), nullptr)) return;
+
+ // Now gtk owns the print job, and will be released via our callback.
+ gtk_print_job_send(job, print_callback, mSpoolFile.forget().take(),
+ [](gpointer aData) {
+ auto* spoolFile = static_cast<nsIFile*>(aData);
+ NS_RELEASE(spoolFile);
+ });
+}
+
+void nsDeviceContextSpecGTK::EnumeratePrinters() {
+ mHasEnumerationFoundAMatch = false;
+ gtk_enumerate_printers(&nsDeviceContextSpecGTK::PrinterEnumerator, this,
+ nullptr, TRUE);
+}
+
+NS_IMETHODIMP
+nsDeviceContextSpecGTK::BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) {
+ // Print job names exceeding 255 bytes are safe with GTK version 3.18.2 or
+ // newer. This is a workaround for old GTK.
+ if (gtk_check_version(3, 18, 2) != nullptr) {
+ PrintTarget::AdjustPrintJobNameForIPP(aTitle, mTitle);
+ } else {
+ CopyUTF16toUTF8(aTitle, mTitle);
+ }
+
+ return NS_OK;
+}
+
+RefPtr<PrintEndDocumentPromise> nsDeviceContextSpecGTK::EndDocument() {
+ switch (mPrintSettings->GetOutputDestination()) {
+ case nsIPrintSettings::kOutputDestinationPrinter: {
+ // At this point, we might have a GtkPrinter set up in nsPrintSettingsGTK,
+ // or we might not. In the single-process case, we probably will, as this
+ // is populated by the print settings dialog, or set to the default
+ // printer.
+ // In the multi-process case, we proxy the print settings dialog over to
+ // the parent process, and only get the name of the printer back on the
+ // content process side. In that case, we need to enumerate the printers
+ // on the content side, and find a printer with a matching name.
+
+ if (nsPrintSettingsGTK::From(mPrintSettings)->GetGtkPrinter()) {
+ // We have a printer, so we can print right away.
+ StartPrintJob();
+ } else {
+ // We don't have a printer. We have to enumerate the printers and find
+ // one with a matching name.
+ NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsDeviceContextSpecGTK::EnumeratePrinters", this,
+ &nsDeviceContextSpecGTK::EnumeratePrinters));
+ }
+ break;
+ }
+ case nsIPrintSettings::kOutputDestinationFile: {
+ // Handle print-to-file ourselves for the benefit of embedders
+ nsString targetPath;
+ nsCOMPtr<nsIFile> destFile;
+ mPrintSettings->GetToFileName(targetPath);
+
+ nsresult rv =
+ NS_NewLocalFile(targetPath, false, getter_AddRefs(destFile));
+ if (NS_FAILED(rv)) {
+ return PrintEndDocumentPromise::CreateAndReject(rv, __func__);
+ }
+
+ return nsIDeviceContextSpec::EndDocumentAsync(
+ __func__,
+ [destFile = std::move(destFile),
+ spoolFile = std::move(mSpoolFile)]() -> nsresult {
+ nsAutoString destLeafName;
+ auto rv = destFile->GetLeafName(destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destDir;
+ rv = destFile->GetParent(getter_AddRefs(destDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = spoolFile->MoveTo(destDir, destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This is the standard way to get the UNIX umask. Ugh.
+ mode_t mask = umask(0);
+ umask(mask);
+ // If you're not familiar with umasks, they contain the bits of what
+ // NOT to set in the permissions (thats because files and
+ // directories have different numbers of bits for their permissions)
+ destFile->SetPermissions(0666 & ~(mask));
+ return NS_OK;
+ });
+
+ break;
+ }
+ case nsIPrintSettings::kOutputDestinationStream:
+ // Nothing to do, handled in MakePrintTarget.
+ MOZ_ASSERT(!mSpoolFile);
+ break;
+ }
+ return PrintEndDocumentPromise::CreateAndResolve(true, __func__);
+}
diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h
new file mode 100644
index 0000000000..df5552c36c
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDeviceContextSpecGTK_h___
+#define nsDeviceContextSpecGTK_h___
+
+struct JSContext;
+
+#include "nsIDeviceContextSpec.h"
+#include "nsIPrinterList.h"
+#include "nsIPrintSettings.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/gfx/PrintPromise.h"
+
+#include "nsCRT.h" /* should be <limits.h>? */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+
+#define NS_PORTRAIT 0
+#define NS_LANDSCAPE 1
+
+class nsPrintSettingsGTK;
+
+class nsDeviceContextSpecGTK : public nsIDeviceContextSpec {
+ public:
+ nsDeviceContextSpecGTK();
+
+ NS_DECL_ISUPPORTS
+
+ already_AddRefed<PrintTarget> MakePrintTarget() final;
+
+ NS_IMETHOD Init(nsIPrintSettings* aPS, bool aIsPrintPreview) override;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ RefPtr<mozilla::gfx::PrintEndDocumentPromise> EndDocument() override;
+ NS_IMETHOD BeginPage(const IntSize& aSizeInPoints) override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ protected:
+ virtual ~nsDeviceContextSpecGTK();
+ GtkPrintSettings* mGtkPrintSettings;
+ GtkPageSetup* mGtkPageSetup;
+
+ nsCString mSpoolName;
+ nsCOMPtr<nsIFile> mSpoolFile;
+ nsCString mTitle;
+ // Helper for EnumeratePrinters / PrinterEnumerator:
+ bool mHasEnumerationFoundAMatch = false;
+
+ private:
+ void EnumeratePrinters();
+ void StartPrintJob();
+ static gboolean PrinterEnumerator(GtkPrinter* aPrinter, gpointer aData);
+};
+
+#endif /* !nsDeviceContextSpecGTK_h___ */
diff --git a/widget/gtk/nsDragService.cpp b/widget/gtk/nsDragService.cpp
new file mode 100644
index 0000000000..df0965b5e4
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2756 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDragService.h"
+#include "nsArrayUtils.h"
+#include "nsIObserverService.h"
+#include "nsWidgetsCID.h"
+#include "nsWindow.h"
+#include "nsSystemInfo.h"
+#include "nsXPCOM.h"
+#include "nsICookieJarSettings.h"
+#include "nsISupportsPrimitives.h"
+#include "nsIIOService.h"
+#include "nsIFileURL.h"
+#include "nsNetUtil.h"
+#include "mozilla/Logging.h"
+#include "nsTArray.h"
+#include "nsPrimitiveHelpers.h"
+#include "prtime.h"
+#include "prthread.h"
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include "nsCRT.h"
+#include "mozilla/BasicEvents.h"
+#include "mozilla/Services.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "GRefPtr.h"
+#include "nsAppShell.h"
+
+#ifdef MOZ_X11
+# include "gfxXlibSurface.h"
+#endif
+#include "gfxContext.h"
+#include "nsImageToPixbuf.h"
+#include "nsPresContext.h"
+#include "nsIContent.h"
+#include "mozilla/dom/Document.h"
+#include "nsViewManager.h"
+#include "nsIFrame.h"
+#include "nsGtkUtils.h"
+#include "nsGtkKeyUtils.h"
+#include "mozilla/gfx/2D.h"
+#include "gfxPlatform.h"
+#include "ScreenHelperGTK.h"
+#include "nsArrayUtils.h"
+#include "nsStringStream.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsEscape.h"
+#include "nsString.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+// The maximum time to wait for a "drag_received" arrived in microseconds.
+#define NS_DND_TIMEOUT 1000000
+
+// The maximum time to wait before temporary files resulting
+// from drag'n'drop events will be removed in miliseconds.
+// It's set to 5 min as the file has to be present some time after drop
+// event to give target application time to get the data.
+// (A target application can throw a dialog to ask user what to do with
+// the data and will access the tmp file after user action.)
+#define NS_DND_TMP_CLEANUP_TIMEOUT (1000 * 60 * 5)
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+
+// This sets how opaque the drag image is
+#define DRAG_IMAGE_ALPHA_LEVEL 0.5
+
+#ifdef MOZ_LOGGING
+extern mozilla::LazyLogModule gWidgetDragLog;
+# define LOGDRAGSERVICE(str, ...) \
+ MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, \
+ ("[Depth %d]: " str, GetLoopDepth(), ##__VA_ARGS__))
+# define LOGDRAGSERVICESTATIC(str, ...) \
+ MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, (str, ##__VA_ARGS__))
+#else
+# define LOGDRAGSERVICE(...)
+#endif
+
+// Helper class to block native events processing.
+class MOZ_STACK_CLASS AutoSuspendNativeEvents {
+ public:
+ AutoSuspendNativeEvents() {
+ mAppShell = do_GetService(NS_APPSHELL_CID);
+ mAppShell->SuspendNative();
+ }
+ ~AutoSuspendNativeEvents() { mAppShell->ResumeNative(); }
+
+ private:
+ nsCOMPtr<nsIAppShell> mAppShell;
+};
+
+// data used for synthetic periodic motion events sent to the source widget
+// grabbing real events for the drag.
+static guint sMotionEventTimerID;
+
+static GdkEvent* sMotionEvent;
+static GUniquePtr<GdkEvent> TakeMotionEvent() {
+ GUniquePtr<GdkEvent> event(sMotionEvent);
+ sMotionEvent = nullptr;
+ return event;
+}
+static void SetMotionEvent(GUniquePtr<GdkEvent> aEvent) {
+ TakeMotionEvent();
+ sMotionEvent = aEvent.release();
+}
+
+static GtkWidget* sGrabWidget;
+
+static constexpr nsLiteralString kDisallowedExportedSchemes[] = {
+ u"about"_ns, u"blob"_ns, u"cached-favicon"_ns,
+ u"chrome"_ns, u"imap"_ns, u"javascript"_ns,
+ u"mailbox"_ns, u"news"_ns, u"page-icon"_ns,
+ u"resource"_ns, u"view-source"_ns, u"moz-extension"_ns,
+ u"moz-page-thumb"_ns,
+};
+
+// _NETSCAPE_URL is similar to text/uri-list type.
+// Format is UTF8: URL + "\n" + title.
+// While text/uri-list tells target application to fetch, copy and store data
+// from URL, _NETSCAPE_URL suggest to create a link to the target.
+// Also _NETSCAPE_URL points to only one item while text/uri-list can point to
+// multiple ones.
+static const char gMozUrlType[] = "_NETSCAPE_URL";
+static const char gMimeListType[] = "application/x-moz-internal-item-list";
+static const char gTextUriListType[] = "text/uri-list";
+static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8";
+static const char gXdndDirectSaveType[] = "XdndDirectSave0";
+static const char gTabDropType[] = "application/x-moz-tabbrowser-tab";
+static const char gPortalFile[] = "application/vnd.portal.files";
+static const char gPortalFileTransfer[] = "application/vnd.portal.filetransfer";
+
+// See https://docs.gtk.org/gtk3/enum.DragResult.html
+static const char kGtkDragResults[][100]{
+ "GTK_DRAG_RESULT_SUCCESS", "GTK_DRAG_RESULT_NO_TARGET",
+ "GTK_DRAG_RESULT_USER_CANCELLED", "GTK_DRAG_RESULT_TIMEOUT_EXPIRED",
+ "GTK_DRAG_RESULT_GRAB_BROKEN", "GTK_DRAG_RESULT_ERROR"};
+
+static void invisibleSourceDragBegin(GtkWidget* aWidget,
+ GdkDragContext* aContext, gpointer aData);
+
+static void invisibleSourceDragEnd(GtkWidget* aWidget, GdkDragContext* aContext,
+ gpointer aData);
+
+static gboolean invisibleSourceDragFailed(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ gint aResult, gpointer aData);
+
+static void invisibleSourceDragDataGet(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData);
+
+nsDragService::nsDragService()
+ : mScheduledTask(eDragTaskNone),
+ mTaskSource(0),
+ mScheduledTaskIsRunning(false),
+ mCachedDragContext() {
+ // We have to destroy the hidden widget before the event loop stops
+ // running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "quit-application", false);
+
+ // our hidden source widget
+ // Using an offscreen window works around bug 983843.
+ mHiddenWidget = gtk_offscreen_window_new();
+ // make sure that the widget is realized so that
+ // we can use it as a drag source.
+ gtk_widget_realize(mHiddenWidget);
+ // hook up our internal signals so that we can get some feedback
+ // from our drag source
+ g_signal_connect(mHiddenWidget, "drag_begin",
+ G_CALLBACK(invisibleSourceDragBegin), this);
+ g_signal_connect(mHiddenWidget, "drag_data_get",
+ G_CALLBACK(invisibleSourceDragDataGet), this);
+ g_signal_connect(mHiddenWidget, "drag_end",
+ G_CALLBACK(invisibleSourceDragEnd), this);
+ // drag-failed is available from GTK+ version 2.12
+ guint dragFailedID =
+ g_signal_lookup("drag-failed", G_TYPE_FROM_INSTANCE(mHiddenWidget));
+ if (dragFailedID) {
+ g_signal_connect_closure_by_id(
+ mHiddenWidget, dragFailedID, 0,
+ g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), this, nullptr),
+ FALSE);
+ }
+
+ // set up our logging module
+ mCanDrop = false;
+ mTargetDragDataReceived = false;
+ mTargetDragUris = nullptr;
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+ mTempFileTimerID = 0;
+ mEventLoopDepth = 0;
+ LOGDRAGSERVICE("nsDragService::nsDragService");
+}
+
+nsDragService::~nsDragService() {
+ LOGDRAGSERVICE("nsDragService::~nsDragService");
+ if (mTaskSource) g_source_remove(mTaskSource);
+ if (mTempFileTimerID) {
+ g_source_remove(mTempFileTimerID);
+ RemoveTempFiles();
+ }
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver)
+
+mozilla::StaticRefPtr<nsDragService> sDragServiceInstance;
+/* static */
+already_AddRefed<nsDragService> nsDragService::GetInstance() {
+ if (gfxPlatform::IsHeadless()) {
+ return nullptr;
+ }
+ if (!sDragServiceInstance) {
+ sDragServiceInstance = new nsDragService();
+ ClearOnShutdown(&sDragServiceInstance);
+ }
+
+ RefPtr<nsDragService> service = sDragServiceInstance.get();
+ return service.forget();
+}
+
+// nsIObserver
+
+NS_IMETHODIMP
+nsDragService::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "quit-application")) {
+ LOGDRAGSERVICE("nsDragService::Observe(\"quit-application\")");
+ if (mHiddenWidget) {
+ gtk_widget_destroy(mHiddenWidget);
+ mHiddenWidget = 0;
+ }
+ TargetResetData();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+// Support for periodic drag events
+
+// http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+// and the Xdnd protocol both recommend that drag events are sent periodically,
+// but GTK does not normally provide this.
+//
+// Here GTK is periodically stimulated by copies of the most recent mouse
+// motion events so as to send drag position messages to the destination when
+// appropriate (after it has received a status event from the previous
+// message).
+//
+// (If events were sent only on the destination side then the destination
+// would have no message to which it could reply with a drag status. Without
+// sending a drag status to the source, the destination would not be able to
+// change its feedback re whether it could accept the drop, and so the
+// source's behavior on drop will not be consistent.)
+
+static gboolean DispatchMotionEventCopy(gpointer aData) {
+ // Clear the timer id before OnSourceGrabEventAfter is called during event
+ // dispatch.
+ sMotionEventTimerID = 0;
+
+ GUniquePtr<GdkEvent> event = TakeMotionEvent();
+ // If there is no longer a grab on the widget, then the drag is over and
+ // there is no need to continue drag motion.
+ if (gtk_widget_has_grab(sGrabWidget)) {
+ gtk_propagate_event(sGrabWidget, event.get());
+ }
+
+ // Cancel this timer;
+ // We've already started another if the motion event was dispatched.
+ return FALSE;
+}
+
+static void OnSourceGrabEventAfter(GtkWidget* widget, GdkEvent* event,
+ gpointer user_data) {
+ // If there is no longer a grab on the widget, then the drag motion is
+ // over (though the data may not be fetched yet).
+ if (!gtk_widget_has_grab(sGrabWidget)) return;
+
+ if (event->type == GDK_MOTION_NOTIFY) {
+ SetMotionEvent(GUniquePtr<GdkEvent>(gdk_event_copy(event)));
+
+ // Update the cursor position. The last of these recorded gets used for
+ // the eDragEnd event.
+ nsDragService* dragService = static_cast<nsDragService*>(user_data);
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale,
+ event->motion.y_root * scale);
+ dragService->SetDragEndPoint(p);
+ } else if (sMotionEvent &&
+ (event->type == GDK_KEY_PRESS || event->type == GDK_KEY_RELEASE)) {
+ // Update modifier state from key events.
+ sMotionEvent->motion.state = event->key.state;
+ } else {
+ return;
+ }
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ }
+
+ // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
+ // and lower than GTK's idle source that sends drag position messages after
+ // motion-notify signals.
+ //
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // recommends an interval of 350ms +/- 200ms.
+ sMotionEventTimerID = g_timeout_add_full(
+ G_PRIORITY_DEFAULT_IDLE, 350, DispatchMotionEventCopy, nullptr, nullptr);
+}
+
+static GtkWindow* GetGtkWindow(dom::Document* aDocument) {
+ if (!aDocument) return nullptr;
+
+ PresShell* presShell = aDocument->GetPresShell();
+ if (!presShell) {
+ return nullptr;
+ }
+
+ RefPtr<nsViewManager> vm = presShell->GetViewManager();
+ if (!vm) return nullptr;
+
+ nsCOMPtr<nsIWidget> widget = vm->GetRootWidget();
+ if (!widget) return nullptr;
+
+ GtkWidget* gtkWidget = static_cast<nsWindow*>(widget.get())->GetGtkWidget();
+ if (!gtkWidget) return nullptr;
+
+ GtkWidget* toplevel = nullptr;
+ toplevel = gtk_widget_get_toplevel(gtkWidget);
+ if (!GTK_IS_WINDOW(toplevel)) return nullptr;
+
+ return GTK_WINDOW(toplevel);
+}
+
+// nsIDragService
+
+NS_IMETHODIMP
+nsDragService::InvokeDragSession(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
+ nsICookieJarSettings* aCookieJarSettings, nsIArray* aArrayTransferables,
+ uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
+ LOGDRAGSERVICE("nsDragService::InvokeDragSession");
+
+ // If the previous source drag has not yet completed, signal handlers need
+ // to be removed from sGrabWidget and dragend needs to be dispatched to
+ // the source node, but we can't call EndDragSession yet because we don't
+ // know whether or not the drag succeeded.
+ if (mSourceNode) return NS_ERROR_NOT_AVAILABLE;
+
+ return nsBaseDragService::InvokeDragSession(
+ aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aArrayTransferables,
+ aActionType, aContentPolicyType);
+}
+
+// nsBaseDragService
+nsresult nsDragService::InvokeDragSessionImpl(
+ nsIArray* aArrayTransferables, const Maybe<CSSIntRegion>& aRegion,
+ uint32_t aActionType) {
+ // make sure that we have an array of transferables to use
+ if (!aArrayTransferables) return NS_ERROR_INVALID_ARG;
+ // set our reference to the transferables. this will also addref
+ // the transferables since we're going to hang onto this beyond the
+ // length of this call
+ mSourceDataItems = aArrayTransferables;
+
+ LOGDRAGSERVICE("nsDragService::InvokeDragSessionImpl");
+
+ GdkDevice* device = widget::GdkGetPointer();
+ GdkWindow* originGdkWindow = nullptr;
+ if (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol()) {
+ originGdkWindow =
+ gdk_device_get_window_at_position(device, nullptr, nullptr);
+ // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/6385
+ // Check we have GdkWindow drag source.
+ if (!originGdkWindow) {
+ NS_WARNING(
+ "nsDragService::InvokeDragSessionImpl(): Missing origin GdkWindow!");
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // get the list of items we offer for drags
+ GtkTargetList* sourceList = GetSourceList();
+
+ if (!sourceList) return NS_OK;
+
+ // save our action type
+ GdkDragAction action = GDK_ACTION_DEFAULT;
+
+ if (aActionType & DRAGDROP_ACTION_COPY)
+ action = (GdkDragAction)(action | GDK_ACTION_COPY);
+ if (aActionType & DRAGDROP_ACTION_MOVE)
+ action = (GdkDragAction)(action | GDK_ACTION_MOVE);
+ if (aActionType & DRAGDROP_ACTION_LINK)
+ action = (GdkDragAction)(action | GDK_ACTION_LINK);
+
+ GdkEvent* existingEvent = widget::GetLastMousePressEvent();
+ GdkEvent fakeEvent;
+ if (!existingEvent) {
+ // Create a fake event for the drag so we can pass the time (so to speak).
+ // If we don't do this, then, when the timestamp for the pending button
+ // release event is used for the ungrab, the ungrab can fail due to the
+ // timestamp being _earlier_ than CurrentTime.
+ memset(&fakeEvent, 0, sizeof(GdkEvent));
+ fakeEvent.type = GDK_BUTTON_PRESS;
+ fakeEvent.button.window = gtk_widget_get_window(mHiddenWidget);
+ fakeEvent.button.time = nsWindow::GetLastUserInputTime();
+ fakeEvent.button.device = device;
+ }
+
+ // Put the drag widget in the window group of the source node so that the
+ // gtk_grab_add during gtk_drag_begin is effective.
+ // gtk_window_get_group(nullptr) returns the default window group.
+ GtkWindowGroup* window_group =
+ gtk_window_get_group(GetGtkWindow(mSourceDocument));
+ gtk_window_group_add_window(window_group, GTK_WINDOW(mHiddenWidget));
+
+ // start our drag.
+ GdkDragContext* context = gtk_drag_begin_with_coordinates(
+ mHiddenWidget, sourceList, action, 1,
+ existingEvent ? existingEvent : &fakeEvent, -1, -1);
+
+ if (originGdkWindow) {
+ mSourceWindow = nsWindow::GetWindow(originGdkWindow);
+ if (mSourceWindow) {
+ mSourceWindow->SetDragSource(context);
+ }
+ }
+
+ LOGDRAGSERVICE(" GdkDragContext [%p] nsWindow [%p]", context,
+ mSourceWindow.get());
+
+ nsresult rv;
+ if (context) {
+ StartDragSession();
+
+ // GTK uses another hidden window for receiving mouse events.
+ sGrabWidget = gtk_window_group_get_current_grab(window_group);
+ if (sGrabWidget) {
+ g_object_ref(sGrabWidget);
+ // Only motion and key events are required but connect to
+ // "event-after" as this is never blocked by other handlers.
+ g_signal_connect(sGrabWidget, "event-after",
+ G_CALLBACK(OnSourceGrabEventAfter), this);
+ }
+ // We don't have a drag end point yet.
+ mEndDragPoint = LayoutDeviceIntPoint(-1, -1);
+ rv = NS_OK;
+ } else {
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_list_unref(sourceList);
+
+ return rv;
+}
+
+bool nsDragService::SetAlphaPixmap(SourceSurface* aSurface,
+ GdkDragContext* aContext, int32_t aXOffset,
+ int32_t aYOffset,
+ const LayoutDeviceIntRect& dragRect) {
+ GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget);
+
+ // Transparent drag icons need, like a lot of transparency-related things,
+ // a compositing X window manager
+ if (!gdk_screen_is_composited(screen)) {
+ return false;
+ }
+
+#ifdef cairo_image_surface_create
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+
+ // TODO: grab X11 pixmap or image data instead of expensive readback.
+ cairo_surface_t* surf = cairo_image_surface_create(
+ CAIRO_FORMAT_ARGB32, dragRect.width, dragRect.height);
+ if (!surf) return false;
+
+ RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData(
+ cairo_image_surface_get_data(surf),
+ nsIntSize(dragRect.width, dragRect.height),
+ cairo_image_surface_get_stride(surf), SurfaceFormat::B8G8R8A8);
+ if (!dt) return false;
+
+ dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height));
+ dt->DrawSurface(
+ aSurface, Rect(0, 0, dragRect.width, dragRect.height),
+ Rect(0, 0, dragRect.width, dragRect.height), DrawSurfaceOptions(),
+ DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE));
+
+ cairo_surface_mark_dirty(surf);
+ cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset);
+
+ // Ensure that the surface is drawn at the correct scale on HiDPI displays.
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*, double, double))dlsym(
+ RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ if (sCairoSurfaceSetDeviceScalePtr) {
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ sCairoSurfaceSetDeviceScalePtr(surf, scale, scale);
+ }
+
+ gtk_drag_set_icon_surface(aContext, surf);
+ cairo_surface_destroy(surf);
+ return true;
+}
+
+NS_IMETHODIMP
+nsDragService::StartDragSession() {
+ LOGDRAGSERVICE("nsDragService::StartDragSession");
+ mTempFileUrls.Clear();
+ return nsBaseDragService::StartDragSession();
+}
+
+bool nsDragService::RemoveTempFiles() {
+ LOGDRAGSERVICE("nsDragService::RemoveTempFiles");
+
+ // We can not delete the temporary files immediately after the
+ // drag has finished, because the target application might have not
+ // copied the temporary file yet. The Qt toolkit does not provide a
+ // way to mark a drop as finished in an asynchronous way, so most
+ // Qt based applications do send the dnd_finished signal before they
+ // have actually accessed the data from the temporary file.
+ // (https://bugreports.qt.io/browse/QTBUG-5441)
+ //
+ // To work also with these applications we collect all temporary
+ // files in mTemporaryFiles array and remove them here in the timer event.
+ auto files = std::move(mTemporaryFiles);
+ for (nsIFile* file : files) {
+#ifdef MOZ_LOGGING
+ if (MOZ_LOG_TEST(gWidgetDragLog, LogLevel::Debug)) {
+ nsAutoCString path;
+ if (NS_SUCCEEDED(file->GetNativePath(path))) {
+ LOGDRAGSERVICE(" removing %s", path.get());
+ }
+ }
+#endif
+ file->Remove(/* recursive = */ true);
+ }
+ MOZ_ASSERT(mTemporaryFiles.IsEmpty());
+ mTempFileTimerID = 0;
+ // Return false to remove the timer added by g_timeout_add_full().
+ return false;
+}
+
+gboolean nsDragService::TaskRemoveTempFiles(gpointer data) {
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ return dragService->RemoveTempFiles();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ LOGDRAGSERVICE("nsDragService::EndDragSession(%p) %d",
+ mTargetDragContext.get(), aDoneDrag);
+
+ if (sGrabWidget) {
+ g_signal_handlers_disconnect_by_func(
+ sGrabWidget, FuncToGpointer(OnSourceGrabEventAfter), this);
+ g_object_unref(sGrabWidget);
+ sGrabWidget = nullptr;
+
+ if (sMotionEventTimerID) {
+ g_source_remove(sMotionEventTimerID);
+ sMotionEventTimerID = 0;
+ }
+ if (sMotionEvent) {
+ TakeMotionEvent();
+ }
+ }
+
+ // unset our drag action
+ SetDragAction(DRAGDROP_ACTION_NONE);
+
+ // start timer to remove temporary files
+ if (mTemporaryFiles.Count() > 0 && !mTempFileTimerID) {
+ LOGDRAGSERVICE(" queue removing of temporary files");
+ // |this| won't be used after nsDragService delete because the timer is
+ // removed in the nsDragService destructor.
+ mTempFileTimerID =
+ g_timeout_add(NS_DND_TMP_CLEANUP_TIMEOUT, TaskRemoveTempFiles, this);
+ mTempFileUrls.Clear();
+ }
+
+ // We're done with the drag context.
+ if (mSourceWindow) {
+ mSourceWindow->SetDragSource(nullptr);
+ mSourceWindow = nullptr;
+ }
+ mTargetDragContextForRemote = nullptr;
+ mTargetWindow = nullptr;
+ mPendingWindow = nullptr;
+ mCachedDragContext = 0;
+
+ return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
+}
+
+// nsIDragSession
+NS_IMETHODIMP
+nsDragService::SetCanDrop(bool aCanDrop) {
+ LOGDRAGSERVICE("nsDragService::SetCanDrop %d", aCanDrop);
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetCanDrop(bool* aCanDrop) {
+ LOGDRAGSERVICE("nsDragService::GetCanDrop");
+ *aCanDrop = mCanDrop;
+ return NS_OK;
+}
+
+static void UTF16ToNewUTF8(const char16_t* aUTF16, uint32_t aUTF16Len,
+ char** aUTF8, uint32_t* aUTF8Len) {
+ nsDependentSubstring utf16(aUTF16, aUTF16Len);
+ *aUTF8 = ToNewUTF8String(utf16, aUTF8Len);
+}
+
+static void UTF8ToNewUTF16(const char* aUTF8, uint32_t aUTF8Len,
+ char16_t** aUTF16, uint32_t* aUTF16Len) {
+ nsDependentCSubstring utf8(aUTF8, aUTF8Len);
+ *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len);
+}
+
+// extract an item from text/uri-list formatted data and convert it to
+// unicode.
+static void GetTextUriListItem(const char* data, uint32_t datalen,
+ uint32_t aItemIndex, char16_t** convertedText,
+ uint32_t* convertedTextLen) {
+ const char* p = data;
+ const char* endPtr = p + datalen;
+ unsigned int count = 0;
+
+ *convertedText = nullptr;
+ while (p < endPtr) {
+ // skip whitespace (if any)
+ while (p < endPtr && *p != '\0' && isspace(*p)) p++;
+ // if we aren't at the end of the line, we have a url
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
+ // this is the item we are after ...
+ if (aItemIndex + 1 == count) {
+ const char* q = p;
+ while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') q++;
+ UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen);
+ break;
+ }
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n') p++;
+ p++; // skip the actual newline as well.
+ }
+
+ // didn't find the desired item, so just pass the whole lot
+ if (!*convertedText) {
+ UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen);
+ }
+}
+
+// Spins event loop, called from JS.
+// Can lead to another round of drag_motion events.
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems) {
+ LOGDRAGSERVICE("nsDragService::GetNumDropItems");
+
+ if (!mTargetWidget) {
+ LOGDRAGSERVICE(
+ "*** warning: GetNumDropItems \
+ called without a valid target widget!\n");
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ bool isList = IsTargetContextList();
+ if (isList) {
+ if (!mSourceDataItems) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+ mSourceDataItems->GetLength(aNumItems);
+ } else {
+ // text/uri-list
+ GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ if (!gdkFlavor) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ nsTArray<nsCString> dragFlavors;
+ GetDragFlavors(dragFlavors);
+ GetTargetDragData(gdkFlavor, dragFlavors);
+
+ // application/vnd.portal.files
+ if (!mTargetDragUris) {
+ gdkFlavor = gdk_atom_intern(gPortalFile, FALSE);
+ if (!gdkFlavor) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ }
+
+ // application/vnd.portal.filetransfer
+ if (!mTargetDragUris) {
+ gdkFlavor = gdk_atom_intern(gPortalFileTransfer, FALSE);
+ if (!gdkFlavor) {
+ *aNumItems = 0;
+ return NS_OK;
+ }
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ }
+
+ if (mTargetDragUris) {
+ *aNumItems = g_strv_length(mTargetDragUris.get());
+ } else
+ *aNumItems = 1;
+ }
+ LOGDRAGSERVICE(" NumOfDropItems %d", *aNumItems);
+ return NS_OK;
+}
+
+void nsDragService::GetDragFlavors(nsTArray<nsCString>& aFlavors) {
+ for (GList* tmp = gdk_drag_context_list_targets(mTargetDragContext); tmp;
+ tmp = tmp->next) {
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ GUniquePtr<gchar> name(gdk_atom_name(atom));
+ if (!name) {
+ continue;
+ }
+ aFlavors.AppendElement(nsCString(name.get()));
+ }
+}
+
+static nsresult GetFileFromUri(const nsCString& aUri,
+ nsCOMPtr<nsIFile>& aFile) {
+ nsresult rv;
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ nsCOMPtr<nsIURI> fileURI;
+ rv = ioService->NewURI(aUri, nullptr, nullptr, getter_AddRefs(fileURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ rv = fileURL->GetFile(getter_AddRefs(aFile));
+ if (NS_SUCCEEDED(rv)) {
+ return NS_OK;
+ }
+ }
+ }
+ return rv;
+}
+
+nsresult GetReachableFileFromUriList(char** aFileUriList, uint32_t aItemIndex,
+ nsCOMPtr<nsIFile>& aFile) {
+ if (!aFileUriList || !(g_strv_length(aFileUriList) > aItemIndex) ||
+ !aFileUriList[aItemIndex]) {
+ return NS_ERROR_INVALID_ARG;
+ }
+ nsresult rv;
+ rv = GetFileFromUri(nsDependentCString(aFileUriList[aItemIndex]), aFile);
+ if (NS_SUCCEEDED(rv)) {
+ bool fileExists = false;
+ aFile->Exists(&fileExists);
+ if (fileExists) {
+ LOGDRAG(" good, file %s exists\n", aFileUriList[aItemIndex]);
+ return NS_OK;
+ }
+ }
+ LOGDRAG(" uri %s not reachable/not found\n", aFileUriList[aItemIndex]);
+ aFile = nullptr;
+ return NS_ERROR_FILE_NOT_FOUND;
+}
+
+// Spins event loop, called from JS.
+// Can lead to another round of drag_motion events.
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
+ LOGDRAGSERVICE("nsDragService::GetData(), index %d", aItemIndex);
+
+ // make sure that we have a transferable
+ if (!aTransferable) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ if (!mTargetWidget) {
+ LOGDRAGSERVICE(
+ "*** failed: GetData called without a valid target widget!\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ // get flavor list that includes all acceptable flavors (including
+ // ones obtained through conversion).
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" failed to get flavors, quit.");
+ return rv;
+ }
+
+ // check to see if this is an internal list
+ bool isList = IsTargetContextList();
+
+ if (isList) {
+ LOGDRAGSERVICE(" Process as a list...");
+ // find a matching flavor
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+ LOGDRAGSERVICE(" [%d] flavor is %s\n", i, flavorStr.get());
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item) continue;
+
+ nsCOMPtr<nsISupports> data;
+ LOGDRAGSERVICE(" trying to get transfer data for %s\n", flavorStr.get());
+ rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" failed.\n");
+ continue;
+ }
+ rv = aTransferable->SetTransferData(flavorStr.get(), data);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" fail to set transfer data into transferable!\n");
+ continue;
+ }
+ LOGDRAGSERVICE(" succeeded\n");
+ // ok, we got the data
+ return NS_OK;
+ }
+ // if we got this far, we failed
+ LOGDRAGSERVICE(" failed to match flavors\n");
+ return NS_ERROR_FAILURE;
+ }
+
+ nsTArray<nsCString> dragFlavors;
+ GetDragFlavors(dragFlavors);
+
+ // Now walk down the list of flavors. When we find one that is
+ // actually present, copy out the data into the transferable in that
+ // format. SetTransferData() implicitly handles conversions.
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+
+ GdkAtom gdkFlavor;
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
+ } else {
+ gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE);
+ }
+ LOGDRAGSERVICE(" we're getting data %s (gdk flavor %p)\n", flavorStr.get(),
+ gdkFlavor);
+ bool dataFound = false;
+ nsCOMPtr<nsIFile> file;
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
+ }
+
+ // application/vnd.portal.files
+ if (!file || !mTargetDragUris) {
+ LOGDRAGSERVICE(" file not found, proceed with %s flavor\n", gPortalFile);
+ gdkFlavor = gdk_atom_intern(gPortalFile, FALSE);
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
+ }
+ }
+
+ // application/vnd.portal.filetransfer
+ if (!file || !mTargetDragUris) {
+ LOGDRAGSERVICE(" file not found, proceed with %s flavor\n",
+ gPortalFileTransfer);
+ gdkFlavor = gdk_atom_intern(gPortalFileTransfer, FALSE);
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
+ }
+ }
+
+ // Conversion from application/x-moz-file to text/uri-list
+ if ((!file || !mTargetDragUris) && (flavorStr.EqualsLiteral(kFileMime))) {
+ LOGDRAGSERVICE(
+ " file not found, proceed with conversion %s => %s flavor\n",
+ kFileMime, gTextUriListType);
+
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ GetReachableFileFromUriList(mTargetDragUris.get(), aItemIndex, file);
+ }
+ }
+
+ if (file) {
+ LOGDRAGSERVICE(" from drag uris set as file %s - flavor: %s",
+ mTargetDragUris.get()[aItemIndex], flavorStr.get());
+ aTransferable->SetTransferData(flavorStr.get(), file);
+ return NS_OK;
+ }
+
+ if (mTargetDragData) {
+ LOGDRAGSERVICE(" dataFound = true\n");
+ dataFound = true;
+ } else {
+ LOGDRAGSERVICE(" dataFound = false, try conversions\n");
+ // If we are looking for text/plain, try again with non utf-8 text.
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ LOGDRAGSERVICE(" conversion %s => %s", kTextMime, kTextMime);
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ if (mTargetDragData) {
+ dataFound = true;
+ } // if plain text flavor present
+ } // if looking for text/plain
+
+ // if we are looking for text/x-moz-url and we failed to find
+ // it on the clipboard, try again with text/uri-list, and then
+ // _NETSCAPE_URL
+ if (flavorStr.EqualsLiteral(kURLMime)) {
+ LOGDRAGSERVICE(" conversion %s => %s", kURLMime, gTextUriListType);
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ if (mTargetDragData) {
+ const char* data = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ if (!dataFound) {
+ LOGDRAGSERVICE(" conversion %s => %s", kURLMime, gMozUrlType);
+ gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
+ GetTargetDragData(gdkFlavor, dragFlavors);
+ if (mTargetDragData) {
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
+ &convertedTextLen);
+ if (convertedText) {
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ }
+ }
+ }
+
+ } // else we try one last ditch effort to find our data
+
+ if (dataFound) {
+ LOGDRAGSERVICE(" actual data found %s\n",
+ GUniquePtr<gchar>(gdk_atom_name(gdkFlavor)).get());
+
+ if (flavorStr.EqualsLiteral(kTextMime)) {
+ // The text is in UTF-8, so convert the text into UTF-16
+ const char* text = static_cast<char*>(mTargetDragData);
+ NS_ConvertUTF8toUTF16 ucs2string(text, mTargetDragDataLen);
+ char16_t* convertedText = ToNewUnicode(ucs2string, mozilla::fallible);
+ if (convertedText) {
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = ucs2string.Length() * 2;
+ }
+ }
+
+ if (flavorStr.EqualsLiteral(kJPEGImageMime) ||
+ flavorStr.EqualsLiteral(kJPGImageMime) ||
+ flavorStr.EqualsLiteral(kPNGImageMime) ||
+ flavorStr.EqualsLiteral(kGIFImageMime)) {
+ LOGDRAGSERVICE(" saving as image %s\n", flavorStr.get());
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ Span((char*)mTargetDragData, mTargetDragDataLen),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ continue;
+ }
+
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ // the DOM only wants LF, so convert from MacOS line endings
+ // to DOM line endings.
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ flavorStr.EqualsLiteral(kRTFMime), &mTargetDragData,
+ reinterpret_cast<int*>(&mTargetDragDataLen));
+ }
+
+ // put it into the transferable.
+ nsCOMPtr<nsISupports> genericDataWrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ flavorStr, mTargetDragData, mTargetDragDataLen,
+ getter_AddRefs(genericDataWrapper));
+ aTransferable->SetTransferData(flavorStr.get(), genericDataWrapper);
+ // we found one, get out of this loop!
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ LOGDRAGSERVICE("nsDragService::IsDataFlavorSupported(%p) %s",
+ mTargetDragContext.get(), aDataFlavor);
+ if (!_retval) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ // set this to no by default
+ *_retval = false;
+
+ // check to make sure that we have a drag object set, here
+ if (!mTargetWidget) {
+ LOGDRAGSERVICE(
+ "*** warning: IsDataFlavorSupported called without a valid target "
+ "widget!\n");
+ return NS_OK;
+ }
+
+ // check to see if the target context is a list.
+ bool isList = IsTargetContextList();
+ // if it is, just look in the internal data since we are the source
+ // for it.
+ if (isList) {
+ LOGDRAGSERVICE(" It's a list");
+ uint32_t numDragItems = 0;
+ // if we don't have mDataItems we didn't start this drag so it's
+ // an external client trying to fool us.
+ if (!mSourceDataItems) {
+ LOGDRAGSERVICE(" quit");
+ return NS_OK;
+ }
+ mSourceDataItems->GetLength(&numDragItems);
+ LOGDRAGSERVICE(" drag items %d", numDragItems);
+ for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) {
+ nsCOMPtr<nsITransferable> currItem =
+ do_QueryElementAt(mSourceDataItems, itemIndex);
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ LOGDRAGSERVICE(" checking %s against %s\n", flavors[i].get(),
+ aDataFlavor);
+ if (flavors[i].Equals(aDataFlavor)) {
+ LOGDRAGSERVICE(" found.\n");
+ *_retval = true;
+ }
+ }
+ }
+ }
+ return NS_OK;
+ }
+
+ // check the target context vs. this flavor, one at a time
+ GList* tmp = nullptr;
+ if (mTargetDragContext) {
+ tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ }
+
+ for (; tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ GUniquePtr<gchar> name(gdk_atom_name(atom));
+ if (!name) {
+ continue;
+ }
+
+ if (strcmp(name.get(), aDataFlavor) == 0) {
+ *_retval = true;
+ }
+ // check for automatic text/uri-list -> text/x-moz-url mapping
+ else if (strcmp(name.get(), gTextUriListType) == 0 &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ *_retval = true;
+ }
+ // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
+ else if (strcmp(name.get(), gMozUrlType) == 0 &&
+ (strcmp(aDataFlavor, kURLMime) == 0)) {
+ *_retval = true;
+ }
+ // check for file portal
+ // If we're asked for kURLMime/kFileMime we can convert gPortalFile
+ // or gPortalFileTransfer to it.
+ else if ((strcmp(name.get(), gPortalFile) == 0 ||
+ strcmp(name.get(), gPortalFileTransfer) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ *_retval = true;
+ }
+ if (*_retval) {
+ LOGDRAGSERVICE(" supported, with converting %s => %s", name.get(),
+ aDataFlavor);
+ }
+ }
+
+ if (!*_retval) {
+ LOGDRAGSERVICE(" %s is not supported", aDataFlavor);
+ }
+
+ return NS_OK;
+}
+
+void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext,
+ guint aTime) {
+ LOGDRAGSERVICE("nsDragService::ReplyToDragMotion(%p) can drop %d",
+ aDragContext, mCanDrop);
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ LOGDRAGSERVICE(" set explicit action copy");
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ LOGDRAGSERVICE(" set explicit action link");
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ LOGDRAGSERVICE(" set explicit action none");
+ action = (GdkDragAction)0;
+ break;
+ default:
+ LOGDRAGSERVICE(" set explicit action move");
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ } else {
+ LOGDRAGSERVICE(" mCanDrop is false, disable drop");
+ }
+
+ // gdk_drag_status() is a kind of red herring here.
+ // It does not control final D&D operation type (copy/move) but controls
+ // drop/no-drop D&D state and default cursor type (copy/move).
+
+ // Actual D&D operation is determined by mDragAction which is set by
+ // SetDragAction() from UpdateDragAction() or gecko/layout.
+
+ // State passed to gdk_drag_status() sets default D&D cursor type
+ // which can be switched by key control (CTRL/SHIFT).
+ // If user changes D&D cursor (and D&D operation) we're notified by
+ // gdk_drag_context_get_selected_action() and update mDragAction.
+
+ // But if we pass mDragAction back to gdk_drag_status() the D&D operation
+ // becames locked and won't be returned when D&D modifiers (CTRL/SHIFT)
+ // are released.
+
+ // This gdk_drag_context_get_selected_action() -> gdk_drag_status() ->
+ // gdk_drag_context_get_selected_action() cycle happens on Wayland.
+ if (widget::GdkIsWaylandDisplay() && action == GDK_ACTION_COPY) {
+ LOGDRAGSERVICE(" Wayland: switch copy to move");
+ action = GDK_ACTION_MOVE;
+ }
+
+ LOGDRAGSERVICE(" gdk_drag_status() action %d", action);
+ gdk_drag_status(aDragContext, action, aTime);
+}
+
+void nsDragService::EnsureCachedDataValidForContext(
+ GdkDragContext* aDragContext) {
+ if (mCachedDragContext != (uintptr_t)aDragContext) {
+ mCachedUris.Clear();
+ mCachedData.Clear();
+ mCachedDragContext = (uintptr_t)aDragContext;
+ }
+}
+
+void nsDragService::TargetDataReceived(GtkWidget* aWidget,
+ GdkDragContext* aContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime) {
+ LOGDRAGSERVICE("nsDragService::TargetDataReceived(%p)", aContext);
+ TargetResetData();
+
+ EnsureCachedDataValidForContext(aContext);
+
+ mTargetDragDataReceived = true;
+
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ GUniquePtr<gchar> name(gdk_atom_name(target));
+ nsDependentCString flavor(name.get());
+
+ if (gtk_targets_include_uri(&target, 1)) {
+ GUniquePtr<gchar*> uris(gtk_selection_data_get_uris(aSelectionData));
+#ifdef MOZ_LOGGING
+ if (MOZ_LOG_TEST(gWidgetDragLog, mozilla::LogLevel::Debug)) {
+ gchar** uri = uris.get();
+ while (uri && *uri) {
+ LOGDRAGSERVICE(" got uri %s, MIME %s", *uri, flavor.get());
+ uri++;
+ }
+ }
+#endif
+ uris.swap(mTargetDragUris);
+ if (mTargetDragUris) {
+ mCachedUris.InsertOrUpdate(
+ flavor, GUniquePtr<gchar*>(g_strdupv(mTargetDragUris.get())));
+ } else {
+ LOGDRAGSERVICE("Failed to get uri list\n");
+ mCachedUris.InsertOrUpdate(flavor, GUniquePtr<gchar*>(nullptr));
+ }
+ return;
+ }
+
+ const guchar* data = gtk_selection_data_get_data(aSelectionData);
+ gint len = gtk_selection_data_get_length(aSelectionData);
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+
+ LOGDRAGSERVICE(" got data, len = %d", mTargetDragDataLen);
+
+ nsTArray<uint8_t> copy;
+ if (!copy.SetLength(len, fallible)) {
+ return;
+ }
+ memcpy(copy.Elements(), data, len);
+
+ mCachedData.InsertOrUpdate(flavor, std::move(copy));
+ } else {
+ LOGDRAGSERVICE("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen);
+ mCachedData.InsertOrUpdate(flavor, nsTArray<uint8_t>());
+ }
+ LOGDRAGSERVICE(" got data, MIME %s", flavor.get());
+}
+
+bool nsDragService::IsTargetContextList(void) {
+ bool retval = false;
+
+ // gMimeListType drags only work for drags within a single process. The
+ // gtk_drag_get_source_widget() function will return nullptr if the source
+ // of the drag is another app, so we use it to check if a gMimeListType
+ // drop will work or not.
+ if (mTargetDragContext &&
+ gtk_drag_get_source_widget(mTargetDragContext) == nullptr) {
+ return retval;
+ }
+
+ GList* tmp = nullptr;
+ if (mTargetDragContext) {
+ tmp = gdk_drag_context_list_targets(mTargetDragContext);
+ }
+
+ // walk the list of context targets and see if one of them is a list
+ // of items.
+ for (; tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ GUniquePtr<gchar> name(gdk_atom_name(atom));
+ if (name && !strcmp(name.get(), gMimeListType)) {
+ return true;
+ }
+ }
+
+ return retval;
+}
+
+// Spins event loop, called from eDragTaskMotion handler by
+// DispatchMotionEvents().
+// Can lead to another round of drag_motion events.
+void nsDragService::GetTargetDragData(GdkAtom aFlavor,
+ nsTArray<nsCString>& aDropFlavors) {
+ LOGDRAGSERVICE("nsDragService::GetTargetDragData(%p) '%s'\n",
+ mTargetDragContext.get(),
+ GUniquePtr<gchar>(gdk_atom_name(aFlavor)).get());
+
+ // reset our target data areas
+ TargetResetData();
+
+ GUniquePtr<gchar> name(gdk_atom_name(aFlavor));
+ nsDependentCString flavor(name.get());
+
+ // Return early when requested MIME is not offered by D&D.
+ if (!aDropFlavors.Contains(flavor)) {
+ LOGDRAGSERVICE(" %s is missing", flavor.get());
+ return;
+ }
+
+ if (mTargetDragContext) {
+ // We keep a copy of the requested data with the same life-time
+ // as mTargetDragContext.
+ // Especially with multiple items the same data is requested
+ // very often.
+ EnsureCachedDataValidForContext(mTargetDragContext);
+ if (auto cached = mCachedUris.Lookup(flavor)) {
+ LOGDRAGSERVICE(" using cached uri list for %s", flavor.get());
+
+ mTargetDragUris = GUniquePtr<gchar*>(g_strdupv(cached->get()));
+ mTargetDragDataReceived = true;
+ LOGDRAGSERVICE(" %s found in cache", flavor.get());
+ return;
+ }
+ if (auto cached = mCachedData.Lookup(flavor)) {
+ mTargetDragDataLen = cached->Length();
+ LOGDRAGSERVICE(" using cached data for %s, length is %d", flavor.get(),
+ mTargetDragDataLen);
+
+ if (mTargetDragDataLen) {
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, cached->Elements(), mTargetDragDataLen);
+ }
+
+ mTargetDragDataReceived = true;
+ LOGDRAGSERVICE(" %s found in cache", flavor.get());
+ return;
+ }
+
+ gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
+
+ LOGDRAGSERVICE(" about to start inner iteration.");
+ gtk_main_iteration();
+
+ PRTime entryTime = PR_Now();
+ while (!mTargetDragDataReceived && mDoingDrag) {
+ // check the number of iterations
+ LOGDRAGSERVICE(" doing iteration...\n");
+ PR_Sleep(PR_MillisecondsToInterval(10)); /* sleep for 10 ms/iteration */
+ if (PR_Now() - entryTime > NS_DND_TIMEOUT) {
+ LOGDRAGSERVICE(" failed to get D&D data in time!\n");
+ break;
+ }
+ gtk_main_iteration();
+ }
+ }
+
+#ifdef MOZ_LOGGING
+ if (mTargetDragUris || (mTargetDragDataLen && mTargetDragData)) {
+ LOGDRAGSERVICE(" %s got from system", flavor.get());
+ } else {
+ LOGDRAGSERVICE(" %s failed to get from system", flavor.get());
+ }
+#endif
+}
+
+void nsDragService::TargetResetData(void) {
+ mTargetDragDataReceived = false;
+ // make sure to free old data if we have to
+ mTargetDragUris = nullptr;
+ g_free(mTargetDragData);
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+static void TargetArrayAddTarget(nsTArray<GtkTargetEntry*>& aTargetArray,
+ const char* aTarget) {
+ GtkTargetEntry* target = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(aTarget);
+ target->flags = 0;
+ aTargetArray.AppendElement(target);
+ LOGDRAGSERVICESTATIC("adding target %s\n", aTarget);
+}
+
+static bool CanExportAsURLTarget(const char16_t* aURLData, uint32_t aURLLen) {
+ for (const nsLiteralString& disallowed : kDisallowedExportedSchemes) {
+ auto len = disallowed.AsString().Length();
+ if (len < aURLLen) {
+ if (!memcmp(disallowed.get(), aURLData,
+ /* len is char16_t char count */ len * 2)) {
+ LOGDRAGSERVICESTATIC("rejected URL scheme %s\n",
+ NS_ConvertUTF16toUTF8(disallowed).get());
+ return false;
+ }
+ }
+ }
+ return true;
+}
+
+GtkTargetList* nsDragService::GetSourceList(void) {
+ if (!mSourceDataItems) {
+ return nullptr;
+ }
+
+ nsTArray<GtkTargetEntry*> targetArray;
+ GtkTargetEntry* targets;
+ GtkTargetList* targetList = 0;
+ uint32_t targetCount = 0;
+ unsigned int numDragItems = 0;
+
+ mSourceDataItems->GetLength(&numDragItems);
+ LOGDRAGSERVICE(" numDragItems = %d", numDragItems);
+
+ // Check to see if we're dragging > 1 item.
+ if (numDragItems > 1) {
+ // as the Xdnd protocol only supports a single item (or is it just
+ // gtk's implementation?), we don't advertise all flavours listed
+ // in the nsITransferable.
+
+ // the application/x-moz-internal-item-list format, which preserves
+ // all information for drags within the same mozilla instance.
+ TargetArrayAddTarget(targetArray, gMimeListType);
+
+ // check what flavours are supported so we can decide what other
+ // targets to advertise.
+ nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
+
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ // check if text/x-moz-url is supported.
+ // If so, advertise
+ // text/uri-list.
+ if (flavors[i].EqualsLiteral(kURLMime)) {
+ TargetArrayAddTarget(targetArray, gTextUriListType);
+ break;
+ }
+ }
+ } // if item is a transferable
+ } else if (numDragItems == 1) {
+ nsCOMPtr<nsITransferable> currItem = do_QueryElementAt(mSourceDataItems, 0);
+ if (currItem) {
+ nsTArray<nsCString> flavors;
+ currItem->FlavorsTransferableCanExport(flavors);
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+
+ TargetArrayAddTarget(targetArray, flavorStr.get());
+
+ // If there is a file, add the text/uri-list type.
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ TargetArrayAddTarget(targetArray, gTextUriListType);
+ }
+ // Check to see if this is text/plain.
+ else if (flavorStr.EqualsLiteral(kTextMime)) {
+ TargetArrayAddTarget(targetArray, gTextPlainUTF8Type);
+ }
+ // Check to see if this is the x-moz-url type.
+ // If it is, add _NETSCAPE_URL
+ // this is a type used by everybody.
+ else if (flavorStr.EqualsLiteral(kURLMime)) {
+ nsCOMPtr<nsISupports> data;
+ if (NS_SUCCEEDED(currItem->GetTransferData(flavorStr.get(),
+ getter_AddRefs(data)))) {
+ void* tmpData = nullptr;
+ uint32_t tmpDataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(flavorStr.get()), data, &tmpData,
+ &tmpDataLen);
+ if (tmpData) {
+ if (CanExportAsURLTarget(reinterpret_cast<char16_t*>(tmpData),
+ tmpDataLen / 2)) {
+ TargetArrayAddTarget(targetArray, gMozUrlType);
+ }
+ free(tmpData);
+ }
+ }
+ }
+ // check if application/x-moz-file-promise url is supported.
+ // If so, advertise text/uri-list.
+ else if (flavorStr.EqualsLiteral(kFilePromiseURLMime)) {
+ TargetArrayAddTarget(targetArray, gTextUriListType);
+ }
+ // XdndDirectSave, use on X.org only.
+ else if (widget::GdkIsX11Display() && !widget::IsXWaylandProtocol() &&
+ flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ TargetArrayAddTarget(targetArray, gXdndDirectSaveType);
+ }
+ // kNativeImageMime
+ else if (flavorStr.EqualsLiteral(kNativeImageMime)) {
+ TargetArrayAddTarget(targetArray, kPNGImageMime);
+ TargetArrayAddTarget(targetArray, kJPEGImageMime);
+ TargetArrayAddTarget(targetArray, kJPGImageMime);
+ TargetArrayAddTarget(targetArray, kGIFImageMime);
+ }
+ }
+ }
+ }
+
+ // get all the elements that we created.
+ targetCount = targetArray.Length();
+ if (targetCount) {
+ // allocate space to create the list of valid targets
+ targets = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry) * targetCount);
+ uint32_t targetIndex;
+ for (targetIndex = 0; targetIndex < targetCount; ++targetIndex) {
+ GtkTargetEntry* disEntry = targetArray.ElementAt(targetIndex);
+ // this is a string reference but it will be freed later.
+ targets[targetIndex].target = disEntry->target;
+ targets[targetIndex].flags = disEntry->flags;
+ targets[targetIndex].info = 0;
+ }
+ targetList = gtk_target_list_new(targets, targetCount);
+ // clean up the target list
+ for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) {
+ GtkTargetEntry* thisTarget = targetArray.ElementAt(cleanIndex);
+ g_free(thisTarget->target);
+ g_free(thisTarget);
+ }
+ g_free(targets);
+ } else {
+ // We need to create a dummy target list to be able initialize dnd.
+ targetList = gtk_target_list_new(nullptr, 0);
+ }
+ return targetList;
+}
+
+void nsDragService::SourceEndDragSession(GdkDragContext* aContext,
+ gint aResult) {
+ LOGDRAGSERVICE("SourceEndDragSession(%p) result %s\n", aContext,
+ kGtkDragResults[aResult]);
+
+ // this just releases the list of data items that we provide
+ mSourceDataItems = nullptr;
+
+ // Remove this property, if it exists, to satisfy the Direct Save Protocol.
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ gdk_property_delete(gdk_drag_context_get_source_window(aContext), property);
+
+ if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd)
+ // EndDragSession() was already called on drop
+ // or SourceEndDragSession on drag-failed
+ return;
+
+ if (mEndDragPoint.x < 0) {
+ // We don't have a drag end point, so guess
+ gint x, y;
+ GdkDisplay* display = gdk_display_get_default();
+ GdkScreen* screen = gdk_display_get_default_screen(display);
+ GtkWindow* window = GetGtkWindow(mSourceDocument);
+ GdkWindow* gdkWindow = window ? gtk_widget_get_window(GTK_WIDGET(window))
+ : gdk_screen_get_root_window(screen);
+ if (!gdkWindow) {
+ return;
+ }
+ gdk_window_get_device_position(
+ gdkWindow, gdk_drag_context_get_device(aContext), &x, &y, nullptr);
+ gint scale = gdk_window_get_scale_factor(gdkWindow);
+ SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
+ LOGDRAGSERVICE(" guess drag end point %d %d\n", x * scale, y * scale);
+ }
+
+ // Either the drag was aborted or the drop occurred outside the app.
+ // The dropEffect of mDataTransfer is not updated for motion outside the
+ // app, but is needed for the dragend event, so set it now.
+
+ uint32_t dropEffect;
+
+ if (aResult == GTK_DRAG_RESULT_SUCCESS) {
+ LOGDRAGSERVICE(" drop is accepted");
+ // With GTK+ versions 2.10.x and prior the drag may have been
+ // cancelled (but no drag-failed signal would have been sent).
+ // aContext->dest_window will be non-nullptr only if the drop was
+ // sent.
+ GdkDragAction action = gdk_drag_context_get_dest_window(aContext)
+ ? gdk_drag_context_get_actions(aContext)
+ : (GdkDragAction)0;
+
+ // Only one bit of action should be set, but, just in case someone
+ // does something funny, erring away from MOVE, and not recording
+ // unusual action combinations as NONE.
+ if (!action) {
+ LOGDRAGSERVICE(" drop action is none");
+ dropEffect = DRAGDROP_ACTION_NONE;
+ } else if (action & GDK_ACTION_COPY) {
+ LOGDRAGSERVICE(" drop action is copy");
+ dropEffect = DRAGDROP_ACTION_COPY;
+ } else if (action & GDK_ACTION_LINK) {
+ LOGDRAGSERVICE(" drop action is link");
+ dropEffect = DRAGDROP_ACTION_LINK;
+ } else if (action & GDK_ACTION_MOVE) {
+ LOGDRAGSERVICE(" drop action is move");
+ dropEffect = DRAGDROP_ACTION_MOVE;
+ } else {
+ LOGDRAGSERVICE(" drop action is copy");
+ dropEffect = DRAGDROP_ACTION_COPY;
+ }
+ } else {
+ LOGDRAGSERVICE(" drop action is none");
+ dropEffect = DRAGDROP_ACTION_NONE;
+ if (aResult != GTK_DRAG_RESULT_NO_TARGET) {
+ LOGDRAGSERVICE(" drop is user chancelled\n");
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, LayoutDeviceIntPoint(), 0);
+}
+
+static nsresult GetDownloadDetails(nsITransferable* aTransferable,
+ nsIURI** aSourceURI, nsAString& aFilename) {
+ *aSourceURI = nullptr;
+ MOZ_ASSERT(aTransferable != nullptr, "aTransferable must not be null");
+
+ // get the URI from the kFilePromiseURLMime flavor
+ nsCOMPtr<nsISupports> urlPrimitive;
+ nsresult rv = aTransferable->GetTransferData(kFilePromiseURLMime,
+ getter_AddRefs(urlPrimitive));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsISupportsString> srcUrlPrimitive = do_QueryInterface(urlPrimitive);
+ if (!srcUrlPrimitive) {
+ return NS_ERROR_FAILURE;
+ }
+
+ nsAutoString srcUri;
+ srcUrlPrimitive->GetData(srcUri);
+ if (srcUri.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsIURI> sourceURI;
+ NS_NewURI(getter_AddRefs(sourceURI), srcUri);
+
+ nsAutoString srcFileName;
+ nsCOMPtr<nsISupports> fileNamePrimitive;
+ rv = aTransferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(fileNamePrimitive));
+ if (NS_FAILED(rv)) {
+ return NS_ERROR_FAILURE;
+ }
+ nsCOMPtr<nsISupportsString> srcFileNamePrimitive =
+ do_QueryInterface(fileNamePrimitive);
+ if (srcFileNamePrimitive) {
+ srcFileNamePrimitive->GetData(srcFileName);
+ } else {
+ nsCOMPtr<nsIURL> sourceURL = do_QueryInterface(sourceURI);
+ if (!sourceURL) {
+ return NS_ERROR_FAILURE;
+ }
+ nsAutoCString urlFileName;
+ sourceURL->GetFileName(urlFileName);
+ NS_UnescapeURL(urlFileName);
+ CopyUTF8toUTF16(urlFileName, srcFileName);
+ }
+ if (srcFileName.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ sourceURI.swap(*aSourceURI);
+ aFilename = srcFileName;
+ return NS_OK;
+}
+
+// See nsContentAreaDragDropDataProvider::GetFlavorData() for reference.
+nsresult nsDragService::CreateTempFile(nsITransferable* aItem,
+ nsACString& aURI) {
+ LOGDRAGSERVICE("nsDragService::CreateTempFile()");
+
+ nsCOMPtr<nsIFile> tmpDir;
+ nsresult rv = NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(tmpDir));
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to get temp directory\n");
+ return rv;
+ }
+
+ nsCOMPtr<nsIInputStream> inputStream;
+ nsCOMPtr<nsIChannel> channel;
+
+ // extract the file name and source uri of the promise-file data
+ nsAutoString wideFileName;
+ nsCOMPtr<nsIURI> sourceURI;
+ rv = GetDownloadDetails(aItem, getter_AddRefs(sourceURI), wideFileName);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(
+ " Failed to extract file name and source uri from download url");
+ return rv;
+ }
+
+ // Check if the file is already stored at /tmp.
+ // It happens when drop destination is changed and SourceDataGet() is caled
+ // more than once.
+ nsAutoCString fileName;
+ CopyUTF16toUTF8(wideFileName, fileName);
+ auto fileLen = fileName.Length();
+ for (const auto& url : mTempFileUrls) {
+ auto URLLen = url.Length();
+ if (URLLen > fileLen &&
+ fileName.Equals(nsDependentCString(url, URLLen - fileLen))) {
+ aURI = url;
+ LOGDRAGSERVICE(" recycle file %s", PromiseFlatCString(aURI).get());
+ return NS_OK;
+ }
+ }
+
+ // create and open channel for source uri
+ nsCOMPtr<nsIPrincipal> principal = aItem->GetRequestingPrincipal();
+ nsContentPolicyType contentPolicyType = aItem->GetContentPolicyType();
+ nsCOMPtr<nsICookieJarSettings> cookieJarSettings =
+ aItem->GetCookieJarSettings();
+ rv = NS_NewChannel(getter_AddRefs(channel), sourceURI, principal,
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ contentPolicyType, cookieJarSettings);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to create new channel for source uri");
+ return rv;
+ }
+
+ rv = channel->Open(getter_AddRefs(inputStream));
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to open channel for source uri");
+ return rv;
+ }
+
+ // build the file:///tmp/dnd_file URL
+ tmpDir->Append(NS_LITERAL_STRING_FROM_CSTRING("dnd_file"));
+ rv = tmpDir->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed create tmp dir");
+ return rv;
+ }
+
+ // store a copy of that temporary directory so we can
+ // clean them up when nsDragService is destructed
+ nsCOMPtr<nsIFile> tempFile;
+ tmpDir->Clone(getter_AddRefs(tempFile));
+ mTemporaryFiles.AppendObject(tempFile);
+ if (mTempFileTimerID) {
+ g_source_remove(mTempFileTimerID);
+ mTempFileTimerID = 0;
+ }
+
+ // extend file:///tmp/dnd_file/<filename> URL
+ tmpDir->Append(wideFileName);
+
+ nsCOMPtr<nsIOutputStream> outputStream;
+ rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), tmpDir);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to open output stream for temporary file");
+ return rv;
+ }
+
+ char buffer[8192];
+ uint32_t readCount = 0;
+ uint32_t writeCount = 0;
+ while (1) {
+ rv = inputStream->Read(buffer, sizeof(buffer), &readCount);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to read data from source uri");
+ return rv;
+ }
+
+ if (readCount == 0) break;
+
+ rv = outputStream->Write(buffer, readCount, &writeCount);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to write data to temporary file");
+ return rv;
+ }
+ }
+
+ inputStream->Close();
+ rv = outputStream->Close();
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to write data to temporary file");
+ return rv;
+ }
+
+ nsCOMPtr<nsIURI> uri;
+ rv = NS_NewFileURI(getter_AddRefs(uri), tmpDir);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to get file URI");
+ return rv;
+ }
+ nsCOMPtr<nsIURL> fileURL(do_QueryInterface(uri));
+ if (!fileURL) {
+ LOGDRAGSERVICE(" Failed to query file interface");
+ return NS_ERROR_FAILURE;
+ }
+ rv = fileURL->GetSpec(aURI);
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" Failed to get filepath");
+ return rv;
+ }
+
+ // store url of temporary file
+ mTempFileUrls.AppendElement()->Assign(aURI);
+ LOGDRAGSERVICE(" storing tmp file as %s", PromiseFlatCString(aURI).get());
+ return NS_OK;
+}
+
+bool nsDragService::SourceDataAppendURLFileItem(nsACString& aURI,
+ nsITransferable* aItem) {
+ // If there is a file available, create a URI from the file.
+ nsCOMPtr<nsISupports> data;
+ nsresult rv = aItem->GetTransferData(kFileMime, getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, false);
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ fileURI->GetSpec(aURI);
+ return true;
+ }
+ }
+ return false;
+}
+
+bool nsDragService::SourceDataAppendURLItem(nsITransferable* aItem,
+ bool aExternalDrop,
+ nsACString& aURI) {
+ nsCOMPtr<nsISupports> data;
+ nsresult rv = aItem->GetTransferData(kURLMime, getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ return SourceDataAppendURLFileItem(aURI, aItem);
+ }
+
+ nsCOMPtr<nsISupportsString> string = do_QueryInterface(data);
+ if (!string) {
+ return false;
+ }
+
+ nsAutoString text;
+ string->GetData(text);
+ if (!aExternalDrop || CanExportAsURLTarget(text.get(), text.Length())) {
+ AppendUTF16toUTF8(text, aURI);
+ return true;
+ }
+
+ // We're dropping to another application and the URL can't be exported
+ // as it's internal one (mailbox:// etc.)
+ // Try to get file target directly.
+ if (SourceDataAppendURLFileItem(aURI, aItem)) {
+ return true;
+ }
+
+ // We can't get the file directly so try to download it and save to tmp.
+ // The desktop or file manager expects for drags of promise-file data
+ // the text/uri-list flavor set to a temporary file that contains the
+ // promise-file data.
+ // We open a stream on the <protocol>:// url here and save the content
+ // to file:///tmp/dnd_file/<filename> and pass this url
+ // as text/uri-list flavor.
+
+ // check whether transferable contains FilePromiseUrl flavor...
+ nsCOMPtr<nsISupports> promiseData;
+ rv = aItem->GetTransferData(kFilePromiseURLMime, getter_AddRefs(promiseData));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ // ... if so, create a temporary file and pass its url
+ return NS_SUCCEEDED(CreateTempFile(aItem, aURI));
+}
+
+void nsDragService::SourceDataGetUriList(GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ uint32_t aDragItems) {
+ // Check if we're transfering data to another application.
+ // gdk_drag_context_get_dest_window() on X11 returns GdkWindow even for
+ // different application so use nsWindow::GetWindow() to check if that's
+ // our window.
+ const bool isExternalDrop =
+ widget::GdkIsX11Display()
+ ? !nsWindow::GetWindow(gdk_drag_context_get_dest_window(aContext))
+ : !gdk_drag_context_get_dest_window(aContext);
+
+ LOGDRAGSERVICE("nsDragService::SourceDataGetUriLists() len %d external %d",
+ aDragItems, isExternalDrop);
+
+ // Disable processing of native events until we store all files to /tmp.
+ // Otherwise user can quit drop before we have all files saved
+ // and that cancels whole D&D.
+ AutoSuspendNativeEvents suspend;
+
+ nsAutoCString uriList;
+ for (uint32_t i = 0; i < aDragItems; i++) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(mSourceDataItems, i);
+ if (!item) {
+ continue;
+ }
+ nsAutoCString uri;
+ if (!SourceDataAppendURLItem(item, isExternalDrop, uri)) {
+ continue;
+ }
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ int32_t separatorPos = uri.FindChar(u'\n');
+ if (separatorPos >= 0) {
+ uri.Truncate(separatorPos);
+ }
+ uriList.Append(uri);
+ uriList.AppendLiteral("\r\n");
+ }
+
+ LOGDRAGSERVICE("URI list\n%s", uriList.get());
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)uriList.get(),
+ uriList.Length());
+}
+
+void nsDragService::SourceDataGetImage(nsITransferable* aItem,
+ GtkSelectionData* aSelectionData) {
+ LOGDRAGSERVICE("nsDragService::SourceDataGetImage()");
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = aItem->GetTransferData(kNativeImageMime, getter_AddRefs(data));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ LOGDRAGSERVICE(" posting image\n");
+ nsCOMPtr<imgIContainer> image = do_QueryInterface(data);
+ if (!image) {
+ LOGDRAGSERVICE(" do_QueryInterface failed\n");
+ return;
+ }
+ RefPtr<GdkPixbuf> pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf) {
+ LOGDRAGSERVICE(" ImageToPixbuf failed\n");
+ return;
+ }
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ LOGDRAGSERVICE(" image data set\n");
+ return;
+}
+
+void nsDragService::SourceDataGetXDND(nsITransferable* aItem,
+ GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData) {
+ LOGDRAGSERVICE("nsDragService::SourceDataGetXDND");
+
+ // Indicate failure by default.
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"E", 1);
+
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
+
+ GdkWindow* srcWindow = gdk_drag_context_get_source_window(aContext);
+ if (!srcWindow) {
+ LOGDRAGSERVICE(" failed to get source GdkWindow!");
+ return;
+ }
+
+ // Ensure null termination.
+ nsAutoCString data;
+ {
+ GUniquePtr<guchar> gdata;
+ gint length = 0;
+ if (!gdk_property_get(srcWindow, property, type, 0, INT32_MAX, FALSE,
+ nullptr, nullptr, &length, getter_Transfers(gdata))) {
+ LOGDRAGSERVICE(" failed to get gXdndDirectSaveType GdkWindow property.");
+ return;
+ }
+ data.Assign(nsDependentCSubstring((const char*)gdata.get(), length));
+ }
+
+ GUniquePtr<char> hostname;
+ GUniquePtr<char> fullpath(
+ g_filename_from_uri(data.get(), getter_Transfers(hostname), nullptr));
+ if (!fullpath) {
+ LOGDRAGSERVICE(" failed to get file from uri.");
+ return;
+ }
+
+ // If there is no hostname in the URI, NULL will be stored.
+ // We should not accept uris with from a different host.
+ if (hostname) {
+ nsCOMPtr<nsIPropertyBag2> infoService =
+ do_GetService(NS_SYSTEMINFO_CONTRACTID);
+ if (!infoService) {
+ return;
+ }
+ nsAutoCString host;
+ if (NS_SUCCEEDED(infoService->GetPropertyAsACString(u"host"_ns, host))) {
+ if (!host.Equals(hostname.get())) {
+ LOGDRAGSERVICE(" ignored drag because of different host.");
+ // Special error code "F" for this case.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"F", 1);
+ return;
+ }
+ }
+ }
+
+ LOGDRAGSERVICE(" XdndDirectSave filepath is %s", fullpath.get());
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_FAILED(NS_NewNativeLocalFile(nsDependentCString(fullpath.get()), false,
+ getter_AddRefs(file)))) {
+ LOGDRAGSERVICE(" failed to get local file");
+ return;
+ }
+
+ // We have to split the path into a directory and filename,
+ // because our internal file-promise API is based on these.
+ nsCOMPtr<nsIFile> directory;
+ file->GetParent(getter_AddRefs(directory));
+
+ aItem->SetTransferData(kFilePromiseDirectoryMime, directory);
+
+ nsCOMPtr<nsISupportsString> filenamePrimitive =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (!filenamePrimitive) {
+ return;
+ }
+
+ nsAutoString leafName;
+ file->GetLeafName(leafName);
+ filenamePrimitive->SetData(leafName);
+
+ aItem->SetTransferData(kFilePromiseDestFilename, filenamePrimitive);
+
+ nsCOMPtr<nsISupports> promiseData;
+ nsresult rv =
+ aItem->GetTransferData(kFilePromiseMime, getter_AddRefs(promiseData));
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ // Indicate success.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"S", 1);
+ return;
+}
+
+bool nsDragService::SourceDataGetText(nsITransferable* aItem,
+ const nsACString& aMIMEType,
+ bool aNeedToDoConversionToPlainText,
+ GtkSelectionData* aSelectionData) {
+ LOGDRAGSERVICE("nsDragService::SourceDataGetPlain()");
+
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = aItem->GetTransferData(PromiseFlatCString(aMIMEType).get(),
+ getter_AddRefs(data));
+ NS_ENSURE_SUCCESS(rv, false);
+
+ void* tmpData = nullptr;
+ uint32_t tmpDataLen = 0;
+
+ nsPrimitiveHelpers::CreateDataFromPrimitive(aMIMEType, data, &tmpData,
+ &tmpDataLen);
+ // if required, do the extra work to convert unicode to plain
+ // text and replace the output values with the plain text.
+ if (aNeedToDoConversionToPlainText) {
+ char* plainTextData = nullptr;
+ char16_t* castedUnicode = reinterpret_cast<char16_t*>(tmpData);
+ uint32_t plainTextLen = 0;
+ UTF16ToNewUTF8(castedUnicode, tmpDataLen / 2, &plainTextData,
+ &plainTextLen);
+ if (tmpData) {
+ // this was not allocated using glib
+ free(tmpData);
+ tmpData = plainTextData;
+ tmpDataLen = plainTextLen;
+ }
+ }
+ if (tmpData) {
+ // this copies the data
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)tmpData,
+ tmpDataLen);
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+
+ return true;
+}
+
+// We're asked to get data from mSourceDataItems and pass it to
+// GtkSelectionData (Gtk D&D interface).
+// We need to check mSourceDataItems data type and try to convert it
+// to data type accepted by Gtk.
+void nsDragService::SourceDataGet(GtkWidget* aWidget, GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint32 aTime) {
+ LOGDRAGSERVICE("nsDragService::SourceDataGet(%p)", aContext);
+
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ GUniquePtr<gchar> requestedTypeName(gdk_atom_name(target));
+ if (!requestedTypeName) {
+ LOGDRAGSERVICE(" failed to get atom name.\n");
+ return;
+ }
+
+ LOGDRAGSERVICE(" Gtk asks us for %s data type\n", requestedTypeName.get());
+ // check to make sure that we have data items to return.
+ if (!mSourceDataItems) {
+ LOGDRAGSERVICE(" Failed to get our data items\n");
+ return;
+ }
+
+ uint32_t dragItems;
+ mSourceDataItems->GetLength(&dragItems);
+ LOGDRAGSERVICE(" source data items %d", dragItems);
+
+ nsDependentCString mimeFlavor(requestedTypeName.get());
+ if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
+ SourceDataGetUriList(aContext, aSelectionData, dragItems);
+ return;
+ }
+
+#ifdef MOZ_LOGGING
+ if (dragItems > 1) {
+ LOGDRAGSERVICE(
+ " There are %d data items but we're asked for %s MIME type. Only "
+ "first data element can be transfered!",
+ dragItems, mimeFlavor.get());
+ }
+#endif
+
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(mSourceDataItems, 0);
+ if (!item) {
+ LOGDRAGSERVICE(" Failed to get SourceDataItems!");
+ return;
+ }
+
+ if (mimeFlavor.EqualsLiteral(kTextMime) ||
+ mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) {
+ SourceDataGetText(item, nsDependentCString(kTextMime),
+ /* aNeedToDoConversionToPlainText */ true,
+ aSelectionData);
+ // no fallback for text mime types
+ return;
+ }
+ // Someone is asking for the special Direct Save Protocol type.
+ else if (mimeFlavor.EqualsLiteral(gXdndDirectSaveType)) {
+ SourceDataGetXDND(item, aContext, aSelectionData);
+ // no fallback for XDND mime types
+ return;
+ } else if (mimeFlavor.EqualsLiteral(kPNGImageMime) ||
+ mimeFlavor.EqualsLiteral(kJPEGImageMime) ||
+ mimeFlavor.EqualsLiteral(kJPGImageMime) ||
+ mimeFlavor.EqualsLiteral(kGIFImageMime)) {
+ // no fallback for image mime types
+ SourceDataGetImage(item, aSelectionData);
+ return;
+ } else if (mimeFlavor.EqualsLiteral(gMozUrlType)) {
+ // Someone was asking for _NETSCAPE_URL. We need to get it from
+ // transferable as x-moz-url and convert it to plain text.
+ // No need to check
+ if (SourceDataGetText(item, nsDependentCString(kURLMime),
+ /* aNeedToDoConversionToPlainText */ true,
+ aSelectionData)) {
+ return;
+ }
+ }
+ // Just try to get and set whatever we're asked for.
+ SourceDataGetText(item, mimeFlavor,
+ /* aNeedToDoConversionToPlainText */ false, aSelectionData);
+}
+
+void nsDragService::SourceBeginDrag(GdkDragContext* aContext) {
+ LOGDRAGSERVICE("nsDragService::SourceBeginDrag(%p)\n", aContext);
+
+ nsCOMPtr<nsITransferable> transferable =
+ do_QueryElementAt(mSourceDataItems, 0);
+ if (!transferable) return;
+
+ nsTArray<nsCString> flavors;
+ nsresult rv = transferable->FlavorsTransferableCanImport(flavors);
+ NS_ENSURE_SUCCESS(rv, );
+
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ if (flavors[i].EqualsLiteral(kFilePromiseDestFilename)) {
+ nsCOMPtr<nsISupports> data;
+ rv = transferable->GetTransferData(kFilePromiseDestFilename,
+ getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ LOGDRAGSERVICE(" transferable doesn't contain '%s",
+ kFilePromiseDestFilename);
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> fileName = do_QueryInterface(data);
+ if (!fileName) {
+ LOGDRAGSERVICE(" failed to get file name");
+ return;
+ }
+
+ nsAutoString fileNameStr;
+ fileName->GetData(fileNameStr);
+
+ nsCString fileNameCStr;
+ CopyUTF16toUTF8(fileNameStr, fileNameCStr);
+
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
+
+ gdk_property_change(gdk_drag_context_get_source_window(aContext),
+ property, type, 8, GDK_PROP_MODE_REPLACE,
+ (const guchar*)fileNameCStr.get(),
+ fileNameCStr.Length());
+ break;
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext) {
+ if (!mHasImage && !mSelection) return;
+
+ LOGDRAGSERVICE("nsDragService::SetDragIcon(%p)", aContext);
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mRegion, mScreenPosition, &dragRect, &surface, &pc);
+ if (!pc) {
+ LOGDRAGSERVICE(" PresContext is missing!");
+ return;
+ }
+
+ const auto screenPoint =
+ LayoutDeviceIntPoint::Round(mScreenPosition * pc->CSSToDevPixelScale());
+ int32_t offsetX = screenPoint.x - dragRect.x;
+ int32_t offsetY = screenPoint.y - dragRect.y;
+
+ // If a popup is set as the drag image, use its widget. Otherwise, use
+ // the surface that DrawDrag created.
+ //
+ // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
+ // Fix this once a new GTK version ships that does not destroy our
+ // widget in gtk_drag_set_icon_widget.
+ // This is fixed in GTK 3.24
+ // by
+ // https://gitlab.gnome.org/GNOME/gtk/-/commit/c27c4e2048acb630feb24c31288f802345e99f4c
+ bool gtk_drag_set_icon_widget_is_working =
+ gtk_check_version(3, 19, 4) != nullptr ||
+ gtk_check_version(3, 24, 0) == nullptr;
+ if (mDragPopup && gtk_drag_set_icon_widget_is_working) {
+ GtkWidget* gtkWidget = nullptr;
+ nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+ if (frame) {
+ // DrawDrag ensured that this is a popup frame.
+ nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+ if (widget) {
+ gtkWidget = (GtkWidget*)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+ if (gtkWidget) {
+ // When mDragPopup has a parent it's already attached to D&D context.
+ // That may happens when D&D operation is aborted but not finished
+ // on Gtk side yet so let's remove it now.
+ if (GtkWidget* parent = gtk_widget_get_parent(gtkWidget)) {
+ gtk_container_remove(GTK_CONTAINER(parent), gtkWidget);
+ }
+ LOGDRAGSERVICE(" set drag popup [%p]", widget.get());
+ OpenDragPopup();
+ gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+ return;
+ } else {
+ LOGDRAGSERVICE(" NS_NATIVE_SHELLWIDGET is missing!");
+ }
+ } else {
+ LOGDRAGSERVICE(" NearestWidget is missing!");
+ }
+ } else {
+ LOGDRAGSERVICE(" PrimaryFrame is missing!");
+ }
+ }
+
+ if (surface) {
+ LOGDRAGSERVICE(" We have a surface");
+ if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
+ RefPtr<GdkPixbuf> dragPixbuf = nsImageToPixbuf::SourceSurfaceToPixbuf(
+ surface, dragRect.width, dragRect.height);
+ if (dragPixbuf) {
+ LOGDRAGSERVICE(" set drag pixbuf");
+ gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
+ } else {
+ LOGDRAGSERVICE(" SourceSurfaceToPixbuf failed!");
+ }
+ }
+ } else {
+ LOGDRAGSERVICE(" Surface is missing!");
+ }
+}
+
+static void invisibleSourceDragBegin(GtkWidget* aWidget,
+ GdkDragContext* aContext, gpointer aData) {
+ LOGDRAGSERVICESTATIC("invisibleSourceDragBegin (%p)", aContext);
+ nsDragService* dragService = (nsDragService*)aData;
+
+ dragService->SourceBeginDrag(aContext);
+ dragService->SetDragIcon(aContext);
+}
+
+static void invisibleSourceDragDataGet(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData) {
+ LOGDRAGSERVICESTATIC("invisibleSourceDragDataGet (%p)", aContext);
+ nsDragService* dragService = (nsDragService*)aData;
+ dragService->SourceDataGet(aWidget, aContext, aSelectionData, aTime);
+}
+
+static gboolean invisibleSourceDragFailed(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ gint aResult, gpointer aData) {
+#ifdef MOZ_WAYLAND
+ // Wayland and X11 uses different drag results here. When drag target is
+ // missing X11 passes GDK_DRAG_CANCEL_NO_TARGET
+ // (from gdk_dnd_handle_button_event()/gdkdnd-x11.c)
+ // as backend X11 has info about other application windows.
+ // Wayland does not have such info so it always passes
+ // GDK_DRAG_CANCEL_ERROR error code
+ // (see data_source_cancelled/gdkselection-wayland.c).
+ // Bug 1527976
+ if (widget::GdkIsWaylandDisplay() && aResult == GTK_DRAG_RESULT_ERROR) {
+ for (GList* tmp = gdk_drag_context_list_targets(aContext); tmp;
+ tmp = tmp->next) {
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ GUniquePtr<gchar> name(gdk_atom_name(atom));
+ if (name && !strcmp(name.get(), gTabDropType)) {
+ aResult = GTK_DRAG_RESULT_NO_TARGET;
+ LOGDRAGSERVICESTATIC("invisibleSourceDragFailed(%p): Wayland tab drop",
+ aContext);
+ break;
+ }
+ }
+ }
+#endif
+ LOGDRAGSERVICESTATIC("invisibleSourceDragFailed(%p) %s", aContext,
+ kGtkDragResults[aResult]);
+ nsDragService* dragService = (nsDragService*)aData;
+ // End the drag session now (rather than waiting for the drag-end signal)
+ // so that operations performed on dropEffect == none can start immediately
+ // rather than waiting for the drag-failed animation to finish.
+ dragService->SourceEndDragSession(aContext, aResult);
+
+ // We should return TRUE to disable the drag-failed animation iff the
+ // source performed an operation when dropEffect was none, but the handler
+ // of the dragend DOM event doesn't provide this information.
+ return FALSE;
+}
+
+static void invisibleSourceDragEnd(GtkWidget* aWidget, GdkDragContext* aContext,
+ gpointer aData) {
+ LOGDRAGSERVICESTATIC("invisibleSourceDragEnd(%p)", aContext);
+ nsDragService* dragService = (nsDragService*)aData;
+
+ // The drag has ended. Release the hostages!
+ dragService->SourceEndDragSession(aContext, GTK_DRAG_RESULT_SUCCESS);
+}
+
+// The following methods handle responding to GTK drag signals and
+// tracking state between these signals.
+//
+// In general, GTK does not expect us to run the event loop while handling its
+// drag signals, however our drag event handlers may run the
+// event loop, most often to fetch information about the drag data.
+//
+// GTK, for example, uses the return value from drag-motion signals to
+// determine whether drag-leave signals should be sent. If an event loop is
+// run during drag-motion the XdndLeave message can get processed but when GTK
+// receives the message it does not yet know that it needs to send the
+// drag-leave signal to our widget.
+//
+// After a drag-drop signal, we need to reply with gtk_drag_finish().
+// However, gtk_drag_finish should happen after the drag-drop signal handler
+// returns so that when the Motif drag protocol is used, the
+// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
+// reply sent on return from the drag-drop signal handler.
+//
+// Similarly drag-end for a successful drag and drag-failed are not good
+// times to run a nested event loop as gtk_drag_drop_finished() and
+// gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
+// drop_timeout until after at least the first of these signals is sent.
+// Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
+// timeout) could cause gtk_drag_drop_finished to be called again with the
+// same GtkDragSourceInfo, which won't like being destroyed twice.
+//
+// Therefore we reply to the signals immediately and schedule a task to
+// dispatch the Gecko events, which may run the event loop.
+//
+// Action in response to drag-leave signals is also delayed until the event
+// loop runs again so that we find out whether a drag-drop signal follows.
+//
+// A single task is scheduled to manage responses to all three GTK signals.
+// If further signals are received while the task is scheduled, the scheduled
+// response is updated, sometimes effectively compressing successive signals.
+//
+// No Gecko drag events are dispatched (during nested event loops) while other
+// Gecko drag events are in flight. This helps event handlers that may not
+// expect nested events, while accessing an event's dataTransfer for example.
+
+gboolean nsDragService::ScheduleMotionEvent(nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ LayoutDeviceIntPoint aWindowPoint,
+ guint aTime) {
+ if (aDragContext && mScheduledTask == eDragTaskMotion) {
+ // The drag source has sent another motion message before we've
+ // replied to the previous. That shouldn't happen with Xdnd. The
+ // spec for Motif drags is less clear, but we'll just update the
+ // scheduled task with the new position reply only to the most
+ // recent message.
+ NS_WARNING("Drag Motion message received before previous reply was sent");
+ }
+
+ // Returning TRUE means we'll reply with a status message, unless we first
+ // get a leave.
+ return Schedule(eDragTaskMotion, aWindow, aDragContext, aWindowPoint, aTime);
+}
+
+void nsDragService::ScheduleLeaveEvent() {
+ // We don't know at this stage whether a drop signal will immediately
+ // follow. If the drop signal gets sent it will happen before we return
+ // to the main loop and the scheduled leave task will be replaced.
+ if (!Schedule(eDragTaskLeave, nullptr, nullptr, LayoutDeviceIntPoint(), 0)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean nsDragService::ScheduleDropEvent(nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ LayoutDeviceIntPoint aWindowPoint,
+ guint aTime) {
+ if (!Schedule(eDragTaskDrop, aWindow, aDragContext, aWindowPoint, aTime)) {
+ NS_WARNING("Additional drag drop ignored");
+ return FALSE;
+ }
+
+ SetDragEndPoint(aWindowPoint);
+
+ // We'll reply with gtk_drag_finish().
+ return TRUE;
+}
+
+#ifdef MOZ_LOGGING
+const char* nsDragService::GetDragServiceTaskName(DragTask aTask) {
+ static const char* taskNames[] = {"eDragTaskNone", "eDragTaskMotion",
+ "eDragTaskLeave", "eDragTaskDrop",
+ "eDragTaskSourceEnd"};
+ MOZ_ASSERT(size_t(aTask) < ArrayLength(taskNames));
+ return taskNames[aTask];
+}
+#endif
+
+gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ LayoutDeviceIntPoint aWindowPoint,
+ guint aTime) {
+ // If there is an existing leave or motion task scheduled, then that
+ // will be replaced. When the new task is run, it will dispatch
+ // any necessary leave or motion events.
+
+ // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
+ // drop event (which could happen if the drop event has not been processed
+ // within the allowed time). Otherwise, if we haven't yet run a scheduled
+ // drop or end task, just say that we are not ready to receive another
+ // drop.
+ LOGDRAGSERVICE("nsDragService::Schedule(%p) task %s window %p\n",
+ aDragContext, GetDragServiceTaskName(aTask), aWindow);
+
+ if (mScheduledTask == eDragTaskSourceEnd ||
+ (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) {
+ LOGDRAGSERVICE(" task does not fit recent task %s, quit!\n",
+ GetDragServiceTaskName(mScheduledTask));
+ return FALSE;
+ }
+
+ mScheduledTask = aTask;
+ mPendingWindow = aWindow;
+ mPendingDragContext = aDragContext;
+ mPendingWindowPoint = aWindowPoint;
+ mPendingTime = aTime;
+
+ if (!mTaskSource) {
+ // High priority is used here because we want to process motion events
+ // right after drag_motion event handler which is called by Gtk.
+ // An ideal scenario is to call TaskDispatchCallback() directly here
+ // but we can't do that. TaskDispatchCallback() spins gtk event loop
+ // while nsDragService::Schedule() is already called from event loop
+ // (by drag_motion* gtk_widget events) so that direct call will cause
+ // nested recursion.
+ mTaskSource = g_timeout_add_full(G_PRIORITY_HIGH, 0, TaskDispatchCallback,
+ this, nullptr);
+ }
+
+ // We need to reply to drag_motion event on Wayland immediately,
+ // see Bug 1730203.
+ if (widget::GdkIsWaylandDisplay() && mScheduledTask == eDragTaskMotion) {
+ UpdateDragAction(aDragContext);
+ ReplyToDragMotion(aDragContext, aTime);
+ }
+
+ return TRUE;
+}
+
+gboolean nsDragService::TaskDispatchCallback(gpointer data) {
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ AutoEventLoop loop(dragService);
+ return dragService->RunScheduledTask();
+}
+
+gboolean nsDragService::RunScheduledTask() {
+ LOGDRAGSERVICE(
+ "nsDragService::RunScheduledTask() task %s mTargetWindow %p "
+ "mPendingWindow %p\n",
+ GetDragServiceTaskName(mScheduledTask), mTargetWindow.get(),
+ mPendingWindow.get());
+
+ // Don't run RunScheduledTask() twice. As we use it in main thread only
+ // we don't need to be thread safe here.
+ if (mScheduledTaskIsRunning) {
+ LOGDRAGSERVICE(" sheduled task is already running, quit.");
+ return FALSE;
+ }
+ AutoRestore<bool> guard(mScheduledTaskIsRunning);
+ mScheduledTaskIsRunning = true;
+
+ if (mTargetWindow && mTargetWindow != mPendingWindow) {
+ LOGDRAGSERVICE(" dispatch eDragExit (%p)\n", mTargetWindow.get());
+ mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
+
+ if (!mSourceNode) {
+ // The drag that was initiated in a different app. End the drag
+ // session, since we're done with it for now (until the user drags
+ // back into this app).
+ EndDragSession(false, GetCurrentModifiers());
+ }
+ }
+
+ // It is possible that the pending state has been updated during dispatch
+ // of the leave event. That's fine.
+
+ // Now we collect the pending state because, from this point on, we want
+ // to use the same state for all events dispatched. All state is updated
+ // so that when other tasks are scheduled during dispatch here, this
+ // task is considered to have already been run.
+ bool positionHasChanged = mPendingWindow != mTargetWindow ||
+ mPendingWindowPoint != mTargetWindowPoint;
+ DragTask task = mScheduledTask;
+ mScheduledTask = eDragTaskNone;
+ mTargetWindow = std::move(mPendingWindow);
+ mTargetWindowPoint = mPendingWindowPoint;
+
+ if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
+ LOGDRAGSERVICE(" quit, selected task %s\n", GetDragServiceTaskName(task));
+ if (task == eDragTaskSourceEnd) {
+ // Dispatch drag end events.
+ EndDragSession(true, GetCurrentModifiers());
+ }
+
+ // Nothing more to do
+ // Returning false removes the task source from the event loop.
+ mTaskSource = 0;
+ return FALSE;
+ }
+
+ // This may be the start of a destination drag session.
+ StartDragSession();
+
+ // mTargetWidget may be nullptr if the window has been destroyed.
+ // (The leave event is not scheduled if a drop task is still scheduled.)
+ // We still reply appropriately to indicate that the drop will or didn't
+ // succeeed.
+ mTargetWidget = mTargetWindow ? mTargetWindow->GetGtkWidget() : nullptr;
+ LOGDRAGSERVICE(" start drag session mTargetWindow %p mTargetWidget %p\n",
+ mTargetWindow.get(), mTargetWidget.get());
+ LOGDRAGSERVICE(" mPendingDragContext %p => mTargetDragContext %p\n",
+ mPendingDragContext.get(), mTargetDragContext.get());
+ mTargetDragContext = std::move(mPendingDragContext);
+ mTargetTime = mPendingTime;
+
+ // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
+ // (as at 27 December 2010) indicates that a "drop" event should only be
+ // fired (at the current target element) if the current drag operation is
+ // not none. The current drag operation will only be set to a non-none
+ // value during a "dragover" event.
+ //
+ // If the user has ended the drag before any dragover events have been
+ // sent, then the spec recommends skipping the drop (because the current
+ // drag operation is none). However, here we assume that, by releasing
+ // the mouse button, the user has indicated that they want to drop, so we
+ // proceed with the drop where possible.
+ //
+ // In order to make the events appear to content in the same way as if the
+ // spec is being followed we make sure to dispatch a "dragover" event with
+ // appropriate coordinates and check canDrop before the "drop" event.
+ //
+ // When the Xdnd protocol is used for source/destination communication (as
+ // should be the case with GTK source applications) a dragover event
+ // should have already been sent during the drag-motion signal, which
+ // would have already been received because XdndDrop messages do not
+ // contain a position. However, we can't assume the same when the Motif
+ // protocol is used.
+ if (task == eDragTaskMotion || positionHasChanged) {
+ LOGDRAGSERVICE(" process motion event\n");
+ UpdateDragAction();
+ TakeDragEventDispatchedToChildProcess(); // Clear the old value.
+ DispatchMotionEvents();
+ if (task == eDragTaskMotion) {
+ if (TakeDragEventDispatchedToChildProcess()) {
+ mTargetDragContextForRemote = mTargetDragContext;
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ ReplyToDragMotion();
+ }
+ }
+ }
+
+ if (task == eDragTaskDrop) {
+ LOGDRAGSERVICE(" process drop task\n");
+ gboolean success = DispatchDropEvent();
+
+ // Perhaps we should set the del parameter to TRUE when the drag
+ // action is move, but we don't know whether the data was successfully
+ // transferred.
+ if (mTargetDragContext) {
+ LOGDRAGSERVICE(" drag finished\n");
+ gtk_drag_finish(mTargetDragContext, success,
+ /* del = */ FALSE, mTargetTime);
+ }
+ // Make sure to end the drag session. If this drag started in a
+ // different app, we won't get a drag_end signal to end it from.
+ EndDragSession(true, GetCurrentModifiers());
+ }
+
+ // We're done with the drag context.
+ LOGDRAGSERVICE(" clear mTargetWindow mTargetWidget and other data\n");
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+
+ // If we got another drag signal while running the sheduled task, that
+ // must have happened while running a nested event loop. Leave the task
+ // source on the event loop.
+ if (mScheduledTask != eDragTaskNone) return TRUE;
+
+ // We have no task scheduled.
+ // Returning false removes the task source from the event loop.
+ LOGDRAGSERVICE(" remove task source\n");
+ mTaskSource = 0;
+ return FALSE;
+}
+
+// This will update the drag action based on the information in the
+// drag context. Gtk gets this from a combination of the key settings
+// and what the source is offering.
+
+void nsDragService::UpdateDragAction(GdkDragContext* aDragContext) {
+ // This doesn't look right. dragSession.dragAction is used by
+ // nsContentUtils::SetDataTransferInEvent() to set the initial
+ // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
+ // more appropriate. GdkDragContext::actions should be used to set
+ // dataTransfer.effectAllowed, which doesn't currently happen with
+ // external sources.
+ LOGDRAGSERVICE("nsDragService::UpdateDragAction(%p)", aDragContext);
+
+ // default is to do nothing
+ int action = nsIDragService::DRAGDROP_ACTION_NONE;
+ GdkDragAction gdkAction = GDK_ACTION_DEFAULT;
+ if (aDragContext) {
+ gdkAction = gdk_drag_context_get_actions(aDragContext);
+ LOGDRAGSERVICE(" gdk_drag_context_get_actions() returns 0x%X", gdkAction);
+
+ // When D&D modifiers (CTRL/SHIFT) are involved,
+ // gdk_drag_context_get_actions() on X11 returns selected action but
+ // Wayland returns all allowed actions.
+
+ // So we need to call gdk_drag_context_get_selected_action() on Wayland
+ // to get potential D&D modifier.
+ // gdk_drag_context_get_selected_action() is also affected by
+ // gdk_drag_status(), see nsDragService::ReplyToDragMotion().
+ if (widget::GdkIsWaylandDisplay()) {
+ GdkDragAction gdkActionSelected =
+ gdk_drag_context_get_selected_action(aDragContext);
+ LOGDRAGSERVICE(" gdk_drag_context_get_selected_action() returns 0x%X",
+ gdkActionSelected);
+ if (gdkActionSelected) {
+ gdkAction = gdkActionSelected;
+ }
+ }
+ }
+
+ // set the default just in case nothing matches below
+ if (gdkAction & GDK_ACTION_DEFAULT) {
+ LOGDRAGSERVICE(" set default move");
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+ }
+ // first check to see if move is set
+ if (gdkAction & GDK_ACTION_MOVE) {
+ LOGDRAGSERVICE(" set explicit move");
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+ } else if (gdkAction & GDK_ACTION_LINK) {
+ // then fall to the others
+ LOGDRAGSERVICE(" set explicit link");
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+ } else if (gdkAction & GDK_ACTION_COPY) {
+ // copy is ctrl
+ LOGDRAGSERVICE(" set explicit copy");
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+ }
+
+ // update the drag information
+ SetDragAction(action);
+}
+
+void nsDragService::UpdateDragAction() { UpdateDragAction(mTargetDragContext); }
+
+NS_IMETHODIMP
+nsDragService::UpdateDragEffect() {
+ LOGDRAGSERVICE("nsDragService::UpdateDragEffect() from e10s child process");
+ if (mTargetDragContextForRemote) {
+ ReplyToDragMotion(mTargetDragContextForRemote, mTargetTime);
+ mTargetDragContextForRemote = nullptr;
+ }
+ return NS_OK;
+}
+
+void nsDragService::ReplyToDragMotion() {
+ if (mTargetDragContext) {
+ ReplyToDragMotion(mTargetDragContext, mTargetTime);
+ }
+}
+
+void nsDragService::DispatchMotionEvents() {
+ FireDragEventAtSource(eDrag, GetCurrentModifiers());
+ if (mTargetWindow) {
+ mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint,
+ mTargetTime);
+ }
+}
+
+// Returns true if the drop was successful
+gboolean nsDragService::DispatchDropEvent() {
+ // We need to check IsDestroyed here because the nsRefPtr
+ // only protects this from being deleted, it does NOT protect
+ // against nsView::~nsView() calling Destroy() on it, bug 378273.
+ if (!mTargetWindow || mTargetWindow->IsDestroyed()) {
+ return FALSE;
+ }
+
+ EventMessage msg = mCanDrop ? eDrop : eDragExit;
+
+ mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime);
+
+ return mCanDrop;
+}
+
+/* static */
+uint32_t nsDragService::GetCurrentModifiers() {
+ return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers();
+}
+
+#undef LOGDRAGSERVICE
diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h
new file mode 100644
index 0000000000..8a45b7b8ed
--- /dev/null
+++ b/widget/gtk/nsDragService.h
@@ -0,0 +1,268 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=4 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsDragService_h__
+#define nsDragService_h__
+
+#include "mozilla/RefPtr.h"
+#include "nsBaseDragService.h"
+#include "nsCOMArray.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+#include "nsITimer.h"
+#include "GUniquePtr.h"
+
+class nsICookieJarSettings;
+class nsWindow;
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+} // namespace mozilla
+
+/**
+ * Native GTK DragService wrapper
+ */
+
+class nsDragService final : public nsBaseDragService, public nsIObserver {
+ public:
+ nsDragService();
+
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECL_NSIOBSERVER
+
+ // nsBaseDragService
+ MOZ_CAN_RUN_SCRIPT virtual nsresult InvokeDragSessionImpl(
+ nsIArray* anArrayTransferables,
+ const mozilla::Maybe<mozilla::CSSIntRegion>& aRegion,
+ uint32_t aActionType) override;
+ // nsIDragService
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD InvokeDragSession(
+ nsINode* aDOMNode, nsIPrincipal* aPrincipal,
+ nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings,
+ nsIArray* anArrayTransferables, uint32_t aActionType,
+ nsContentPolicyType aContentPolicyType) override;
+ NS_IMETHOD StartDragSession() override;
+ MOZ_CAN_RUN_SCRIPT NS_IMETHOD EndDragSession(bool aDoneDrag,
+ uint32_t aKeyModifiers) override;
+
+ // nsIDragSession
+ NS_IMETHOD SetCanDrop(bool aCanDrop) override;
+ NS_IMETHOD GetCanDrop(bool* aCanDrop) override;
+ NS_IMETHOD GetNumDropItems(uint32_t* aNumItems) override;
+ NS_IMETHOD GetData(nsITransferable* aTransferable,
+ uint32_t aItemIndex) override;
+ NS_IMETHOD IsDataFlavorSupported(const char* aDataFlavor,
+ bool* _retval) override;
+
+ // Update Drag&Drop state according child process state.
+ // UpdateDragEffect() is called by IPC bridge when child process
+ // accepts/denies D&D operation and uses stored
+ // mTargetDragContextForRemote context.
+ NS_IMETHOD UpdateDragEffect() override;
+
+ // Methods called from nsWindow to handle responding to GTK drag
+ // destination signals
+
+ static already_AddRefed<nsDragService> GetInstance();
+
+ void TargetDataReceived(GtkWidget* aWidget, GdkDragContext* aContext, gint aX,
+ gint aY, GtkSelectionData* aSelection_data,
+ guint aInfo, guint32 aTime);
+
+ gboolean ScheduleMotionEvent(nsWindow* aWindow, GdkDragContext* aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+ void ScheduleLeaveEvent();
+ gboolean ScheduleDropEvent(nsWindow* aWindow, GdkDragContext* aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+
+ nsWindow* GetMostRecentDestWindow() {
+ return mScheduledTask == eDragTaskNone ? mTargetWindow : mPendingWindow;
+ }
+
+ // END PUBLIC API
+
+ // These methods are public only so that they can be called from functions
+ // with C calling conventions. They are called for drags started with the
+ // invisible widget.
+ void SourceEndDragSession(GdkDragContext* aContext, gint aResult);
+ void SourceDataGet(GtkWidget* widget, GdkDragContext* context,
+ GtkSelectionData* selection_data, guint32 aTime);
+ bool SourceDataGetText(nsITransferable* aItem, const nsACString& aMIMEType,
+ bool aNeedToDoConversionToPlainText,
+ GtkSelectionData* aSelectionData);
+ void SourceDataGetImage(nsITransferable* aItem,
+ GtkSelectionData* aSelectionData);
+ void SourceDataGetXDND(nsITransferable* aItem, GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData);
+ void SourceDataGetUriList(GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ uint32_t aDragItems);
+ bool SourceDataAppendURLFileItem(nsACString& aURI, nsITransferable* aItem);
+ bool SourceDataAppendURLItem(nsITransferable* aItem, bool aExternalDrop,
+ nsACString& aURI);
+
+ void SourceBeginDrag(GdkDragContext* aContext);
+
+ // set the drag icon during drag-begin
+ void SetDragIcon(GdkDragContext* aContext);
+
+ class AutoEventLoop {
+ RefPtr<nsDragService> mService;
+
+ public:
+ explicit AutoEventLoop(RefPtr<nsDragService> aService)
+ : mService(std::move(aService)) {
+ mService->mEventLoopDepth++;
+ }
+ ~AutoEventLoop() { mService->mEventLoopDepth--; }
+ };
+ int GetLoopDepth() const { return mEventLoopDepth; };
+
+ protected:
+ virtual ~nsDragService();
+
+ private:
+ // mScheduledTask indicates what signal has been received from GTK and
+ // so what needs to be dispatched when the scheduled task is run. It is
+ // eDragTaskNone when there is no task scheduled (but the
+ // previous task may still not have finished running).
+ enum DragTask {
+ eDragTaskNone,
+ eDragTaskMotion,
+ eDragTaskLeave,
+ eDragTaskDrop,
+ eDragTaskSourceEnd
+ };
+ DragTask mScheduledTask;
+ // mTaskSource is the GSource id for the task that is either scheduled
+ // or currently running. It is 0 if no task is scheduled or running.
+ guint mTaskSource;
+ bool mScheduledTaskIsRunning;
+
+ // Where the drag begins. We need to keep it open on Wayland.
+ RefPtr<nsWindow> mSourceWindow;
+
+ // target/destination side vars
+ // These variables keep track of the state of the current drag.
+
+ // mPendingWindow, mPendingWindowPoint, mPendingDragContext, and
+ // mPendingTime, carry information from the GTK signal that will be used
+ // when the scheduled task is run. mPendingWindow and mPendingDragContext
+ // will be nullptr if the scheduled task is eDragTaskLeave.
+ RefPtr<nsWindow> mPendingWindow;
+ mozilla::LayoutDeviceIntPoint mPendingWindowPoint;
+ RefPtr<GdkDragContext> mPendingDragContext;
+
+ // We cache all data for the current drag context,
+ // because waiting for the data in GetTargetDragData can be very slow.
+ nsTHashMap<nsCStringHashKey, nsTArray<uint8_t>> mCachedData;
+ // mCachedData are tied to mCachedDragContext. mCachedDragContext is not
+ // ref counted and may be already deleted on Gtk side.
+ // We used it for mCachedData invalidation only and can't be used for
+ // any D&D operation.
+ uintptr_t mCachedDragContext;
+
+ nsTHashMap<nsCStringHashKey, mozilla::GUniquePtr<gchar*>> mCachedUris;
+
+ guint mPendingTime;
+
+ // mTargetWindow and mTargetWindowPoint record the position of the last
+ // eDragTaskMotion or eDragTaskDrop task that was run or is still running.
+ // mTargetWindow is cleared once the drag has completed or left.
+ RefPtr<nsWindow> mTargetWindow;
+ mozilla::LayoutDeviceIntPoint mTargetWindowPoint;
+ // mTargetWidget and mTargetDragContext are set only while dispatching
+ // motion or drop events. mTime records the corresponding timestamp.
+ RefPtr<GtkWidget> mTargetWidget;
+ RefPtr<GdkDragContext> mTargetDragContext;
+
+ // When we route D'n'D request to child process
+ // (by EventStateManager::DispatchCrossProcessEvent)
+ // we save GdkDragContext to mTargetDragContextForRemote.
+ // When we get a reply from child process we use
+ // the stored GdkDragContext to send reply to OS.
+ //
+ // We need to store GdkDragContext because mTargetDragContext is cleared
+ // after every D'n'D event.
+ RefPtr<GdkDragContext> mTargetDragContextForRemote;
+ guint mTargetTime;
+
+ // is it OK to drop on us?
+ bool mCanDrop;
+
+ // have we received our drag data?
+ bool mTargetDragDataReceived;
+ // last data received and its length
+ void* mTargetDragData;
+ uint32_t mTargetDragDataLen;
+ mozilla::GUniquePtr<gchar*> mTargetDragUris;
+ // is the current target drag context contain a list?
+ bool IsTargetContextList(void);
+ // this will get the native data from the last target given a
+ // specific flavor
+ void GetTargetDragData(GdkAtom aFlavor, nsTArray<nsCString>& aDropFlavors);
+ // this will reset all of the target vars
+ void TargetResetData(void);
+ // Ensure our data cache belongs to aDragContext and clear the cache if
+ // aDragContext is different than mCachedDragContext.
+ void EnsureCachedDataValidForContext(GdkDragContext* aDragContext);
+
+ // source side vars
+
+ // the source of our drags
+ GtkWidget* mHiddenWidget;
+ // our source data items
+ nsCOMPtr<nsIArray> mSourceDataItems;
+
+ // get a list of the sources in gtk's format
+ GtkTargetList* GetSourceList(void);
+
+ // attempts to create a semi-transparent drag image. Returns TRUE if
+ // successful, FALSE if not
+ bool SetAlphaPixmap(SourceSurface* aPixbuf, GdkDragContext* aContext,
+ int32_t aXOffset, int32_t aYOffset,
+ const mozilla::LayoutDeviceIntRect& dragRect);
+
+ gboolean Schedule(DragTask aTask, nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint, guint aTime);
+
+ // Callback for g_idle_add_full() to run mScheduledTask.
+ MOZ_CAN_RUN_SCRIPT static gboolean TaskDispatchCallback(gpointer data);
+ MOZ_CAN_RUN_SCRIPT gboolean RunScheduledTask();
+ MOZ_CAN_RUN_SCRIPT void DispatchMotionEvents();
+ void ReplyToDragMotion(GdkDragContext* aDragContext, guint aTime);
+ void ReplyToDragMotion();
+ void UpdateDragAction(GdkDragContext* aDragContext);
+ void UpdateDragAction();
+
+#ifdef MOZ_LOGGING
+ const char* GetDragServiceTaskName(nsDragService::DragTask aTask);
+#endif
+ void GetDragFlavors(nsTArray<nsCString>& aFlavors);
+ gboolean DispatchDropEvent();
+ static uint32_t GetCurrentModifiers();
+
+ nsresult CreateTempFile(nsITransferable* aItem, nsACString& aURI);
+ bool RemoveTempFiles();
+ static gboolean TaskRemoveTempFiles(gpointer data);
+
+ // the url of the temporary file that has been created in the current drag
+ // session
+ nsTArray<nsCString> mTempFileUrls;
+ // stores all temporary files
+ nsCOMArray<nsIFile> mTemporaryFiles;
+ // timer to trigger deletion of temporary files
+ guint mTempFileTimerID;
+ // How deep we're nested in event loops
+ int mEventLoopDepth;
+};
+
+#endif // nsDragService_h__
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
new file mode 100644
index 0000000000..751da3dc6b
--- /dev/null
+++ b/widget/gtk/nsFilePicker.cpp
@@ -0,0 +1,785 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <dlfcn.h>
+#include <gtk/gtk.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <unistd.h>
+
+#include "mozilla/Types.h"
+#include "AsyncDBus.h"
+#include "nsGtkUtils.h"
+#include "nsIFileURL.h"
+#include "nsIGIOService.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "nsIStringBundle.h"
+#include "mozilla/Components.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/dom/Promise.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "MozContainer.h"
+#include "WidgetUtilsGtk.h"
+
+#include "nsFilePicker.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetLog;
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOG(args)
+#endif /* MOZ_LOGGING */
+
+using namespace mozilla;
+using mozilla::dom::Promise;
+
+#define MAX_PREVIEW_SIZE 180
+// bug 1184009
+#define MAX_PREVIEW_SOURCE_SIZE 4096
+
+nsIFile* nsFilePicker::mPrevDisplayDirectory = nullptr;
+
+void nsFilePicker::Shutdown() { NS_IF_RELEASE(mPrevDisplayDirectory); }
+
+static GtkFileChooserAction GetGtkFileChooserAction(nsIFilePicker::Mode aMode) {
+ GtkFileChooserAction action;
+
+ switch (aMode) {
+ case nsIFilePicker::modeSave:
+ action = GTK_FILE_CHOOSER_ACTION_SAVE;
+ break;
+
+ case nsIFilePicker::modeGetFolder:
+ action = GTK_FILE_CHOOSER_ACTION_SELECT_FOLDER;
+ break;
+
+ case nsIFilePicker::modeOpen:
+ case nsIFilePicker::modeOpenMultiple:
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+
+ default:
+ NS_WARNING("Unknown nsIFilePicker mode");
+ action = GTK_FILE_CHOOSER_ACTION_OPEN;
+ break;
+ }
+
+ return action;
+}
+
+static void UpdateFilePreviewWidget(GtkFileChooser* file_chooser,
+ gpointer preview_widget_voidptr) {
+ GtkImage* preview_widget = GTK_IMAGE(preview_widget_voidptr);
+ char* image_filename = gtk_file_chooser_get_preview_filename(file_chooser);
+ struct stat st_buf;
+
+ if (!image_filename) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ gint preview_width = 0;
+ gint preview_height = 0;
+ /* check type of file
+ * if file is named pipe, Open is blocking which may lead to UI
+ * nonresponsiveness; if file is directory/socket, it also isn't
+ * likely to get preview */
+ if (stat(image_filename, &st_buf) || (!S_ISREG(st_buf.st_mode))) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return; /* stat failed or file is not regular */
+ }
+
+ GdkPixbufFormat* preview_format =
+ gdk_pixbuf_get_file_info(image_filename, &preview_width, &preview_height);
+ if (!preview_format || preview_width <= 0 || preview_height <= 0 ||
+ preview_width > MAX_PREVIEW_SOURCE_SIZE ||
+ preview_height > MAX_PREVIEW_SOURCE_SIZE) {
+ g_free(image_filename);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf* preview_pixbuf = nullptr;
+ // Only scale down images that are too big
+ if (preview_width > MAX_PREVIEW_SIZE || preview_height > MAX_PREVIEW_SIZE) {
+ preview_pixbuf = gdk_pixbuf_new_from_file_at_size(
+ image_filename, MAX_PREVIEW_SIZE, MAX_PREVIEW_SIZE, nullptr);
+ } else {
+ preview_pixbuf = gdk_pixbuf_new_from_file(image_filename, nullptr);
+ }
+
+ g_free(image_filename);
+
+ if (!preview_pixbuf) {
+ gtk_file_chooser_set_preview_widget_active(file_chooser, FALSE);
+ return;
+ }
+
+ GdkPixbuf* preview_pixbuf_temp = preview_pixbuf;
+ preview_pixbuf = gdk_pixbuf_apply_embedded_orientation(preview_pixbuf_temp);
+ g_object_unref(preview_pixbuf_temp);
+
+ // This is the easiest way to do center alignment without worrying about
+ // containers Minimum 3px padding each side (hence the 6) just to make things
+ // nice
+ gint x_padding =
+ (MAX_PREVIEW_SIZE + 6 - gdk_pixbuf_get_width(preview_pixbuf)) / 2;
+ gtk_misc_set_padding(GTK_MISC(preview_widget), x_padding, 0);
+
+ gtk_image_set_from_pixbuf(preview_widget, preview_pixbuf);
+ g_object_unref(preview_pixbuf);
+ gtk_file_chooser_set_preview_widget_active(file_chooser, TRUE);
+}
+
+static nsAutoCString MakeCaseInsensitiveShellGlob(const char* aPattern) {
+ // aPattern is UTF8
+ nsAutoCString result;
+ unsigned int len = strlen(aPattern);
+
+ for (unsigned int i = 0; i < len; i++) {
+ if (!g_ascii_isalpha(aPattern[i])) {
+ // non-ASCII characters will also trigger this path, so unicode
+ // is safely handled albeit case-sensitively
+ result.Append(aPattern[i]);
+ continue;
+ }
+
+ // add the lowercase and uppercase version of a character to a bracket
+ // match, so it matches either the lowercase or uppercase char.
+ result.Append('[');
+ result.Append(g_ascii_tolower(aPattern[i]));
+ result.Append(g_ascii_toupper(aPattern[i]));
+ result.Append(']');
+ }
+
+ return result;
+}
+
+NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
+
+nsFilePicker::nsFilePicker()
+ : mSelectedType(0), mAllowURLs(false), mFileChooserDelegate(nullptr) {
+ mUseNativeFileChooser =
+ widget::ShouldUsePortal(widget::PortalKind::FilePicker);
+}
+
+nsFilePicker::~nsFilePicker() = default;
+
+void ReadMultipleFiles(gpointer filename, gpointer array) {
+ nsCOMPtr<nsIFile> localfile;
+ nsresult rv =
+ NS_NewNativeLocalFile(nsDependentCString(static_cast<char*>(filename)),
+ false, getter_AddRefs(localfile));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMArray<nsIFile>& files = *static_cast<nsCOMArray<nsIFile>*>(array);
+ files.AppendObject(localfile);
+ }
+
+ g_free(filename);
+}
+
+void nsFilePicker::ReadValuesFromFileChooser(void* file_chooser) {
+ mFiles.Clear();
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ mFileURL.Truncate();
+
+ GSList* list =
+ gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(file_chooser));
+ g_slist_foreach(list, ReadMultipleFiles, static_cast<gpointer>(&mFiles));
+ g_slist_free(list);
+ } else {
+ gchar* filename = gtk_file_chooser_get_uri(GTK_FILE_CHOOSER(file_chooser));
+ mFileURL.Assign(filename);
+ g_free(filename);
+ }
+
+ GtkFileFilter* filter =
+ gtk_file_chooser_get_filter(GTK_FILE_CHOOSER(file_chooser));
+ GSList* filter_list =
+ gtk_file_chooser_list_filters(GTK_FILE_CHOOSER(file_chooser));
+
+ mSelectedType = static_cast<int16_t>(g_slist_index(filter_list, filter));
+ g_slist_free(filter_list);
+
+ // Remember last used directory.
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ nsCOMPtr<nsIFile> dir;
+ file->GetParent(getter_AddRefs(dir));
+ if (dir) {
+ dir.swap(mPrevDisplayDirectory);
+ }
+ }
+}
+
+void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
+ mParentWidget = aParent;
+ mTitle.Assign(aTitle);
+}
+
+NS_IMETHODIMP
+nsFilePicker::IsModeSupported(nsIFilePicker::Mode aMode, JSContext* aCx,
+ Promise** aRetPromise) {
+#ifdef MOZ_ENABLE_DBUS
+ if (!widget::ShouldUsePortal(widget::PortalKind::FilePicker) ||
+ aMode != nsIFilePicker::modeGetFolder) {
+ return nsBaseFilePicker::IsModeSupported(aMode, aCx, aRetPromise);
+ }
+
+ const char kFreedesktopPortalName[] = "org.freedesktop.portal.Desktop";
+ const char kFreedesktopPortalPath[] = "/org/freedesktop/portal/desktop";
+ const char kFreedesktopPortalFileChooser[] =
+ "org.freedesktop.portal.FileChooser";
+
+ MOZ_ASSERT(aCx);
+ MOZ_ASSERT(aRetPromise);
+
+ nsIGlobalObject* globalObject = xpc::CurrentNativeGlobal(aCx);
+ if (NS_WARN_IF(!globalObject)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ ErrorResult result;
+ RefPtr<Promise> retPromise = Promise::Create(globalObject, result);
+ if (NS_WARN_IF(result.Failed())) {
+ return result.StealNSResult();
+ }
+
+ widget::CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS),
+ /* aInterfaceInfo = */ nullptr, kFreedesktopPortalName,
+ kFreedesktopPortalPath, kFreedesktopPortalFileChooser)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [retPromise](RefPtr<GDBusProxy>&& aProxy) {
+ const char kFreedesktopPortalVersionProperty[] = "version";
+ // Folder selection was added in version 3 of xdg-desktop-portal
+ const uint32_t kFreedesktopPortalMinimumVersion = 3;
+ uint32_t foundVersion = 0;
+
+ RefPtr<GVariant> property =
+ dont_AddRef(g_dbus_proxy_get_cached_property(
+ aProxy, kFreedesktopPortalVersionProperty));
+
+ if (property) {
+ foundVersion = g_variant_get_uint32(property);
+ LOG(("Found portal version: %u", foundVersion));
+ }
+
+ retPromise->MaybeResolve(foundVersion >=
+ kFreedesktopPortalMinimumVersion);
+ },
+ [retPromise](GUniquePtr<GError>&& aError) {
+ g_printerr("Failed to create DBUS proxy: %s\n", aError->message);
+ retPromise->MaybeReject(NS_ERROR_FAILURE);
+ });
+
+ retPromise.forget(aRetPromise);
+ return NS_OK;
+#else
+ return nsBaseFilePicker::IsModeSupported(aMode, aCx, aRetPromise);
+#endif
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilters(int32_t aFilterMask) {
+ mAllowURLs = !!(aFilterMask & filterAllowURLs);
+ return nsBaseFilePicker::AppendFilters(aFilterMask);
+}
+
+NS_IMETHODIMP
+nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
+ if (aFilter.EqualsLiteral("..apps")) {
+ // No platform specific thing we can do here, really....
+ return NS_OK;
+ }
+
+ nsAutoCString filter, name;
+ CopyUTF16toUTF8(aFilter, filter);
+ CopyUTF16toUTF8(aTitle, name);
+
+ mFilters.AppendElement(filter);
+ mFilterNames.AppendElement(name);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultString(const nsAString& aString) {
+ mDefault = aString;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultString(nsAString& aString) {
+ // Per API...
+ return NS_ERROR_FAILURE;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetDefaultExtension(const nsAString& aExtension) {
+ mDefaultExtension = aExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetDefaultExtension(nsAString& aExtension) {
+ aExtension = mDefaultExtension;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
+ *aFilterIndex = mSelectedType;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
+ mSelectedType = aFilterIndex;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFile(nsIFile** aFile) {
+ NS_ENSURE_ARG_POINTER(aFile);
+
+ *aFile = nullptr;
+ nsCOMPtr<nsIURI> uri;
+ nsresult rv = GetFileURL(getter_AddRefs(uri));
+ if (!uri) return rv;
+
+ nsCOMPtr<nsIFileURL> fileURL(do_QueryInterface(uri, &rv));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ file.forget(aFile);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFileURL(nsIURI** aFileURL) {
+ *aFileURL = nullptr;
+ return NS_NewURI(aFileURL, mFileURL);
+}
+
+NS_IMETHODIMP
+nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
+ NS_ENSURE_ARG_POINTER(aFiles);
+
+ if (mMode == nsIFilePicker::modeOpenMultiple) {
+ return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
+ }
+
+ return NS_ERROR_FAILURE;
+}
+
+nsresult nsFilePicker::Show(nsIFilePicker::ResultCode* aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ nsresult rv = Open(nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ while (mFileChooser) {
+ g_main_context_iteration(nullptr, TRUE);
+ }
+
+ *aReturn = mResult;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
+ // Can't show two dialogs concurrently with the same filepicker
+ if (mFileChooser) return NS_ERROR_NOT_AVAILABLE;
+
+ if (MaybeBlockFilePicker(aCallback)) {
+ return NS_OK;
+ }
+
+ NS_ConvertUTF16toUTF8 title(mTitle);
+
+ GtkWindow* parent_widget =
+ GTK_WINDOW(mParentWidget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+
+ GtkFileChooserAction action = GetGtkFileChooserAction(mMode);
+
+ const gchar* accept_button;
+ NS_ConvertUTF16toUTF8 buttonLabel(mOkButtonLabel);
+ if (!mOkButtonLabel.IsEmpty()) {
+ accept_button = buttonLabel.get();
+ } else {
+ accept_button = nullptr;
+ }
+
+ void* file_chooser =
+ GtkFileChooserNew(title.get(), parent_widget, action, accept_button);
+
+ // If we have --enable-proxy-bypass-protection, then don't allow
+ // remote URLs to be used.
+#ifndef MOZ_PROXY_BYPASS_PROTECTION
+ if (mAllowURLs) {
+ gtk_file_chooser_set_local_only(GTK_FILE_CHOOSER(file_chooser), FALSE);
+ }
+#endif
+
+ if (action == GTK_FILE_CHOOSER_ACTION_OPEN ||
+ action == GTK_FILE_CHOOSER_ACTION_SAVE) {
+ GtkWidget* img_preview = gtk_image_new();
+ gtk_file_chooser_set_preview_widget(GTK_FILE_CHOOSER(file_chooser),
+ img_preview);
+ g_signal_connect(file_chooser, "update-preview",
+ G_CALLBACK(UpdateFilePreviewWidget), img_preview);
+ }
+
+ GtkFileChooserSetModal(file_chooser, parent_widget, TRUE);
+
+ NS_ConvertUTF16toUTF8 defaultName(mDefault);
+ switch (mMode) {
+ case nsIFilePicker::modeOpenMultiple:
+ gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(file_chooser),
+ TRUE);
+ break;
+ case nsIFilePicker::modeSave:
+ gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(file_chooser),
+ defaultName.get());
+ break;
+
+ default:
+ /* no additional setup needed */
+ break;
+ }
+
+ nsCOMPtr<nsIFile> defaultPath;
+ if (mDisplayDirectory) {
+ mDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ } else if (mPrevDisplayDirectory) {
+ mPrevDisplayDirectory->Clone(getter_AddRefs(defaultPath));
+ }
+
+ if (defaultPath) {
+ if (!defaultName.IsEmpty() && mMode != nsIFilePicker::modeSave) {
+ // Try to select the intended file. Even if it doesn't exist, GTK still
+ // switches directories.
+ defaultPath->AppendNative(defaultName);
+ nsAutoCString path;
+ defaultPath->GetNativePath(path);
+ gtk_file_chooser_set_filename(GTK_FILE_CHOOSER(file_chooser), path.get());
+ } else {
+ nsAutoCString directory;
+ defaultPath->GetNativePath(directory);
+
+ // Workaround for problematic refcounting in GTK3 before 3.16.
+ // We need to keep a reference to the dialog's internal delegate.
+ // Otherwise, if our dialog gets destroyed, we'll lose the dialog's
+ // delegate by the time this gets processed in the event loop.
+ // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1166741
+ if (GTK_IS_DIALOG(file_chooser)) {
+ GtkDialog* dialog = GTK_DIALOG(file_chooser);
+ GtkContainer* area = GTK_CONTAINER(gtk_dialog_get_content_area(dialog));
+ gtk_container_forall(
+ area,
+ [](GtkWidget* widget, gpointer data) {
+ if (GTK_IS_FILE_CHOOSER_WIDGET(widget)) {
+ auto result = static_cast<GtkFileChooserWidget**>(data);
+ *result = GTK_FILE_CHOOSER_WIDGET(widget);
+ }
+ },
+ &mFileChooserDelegate);
+
+ if (mFileChooserDelegate != nullptr) {
+ g_object_ref(mFileChooserDelegate);
+ }
+ }
+ gtk_file_chooser_set_current_folder(GTK_FILE_CHOOSER(file_chooser),
+ directory.get());
+ }
+ }
+
+ if (GTK_IS_DIALOG(file_chooser)) {
+ gtk_dialog_set_default_response(GTK_DIALOG(file_chooser),
+ GTK_RESPONSE_ACCEPT);
+ }
+
+ int32_t count = mFilters.Length();
+ for (int32_t i = 0; i < count; ++i) {
+ // This is fun... the GTK file picker does not accept a list of filters
+ // so we need to split out each string, and add it manually.
+
+ char** patterns = g_strsplit(mFilters[i].get(), ";", -1);
+ if (!patterns) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ GtkFileFilter* filter = gtk_file_filter_new();
+ for (int j = 0; patterns[j] != nullptr; ++j) {
+ nsAutoCString caseInsensitiveFilter =
+ MakeCaseInsensitiveShellGlob(g_strstrip(patterns[j]));
+ gtk_file_filter_add_pattern(filter, caseInsensitiveFilter.get());
+ }
+
+ g_strfreev(patterns);
+
+ if (!mFilterNames[i].IsEmpty()) {
+ // If we have a name for our filter, let's use that.
+ const char* filter_name = mFilterNames[i].get();
+ gtk_file_filter_set_name(filter, filter_name);
+ } else {
+ // If we don't have a name, let's just use the filter pattern.
+ const char* filter_pattern = mFilters[i].get();
+ gtk_file_filter_set_name(filter, filter_pattern);
+ }
+
+ gtk_file_chooser_add_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+
+ // Set the initially selected filter
+ if (mSelectedType == i) {
+ gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(file_chooser), filter);
+ }
+ }
+
+ gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(file_chooser),
+ TRUE);
+
+ mFileChooser = file_chooser;
+ mCallback = aCallback;
+ NS_ADDREF_THIS();
+ g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
+ GtkFileChooserShow(file_chooser);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsFilePicker::Close() {
+ if (mFileChooser) {
+ // Call ourself as done.
+ Done(mFileChooser, GTK_RESPONSE_CLOSE);
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsFilePicker::OnResponse(void* file_chooser, gint response_id,
+ gpointer user_data) {
+ static_cast<nsFilePicker*>(user_data)->Done(file_chooser, response_id);
+}
+
+/* static */
+void nsFilePicker::OnDestroy(GtkWidget* file_chooser, gpointer user_data) {
+ static_cast<nsFilePicker*>(user_data)->Done(file_chooser,
+ GTK_RESPONSE_CANCEL);
+}
+
+bool nsFilePicker::WarnForNonReadableFile(void* file_chooser) {
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (!file) {
+ return false;
+ }
+
+ bool isReadable = false;
+ file->IsReadable(&isReadable);
+ if (isReadable) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundleService> stringService =
+ mozilla::components::StringBundle::Service();
+ if (!stringService) {
+ return false;
+ }
+
+ nsCOMPtr<nsIStringBundle> filepickerBundle;
+ nsresult rv = stringService->CreateBundle(
+ "chrome://global/locale/filepicker.properties",
+ getter_AddRefs(filepickerBundle));
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ nsAutoString errorMessage;
+ rv = filepickerBundle->GetStringFromName("selectedFileNotReadableError",
+ errorMessage);
+ if (NS_FAILED(rv)) {
+ return false;
+ }
+
+ GtkDialogFlags flags = GTK_DIALOG_DESTROY_WITH_PARENT;
+ auto* cancel_dialog = gtk_message_dialog_new(
+ GTK_WINDOW(file_chooser), flags, GTK_MESSAGE_ERROR, GTK_BUTTONS_CLOSE,
+ "%s", NS_ConvertUTF16toUTF8(errorMessage).get());
+ gtk_dialog_run(GTK_DIALOG(cancel_dialog));
+ gtk_widget_destroy(cancel_dialog);
+
+ return true;
+}
+
+void nsFilePicker::Done(void* file_chooser, gint response) {
+ mFileChooser = nullptr;
+
+ nsIFilePicker::ResultCode result;
+ switch (response) {
+ case GTK_RESPONSE_OK:
+ case GTK_RESPONSE_ACCEPT:
+ ReadValuesFromFileChooser(file_chooser);
+ result = nsIFilePicker::returnOK;
+ if (mMode == nsIFilePicker::modeSave) {
+ nsCOMPtr<nsIFile> file;
+ GetFile(getter_AddRefs(file));
+ if (file) {
+ bool exists = false;
+ file->Exists(&exists);
+ if (exists) result = nsIFilePicker::returnReplace;
+ }
+ } else if (mMode == nsIFilePicker::modeOpen) {
+ if (WarnForNonReadableFile(file_chooser)) {
+ result = nsIFilePicker::returnCancel;
+ }
+ }
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ result = nsIFilePicker::returnCancel;
+ break;
+
+ default:
+ NS_WARNING("Unexpected response");
+ result = nsIFilePicker::returnCancel;
+ break;
+ }
+
+ // A "response" signal won't be sent again but "destroy" will be.
+ g_signal_handlers_disconnect_by_func(file_chooser, FuncToGpointer(OnDestroy),
+ this);
+
+ // When response_id is GTK_RESPONSE_DELETE_EVENT or when called from
+ // OnDestroy, the widget would be destroyed anyway but it is fine if
+ // gtk_widget_destroy is called more than once. gtk_widget_destroy has
+ // requests that any remaining references be released, but the reference
+ // count will not be decremented again if GtkWindow's reference has already
+ // been released.
+ GtkFileChooserDestroy(file_chooser);
+
+ if (mFileChooserDelegate) {
+ // Properly deref our acquired reference. We call this after
+ // gtk_widget_destroy() to try and ensure that pending file info
+ // queries caused by updating the current folder have been cancelled.
+ // However, we do not know for certain when the callback will run after
+ // cancelled.
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ mFileChooserDelegate);
+ mFileChooserDelegate = nullptr;
+ }
+
+ if (mCallback) {
+ mCallback->Done(result);
+ mCallback = nullptr;
+ } else {
+ mResult = result;
+ }
+ NS_RELEASE_THIS();
+}
+
+// All below functions available as of GTK 3.20+
+void* nsFilePicker::GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label) {
+ static auto sGtkFileChooserNativeNewPtr =
+ (void* (*)(const gchar*, GtkWindow*, GtkFileChooserAction, const gchar*,
+ const gchar*))dlsym(RTLD_DEFAULT,
+ "gtk_file_chooser_native_new");
+ if (mUseNativeFileChooser && sGtkFileChooserNativeNewPtr != nullptr) {
+ return (*sGtkFileChooserNativeNewPtr)(title, parent, action, accept_label,
+ nullptr);
+ }
+ if (accept_label == nullptr) {
+ accept_label = (action == GTK_FILE_CHOOSER_ACTION_SAVE) ? GTK_STOCK_SAVE
+ : GTK_STOCK_OPEN;
+ }
+ GtkWidget* file_chooser = gtk_file_chooser_dialog_new(
+ title, parent, action, GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
+ accept_label, GTK_RESPONSE_ACCEPT, nullptr);
+ gtk_dialog_set_alternative_button_order(
+ GTK_DIALOG(file_chooser), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_CANCEL, -1);
+ return file_chooser;
+}
+
+void nsFilePicker::GtkFileChooserShow(void* file_chooser) {
+ static auto sGtkNativeDialogShowPtr =
+ (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_show");
+ if (mUseNativeFileChooser && sGtkNativeDialogShowPtr != nullptr) {
+ const char* portalEnvString = g_getenv("GTK_USE_PORTAL");
+ bool setPortalEnv =
+ (portalEnvString && *portalEnvString == '0') || !portalEnvString;
+ if (setPortalEnv) {
+ setenv("GTK_USE_PORTAL", "1", true);
+ }
+ (*sGtkNativeDialogShowPtr)(file_chooser);
+ if (setPortalEnv) {
+ unsetenv("GTK_USE_PORTAL");
+ }
+ } else {
+ g_signal_connect(file_chooser, "destroy", G_CALLBACK(OnDestroy), this);
+ gtk_widget_show(GTK_WIDGET(file_chooser));
+ }
+}
+
+void nsFilePicker::GtkFileChooserDestroy(void* file_chooser) {
+ static auto sGtkNativeDialogDestroyPtr =
+ (void (*)(void*))dlsym(RTLD_DEFAULT, "gtk_native_dialog_destroy");
+ if (mUseNativeFileChooser && sGtkNativeDialogDestroyPtr != nullptr) {
+ (*sGtkNativeDialogDestroyPtr)(file_chooser);
+ } else {
+ gtk_widget_destroy(GTK_WIDGET(file_chooser));
+ }
+}
+
+void nsFilePicker::GtkFileChooserSetModal(void* file_chooser,
+ GtkWindow* parent_widget,
+ gboolean modal) {
+ static auto sGtkNativeDialogSetModalPtr = (void (*)(void*, gboolean))dlsym(
+ RTLD_DEFAULT, "gtk_native_dialog_set_modal");
+ if (mUseNativeFileChooser && sGtkNativeDialogSetModalPtr != nullptr) {
+ (*sGtkNativeDialogSetModalPtr)(file_chooser, modal);
+ } else {
+ GtkWindow* window = GTK_WINDOW(file_chooser);
+ gtk_window_set_modal(window, modal);
+ if (parent_widget != nullptr) {
+ gtk_window_set_destroy_with_parent(window, modal);
+ }
+ }
+}
+
+#undef LOG
diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
new file mode 100644
index 0000000000..f8fc22bc97
--- /dev/null
+++ b/widget/gtk/nsFilePicker.h
@@ -0,0 +1,96 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsFilePicker_h__
+#define nsFilePicker_h__
+
+#include <gtk/gtk.h>
+
+#include "nsBaseFilePicker.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsCOMArray.h"
+
+class nsIWidget;
+class nsIFile;
+
+class nsFilePicker : public nsBaseFilePicker {
+ public:
+ nsFilePicker();
+
+ NS_DECL_ISUPPORTS
+
+ // nsIFilePicker (less what's in nsBaseFilePicker)
+ NS_IMETHOD Open(nsIFilePickerShownCallback* aCallback) override;
+ NS_IMETHOD Close() override;
+ NS_IMETHOD IsModeSupported(nsIFilePicker::Mode, JSContext*,
+ mozilla::dom::Promise**) override;
+ NS_IMETHOD AppendFilters(int32_t aFilterMask) override;
+ NS_IMETHOD AppendFilter(const nsAString& aTitle,
+ const nsAString& aFilter) override;
+ NS_IMETHOD SetDefaultString(const nsAString& aString) override;
+ NS_IMETHOD GetDefaultString(nsAString& aString) override;
+ NS_IMETHOD SetDefaultExtension(const nsAString& aExtension) override;
+ NS_IMETHOD GetDefaultExtension(nsAString& aExtension) override;
+ NS_IMETHOD GetFilterIndex(int32_t* aFilterIndex) override;
+ NS_IMETHOD SetFilterIndex(int32_t aFilterIndex) override;
+ NS_IMETHOD GetFile(nsIFile** aFile) override;
+ NS_IMETHOD GetFileURL(nsIURI** aFileURL) override;
+ NS_IMETHOD GetFiles(nsISimpleEnumerator** aFiles) override;
+
+ // nsBaseFilePicker
+ virtual void InitNative(nsIWidget* aParent, const nsAString& aTitle) override;
+
+ static void Shutdown();
+
+ protected:
+ virtual ~nsFilePicker();
+
+ nsresult Show(nsIFilePicker::ResultCode* aReturn) override;
+ void ReadValuesFromFileChooser(void* file_chooser);
+
+ bool WarnForNonReadableFile(void* file_chooser);
+
+ static void OnResponse(void* file_chooser, gint response_id,
+ gpointer user_data);
+ static void OnDestroy(GtkWidget* file_chooser, gpointer user_data);
+ void Done(void* file_chooser, gint response_id);
+
+ nsCOMPtr<nsIWidget> mParentWidget;
+ nsCOMPtr<nsIFilePickerShownCallback> mCallback;
+ nsCOMArray<nsIFile> mFiles;
+
+ int16_t mSelectedType;
+ nsIFilePicker::ResultCode mResult;
+ bool mAllowURLs;
+ nsCString mFileURL;
+ nsString mTitle;
+ nsString mDefault;
+ nsString mDefaultExtension;
+
+ nsTArray<nsCString> mFilters;
+ nsTArray<nsCString> mFilterNames;
+
+ private:
+ static nsIFile* mPrevDisplayDirectory;
+
+ void* GtkFileChooserNew(const gchar* title, GtkWindow* parent,
+ GtkFileChooserAction action,
+ const gchar* accept_label);
+ void GtkFileChooserShow(void* file_chooser);
+ void GtkFileChooserDestroy(void* file_chooser);
+ void GtkFileChooserSetModal(void* file_chooser, GtkWindow* parent_widget,
+ gboolean modal);
+
+ GtkFileChooserWidget* mFileChooserDelegate;
+ bool mUseNativeFileChooser;
+
+ /**
+ * mFileChooser is non-null while open.
+ */
+ void* mFileChooser = nullptr;
+};
+
+#endif
diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h
new file mode 100644
index 0000000000..5129ba419d
--- /dev/null
+++ b/widget/gtk/nsGTKToolkit.h
@@ -0,0 +1,52 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef GTKTOOLKIT_H
+#define GTKTOOLKIT_H
+
+#include "nsString.h"
+#include <gtk/gtk.h>
+
+/**
+ * Wrapper around the thread running the message pump.
+ * The toolkit abstraction is necessary because the message pump must
+ * execute within the same thread that created the widget under Win32.
+ */
+
+class nsGTKToolkit final {
+ public:
+ nsGTKToolkit() = default;
+
+ static nsGTKToolkit* GetToolkit();
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ /**
+ * Get/set our startup token value. (XDG_ACTIVATION_TOKEN/DESKTOP_STARTUP_ID)
+ * When non-empty, this is applied to the next toplevel window to be shown or
+ * focused (and then immediately cleared).
+ */
+ void SetStartupToken(const nsACString& aToken) { mStartupToken = aToken; }
+ const nsCString& GetStartupToken() const { return mStartupToken; }
+
+ /**
+ * Get/set the timestamp value to be used, if non-zero, to focus the
+ * next top-level window to be shown or focused (upon which it is cleared).
+ */
+ void SetFocusTimestamp(uint32_t aTimestamp) { mFocusTimestamp = aTimestamp; }
+ uint32_t GetFocusTimestamp() const { return mFocusTimestamp; }
+
+ private:
+ static nsGTKToolkit* gToolkit;
+
+ nsCString mStartupToken;
+ uint32_t mFocusTimestamp = 0;
+};
+
+#endif // GTKTOOLKIT_H
diff --git a/widget/gtk/nsGtkCursors.h b/widget/gtk/nsGtkCursors.h
new file mode 100644
index 0000000000..f30ed593a9
--- /dev/null
+++ b/widget/gtk/nsGtkCursors.h
@@ -0,0 +1,416 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGtkCursors_h__
+#define nsGtkCursors_h__
+
+typedef struct {
+ const unsigned char* bits;
+ const unsigned char* mask_bits;
+ int hot_x;
+ int hot_y;
+ const char* hash;
+} nsGtkCursor;
+
+/* MOZ_CURSOR_HAND_GRAB */
+static const unsigned char moz_hand_grab_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00,
+ 0x60, 0x39, 0x00, 0x00, 0x90, 0x49, 0x00, 0x00, 0x90, 0x49, 0x01, 0x00,
+ 0x20, 0xc9, 0x02, 0x00, 0x20, 0x49, 0x02, 0x00, 0x58, 0x40, 0x02, 0x00,
+ 0x64, 0x00, 0x02, 0x00, 0x44, 0x00, 0x01, 0x00, 0x08, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_hand_grab_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x60, 0x3f, 0x00, 0x00,
+ 0xf0, 0x7f, 0x00, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf0, 0xff, 0x07, 0x00, 0xf8, 0xff, 0x07, 0x00, 0xfc, 0xff, 0x07, 0x00,
+ 0xfe, 0xff, 0x07, 0x00, 0xfe, 0xff, 0x03, 0x00, 0xfc, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_HAND_GRABBING */
+static const unsigned char moz_hand_grabbing_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0xc0, 0x36, 0x00, 0x00, 0x20, 0xc9, 0x00, 0x00, 0x20, 0x40, 0x01, 0x00,
+ 0x40, 0x00, 0x01, 0x00, 0x60, 0x00, 0x01, 0x00, 0x10, 0x00, 0x01, 0x00,
+ 0x10, 0x00, 0x01, 0x00, 0x10, 0x80, 0x00, 0x00, 0x20, 0x80, 0x00, 0x00,
+ 0x40, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00, 0x80, 0x40, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_hand_grabbing_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x36, 0x00, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xf0, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x03, 0x00,
+ 0xe0, 0xff, 0x03, 0x00, 0xf0, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x03, 0x00,
+ 0xf8, 0xff, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x01, 0x00,
+ 0xe0, 0xff, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_COPY */
+static const unsigned char moz_copy_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0x30, 0x00, 0x00, 0x6c, 0x30, 0x00, 0x00,
+ 0xc4, 0xfc, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00,
+ 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_copy_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0x37, 0x00, 0x00, 0xfe, 0x7b, 0x00, 0x00, 0xfe, 0xfc, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x00, 0x00,
+ 0xc0, 0x7b, 0x00, 0x00, 0x80, 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ALIAS */
+static const unsigned char moz_alias_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x03, 0x00, 0x00, 0x7c, 0xf0, 0x00, 0x00, 0x6c, 0xe0, 0x00, 0x00,
+ 0xc4, 0xf0, 0x00, 0x00, 0xc0, 0xb0, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00,
+ 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_alias_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x03, 0x00, 0x00,
+ 0xfe, 0xf7, 0x00, 0x00, 0xfe, 0xfb, 0x01, 0x00, 0xfe, 0xf0, 0x01, 0x00,
+ 0xee, 0xf9, 0x01, 0x00, 0xe4, 0xf9, 0x01, 0x00, 0xc0, 0xbf, 0x00, 0x00,
+ 0xc0, 0x3f, 0x00, 0x00, 0x80, 0x19, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_CONTEXT_MENU */
+static const unsigned char moz_menu_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0xfd, 0x00, 0x00,
+ 0xfc, 0xff, 0x00, 0x00, 0x7c, 0x84, 0x00, 0x00, 0x6c, 0xfc, 0x00, 0x00,
+ 0xc4, 0x84, 0x00, 0x00, 0xc0, 0xfc, 0x00, 0x00, 0x80, 0x85, 0x00, 0x00,
+ 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_menu_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0xfd, 0x00, 0x00, 0xfe, 0xff, 0x01, 0x00,
+ 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xff, 0x01, 0x00, 0xfe, 0xfe, 0x01, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0x80, 0xfd, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_SPINNING */
+static const unsigned char moz_spinning_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x00, 0x00, 0x00,
+ 0x0c, 0x00, 0x00, 0x00, 0x1c, 0x00, 0x00, 0x00, 0x3c, 0x00, 0x00, 0x00,
+ 0x7c, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0xfc, 0x01, 0x00, 0x00,
+ 0xfc, 0x3b, 0x00, 0x00, 0x7c, 0x38, 0x00, 0x00, 0x6c, 0x54, 0x00, 0x00,
+ 0xc4, 0xdc, 0x00, 0x00, 0xc0, 0x44, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00,
+ 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_spinning_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x0e, 0x00, 0x00, 0x00,
+ 0x1e, 0x00, 0x00, 0x00, 0x3e, 0x00, 0x00, 0x00, 0x7e, 0x00, 0x00, 0x00,
+ 0xfe, 0x00, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfe, 0x3b, 0x00, 0x00,
+ 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xfe, 0xfe, 0x00, 0x00,
+ 0xee, 0xff, 0x01, 0x00, 0xe4, 0xff, 0x00, 0x00, 0xc0, 0x7f, 0x00, 0x00,
+ 0xc0, 0x7f, 0x00, 0x00, 0x80, 0x39, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ZOOM_IN */
+static const unsigned char moz_zoom_in_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x62, 0x04, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x61, 0x08, 0x00, 0x00, 0x62, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_zoom_in_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_ZOOM_OUT */
+static const unsigned char moz_zoom_out_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0x0c, 0x03, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0xf9, 0x09, 0x00, 0x00,
+ 0xf9, 0x09, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x02, 0x04, 0x00, 0x00,
+ 0x02, 0x04, 0x00, 0x00, 0x0c, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_zoom_out_mask_bits[] = {
+ 0xf0, 0x00, 0x00, 0x00, 0xfc, 0x03, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00,
+ 0xff, 0x0f, 0x00, 0x00, 0xff, 0x0f, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0xfc, 0x0f, 0x00, 0x00, 0xf0, 0x1c, 0x00, 0x00,
+ 0x00, 0x38, 0x00, 0x00, 0x00, 0x70, 0x00, 0x00, 0x00, 0xe0, 0x00, 0x00,
+ 0x00, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NOT_ALLOWED */
+static const unsigned char moz_not_allowed_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0xf0, 0xf0, 0x00, 0x00, 0x38, 0xc0, 0x01, 0x00, 0x7c, 0x80, 0x03, 0x00,
+ 0xec, 0x00, 0x03, 0x00, 0xce, 0x01, 0x07, 0x00, 0x86, 0x03, 0x06, 0x00,
+ 0x06, 0x07, 0x06, 0x00, 0x06, 0x0e, 0x06, 0x00, 0x06, 0x1c, 0x06, 0x00,
+ 0x0e, 0x38, 0x07, 0x00, 0x0c, 0x70, 0x03, 0x00, 0x1c, 0xe0, 0x03, 0x00,
+ 0x38, 0xc0, 0x01, 0x00, 0xf0, 0xf0, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00,
+ 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_not_allowed_mask_bits[] = {
+ 0x80, 0x1f, 0x00, 0x00, 0xe0, 0x7f, 0x00, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xf8, 0xff, 0x01, 0x00, 0xfc, 0xf0, 0x03, 0x00, 0xfe, 0xc0, 0x07, 0x00,
+ 0xfe, 0x81, 0x07, 0x00, 0xff, 0x83, 0x0f, 0x00, 0xcf, 0x07, 0x0f, 0x00,
+ 0x8f, 0x0f, 0x0f, 0x00, 0x0f, 0x1f, 0x0f, 0x00, 0x0f, 0x3e, 0x0f, 0x00,
+ 0x1f, 0xfc, 0x0f, 0x00, 0x1e, 0xf8, 0x07, 0x00, 0x3e, 0xf0, 0x07, 0x00,
+ 0xfc, 0xf0, 0x03, 0x00, 0xf8, 0xff, 0x01, 0x00, 0xf0, 0xff, 0x00, 0x00,
+ 0xe0, 0x7f, 0x00, 0x00, 0x80, 0x1f, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_VERTICAL_TEXT */
+static const unsigned char moz_vertical_text_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00,
+ 0x06, 0x60, 0x00, 0x00, 0xfc, 0x3f, 0x00, 0x00, 0x06, 0x60, 0x00, 0x00,
+ 0x02, 0x40, 0x00, 0x00, 0x02, 0x40, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_vertical_text_mask_bits[] = {
+ 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x0f, 0xf0, 0x00, 0x00,
+ 0xff, 0xff, 0x00, 0x00, 0xfe, 0x7f, 0x00, 0x00, 0xff, 0xff, 0x00, 0x00,
+ 0x0f, 0xf0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00, 0x07, 0xe0, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NESW_RESIZE */
+static const unsigned char moz_nesw_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xc0, 0xff, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0x00, 0xbe, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x02, 0xb4, 0x00, 0x00, 0x02, 0xa2, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x8a, 0x80, 0x00, 0x00, 0x5a, 0x80, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x7a, 0x00, 0x00, 0x00, 0xfa, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfe, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_nesw_resize_mask_bits[] = {
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0x00, 0xff, 0x01, 0x00, 0x00, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x07, 0xfe, 0x01, 0x00, 0x07, 0xf7, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0xdf, 0xc1, 0x01, 0x00, 0xff, 0xc0, 0x01, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0x00, 0x00, 0x00, 0xff, 0x01, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NWSE_RESIZE */
+static const unsigned char moz_nwse_resize_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0xfe, 0x07, 0x00, 0x00, 0x02, 0x00, 0x00, 0x00,
+ 0xfa, 0x00, 0x00, 0x00, 0x7a, 0x00, 0x00, 0x00, 0x3a, 0x00, 0x00, 0x00,
+ 0x5a, 0x80, 0x00, 0x00, 0x8a, 0x80, 0x00, 0x00, 0x02, 0x81, 0x00, 0x00,
+ 0x02, 0xa2, 0x00, 0x00, 0x02, 0xb4, 0x00, 0x00, 0x00, 0xb8, 0x00, 0x00,
+ 0x00, 0xbc, 0x00, 0x00, 0x00, 0xbe, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00,
+ 0xc0, 0xff, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_nwse_resize_mask_bits[] = {
+ 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00, 0xff, 0x07, 0x00, 0x00,
+ 0xff, 0x01, 0x00, 0x00, 0xff, 0x00, 0x00, 0x00, 0x7f, 0x00, 0x00, 0x00,
+ 0xff, 0xc0, 0x01, 0x00, 0xdf, 0xc1, 0x01, 0x00, 0x8f, 0xe3, 0x01, 0x00,
+ 0x07, 0xf7, 0x01, 0x00, 0x07, 0xfe, 0x01, 0x00, 0x00, 0xfc, 0x01, 0x00,
+ 0x00, 0xfe, 0x01, 0x00, 0x00, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00,
+ 0xc0, 0xff, 0x01, 0x00, 0xc0, 0xff, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+/* MOZ_CURSOR_NONE */
+static const unsigned char moz_none_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+static const unsigned char moz_none_mask_bits[] = {
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+ 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
+
+enum {
+ MOZ_CURSOR_HAND_GRAB,
+ MOZ_CURSOR_HAND_GRABBING,
+ MOZ_CURSOR_COPY,
+ MOZ_CURSOR_ALIAS,
+ MOZ_CURSOR_CONTEXT_MENU,
+ MOZ_CURSOR_SPINNING,
+ MOZ_CURSOR_ZOOM_IN,
+ MOZ_CURSOR_ZOOM_OUT,
+ MOZ_CURSOR_NOT_ALLOWED,
+ MOZ_CURSOR_VERTICAL_TEXT,
+ MOZ_CURSOR_NESW_RESIZE,
+ MOZ_CURSOR_NWSE_RESIZE,
+ MOZ_CURSOR_NONE
+};
+
+// create custom pixmap cursor. The hash values must stay in sync with the
+// bitmap data above. To see the hash function, have a look at XcursorImageHash
+// in libXcursor
+static const nsGtkCursor GtkCursors[] = {
+ {moz_hand_grab_bits, moz_hand_grab_mask_bits, 10, 10,
+ "5aca4d189052212118709018842178c0"},
+ {moz_hand_grabbing_bits, moz_hand_grabbing_mask_bits, 10, 10,
+ "208530c400c041818281048008011002"},
+ {moz_copy_bits, moz_copy_mask_bits, 2, 2,
+ "08ffe1cb5fe6fc01f906f1c063814ccf"},
+ {moz_alias_bits, moz_alias_mask_bits, 2, 2,
+ "0876e1c15ff2fc01f906f1c363074c0f"},
+ {moz_menu_bits, moz_menu_mask_bits, 2, 2,
+ "08ffe1e65f80fcfdf9fff11263e74c48"},
+ {moz_spinning_bits, moz_spinning_mask_bits, 2, 2,
+ "08e8e1c95fe2fc01f976f1e063a24ccd"},
+ {moz_zoom_in_bits, moz_zoom_in_mask_bits, 6, 6,
+ "f41c0e382c94c0958e07017e42b00462"},
+ {moz_zoom_out_bits, moz_zoom_out_mask_bits, 6, 6,
+ "f41c0e382c97c0938e07017e42800402"},
+ {moz_not_allowed_bits, moz_not_allowed_mask_bits, 9, 9,
+ "03b6e0fcb3499374a867d041f52298f0"},
+ {moz_vertical_text_bits, moz_vertical_text_mask_bits, 8, 4,
+ "048008013003cff3c00c801001200000"},
+ {moz_nesw_resize_bits, moz_nesw_resize_mask_bits, 8, 8,
+ "50585d75b494802d0151028115016902"},
+ {moz_nwse_resize_bits, moz_nwse_resize_mask_bits, 8, 8,
+ "38c5dff7c7b8962045400281044508d2"},
+ {moz_none_bits, moz_none_mask_bits, 0, 0, nullptr}};
+
+#endif /* nsGtkCursors_h__ */
diff --git a/widget/gtk/nsGtkKeyUtils.cpp b/widget/gtk/nsGtkKeyUtils.cpp
new file mode 100644
index 0000000000..7157a09664
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -0,0 +1,2541 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "mozilla/Logging.h"
+
+#include "nsGtkKeyUtils.h"
+
+#include <gdk/gdkkeysyms.h>
+#include <algorithm>
+#include <gdk/gdk.h>
+#include <dlfcn.h>
+#include <gdk/gdkkeysyms-compat.h>
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include <X11/XKBlib.h>
+# include "X11UndefineNone.h"
+#endif
+#include "IMContextWrapper.h"
+#include "WidgetUtils.h"
+#include "WidgetUtilsGtk.h"
+#include "x11/keysym2ucs.h"
+#include "nsContentUtils.h"
+#include "nsGtkUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+
+#ifdef MOZ_WAYLAND
+# include <sys/mman.h>
+# include "nsWaylandDisplay.h"
+#endif
+
+// For collecting other people's log, tell them `MOZ_LOG=KeyboardHandler:4,sync`
+// rather than `MOZ_LOG=KeyboardHandler:5,sync` since using `5` may create too
+// big file.
+// Therefore you shouldn't use `LogLevel::Verbose` for logging usual behavior.
+mozilla::LazyLogModule gKeyLog("KeyboardHandler");
+
+namespace mozilla {
+namespace widget {
+
+#define IS_ASCII_ALPHABETICAL(key) \
+ ((('a' <= key) && (key <= 'z')) || (('A' <= key) && (key <= 'Z')))
+
+#define MOZ_MODIFIER_KEYS "MozKeymapWrapper"
+
+KeymapWrapper* KeymapWrapper::sInstance = nullptr;
+guint KeymapWrapper::sLastRepeatableHardwareKeyCode = 0;
+#ifdef MOZ_X11
+Time KeymapWrapper::sLastRepeatableKeyTime = 0;
+#endif
+KeymapWrapper::RepeatState KeymapWrapper::sRepeatState =
+ KeymapWrapper::NOT_PRESSED;
+
+#ifdef MOZ_WAYLAND
+wl_seat* KeymapWrapper::sSeat = nullptr;
+int KeymapWrapper::sSeatID = -1;
+wl_keyboard* KeymapWrapper::sKeyboard = nullptr;
+#endif
+
+static const char* GetBoolName(bool aBool) { return aBool ? "TRUE" : "FALSE"; }
+
+static const char* GetStatusName(nsEventStatus aStatus) {
+ switch (aStatus) {
+ case nsEventStatus_eConsumeDoDefault:
+ return "nsEventStatus_eConsumeDoDefault";
+ case nsEventStatus_eConsumeNoDefault:
+ return "nsEventStatus_eConsumeNoDefault";
+ case nsEventStatus_eIgnore:
+ return "nsEventStatus_eIgnore";
+ case nsEventStatus_eSentinel:
+ return "nsEventStatus_eSentinel";
+ default:
+ return "Illegal value";
+ }
+}
+
+static const nsCString GetKeyLocationName(uint32_t aLocation) {
+ switch (aLocation) {
+ case eKeyLocationLeft:
+ return "KEY_LOCATION_LEFT"_ns;
+ case eKeyLocationRight:
+ return "KEY_LOCATION_RIGHT"_ns;
+ case eKeyLocationStandard:
+ return "KEY_LOCATION_STANDARD"_ns;
+ case eKeyLocationNumpad:
+ return "KEY_LOCATION_NUMPAD"_ns;
+ default:
+ return nsPrintfCString("Unknown (0x%04X)", aLocation);
+ }
+}
+
+static const nsCString GetCharacterCodeName(char16_t aChar) {
+ switch (aChar) {
+ case 0x0000:
+ return "NULL (0x0000)"_ns;
+ case 0x0008:
+ return "BACKSPACE (0x0008)"_ns;
+ case 0x0009:
+ return "CHARACTER TABULATION (0x0009)"_ns;
+ case 0x000A:
+ return "LINE FEED (0x000A)"_ns;
+ case 0x000B:
+ return "LINE TABULATION (0x000B)"_ns;
+ case 0x000C:
+ return "FORM FEED (0x000C)"_ns;
+ case 0x000D:
+ return "CARRIAGE RETURN (0x000D)"_ns;
+ case 0x0018:
+ return "CANCEL (0x0018)"_ns;
+ case 0x001B:
+ return "ESCAPE (0x001B)"_ns;
+ case 0x0020:
+ return "SPACE (0x0020)"_ns;
+ case 0x007F:
+ return "DELETE (0x007F)"_ns;
+ case 0x00A0:
+ return "NO-BREAK SPACE (0x00A0)"_ns;
+ case 0x00AD:
+ return "SOFT HYPHEN (0x00AD)"_ns;
+ case 0x2000:
+ return "EN QUAD (0x2000)"_ns;
+ case 0x2001:
+ return "EM QUAD (0x2001)"_ns;
+ case 0x2002:
+ return "EN SPACE (0x2002)"_ns;
+ case 0x2003:
+ return "EM SPACE (0x2003)"_ns;
+ case 0x2004:
+ return "THREE-PER-EM SPACE (0x2004)"_ns;
+ case 0x2005:
+ return "FOUR-PER-EM SPACE (0x2005)"_ns;
+ case 0x2006:
+ return "SIX-PER-EM SPACE (0x2006)"_ns;
+ case 0x2007:
+ return "FIGURE SPACE (0x2007)"_ns;
+ case 0x2008:
+ return "PUNCTUATION SPACE (0x2008)"_ns;
+ case 0x2009:
+ return "THIN SPACE (0x2009)"_ns;
+ case 0x200A:
+ return "HAIR SPACE (0x200A)"_ns;
+ case 0x200B:
+ return "ZERO WIDTH SPACE (0x200B)"_ns;
+ case 0x200C:
+ return "ZERO WIDTH NON-JOINER (0x200C)"_ns;
+ case 0x200D:
+ return "ZERO WIDTH JOINER (0x200D)"_ns;
+ case 0x200E:
+ return "LEFT-TO-RIGHT MARK (0x200E)"_ns;
+ case 0x200F:
+ return "RIGHT-TO-LEFT MARK (0x200F)"_ns;
+ case 0x2029:
+ return "PARAGRAPH SEPARATOR (0x2029)"_ns;
+ case 0x202A:
+ return "LEFT-TO-RIGHT EMBEDDING (0x202A)"_ns;
+ case 0x202B:
+ return "RIGHT-TO-LEFT EMBEDDING (0x202B)"_ns;
+ case 0x202D:
+ return "LEFT-TO-RIGHT OVERRIDE (0x202D)"_ns;
+ case 0x202E:
+ return "RIGHT-TO-LEFT OVERRIDE (0x202E)"_ns;
+ case 0x202F:
+ return "NARROW NO-BREAK SPACE (0x202F)"_ns;
+ case 0x205F:
+ return "MEDIUM MATHEMATICAL SPACE (0x205F)"_ns;
+ case 0x2060:
+ return "WORD JOINER (0x2060)"_ns;
+ case 0x2066:
+ return "LEFT-TO-RIGHT ISOLATE (0x2066)"_ns;
+ case 0x2067:
+ return "RIGHT-TO-LEFT ISOLATE (0x2067)"_ns;
+ case 0x3000:
+ return "IDEOGRAPHIC SPACE (0x3000)"_ns;
+ case 0xFEFF:
+ return "ZERO WIDTH NO-BREAK SPACE (0xFEFF)"_ns;
+ default: {
+ if (aChar < ' ' || (aChar >= 0x80 && aChar < 0xA0)) {
+ return nsPrintfCString("control (0x%04X)", aChar);
+ }
+ if (NS_IS_HIGH_SURROGATE(aChar)) {
+ return nsPrintfCString("high surrogate (0x%04X)", aChar);
+ }
+ if (NS_IS_LOW_SURROGATE(aChar)) {
+ return nsPrintfCString("low surrogate (0x%04X)", aChar);
+ }
+ return nsPrintfCString("'%s' (0x%04X)",
+ NS_ConvertUTF16toUTF8(nsAutoString(aChar)).get(),
+ aChar);
+ }
+ }
+}
+
+static const nsCString GetCharacterCodeNames(const char16_t* aChars,
+ uint32_t aLength) {
+ if (!aLength) {
+ return "\"\""_ns;
+ }
+ nsCString result;
+ result.AssignLiteral("\"");
+ StringJoinAppend(result, ", "_ns, Span{aChars, aLength},
+ [](nsACString& dest, const char16_t charValue) {
+ dest.Append(GetCharacterCodeName(charValue));
+ });
+ result.AppendLiteral("\"");
+ return result;
+}
+
+static const nsCString GetCharacterCodeNames(const nsAString& aString) {
+ return GetCharacterCodeNames(aString.BeginReading(), aString.Length());
+}
+
+/* static */
+const char* KeymapWrapper::GetModifierName(MappedModifier aModifier) {
+ switch (aModifier) {
+ case CAPS_LOCK:
+ return "CapsLock";
+ case NUM_LOCK:
+ return "NumLock";
+ case SCROLL_LOCK:
+ return "ScrollLock";
+ case SHIFT:
+ return "Shift";
+ case CTRL:
+ return "Ctrl";
+ case ALT:
+ return "Alt";
+ case SUPER:
+ return "Super";
+ case HYPER:
+ return "Hyper";
+ case META:
+ return "Meta";
+ case LEVEL3:
+ return "Level3";
+ case LEVEL5:
+ return "Level5";
+ case NOT_MODIFIER:
+ return "NotModifier";
+ default:
+ return "InvalidValue";
+ }
+}
+
+/* static */
+KeymapWrapper::MappedModifier KeymapWrapper::GetModifierForGDKKeyval(
+ guint aGdkKeyval) {
+ switch (aGdkKeyval) {
+ case GDK_Caps_Lock:
+ return CAPS_LOCK;
+ case GDK_Num_Lock:
+ return NUM_LOCK;
+ case GDK_Scroll_Lock:
+ return SCROLL_LOCK;
+ case GDK_Shift_Lock:
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ return SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ return CTRL;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ return ALT;
+ case GDK_Super_L:
+ case GDK_Super_R:
+ return SUPER;
+ case GDK_Hyper_L:
+ case GDK_Hyper_R:
+ return HYPER;
+ case GDK_Meta_L:
+ case GDK_Meta_R:
+ return META;
+ case GDK_ISO_Level3_Shift:
+ case GDK_Mode_switch:
+ return LEVEL3;
+ case GDK_ISO_Level5_Shift:
+ return LEVEL5;
+ default:
+ return NOT_MODIFIER;
+ }
+}
+
+guint KeymapWrapper::GetGdkModifierMask(MappedModifier aModifier) const {
+ switch (aModifier) {
+ case CAPS_LOCK:
+ return GDK_LOCK_MASK;
+ case NUM_LOCK:
+ return mModifierMasks[INDEX_NUM_LOCK];
+ case SCROLL_LOCK:
+ return mModifierMasks[INDEX_SCROLL_LOCK];
+ case SHIFT:
+ return GDK_SHIFT_MASK;
+ case CTRL:
+ return GDK_CONTROL_MASK;
+ case ALT:
+ return mModifierMasks[INDEX_ALT];
+ case SUPER:
+ return GDK_SUPER_MASK;
+ case HYPER:
+ return mModifierMasks[INDEX_HYPER];
+ case META:
+ return mModifierMasks[INDEX_META];
+ case LEVEL3:
+ return mModifierMasks[INDEX_LEVEL3];
+ case LEVEL5:
+ return mModifierMasks[INDEX_LEVEL5];
+ default:
+ return 0;
+ }
+}
+
+KeymapWrapper::ModifierKey* KeymapWrapper::GetModifierKey(
+ guint aHardwareKeycode) {
+ for (uint32_t i = 0; i < mModifierKeys.Length(); i++) {
+ ModifierKey& key = mModifierKeys[i];
+ if (key.mHardwareKeycode == aHardwareKeycode) {
+ return &key;
+ }
+ }
+ return nullptr;
+}
+
+/* static */
+KeymapWrapper* KeymapWrapper::GetInstance() {
+ if (!sInstance) {
+ sInstance = new KeymapWrapper();
+ sInstance->Init();
+ }
+ return sInstance;
+}
+
+#ifdef MOZ_WAYLAND
+void KeymapWrapper::EnsureInstance() { (void)GetInstance(); }
+
+void KeymapWrapper::InitBySystemSettingsWayland() {
+ MOZ_UNUSED(WaylandDisplayGet());
+}
+#endif
+
+/* static */
+void KeymapWrapper::Shutdown() {
+ if (sInstance) {
+ delete sInstance;
+ sInstance = nullptr;
+ }
+}
+
+KeymapWrapper::KeymapWrapper()
+ : mInitialized(false),
+ mGdkKeymap(gdk_keymap_get_default()),
+ mXKBBaseEventCode(0),
+ mOnKeysChangedSignalHandle(0),
+ mOnDirectionChangedSignalHandle(0) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p Constructor, mGdkKeymap=%p", this, mGdkKeymap));
+
+ g_object_ref(mGdkKeymap);
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ InitXKBExtension();
+ }
+#endif
+}
+
+void KeymapWrapper::Init() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p Init, mGdkKeymap=%p", this, mGdkKeymap));
+
+ mModifierKeys.Clear();
+ memset(mModifierMasks, 0, sizeof(mModifierMasks));
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ InitBySystemSettingsX11();
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ InitBySystemSettingsWayland();
+ }
+#endif
+
+#ifdef MOZ_X11
+ gdk_window_add_filter(nullptr, FilterEvents, this);
+#endif
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p Init, CapsLock=0x%X, NumLock=0x%X, "
+ "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, "
+ "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X",
+ this, GetGdkModifierMask(CAPS_LOCK), GetGdkModifierMask(NUM_LOCK),
+ GetGdkModifierMask(SCROLL_LOCK), GetGdkModifierMask(LEVEL3),
+ GetGdkModifierMask(LEVEL5), GetGdkModifierMask(SHIFT),
+ GetGdkModifierMask(CTRL), GetGdkModifierMask(ALT),
+ GetGdkModifierMask(META), GetGdkModifierMask(SUPER),
+ GetGdkModifierMask(HYPER)));
+}
+
+#ifdef MOZ_X11
+void KeymapWrapper::InitXKBExtension() {
+ PodZero(&mKeyboardState);
+
+ int xkbMajorVer = XkbMajorVersion;
+ int xkbMinorVer = XkbMinorVersion;
+ if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbLibraryVersion()",
+ this));
+ return;
+ }
+
+ Display* display = gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ // XkbLibraryVersion() set xkbMajorVer and xkbMinorVer to that of the
+ // library, which may be newer than what is required of the server in
+ // XkbQueryExtension(), so these variables should be reset to
+ // XkbMajorVersion and XkbMinorVersion before the XkbQueryExtension call.
+ xkbMajorVer = XkbMajorVersion;
+ xkbMinorVer = XkbMinorVersion;
+ int opcode, baseErrorCode;
+ if (!XkbQueryExtension(display, &opcode, &mXKBBaseEventCode, &baseErrorCode,
+ &xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbQueryExtension(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbStateNotify,
+ XkbModifierStateMask, XkbModifierStateMask)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XModifierStateMask, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XkbSelectEventDetails(display, XkbUseCoreKbd, XkbControlsNotify,
+ XkbPerKeyRepeatMask, XkbPerKeyRepeatMask)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XGetKeyboardControl(display, &mKeyboardState)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XGetKeyboardControl(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info, ("%p InitXKBExtension, Succeeded", this));
+}
+
+void KeymapWrapper::InitBySystemSettingsX11() {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettingsX11, mGdkKeymap=%p", this, mGdkKeymap));
+
+ if (!mOnKeysChangedSignalHandle) {
+ mOnKeysChangedSignalHandle = g_signal_connect(
+ mGdkKeymap, "keys-changed", (GCallback)OnKeysChanged, this);
+ }
+ if (!mOnDirectionChangedSignalHandle) {
+ mOnDirectionChangedSignalHandle = g_signal_connect(
+ mGdkKeymap, "direction-changed", (GCallback)OnDirectionChanged, this);
+ }
+
+ Display* display = gdk_x11_display_get_xdisplay(gdk_display_get_default());
+
+ int min_keycode = 0;
+ int max_keycode = 0;
+ XDisplayKeycodes(display, &min_keycode, &max_keycode);
+
+ int keysyms_per_keycode = 0;
+ KeySym* xkeymap =
+ XGetKeyboardMapping(display, min_keycode, max_keycode - min_keycode + 1,
+ &keysyms_per_keycode);
+ if (!xkeymap) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xkeymap",
+ this));
+ return;
+ }
+
+ XModifierKeymap* xmodmap = XGetModifierMapping(display);
+ if (!xmodmap) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xmodmap",
+ this));
+ XFree(xkeymap);
+ return;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettings, min_keycode=%d, "
+ "max_keycode=%d, keysyms_per_keycode=%d, max_keypermod=%d",
+ this, min_keycode, max_keycode, keysyms_per_keycode,
+ xmodmap->max_keypermod));
+
+ // The modifiermap member of the XModifierKeymap structure contains 8 sets
+ // of max_keypermod KeyCodes, one for each modifier in the order Shift,
+ // Lock, Control, Mod1, Mod2, Mod3, Mod4, and Mod5.
+ // Only nonzero KeyCodes have meaning in each set, and zero KeyCodes are
+ // ignored.
+
+ // Note that two or more modifiers may use one modifier flag. E.g.,
+ // on Ubuntu 10.10, Alt and Meta share the Mod1 in default settings.
+ // And also Super and Hyper share the Mod4. In such cases, we need to
+ // decide which modifier flag means one of DOM modifiers.
+
+ // mod[0] is Modifier introduced by Mod1.
+ MappedModifier mod[5];
+ int32_t foundLevel[5];
+ for (uint32_t i = 0; i < ArrayLength(mod); i++) {
+ mod[i] = NOT_MODIFIER;
+ foundLevel[i] = INT32_MAX;
+ }
+ const uint32_t map_size = 8 * xmodmap->max_keypermod;
+ for (uint32_t i = 0; i < map_size; i++) {
+ KeyCode keycode = xmodmap->modifiermap[i];
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " i=%d, keycode=0x%08X",
+ this, i, keycode));
+ if (!keycode || keycode < min_keycode || keycode > max_keycode) {
+ continue;
+ }
+
+ ModifierKey* modifierKey = GetModifierKey(keycode);
+ if (!modifierKey) {
+ modifierKey = mModifierKeys.AppendElement(ModifierKey(keycode));
+ }
+
+ const KeySym* syms =
+ xkeymap + (keycode - min_keycode) * keysyms_per_keycode;
+ const uint32_t bit = i / xmodmap->max_keypermod;
+ modifierKey->mMask |= 1 << bit;
+
+ // We need to know the meaning of Mod1, Mod2, Mod3, Mod4 and Mod5.
+ // Let's skip if current map is for others.
+ if (bit < 3) {
+ continue;
+ }
+
+ const int32_t modIndex = bit - 3;
+ for (int32_t j = 0; j < keysyms_per_keycode; j++) {
+ MappedModifier modifier = GetModifierForGDKKeyval(syms[j]);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ " Mod%d, j=%d, syms[j]=%s(0x%lX), modifier=%s",
+ this, modIndex + 1, j, gdk_keyval_name(syms[j]), syms[j],
+ GetModifierName(modifier)));
+
+ switch (modifier) {
+ case NOT_MODIFIER:
+ // Don't overwrite the stored information with
+ // NOT_MODIFIER.
+ break;
+ case CAPS_LOCK:
+ case SHIFT:
+ case CTRL:
+ case SUPER:
+ // Ignore the modifiers defined in GDK spec. They shouldn't
+ // be mapped to Mod1-5 because they must not work on native
+ // GTK applications.
+ break;
+ default:
+ // If new modifier is found in higher level than stored
+ // value, we don't need to overwrite it.
+ if (j > foundLevel[modIndex]) {
+ break;
+ }
+ // If new modifier is more important than stored value,
+ // we should overwrite it with new modifier.
+ if (j == foundLevel[modIndex]) {
+ mod[modIndex] = std::min(modifier, mod[modIndex]);
+ break;
+ }
+ foundLevel[modIndex] = j;
+ mod[modIndex] = modifier;
+ break;
+ }
+ }
+ }
+
+ for (uint32_t i = 0; i < COUNT_OF_MODIFIER_INDEX; i++) {
+ MappedModifier modifier;
+ switch (i) {
+ case INDEX_NUM_LOCK:
+ modifier = NUM_LOCK;
+ break;
+ case INDEX_SCROLL_LOCK:
+ modifier = SCROLL_LOCK;
+ break;
+ case INDEX_ALT:
+ modifier = ALT;
+ break;
+ case INDEX_META:
+ modifier = META;
+ break;
+ case INDEX_HYPER:
+ modifier = HYPER;
+ break;
+ case INDEX_LEVEL3:
+ modifier = LEVEL3;
+ break;
+ case INDEX_LEVEL5:
+ modifier = LEVEL5;
+ break;
+ default:
+ MOZ_CRASH("All indexes must be handled here");
+ }
+ for (uint32_t j = 0; j < ArrayLength(mod); j++) {
+ if (modifier == mod[j]) {
+ mModifierMasks[i] |= 1 << (j + 3);
+ }
+ }
+ }
+
+ XFreeModifiermap(xmodmap);
+ XFree(xkeymap);
+}
+#endif
+
+#ifdef MOZ_WAYLAND
+void KeymapWrapper::SetModifierMask(xkb_keymap* aKeymap,
+ ModifierIndex aModifierIndex,
+ const char* aModifierName) {
+ static auto sXkbKeymapModGetIndex =
+ (xkb_mod_index_t(*)(struct xkb_keymap*, const char*))dlsym(
+ RTLD_DEFAULT, "xkb_keymap_mod_get_index");
+
+ xkb_mod_index_t index = sXkbKeymapModGetIndex(aKeymap, aModifierName);
+ if (index != XKB_MOD_INVALID) {
+ mModifierMasks[aModifierIndex] = (1 << index);
+ }
+}
+
+void KeymapWrapper::SetModifierMasks(xkb_keymap* aKeymap) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ // This mapping is derived from get_xkb_modifiers() at gdkkeys-wayland.c
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_NUM_LOCK, XKB_MOD_NAME_NUM);
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_ALT, XKB_MOD_NAME_ALT);
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_META, "Meta");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_HYPER, "Hyper");
+
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_SCROLL_LOCK, "ScrollLock");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL3, "Level3");
+ keymapWrapper->SetModifierMask(aKeymap, INDEX_LEVEL5, "Level5");
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p KeymapWrapper::SetModifierMasks, CapsLock=0x%X, NumLock=0x%X, "
+ "ScrollLock=0x%X, Level3=0x%X, Level5=0x%X, "
+ "Shift=0x%X, Ctrl=0x%X, Alt=0x%X, Meta=0x%X, Super=0x%X, Hyper=0x%X",
+ keymapWrapper, keymapWrapper->GetGdkModifierMask(CAPS_LOCK),
+ keymapWrapper->GetGdkModifierMask(NUM_LOCK),
+ keymapWrapper->GetGdkModifierMask(SCROLL_LOCK),
+ keymapWrapper->GetGdkModifierMask(LEVEL3),
+ keymapWrapper->GetGdkModifierMask(LEVEL5),
+ keymapWrapper->GetGdkModifierMask(SHIFT),
+ keymapWrapper->GetGdkModifierMask(CTRL),
+ keymapWrapper->GetGdkModifierMask(ALT),
+ keymapWrapper->GetGdkModifierMask(META),
+ keymapWrapper->GetGdkModifierMask(SUPER),
+ keymapWrapper->GetGdkModifierMask(HYPER)));
+}
+
+/* This keymap routine is derived from weston-2.0.0/clients/simple-im.c
+ */
+static void keyboard_handle_keymap(void* data, struct wl_keyboard* wl_keyboard,
+ uint32_t format, int fd, uint32_t size) {
+ KeymapWrapper::ResetKeyboard();
+
+ if (format != WL_KEYBOARD_KEYMAP_FORMAT_XKB_V1) {
+ close(fd);
+ return;
+ }
+
+ char* mapString = (char*)mmap(NULL, size, PROT_READ, MAP_SHARED, fd, 0);
+ if (mapString == MAP_FAILED) {
+ close(fd);
+ return;
+ }
+
+ static auto sXkbContextNew =
+ (struct xkb_context * (*)(enum xkb_context_flags))
+ dlsym(RTLD_DEFAULT, "xkb_context_new");
+ static auto sXkbKeymapNewFromString =
+ (struct xkb_keymap * (*)(struct xkb_context*, const char*,
+ enum xkb_keymap_format,
+ enum xkb_keymap_compile_flags))
+ dlsym(RTLD_DEFAULT, "xkb_keymap_new_from_string");
+
+ struct xkb_context* xkb_context = sXkbContextNew(XKB_CONTEXT_NO_FLAGS);
+ struct xkb_keymap* keymap =
+ sXkbKeymapNewFromString(xkb_context, mapString, XKB_KEYMAP_FORMAT_TEXT_V1,
+ XKB_KEYMAP_COMPILE_NO_FLAGS);
+
+ munmap(mapString, size);
+ close(fd);
+
+ if (!keymap) {
+ NS_WARNING("keyboard_handle_keymap(): Failed to compile keymap!\n");
+ return;
+ }
+
+ KeymapWrapper::SetModifierMasks(keymap);
+
+ static auto sXkbKeymapUnRef =
+ (void (*)(struct xkb_keymap*))dlsym(RTLD_DEFAULT, "xkb_keymap_unref");
+ sXkbKeymapUnRef(keymap);
+
+ static auto sXkbContextUnref =
+ (void (*)(struct xkb_context*))dlsym(RTLD_DEFAULT, "xkb_context_unref");
+ sXkbContextUnref(xkb_context);
+}
+
+static void keyboard_handle_enter(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, struct wl_surface* surface,
+ struct wl_array* keys) {
+ KeymapWrapper::SetFocusIn(surface, serial);
+}
+
+static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, struct wl_surface* surface) {
+ KeymapWrapper::SetFocusOut(surface);
+}
+
+static void keyboard_handle_key(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, uint32_t time, uint32_t key,
+ uint32_t state) {}
+static void keyboard_handle_modifiers(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, uint32_t mods_depressed,
+ uint32_t mods_latched,
+ uint32_t mods_locked, uint32_t group) {}
+static void keyboard_handle_repeat_info(void* data,
+ struct wl_keyboard* keyboard,
+ int32_t rate, int32_t delay) {}
+
+static const struct wl_keyboard_listener keyboard_listener = {
+ keyboard_handle_keymap, keyboard_handle_enter,
+ keyboard_handle_leave, keyboard_handle_key,
+ keyboard_handle_modifiers, keyboard_handle_repeat_info};
+
+static void seat_handle_capabilities(void* data, struct wl_seat* seat,
+ unsigned int caps) {
+ wl_keyboard* keyboard = KeymapWrapper::GetKeyboard();
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
+ keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr);
+ KeymapWrapper::SetKeyboard(keyboard);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
+ KeymapWrapper::ClearKeyboard();
+ }
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_handle_capabilities,
+};
+
+#endif
+
+KeymapWrapper::~KeymapWrapper() {
+#ifdef MOZ_X11
+ gdk_window_remove_filter(nullptr, FilterEvents, this);
+#endif
+ if (mOnKeysChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnKeysChangedSignalHandle);
+ }
+ if (mOnDirectionChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnDirectionChangedSignalHandle);
+ }
+ g_object_unref(mGdkKeymap);
+ MOZ_LOG(gKeyLog, LogLevel::Info, ("%p Destructor", this));
+}
+
+#ifdef MOZ_X11
+/* static */
+GdkFilterReturn KeymapWrapper::FilterEvents(GdkXEvent* aXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aData) {
+ XEvent* xEvent = static_cast<XEvent*>(aXEvent);
+ switch (xEvent->type) {
+ case KeyPress: {
+ // If the key doesn't support auto repeat, ignore the event because
+ // even if such key (e.g., Shift) is pressed during auto repeat of
+ // anoter key, it doesn't stop the auto repeat.
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (!self->IsAutoRepeatableKey(xEvent->xkey.keycode)) {
+ break;
+ }
+ if (sRepeatState == NOT_PRESSED) {
+ sRepeatState = FIRST_PRESS;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected first keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ } else if (sLastRepeatableHardwareKeyCode == xEvent->xkey.keycode) {
+ if (sLastRepeatableKeyTime == xEvent->xkey.time &&
+ sLastRepeatableHardwareKeyCode ==
+ IMContextWrapper::
+ GetWaitingSynthesizedKeyPressHardwareKeyCode()) {
+ // On some environment, IM may generate duplicated KeyPress event
+ // without any special state flags. In such case, we shouldn't
+ // treat the event as "repeated".
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "igored keypress since it must be synthesized by IME",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ break;
+ }
+ sRepeatState = REPEATING;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected repeating keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ } else {
+ // If a different key is pressed while another key is pressed,
+ // auto repeat system repeats only the last pressed key.
+ // So, setting new keycode and setting repeat state as first key
+ // press should work fine.
+ sRepeatState = FIRST_PRESS;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyPress, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected different keypress",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ }
+ sLastRepeatableHardwareKeyCode = xEvent->xkey.keycode;
+ sLastRepeatableKeyTime = xEvent->xkey.time;
+ break;
+ }
+ case KeyRelease: {
+ if (sLastRepeatableHardwareKeyCode != xEvent->xkey.keycode) {
+ // This case means the key release event is caused by
+ // a non-repeatable key such as Shift or a repeatable key that
+ // was pressed before sLastRepeatableHardwareKeyCode was
+ // pressed.
+ break;
+ }
+ sRepeatState = NOT_PRESSED;
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("FilterEvents(aXEvent={ type=KeyRelease, "
+ "xkey={ keycode=0x%08X, state=0x%08X, time=%lu } }, "
+ "aGdkEvent={ state=0x%08X }), "
+ "detected key release",
+ xEvent->xkey.keycode, xEvent->xkey.state, xEvent->xkey.time,
+ reinterpret_cast<GdkEventKey*>(aGdkEvent)->state));
+ break;
+ }
+ case FocusOut: {
+ // At moving focus, we should reset keyboard repeat state.
+ // Strictly, this causes incorrect behavior. However, this
+ // correctness must be enough for web applications.
+ sRepeatState = NOT_PRESSED;
+ break;
+ }
+ default: {
+ KeymapWrapper* self = static_cast<KeymapWrapper*>(aData);
+ if (xEvent->type != self->mXKBBaseEventCode) {
+ break;
+ }
+ XkbEvent* xkbEvent = (XkbEvent*)xEvent;
+ if (xkbEvent->any.xkb_type != XkbControlsNotify ||
+ !(xkbEvent->ctrls.changed_ctrls & XkbPerKeyRepeatMask)) {
+ break;
+ }
+ if (!XGetKeyboardControl(xkbEvent->any.display, &self->mKeyboardState)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p FilterEvents failed due to failure "
+ "of XGetKeyboardControl(), display=0x%p",
+ self, xkbEvent->any.display));
+ }
+ break;
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+#endif
+
+static void ResetBidiKeyboard() {
+ // Reset the bidi keyboard settings for the new GdkKeymap
+ nsCOMPtr<nsIBidiKeyboard> bidiKeyboard = nsContentUtils::GetBidiKeyboard();
+ if (bidiKeyboard) {
+ bidiKeyboard->Reset();
+ }
+ WidgetUtils::SendBidiKeyboardInfoToContent();
+}
+
+/* static */
+void KeymapWrapper::ResetKeyboard() {
+ if (sInstance) {
+ sInstance->mInitialized = false;
+ ResetBidiKeyboard();
+ }
+}
+
+/* static */
+void KeymapWrapper::OnKeysChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("OnKeysChanged, aGdkKeymap=%p, aKeymapWrapper=%p", aGdkKeymap,
+ aKeymapWrapper));
+
+ MOZ_ASSERT(sInstance == aKeymapWrapper,
+ "This instance must be the singleton instance");
+
+ // We cannot reintialize here becasue we don't have GdkWindow which is using
+ // the GdkKeymap. We'll reinitialize it when next GetInstance() is called.
+ ResetKeyboard();
+}
+
+// static
+void KeymapWrapper::OnDirectionChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper) {
+ // XXX
+ // A lot of diretion-changed signal might be fired on switching bidi
+ // keyboard when using both ibus (with arabic layout) and fcitx (with IME).
+ // See https://github.com/fcitx/fcitx/issues/257
+ //
+ // Also, when using ibus, switching to IM might not cause this signal.
+ // See https://github.com/ibus/ibus/issues/1848
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("OnDirectionChanged, aGdkKeymap=%p, aKeymapWrapper=%p", aGdkKeymap,
+ aKeymapWrapper));
+
+ ResetBidiKeyboard();
+}
+
+/* static */
+guint KeymapWrapper::GetCurrentModifierState() {
+ GdkModifierType modifiers;
+ GdkDisplay* display = gdk_display_get_default();
+ GdkScreen* screen = gdk_display_get_default_screen(display);
+ GdkWindow* window = gdk_screen_get_root_window(screen);
+ gdk_window_get_device_position(window, GdkGetPointer(), nullptr, nullptr,
+ &modifiers);
+ return static_cast<guint>(modifiers);
+}
+
+/* static */
+bool KeymapWrapper::AreModifiersActive(MappedModifiers aModifiers,
+ guint aGdkModifierState) {
+ NS_ENSURE_TRUE(aModifiers, false);
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+ for (uint32_t i = 0; i < sizeof(MappedModifier) * 8 && aModifiers; i++) {
+ MappedModifier modifier = static_cast<MappedModifier>(1 << i);
+ // Is the binary position used by modifier?
+ if (!(aModifiers & modifier)) {
+ continue;
+ }
+ // Is the modifier active?
+ if (!(aGdkModifierState & keymapWrapper->GetGdkModifierMask(modifier))) {
+ return false;
+ }
+ aModifiers &= ~modifier;
+ }
+ return true;
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeCurrentKeyModifiers() {
+ return ComputeKeyModifiers(GetCurrentModifierState());
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeKeyModifiers(guint aGdkModifierState) {
+ uint32_t keyModifiers = 0;
+ if (!aGdkModifierState) {
+ return keyModifiers;
+ }
+
+ // DOM Meta key should be TRUE only on Mac. We need to discuss this
+ // issue later.
+ KeymapWrapper* keymapWrapper = GetInstance();
+ if (keymapWrapper->AreModifiersActive(SHIFT, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_SHIFT;
+ }
+ if (keymapWrapper->AreModifiersActive(CTRL, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_CONTROL;
+ }
+ if (keymapWrapper->AreModifiersActive(ALT, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_ALT;
+ }
+ if (keymapWrapper->AreModifiersActive(SUPER, aGdkModifierState) ||
+ keymapWrapper->AreModifiersActive(HYPER, aGdkModifierState) ||
+ // "Meta" state is typically mapped to `Alt` + `Shift`, but we ignore the
+ // state if `Alt` is mapped to "Alt" state. Additionally it's mapped to
+ // `Win` in Sun/Solaris keyboard layout. In this case, we want to treat
+ // them as DOM Meta modifier keys like "Super" state in the major Linux
+ // environments.
+ keymapWrapper->AreModifiersActive(META, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_META;
+ }
+ if (keymapWrapper->AreModifiersActive(LEVEL3, aGdkModifierState) ||
+ keymapWrapper->AreModifiersActive(LEVEL5, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_ALTGRAPH;
+ }
+ if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(NUM_LOCK, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aGdkModifierState)) {
+ keyModifiers |= MODIFIER_SCROLLLOCK;
+ }
+ return keyModifiers;
+}
+
+/* static */
+guint KeymapWrapper::ConvertWidgetModifierToGdkState(
+ nsIWidget::Modifiers aNativeModifiers) {
+ if (!aNativeModifiers) {
+ return 0;
+ }
+ struct ModifierMapEntry {
+ nsIWidget::Modifiers mWidgetModifier;
+ MappedModifier mModifier;
+ };
+ // TODO: Currently, we don't treat L/R of each modifier on Linux.
+ // TODO: No proper native modifier for Level5.
+ static constexpr ModifierMapEntry sModifierMap[] = {
+ {nsIWidget::CAPS_LOCK, MappedModifier::CAPS_LOCK},
+ {nsIWidget::NUM_LOCK, MappedModifier::NUM_LOCK},
+ {nsIWidget::SHIFT_L, MappedModifier::SHIFT},
+ {nsIWidget::SHIFT_R, MappedModifier::SHIFT},
+ {nsIWidget::CTRL_L, MappedModifier::CTRL},
+ {nsIWidget::CTRL_R, MappedModifier::CTRL},
+ {nsIWidget::ALT_L, MappedModifier::ALT},
+ {nsIWidget::ALT_R, MappedModifier::ALT},
+ {nsIWidget::ALTGRAPH, MappedModifier::LEVEL3},
+ {nsIWidget::COMMAND_L, MappedModifier::SUPER},
+ {nsIWidget::COMMAND_R, MappedModifier::SUPER}};
+
+ guint state = 0;
+ KeymapWrapper* instance = GetInstance();
+ for (const ModifierMapEntry& entry : sModifierMap) {
+ if (aNativeModifiers & entry.mWidgetModifier) {
+ state |= instance->GetGdkModifierMask(entry.mModifier);
+ }
+ }
+ return state;
+}
+
+/* static */
+void KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aGdkModifierState) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aInputEvent.mModifiers = ComputeKeyModifiers(aGdkModifierState);
+
+ // Don't log this method for non-important events because e.g., eMouseMove is
+ // just noisy and there is no reason to log it.
+ bool doLog = aInputEvent.mMessage != eMouseMove;
+ if (doLog) {
+ MOZ_LOG(gKeyLog, LogLevel::Debug,
+ ("%p InitInputEvent, aGdkModifierState=0x%08X, "
+ "aInputEvent={ mMessage=%s, mModifiers=0x%04X (Shift: %s, "
+ "Control: %s, Alt: %s, Meta: %s, AltGr: %s, "
+ "CapsLock: %s, NumLock: %s, ScrollLock: %s })",
+ keymapWrapper, aGdkModifierState, ToChar(aInputEvent.mMessage),
+ aInputEvent.mModifiers,
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SHIFT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CONTROL),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALT),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_META),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_ALTGRAPH),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_CAPSLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_NUMLOCK),
+ GetBoolName(aInputEvent.mModifiers & MODIFIER_SCROLLLOCK)));
+ }
+
+ switch (aInputEvent.mClass) {
+ case eMouseEventClass:
+ case eMouseScrollEventClass:
+ case eWheelEventClass:
+ case eDragEventClass:
+ case eSimpleGestureEventClass:
+ break;
+ default:
+ return;
+ }
+
+ WidgetMouseEventBase& mouseEvent = *aInputEvent.AsMouseEventBase();
+ mouseEvent.mButtons = 0;
+ if (aGdkModifierState & GDK_BUTTON1_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (aGdkModifierState & GDK_BUTTON3_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (aGdkModifierState & GDK_BUTTON2_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+
+ if (doLog) {
+ MOZ_LOG(
+ gKeyLog, LogLevel::Debug,
+ ("%p InitInputEvent, aInputEvent has mButtons, "
+ "aInputEvent.mButtons=0x%04X (Left: %s, Right: %s, Middle: %s, "
+ "4th (BACK): %s, 5th (FORWARD): %s)",
+ keymapWrapper, mouseEvent.mButtons,
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::ePrimaryFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::eSecondaryFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::eMiddleFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::e4thFlag),
+ GetBoolName(mouseEvent.mButtons & MouseButtonsFlag::e5thFlag)));
+ }
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent) {
+ // If the keyval indicates it's a modifier key, we should use unshifted
+ // key's modifier keyval.
+ guint keyval = aGdkKeyEvent->keyval;
+ if (GetModifierForGDKKeyval(keyval)) {
+ // But if the keyval without modifiers isn't a modifier key, we
+ // shouldn't use it. E.g., Japanese keyboard layout's
+ // Shift + Eisu-Toggle key is CapsLock. This is an actual rare case,
+ // Windows uses different keycode for a physical key for different
+ // shift key state.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ if (GetModifierForGDKKeyval(keyvalWithoutModifier)) {
+ keyval = keyvalWithoutModifier;
+ }
+ // Note that the modifier keycode and activating or deactivating
+ // modifier flag may be mismatched, but it's okay. If a DOM key
+ // event handler is testing a keydown event, it's more likely being
+ // used to test which key is being pressed than to test which
+ // modifier will become active. So, if we computed DOM keycode
+ // from modifier flag which were changing by the physical key, then
+ // there would be no other way for the user to generate the original
+ // keycode.
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ NS_ASSERTION(DOMKeyCode, "All modifier keys must have a DOM keycode");
+ return DOMKeyCode;
+ }
+
+ // If the key isn't printable, let's look at the key pairs.
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ // Note that any key may be a function key because of some unusual keyboard
+ // layouts. I.e., even if the pressed key is a printable key of en-US
+ // keyboard layout, we should expose the function key's keyCode value to
+ // web apps because web apps should handle the keydown/keyup events as
+ // inputted by usual keyboard layout. For example, Hatchak keyboard
+ // maps Tab key to "Digit3" key and Level3 Shift makes it "Backspace".
+ // In this case, we should expose DOM_VK_BACK_SPACE (8).
+ uint32_t DOMKeyCode = GetDOMKeyCodeFromKeyPairs(keyval);
+ if (DOMKeyCode) {
+ // XXX If DOMKeyCode is a function key's keyCode value, it might be
+ // better to consume necessary modifiers. For example, if there is
+ // no Control Pad section on keyboard like notebook, Delete key is
+ // available only with Level3 Shift+"Backspace" key if using Hatchak.
+ // If web apps accept Delete key operation only when no modifiers are
+ // active, such users cannot use Delete key to do it. However,
+ // Chromium doesn't consume such necessary modifiers. So, our default
+ // behavior should keep not touching modifiers for compatibility, but
+ // it might be better to add a pref to consume necessary modifiers.
+ return DOMKeyCode;
+ }
+ // If aGdkKeyEvent cannot be mapped to a DOM keyCode value, we should
+ // refer keyCode value without modifiers because web apps should be
+ // able to identify the key as far as possible.
+ guint keyvalWithoutModifier = GetGDKKeyvalWithoutModifier(aGdkKeyEvent);
+ return GetDOMKeyCodeFromKeyPairs(keyvalWithoutModifier);
+ }
+
+ // printable numpad keys should be resolved here.
+ switch (keyval) {
+ case GDK_KP_Multiply:
+ return NS_VK_MULTIPLY;
+ case GDK_KP_Add:
+ return NS_VK_ADD;
+ case GDK_KP_Separator:
+ return NS_VK_SEPARATOR;
+ case GDK_KP_Subtract:
+ return NS_VK_SUBTRACT;
+ case GDK_KP_Decimal:
+ return NS_VK_DECIMAL;
+ case GDK_KP_Divide:
+ return NS_VK_DIVIDE;
+ case GDK_KP_0:
+ return NS_VK_NUMPAD0;
+ case GDK_KP_1:
+ return NS_VK_NUMPAD1;
+ case GDK_KP_2:
+ return NS_VK_NUMPAD2;
+ case GDK_KP_3:
+ return NS_VK_NUMPAD3;
+ case GDK_KP_4:
+ return NS_VK_NUMPAD4;
+ case GDK_KP_5:
+ return NS_VK_NUMPAD5;
+ case GDK_KP_6:
+ return NS_VK_NUMPAD6;
+ case GDK_KP_7:
+ return NS_VK_NUMPAD7;
+ case GDK_KP_8:
+ return NS_VK_NUMPAD8;
+ case GDK_KP_9:
+ return NS_VK_NUMPAD9;
+ }
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ // Ignore all modifier state except NumLock.
+ guint baseState =
+ (aGdkKeyEvent->state & keymapWrapper->GetGdkModifierMask(NUM_LOCK));
+
+ // Basically, we should use unmodified character for deciding our keyCode.
+ uint32_t unmodifiedChar = keymapWrapper->GetCharCodeFor(
+ aGdkKeyEvent, baseState, aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(unmodifiedChar)) {
+ // If the unmodified character is an ASCII alphabet or an ASCII
+ // numeric, it's the best hint for deciding our keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar);
+ }
+
+ // If the unmodified character is not an ASCII character, that means we
+ // couldn't find the hint. We should reset it.
+ if (!IsPrintableASCIICharacter(unmodifiedChar)) {
+ unmodifiedChar = 0;
+ }
+
+ // Retry with shifted keycode.
+ guint shiftState = (baseState | keymapWrapper->GetGdkModifierMask(SHIFT));
+ uint32_t shiftedChar = keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState,
+ aGdkKeyEvent->group);
+ if (IsBasicLatinLetterOrNumeral(shiftedChar)) {
+ // A shifted character can be an ASCII alphabet on Hebrew keyboard
+ // layout. And also shifted character can be an ASCII numeric on
+ // AZERTY keyboad layout. Then, it's a good hint for deciding our
+ // keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedChar);
+ }
+
+ // If the shifted unmodified character isn't an ASCII character, we should
+ // discard it too.
+ if (!IsPrintableASCIICharacter(shiftedChar)) {
+ shiftedChar = 0;
+ }
+
+ // If current keyboard layout isn't ASCII alphabet inputtable layout,
+ // look for ASCII alphabet inputtable keyboard layout. If the key
+ // inputs an ASCII alphabet or an ASCII numeric, we should use it
+ // for deciding our keyCode.
+ uint32_t unmodCharLatin = 0;
+ uint32_t shiftedCharLatin = 0;
+ if (!keymapWrapper->IsLatinGroup(aGdkKeyEvent->group)) {
+ gint minGroup = keymapWrapper->GetFirstLatinGroup();
+ if (minGroup >= 0) {
+ unmodCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, baseState, minGroup);
+ if (IsBasicLatinLetterOrNumeral(unmodCharLatin)) {
+ // If the unmodified character is an ASCII alphabet or
+ // an ASCII numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodCharLatin);
+ }
+ // If the unmodified character in the alternative ASCII capable
+ // keyboard layout isn't an ASCII character, that means we couldn't
+ // find the hint. We should reset it.
+ if (!IsPrintableASCIICharacter(unmodCharLatin)) {
+ unmodCharLatin = 0;
+ }
+ shiftedCharLatin =
+ keymapWrapper->GetCharCodeFor(aGdkKeyEvent, shiftState, minGroup);
+ if (IsBasicLatinLetterOrNumeral(shiftedCharLatin)) {
+ // If the shifted character is an ASCII alphabet or an ASCII
+ // numeric, we should use it for the keyCode.
+ return WidgetUtils::ComputeKeyCodeFromChar(shiftedCharLatin);
+ }
+ // If the shifted unmodified character in the alternative ASCII
+ // capable keyboard layout isn't an ASCII character, we should
+ // discard it too.
+ if (!IsPrintableASCIICharacter(shiftedCharLatin)) {
+ shiftedCharLatin = 0;
+ }
+ }
+ }
+
+ // If the key itself or with Shift state on active keyboard layout produces
+ // an ASCII punctuation character, we should decide keyCode value with it.
+ if (unmodifiedChar || shiftedChar) {
+ return WidgetUtils::ComputeKeyCodeFromChar(unmodifiedChar ? unmodifiedChar
+ : shiftedChar);
+ }
+
+ // If the key itself or with Shift state on alternative ASCII capable
+ // keyboard layout produces an ASCII punctuation character, we should
+ // decide keyCode value with it. Note that We've returned 0 for long
+ // time if keyCode isn't for an alphabet keys or a numeric key even in
+ // alternative ASCII capable keyboard layout because we decided that we
+ // should avoid setting same keyCode value to 2 or more keys since active
+ // keyboard layout may have a key to input the punctuation with different
+ // key. However, setting keyCode to 0 makes some web applications which
+ // are aware of neither KeyboardEvent.key nor KeyboardEvent.code not work
+ // with Firefox when user selects non-ASCII capable keyboard layout such
+ // as Russian and Thai. So, if alternative ASCII capable keyboard layout
+ // has keyCode value for the key, we should use it. In other words, this
+ // behavior means that non-ASCII capable keyboard layout overrides some
+ // keys' keyCode value only if the key produces ASCII character by itself
+ // or with Shift key.
+ if (unmodCharLatin || shiftedCharLatin) {
+ return WidgetUtils::ComputeKeyCodeFromChar(
+ unmodCharLatin ? unmodCharLatin : shiftedCharLatin);
+ }
+
+ // Otherwise, let's decide keyCode value from the hardware_keycode
+ // value on major keyboard layout.
+ CodeNameIndex code = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ return WidgetKeyboardEvent::GetFallbackKeyCodeOfPunctuationKey(code);
+}
+
+KeyNameIndex KeymapWrapper::ComputeDOMKeyNameIndex(
+ const GdkEventKey* aGdkKeyEvent) {
+ switch (aGdkKeyEvent->keyval) {
+#define NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX(aNativeKey, aKeyNameIndex) \
+ case aNativeKey: \
+ return aKeyNameIndex;
+
+#include "NativeKeyToDOMKeyName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_KEY_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return KEY_NAME_INDEX_Unidentified;
+}
+
+/* static */
+CodeNameIndex KeymapWrapper::ComputeDOMCodeNameIndex(
+ const GdkEventKey* aGdkKeyEvent) {
+ switch (aGdkKeyEvent->hardware_keycode) {
+#define NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX(aNativeKey, aCodeNameIndex) \
+ case aNativeKey: \
+ return aCodeNameIndex;
+
+#include "NativeKeyToDOMCodeName.h"
+
+#undef NS_NATIVE_KEY_TO_DOM_CODE_NAME_INDEX
+
+ default:
+ break;
+ }
+
+ return CODE_NAME_INDEX_UNKNOWN;
+}
+
+/* static */
+bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME,
+ bool* aIsCancelled) {
+ MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+ *aIsCancelled = false;
+
+ if (aGdkKeyEvent->type == GDK_KEY_PRESS && aGdkKeyEvent->keyval == GDK_Tab &&
+ AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" DispatchKeyDownOrKeyUpEvent(), didn't dispatch keyboard events "
+ "because it's Ctrl + Alt + Tab"));
+ return false;
+ }
+
+ EventMessage message =
+ aGdkKeyEvent->type == GDK_KEY_PRESS ? eKeyDown : eKeyUp;
+ WidgetKeyboardEvent keyEvent(true, message, aWindow);
+ KeymapWrapper::InitKeyEvent(keyEvent, aGdkKeyEvent, aIsProcessedByIME);
+ return DispatchKeyDownOrKeyUpEvent(aWindow, keyEvent, aIsCancelled);
+}
+
+/* static */
+bool KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+ nsWindow* aWindow, WidgetKeyboardEvent& aKeyboardEvent,
+ bool* aIsCancelled) {
+ MOZ_ASSERT(aIsCancelled, "aIsCancelled must not be nullptr");
+
+ *aIsCancelled = false;
+
+ RefPtr<TextEventDispatcher> dispatcher = aWindow->GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ (" DispatchKeyDownOrKeyUpEvent(), stopped dispatching %s event "
+ "because of failed to initialize TextEventDispatcher",
+ ToChar(aKeyboardEvent.mMessage)));
+ return FALSE;
+ }
+
+ nsEventStatus status = nsEventStatus_eIgnore;
+ bool dispatched = dispatcher->DispatchKeyboardEvent(
+ aKeyboardEvent.mMessage, aKeyboardEvent, status, nullptr);
+ *aIsCancelled = (status == nsEventStatus_eConsumeNoDefault);
+ return dispatched;
+}
+
+/* static */
+bool KeymapWrapper::MaybeDispatchContextMenuEvent(nsWindow* aWindow,
+ const GdkEventKey* aEvent) {
+ KeyNameIndex keyNameIndex = ComputeDOMKeyNameIndex(aEvent);
+
+ // Shift+F10 and ContextMenu should cause eContextMenu event.
+ if (keyNameIndex != KEY_NAME_INDEX_F10 &&
+ keyNameIndex != KEY_NAME_INDEX_ContextMenu) {
+ return false;
+ }
+
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, aWindow,
+ WidgetMouseEvent::eReal,
+ WidgetMouseEvent::eContextMenuKey);
+
+ contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ contextMenuEvent.AssignEventTime(aWindow->GetWidgetEventTime(aEvent->time));
+ contextMenuEvent.mClickCount = 1;
+ KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state);
+
+ if (contextMenuEvent.IsControl() || contextMenuEvent.IsMeta() ||
+ contextMenuEvent.IsAlt()) {
+ return false;
+ }
+
+ // If the key is ContextMenu, then an eContextMenu mouse event is
+ // dispatched regardless of the state of the Shift modifier. When it is
+ // pressed without the Shift modifier, a web page can prevent the default
+ // context menu action. When pressed with the Shift modifier, the web page
+ // cannot prevent the default context menu action.
+ // (PresShell::HandleEventInternal() sets mOnlyChromeDispatch to true.)
+
+ // If the key is F10, it needs Shift state because Shift+F10 is well-known
+ // shortcut key on Linux. However, eContextMenu with Shift state is
+ // special. It won't fire "contextmenu" event in the web content for
+ // blocking web page to prevent its default. Therefore, this combination
+ // should work same as ContextMenu key.
+ // XXX Should we allow to block web page to prevent its default with
+ // Ctrl+Shift+F10 or Alt+Shift+F10 instead?
+ if (keyNameIndex == KEY_NAME_INDEX_F10) {
+ if (!contextMenuEvent.IsShift()) {
+ return false;
+ }
+ contextMenuEvent.mModifiers &= ~MODIFIER_SHIFT;
+ }
+
+ aWindow->DispatchInputEvent(&contextMenuEvent);
+ return true;
+}
+
+/* static*/
+void KeymapWrapper::HandleKeyPressEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("HandleKeyPressEvent(aWindow=%p, aGdkKeyEvent={ type=%s, "
+ "keyval=%s(0x%X), state=0x%08X, hardware_keycode=0x%08X, "
+ "time=%u, is_modifier=%s })",
+ aWindow,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? "GDK_KEY_PRESS"
+ : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval), aGdkKeyEvent->keyval,
+ aGdkKeyEvent->state, aGdkKeyEvent->hardware_keycode,
+ aGdkKeyEvent->time, GetBoolName(aGdkKeyEvent->is_modifier)));
+
+ // if we are in the middle of composing text, XIM gets to see it
+ // before mozilla does.
+ // FYI: Don't dispatch keydown event before notifying IME of the event
+ // because IME may send a key event synchronously and consume the
+ // original event.
+ bool IMEWasEnabled = false;
+ KeyHandlingState handlingState = KeyHandlingState::eNotHandled;
+ RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+ if (imContext) {
+ IMEWasEnabled = imContext->IsEnabled();
+ handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+ if (handlingState == KeyHandlingState::eHandled) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), the event was handled by "
+ "IMContextWrapper"));
+ return;
+ }
+ }
+
+ // work around for annoying things.
+ if (aGdkKeyEvent->keyval == GDK_Tab &&
+ AreModifiersActive(CTRL | ALT, aGdkKeyEvent->state)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), didn't dispatch keyboard events "
+ "because it's Ctrl + Alt + Tab"));
+ return;
+ }
+
+ // Dispatch keydown event always. At auto repeating, we should send
+ // KEYDOWN -> KEYPRESS -> KEYDOWN -> KEYPRESS ... -> KEYUP
+ // However, old distributions (e.g., Ubuntu 9.10) sent native key
+ // release event, so, on such platform, the DOM events will be:
+ // KEYDOWN -> KEYPRESS -> KEYUP -> KEYDOWN -> KEYPRESS -> KEYUP...
+
+ bool isKeyDownCancelled = false;
+ if (handlingState == KeyHandlingState::eNotHandled) {
+ if (DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+ &isKeyDownCancelled) &&
+ (MOZ_UNLIKELY(aWindow->IsDestroyed()) || isKeyDownCancelled)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyDown event and "
+ "stopped handling the event because %s",
+ aWindow->IsDestroyed() ? "the window has been destroyed"
+ : "the event was consumed"));
+ return;
+ }
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyDown event and "
+ "it wasn't consumed"));
+ handlingState = KeyHandlingState::eNotHandledButEventDispatched;
+ }
+
+ // If a keydown event handler causes to enable IME, i.e., it moves
+ // focus from IME unusable content to IME usable editor, we should
+ // send the native key event to IME for the first input on the editor.
+ imContext = aWindow->GetIMContext();
+ if (!IMEWasEnabled && imContext && imContext->IsEnabled()) {
+ // Notice our keydown event was already dispatched. This prevents
+ // unnecessary DOM keydown event in the editor.
+ handlingState = imContext->OnKeyEvent(aWindow, aGdkKeyEvent, true);
+ if (handlingState == KeyHandlingState::eHandled) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), the event was handled by "
+ "IMContextWrapper which was enabled by the preceding eKeyDown "
+ "event"));
+ return;
+ }
+ }
+
+ // Look for specialized app-command keys
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Back:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Back);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Back\" command event"));
+ return;
+ case GDK_Forward:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Forward);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Forward\" command "
+ "event"));
+ return;
+ case GDK_Reload:
+ case GDK_Refresh:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Reload);
+ return;
+ case GDK_Stop:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Stop);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Stop\" command event"));
+ return;
+ case GDK_Search:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Search);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Search\" command event"));
+ return;
+ case GDK_Favorites:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Bookmarks);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Bookmarks\" command "
+ "event"));
+ return;
+ case GDK_HomePage:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Home);
+ return;
+ case GDK_Copy:
+ case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo
+ aWindow->DispatchContentCommandEvent(eContentCommandCopy);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Copy\" content command "
+ "event"));
+ return;
+ case GDK_Cut:
+ case GDK_F20:
+ aWindow->DispatchContentCommandEvent(eContentCommandCut);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Cut\" content command "
+ "event"));
+ return;
+ case GDK_Paste:
+ case GDK_F18:
+ aWindow->DispatchContentCommandEvent(eContentCommandPaste);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Paste\" content command "
+ "event"));
+ return;
+ case GDK_Redo:
+ aWindow->DispatchContentCommandEvent(eContentCommandRedo);
+ return;
+ case GDK_Undo:
+ case GDK_F14:
+ aWindow->DispatchContentCommandEvent(eContentCommandUndo);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Undo\" content command "
+ "event"));
+ return;
+ default:
+ break;
+ }
+
+ // before we dispatch a key, check if it's the context menu key.
+ // If so, send a context menu key event instead.
+ if (MaybeDispatchContextMenuEvent(aWindow, aGdkKeyEvent)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), stopped dispatching eKeyPress event "
+ "because eContextMenu event was dispatched"));
+ return;
+ }
+
+ RefPtr<TextEventDispatcher> textEventDispatcher =
+ aWindow->GetTextEventDispatcher();
+ nsresult rv = textEventDispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ (" HandleKeyPressEvent(), stopped dispatching eKeyPress event "
+ "because of failed to initialize TextEventDispatcher"));
+ return;
+ }
+
+ // If the character code is in the BMP, send the key press event.
+ // Otherwise, send a compositionchange event with the equivalent UTF-16
+ // string.
+ // TODO: Investigate other browser's behavior in this case because
+ // this hack is odd for UI Events.
+ WidgetKeyboardEvent keypressEvent(true, eKeyPress, aWindow);
+ KeymapWrapper::InitKeyEvent(keypressEvent, aGdkKeyEvent, false);
+ nsEventStatus status = nsEventStatus_eIgnore;
+ if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ keypressEvent.mKeyValue.Length() == 1) {
+ if (textEventDispatcher->MaybeDispatchKeypressEvents(keypressEvent, status,
+ aGdkKeyEvent)) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyPress event "
+ "(status=%s)",
+ GetStatusName(status)));
+ } else {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), didn't dispatch eKeyPress event "
+ "(status=%s)",
+ GetStatusName(status)));
+ }
+ } else {
+ WidgetEventTime eventTime = aWindow->GetWidgetEventTime(aGdkKeyEvent->time);
+ textEventDispatcher->CommitComposition(status, &keypressEvent.mKeyValue,
+ &eventTime);
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched a set of composition "
+ "events"));
+ }
+}
+
+/* static */
+bool KeymapWrapper::HandleKeyReleaseEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("HandleKeyReleaseEvent(aWindow=%p, aGdkKeyEvent={ type=%s, "
+ "keyval=%s(0x%X), state=0x%08X, hardware_keycode=0x%08X, "
+ "time=%u, is_modifier=%s })",
+ aWindow,
+ ((aGdkKeyEvent->type == GDK_KEY_PRESS) ? "GDK_KEY_PRESS"
+ : "GDK_KEY_RELEASE"),
+ gdk_keyval_name(aGdkKeyEvent->keyval), aGdkKeyEvent->keyval,
+ aGdkKeyEvent->state, aGdkKeyEvent->hardware_keycode,
+ aGdkKeyEvent->time, GetBoolName(aGdkKeyEvent->is_modifier)));
+
+ RefPtr<IMContextWrapper> imContext = aWindow->GetIMContext();
+ if (imContext) {
+ KeyHandlingState handlingState =
+ imContext->OnKeyEvent(aWindow, aGdkKeyEvent);
+ if (handlingState != KeyHandlingState::eNotHandled) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyReleaseEvent(), the event was handled by "
+ "IMContextWrapper"));
+ return true;
+ }
+ }
+
+ bool isCancelled = false;
+ if (NS_WARN_IF(!DispatchKeyDownOrKeyUpEvent(aWindow, aGdkKeyEvent, false,
+ &isCancelled))) {
+ MOZ_LOG(gKeyLog, LogLevel::Error,
+ (" HandleKeyReleaseEvent(), didn't dispatch eKeyUp event"));
+ return false;
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ (" HandleKeyReleaseEvent(), dispatched eKeyUp event "
+ "(isCancelled=%s)",
+ GetBoolName(isCancelled)));
+ return true;
+}
+
+guint KeymapWrapper::GetModifierState(GdkEventKey* aGdkKeyEvent,
+ KeymapWrapper* aWrapper) {
+ guint state = aGdkKeyEvent->state;
+ if (!aGdkKeyEvent->is_modifier) {
+ return state;
+ }
+#ifdef MOZ_X11
+ // NOTE: The state of given key event indicates adjacent state of
+ // modifier keys. E.g., even if the event is Shift key press event,
+ // the bit for Shift is still false. By the same token, even if the
+ // event is Shift key release event, the bit for Shift is still true.
+ // Unfortunately, gdk_keyboard_get_modifiers() returns current modifier
+ // state. It means if there're some pending modifier key press or
+ // key release events, the result isn't what we want.
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (GdkIsX11Display(gdkDisplay)) {
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ Display* display = gdk_x11_display_get_xdisplay(gdkDisplay);
+ if (XEventsQueued(display, QueuedAfterReading)) {
+ XEvent nextEvent;
+ XPeekEvent(display, &nextEvent);
+ if (nextEvent.type == aWrapper->mXKBBaseEventCode) {
+ XkbEvent* XKBEvent = (XkbEvent*)&nextEvent;
+ if (XKBEvent->any.xkb_type == XkbStateNotify) {
+ XkbStateNotifyEvent* stateNotifyEvent =
+ (XkbStateNotifyEvent*)XKBEvent;
+ state &= ~0xFF;
+ state |= stateNotifyEvent->lookup_mods;
+ }
+ }
+ }
+ return state;
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ int mask = 0;
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ mask = aWrapper->GetGdkModifierMask(SHIFT);
+ break;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ mask = aWrapper->GetGdkModifierMask(CTRL);
+ break;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ mask = aWrapper->GetGdkModifierMask(ALT);
+ break;
+ case GDK_Super_L:
+ case GDK_Super_R:
+ mask = aWrapper->GetGdkModifierMask(SUPER);
+ break;
+ case GDK_Hyper_L:
+ case GDK_Hyper_R:
+ mask = aWrapper->GetGdkModifierMask(HYPER);
+ break;
+ case GDK_Meta_L:
+ case GDK_Meta_R:
+ mask = aWrapper->GetGdkModifierMask(META);
+ break;
+ default:
+ break;
+ }
+ if (aGdkKeyEvent->type == GDK_KEY_PRESS) {
+ state |= mask;
+ } else {
+ state &= ~mask;
+ }
+#endif
+ return state;
+}
+
+/* static */
+void KeymapWrapper::InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME) {
+ MOZ_ASSERT(
+ !aIsProcessedByIME || aKeyEvent.mMessage != eKeyPress,
+ "If the key event is handled by IME, keypress event shouldn't be fired");
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aKeyEvent.mCodeNameIndex = ComputeDOMCodeNameIndex(aGdkKeyEvent);
+ MOZ_ASSERT(aKeyEvent.mCodeNameIndex != CODE_NAME_INDEX_USE_STRING);
+ aKeyEvent.mKeyNameIndex =
+ aIsProcessedByIME ? KEY_NAME_INDEX_Process
+ : keymapWrapper->ComputeDOMKeyNameIndex(aGdkKeyEvent);
+ if (aKeyEvent.mKeyNameIndex == KEY_NAME_INDEX_Unidentified) {
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ charCode = keymapWrapper->GetUnmodifiedCharCodeFor(aGdkKeyEvent);
+ }
+ if (charCode) {
+ aKeyEvent.mKeyNameIndex = KEY_NAME_INDEX_USE_STRING;
+ MOZ_ASSERT(aKeyEvent.mKeyValue.IsEmpty(),
+ "Uninitialized mKeyValue must be empty");
+ AppendUCS4ToUTF16(charCode, aKeyEvent.mKeyValue);
+ }
+ }
+
+ if (aIsProcessedByIME) {
+ aKeyEvent.mKeyCode = NS_VK_PROCESSKEY;
+ } else if (aKeyEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING ||
+ aKeyEvent.mMessage != eKeyPress) {
+ aKeyEvent.mKeyCode = ComputeDOMKeyCode(aGdkKeyEvent);
+ } else {
+ aKeyEvent.mKeyCode = 0;
+ }
+
+ const guint modifierState = GetModifierState(aGdkKeyEvent, keymapWrapper);
+ InitInputEvent(aKeyEvent, modifierState);
+
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_Shift_L:
+ case GDK_Control_L:
+ case GDK_Alt_L:
+ case GDK_Super_L:
+ case GDK_Hyper_L:
+ case GDK_Meta_L:
+ aKeyEvent.mLocation = eKeyLocationLeft;
+ break;
+
+ case GDK_Shift_R:
+ case GDK_Control_R:
+ case GDK_Alt_R:
+ case GDK_Super_R:
+ case GDK_Hyper_R:
+ case GDK_Meta_R:
+ aKeyEvent.mLocation = eKeyLocationRight;
+ break;
+
+ case GDK_KP_0:
+ case GDK_KP_1:
+ case GDK_KP_2:
+ case GDK_KP_3:
+ case GDK_KP_4:
+ case GDK_KP_5:
+ case GDK_KP_6:
+ case GDK_KP_7:
+ case GDK_KP_8:
+ case GDK_KP_9:
+ case GDK_KP_Space:
+ case GDK_KP_Tab:
+ case GDK_KP_Enter:
+ case GDK_KP_F1:
+ case GDK_KP_F2:
+ case GDK_KP_F3:
+ case GDK_KP_F4:
+ case GDK_KP_Home:
+ case GDK_KP_Left:
+ case GDK_KP_Up:
+ case GDK_KP_Right:
+ case GDK_KP_Down:
+ case GDK_KP_Prior: // same as GDK_KP_Page_Up
+ case GDK_KP_Next: // same as GDK_KP_Page_Down
+ case GDK_KP_End:
+ case GDK_KP_Begin:
+ case GDK_KP_Insert:
+ case GDK_KP_Delete:
+ case GDK_KP_Equal:
+ case GDK_KP_Multiply:
+ case GDK_KP_Add:
+ case GDK_KP_Separator:
+ case GDK_KP_Subtract:
+ case GDK_KP_Decimal:
+ case GDK_KP_Divide:
+ aKeyEvent.mLocation = eKeyLocationNumpad;
+ break;
+
+ default:
+ aKeyEvent.mLocation = eKeyLocationStandard;
+ break;
+ }
+
+ // The transformations above and in gdk for the keyval are not invertible
+ // so link to the GdkEvent (which will vanish soon after return from the
+ // event callback) to give plugins access to hardware_keycode and state.
+ // (An XEvent would be nice but the GdkEvent is good enough.)
+ aKeyEvent.mNativeKeyEvent = static_cast<void*>(aGdkKeyEvent);
+ aKeyEvent.mIsRepeat =
+ sRepeatState == REPEATING &&
+ aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode;
+
+ MOZ_LOG(
+ gKeyLog, LogLevel::Info,
+ ("%p InitKeyEvent, modifierState=0x%08X "
+ "aKeyEvent={ mMessage=%s, isShift=%s, isControl=%s, "
+ "isAlt=%s, isMeta=%s , mKeyCode=0x%02X, mCharCode=%s, "
+ "mKeyNameIndex=%s, mKeyValue=%s, mCodeNameIndex=%s, mCodeValue=%s, "
+ "mLocation=%s, mIsRepeat=%s }",
+ keymapWrapper, modifierState, ToChar(aKeyEvent.mMessage),
+ GetBoolName(aKeyEvent.IsShift()), GetBoolName(aKeyEvent.IsControl()),
+ GetBoolName(aKeyEvent.IsAlt()), GetBoolName(aKeyEvent.IsMeta()),
+ aKeyEvent.mKeyCode,
+ GetCharacterCodeName(static_cast<char16_t>(aKeyEvent.mCharCode)).get(),
+ ToString(aKeyEvent.mKeyNameIndex).get(),
+ GetCharacterCodeNames(aKeyEvent.mKeyValue).get(),
+ ToString(aKeyEvent.mCodeNameIndex).get(),
+ GetCharacterCodeNames(aKeyEvent.mCodeValue).get(),
+ GetKeyLocationName(aKeyEvent.mLocation).get(),
+ GetBoolName(aKeyEvent.mIsRepeat)));
+}
+
+/* static */
+uint32_t KeymapWrapper::GetCharCodeFor(const GdkEventKey* aGdkKeyEvent) {
+ // Anything above 0xf000 is considered a non-printable
+ // Exception: directly encoded UCS characters
+ if (aGdkKeyEvent->keyval > 0xf000 &&
+ (aGdkKeyEvent->keyval & 0xff000000) != 0x01000000) {
+ // Keypad keys are an exception: they return a value different
+ // from their non-keypad equivalents, but mozilla doesn't distinguish.
+ switch (aGdkKeyEvent->keyval) {
+ case GDK_KP_Space:
+ return ' ';
+ case GDK_KP_Equal:
+ return '=';
+ case GDK_KP_Multiply:
+ return '*';
+ case GDK_KP_Add:
+ return '+';
+ case GDK_KP_Separator:
+ return ',';
+ case GDK_KP_Subtract:
+ return '-';
+ case GDK_KP_Decimal:
+ return '.';
+ case GDK_KP_Divide:
+ return '/';
+ case GDK_KP_0:
+ return '0';
+ case GDK_KP_1:
+ return '1';
+ case GDK_KP_2:
+ return '2';
+ case GDK_KP_3:
+ return '3';
+ case GDK_KP_4:
+ return '4';
+ case GDK_KP_5:
+ return '5';
+ case GDK_KP_6:
+ return '6';
+ case GDK_KP_7:
+ return '7';
+ case GDK_KP_8:
+ return '8';
+ case GDK_KP_9:
+ return '9';
+ default:
+ return 0; // non-printables
+ }
+ }
+
+ static const long MAX_UNICODE = 0x10FFFF;
+
+ // we're supposedly printable, let's try to convert
+ long ucs = keysym2ucs(aGdkKeyEvent->keyval);
+ if ((ucs != -1) && (ucs < MAX_UNICODE)) {
+ return ucs;
+ }
+
+ // I guess we couldn't convert
+ return 0;
+}
+
+uint32_t KeymapWrapper::GetCharCodeFor(const GdkEventKey* aGdkKeyEvent,
+ guint aGdkModifierState, gint aGroup) {
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(
+ mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aGdkModifierState), aGroup, &keyval, nullptr, nullptr,
+ nullptr)) {
+ return 0;
+ }
+ GdkEventKey tmpEvent = *aGdkKeyEvent;
+ tmpEvent.state = aGdkModifierState;
+ tmpEvent.keyval = keyval;
+ tmpEvent.group = aGroup;
+ return GetCharCodeFor(&tmpEvent);
+}
+
+uint32_t KeymapWrapper::GetUnmodifiedCharCodeFor(
+ const GdkEventKey* aGdkKeyEvent) {
+ guint state =
+ aGdkKeyEvent->state &
+ (GetGdkModifierMask(SHIFT) | GetGdkModifierMask(CAPS_LOCK) |
+ GetGdkModifierMask(NUM_LOCK) | GetGdkModifierMask(SCROLL_LOCK) |
+ GetGdkModifierMask(LEVEL3) | GetGdkModifierMask(LEVEL5));
+ uint32_t charCode =
+ GetCharCodeFor(aGdkKeyEvent, GdkModifierType(state), aGdkKeyEvent->group);
+ if (charCode) {
+ return charCode;
+ }
+ // If no character is mapped to the key when Level3 Shift or Level5 Shift
+ // is active, let's return a character which is inputted by the key without
+ // Level3 nor Level5 Shift.
+ guint stateWithoutAltGraph =
+ state & ~(GetGdkModifierMask(LEVEL3) | GetGdkModifierMask(LEVEL5));
+ if (state == stateWithoutAltGraph) {
+ return 0;
+ }
+ return GetCharCodeFor(aGdkKeyEvent, GdkModifierType(stateWithoutAltGraph),
+ aGdkKeyEvent->group);
+}
+
+gint KeymapWrapper::GetKeyLevel(GdkEventKey* aGdkKeyEvent) {
+ gint level;
+ if (!gdk_keymap_translate_keyboard_state(
+ mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aGdkKeyEvent->state), aGdkKeyEvent->group, nullptr,
+ nullptr, &level, nullptr)) {
+ return -1;
+ }
+ return level;
+}
+
+gint KeymapWrapper::GetFirstLatinGroup() {
+ GdkKeymapKey* keys;
+ gint count;
+ gint minGroup = -1;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ // find the minimum number group for latin inputtable layout
+ for (gint i = 0; i < count && minGroup != 0; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (minGroup >= 0 && keys[i].group > minGroup) {
+ continue;
+ }
+ minGroup = keys[i].group;
+ }
+ g_free(keys);
+ }
+ return minGroup;
+}
+
+bool KeymapWrapper::IsLatinGroup(guint8 aGroup) {
+ GdkKeymapKey* keys;
+ gint count;
+ bool result = false;
+ if (gdk_keymap_get_entries_for_keyval(mGdkKeymap, GDK_a, &keys, &count)) {
+ for (gint i = 0; i < count; ++i) {
+ if (keys[i].level != 0 && keys[i].level != 1) {
+ continue;
+ }
+ if (keys[i].group == aGroup) {
+ result = true;
+ break;
+ }
+ }
+ g_free(keys);
+ }
+ return result;
+}
+
+bool KeymapWrapper::IsAutoRepeatableKey(guint aHardwareKeyCode) {
+#ifdef MOZ_X11
+ uint8_t indexOfArray = aHardwareKeyCode / 8;
+ MOZ_ASSERT(indexOfArray < ArrayLength(mKeyboardState.auto_repeats),
+ "invalid index");
+ char bitMask = 1 << (aHardwareKeyCode % 8);
+ return (mKeyboardState.auto_repeats[indexOfArray] & bitMask) != 0;
+#else
+ return false;
+#endif
+}
+
+/* static */
+bool KeymapWrapper::IsBasicLatinLetterOrNumeral(uint32_t aCharCode) {
+ return (aCharCode >= 'a' && aCharCode <= 'z') ||
+ (aCharCode >= 'A' && aCharCode <= 'Z') ||
+ (aCharCode >= '0' && aCharCode <= '9');
+}
+
+/* static */
+guint KeymapWrapper::GetGDKKeyvalWithoutModifier(
+ const GdkEventKey* aGdkKeyEvent) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+ guint state =
+ (aGdkKeyEvent->state & keymapWrapper->GetGdkModifierMask(NUM_LOCK));
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(
+ keymapWrapper->mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(state), aGdkKeyEvent->group, &keyval, nullptr,
+ nullptr, nullptr)) {
+ return 0;
+ }
+ return keyval;
+}
+
+/* static */
+uint32_t KeymapWrapper::GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval) {
+ switch (aGdkKeyval) {
+ case GDK_Cancel:
+ return NS_VK_CANCEL;
+ case GDK_BackSpace:
+ return NS_VK_BACK;
+ case GDK_Tab:
+ case GDK_ISO_Left_Tab:
+ return NS_VK_TAB;
+ case GDK_Clear:
+ return NS_VK_CLEAR;
+ case GDK_Return:
+ return NS_VK_RETURN;
+ case GDK_Shift_L:
+ case GDK_Shift_R:
+ case GDK_Shift_Lock:
+ return NS_VK_SHIFT;
+ case GDK_Control_L:
+ case GDK_Control_R:
+ return NS_VK_CONTROL;
+ case GDK_Alt_L:
+ case GDK_Alt_R:
+ return NS_VK_ALT;
+ case GDK_Meta_L:
+ case GDK_Meta_R:
+ return NS_VK_META;
+
+ // Assume that Super or Hyper is always mapped to physical Win key.
+ case GDK_Super_L:
+ case GDK_Super_R:
+ case GDK_Hyper_L:
+ case GDK_Hyper_R:
+ return NS_VK_WIN;
+
+ // GTK's AltGraph key is similar to Mac's Option (Alt) key. However,
+ // unfortunately, browsers on Mac are using NS_VK_ALT for it even though
+ // it's really different from Alt key on Windows.
+ // On the other hand, GTK's AltGrapsh keys are really different from
+ // Alt key. However, there is no AltGrapsh key on Windows. On Windows,
+ // both Ctrl and Alt keys are pressed internally when AltGr key is
+ // pressed. For some languages' users, AltGraph key is important, so,
+ // web applications on such locale may want to know AltGraph key press.
+ // Therefore, we should map AltGr keycode for them only on GTK.
+ case GDK_ISO_Level3_Shift:
+ case GDK_ISO_Level5_Shift:
+ // We assume that Mode_switch is always used for level3 shift.
+ case GDK_Mode_switch:
+ return NS_VK_ALTGR;
+
+ case GDK_Pause:
+ return NS_VK_PAUSE;
+ case GDK_Caps_Lock:
+ return NS_VK_CAPS_LOCK;
+ case GDK_Kana_Lock:
+ case GDK_Kana_Shift:
+ return NS_VK_KANA;
+ case GDK_Hangul:
+ return NS_VK_HANGUL;
+ // case GDK_XXX: return NS_VK_JUNJA;
+ // case GDK_XXX: return NS_VK_FINAL;
+ case GDK_Hangul_Hanja:
+ return NS_VK_HANJA;
+ case GDK_Kanji:
+ return NS_VK_KANJI;
+ case GDK_Escape:
+ return NS_VK_ESCAPE;
+ case GDK_Henkan:
+ return NS_VK_CONVERT;
+ case GDK_Muhenkan:
+ return NS_VK_NONCONVERT;
+ // case GDK_XXX: return NS_VK_ACCEPT;
+ // case GDK_XXX: return NS_VK_MODECHANGE;
+ case GDK_Page_Up:
+ return NS_VK_PAGE_UP;
+ case GDK_Page_Down:
+ return NS_VK_PAGE_DOWN;
+ case GDK_End:
+ return NS_VK_END;
+ case GDK_Home:
+ return NS_VK_HOME;
+ case GDK_Left:
+ return NS_VK_LEFT;
+ case GDK_Up:
+ return NS_VK_UP;
+ case GDK_Right:
+ return NS_VK_RIGHT;
+ case GDK_Down:
+ return NS_VK_DOWN;
+ case GDK_Select:
+ return NS_VK_SELECT;
+ case GDK_Print:
+ return NS_VK_PRINT;
+ case GDK_Execute:
+ return NS_VK_EXECUTE;
+ case GDK_Insert:
+ return NS_VK_INSERT;
+ case GDK_Delete:
+ return NS_VK_DELETE;
+ case GDK_Help:
+ return NS_VK_HELP;
+
+ // keypad keys
+ case GDK_KP_Left:
+ return NS_VK_LEFT;
+ case GDK_KP_Right:
+ return NS_VK_RIGHT;
+ case GDK_KP_Up:
+ return NS_VK_UP;
+ case GDK_KP_Down:
+ return NS_VK_DOWN;
+ case GDK_KP_Page_Up:
+ return NS_VK_PAGE_UP;
+ // Not sure what these are
+ // case GDK_KP_Prior: return NS_VK_;
+ // case GDK_KP_Next: return NS_VK_;
+ case GDK_KP_Begin:
+ return NS_VK_CLEAR; // Num-unlocked 5
+ case GDK_KP_Page_Down:
+ return NS_VK_PAGE_DOWN;
+ case GDK_KP_Home:
+ return NS_VK_HOME;
+ case GDK_KP_End:
+ return NS_VK_END;
+ case GDK_KP_Insert:
+ return NS_VK_INSERT;
+ case GDK_KP_Delete:
+ return NS_VK_DELETE;
+ case GDK_KP_Enter:
+ return NS_VK_RETURN;
+
+ case GDK_Num_Lock:
+ return NS_VK_NUM_LOCK;
+ case GDK_Scroll_Lock:
+ return NS_VK_SCROLL_LOCK;
+
+ // Function keys
+ case GDK_F1:
+ return NS_VK_F1;
+ case GDK_F2:
+ return NS_VK_F2;
+ case GDK_F3:
+ return NS_VK_F3;
+ case GDK_F4:
+ return NS_VK_F4;
+ case GDK_F5:
+ return NS_VK_F5;
+ case GDK_F6:
+ return NS_VK_F6;
+ case GDK_F7:
+ return NS_VK_F7;
+ case GDK_F8:
+ return NS_VK_F8;
+ case GDK_F9:
+ return NS_VK_F9;
+ case GDK_F10:
+ return NS_VK_F10;
+ case GDK_F11:
+ return NS_VK_F11;
+ case GDK_F12:
+ return NS_VK_F12;
+ case GDK_F13:
+ return NS_VK_F13;
+ case GDK_F14:
+ return NS_VK_F14;
+ case GDK_F15:
+ return NS_VK_F15;
+ case GDK_F16:
+ return NS_VK_F16;
+ case GDK_F17:
+ return NS_VK_F17;
+ case GDK_F18:
+ return NS_VK_F18;
+ case GDK_F19:
+ return NS_VK_F19;
+ case GDK_F20:
+ return NS_VK_F20;
+ case GDK_F21:
+ return NS_VK_F21;
+ case GDK_F22:
+ return NS_VK_F22;
+ case GDK_F23:
+ return NS_VK_F23;
+ case GDK_F24:
+ return NS_VK_F24;
+
+ // context menu key, keysym 0xff67, typically keycode 117 on 105-key
+ // (Microsoft) x86 keyboards, located between right 'Windows' key and
+ // right Ctrl key
+ case GDK_Menu:
+ return NS_VK_CONTEXT_MENU;
+ case GDK_Sleep:
+ return NS_VK_SLEEP;
+
+ case GDK_3270_Attn:
+ return NS_VK_ATTN;
+ case GDK_3270_CursorSelect:
+ return NS_VK_CRSEL;
+ case GDK_3270_ExSelect:
+ return NS_VK_EXSEL;
+ case GDK_3270_EraseEOF:
+ return NS_VK_EREOF;
+ case GDK_3270_Play:
+ return NS_VK_PLAY;
+ // case GDK_XXX: return NS_VK_ZOOM;
+ case GDK_3270_PA1:
+ return NS_VK_PA1;
+
+ // map Sun Keyboard special keysyms on to NS_VK keys
+
+ // Sun F11 key generates SunF36(0x1005ff10) keysym
+ case 0x1005ff10:
+ return NS_VK_F11;
+ // Sun F12 key generates SunF37(0x1005ff11) keysym
+ case 0x1005ff11:
+ return NS_VK_F12;
+ default:
+ return 0;
+ }
+}
+
+void KeymapWrapper::WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent) {
+ GetInstance()->WillDispatchKeyboardEventInternal(aKeyEvent, aGdkKeyEvent);
+}
+
+void KeymapWrapper::WillDispatchKeyboardEventInternal(
+ WidgetKeyboardEvent& aKeyEvent, GdkEventKey* aGdkKeyEvent) {
+ if (!aGdkKeyEvent) {
+ // If aGdkKeyEvent is nullptr, we're trying to dispatch a fake keyboard
+ // event in such case, we don't need to set alternative char codes.
+ // So, we don't need to do nothing here. This case is typically we're
+ // dispatching eKeyDown or eKeyUp event during composition.
+ return;
+ }
+
+ uint32_t charCode = GetCharCodeFor(aGdkKeyEvent);
+ if (!charCode) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, charCode=0x%08X",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode));
+ return;
+ }
+
+ // The mCharCode was set from mKeyValue. However, for example, when Ctrl key
+ // is pressed, its value should indicate an ASCII character for backward
+ // compatibility rather than inputting character without the modifiers.
+ // Therefore, we need to modify mCharCode value here.
+ aKeyEvent.SetCharCode(charCode);
+
+ gint level = GetKeyLevel(aGdkKeyEvent);
+ if (level != 0 && level != 1) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level));
+ return;
+ }
+
+ guint baseState = aGdkKeyEvent->state &
+ ~(GetGdkModifierMask(SHIFT) | GetGdkModifierMask(CTRL) |
+ GetGdkModifierMask(ALT) | GetGdkModifierMask(META) |
+ GetGdkModifierMask(SUPER) | GetGdkModifierMask(HYPER));
+
+ // We shold send both shifted char and unshifted char, all keyboard layout
+ // users can use all keys. Don't change event.mCharCode. On some keyboard
+ // layouts, Ctrl/Alt/Meta keys are used for inputting some characters.
+ AlternativeCharCode altCharCodes(0, 0);
+ // unshifted charcode of current keyboard layout.
+ altCharCodes.mUnshiftedCharCode =
+ GetCharCodeFor(aGdkKeyEvent, baseState, aGdkKeyEvent->group);
+ bool isLatin = (altCharCodes.mUnshiftedCharCode <= 0xFF);
+ // shifted charcode of current keyboard layout.
+ altCharCodes.mShiftedCharCode = GetCharCodeFor(
+ aGdkKeyEvent, baseState | GetGdkModifierMask(SHIFT), aGdkKeyEvent->group);
+ isLatin = isLatin && (altCharCodes.mShiftedCharCode <= 0xFF);
+ if (altCharCodes.mUnshiftedCharCode || altCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altCharCodes);
+ }
+
+ bool needLatinKeyCodes = !isLatin;
+ if (!needLatinKeyCodes) {
+ needLatinKeyCodes =
+ (IS_ASCII_ALPHABETICAL(altCharCodes.mUnshiftedCharCode) !=
+ IS_ASCII_ALPHABETICAL(altCharCodes.mShiftedCharCode));
+ }
+
+ // If current keyboard layout can input Latin characters, we don't need
+ // more information.
+ if (!needLatinKeyCodes) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, altCharCodes={ "
+ "mUnshiftedCharCode=0x%08X, mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ // Next, find Latin inputtable keyboard layout.
+ gint minGroup = GetFirstLatinGroup();
+ if (minGroup < 0) {
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "Latin keyboard layout isn't found: "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode));
+ return;
+ }
+
+ AlternativeCharCode altLatinCharCodes(0, 0);
+ uint32_t unmodifiedCh = aKeyEvent.IsShift() ? altCharCodes.mShiftedCharCode
+ : altCharCodes.mUnshiftedCharCode;
+
+ // unshifted charcode of found keyboard layout.
+ uint32_t ch = GetCharCodeFor(aGdkKeyEvent, baseState, minGroup);
+ altLatinCharCodes.mUnshiftedCharCode =
+ IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ // shifted charcode of found keyboard layout.
+ ch = GetCharCodeFor(aGdkKeyEvent, baseState | GetGdkModifierMask(SHIFT),
+ minGroup);
+ altLatinCharCodes.mShiftedCharCode = IsBasicLatinLetterOrNumeral(ch) ? ch : 0;
+ if (altLatinCharCodes.mUnshiftedCharCode ||
+ altLatinCharCodes.mShiftedCharCode) {
+ aKeyEvent.mAlternativeCharCodes.AppendElement(altLatinCharCodes);
+ }
+ // If the mCharCode is not Latin, and the level is 0 or 1, we should
+ // replace the mCharCode to Latin char if Alt keys are not pressed. (Alt
+ // should be sent the localized char for accesskey like handling of Web
+ // Applications.)
+ ch = aKeyEvent.IsShift() ? altLatinCharCodes.mShiftedCharCode
+ : altLatinCharCodes.mUnshiftedCharCode;
+ if (ch && !aKeyEvent.IsAlt() && charCode == unmodifiedCh) {
+ aKeyEvent.SetCharCode(ch);
+ }
+
+ MOZ_LOG(gKeyLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d, minGroup=%d, "
+ "altCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X } "
+ "altLatinCharCodes={ mUnshiftedCharCode=0x%08X, "
+ "mShiftedCharCode=0x%08X }",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level, minGroup,
+ altCharCodes.mUnshiftedCharCode, altCharCodes.mShiftedCharCode,
+ altLatinCharCodes.mUnshiftedCharCode,
+ altLatinCharCodes.mShiftedCharCode));
+}
+
+#ifdef MOZ_WAYLAND
+void KeymapWrapper::SetFocusIn(wl_surface* aFocusSurface,
+ uint32_t aFocusSerial) {
+ LOGW("KeymapWrapper::SetFocusIn() surface %p ID %d serial %d", aFocusSurface,
+ aFocusSurface ? wl_proxy_get_id((struct wl_proxy*)aFocusSurface) : 0,
+ aFocusSerial);
+
+ KeymapWrapper* keymapWrapper = KeymapWrapper::GetInstance();
+ keymapWrapper->mFocusSurface = aFocusSurface;
+ keymapWrapper->mFocusSerial = aFocusSerial;
+}
+
+// aFocusSurface can be null in case that focused surface is already destroyed.
+void KeymapWrapper::SetFocusOut(wl_surface* aFocusSurface) {
+ KeymapWrapper* keymapWrapper = KeymapWrapper::GetInstance();
+ LOGW("KeymapWrapper::SetFocusOut surface %p ID %d", aFocusSurface,
+ aFocusSurface ? wl_proxy_get_id((struct wl_proxy*)aFocusSurface) : 0);
+
+ keymapWrapper->mFocusSurface = nullptr;
+ keymapWrapper->mFocusSerial = 0;
+}
+
+void KeymapWrapper::GetFocusInfo(wl_surface** aFocusSurface,
+ uint32_t* aFocusSerial) {
+ KeymapWrapper* keymapWrapper = KeymapWrapper::GetInstance();
+ *aFocusSurface = keymapWrapper->mFocusSurface;
+ *aFocusSerial = keymapWrapper->mFocusSerial;
+}
+
+void KeymapWrapper::SetSeat(wl_seat* aSeat, int aId) {
+ sSeat = aSeat;
+ sSeatID = aId;
+ wl_seat_add_listener(aSeat, &seat_listener, nullptr);
+}
+
+void KeymapWrapper::ClearSeat(int aId) {
+ if (sSeatID == aId) {
+ ClearKeyboard();
+ sSeat = nullptr;
+ sSeatID = -1;
+ }
+}
+
+wl_seat* KeymapWrapper::GetSeat() { return sSeat; }
+
+void KeymapWrapper::SetKeyboard(wl_keyboard* aKeyboard) {
+ sKeyboard = aKeyboard;
+}
+
+wl_keyboard* KeymapWrapper::GetKeyboard() { return sKeyboard; }
+
+void KeymapWrapper::ClearKeyboard() {
+ if (sKeyboard) {
+ wl_keyboard_destroy(sKeyboard);
+ sKeyboard = nullptr;
+ }
+}
+#endif
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsGtkKeyUtils.h b/widget/gtk/nsGtkKeyUtils.h
new file mode 100644
index 0000000000..aac9d446f3
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -0,0 +1,509 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsGdkKeyUtils_h__
+#define __nsGdkKeyUtils_h__
+
+#include "mozilla/EventForwards.h"
+#include "nsIWidget.h"
+#include "nsTArray.h"
+
+#include <gdk/gdk.h>
+#ifdef MOZ_X11
+# include <X11/XKBlib.h>
+#endif
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include <xkbcommon/xkbcommon.h>
+#endif
+#include "X11UndefineNone.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/**
+ * KeymapWrapper is a wrapper class of GdkKeymap. GdkKeymap doesn't support
+ * all our needs, therefore, we need to access lower level APIs.
+ * But such code is usually complex and might be slow. Against such issues,
+ * we should cache some information.
+ *
+ * This class provides only static methods. The methods is using internal
+ * singleton instance which is initialized by default GdkKeymap. When the
+ * GdkKeymap is destroyed, the singleton instance will be destroyed.
+ */
+
+class KeymapWrapper {
+ public:
+ /**
+ * Compute an our DOM keycode from a GDK keyval.
+ */
+ static uint32_t ComputeDOMKeyCode(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM key name index from aGdkKeyEvent.
+ */
+ static KeyNameIndex ComputeDOMKeyNameIndex(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * Compute a DOM code name index from aGdkKeyEvent.
+ */
+ static CodeNameIndex ComputeDOMCodeNameIndex(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * We need to translate modifiers masks from Gdk to Gecko.
+ * MappedModifier is a table of mapped modifiers, we ignore other
+ * Gdk ones.
+ */
+ enum MappedModifier {
+ NOT_MODIFIER = 0x0000,
+ CAPS_LOCK = 0x0001,
+ NUM_LOCK = 0x0002,
+ SCROLL_LOCK = 0x0004,
+ SHIFT = 0x0008,
+ CTRL = 0x0010,
+ ALT = 0x0020,
+ META = 0x0040,
+ SUPER = 0x0080,
+ HYPER = 0x0100,
+ LEVEL3 = 0x0200,
+ LEVEL5 = 0x0400
+ };
+
+ /**
+ * MappedModifiers is used for combination of MappedModifier.
+ * E.g., |MappedModifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl.
+ */
+ typedef uint32_t MappedModifiers;
+
+ /**
+ * GetCurrentModifierState() returns current modifier key state.
+ * The "current" means actual state of hardware keyboard when this is
+ * called. I.e., if some key events are not still dispatched by GDK,
+ * the state may mismatch with GdkEventKey::state.
+ *
+ * @return Current modifier key state.
+ */
+ static guint GetCurrentModifierState();
+
+ /**
+ * Utility function to compute current keyboard modifiers for
+ * WidgetInputEvent
+ */
+ static uint32_t ComputeCurrentKeyModifiers();
+
+ /**
+ * Utility function to covert platform modifier state to keyboard modifiers
+ * of WidgetInputEvent
+ */
+ static uint32_t ComputeKeyModifiers(guint aGdkModifierState);
+
+ /**
+ * Convert native modifiers for `nsIWidget::SynthesizeNative*()` to
+ * GDK's state.
+ */
+ static guint ConvertWidgetModifierToGdkState(
+ nsIWidget::Modifiers aNativeModifiers);
+
+ /**
+ * InitInputEvent() initializes the aInputEvent with aModifierState.
+ */
+ static void InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aGdkModifierState);
+
+ /**
+ * InitKeyEvent() intializes aKeyEvent's modifier key related members
+ * and keycode related values.
+ *
+ * @param aKeyEvent It's an WidgetKeyboardEvent which needs to be
+ * initialized.
+ * @param aGdkKeyEvent A native GDK key event.
+ * @param aIsProcessedByIME true if aGdkKeyEvent is handled by IME.
+ */
+ static void InitKeyEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent, bool aIsProcessedByIME);
+
+ /**
+ * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+ *
+ * @param aWindow The window to dispatch a keyboard event.
+ * @param aGdkKeyEvent A native GDK_KEY_PRESS or GDK_KEY_RELEASE
+ * event.
+ * @param aIsProcessedByIME true if the event is handled by IME.
+ * @param aIsCancelled [Out] true if the default is prevented.
+ * @return true if eKeyDown event is actually dispatched.
+ * Otherwise, false.
+ */
+ static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent,
+ bool aIsProcessedByIME,
+ bool* aIsCancelled);
+
+ /**
+ * DispatchKeyDownOrKeyUpEvent() dispatches eKeyDown or eKeyUp event.
+ *
+ * @param aWindow The window to dispatch aKeyboardEvent.
+ * @param aKeyboardEvent An eKeyDown or eKeyUp event. This will be
+ * dispatched as is.
+ * @param aIsCancelled [Out] true if the default is prevented.
+ * @return true if eKeyDown event is actually dispatched.
+ * Otherwise, false.
+ */
+ static bool DispatchKeyDownOrKeyUpEvent(nsWindow* aWindow,
+ WidgetKeyboardEvent& aKeyboardEvent,
+ bool* aIsCancelled);
+
+ /**
+ * GDK_KEY_PRESS event handler.
+ *
+ * @param aWindow The window to dispatch eKeyDown event (and maybe
+ * eKeyPress events).
+ * @param aGdkKeyEvent Receivied GDK_KEY_PRESS event.
+ */
+ static void HandleKeyPressEvent(nsWindow* aWindow, GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GDK_KEY_RELEASE event handler.
+ *
+ * @param aWindow The window to dispatch eKeyUp event.
+ * @param aGdkKeyEvent Receivied GDK_KEY_RELEASE event.
+ * @return true if an event is dispatched. Otherwise, false.
+ */
+ static bool HandleKeyReleaseEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * WillDispatchKeyboardEvent() is called via
+ * TextEventDispatcherListener::WillDispatchKeyboardEvent().
+ *
+ * @param aKeyEvent An instance of KeyboardEvent which will be
+ * dispatched. This method should set charCode
+ * and alternative char codes if it's necessary.
+ * @param aGdkKeyEvent A GdkEventKey instance which caused the
+ * aKeyEvent.
+ */
+ static void WillDispatchKeyboardEvent(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+#ifdef MOZ_WAYLAND
+ /**
+ * Utility function to set all supported modifier masks
+ * from xkb_keymap. We call that from Wayland backend routines.
+ */
+ static void SetModifierMasks(xkb_keymap* aKeymap);
+
+ /**
+ * Wayland global focus handlers
+ */
+ static void SetFocusIn(wl_surface* aFocusSurface, uint32_t aFocusSerial);
+ static void SetFocusOut(wl_surface* aFocusSurface);
+ static void GetFocusInfo(wl_surface** aFocusSurface, uint32_t* aFocusSerial);
+
+ static void SetSeat(wl_seat* aSeat, int aId);
+ static void ClearSeat(int aId);
+ static wl_seat* GetSeat();
+
+ static void SetKeyboard(wl_keyboard* aKeyboard);
+ static wl_keyboard* GetKeyboard();
+ static void ClearKeyboard();
+
+ /**
+ * EnsureInstance() is provided on Wayland to register Wayland callbacks
+ * early.
+ */
+ static void EnsureInstance();
+#endif
+
+ /**
+ * ResetKeyboard is called on keymap changes from OnKeysChanged and
+ * keyboard_handle_keymap to prepare for keymap changes.
+ */
+ static void ResetKeyboard();
+
+ /**
+ * Destroys the singleton KeymapWrapper instance, if it exists.
+ */
+ static void Shutdown();
+
+ private:
+ /**
+ * GetInstance() returns a KeymapWrapper instance.
+ *
+ * @return A singleton instance of KeymapWrapper.
+ */
+ static KeymapWrapper* GetInstance();
+
+ KeymapWrapper();
+ ~KeymapWrapper();
+
+ bool mInitialized;
+
+ /**
+ * Initializing methods.
+ */
+ void Init();
+#ifdef MOZ_X11
+ void InitXKBExtension();
+ void InitBySystemSettingsX11();
+#endif
+#ifdef MOZ_WAYLAND
+ void InitBySystemSettingsWayland();
+#endif
+
+ /**
+ * mModifierKeys stores each hardware key information.
+ */
+ struct ModifierKey {
+ guint mHardwareKeycode;
+ guint mMask;
+
+ explicit ModifierKey(guint aHardwareKeycode)
+ : mHardwareKeycode(aHardwareKeycode), mMask(0) {}
+ };
+ nsTArray<ModifierKey> mModifierKeys;
+
+ /**
+ * GetModifierKey() returns modifier key information of the hardware
+ * keycode. If the key isn't a modifier key, returns nullptr.
+ */
+ ModifierKey* GetModifierKey(guint aHardwareKeycode);
+
+ /**
+ * mModifierMasks is bit masks for each modifier. The index should be one
+ * of ModifierIndex values.
+ */
+ enum ModifierIndex {
+ INDEX_NUM_LOCK,
+ INDEX_SCROLL_LOCK,
+ INDEX_ALT,
+ INDEX_META,
+ INDEX_HYPER,
+ INDEX_LEVEL3,
+ INDEX_LEVEL5,
+ COUNT_OF_MODIFIER_INDEX
+ };
+ guint mModifierMasks[COUNT_OF_MODIFIER_INDEX];
+
+ guint GetGdkModifierMask(MappedModifier aModifier) const;
+
+ /**
+ * @param aGdkKeyval A GDK defined modifier key value such as
+ * GDK_Shift_L.
+ * @return Returns MappedModifier values for aGdkKeyval.
+ * If the given key code isn't a modifier key,
+ * returns NOT_MODIFIER.
+ */
+ static MappedModifier GetModifierForGDKKeyval(guint aGdkKeyval);
+
+ static const char* GetModifierName(MappedModifier aModifier);
+
+ /**
+ * AreModifiersActive() just checks whether aGdkModifierState indicates
+ * all modifiers in aModifiers are active or not.
+ *
+ * @param aModifiers One or more of MappedModifier values except
+ * NOT_MODIFIER.
+ * @param aGdkModifierState GDK's modifier states.
+ * @return TRUE if aGdkModifierType indicates all of
+ * modifiers in aModifier are active.
+ * Otherwise, FALSE.
+ */
+ static bool AreModifiersActive(MappedModifiers aModifiers,
+ guint aGdkModifierState);
+
+ /**
+ * mGdkKeymap is a wrapped instance by this class.
+ */
+ GdkKeymap* mGdkKeymap;
+
+ /**
+ * The base event code of XKB extension.
+ */
+ int mXKBBaseEventCode;
+
+#ifdef MOZ_X11
+ /**
+ * Only auto_repeats[] stores valid value. If you need to use other
+ * members, you need to listen notification events for them.
+ * See a call of XkbSelectEventDetails() with XkbControlsNotify in
+ * InitXKBExtension().
+ */
+ XKeyboardState mKeyboardState;
+#endif
+
+ /**
+ * Pointer of the singleton instance.
+ */
+ static KeymapWrapper* sInstance;
+
+ /**
+ * Auto key repeat management.
+ */
+ static guint sLastRepeatableHardwareKeyCode;
+#ifdef MOZ_X11
+ static Time sLastRepeatableKeyTime;
+#endif
+ enum RepeatState { NOT_PRESSED, FIRST_PRESS, REPEATING };
+ static RepeatState sRepeatState;
+
+ /**
+ * IsAutoRepeatableKey() returns true if the key supports auto repeat.
+ * Otherwise, false.
+ */
+ bool IsAutoRepeatableKey(guint aHardwareKeyCode);
+
+ /**
+ * Signal handlers.
+ */
+ static void OnKeysChanged(GdkKeymap* aKeymap, KeymapWrapper* aKeymapWrapper);
+ static void OnDirectionChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper);
+
+ gulong mOnKeysChangedSignalHandle;
+ gulong mOnDirectionChangedSignalHandle;
+
+ /**
+ * GetCharCodeFor() Computes what character is inputted by the key event
+ * with aModifierState and aGroup.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @param aModifierState Combination of GdkModifierType which you
+ * want to test with aGdkKeyEvent.
+ * @param aGroup Set group in the mGdkKeymap.
+ * @return charCode which is inputted by aGdkKeyEvent.
+ * If failed, this returns 0.
+ */
+ static uint32_t GetCharCodeFor(const GdkEventKey* aGdkKeyEvent);
+ uint32_t GetCharCodeFor(const GdkEventKey* aGdkKeyEvent,
+ guint aGdkModifierState, gint aGroup);
+
+ /**
+ * GetUnmodifiedCharCodeFor() computes what character is inputted by the
+ * key event without Ctrl/Alt/Meta/Super/Hyper modifiers.
+ * If Level3 or Level5 Shift causes no character input, this also ignores
+ * them.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return charCode which is computed without modifiers
+ * which prevent text input.
+ */
+ uint32_t GetUnmodifiedCharCodeFor(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetKeyLevel() returns level of the aGdkKeyEvent in mGdkKeymap.
+ *
+ * @param aGdkKeyEvent Native key event, must not be nullptr.
+ * @return Using level. Typically, this is 0 or 1.
+ * If failed, this returns -1.
+ */
+ gint GetKeyLevel(GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetFirstLatinGroup() returns group of mGdkKeymap which can input an
+ * ASCII character by GDK_A.
+ *
+ * @return group value of GdkEventKey.
+ */
+ gint GetFirstLatinGroup();
+
+ /**
+ * IsLatinGroup() checkes whether the keyboard layout of aGroup is
+ * ASCII alphabet inputtable or not.
+ *
+ * @param aGroup The group value of GdkEventKey.
+ * @return TRUE if the keyboard layout can input
+ * ASCII alphabet. Otherwise, FALSE.
+ */
+ bool IsLatinGroup(guint8 aGroup);
+
+ /**
+ * IsBasicLatinLetterOrNumeral() Checks whether the aCharCode is an
+ * alphabet or a numeric character in ASCII.
+ *
+ * @param aCharCode Charcode which you want to test.
+ * @return TRUE if aCharCode is an alphabet or a numeric
+ * in ASCII range. Otherwise, FALSE.
+ */
+ static bool IsBasicLatinLetterOrNumeral(uint32_t aCharCode);
+
+ /**
+ * IsPrintableASCIICharacter() checks whether the aCharCode is a printable
+ * ASCII character. I.e., returns false if aCharCode is a control
+ * character even in an ASCII character.
+ */
+ static bool IsPrintableASCIICharacter(uint32_t aCharCode) {
+ return aCharCode >= 0x20 && aCharCode <= 0x7E;
+ }
+
+ /**
+ * GetGDKKeyvalWithoutModifier() returns the keyval for aGdkKeyEvent when
+ * ignoring the modifier state except NumLock. (NumLock is a key to change
+ * some key's meaning.)
+ */
+ static guint GetGDKKeyvalWithoutModifier(const GdkEventKey* aGdkKeyEvent);
+
+ /**
+ * GetDOMKeyCodeFromKeyPairs() returns DOM keycode for aGdkKeyval if
+ * it's in KeyPair table.
+ */
+ static uint32_t GetDOMKeyCodeFromKeyPairs(guint aGdkKeyval);
+
+#ifdef MOZ_X11
+ /**
+ * FilterEvents() listens all events on all our windows.
+ * Be careful, this may make damage to performance if you add expensive
+ * code in this method.
+ */
+ static GdkFilterReturn FilterEvents(GdkXEvent* aXEvent, GdkEvent* aGdkEvent,
+ gpointer aData);
+#endif
+
+ /**
+ * MaybeDispatchContextMenuEvent() may dispatch eContextMenu event if
+ * the given key combination should cause opening context menu.
+ *
+ * @param aWindow The window to dispatch a contextmenu event.
+ * @param aEvent The native key event.
+ * @return true if this method dispatched eContextMenu
+ * event. Otherwise, false.
+ * Be aware, when this returns true, the
+ * widget may have been destroyed.
+ */
+ static bool MaybeDispatchContextMenuEvent(nsWindow* aWindow,
+ const GdkEventKey* aEvent);
+
+ /**
+ * See the document of WillDispatchKeyboardEvent().
+ */
+ void WillDispatchKeyboardEventInternal(WidgetKeyboardEvent& aKeyEvent,
+ GdkEventKey* aGdkKeyEvent);
+
+ static guint GetModifierState(GdkEventKey* aGdkKeyEvent,
+ KeymapWrapper* aWrapper);
+
+#ifdef MOZ_WAYLAND
+ /**
+ * Utility function to set Xkb modifier key mask.
+ */
+ void SetModifierMask(xkb_keymap* aKeymap, ModifierIndex aModifierIndex,
+ const char* aModifierName);
+#endif
+
+#ifdef MOZ_WAYLAND
+ static wl_seat* sSeat;
+ static int sSeatID;
+ static wl_keyboard* sKeyboard;
+ wl_surface* mFocusSurface = nullptr;
+ uint32_t mFocusSerial = 0;
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif /* __nsGdkKeyUtils_h__ */
diff --git a/widget/gtk/nsGtkUtils.h b/widget/gtk/nsGtkUtils.h
new file mode 100644
index 0000000000..ae26049e7f
--- /dev/null
+++ b/widget/gtk/nsGtkUtils.h
@@ -0,0 +1,59 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsGtkUtils_h__
+#define nsGtkUtils_h__
+
+#include <glib.h>
+
+// Some gobject functions expect functions for gpointer arguments.
+// gpointer is void* but C++ doesn't like casting functions to void*.
+template <class T>
+static inline gpointer FuncToGpointer(T aFunction) {
+ return reinterpret_cast<gpointer>(
+ reinterpret_cast<uintptr_t>
+ // This cast just provides a warning if T is not a function.
+ (reinterpret_cast<void (*)()>(aFunction)));
+}
+
+// Type-safe alternative to glib's g_clear_pointer.
+//
+// Using `g_clear_pointer` itself causes UBSan to report undefined
+// behavior. The function-based definition of `g_clear_pointer` (as
+// opposed to the older preprocessor macro) treats the `destroy`
+// function as a `void (*)(void *)`, but the actual destroy functions
+// that are used (say `wl_buffer_destroy`) usually have more specific
+// pointer types.
+//
+// C++ draft n4901 [expr.call] para 6:
+//
+// Calling a function through an expression whose function type E
+// is different from the function type F of the called function’s
+// definition results in undefined behavior unless the type
+// “pointer to F” can be converted to the type “pointer to E” via
+// a function pointer conversion (7.3.14).
+//
+// §7.3.14 only talks about converting between noexcept and ordinary
+// function pointers.
+template <class T>
+static inline void MozClearPointer(T*& pointer, void (*destroy)(T*)) {
+ T* hold = pointer;
+ pointer = nullptr;
+ if (hold) {
+ destroy(hold);
+ }
+}
+
+template <class T>
+static inline void MozClearHandleID(T& handle, gboolean (*destroy)(T)) {
+ if (handle) {
+ destroy(handle);
+ handle = 0;
+ }
+}
+
+#endif // nsGtkUtils_h__
diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp
new file mode 100644
index 0000000000..73094b74ef
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.cpp
@@ -0,0 +1,121 @@
+/* vim:set sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gdk-pixbuf/gdk-pixbuf.h>
+
+#include "nsImageToPixbuf.h"
+
+#include "imgIContainer.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/RefPtr.h"
+#include "GRefPtr.h"
+#include "nsCOMPtr.h"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SurfaceFormat;
+
+inline unsigned char unpremultiply(unsigned char color, unsigned char alpha) {
+ if (alpha == 0) return 0;
+ // plus alpha/2 to round instead of truncate
+ return (color * 255 + alpha / 2) / alpha;
+}
+
+already_AddRefed<GdkPixbuf> nsImageToPixbuf::ImageToPixbuf(
+ imgIContainer* aImage, const mozilla::Maybe<nsIntSize>& aOverrideSize) {
+ RefPtr<SourceSurface> surface;
+
+ const uint32_t flags =
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY;
+ if (aOverrideSize) {
+ surface = aImage->GetFrameAtSize(*aOverrideSize,
+ imgIContainer::FRAME_CURRENT, flags);
+ } else {
+ surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT, flags);
+ }
+
+ // If the last call failed, it was probably because our call stack originates
+ // in an imgINotificationObserver event, meaning that we're not allowed
+ // request a sync decode. Presumably the originating event is something
+ // sensible like OnStopFrame(), so we can just retry the call without a sync
+ // decode.
+ if (!surface) {
+ if (aOverrideSize) {
+ surface =
+ aImage->GetFrameAtSize(*aOverrideSize, imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_NONE);
+ } else {
+ surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_NONE);
+ }
+ }
+
+ NS_ENSURE_TRUE(surface, nullptr);
+
+ return SourceSurfaceToPixbuf(surface, surface->GetSize().width,
+ surface->GetSize().height);
+}
+
+already_AddRefed<GdkPixbuf> nsImageToPixbuf::SourceSurfaceToPixbuf(
+ SourceSurface* aSurface, int32_t aWidth, int32_t aHeight) {
+ MOZ_ASSERT(aWidth <= aSurface->GetSize().width &&
+ aHeight <= aSurface->GetSize().height,
+ "Requested rect is bigger than the supplied surface");
+
+ RefPtr<GdkPixbuf> pixbuf =
+ dont_AddRef(gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, aWidth, aHeight));
+ if (!pixbuf) {
+ return nullptr;
+ }
+
+ uint32_t destStride = gdk_pixbuf_get_rowstride(pixbuf);
+ guchar* destPixels = gdk_pixbuf_get_pixels(pixbuf);
+
+ RefPtr<DataSourceSurface> dataSurface = aSurface->GetDataSurface();
+ DataSourceSurface::MappedSurface map;
+ if (!dataSurface->Map(DataSourceSurface::MapType::READ, &map)) {
+ return nullptr;
+ }
+
+ uint8_t* srcData = map.mData;
+ int32_t srcStride = map.mStride;
+
+ SurfaceFormat format = dataSurface->GetFormat();
+
+ for (int32_t row = 0; row < aHeight; ++row) {
+ for (int32_t col = 0; col < aWidth; ++col) {
+ guchar* destPixel = destPixels + row * destStride + 4 * col;
+
+ uint32_t* srcPixel =
+ reinterpret_cast<uint32_t*>((srcData + row * srcStride + 4 * col));
+
+ if (format == SurfaceFormat::B8G8R8A8) {
+ const uint8_t a = (*srcPixel >> 24) & 0xFF;
+ const uint8_t r = unpremultiply((*srcPixel >> 16) & 0xFF, a);
+ const uint8_t g = unpremultiply((*srcPixel >> 8) & 0xFF, a);
+ const uint8_t b = unpremultiply((*srcPixel >> 0) & 0xFF, a);
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = a;
+ } else {
+ MOZ_ASSERT(format == SurfaceFormat::B8G8R8X8);
+
+ const uint8_t r = (*srcPixel >> 16) & 0xFF;
+ const uint8_t g = (*srcPixel >> 8) & 0xFF;
+ const uint8_t b = (*srcPixel >> 0) & 0xFF;
+
+ *destPixel++ = r;
+ *destPixel++ = g;
+ *destPixel++ = b;
+ *destPixel++ = 0xFF; // A
+ }
+ }
+ }
+
+ dataSurface->Unmap();
+
+ return pixbuf.forget();
+}
diff --git a/widget/gtk/nsImageToPixbuf.h b/widget/gtk/nsImageToPixbuf.h
new file mode 100644
index 0000000000..500489d7eb
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.h
@@ -0,0 +1,37 @@
+/* vim:set sw=2 sts=2 et cin: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NSIMAGETOPIXBUF_H_
+#define NSIMAGETOPIXBUF_H_
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "nsSize.h"
+
+class imgIContainer;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+namespace mozilla::gfx {
+class SourceSurface;
+} // namespace mozilla::gfx
+
+class nsImageToPixbuf final {
+ using SourceSurface = mozilla::gfx::SourceSurface;
+
+ public:
+ // Friendlier version of ConvertImageToPixbuf for callers inside of
+ // widget
+ static already_AddRefed<GdkPixbuf> ImageToPixbuf(
+ imgIContainer* aImage,
+ const mozilla::Maybe<nsIntSize>& aOverrideSize = mozilla::Nothing());
+ static already_AddRefed<GdkPixbuf> SourceSurfaceToPixbuf(
+ SourceSurface* aSurface, int32_t aWidth, int32_t aHeight);
+
+ private:
+ ~nsImageToPixbuf() = default;
+};
+
+#endif
diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..c4b430d2eb
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -0,0 +1,2329 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+// for strtod()
+#include <stdlib.h>
+#include <dlfcn.h>
+
+#include "nsLookAndFeel.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <pango/pango.h>
+#include <pango/pango-fontmap.h>
+#include <fontconfig/fontconfig.h>
+
+#include "GRefPtr.h"
+#include "GUniquePtr.h"
+#include "nsGtkUtils.h"
+#include "gfxPlatformGtk.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/AutoRestore.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/WidgetUtilsGtk.h"
+#include "ScreenHelperGTK.h"
+#include "ScrollbarDrawing.h"
+
+#include "gtkdrawing.h"
+#include "nsString.h"
+#include "nsStyleConsts.h"
+#include "gfxFontConstants.h"
+#include "WidgetUtils.h"
+#include "nsWindow.h"
+
+#include "mozilla/gfx/2D.h"
+
+#include <cairo-gobject.h>
+#include <dlfcn.h>
+#include "WidgetStyleCache.h"
+#include "prenv.h"
+#include "nsCSSColorUtils.h"
+#include "mozilla/Preferences.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+static LazyLogModule gLnfLog("LookAndFeel");
+# define LOGLNF(...) MOZ_LOG(gLnfLog, LogLevel::Debug, (__VA_ARGS__))
+# define LOGLNF_ENABLED() MOZ_LOG_TEST(gLnfLog, LogLevel::Debug)
+#else
+# define LOGLNF(args)
+# define LOGLNF_ENABLED() false
+#endif /* MOZ_LOGGING */
+
+#define GDK_COLOR_TO_NS_RGB(c) \
+ ((nscolor)NS_RGB(c.red >> 8, c.green >> 8, c.blue >> 8))
+#define GDK_RGBA_TO_NS_RGBA(c) \
+ ((nscolor)NS_RGBA((int)((c).red * 255), (int)((c).green * 255), \
+ (int)((c).blue * 255), (int)((c).alpha * 255)))
+
+static bool sIgnoreChangedSettings = false;
+
+static void OnSettingsChange() {
+ if (sIgnoreChangedSettings) {
+ return;
+ }
+ // TODO: We could be more granular here, but for now assume everything
+ // changed.
+ LookAndFeel::NotifyChangedAllWindows(widget::ThemeChangeKind::StyleAndLayout);
+ widget::IMContextWrapper::OnThemeChanged();
+}
+
+static void settings_changed_cb(GtkSettings*, GParamSpec*, void*) {
+ OnSettingsChange();
+}
+
+static bool sCSDAvailable;
+
+static nsCString GVariantToString(GVariant* aVariant) {
+ nsCString ret;
+ gchar* s = g_variant_print(aVariant, TRUE);
+ if (s) {
+ ret.Assign(s);
+ g_free(s);
+ }
+ return ret;
+}
+
+static nsDependentCString GVariantGetString(GVariant* aVariant) {
+ gsize len = 0;
+ const gchar* v = g_variant_get_string(aVariant, &len);
+ return nsDependentCString(v, len);
+}
+
+static void settings_changed_signal_cb(GDBusProxy* proxy, gchar* sender_name,
+ gchar* signal_name, GVariant* parameters,
+ gpointer user_data) {
+ LOGLNF("Settings Change sender=%s signal=%s params=%s\n", sender_name,
+ signal_name, GVariantToString(parameters).get());
+ if (strcmp(signal_name, "SettingChanged")) {
+ NS_WARNING("Unknown change signal for settings");
+ return;
+ }
+ RefPtr<GVariant> ns = dont_AddRef(g_variant_get_child_value(parameters, 0));
+ RefPtr<GVariant> key = dont_AddRef(g_variant_get_child_value(parameters, 1));
+ // Third parameter is the value, but we don't care about it.
+ if (!ns || !key || !g_variant_is_of_type(ns, G_VARIANT_TYPE_STRING) ||
+ !g_variant_is_of_type(key, G_VARIANT_TYPE_STRING)) {
+ MOZ_ASSERT(false, "Unexpected setting change signal parameters");
+ return;
+ }
+
+ auto* lnf = static_cast<nsLookAndFeel*>(user_data);
+
+ auto nsStr = GVariantGetString(ns);
+ auto keyStr = GVariantGetString(key);
+ if (nsStr.Equals("org.freedesktop.appearance"_ns) &&
+ keyStr.Equals("color-scheme"_ns)) {
+ lnf->OnColorSchemeSettingChanged();
+ }
+}
+
+void nsLookAndFeel::WatchDBus() {
+ GUniquePtr<GError> error;
+ mDBusSettingsProxy = dont_AddRef(g_dbus_proxy_new_for_bus_sync(
+ G_BUS_TYPE_SESSION, G_DBUS_PROXY_FLAGS_NONE, nullptr,
+ "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop",
+ "org.freedesktop.portal.Settings", nullptr, getter_Transfers(error)));
+ if (mDBusSettingsProxy) {
+ g_signal_connect(mDBusSettingsProxy, "g-signal",
+ G_CALLBACK(settings_changed_signal_cb), this);
+ } else {
+ LOGLNF("Can't create DBus proxy for settings: %s\n", error->message);
+ return;
+ }
+
+ // DBus interface was started after L&F init so we need to load
+ // our settings from DBus explicitly.
+ if (!sIgnoreChangedSettings) {
+ OnColorSchemeSettingChanged();
+ }
+}
+
+void nsLookAndFeel::UnwatchDBus() {
+ if (mDBusSettingsProxy) {
+ g_signal_handlers_disconnect_by_func(
+ mDBusSettingsProxy, FuncToGpointer(settings_changed_signal_cb), this);
+ mDBusSettingsProxy = nullptr;
+ }
+}
+
+nsLookAndFeel::nsLookAndFeel() {
+ static constexpr nsLiteralCString kObservedSettings[] = {
+ // Affects system font sizes.
+ "notify::gtk-xft-dpi"_ns,
+ // Affects mSystemTheme and mAltTheme as expected.
+ "notify::gtk-theme-name"_ns,
+ // System fonts?
+ "notify::gtk-font-name"_ns,
+ // prefers-reduced-motion
+ "notify::gtk-enable-animations"_ns,
+ // CSD media queries, etc.
+ "notify::gtk-decoration-layout"_ns,
+ // Text resolution affects system font and widget sizes.
+ "notify::resolution"_ns,
+ // These three Affect mCaretBlinkTime
+ "notify::gtk-cursor-blink"_ns,
+ "notify::gtk-cursor-blink-time"_ns,
+ "notify::gtk-cursor-blink-timeout"_ns,
+ // Affects SelectTextfieldsOnKeyFocus
+ "notify::gtk-entry-select-on-focus"_ns,
+ // Affects ScrollToClick
+ "notify::gtk-primary-button-warps-slider"_ns,
+ // Affects SubmenuDelay
+ "notify::gtk-menu-popup-delay"_ns,
+ // Affects DragThresholdX/Y
+ "notify::gtk-dnd-drag-threshold"_ns,
+ // Affects titlebar actions loaded at moz_gtk_refresh().
+ "notify::gtk-titlebar-double-click"_ns,
+ "notify::gtk-titlebar-middle-click"_ns,
+ };
+
+ GtkSettings* settings = gtk_settings_get_default();
+ for (const auto& setting : kObservedSettings) {
+ g_signal_connect_after(settings, setting.get(),
+ G_CALLBACK(settings_changed_cb), nullptr);
+ }
+
+ sCSDAvailable =
+ nsWindow::GetSystemGtkWindowDecoration() != nsWindow::GTK_DECORATION_NONE;
+
+ if (ShouldUsePortal(PortalKind::Settings)) {
+ mDBusID = g_bus_watch_name(
+ G_BUS_TYPE_SESSION, "org.freedesktop.portal.Desktop",
+ G_BUS_NAME_WATCHER_FLAGS_AUTO_START,
+ [](GDBusConnection*, const gchar*, const gchar*,
+ gpointer data) -> void {
+ auto* lnf = static_cast<nsLookAndFeel*>(data);
+ lnf->WatchDBus();
+ },
+ [](GDBusConnection*, const gchar*, gpointer data) -> void {
+ auto* lnf = static_cast<nsLookAndFeel*>(data);
+ lnf->UnwatchDBus();
+ },
+ this, nullptr);
+ }
+ if (IsKdeDesktopEnvironment()) {
+ GUniquePtr<gchar> path(
+ g_strconcat(g_get_user_config_dir(), "/gtk-3.0/colors.css", NULL));
+ mKdeColors = dont_AddRef(g_file_new_for_path(path.get()));
+ mKdeColorsMonitor = dont_AddRef(
+ g_file_monitor_file(mKdeColors.get(), G_FILE_MONITOR_NONE, NULL, NULL));
+ if (mKdeColorsMonitor) {
+ g_signal_connect(mKdeColorsMonitor.get(), "changed",
+ G_CALLBACK(settings_changed_cb), NULL);
+ }
+ }
+}
+
+nsLookAndFeel::~nsLookAndFeel() {
+ ClearRoundedCornerProvider();
+ if (mDBusID) {
+ g_bus_unwatch_name(mDBusID);
+ mDBusID = 0;
+ }
+ UnwatchDBus();
+ g_signal_handlers_disconnect_by_func(
+ gtk_settings_get_default(), FuncToGpointer(settings_changed_cb), nullptr);
+}
+
+#if 0
+static void DumpStyleContext(GtkStyleContext* aStyle) {
+ static auto sGtkStyleContextToString =
+ reinterpret_cast<char* (*)(GtkStyleContext*, gint)>(
+ dlsym(RTLD_DEFAULT, "gtk_style_context_to_string"));
+ char* str = sGtkStyleContextToString(aStyle, ~0);
+ printf("%s\n", str);
+ g_free(str);
+ str = gtk_widget_path_to_string(gtk_style_context_get_path(aStyle));
+ printf("%s\n", str);
+ g_free(str);
+}
+#endif
+
+// Modifies color |*aDest| as if a pattern of color |aSource| was painted with
+// CAIRO_OPERATOR_OVER to a surface with color |*aDest|.
+static void ApplyColorOver(const GdkRGBA& aSource, GdkRGBA* aDest) {
+ gdouble sourceCoef = aSource.alpha;
+ gdouble destCoef = aDest->alpha * (1.0 - sourceCoef);
+ gdouble resultAlpha = sourceCoef + destCoef;
+ if (resultAlpha != 0.0) { // don't divide by zero
+ destCoef /= resultAlpha;
+ sourceCoef /= resultAlpha;
+ aDest->red = sourceCoef * aSource.red + destCoef * aDest->red;
+ aDest->green = sourceCoef * aSource.green + destCoef * aDest->green;
+ aDest->blue = sourceCoef * aSource.blue + destCoef * aDest->blue;
+ aDest->alpha = resultAlpha;
+ }
+}
+
+static void GetLightAndDarkness(const GdkRGBA& aColor, double* aLightness,
+ double* aDarkness) {
+ double sum = aColor.red + aColor.green + aColor.blue;
+ *aLightness = sum * aColor.alpha;
+ *aDarkness = (3.0 - sum) * aColor.alpha;
+}
+
+static bool GetGradientColors(const GValue* aValue, GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
+ return false;
+ }
+
+ auto pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
+ if (!pattern) {
+ return false;
+ }
+
+ // Just picking the lightest and darkest colors as simple samples rather
+ // than trying to blend, which could get messy if there are many stops.
+ if (CAIRO_STATUS_SUCCESS !=
+ cairo_pattern_get_color_stop_rgba(pattern, 0, nullptr, &aDarkColor->red,
+ &aDarkColor->green, &aDarkColor->blue,
+ &aDarkColor->alpha)) {
+ return false;
+ }
+
+ double maxLightness, maxDarkness;
+ GetLightAndDarkness(*aDarkColor, &maxLightness, &maxDarkness);
+ *aLightColor = *aDarkColor;
+
+ GdkRGBA stop;
+ for (int index = 1;
+ CAIRO_STATUS_SUCCESS ==
+ cairo_pattern_get_color_stop_rgba(pattern, index, nullptr, &stop.red,
+ &stop.green, &stop.blue, &stop.alpha);
+ ++index) {
+ double lightness, darkness;
+ GetLightAndDarkness(stop, &lightness, &darkness);
+ if (lightness > maxLightness) {
+ maxLightness = lightness;
+ *aLightColor = stop;
+ }
+ if (darkness > maxDarkness) {
+ maxDarkness = darkness;
+ *aDarkColor = stop;
+ }
+ }
+
+ return true;
+}
+
+static bool GetColorFromImagePattern(const GValue* aValue, nscolor* aColor) {
+ if (!G_TYPE_CHECK_VALUE_TYPE(aValue, CAIRO_GOBJECT_TYPE_PATTERN)) {
+ return false;
+ }
+
+ auto* pattern = static_cast<cairo_pattern_t*>(g_value_get_boxed(aValue));
+ if (!pattern) {
+ return false;
+ }
+
+ cairo_surface_t* surface;
+ if (cairo_pattern_get_surface(pattern, &surface) != CAIRO_STATUS_SUCCESS) {
+ return false;
+ }
+
+ cairo_format_t format = cairo_image_surface_get_format(surface);
+ if (format == CAIRO_FORMAT_INVALID) {
+ return false;
+ }
+ int width = cairo_image_surface_get_width(surface);
+ int height = cairo_image_surface_get_height(surface);
+ int stride = cairo_image_surface_get_stride(surface);
+ if (!width || !height) {
+ return false;
+ }
+
+ // Guesstimate the central pixel would have a sensible color.
+ int x = width / 2;
+ int y = height / 2;
+
+ unsigned char* data = cairo_image_surface_get_data(surface);
+ switch (format) {
+ // Most (all?) GTK images / patterns / etc use ARGB32.
+ case CAIRO_FORMAT_ARGB32: {
+ size_t offset = x * 4 + y * stride;
+ uint32_t* pixel = reinterpret_cast<uint32_t*>(data + offset);
+ *aColor = gfx::sRGBColor::UnusualFromARGB(*pixel).ToABGR();
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+static bool GetUnicoBorderGradientColors(GtkStyleContext* aContext,
+ GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ // Ubuntu 12.04 has GTK engine Unico-1.0.2, which overrides render_frame,
+ // providing its own border code. Ubuntu 14.04 has
+ // Unico-1.0.3+14.04.20140109, which does not override render_frame, and
+ // so does not need special attention. The earlier Unico can be detected
+ // by the -unico-border-gradient style property it registers.
+ // gtk_style_properties_lookup_property() is checked first to avoid the
+ // warning from gtk_style_context_get_property() when the property does
+ // not exist. (gtk_render_frame() of GTK+ 3.16 no longer uses the
+ // engine.)
+ const char* propertyName = "-unico-border-gradient";
+ if (!gtk_style_properties_lookup_property(propertyName, nullptr, nullptr))
+ return false;
+
+ // -unico-border-gradient is used only when the CSS node's engine is Unico.
+ GtkThemingEngine* engine;
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ gtk_style_context_get(aContext, state, "engine", &engine, nullptr);
+ if (strcmp(g_type_name(G_TYPE_FROM_INSTANCE(engine)), "UnicoEngine") != 0)
+ return false;
+
+ // draw_border() of Unico engine uses -unico-border-gradient
+ // in preference to border-color.
+ GValue value = G_VALUE_INIT;
+ gtk_style_context_get_property(aContext, propertyName, state, &value);
+
+ bool result = GetGradientColors(&value, aLightColor, aDarkColor);
+
+ g_value_unset(&value);
+ return result;
+}
+
+// Sets |aLightColor| and |aDarkColor| to colors from |aContext|. Returns
+// true if |aContext| uses these colors to render a visible border.
+// If returning false, then the colors returned are a fallback from the
+// border-color value even though |aContext| does not use these colors to
+// render a border.
+static bool GetBorderColors(GtkStyleContext* aContext, GdkRGBA* aLightColor,
+ GdkRGBA* aDarkColor) {
+ // Determine whether the border on this style context is visible.
+ GtkStateFlags state = gtk_style_context_get_state(aContext);
+ GtkBorderStyle borderStyle;
+ gtk_style_context_get(aContext, state, GTK_STYLE_PROPERTY_BORDER_STYLE,
+ &borderStyle, nullptr);
+ bool visible = borderStyle != GTK_BORDER_STYLE_NONE &&
+ borderStyle != GTK_BORDER_STYLE_HIDDEN;
+ if (visible) {
+ // GTK has an initial value of zero for border-widths, and so themes
+ // need to explicitly set border-widths to make borders visible.
+ GtkBorder border;
+ gtk_style_context_get_border(aContext, state, &border);
+ visible = border.top != 0 || border.right != 0 || border.bottom != 0 ||
+ border.left != 0;
+ }
+
+ if (visible &&
+ GetUnicoBorderGradientColors(aContext, aLightColor, aDarkColor))
+ return true;
+
+ // The initial value for the border-color is the foreground color, and so
+ // this will usually return a color distinct from the background even if
+ // there is no visible border detected.
+ gtk_style_context_get_border_color(aContext, state, aDarkColor);
+ // TODO GTK3 - update aLightColor
+ // for GTK_BORDER_STYLE_INSET/OUTSET/GROVE/RIDGE border styles.
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=978172#c25
+ *aLightColor = *aDarkColor;
+ return visible;
+}
+
+static bool GetBorderColors(GtkStyleContext* aContext, nscolor* aLightColor,
+ nscolor* aDarkColor) {
+ GdkRGBA lightColor, darkColor;
+ bool ret = GetBorderColors(aContext, &lightColor, &darkColor);
+ *aLightColor = GDK_RGBA_TO_NS_RGBA(lightColor);
+ *aDarkColor = GDK_RGBA_TO_NS_RGBA(darkColor);
+ return ret;
+}
+
+// Finds ideal cell highlight colors used for unfocused+selected cells distinct
+// from both Highlight, used as focused+selected background, and the listbox
+// background which is assumed to be similar to -moz-field
+void nsLookAndFeel::PerThemeData::InitCellHighlightColors() {
+ int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE_BG;
+ int32_t backLuminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(mWindow.mBg, mField.mBg);
+ if (backLuminosityDifference >= minLuminosityDifference) {
+ mCellHighlight = mWindow;
+ return;
+ }
+
+ uint16_t hue, sat, luminance;
+ uint8_t alpha;
+ mCellHighlight = mField;
+
+ NS_RGB2HSV(mCellHighlight.mBg, hue, sat, luminance, alpha);
+
+ uint16_t step = 30;
+ // Lighten the color if the color is very dark
+ if (luminance <= step) {
+ luminance += step;
+ }
+ // Darken it if it is very light
+ else if (luminance >= 255 - step) {
+ luminance -= step;
+ }
+ // Otherwise, compute what works best depending on the text luminance.
+ else {
+ uint16_t textHue, textSat, textLuminance;
+ uint8_t textAlpha;
+ NS_RGB2HSV(mCellHighlight.mFg, textHue, textSat, textLuminance, textAlpha);
+ // Text is darker than background, use a lighter shade
+ if (textLuminance < luminance) {
+ luminance += step;
+ }
+ // Otherwise, use a darker shade
+ else {
+ luminance -= step;
+ }
+ }
+ NS_HSV2RGB(mCellHighlight.mBg, hue, sat, luminance, alpha);
+}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+void nsLookAndFeel::RefreshImpl() {
+ mInitialized = false;
+ moz_gtk_refresh();
+
+ nsXPLookAndFeel::RefreshImpl();
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, ColorScheme aScheme,
+ nscolor& aColor) {
+ EnsureInit();
+
+ const auto& theme =
+ aScheme == ColorScheme::Light ? LightTheme() : DarkTheme();
+ return theme.GetColor(aID, aColor);
+}
+
+static bool ShouldUseColorForActiveDarkScrollbarThumb(nscolor aColor) {
+ auto IsDifferentEnough = [](int32_t aChannel, int32_t aOtherChannel) {
+ return std::abs(aChannel - aOtherChannel) > 10;
+ };
+ return IsDifferentEnough(NS_GET_R(aColor), NS_GET_G(aColor)) ||
+ IsDifferentEnough(NS_GET_R(aColor), NS_GET_B(aColor));
+}
+
+static bool ShouldUseThemedScrollbarColor(StyleSystemColor aID, nscolor aColor,
+ bool aIsDark) {
+ if (!aIsDark) {
+ return true;
+ }
+ if (StaticPrefs::widget_non_native_theme_scrollbar_dark_themed()) {
+ return true;
+ }
+ return aID == StyleSystemColor::ThemedScrollbarThumbActive &&
+ StaticPrefs::widget_non_native_theme_scrollbar_active_always_themed();
+}
+
+nsresult nsLookAndFeel::PerThemeData::GetColor(ColorID aID,
+ nscolor& aColor) const {
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // The CSS2 colors below are used.
+ case ColorID::Appworkspace: // MDI background color
+ case ColorID::Background: // desktop background
+ case ColorID::Window:
+ case ColorID::Windowframe:
+ case ColorID::MozCombobox:
+ aColor = mWindow.mBg;
+ break;
+ case ColorID::Windowtext:
+ aColor = mWindow.mFg;
+ break;
+ case ColorID::MozDialog:
+ aColor = mDialog.mBg;
+ break;
+ case ColorID::MozDialogtext:
+ aColor = mDialog.mFg;
+ break;
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::Highlight: // preference selected item,
+ aColor = mSelectedText.mBg;
+ break;
+ case ColorID::Highlighttext:
+ if (NS_GET_A(mSelectedText.mBg) < 155) {
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ }
+ [[fallthrough]];
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ aColor = mSelectedText.mFg;
+ break;
+ case ColorID::Selecteditem:
+ aColor = mSelectedItem.mBg;
+ break;
+ case ColorID::Selecteditemtext:
+ aColor = mSelectedItem.mFg;
+ break;
+ case ColorID::Accentcolor:
+ aColor = mAccent.mBg;
+ break;
+ case ColorID::Accentcolortext:
+ aColor = mAccent.mFg;
+ break;
+ case ColorID::MozCellhighlight:
+ aColor = mCellHighlight.mBg;
+ break;
+ case ColorID::MozCellhighlighttext:
+ aColor = mCellHighlight.mFg;
+ break;
+ case ColorID::IMERawInputBackground:
+ case ColorID::IMEConvertedTextBackground:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::IMERawInputForeground:
+ case ColorID::IMEConvertedTextForeground:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMERawInputUnderline:
+ case ColorID::IMEConvertedTextUnderline:
+ aColor = NS_SAME_AS_FOREGROUND_COLOR;
+ break;
+ case ColorID::IMESelectedRawTextUnderline:
+ case ColorID::IMESelectedConvertedTextUnderline:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::Scrollbar:
+ aColor = mThemedScrollbar;
+ break;
+ case ColorID::ThemedScrollbar:
+ aColor = mThemedScrollbar;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ case ColorID::ThemedScrollbarInactive:
+ aColor = mThemedScrollbarInactive;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ case ColorID::ThemedScrollbarThumb:
+ aColor = mThemedScrollbarThumb;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ case ColorID::ThemedScrollbarThumbHover:
+ aColor = mThemedScrollbarThumbHover;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ case ColorID::ThemedScrollbarThumbActive:
+ aColor = mThemedScrollbarThumbActive;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+ case ColorID::ThemedScrollbarThumbInactive:
+ aColor = mThemedScrollbarThumbInactive;
+ if (!ShouldUseThemedScrollbarColor(aID, aColor, mIsDark)) {
+ return NS_ERROR_FAILURE;
+ }
+ break;
+
+ // css2 http://www.w3.org/TR/REC-CSS2/ui.html#system-colors
+ case ColorID::Activeborder:
+ // active window border
+ aColor = mMozWindowActiveBorder;
+ break;
+ case ColorID::Inactiveborder:
+ // inactive window border
+ aColor = mMozWindowInactiveBorder;
+ break;
+ case ColorID::Graytext: // disabled text in windows, menus, etc.
+ aColor = mGrayText;
+ break;
+ case ColorID::Activecaption:
+ aColor = mTitlebar.mBg;
+ break;
+ case ColorID::Captiontext: // text in active window caption (titlebar)
+ aColor = mTitlebar.mFg;
+ break;
+ case ColorID::Inactivecaption:
+ // inactive window caption
+ aColor = mTitlebarInactive.mBg;
+ break;
+ case ColorID::Inactivecaptiontext:
+ aColor = mTitlebarInactive.mFg;
+ break;
+ case ColorID::Infobackground:
+ aColor = mInfo.mBg;
+ break;
+ case ColorID::Infotext:
+ aColor = mInfo.mFg;
+ break;
+ case ColorID::Menu:
+ aColor = mMenu.mBg;
+ break;
+ case ColorID::Menutext:
+ aColor = mMenu.mFg;
+ break;
+ case ColorID::MozHeaderbar:
+ aColor = mHeaderBar.mBg;
+ break;
+ case ColorID::MozHeaderbartext:
+ aColor = mHeaderBar.mFg;
+ break;
+ case ColorID::MozHeaderbarinactive:
+ aColor = mHeaderBarInactive.mBg;
+ break;
+ case ColorID::MozHeaderbarinactivetext:
+ aColor = mHeaderBarInactive.mFg;
+ break;
+ case ColorID::Threedface:
+ case ColorID::Buttonface:
+ case ColorID::MozButtondisabledface:
+ // 3-D face color
+ aColor = mWindow.mBg;
+ break;
+
+ case ColorID::Buttontext:
+ // text on push buttons
+ aColor = mButton.mFg;
+ break;
+
+ case ColorID::Buttonhighlight:
+ // 3-D highlighted edge color
+ case ColorID::Threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = mThreeDHighlight;
+ break;
+
+ case ColorID::Buttonshadow:
+ // 3-D shadow edge color
+ case ColorID::Threedshadow:
+ // 3-D shadow inner edge color
+ aColor = mThreeDShadow;
+ break;
+ case ColorID::Buttonborder:
+ aColor = mButtonBorder;
+ break;
+ case ColorID::Threedlightshadow:
+ case ColorID::MozDisabledfield:
+ aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xE0, 0xE0, 0xE0);
+ break;
+ case ColorID::Threeddarkshadow:
+ aColor = mIsDark ? *GenericDarkColor(aID) : NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+
+ case ColorID::MozEventreerow:
+ case ColorID::Field:
+ aColor = mField.mBg;
+ break;
+ case ColorID::Fieldtext:
+ aColor = mField.mFg;
+ break;
+ case ColorID::MozSidebar:
+ aColor = mSidebar.mBg;
+ break;
+ case ColorID::MozSidebartext:
+ aColor = mSidebar.mFg;
+ break;
+ case ColorID::MozSidebarborder:
+ aColor = mSidebarBorder;
+ break;
+ case ColorID::MozButtonhoverface:
+ aColor = mButtonHover.mBg;
+ break;
+ case ColorID::MozButtonhovertext:
+ aColor = mButtonHover.mFg;
+ break;
+ case ColorID::MozButtonactiveface:
+ aColor = mButtonActive.mBg;
+ break;
+ case ColorID::MozButtonactivetext:
+ aColor = mButtonActive.mFg;
+ break;
+ case ColorID::MozMenuhover:
+ aColor = mMenuHover.mBg;
+ break;
+ case ColorID::MozMenuhovertext:
+ aColor = mMenuHover.mFg;
+ break;
+ case ColorID::MozMenuhoverdisabled:
+ aColor = NS_TRANSPARENT;
+ break;
+ case ColorID::MozOddtreerow:
+ aColor = mOddCellBackground;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ aColor = mNativeHyperLinkText;
+ break;
+ case ColorID::MozNativevisitedhyperlinktext:
+ aColor = mNativeVisitedHyperLinkText;
+ break;
+ case ColorID::MozComboboxtext:
+ aColor = mComboBoxText;
+ break;
+ case ColorID::MozColheader:
+ aColor = mMozColHeader.mBg;
+ break;
+ case ColorID::MozColheadertext:
+ aColor = mMozColHeader.mFg;
+ break;
+ case ColorID::MozColheaderhover:
+ aColor = mMozColHeaderHover.mBg;
+ break;
+ case ColorID::MozColheaderhovertext:
+ aColor = mMozColHeaderHover.mFg;
+ break;
+ case ColorID::MozColheaderactive:
+ aColor = mMozColHeaderActive.mBg;
+ break;
+ case ColorID::MozColheaderactivetext:
+ aColor = mMozColHeaderActive.mFg;
+ break;
+ case ColorID::SpellCheckerUnderline:
+ case ColorID::Mark:
+ case ColorID::Marktext:
+ aColor = GetStandinForNativeColor(
+ aID, mIsDark ? ColorScheme::Dark : ColorScheme::Light);
+ break;
+ default:
+ /* default color is BLACK */
+ aColor = 0;
+ res = NS_ERROR_FAILURE;
+ break;
+ }
+
+ return res;
+}
+
+static int32_t CheckWidgetStyle(GtkWidget* aWidget, const char* aStyle,
+ int32_t aResult) {
+ gboolean value = FALSE;
+ gtk_widget_style_get(aWidget, aStyle, &value, nullptr);
+ return value ? aResult : 0;
+}
+
+static int32_t ConvertGTKStepperStyleToMozillaScrollArrowStyle(
+ GtkWidget* aWidget) {
+ if (!aWidget) return mozilla::LookAndFeel::eScrollArrowStyle_Single;
+
+ return CheckWidgetStyle(aWidget, "has-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartBackward) |
+ CheckWidgetStyle(aWidget, "has-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndForward) |
+ CheckWidgetStyle(aWidget, "has-secondary-backward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_EndBackward) |
+ CheckWidgetStyle(aWidget, "has-secondary-forward-stepper",
+ mozilla::LookAndFeel::eScrollArrow_StartForward);
+}
+
+nsresult nsLookAndFeel::NativeGetInt(IntID aID, int32_t& aResult) {
+ nsresult res = NS_OK;
+
+ // We use delayed initialization by EnsureInit() here
+ // to make sure mozilla::Preferences is available (Bug 115807).
+ // IntID::UseAccessibilityTheme is requested before user preferences
+ // are read, and so EnsureInit(), which depends on preference values,
+ // is deliberately delayed until required.
+ switch (aID) {
+ case IntID::ScrollButtonLeftMouseButtonAction:
+ aResult = 0;
+ break;
+ case IntID::ScrollButtonMiddleMouseButtonAction:
+ aResult = 1;
+ break;
+ case IntID::ScrollButtonRightMouseButtonAction:
+ aResult = 2;
+ break;
+ case IntID::CaretBlinkTime:
+ EnsureInit();
+ aResult = mCaretBlinkTime;
+ break;
+ case IntID::CaretBlinkCount:
+ EnsureInit();
+ aResult = mCaretBlinkCount;
+ break;
+ case IntID::CaretWidth:
+ aResult = 1;
+ break;
+ case IntID::ShowCaretDuringSelection:
+ aResult = 0;
+ break;
+ case IntID::SelectTextfieldsOnKeyFocus: {
+ GtkSettings* settings;
+ gboolean select_on_focus;
+
+ settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-entry-select-on-focus", &select_on_focus,
+ nullptr);
+
+ if (select_on_focus)
+ aResult = 1;
+ else
+ aResult = 0;
+
+ } break;
+ case IntID::ScrollToClick: {
+ GtkSettings* settings;
+ gboolean warps_slider = FALSE;
+
+ settings = gtk_settings_get_default();
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-primary-button-warps-slider")) {
+ g_object_get(settings, "gtk-primary-button-warps-slider", &warps_slider,
+ nullptr);
+ }
+
+ if (warps_slider)
+ aResult = 1;
+ else
+ aResult = 0;
+ } break;
+ case IntID::SubmenuDelay: {
+ GtkSettings* settings;
+ gint delay;
+
+ settings = gtk_settings_get_default();
+ g_object_get(settings, "gtk-menu-popup-delay", &delay, nullptr);
+ aResult = (int32_t)delay;
+ break;
+ }
+ case IntID::TooltipDelay: {
+ aResult = 500;
+ break;
+ }
+ case IntID::MenusCanOverlapOSBar:
+ // we want XUL popups to be able to overlap the task bar.
+ aResult = 1;
+ break;
+ case IntID::SkipNavigatingDisabledMenuItem:
+ aResult = 1;
+ break;
+ case IntID::DragThresholdX:
+ case IntID::DragThresholdY: {
+ gint threshold = 0;
+ g_object_get(gtk_settings_get_default(), "gtk-dnd-drag-threshold",
+ &threshold, nullptr);
+
+ aResult = threshold;
+ } break;
+ case IntID::ScrollArrowStyle: {
+ GtkWidget* scrollbar = GetWidget(MOZ_GTK_SCROLLBAR_VERTICAL);
+ aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
+ break;
+ }
+ case IntID::TreeOpenDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeCloseDelay:
+ aResult = 1000;
+ break;
+ case IntID::TreeLazyScrollDelay:
+ aResult = 150;
+ break;
+ case IntID::TreeScrollDelay:
+ aResult = 100;
+ break;
+ case IntID::TreeScrollLinesMax:
+ aResult = 3;
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::Solid);
+ break;
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::None);
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = static_cast<int32_t>(StyleTextDecorationStyle::Wavy);
+ break;
+ case IntID::MenuBarDrag:
+ EnsureInit();
+ aResult = mSystemTheme.mMenuSupportsDrag;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 1;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 1;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ case IntID::GTKCSDAvailable:
+ aResult = sCSDAvailable;
+ break;
+ case IntID::GTKCSDMaximizeButton:
+ EnsureInit();
+ aResult = mCSDMaximizeButton;
+ break;
+ case IntID::GTKCSDMinimizeButton:
+ EnsureInit();
+ aResult = mCSDMinimizeButton;
+ break;
+ case IntID::GTKCSDCloseButton:
+ EnsureInit();
+ aResult = mCSDCloseButton;
+ break;
+ case IntID::GTKCSDReversedPlacement:
+ EnsureInit();
+ aResult = mCSDReversedPlacement;
+ break;
+ case IntID::PrefersReducedMotion: {
+ EnsureInit();
+ aResult = mPrefersReducedMotion;
+ break;
+ }
+ case IntID::SystemUsesDarkTheme: {
+ EnsureInit();
+ if (mColorSchemePreference) {
+ aResult = *mColorSchemePreference == ColorScheme::Dark;
+ } else {
+ aResult = mSystemTheme.mIsDark;
+ }
+ break;
+ }
+ case IntID::GTKCSDMaximizeButtonPosition:
+ aResult = mCSDMaximizeButtonPosition;
+ break;
+ case IntID::GTKCSDMinimizeButtonPosition:
+ aResult = mCSDMinimizeButtonPosition;
+ break;
+ case IntID::GTKCSDCloseButtonPosition:
+ aResult = mCSDCloseButtonPosition;
+ break;
+ case IntID::GTKThemeFamily: {
+ EnsureInit();
+ aResult = int32_t(EffectiveTheme().mFamily);
+ break;
+ }
+ case IntID::UseAccessibilityTheme:
+ // If high contrast is enabled, enable prefers-reduced-transparency media
+ // query as well as there is no dedicated option.
+ case IntID::PrefersReducedTransparency:
+ EnsureInit();
+ aResult = mSystemTheme.mHighContrast;
+ break;
+ case IntID::InvertedColors:
+ // No GTK API for checking if inverted colors is enabled
+ aResult = 0;
+ break;
+ case IntID::TitlebarRadius: {
+ EnsureInit();
+ aResult = EffectiveTheme().mTitlebarRadius;
+ break;
+ }
+ case IntID::AllowOverlayScrollbarsOverlap: {
+ aResult = 1;
+ break;
+ }
+ case IntID::ScrollbarFadeBeginDelay: {
+ aResult = 1000;
+ break;
+ }
+ case IntID::ScrollbarFadeDuration: {
+ aResult = 400;
+ break;
+ }
+ case IntID::ScrollbarDisplayOnMouseMove: {
+ aResult = 1;
+ break;
+ }
+ case IntID::PanelAnimations:
+ aResult = [&]() -> bool {
+ if (!sCSDAvailable) {
+ // Disabled on systems without CSD, see bug 1385079.
+ return false;
+ }
+ if (GdkIsWaylandDisplay()) {
+ // Disabled on wayland, see bug 1800442 and bug 1800368.
+ return false;
+ }
+ return true;
+ }();
+ break;
+ case IntID::UseOverlayScrollbars: {
+ aResult = StaticPrefs::widget_gtk_overlay_scrollbars_enabled();
+ break;
+ }
+ case IntID::HideCursorWhileTyping: {
+ aResult = StaticPrefs::widget_gtk_hide_pointer_while_typing_enabled();
+ break;
+ }
+ case IntID::TouchDeviceSupportPresent:
+ aResult = widget::WidgetUtilsGTK::IsTouchDeviceSupportPresent();
+ break;
+ default:
+ aResult = 0;
+ res = NS_ERROR_FAILURE;
+ }
+
+ return res;
+}
+
+nsresult nsLookAndFeel::NativeGetFloat(FloatID aID, float& aResult) {
+ nsresult rv = NS_OK;
+ switch (aID) {
+ case FloatID::IMEUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::SpellCheckerUnderlineRelativeSize:
+ aResult = 1.0f;
+ break;
+ case FloatID::CaretAspectRatio:
+ EnsureInit();
+ aResult = mSystemTheme.mCaretRatio;
+ break;
+ case FloatID::TextScaleFactor:
+ aResult = gfxPlatformGtk::GetFontScaleFactor();
+ break;
+ default:
+ aResult = -1.0;
+ rv = NS_ERROR_FAILURE;
+ }
+ return rv;
+}
+
+static void GetSystemFontInfo(GtkStyleContext* aStyle, nsString* aFontName,
+ gfxFontStyle* aFontStyle) {
+ aFontStyle->style = FontSlantStyle::NORMAL;
+
+ // As in
+ // https://git.gnome.org/browse/gtk+/tree/gtk/gtkwidget.c?h=3.22.19#n10333
+ PangoFontDescription* desc;
+ gtk_style_context_get(aStyle, gtk_style_context_get_state(aStyle), "font",
+ &desc, nullptr);
+
+ aFontStyle->systemFont = true;
+
+ constexpr auto quote = u"\""_ns;
+ NS_ConvertUTF8toUTF16 family(pango_font_description_get_family(desc));
+ *aFontName = quote + family + quote;
+
+ aFontStyle->weight =
+ FontWeight::FromInt(pango_font_description_get_weight(desc));
+
+ // FIXME: Set aFontStyle->stretch correctly!
+ aFontStyle->stretch = FontStretch::NORMAL;
+
+ float size = float(pango_font_description_get_size(desc)) / PANGO_SCALE;
+
+ // |size| is now either pixels or pango-points (not Mozilla-points!)
+
+ if (!pango_font_description_get_size_is_absolute(desc)) {
+ // |size| is in pango-points, so convert to pixels.
+ size *= float(gfxPlatformGtk::GetFontScaleDPI()) / POINTS_PER_INCH_FLOAT;
+ }
+
+ // |size| is now pixels but not scaled for the hidpi displays,
+ aFontStyle->size = size;
+
+ pango_font_description_free(desc);
+}
+
+bool nsLookAndFeel::NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) {
+ return mSystemTheme.GetFont(aID, aFontName, aFontStyle);
+}
+
+bool nsLookAndFeel::PerThemeData::GetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) const {
+ switch (aID) {
+ case FontID::Menu: // css2
+ case FontID::MozPullDownMenu: // css3
+ aFontName = mMenuFontName;
+ aFontStyle = mMenuFontStyle;
+ break;
+
+ case FontID::MozField: // css3
+ case FontID::MozList: // css3
+ aFontName = mFieldFontName;
+ aFontStyle = mFieldFontStyle;
+ break;
+
+ case FontID::MozButton: // css3
+ aFontName = mButtonFontName;
+ aFontStyle = mButtonFontStyle;
+ break;
+
+ case FontID::Caption: // css2
+ case FontID::Icon: // css2
+ case FontID::MessageBox: // css2
+ case FontID::SmallCaption: // css2
+ case FontID::StatusBar: // css2
+ default:
+ aFontName = mDefaultFontName;
+ aFontStyle = mDefaultFontStyle;
+ break;
+ }
+
+ // Convert GDK pixels to CSS pixels.
+ // When "layout.css.devPixelsPerPx" > 0, this is not a direct conversion.
+ // The difference produces a scaling of system fonts in proportion with
+ // other scaling from the change in CSS pixel sizes.
+ aFontStyle.size /= LookAndFeel::GetTextScaleFactor();
+ return true;
+}
+
+static nsCString GetGtkSettingsStringKey(const char* aKey) {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ nsCString ret;
+ GtkSettings* settings = gtk_settings_get_default();
+ char* value = nullptr;
+ g_object_get(settings, aKey, &value, nullptr);
+ if (value) {
+ ret.Assign(value);
+ g_free(value);
+ }
+ return ret;
+}
+
+static nsCString GetGtkTheme() {
+ return GetGtkSettingsStringKey("gtk-theme-name");
+}
+
+static bool GetPreferDarkTheme() {
+ GtkSettings* settings = gtk_settings_get_default();
+ gboolean preferDarkTheme = FALSE;
+ g_object_get(settings, "gtk-application-prefer-dark-theme", &preferDarkTheme,
+ nullptr);
+ return preferDarkTheme == TRUE;
+}
+
+// It seems GTK doesn't have an API to query if the current theme is "light" or
+// "dark", so we synthesize it from the CSS2 Window/WindowText colors instead,
+// by comparing their luminosity.
+static bool GetThemeIsDark() {
+ GdkRGBA bg, fg;
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &bg);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &fg);
+ return RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
+ RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg));
+}
+
+void nsLookAndFeel::RestoreSystemTheme() {
+ LOGLNF("RestoreSystemTheme(%s, %d, %d)\n", mSystemTheme.mName.get(),
+ mSystemTheme.mPreferDarkTheme, mSystemThemeOverridden);
+
+ if (!mSystemThemeOverridden) {
+ return;
+ }
+
+ // Available on Gtk 3.20+.
+ static auto sGtkSettingsResetProperty =
+ (void (*)(GtkSettings*, const gchar*))dlsym(
+ RTLD_DEFAULT, "gtk_settings_reset_property");
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (sGtkSettingsResetProperty) {
+ sGtkSettingsResetProperty(settings, "gtk-theme-name");
+ sGtkSettingsResetProperty(settings, "gtk-application-prefer-dark-theme");
+ } else {
+ g_object_set(settings, "gtk-theme-name", mSystemTheme.mName.get(),
+ "gtk-application-prefer-dark-theme",
+ mSystemTheme.mPreferDarkTheme, nullptr);
+ }
+ mSystemThemeOverridden = false;
+ UpdateRoundedBottomCornerStyles();
+ moz_gtk_refresh();
+}
+
+static bool AnyColorChannelIsDifferent(nscolor aColor) {
+ return NS_GET_R(aColor) != NS_GET_G(aColor) ||
+ NS_GET_R(aColor) != NS_GET_B(aColor);
+}
+
+bool nsLookAndFeel::ConfigureAltTheme() {
+ GtkSettings* settings = gtk_settings_get_default();
+ // Toggling gtk-application-prefer-dark-theme is not enough generally to
+ // switch from dark to light theme. If the theme didn't change, and we have
+ // a dark theme, try to first remove -Dark{,er,est} from the theme name to
+ // find the light variant.
+ if (mSystemTheme.mIsDark) {
+ nsCString potentialLightThemeName = mSystemTheme.mName;
+ // clang-format off
+ constexpr nsLiteralCString kSubstringsToRemove[] = {
+ "-darkest"_ns, "-darker"_ns, "-dark"_ns,
+ "-Darkest"_ns, "-Darker"_ns, "-Dark"_ns,
+ "_darkest"_ns, "_darker"_ns, "_dark"_ns,
+ "_Darkest"_ns, "_Darker"_ns, "_Dark"_ns,
+ };
+ // clang-format on
+ bool found = false;
+ for (const auto& s : kSubstringsToRemove) {
+ potentialLightThemeName = mSystemTheme.mName;
+ potentialLightThemeName.ReplaceSubstring(s, ""_ns);
+ if (potentialLightThemeName.Length() != mSystemTheme.mName.Length()) {
+ found = true;
+ break;
+ }
+ }
+ if (found) {
+ LOGLNF(" found potential light variant of %s: %s",
+ mSystemTheme.mName.get(), potentialLightThemeName.get());
+ g_object_set(settings, "gtk-theme-name", potentialLightThemeName.get(),
+ "gtk-application-prefer-dark-theme", !mSystemTheme.mIsDark,
+ nullptr);
+ moz_gtk_refresh();
+
+ if (!GetThemeIsDark()) {
+ return true; // Success!
+ }
+ }
+ }
+
+ LOGLNF(" toggling gtk-application-prefer-dark-theme");
+ g_object_set(settings, "gtk-application-prefer-dark-theme",
+ !mSystemTheme.mIsDark, nullptr);
+ moz_gtk_refresh();
+ if (mSystemTheme.mIsDark != GetThemeIsDark()) {
+ return true; // Success!
+ }
+
+ LOGLNF(" didn't work, falling back to default theme");
+ // If the theme still didn't change enough, fall back to Adwaita with the
+ // appropriate color preference.
+ g_object_set(settings, "gtk-theme-name", "Adwaita",
+ "gtk-application-prefer-dark-theme", !mSystemTheme.mIsDark,
+ nullptr);
+ moz_gtk_refresh();
+
+ // If it _still_ didn't change enough, and we're looking for a dark theme,
+ // try to set Adwaita-dark as a theme name. This might be needed in older GTK
+ // versions.
+ if (!mSystemTheme.mIsDark && !GetThemeIsDark()) {
+ LOGLNF(" last resort Adwaita-dark fallback");
+ g_object_set(settings, "gtk-theme-name", "Adwaita-dark", nullptr);
+ moz_gtk_refresh();
+ }
+
+ return false;
+}
+
+// We override some adwaita colors from GTK3 to LibAdwaita, see:
+// https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html
+void nsLookAndFeel::MaybeApplyAdwaitaOverrides() {
+ auto& dark = mSystemTheme.mIsDark ? mSystemTheme : mAltTheme;
+ auto& light = mSystemTheme.mIsDark ? mAltTheme : mSystemTheme;
+
+ // Unconditional special case for Adwaita-dark: In GTK3 we don't have more
+ // proper accent colors, so we use the selected background colors. Those
+ // colors, however, don't have much contrast in dark mode (see bug 1741293).
+ if (dark.mFamily == ThemeFamily::Adwaita) {
+ dark.mAccent = {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
+ dark.mSelectedText = dark.mAccent;
+ }
+
+ if (light.mFamily == ThemeFamily::Adwaita) {
+ light.mAccent = {NS_RGB(0x35, 0x84, 0xe4), NS_RGB(0xff, 0xff, 0xff)};
+ light.mSelectedText = light.mAccent;
+ }
+
+ if (!StaticPrefs::widget_gtk_libadwaita_colors_enabled()) {
+ return;
+ }
+
+ if (light.mFamily == ThemeFamily::Adwaita) {
+ // #323232 is rgba(0,0,0,.8) over #fafafa.
+ light.mWindow =
+ light.mDialog = {NS_RGB(0xfa, 0xfa, 0xfa), NS_RGB(0x32, 0x32, 0x32)};
+ light.mField = {NS_RGB(0xff, 0xff, 0xff), NS_RGB(0x32, 0x32, 0x32)};
+
+ // We use the sidebar colors for the headerbar in light mode background
+ // because it creates much better contrast. GTK headerbar colors are white,
+ // and meant to "blend" with the contents otherwise.
+ // #2f2f2f is rgba(0,0,0,.8) over #ebebeb.
+ light.mSidebar = light.mHeaderBar =
+ light.mTitlebar = {NS_RGB(0xeb, 0xeb, 0xeb), NS_RGB(0x2f, 0x2f, 0x2f)};
+ light.mHeaderBarInactive = light.mTitlebarInactive = {
+ NS_RGB(0xf2, 0xf2, 0xf2), NS_RGB(0x2f, 0x2f, 0x2f)};
+ light.mThreeDShadow = NS_RGB(0xe0, 0xe0, 0xe0);
+ light.mSidebarBorder = NS_RGBA(0, 0, 0, 18);
+ }
+
+ if (dark.mFamily == ThemeFamily::Adwaita) {
+ dark.mWindow = {NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
+ dark.mDialog = {NS_RGB(0x38, 0x38, 0x38), NS_RGB(0xff, 0xff, 0xff)};
+ dark.mField = {NS_RGB(0x3a, 0x3a, 0x3a), NS_RGB(0xff, 0xff, 0xff)};
+ dark.mSidebar = dark.mHeaderBar =
+ dark.mTitlebar = {NS_RGB(0x30, 0x30, 0x30), NS_RGB(0xff, 0xff, 0xff)};
+ dark.mHeaderBarInactive = dark.mTitlebarInactive = {
+ NS_RGB(0x24, 0x24, 0x24), NS_RGB(0xff, 0xff, 0xff)};
+ // headerbar_shade_color
+ dark.mThreeDShadow = NS_RGB(0x1f, 0x1f, 0x1f);
+ dark.mSidebarBorder = NS_RGBA(0, 0, 0, 92);
+ }
+}
+
+void nsLookAndFeel::ConfigureAndInitializeAltTheme() {
+ const bool fellBackToDefaultTheme = !ConfigureAltTheme();
+
+ mAltTheme.Init();
+
+ MaybeApplyAdwaitaOverrides();
+
+ // Some of the alt theme colors we can grab from the system theme, if we fell
+ // back to the default light / dark themes.
+ if (fellBackToDefaultTheme) {
+ if (StaticPrefs::widget_gtk_alt_theme_selection()) {
+ mAltTheme.mSelectedText = mSystemTheme.mSelectedText;
+ }
+
+ if (StaticPrefs::widget_gtk_alt_theme_scrollbar_active() &&
+ (!mAltTheme.mIsDark || ShouldUseColorForActiveDarkScrollbarThumb(
+ mSystemTheme.mThemedScrollbarThumbActive))) {
+ mAltTheme.mThemedScrollbarThumbActive =
+ mSystemTheme.mThemedScrollbarThumbActive;
+ }
+
+ if (StaticPrefs::widget_gtk_alt_theme_accent()) {
+ mAltTheme.mAccent = mSystemTheme.mAccent;
+ }
+ }
+
+ // Right now we're using the opposite color-scheme theme, make sure to record
+ // it.
+ mSystemThemeOverridden = true;
+ UpdateRoundedBottomCornerStyles();
+}
+
+void nsLookAndFeel::ClearRoundedCornerProvider() {
+ if (mRoundedCornerProvider) {
+ gtk_style_context_remove_provider_for_screen(
+ gdk_screen_get_default(),
+ GTK_STYLE_PROVIDER(mRoundedCornerProvider.get()));
+ mRoundedCornerProvider = nullptr;
+ }
+}
+
+void nsLookAndFeel::UpdateRoundedBottomCornerStyles() {
+ ClearRoundedCornerProvider();
+ if (!StaticPrefs::widget_gtk_rounded_bottom_corners_enabled()) {
+ return;
+ }
+ int32_t radius = EffectiveTheme().mTitlebarRadius;
+ if (!radius) {
+ return;
+ }
+ mRoundedCornerProvider = dont_AddRef(gtk_css_provider_new());
+ nsPrintfCString string(
+ "window.csd decoration {"
+ "border-bottom-right-radius: %dpx;"
+ "border-bottom-left-radius: %dpx;"
+ "}\n",
+ radius, radius);
+ GUniquePtr<GError> error;
+ if (!gtk_css_provider_load_from_data(mRoundedCornerProvider.get(),
+ string.get(), string.Length(),
+ getter_Transfers(error))) {
+ NS_WARNING(nsPrintfCString("Failed to load provider: %s - %s\n",
+ string.get(), error ? error->message : nullptr)
+ .get());
+ }
+ gtk_style_context_add_provider_for_screen(
+ gdk_screen_get_default(),
+ GTK_STYLE_PROVIDER(mRoundedCornerProvider.get()),
+ GTK_STYLE_PROVIDER_PRIORITY_APPLICATION);
+}
+
+Maybe<ColorScheme> nsLookAndFeel::ComputeColorSchemeSetting() {
+ {
+ // Check the pref explicitly here. Usually this shouldn't be needed, but
+ // since we can only load one GTK theme at a time, and the pref will
+ // override the effective value that the rest of gecko assumes for the
+ // "system" color scheme, we need to factor it in our GTK theme decisions.
+ int32_t pref = 0;
+ if (NS_SUCCEEDED(Preferences::GetInt("ui.systemUsesDarkTheme", &pref))) {
+ return Some(pref ? ColorScheme::Dark : ColorScheme::Light);
+ }
+ }
+
+ if (!mDBusSettingsProxy) {
+ return Nothing();
+ }
+ GUniquePtr<GError> error;
+ RefPtr<GVariant> variant = dont_AddRef(g_dbus_proxy_call_sync(
+ mDBusSettingsProxy, "Read",
+ g_variant_new("(ss)", "org.freedesktop.appearance", "color-scheme"),
+ G_DBUS_CALL_FLAGS_NONE,
+ StaticPrefs::widget_gtk_settings_portal_timeout_ms(), nullptr,
+ getter_Transfers(error)));
+ if (!variant) {
+ LOGLNF("color-scheme query error: %s\n", error->message);
+ return Nothing();
+ }
+ LOGLNF("color-scheme query result: %s\n", GVariantToString(variant).get());
+ variant = dont_AddRef(g_variant_get_child_value(variant, 0));
+ while (variant && g_variant_is_of_type(variant, G_VARIANT_TYPE_VARIANT)) {
+ // Unbox the return value.
+ variant = dont_AddRef(g_variant_get_variant(variant));
+ }
+ if (!variant || !g_variant_is_of_type(variant, G_VARIANT_TYPE_UINT32)) {
+ MOZ_ASSERT(false, "Unexpected color-scheme query return value");
+ return Nothing();
+ }
+ switch (g_variant_get_uint32(variant)) {
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unexpected color-scheme query return value");
+ case 0:
+ break;
+ case 1:
+ return Some(ColorScheme::Dark);
+ case 2:
+ return Some(ColorScheme::Light);
+ }
+ return Nothing();
+}
+
+void nsLookAndFeel::Initialize() {
+ LOGLNF("nsLookAndFeel::Initialize");
+ MOZ_DIAGNOSTIC_ASSERT(!mInitialized);
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread(),
+ "LookAndFeel init should be done on the main thread");
+
+ mInitialized = true;
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (MOZ_UNLIKELY(!settings)) {
+ NS_WARNING("EnsureInit: No settings");
+ return;
+ }
+
+ AutoRestore<bool> restoreIgnoreSettings(sIgnoreChangedSettings);
+ sIgnoreChangedSettings = true;
+
+ // Our current theme may be different from the system theme if we're matching
+ // the Firefox theme or using the alt theme intentionally due to the
+ // color-scheme preference. Make sure to restore the original system theme.
+ RestoreSystemTheme();
+
+ // First initialize global settings.
+ InitializeGlobalSettings();
+
+ // Record our system theme settings now.
+ mSystemTheme.Init();
+
+ // Find the alternative-scheme theme (light if the system theme is dark, or
+ // vice versa), configure it and initialize it.
+ ConfigureAndInitializeAltTheme();
+
+ LOGLNF("System Theme: %s. Alt Theme: %s\n", mSystemTheme.mName.get(),
+ mAltTheme.mName.get());
+
+ // Go back to the system theme or keep the alt theme configured, depending on
+ // Firefox theme or user color-scheme preference.
+ ConfigureFinalEffectiveTheme();
+
+ RecordTelemetry();
+}
+
+void nsLookAndFeel::OnColorSchemeSettingChanged() {
+ if (NS_WARN_IF(mColorSchemePreference == ComputeColorSchemeSetting())) {
+ // We sometimes get duplicate color-scheme changes from dbus, avoid doing
+ // extra work if not needed.
+ return;
+ }
+ OnSettingsChange();
+}
+
+void nsLookAndFeel::InitializeGlobalSettings() {
+ GtkSettings* settings = gtk_settings_get_default();
+
+ mColorSchemePreference = ComputeColorSchemeSetting();
+
+ gboolean enableAnimations = false;
+ g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
+ mPrefersReducedMotion = !enableAnimations;
+
+ gint blink_time = 0; // In milliseconds
+ gint blink_timeout = 0; // in seconds
+ gboolean blink;
+ g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
+ "gtk-cursor-blink-timeout", &blink_timeout, "gtk-cursor-blink",
+ &blink, nullptr);
+ // From
+ // https://docs.gtk.org/gtk3/property.Settings.gtk-cursor-blink-timeout.html:
+ //
+ // Setting this to zero has the same effect as setting
+ // GtkSettings:gtk-cursor-blink to FALSE.
+ //
+ mCaretBlinkTime = blink && blink_timeout ? (int32_t)blink_time : 0;
+
+ if (mCaretBlinkTime) {
+ // blink_time * 2 because blink count is a full blink cycle.
+ mCaretBlinkCount =
+ std::max(1, int32_t(std::ceil(float(blink_timeout * 1000) /
+ (float(blink_time) * 2.0f))));
+ } else {
+ mCaretBlinkCount = -1;
+ }
+
+ mCSDCloseButton = false;
+ mCSDMinimizeButton = false;
+ mCSDMaximizeButton = false;
+ mCSDCloseButtonPosition = 0;
+ mCSDMinimizeButtonPosition = 0;
+ mCSDMaximizeButtonPosition = 0;
+
+ // We need to initialize whole CSD config explicitly because it's queried
+ // as -moz-gtk* media features.
+ ButtonLayout buttonLayout[TOOLBAR_BUTTONS];
+
+ size_t activeButtons =
+ GetGtkHeaderBarButtonLayout(Span(buttonLayout), &mCSDReversedPlacement);
+ for (size_t i = 0; i < activeButtons; i++) {
+ // We check if a button is represented on the right side of the tabbar.
+ // Then we assign it a value from 3 to 5, instead of 0 to 2 when it is on
+ // the left side.
+ const ButtonLayout& layout = buttonLayout[i];
+ int32_t* pos = nullptr;
+ switch (layout.mType) {
+ case MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE:
+ mCSDMinimizeButton = true;
+ pos = &mCSDMinimizeButtonPosition;
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE:
+ mCSDMaximizeButton = true;
+ pos = &mCSDMaximizeButtonPosition;
+ break;
+ case MOZ_GTK_HEADER_BAR_BUTTON_CLOSE:
+ mCSDCloseButton = true;
+ pos = &mCSDCloseButtonPosition;
+ break;
+ default:
+ break;
+ }
+
+ if (pos) {
+ *pos = i;
+ }
+ }
+
+ struct actionMapping {
+ TitlebarAction action;
+ char name[100];
+ } ActionMapping[] = {
+ {TitlebarAction::None, "none"},
+ {TitlebarAction::WindowLower, "lower"},
+ {TitlebarAction::WindowMenu, "menu"},
+ {TitlebarAction::WindowMinimize, "minimize"},
+ {TitlebarAction::WindowMaximize, "maximize"},
+ {TitlebarAction::WindowMaximizeToggle, "toggle-maximize"},
+ };
+
+ auto GetWindowAction = [&](const char* eventName) -> TitlebarAction {
+ gchar* action = nullptr;
+ g_object_get(settings, eventName, &action, nullptr);
+ if (!action) {
+ return TitlebarAction::None;
+ }
+ auto free = mozilla::MakeScopeExit([&] { g_free(action); });
+ for (auto const& mapping : ActionMapping) {
+ if (!strncmp(action, mapping.name, strlen(mapping.name))) {
+ return mapping.action;
+ }
+ }
+ return TitlebarAction::None;
+ };
+
+ mDoubleClickAction = GetWindowAction("gtk-titlebar-double-click");
+ mMiddleClickAction = GetWindowAction("gtk-titlebar-middle-click");
+}
+
+void nsLookAndFeel::ConfigureFinalEffectiveTheme() {
+ MOZ_ASSERT(mSystemThemeOverridden,
+ "By this point, the alt theme should be configured");
+ const bool shouldUseSystemTheme = [&] {
+ using ChromeSetting = PreferenceSheet::ChromeColorSchemeSetting;
+ // NOTE: We can't call ColorSchemeForChrome directly because this might run
+ // while we're computing it.
+ switch (PreferenceSheet::ColorSchemeSettingForChrome()) {
+ case ChromeSetting::Light:
+ return !mSystemTheme.mIsDark;
+ case ChromeSetting::Dark:
+ return mSystemTheme.mIsDark;
+ case ChromeSetting::System:
+ break;
+ };
+ if (!mColorSchemePreference) {
+ return true;
+ }
+ const bool preferenceIsDark = *mColorSchemePreference == ColorScheme::Dark;
+ return preferenceIsDark == mSystemTheme.mIsDark;
+ }();
+
+ const bool usingSystem = !mSystemThemeOverridden;
+ LOGLNF("OverrideSystemThemeIfNeeded(matchesSystem=%d, usingSystem=%d)\n",
+ shouldUseSystemTheme, usingSystem);
+
+ if (shouldUseSystemTheme) {
+ RestoreSystemTheme();
+ } else if (usingSystem) {
+ LOGLNF("Setting theme %s, %d\n", mAltTheme.mName.get(),
+ mAltTheme.mPreferDarkTheme);
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (mSystemTheme.mName == mAltTheme.mName) {
+ // Prefer setting only gtk-application-prefer-dark-theme, so we can still
+ // get notified from notify::gtk-theme-name if the user changes the theme.
+ g_object_set(settings, "gtk-application-prefer-dark-theme",
+ mAltTheme.mPreferDarkTheme, nullptr);
+ } else {
+ g_object_set(settings, "gtk-theme-name", mAltTheme.mName.get(),
+ "gtk-application-prefer-dark-theme",
+ mAltTheme.mPreferDarkTheme, nullptr);
+ }
+ mSystemThemeOverridden = true;
+ UpdateRoundedBottomCornerStyles();
+ moz_gtk_refresh();
+ }
+}
+
+static bool GetColorFromBackgroundImage(GtkStyleContext* aStyle,
+ nscolor aForForegroundColor,
+ GtkStateFlags aState, nscolor* aColor) {
+ GValue value = G_VALUE_INIT;
+ gtk_style_context_get_property(aStyle, "background-image", aState, &value);
+ auto cleanup = MakeScopeExit([&] { g_value_unset(&value); });
+ if (GetColorFromImagePattern(&value, aColor)) {
+ return true;
+ }
+
+ {
+ GdkRGBA light, dark;
+ if (GetGradientColors(&value, &light, &dark)) {
+ nscolor l = GDK_RGBA_TO_NS_RGBA(light);
+ nscolor d = GDK_RGBA_TO_NS_RGBA(dark);
+ // Return the one with more contrast.
+ // TODO(emilio): This could do interpolation or what not but seems
+ // overkill.
+ if (NS_LUMINOSITY_DIFFERENCE(l, aForForegroundColor) >
+ NS_LUMINOSITY_DIFFERENCE(d, aForForegroundColor)) {
+ *aColor = l;
+ } else {
+ *aColor = d;
+ }
+ return true;
+ }
+ }
+
+ return false;
+}
+
+static nscolor GetBackgroundColor(
+ GtkStyleContext* aStyle, nscolor aForForegroundColor,
+ GtkStateFlags aState = GTK_STATE_FLAG_NORMAL,
+ nscolor aOverBackgroundColor = NS_TRANSPARENT) {
+ // Try to synthesize a color from a background-image.
+ nscolor imageColor = NS_TRANSPARENT;
+ if (GetColorFromBackgroundImage(aStyle, aForForegroundColor, aState,
+ &imageColor)) {
+ if (NS_GET_A(imageColor) == 255) {
+ return imageColor;
+ }
+ }
+
+ GdkRGBA gdkColor;
+ gtk_style_context_get_background_color(aStyle, aState, &gdkColor);
+ nscolor bgColor = GDK_RGBA_TO_NS_RGBA(gdkColor);
+ // background-image paints over background-color.
+ const nscolor finalColor = NS_ComposeColors(bgColor, imageColor);
+ if (finalColor != aOverBackgroundColor) {
+ return finalColor;
+ }
+ return NS_TRANSPARENT;
+}
+
+static nscolor GetTextColor(GtkStyleContext* aStyle,
+ GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
+ GdkRGBA color;
+ gtk_style_context_get_color(aStyle, aState, &color);
+ return GDK_RGBA_TO_NS_RGBA(color);
+}
+
+using ColorPair = nsLookAndFeel::ColorPair;
+static ColorPair GetColorPair(GtkStyleContext* aStyle,
+ GtkStateFlags aState = GTK_STATE_FLAG_NORMAL) {
+ ColorPair result;
+ result.mFg = GetTextColor(aStyle, aState);
+ result.mBg = GetBackgroundColor(aStyle, result.mFg, aState);
+ return result;
+}
+
+static bool GetNamedColorPair(GtkStyleContext* aStyle, const char* aBgName,
+ const char* aFgName, ColorPair* aPair) {
+ GdkRGBA bg, fg;
+ if (!gtk_style_context_lookup_color(aStyle, aBgName, &bg) ||
+ !gtk_style_context_lookup_color(aStyle, aFgName, &fg)) {
+ return false;
+ }
+
+ aPair->mBg = GDK_RGBA_TO_NS_RGBA(bg);
+ aPair->mFg = GDK_RGBA_TO_NS_RGBA(fg);
+
+ // If the colors are semi-transparent and the theme provides a
+ // background color, blend with them to get the "final" color, see
+ // bug 1717077.
+ if (NS_GET_A(aPair->mBg) != 255 &&
+ (gtk_style_context_lookup_color(aStyle, "bg_color", &bg) ||
+ gtk_style_context_lookup_color(aStyle, "theme_bg_color", &bg))) {
+ aPair->mBg = NS_ComposeColors(GDK_RGBA_TO_NS_RGBA(bg), aPair->mBg);
+ }
+
+ // A semi-transparent foreground color would be kinda silly, but is done
+ // for symmetry.
+ if (NS_GET_A(aPair->mFg) != 255) {
+ aPair->mFg = NS_ComposeColors(aPair->mBg, aPair->mFg);
+ }
+
+ return true;
+}
+
+static void EnsureColorPairIsOpaque(ColorPair& aPair) {
+ // Blend with white, ensuring the color is opaque, so that the UI doesn't have
+ // to care about alpha.
+ aPair.mBg = NS_ComposeColors(NS_RGB(0xff, 0xff, 0xff), aPair.mBg);
+ aPair.mFg = NS_ComposeColors(aPair.mBg, aPair.mFg);
+}
+
+static void PreferDarkerBackground(ColorPair& aPair) {
+ // We use the darker one unless the foreground isn't really a color (is all
+ // white / black / gray) and the background is, in which case we stick to what
+ // we have.
+ if (RelativeLuminanceUtils::Compute(aPair.mBg) >
+ RelativeLuminanceUtils::Compute(aPair.mFg) &&
+ (AnyColorChannelIsDifferent(aPair.mFg) ||
+ !AnyColorChannelIsDifferent(aPair.mBg))) {
+ std::swap(aPair.mBg, aPair.mFg);
+ }
+}
+
+void nsLookAndFeel::PerThemeData::Init() {
+ mName = GetGtkTheme();
+
+ mFamily = [&] {
+ if (mName.EqualsLiteral("Adwaita") || mName.EqualsLiteral("Adwaita-dark")) {
+ return ThemeFamily::Adwaita;
+ }
+ if (mName.EqualsLiteral("Breeze") || mName.EqualsLiteral("Breeze-Dark")) {
+ return ThemeFamily::Breeze;
+ }
+ if (StringBeginsWith(mName, "Yaru"_ns)) {
+ return ThemeFamily::Yaru;
+ }
+ return ThemeFamily::Unknown;
+ }();
+
+ GtkStyleContext* style;
+
+ mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
+ mName.Find("HighContrast"_ns) >= 0;
+
+ mPreferDarkTheme = GetPreferDarkTheme();
+
+ mIsDark = GetThemeIsDark();
+
+ GdkRGBA color;
+ // Some themes style the <trough>, while others style the <scrollbar>
+ // itself, so we look at both and compose the colors.
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mThemedScrollbar = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarInactive = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mThemedScrollbar =
+ NS_ComposeColors(mThemedScrollbar, GDK_RGBA_TO_NS_RGBA(color));
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarInactive =
+ NS_ComposeColors(mThemedScrollbarInactive, GDK_RGBA_TO_NS_RGBA(color));
+
+ style = GetStyleContext(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mThemedScrollbarThumb = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mThemedScrollbarThumbHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(
+ style, GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
+ &color);
+ mThemedScrollbarThumbActive = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_BACKDROP,
+ &color);
+ mThemedScrollbarThumbInactive = GDK_RGBA_TO_NS_RGBA(color);
+
+ // Make sure that the thumb is visible, at least.
+ const bool fallbackToUnthemedColors = [&] {
+ if (!StaticPrefs::widget_gtk_theme_scrollbar_colors_enabled()) {
+ return true;
+ }
+
+ if (!ShouldHonorThemeScrollbarColors()) {
+ return true;
+ }
+ // If any of the scrollbar thumb colors are fully transparent, fall back to
+ // non-native ones.
+ if (!NS_GET_A(mThemedScrollbarThumb) ||
+ !NS_GET_A(mThemedScrollbarThumbHover) ||
+ !NS_GET_A(mThemedScrollbarThumbActive)) {
+ return true;
+ }
+ // If the thumb and track are the same color and opaque, fall back to
+ // non-native colors as well.
+ if (mThemedScrollbar == mThemedScrollbarThumb &&
+ NS_GET_A(mThemedScrollbar) == 0xff) {
+ return true;
+ }
+ return false;
+ }();
+
+ if (fallbackToUnthemedColors) {
+ if (mIsDark) {
+ // Taken from Adwaita-dark.
+ mThemedScrollbar = NS_RGB(0x31, 0x31, 0x31);
+ mThemedScrollbarInactive = NS_RGB(0x2d, 0x2d, 0x2d);
+ mThemedScrollbarThumb = NS_RGB(0xa3, 0xa4, 0xa4);
+ mThemedScrollbarThumbInactive = NS_RGB(0x59, 0x5a, 0x5a);
+ } else {
+ // Taken from Adwaita.
+ mThemedScrollbar = NS_RGB(0xce, 0xce, 0xce);
+ mThemedScrollbarInactive = NS_RGB(0xec, 0xed, 0xef);
+ mThemedScrollbarThumb = NS_RGB(0x82, 0x81, 0x7e);
+ mThemedScrollbarThumbInactive = NS_RGB(0xce, 0xcf, 0xce);
+ }
+
+ mThemedScrollbarThumbHover = ThemeColors::AdjustUnthemedScrollbarThumbColor(
+ mThemedScrollbarThumb, dom::ElementState::HOVER);
+ mThemedScrollbarThumbActive =
+ ThemeColors::AdjustUnthemedScrollbarThumbColor(
+ mThemedScrollbarThumb, dom::ElementState::ACTIVE);
+ }
+
+ // The label is not added to a parent widget, but shared for constructing
+ // different style contexts. The node hierarchy is constructed only on
+ // the label style context.
+ GtkWidget* labelWidget = gtk_label_new("M");
+ g_object_ref_sink(labelWidget);
+
+ // Window colors
+ style = GetStyleContext(MOZ_GTK_WINDOW);
+ mWindow = mDialog = GetColorPair(style);
+
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowActiveBorder = GDK_RGBA_TO_NS_RGBA(color);
+
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_INSENSITIVE, &color);
+ mMozWindowInactiveBorder = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_WINDOW_CONTAINER);
+ {
+ GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+ GetSystemFontInfo(labelStyle, &mDefaultFontName, &mDefaultFontStyle);
+ g_object_unref(labelStyle);
+ }
+
+ // tooltip foreground and background
+ style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
+ mInfo.mFg = GetTextColor(style);
+ style = GetStyleContext(MOZ_GTK_TOOLTIP);
+ mInfo.mBg = GetBackgroundColor(style, mInfo.mFg);
+
+ style = GetStyleContext(MOZ_GTK_MENUITEM);
+ {
+ GtkStyleContext* accelStyle =
+ CreateStyleForWidget(gtk_accel_label_new("M"), style);
+
+ GetSystemFontInfo(accelStyle, &mMenuFontName, &mMenuFontStyle);
+
+ gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_NORMAL, &color);
+ mMenu.mFg = GetTextColor(accelStyle);
+ mGrayText = GetTextColor(accelStyle, GTK_STATE_FLAG_INSENSITIVE);
+ g_object_unref(accelStyle);
+ }
+
+ const auto effectiveTitlebarStyle =
+ HeaderBarShouldDrawContainer(MOZ_GTK_HEADER_BAR) ? MOZ_GTK_HEADERBAR_FIXED
+ : MOZ_GTK_HEADER_BAR;
+ style = GetStyleContext(effectiveTitlebarStyle);
+ {
+ mTitlebar = GetColorPair(style, GTK_STATE_FLAG_NORMAL);
+ mTitlebarInactive = GetColorPair(style, GTK_STATE_FLAG_BACKDROP);
+ mTitlebarRadius = IsSolidCSDStyleUsed() ? 0 : GetBorderRadius(style);
+ }
+
+ // We special-case the header bar color in Adwaita, Yaru and Breeze to be the
+ // titlebar color, because it looks better and matches what apps do by
+ // default, see bug 1838460.
+ //
+ // We only do this in the relevant desktop environments, however, since in
+ // other cases we don't really know if the DE's titlebars are going to match.
+ //
+ // For breeze, additionally we read the KDE colors directly, if available,
+ // since these are user-configurable.
+ //
+ // For most other themes or those in unknown DEs, we use the menubar colors.
+ //
+ // FIXME(emilio): Can we do something a bit less special-case-y?
+ const bool shouldUseTitlebarColorsForHeaderBar = [&] {
+ if (mFamily == ThemeFamily::Adwaita || mFamily == ThemeFamily::Yaru) {
+ return IsGnomeDesktopEnvironment();
+ }
+ if (mFamily == ThemeFamily::Breeze) {
+ return IsKdeDesktopEnvironment();
+ }
+ return false;
+ }();
+
+ if (shouldUseTitlebarColorsForHeaderBar) {
+ mHeaderBar = mTitlebar;
+ mHeaderBarInactive = mTitlebarInactive;
+ if (mFamily == ThemeFamily::Breeze) {
+ GetNamedColorPair(style, "theme_header_background_breeze",
+ "theme_header_foreground_breeze", &mHeaderBar);
+ GetNamedColorPair(style, "theme_header_background_backdrop_breeze",
+ "theme_header_foreground_backdrop_breeze",
+ &mHeaderBarInactive);
+ }
+ } else {
+ style = GetStyleContext(MOZ_GTK_MENUBARITEM);
+ mHeaderBar.mFg = GetTextColor(style);
+ mHeaderBarInactive.mFg = GetTextColor(style, GTK_STATE_FLAG_BACKDROP);
+
+ style = GetStyleContext(MOZ_GTK_MENUBAR);
+ mHeaderBar.mBg = GetBackgroundColor(style, mHeaderBar.mFg);
+ mHeaderBarInactive.mBg = GetBackgroundColor(style, mHeaderBarInactive.mFg,
+ GTK_STATE_FLAG_BACKDROP);
+ }
+
+ style = GetStyleContext(MOZ_GTK_MENUPOPUP);
+ mMenu.mBg = [&] {
+ nscolor color = GetBackgroundColor(style, mMenu.mFg);
+ if (NS_GET_A(color)) {
+ return color;
+ }
+ // Some themes only style menupopups with the backdrop pseudo-class. Since a
+ // context / popup menu always seems to match that, try that before giving
+ // up.
+ color = GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_BACKDROP);
+ if (NS_GET_A(color)) {
+ return color;
+ }
+ // If we get here we couldn't figure out the right color to use. Rather than
+ // falling back to transparent, fall back to the window background.
+ NS_WARNING(
+ "Couldn't find menu background color, falling back to window "
+ "background");
+ return mWindow.mBg;
+ }();
+
+ style = GetStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMenuHover.mFg = GDK_RGBA_TO_NS_RGBA(color);
+ mMenuHover.mBg = NS_ComposeColors(
+ mMenu.mBg,
+ GetBackgroundColor(style, mMenu.mFg, GTK_STATE_FLAG_PRELIGHT, mMenu.mBg));
+
+ GtkWidget* parent = gtk_fixed_new();
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWidget* treeView = gtk_tree_view_new();
+ GtkWidget* linkButton = gtk_link_button_new("http://example.com/");
+ GtkWidget* menuBar = gtk_menu_bar_new();
+ GtkWidget* menuBarItem = gtk_menu_item_new();
+ GtkWidget* entry = gtk_entry_new();
+ GtkWidget* textView = gtk_text_view_new();
+
+ gtk_container_add(GTK_CONTAINER(parent), treeView);
+ gtk_container_add(GTK_CONTAINER(parent), linkButton);
+ gtk_container_add(GTK_CONTAINER(parent), menuBar);
+ gtk_menu_shell_append(GTK_MENU_SHELL(menuBar), menuBarItem);
+ gtk_container_add(GTK_CONTAINER(window), parent);
+ gtk_container_add(GTK_CONTAINER(parent), entry);
+ gtk_container_add(GTK_CONTAINER(parent), textView);
+
+ // Text colors
+ GdkRGBA bgColor;
+ // If the text window background is translucent, then the background of
+ // the textview root node is visible.
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &bgColor);
+
+ style = GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ ApplyColorOver(color, &bgColor);
+ mField.mBg = GDK_RGBA_TO_NS_RGBA(bgColor);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mField.mFg = GDK_RGBA_TO_NS_RGBA(color);
+ mSidebar = mField;
+
+ // Selected text and background
+ {
+ GtkStyleContext* selectionStyle =
+ GetStyleContext(MOZ_GTK_TEXT_VIEW_TEXT_SELECTION);
+ auto GrabSelectionColors = [&](GtkStyleContext* style) {
+ gtk_style_context_get_background_color(
+ style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
+ GTK_STATE_FLAG_SELECTED),
+ &color);
+ mSelectedText.mBg = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(
+ style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
+ GTK_STATE_FLAG_SELECTED),
+ &color);
+ mSelectedText.mFg = GDK_RGBA_TO_NS_RGBA(color);
+ };
+ GrabSelectionColors(selectionStyle);
+ if (mSelectedText.mBg == mSelectedText.mFg) {
+ // Some old distros/themes don't properly use the .selection style, so
+ // fall back to the regular text view style.
+ GrabSelectionColors(style);
+ }
+
+ // Default selected item color is the selection background / foreground
+ // colors, but we prefer named colors, as those are more general purpose
+ // than the actual selection style, which might e.g. be too-transparent.
+ //
+ // NOTE(emilio): It's unclear which one of the theme_selected_* or the
+ // selected_* pairs should we prefer, in all themes that define both that
+ // I've found, they're always the same.
+ if (!GetNamedColorPair(style, "selected_bg_color", "selected_fg_color",
+ &mSelectedItem) &&
+ !GetNamedColorPair(style, "theme_selected_bg_color",
+ "theme_selected_fg_color", &mSelectedItem)) {
+ mSelectedItem = mSelectedText;
+ }
+
+ EnsureColorPairIsOpaque(mSelectedItem);
+
+ // In a similar fashion, default accent color is the selected item/text
+ // pair, but we also prefer named colors, if available.
+ //
+ // accent_{bg,fg}_color is not _really_ a gtk3 thing (it's a gtk4 thing),
+ // but if gtk 3 themes want to specify these we let them, see:
+ //
+ // https://gnome.pages.gitlab.gnome.org/libadwaita/doc/main/named-colors.html#accent-colors
+ if (!GetNamedColorPair(style, "accent_bg_color", "accent_fg_color",
+ &mAccent)) {
+ mAccent = mSelectedItem;
+ }
+
+ EnsureColorPairIsOpaque(mAccent);
+ PreferDarkerBackground(mAccent);
+ }
+
+ // Button text color
+ style = GetStyleContext(MOZ_GTK_BUTTON);
+ {
+ GtkStyleContext* labelStyle = CreateStyleForWidget(labelWidget, style);
+ GetSystemFontInfo(labelStyle, &mButtonFontName, &mButtonFontStyle);
+ g_object_unref(labelStyle);
+ }
+
+ gtk_style_context_get_border_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mButtonBorder = GDK_RGBA_TO_NS_RGBA(color);
+ mButton = GetColorPair(style);
+ mButtonHover = GetColorPair(style, GTK_STATE_FLAG_PRELIGHT);
+ mButtonActive = GetColorPair(style, GTK_STATE_FLAG_ACTIVE);
+ if (!NS_GET_A(mButtonHover.mBg)) {
+ mButtonHover.mBg = mWindow.mBg;
+ }
+ if (!NS_GET_A(mButtonActive.mBg)) {
+ mButtonActive.mBg = mWindow.mBg;
+ }
+
+ // Combobox text color
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_TEXTAREA);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mComboBoxText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // GTK's guide to fancy odd row background colors:
+ // 1) Check if a theme explicitly defines an odd row color
+ // 2) If not, check if it defines an even row color, and darken it
+ // slightly by a hardcoded value (gtkstyle.c)
+ // 3) If neither are defined, take the base background color and
+ // darken that by a hardcoded value
+ style = GetStyleContext(MOZ_GTK_TREEVIEW);
+
+ // Get odd row background color
+ gtk_style_context_save(style);
+ gtk_style_context_add_region(style, GTK_STYLE_REGION_ROW, GTK_REGION_ODD);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mOddCellBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_restore(style);
+
+ // Column header colors
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_CELL);
+ mMozColHeader = GetColorPair(style, GTK_STATE_FLAG_NORMAL);
+ mMozColHeaderHover = GetColorPair(style, GTK_STATE_FLAG_NORMAL);
+ mMozColHeaderActive = GetColorPair(style, GTK_STATE_FLAG_ACTIVE);
+
+ // Compute cell highlight colors
+ InitCellHighlightColors();
+
+ // GtkFrame has a "border" subnode on which Adwaita draws the border.
+ // Some themes do not draw on this node but draw a border on the widget
+ // root node, so check the root node if no border is found on the border
+ // node.
+ style = GetStyleContext(MOZ_GTK_FRAME_BORDER);
+ bool themeUsesColors =
+ GetBorderColors(style, &mThreeDHighlight, &mThreeDShadow);
+ if (!themeUsesColors) {
+ style = GetStyleContext(MOZ_GTK_FRAME);
+ GetBorderColors(style, &mThreeDHighlight, &mThreeDShadow);
+ }
+ mSidebarBorder = mThreeDShadow;
+
+ // Some themes have a unified menu bar, and support window dragging on it
+ gboolean supports_menubar_drag = FALSE;
+ GParamSpec* param_spec = gtk_widget_class_find_style_property(
+ GTK_WIDGET_GET_CLASS(menuBar), "window-dragging");
+ if (param_spec) {
+ if (g_type_is_a(G_PARAM_SPEC_VALUE_TYPE(param_spec), G_TYPE_BOOLEAN)) {
+ gtk_widget_style_get(menuBar, "window-dragging", &supports_menubar_drag,
+ nullptr);
+ }
+ }
+ mMenuSupportsDrag = supports_menubar_drag;
+
+ // TODO: It returns wrong color for themes which
+ // sets link color for GtkLabel only as we query
+ // GtkLinkButton style here.
+ style = gtk_widget_get_style_context(linkButton);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_LINK, &color);
+ mNativeHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
+
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_VISITED, &color);
+ mNativeVisitedHyperLinkText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // invisible character styles
+ guint value;
+ g_object_get(entry, "invisible-char", &value, nullptr);
+ mInvisibleCharacter = char16_t(value);
+
+ // caret styles
+ gtk_widget_style_get(entry, "cursor-aspect-ratio", &mCaretRatio, nullptr);
+
+ GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
+ &mFieldFontStyle);
+
+ gtk_widget_destroy(window);
+ g_object_unref(labelWidget);
+
+ if (LOGLNF_ENABLED()) {
+ LOGLNF("Initialized theme %s (%d)\n", mName.get(), mPreferDarkTheme);
+ for (auto id : MakeEnumeratedRange(ColorID::End)) {
+ nscolor color;
+ nsresult rv = GetColor(id, color);
+ LOGLNF(" * color %d: pref=%s success=%d value=%x\n", int(id),
+ GetColorPrefName(id), NS_SUCCEEDED(rv),
+ NS_SUCCEEDED(rv) ? color : 0);
+ }
+ LOGLNF(" * titlebar-radius: %d\n", mTitlebarRadius);
+ }
+}
+
+// virtual
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+ EnsureInit();
+ return mSystemTheme.mInvisibleCharacter;
+}
+
+bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
+
+bool nsLookAndFeel::GetDefaultDrawInTitlebar() { return sCSDAvailable; }
+
+nsXPLookAndFeel::TitlebarAction nsLookAndFeel::GetTitlebarAction(
+ TitlebarEvent aEvent) {
+ return aEvent == TitlebarEvent::Double_Click ? mDoubleClickAction
+ : mMiddleClickAction;
+}
+
+void nsLookAndFeel::GetThemeInfo(nsACString& aInfo) {
+ aInfo.Append(mSystemTheme.mName);
+ aInfo.Append(" / ");
+ aInfo.Append(mAltTheme.mName);
+}
+
+bool nsLookAndFeel::WidgetUsesImage(WidgetNodeType aNodeType) {
+ static constexpr GtkStateFlags sFlagsToCheck[]{
+ GTK_STATE_FLAG_NORMAL, GTK_STATE_FLAG_PRELIGHT,
+ GtkStateFlags(GTK_STATE_FLAG_PRELIGHT | GTK_STATE_FLAG_ACTIVE),
+ GTK_STATE_FLAG_BACKDROP, GTK_STATE_FLAG_INSENSITIVE};
+
+ GtkStyleContext* style = GetStyleContext(aNodeType);
+
+ GValue value = G_VALUE_INIT;
+ for (GtkStateFlags state : sFlagsToCheck) {
+ gtk_style_context_get_property(style, "background-image", state, &value);
+ bool hasPattern = G_VALUE_TYPE(&value) == CAIRO_GOBJECT_TYPE_PATTERN &&
+ g_value_get_boxed(&value);
+ g_value_unset(&value);
+ if (hasPattern) {
+ return true;
+ }
+ }
+ return false;
+}
+
+void nsLookAndFeel::RecordLookAndFeelSpecificTelemetry() {
+ // Gtk version we're on.
+ nsString version;
+ version.AppendPrintf("%d.%d", gtk_major_version, gtk_minor_version);
+ Telemetry::ScalarSet(Telemetry::ScalarID::WIDGET_GTK_VERSION, version);
+
+ // Whether the current Gtk theme has scrollbar buttons.
+ bool hasScrollbarButtons =
+ GetInt(LookAndFeel::IntID::ScrollArrowStyle) != eScrollArrow_None;
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_HAS_SCROLLBAR_BUTTONS,
+ hasScrollbarButtons);
+
+ // Whether the current Gtk theme uses something other than a solid color
+ // background for scrollbar parts.
+ bool scrollbarUsesImage = !ShouldHonorThemeScrollbarColors();
+ mozilla::Telemetry::ScalarSet(
+ mozilla::Telemetry::ScalarID::WIDGET_GTK_THEME_SCROLLBAR_USES_IMAGES,
+ scrollbarUsesImage);
+}
+
+bool nsLookAndFeel::ShouldHonorThemeScrollbarColors() {
+ // If the Gtk theme uses anything other than solid color backgrounds for Gtk
+ // scrollbar parts, this is a good indication that painting XUL scrollbar part
+ // elements using colors extracted from the theme won't provide good results.
+ return !WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) &&
+ !WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+}
+
+#undef LOGLNF
+#undef LOGLNF_ENABLED
diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h
new file mode 100644
index 0000000000..56608d331f
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.h
@@ -0,0 +1,211 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "X11UndefineNone.h"
+#include "nsXPLookAndFeel.h"
+#include "nsCOMPtr.h"
+#include "gfxFont.h"
+
+enum WidgetNodeType : int;
+struct _GtkStyle;
+typedef struct _GDBusProxy GDBusProxy;
+typedef struct _GtkCssProvider GtkCssProvider;
+typedef struct _GFile GFile;
+typedef struct _GFileMonitor GFileMonitor;
+
+namespace mozilla {
+enum class StyleGtkThemeFamily : uint8_t;
+}
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ nsLookAndFeel();
+ virtual ~nsLookAndFeel();
+
+ void NativeInit() final;
+ void RefreshImpl() override;
+ nsresult NativeGetInt(IntID aID, int32_t& aResult) override;
+ nsresult NativeGetFloat(FloatID aID, float& aResult) override;
+ nsresult NativeGetColor(ColorID, ColorScheme, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ char16_t GetPasswordCharacterImpl() override;
+ bool GetEchoPasswordImpl() override;
+
+ bool GetDefaultDrawInTitlebar() override;
+
+ nsXPLookAndFeel::TitlebarAction GetTitlebarAction(
+ TitlebarEvent aEvent) override;
+
+ void GetThemeInfo(nsACString&) override;
+
+ static const nscolor kBlack = NS_RGB(0, 0, 0);
+ static const nscolor kWhite = NS_RGB(255, 255, 255);
+ void OnColorSchemeSettingChanged();
+
+ struct ColorPair {
+ nscolor mBg = kWhite;
+ nscolor mFg = kBlack;
+ };
+
+ using ThemeFamily = mozilla::StyleGtkThemeFamily;
+
+ protected:
+ static bool WidgetUsesImage(WidgetNodeType aNodeType);
+ void RecordLookAndFeelSpecificTelemetry() override;
+ static bool ShouldHonorThemeScrollbarColors();
+ mozilla::Maybe<ColorScheme> ComputeColorSchemeSetting();
+
+ void WatchDBus();
+ void UnwatchDBus();
+
+ // We use up to two themes (one light, one dark), which might have different
+ // sets of fonts and colors.
+ struct PerThemeData {
+ nsCString mName;
+ bool mIsDark = false;
+ bool mHighContrast = false;
+ bool mPreferDarkTheme = false;
+
+ ThemeFamily mFamily{0};
+
+ // Cached fonts
+ nsString mDefaultFontName;
+ nsString mButtonFontName;
+ nsString mFieldFontName;
+ nsString mMenuFontName;
+ gfxFontStyle mDefaultFontStyle;
+ gfxFontStyle mButtonFontStyle;
+ gfxFontStyle mFieldFontStyle;
+ gfxFontStyle mMenuFontStyle;
+
+ // Cached colors
+ nscolor mGrayText = kBlack;
+ ColorPair mInfo;
+ ColorPair mMenu;
+ ColorPair mMenuHover;
+ ColorPair mHeaderBar;
+ ColorPair mHeaderBarInactive;
+ ColorPair mButton;
+ ColorPair mButtonHover;
+ ColorPair mButtonActive;
+ nscolor mButtonBorder = kBlack;
+ nscolor mThreeDHighlight = kBlack;
+ nscolor mThreeDShadow = kBlack;
+ nscolor mOddCellBackground = kWhite;
+ nscolor mNativeHyperLinkText = kBlack;
+ nscolor mNativeVisitedHyperLinkText = kBlack;
+ // FIXME: This doesn't seem like it'd be sound since we use Window for
+ // -moz-Combobox... But I guess we rely on chrome code not setting
+ // appearance: none on selects or overriding the color if they do.
+ nscolor mComboBoxText = kBlack;
+ ColorPair mField;
+ ColorPair mWindow;
+ ColorPair mDialog;
+ ColorPair mSidebar;
+ nscolor mSidebarBorder = kBlack;
+
+ nscolor mMozWindowActiveBorder = kBlack;
+ nscolor mMozWindowInactiveBorder = kBlack;
+
+ ColorPair mCellHighlight;
+ ColorPair mSelectedText;
+ ColorPair mAccent;
+ ColorPair mSelectedItem;
+
+ ColorPair mMozColHeader;
+ ColorPair mMozColHeaderHover;
+ ColorPair mMozColHeaderActive;
+
+ ColorPair mTitlebar;
+ ColorPair mTitlebarInactive;
+
+ nscolor mThemedScrollbar = kWhite;
+ nscolor mThemedScrollbarInactive = kWhite;
+ nscolor mThemedScrollbarThumb = kBlack;
+ nscolor mThemedScrollbarThumbHover = kBlack;
+ nscolor mThemedScrollbarThumbActive = kBlack;
+ nscolor mThemedScrollbarThumbInactive = kBlack;
+
+ float mCaretRatio = 0.0f;
+ int32_t mTitlebarRadius = 0;
+ char16_t mInvisibleCharacter = 0;
+ bool mMenuSupportsDrag = false;
+
+ void Init();
+ nsresult GetColor(ColorID, nscolor&) const;
+ bool GetFont(FontID, nsString& aFontName, gfxFontStyle&) const;
+ void InitCellHighlightColors();
+ };
+
+ PerThemeData mSystemTheme;
+
+ // If the system theme is light, a dark theme. Otherwise, a light theme. The
+ // alternative theme to the current one is preferred, but otherwise we fall
+ // back to Adwaita / Adwaita Dark, respectively.
+ PerThemeData mAltTheme;
+
+ const PerThemeData& LightTheme() const {
+ return mSystemTheme.mIsDark ? mAltTheme : mSystemTheme;
+ }
+
+ const PerThemeData& DarkTheme() const {
+ return mSystemTheme.mIsDark ? mSystemTheme : mAltTheme;
+ }
+
+ const PerThemeData& EffectiveTheme() const {
+ return mSystemThemeOverridden ? mAltTheme : mSystemTheme;
+ }
+
+ uint32_t mDBusID = 0;
+ RefPtr<GDBusProxy> mDBusSettingsProxy;
+ RefPtr<GFile> mKdeColors;
+ RefPtr<GFileMonitor> mKdeColorsMonitor;
+ mozilla::Maybe<ColorScheme> mColorSchemePreference;
+ int32_t mCaretBlinkTime = 0;
+ int32_t mCaretBlinkCount = -1;
+ bool mCSDMaximizeButton = false;
+ bool mCSDMinimizeButton = false;
+ bool mCSDCloseButton = false;
+ bool mCSDReversedPlacement = false;
+ bool mPrefersReducedMotion = false;
+ bool mInitialized = false;
+ bool mSystemThemeOverridden = false;
+ int32_t mCSDMaximizeButtonPosition = 0;
+ int32_t mCSDMinimizeButtonPosition = 0;
+ int32_t mCSDCloseButtonPosition = 0;
+ TitlebarAction mDoubleClickAction = TitlebarAction::None;
+ TitlebarAction mMiddleClickAction = TitlebarAction::None;
+
+ RefPtr<GtkCssProvider> mRoundedCornerProvider;
+ void UpdateRoundedBottomCornerStyles();
+
+ void ClearRoundedCornerProvider();
+
+ void EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+ Initialize();
+ }
+
+ void Initialize();
+
+ void RestoreSystemTheme();
+ void InitializeGlobalSettings();
+ // Returns whether we found an alternative theme.
+ bool ConfigureAltTheme();
+ void ConfigureAndInitializeAltTheme();
+ void ConfigureFinalEffectiveTheme();
+ void MaybeApplyAdwaitaOverrides();
+};
+
+#endif
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 0000000000..06d9b48007
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,1369 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNativeThemeGTK.h"
+#include "nsPresContext.h"
+#include "nsStyleConsts.h"
+#include "gtkdrawing.h"
+#include "ScreenHelperGTK.h"
+#include "WidgetUtilsGtk.h"
+
+#include "gfx2DGlue.h"
+#include "nsIObserverService.h"
+#include "nsIFrame.h"
+#include "nsIContent.h"
+#include "nsViewManager.h"
+#include "nsNameSpaceManager.h"
+#include "nsGfxCIID.h"
+#include "nsTransform2D.h"
+#include "nsXULPopupManager.h"
+#include "tree/nsTreeBodyFrame.h"
+#include "prlink.h"
+#include "nsGkAtoms.h"
+#include "nsAttrValueInlines.h"
+
+#include "mozilla/dom/HTMLInputElement.h"
+#include "mozilla/ClearOnShutdown.h"
+#include "mozilla/Services.h"
+
+#include <gdk/gdkprivate.h>
+#include <gtk/gtk.h>
+
+#include "gfxContext.h"
+#include "mozilla/dom/XULButtonElement.h"
+#include "mozilla/gfx/BorrowedContext.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/PathHelpers.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/layers/StackingContextHelper.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "nsWindow.h"
+#include "nsLayoutUtils.h"
+#include "Theme.h"
+
+#ifdef MOZ_X11
+# ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+# endif
+#endif
+
+#include <algorithm>
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+
+static int gLastGdkError;
+
+// Return widget scale factor of the monitor where the window is located by the
+// most part. We intentionally honor the text scale factor here in order to
+// have consistent scaling with other UI elements.
+static inline CSSToLayoutDeviceScale GetWidgetScaleFactor(nsIFrame* aFrame) {
+ return aFrame->PresContext()->CSSToDevPixelScale();
+}
+
+nsNativeThemeGTK::nsNativeThemeGTK() : Theme(ScrollbarStyle()) {
+ if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
+ memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
+ return;
+ }
+
+ ThemeChanged();
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() { moz_gtk_shutdown(); }
+
+void nsNativeThemeGTK::RefreshWidgetWindow(nsIFrame* aFrame) {
+ MOZ_ASSERT(aFrame);
+ MOZ_ASSERT(aFrame->PresShell());
+
+ nsViewManager* vm = aFrame->PresShell()->GetViewManager();
+ if (!vm) {
+ return;
+ }
+ vm->InvalidateAllViews();
+}
+
+static bool IsFrameContentNodeInNamespace(nsIFrame* aFrame,
+ uint32_t aNamespace) {
+ nsIContent* content = aFrame ? aFrame->GetContent() : nullptr;
+ if (!content) return false;
+ return content->IsInNamespace(aNamespace);
+}
+
+static bool IsWidgetTypeDisabled(const uint8_t* aDisabledVector,
+ StyleAppearance aAppearance) {
+ auto type = static_cast<size_t>(aAppearance);
+ MOZ_ASSERT(type < static_cast<size_t>(StyleAppearance::Count));
+ return (aDisabledVector[type >> 3] & (1 << (type & 7))) != 0;
+}
+
+static void SetWidgetTypeDisabled(uint8_t* aDisabledVector,
+ StyleAppearance aAppearance) {
+ auto type = static_cast<size_t>(aAppearance);
+ MOZ_ASSERT(type < static_cast<size_t>(mozilla::StyleAppearance::Count));
+ aDisabledVector[type >> 3] |= (1 << (type & 7));
+}
+
+static inline uint16_t GetWidgetStateKey(StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ return (aWidgetState->active | aWidgetState->focused << 1 |
+ aWidgetState->inHover << 2 | aWidgetState->disabled << 3 |
+ aWidgetState->isDefault << 4 |
+ static_cast<uint16_t>(aAppearance) << 5);
+}
+
+static bool IsWidgetStateSafe(uint8_t* aSafeVector, StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ MOZ_ASSERT(static_cast<size_t>(aAppearance) <
+ static_cast<size_t>(mozilla::StyleAppearance::Count));
+ uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
+ return (aSafeVector[key >> 3] & (1 << (key & 7))) != 0;
+}
+
+static void SetWidgetStateSafe(uint8_t* aSafeVector,
+ StyleAppearance aAppearance,
+ GtkWidgetState* aWidgetState) {
+ MOZ_ASSERT(static_cast<size_t>(aAppearance) <
+ static_cast<size_t>(mozilla::StyleAppearance::Count));
+ uint16_t key = GetWidgetStateKey(aAppearance, aWidgetState);
+ aSafeVector[key >> 3] |= (1 << (key & 7));
+}
+
+/* static */
+GtkTextDirection nsNativeThemeGTK::GetTextDirection(nsIFrame* aFrame) {
+ // IsFrameRTL() treats vertical-rl modes as right-to-left (in addition to
+ // horizontal text with direction=RTL), rather than just considering the
+ // text direction. GtkTextDirection does not have distinct values for
+ // vertical writing modes, but considering the block flow direction is
+ // important for resizers and scrollbar elements, at least.
+ return IsFrameRTL(aFrame) ? GTK_TEXT_DIR_RTL : GTK_TEXT_DIR_LTR;
+}
+
+// Returns positive for negative margins (otherwise 0).
+gint nsNativeThemeGTK::GetTabMarginPixels(nsIFrame* aFrame) {
+ nscoord margin = IsBottomTab(aFrame) ? aFrame->GetUsedMargin().top
+ : aFrame->GetUsedMargin().bottom;
+
+ return std::min<gint>(
+ MOZ_GTK_TAB_MARGIN_MASK,
+ std::max(0, aFrame->PresContext()->AppUnitsToDevPixels(-margin)));
+}
+
+bool nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aAppearance,
+ nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags) {
+ if (aWidgetFlags) {
+ *aWidgetFlags = 0;
+ }
+
+ ElementState elementState = GetContentState(aFrame, aAppearance);
+ if (aState) {
+ memset(aState, 0, sizeof(GtkWidgetState));
+
+ // For XUL checkboxes and radio buttons, the state of the parent
+ // determines our state.
+ if (aWidgetFlags) {
+ if (elementState.HasState(ElementState::CHECKED)) {
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+ }
+ if (elementState.HasState(ElementState::INDETERMINATE)) {
+ *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
+ }
+ }
+
+ aState->disabled =
+ elementState.HasState(ElementState::DISABLED) || IsReadOnly(aFrame);
+ aState->active = elementState.HasState(ElementState::ACTIVE);
+ aState->focused = elementState.HasState(ElementState::FOCUS);
+ aState->inHover = elementState.HasState(ElementState::HOVER);
+ aState->isDefault = IsDefaultButton(aFrame);
+ aState->canDefault = FALSE; // XXX fix me
+
+ if (aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Toolbarbutton ||
+ aAppearance == StyleAppearance::Dualbutton ||
+ aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) {
+ aState->active &= aState->inHover;
+ } else if (aAppearance == StyleAppearance::Treetwisty ||
+ aAppearance == StyleAppearance::Treetwistyopen) {
+ if (nsTreeBodyFrame* treeBodyFrame = do_QueryFrame(aFrame)) {
+ const mozilla::AtomArray& atoms =
+ treeBodyFrame->GetPropertyArrayForCurrentDrawingItem();
+ aState->selected = atoms.Contains(nsGkAtoms::selected);
+ aState->inHover = atoms.Contains(nsGkAtoms::hover);
+ }
+ }
+
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ // For these widget types, some element (either a child or parent)
+ // actually has element focus, so we check the focused attribute
+ // to see whether to draw in the focused state.
+ aState->focused = elementState.HasState(ElementState::FOCUSRING);
+ if (aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::Checkbox) {
+ // In XUL, checkboxes and radios shouldn't have focus rings, their
+ // labels do
+ aState->focused = FALSE;
+ }
+
+ // A button with drop down menu open or an activated toggle button
+ // should always appear depressed.
+ if (aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Toolbarbutton ||
+ aAppearance == StyleAppearance::Dualbutton ||
+ aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) {
+ bool menuOpen = IsOpenButton(aFrame);
+ aState->depressed = IsCheckedButton(aFrame) || menuOpen;
+ // we must not highlight buttons with open drop down menus on hover.
+ aState->inHover = aState->inHover && !menuOpen;
+ }
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ aState->backdrop = !nsWindow::GetTopLevelWindowActiveState(aFrame);
+ }
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NORMAL;
+ aGtkWidgetType = MOZ_GTK_BUTTON;
+ break;
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton:
+ if (aWidgetFlags) *aWidgetFlags = GTK_RELIEF_NONE;
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_BUTTON;
+ break;
+ case StyleAppearance::Checkbox:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON;
+ break;
+ case StyleAppearance::Radio:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON;
+ break;
+ case StyleAppearance::Spinner:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON;
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_UP;
+ break;
+ case StyleAppearance::SpinnerDownbutton:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_DOWN;
+ break;
+ case StyleAppearance::SpinnerTextfield:
+ aGtkWidgetType = MOZ_GTK_SPINBUTTON_ENTRY;
+ break;
+ case StyleAppearance::Range: {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_HORIZONTAL;
+ } else {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_VERTICAL;
+ }
+ break;
+ }
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_HORIZONTAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_HORIZONTAL;
+ } else {
+ if (aWidgetFlags) *aWidgetFlags = GTK_ORIENTATION_VERTICAL;
+ aGtkWidgetType = MOZ_GTK_SCALE_THUMB_VERTICAL;
+ }
+ break;
+ }
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case StyleAppearance::Textarea:
+ aGtkWidgetType = MOZ_GTK_TEXT_VIEW;
+ break;
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW;
+ break;
+ case StyleAppearance::Treetwisty:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_COLLAPSED;
+ break;
+ case StyleAppearance::Treetwistyopen:
+ aGtkWidgetType = MOZ_GTK_TREEVIEW_EXPANDER;
+ if (aWidgetFlags) *aWidgetFlags = GTK_EXPANDER_EXPANDED;
+ break;
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Menulist:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN;
+ if (aWidgetFlags)
+ *aWidgetFlags =
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XHTML);
+ break;
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ aGtkWidgetType = MOZ_GTK_TOOLBARBUTTON_ARROW;
+ if (aWidgetFlags) {
+ *aWidgetFlags = GTK_ARROW_DOWN;
+
+ if (aAppearance == StyleAppearance::ButtonArrowUp)
+ *aWidgetFlags = GTK_ARROW_UP;
+ else if (aAppearance == StyleAppearance::ButtonArrowNext)
+ *aWidgetFlags = GTK_ARROW_RIGHT;
+ else if (aAppearance == StyleAppearance::ButtonArrowPrevious)
+ *aWidgetFlags = GTK_ARROW_LEFT;
+ }
+ break;
+ case StyleAppearance::Tooltip:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case StyleAppearance::ProgressBar:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ ElementState elementState = GetContentState(stateFrame, aAppearance);
+
+ aGtkWidgetType = elementState.HasState(ElementState::INDETERMINATE)
+ ? IsVerticalProgress(stateFrame)
+ ? MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE
+ : MOZ_GTK_PROGRESS_CHUNK;
+ } break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ if (aWidgetFlags)
+ *aWidgetFlags = aAppearance == StyleAppearance::TabScrollArrowBack
+ ? GTK_ARROW_LEFT
+ : GTK_ARROW_RIGHT;
+ aGtkWidgetType = MOZ_GTK_TAB_SCROLLARROW;
+ break;
+ case StyleAppearance::Tabpanels:
+ aGtkWidgetType = MOZ_GTK_TABPANELS;
+ break;
+ case StyleAppearance::Tab: {
+ if (IsBottomTab(aFrame)) {
+ aGtkWidgetType = MOZ_GTK_TAB_BOTTOM;
+ } else {
+ aGtkWidgetType = MOZ_GTK_TAB_TOP;
+ }
+
+ if (aWidgetFlags) {
+ /* First bits will be used to store max(0,-bmargin) where bmargin
+ * is the bottom margin of the tab in pixels (resp. top margin,
+ * for bottom tabs). */
+ *aWidgetFlags = GetTabMarginPixels(aFrame);
+
+ if (IsSelectedTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_SELECTED;
+
+ if (IsFirstTab(aFrame)) *aWidgetFlags |= MOZ_GTK_TAB_FIRST;
+ }
+ } break;
+ case StyleAppearance::Splitter:
+ if (IsHorizontal(aFrame))
+ aGtkWidgetType = MOZ_GTK_SPLITTER_VERTICAL;
+ else
+ aGtkWidgetType = MOZ_GTK_SPLITTER_HORIZONTAL;
+ break;
+ case StyleAppearance::MozWindowTitlebar:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR;
+ break;
+ case StyleAppearance::MozWindowDecorations:
+ aGtkWidgetType = MOZ_GTK_WINDOW_DECORATION;
+ break;
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_MAXIMIZED;
+ break;
+ case StyleAppearance::MozWindowButtonBox:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_BOX;
+ break;
+ case StyleAppearance::MozWindowButtonClose:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_CLOSE;
+ break;
+ case StyleAppearance::MozWindowButtonMinimize:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE;
+ break;
+ case StyleAppearance::MozWindowButtonMaximize:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE;
+ break;
+ case StyleAppearance::MozWindowButtonRestore:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE;
+ break;
+ default:
+ return false;
+ }
+
+ return true;
+}
+
+class SystemCairoClipper : public ClipExporter {
+ public:
+ explicit SystemCairoClipper(cairo_t* aContext, gint aScaleFactor = 1)
+ : mContext(aContext), mScaleFactor(aScaleFactor) {}
+
+ void BeginClip(const Matrix& aTransform) override {
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(aTransform, mat);
+ // We also need to remove the scale factor effect from the matrix
+ mat.y0 = mat.y0 / mScaleFactor;
+ mat.x0 = mat.x0 / mScaleFactor;
+ cairo_set_matrix(mContext, &mat);
+
+ cairo_new_path(mContext);
+ }
+
+ void MoveTo(const Point& aPoint) override {
+ cairo_move_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
+ mBeginPoint = aPoint;
+ mCurrentPoint = aPoint;
+ }
+
+ void LineTo(const Point& aPoint) override {
+ cairo_line_to(mContext, aPoint.x / mScaleFactor, aPoint.y / mScaleFactor);
+ mCurrentPoint = aPoint;
+ }
+
+ void BezierTo(const Point& aCP1, const Point& aCP2,
+ const Point& aCP3) override {
+ cairo_curve_to(mContext, aCP1.x / mScaleFactor, aCP1.y / mScaleFactor,
+ aCP2.x / mScaleFactor, aCP2.y / mScaleFactor,
+ aCP3.x / mScaleFactor, aCP3.y / mScaleFactor);
+ mCurrentPoint = aCP3;
+ }
+
+ void QuadraticBezierTo(const Point& aCP1, const Point& aCP2) override {
+ Point CP0 = CurrentPoint();
+ Point CP1 = (CP0 + aCP1 * 2.0) / 3.0;
+ Point CP2 = (aCP2 + aCP1 * 2.0) / 3.0;
+ Point CP3 = aCP2;
+ cairo_curve_to(mContext, CP1.x / mScaleFactor, CP1.y / mScaleFactor,
+ CP2.x / mScaleFactor, CP2.y / mScaleFactor,
+ CP3.x / mScaleFactor, CP3.y / mScaleFactor);
+ mCurrentPoint = aCP2;
+ }
+
+ void Arc(const Point& aOrigin, float aRadius, float aStartAngle,
+ float aEndAngle, bool aAntiClockwise) override {
+ ArcToBezier(this, aOrigin, Size(aRadius, aRadius), aStartAngle, aEndAngle,
+ aAntiClockwise);
+ }
+
+ void Close() override {
+ cairo_close_path(mContext);
+ mCurrentPoint = mBeginPoint;
+ }
+
+ void EndClip() override { cairo_clip(mContext); }
+
+ private:
+ cairo_t* mContext;
+ gint mScaleFactor;
+};
+
+static void DrawThemeWithCairo(gfxContext* aContext, DrawTarget* aDrawTarget,
+ GtkWidgetState aState,
+ WidgetNodeType aGTKWidgetType, gint aFlags,
+ GtkTextDirection aDirection, double aScaleFactor,
+ bool aSnapped, const Point& aDrawOrigin,
+ const nsIntSize& aDrawSize,
+ GdkRectangle& aGDKRect,
+ nsITheme::Transparency aTransparency) {
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*, double, double))dlsym(
+ RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ const bool useHiDPIWidgets =
+ aScaleFactor != 1.0 && sCairoSurfaceSetDeviceScalePtr;
+
+ Point drawOffsetScaled;
+ Point drawOffsetOriginal;
+ Matrix transform;
+ if (!aSnapped) {
+ // If we are not snapped, we depend on the DT for translation.
+ drawOffsetOriginal = aDrawOrigin;
+ drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
+ : drawOffsetOriginal;
+ transform = aDrawTarget->GetTransform().PreTranslate(drawOffsetScaled);
+ } else {
+ // Otherwise, we only need to take the device offset into account.
+ drawOffsetOriginal = aDrawOrigin - aContext->GetDeviceOffset();
+ drawOffsetScaled = useHiDPIWidgets ? drawOffsetOriginal / aScaleFactor
+ : drawOffsetOriginal;
+ transform = Matrix::Translation(drawOffsetScaled);
+ }
+
+ if (!useHiDPIWidgets && aScaleFactor != 1) {
+ transform.PreScale(aScaleFactor, aScaleFactor);
+ }
+
+ cairo_matrix_t mat;
+ GfxMatrixToCairoMatrix(transform, mat);
+
+ Size clipSize((aDrawSize.width + aScaleFactor - 1) / aScaleFactor,
+ (aDrawSize.height + aScaleFactor - 1) / aScaleFactor);
+
+ // A direct Cairo draw target is not available, so we need to create a
+ // temporary one.
+#if defined(MOZ_X11) && defined(CAIRO_HAS_XLIB_SURFACE)
+ if (GdkIsX11Display()) {
+ // If using a Cairo xlib surface, then try to reuse it.
+ BorrowedXlibDrawable borrow(aDrawTarget);
+ if (Drawable drawable = borrow.GetDrawable()) {
+ nsIntSize size = borrow.GetSize();
+ cairo_surface_t* surf = cairo_xlib_surface_create(
+ borrow.GetDisplay(), drawable, borrow.GetVisual(), size.width,
+ size.height);
+ if (!NS_WARN_IF(!surf)) {
+ Point offset = borrow.GetOffset();
+ if (offset != Point()) {
+ cairo_surface_set_device_offset(surf, offset.x, offset.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper = new SystemCairoClipper(cr);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ borrow.Finish();
+ return;
+ }
+ }
+#endif
+
+ // Check if the widget requires complex masking that must be composited.
+ // Try to directly write to the draw target's pixels if possible.
+ uint8_t* data;
+ nsIntSize size;
+ int32_t stride;
+ SurfaceFormat format;
+ IntPoint origin;
+ if (aDrawTarget->LockBits(&data, &size, &stride, &format, &origin)) {
+ // Create a Cairo image surface context the device rectangle.
+ cairo_surface_t* surf = cairo_image_surface_create_for_data(
+ data, GfxFormatToCairoFormat(format), size.width, size.height, stride);
+ if (!NS_WARN_IF(!surf)) {
+ if (useHiDPIWidgets) {
+ sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
+ }
+ if (origin != IntPoint()) {
+ cairo_surface_set_device_offset(surf, -origin.x, -origin.y);
+ }
+ cairo_t* cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ RefPtr<SystemCairoClipper> clipper =
+ new SystemCairoClipper(cr, useHiDPIWidgets ? aScaleFactor : 1);
+ aContext->ExportClip(*clipper);
+
+ cairo_set_matrix(cr, &mat);
+
+ cairo_new_path(cr);
+ cairo_rectangle(cr, 0, 0, clipSize.width, clipSize.height);
+ cairo_clip(cr);
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+
+ cairo_destroy(cr);
+ }
+ cairo_surface_destroy(surf);
+ }
+ aDrawTarget->ReleaseBits(data);
+ } else {
+ // If the widget has any transparency, make sure to choose an alpha format.
+ format = aTransparency != nsITheme::eOpaque ? SurfaceFormat::B8G8R8A8
+ : aDrawTarget->GetFormat();
+ // Create a temporary data surface to render the widget into.
+ RefPtr<DataSourceSurface> dataSurface = Factory::CreateDataSourceSurface(
+ aDrawSize, format, aTransparency != nsITheme::eOpaque);
+ DataSourceSurface::MappedSurface map;
+ if (!NS_WARN_IF(
+ !(dataSurface &&
+ dataSurface->Map(DataSourceSurface::MapType::WRITE, &map)))) {
+ // Create a Cairo image surface wrapping the data surface.
+ cairo_surface_t* surf = cairo_image_surface_create_for_data(
+ map.mData, GfxFormatToCairoFormat(format), aDrawSize.width,
+ aDrawSize.height, map.mStride);
+ cairo_t* cr = nullptr;
+ if (!NS_WARN_IF(!surf)) {
+ cr = cairo_create(surf);
+ if (!NS_WARN_IF(!cr)) {
+ if (aScaleFactor != 1) {
+ if (useHiDPIWidgets) {
+ sCairoSurfaceSetDeviceScalePtr(surf, aScaleFactor, aScaleFactor);
+ } else {
+ cairo_scale(cr, aScaleFactor, aScaleFactor);
+ }
+ }
+
+ moz_gtk_widget_paint(aGTKWidgetType, cr, &aGDKRect, &aState, aFlags,
+ aDirection);
+ }
+ }
+
+ // Unmap the surface before using it as a source
+ dataSurface->Unmap();
+
+ if (cr) {
+ // The widget either needs to be masked or has transparency, so use the
+ // slower drawing path.
+ aDrawTarget->DrawSurface(
+ dataSurface,
+ Rect(aSnapped ? drawOffsetOriginal -
+ aDrawTarget->GetTransform().GetTranslation()
+ : drawOffsetOriginal,
+ Size(aDrawSize)),
+ Rect(0, 0, aDrawSize.width, aDrawSize.height));
+ cairo_destroy(cr);
+ }
+
+ if (surf) {
+ cairo_surface_destroy(surf);
+ }
+ }
+ }
+}
+
+CSSIntMargin nsNativeThemeGTK::GetExtraSizeForWidget(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ CSSIntMargin extra;
+ // Allow an extra one pixel above and below the thumb for certain
+ // GTK2 themes (Ximian Industrial, Bluecurve, Misty, at least);
+ // We modify the frame's overflow area. See bug 297508.
+ switch (aAppearance) {
+ case StyleAppearance::Button: {
+ if (IsDefaultButton(aFrame)) {
+ // Some themes draw a default indicator outside the widget,
+ // include that in overflow
+ moz_gtk_button_get_default_overflow(&extra.top.value, &extra.left.value,
+ &extra.bottom.value,
+ &extra.right.value);
+ break;
+ }
+ return {};
+ }
+ default:
+ return {};
+ }
+ return extra;
+}
+
+bool nsNativeThemeGTK::IsWidgetVisible(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::MozWindowButtonBox:
+ return false;
+ default:
+ break;
+ }
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect,
+ DrawOverflow aDrawOverflow) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::DrawWidgetBackground(aContext, aFrame, aAppearance, aRect,
+ aDirtyRect, aDrawOverflow);
+ }
+
+ GtkWidgetState state;
+ WidgetNodeType gtkWidgetType;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ gint flags;
+
+ if (!IsWidgetVisible(aAppearance) ||
+ !GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, &state,
+ &flags)) {
+ return NS_OK;
+ }
+
+ gfxContext* ctx = aContext;
+ nsPresContext* presContext = aFrame->PresContext();
+
+ gfxRect rect = presContext->AppUnitsToGfxUnits(aRect);
+ gfxRect dirtyRect = presContext->AppUnitsToGfxUnits(aDirtyRect);
+
+ // Align to device pixels where sensible
+ // to provide crisper and faster drawing.
+ // Don't snap if it's a non-unit scale factor. We're going to have to take
+ // slow paths then in any case.
+ // We prioritize the size when snapping in order to avoid distorting widgets
+ // that should be square, which can occur if edges are snapped independently.
+ bool snapped = ctx->UserToDevicePixelSnapped(
+ rect, gfxContext::SnapOption::PrioritizeSize);
+ if (snapped) {
+ // Leave rect in device coords but make dirtyRect consistent.
+ dirtyRect = ctx->UserToDevice(dirtyRect);
+ }
+
+ // Translate the dirty rect so that it is wrt the widget top-left.
+ dirtyRect.MoveBy(-rect.TopLeft());
+ // Round out the dirty rect to gdk pixels to ensure that gtk draws
+ // enough pixels for interpolation to device pixels.
+ dirtyRect.RoundOut();
+
+ // GTK themes can only draw an integer number of pixels
+ // (even when not snapped).
+ LayoutDeviceIntRect widgetRect(0, 0, NS_lround(rect.Width()),
+ NS_lround(rect.Height()));
+
+ // This is the rectangle that will actually be drawn, in gdk pixels
+ LayoutDeviceIntRect drawingRect(
+ int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
+ int32_t(dirtyRect.Width()), int32_t(dirtyRect.Height()));
+ if (widgetRect.IsEmpty() ||
+ !drawingRect.IntersectRect(widgetRect, drawingRect)) {
+ return NS_OK;
+ }
+
+ NS_ASSERTION(!IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance),
+ "Trying to render an unsafe widget!");
+
+ bool safeState = IsWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
+ if (!safeState) {
+ gLastGdkError = 0;
+ gdk_error_trap_push();
+ }
+
+ Transparency transparency = GetWidgetTransparency(aFrame, aAppearance);
+
+ // gdk rectangles are wrt the drawing rect.
+ auto scaleFactor = GetWidgetScaleFactor(aFrame);
+ LayoutDeviceIntRect gdkDevRect(-drawingRect.TopLeft(), widgetRect.Size());
+
+ auto gdkCssRect = CSSIntRect::RoundIn(gdkDevRect / scaleFactor);
+ GdkRectangle gdk_rect = {gdkCssRect.x, gdkCssRect.y, gdkCssRect.width,
+ gdkCssRect.height};
+
+ // Save actual widget scale to GtkWidgetState as we don't provide
+ // the frame to gtk3drawing routines.
+ state.image_scale = std::ceil(scaleFactor.scale);
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft().ToUnknownPoint();
+
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
+ flags, direction, scaleFactor.scale, snapped,
+ ToPoint(origin), drawingRect.Size().ToUnknownSize(),
+ gdk_rect, transparency);
+
+ if (!safeState) {
+ // gdk_flush() call from expose event crashes Gtk+ on Wayland
+ // (Gnome BZ #773307)
+ if (GdkIsX11Display()) {
+ gdk_flush();
+ }
+ gLastGdkError = gdk_error_trap_pop();
+
+ if (gLastGdkError) {
+#ifdef DEBUG
+ printf(
+ "GTK theme failed for widget type %d, error was %d, state was "
+ "[active=%d,focused=%d,inHover=%d,disabled=%d]\n",
+ static_cast<int>(aAppearance), gLastGdkError, state.active,
+ state.focused, state.inHover, state.disabled);
+#endif
+ NS_WARNING("GTK theme failed; disabling unsafe widget");
+ SetWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance);
+ // force refresh of the window, because the widget was not
+ // successfully drawn it must be redrawn using the default look
+ RefreshWidgetWindow(aFrame);
+ } else {
+ SetWidgetStateSafe(mSafeWidgetStates, aAppearance, &state);
+ }
+ }
+
+ // Indeterminate progress bar are animated.
+ if (gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_INDETERMINATE ||
+ gtkWidgetType == MOZ_GTK_PROGRESS_CHUNK_VERTICAL_INDETERMINATE) {
+ if (!QueueAnimatedContentForRefresh(aFrame->GetContent(), 30)) {
+ NS_WARNING("unable to animate widget!");
+ }
+ }
+
+ return NS_OK;
+}
+
+bool nsNativeThemeGTK::CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame* aFrame,
+ StyleAppearance aAppearance, const nsRect& aRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::CreateWebRenderCommandsForWidget(
+ aBuilder, aResources, aSc, aManager, aFrame, aAppearance, aRect);
+ }
+ return false;
+}
+
+WidgetNodeType nsNativeThemeGTK::NativeThemeToGtkTheme(
+ StyleAppearance aAppearance, nsIFrame* aFrame) {
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+
+ if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ MOZ_ASSERT_UNREACHABLE("Unknown native widget to gtk widget mapping");
+ return MOZ_GTK_WINDOW;
+ }
+ return gtkWidgetType;
+}
+
+static void FixupForVerticalWritingMode(WritingMode aWritingMode,
+ CSSIntMargin* aResult) {
+ if (aWritingMode.IsVertical()) {
+ bool rtl = aWritingMode.IsBidiRTL();
+ LogicalMargin logical(aWritingMode, aResult->top,
+ rtl ? aResult->left : aResult->right, aResult->bottom,
+ rtl ? aResult->right : aResult->left);
+ nsMargin physical = logical.GetPhysicalMargin(aWritingMode);
+ aResult->top = physical.top;
+ aResult->right = physical.right;
+ aResult->bottom = physical.bottom;
+ aResult->left = physical.left;
+ }
+}
+
+CSSIntMargin nsNativeThemeGTK::GetCachedWidgetBorder(
+ nsIFrame* aFrame, StyleAppearance aAppearance,
+ GtkTextDirection aDirection) {
+ CSSIntMargin result;
+
+ WidgetNodeType gtkWidgetType;
+ gint unusedFlags;
+ if (GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &unusedFlags)) {
+ MOZ_ASSERT(0 <= gtkWidgetType && gtkWidgetType < MOZ_GTK_WIDGET_NODE_COUNT);
+ uint8_t cacheIndex = gtkWidgetType / 8;
+ uint8_t cacheBit = 1u << (gtkWidgetType % 8);
+
+ if (mBorderCacheValid[cacheIndex] & cacheBit) {
+ result = mBorderCache[gtkWidgetType];
+ } else {
+ moz_gtk_get_widget_border(gtkWidgetType, &result.left.value,
+ &result.top.value, &result.right.value,
+ &result.bottom.value, aDirection);
+ if (gtkWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection
+ mBorderCacheValid[cacheIndex] |= cacheBit;
+ mBorderCache[gtkWidgetType] = result;
+ }
+ }
+ }
+ FixupForVerticalWritingMode(aFrame->GetWritingMode(), &result);
+ return result;
+}
+
+LayoutDeviceIntMargin nsNativeThemeGTK::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetBorder(aContext, aFrame, aAppearance);
+ }
+
+ CSSIntMargin result;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::Toolbox:
+ // gtk has no toolbox equivalent. So, although we map toolbox to
+ // gtk's 'toolbar' for purposes of painting the widget background,
+ // we don't use the toolbar border for toolbox.
+ break;
+ case StyleAppearance::Dualbutton:
+ // TOOLBAR_DUAL_BUTTON is an interesting case. We want a border to draw
+ // around the entire button + dropdown, and also an inner border if you're
+ // over the button part. But, we want the inner button to be right up
+ // against the edge of the outer button so that the borders overlap.
+ // To make this happen, we draw a button border for the outer button,
+ // but don't reserve any space for it.
+ break;
+ case StyleAppearance::Tab: {
+ WidgetNodeType gtkWidgetType;
+ gint flags;
+
+ if (!GetGtkWidgetAndState(aAppearance, aFrame, gtkWidgetType, nullptr,
+ &flags)) {
+ return {};
+ }
+ moz_gtk_get_tab_border(&result.left.value, &result.top.value,
+ &result.right.value, &result.bottom.value,
+ direction, (GtkTabFlags)flags, gtkWidgetType);
+ } break;
+ default: {
+ result = GetCachedWidgetBorder(aFrame, aAppearance, direction);
+ }
+ }
+
+ return (CSSMargin(result) * GetWidgetScaleFactor(aFrame)).Rounded();
+}
+
+bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetWidgetPadding(aContext, aFrame, aAppearance, aResult);
+ }
+ switch (aAppearance) {
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::Dualbutton:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::RangeThumb:
+ // Radios and checkboxes return a fixed size in GetMinimumWidgetSize
+ // and have a meaningful baseline, so they can't have
+ // author-specified padding.
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aResult->SizeTo(0, 0, 0, 0);
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetOverflow(aContext, aFrame, aAppearance,
+ aOverflowRect);
+ }
+ auto overflow = GetExtraSizeForWidget(aFrame, aAppearance);
+ if (overflow == CSSIntMargin()) {
+ return false;
+ }
+ aOverflowRect->Inflate(CSSIntMargin::ToAppUnits(overflow));
+ return true;
+}
+
+auto nsNativeThemeGTK::IsWidgetNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance)
+ -> NonNative {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return NonNative::Always;
+ }
+
+ // If the current GTK theme color scheme matches our color-scheme, then we
+ // can draw a native widget.
+ if (LookAndFeel::ColorSchemeForFrame(aFrame) ==
+ PreferenceSheet::ColorSchemeForChrome()) {
+ return NonNative::No;
+ }
+
+ // As an special-case, for tooltips, we check if the tooltip color is the
+ // same between the light and dark themes. If so we can get away with drawing
+ // the native widget, see bug 1817396.
+ if (aAppearance == StyleAppearance::Tooltip) {
+ auto darkColor =
+ LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Dark,
+ LookAndFeel::UseStandins::No);
+ auto lightColor =
+ LookAndFeel::Color(StyleSystemColor::Infotext, ColorScheme::Light,
+ LookAndFeel::UseStandins::No);
+ if (darkColor == lightColor) {
+ return NonNative::No;
+ }
+ }
+
+ // If the non-native theme doesn't support the widget then oh well...
+ if (!Theme::ThemeSupportsWidget(aFrame->PresContext(), aFrame, aAppearance)) {
+ return NonNative::No;
+ }
+
+ return NonNative::BecauseColorMismatch;
+}
+
+bool nsNativeThemeGTK::IsWidgetAlwaysNonNative(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ return Theme::IsWidgetAlwaysNonNative(aFrame, aAppearance) ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton;
+}
+
+LayoutDeviceIntSize nsNativeThemeGTK::GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::GetMinimumWidgetSize(aPresContext, aFrame, aAppearance);
+ }
+
+ CSSIntSize result;
+ switch (aAppearance) {
+ case StyleAppearance::Splitter: {
+ if (IsHorizontal(aFrame)) {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &result.width);
+ } else {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &result.height);
+ }
+ } break;
+ case StyleAppearance::RangeThumb: {
+ if (IsRangeHorizontal(aFrame)) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
+ &result.width, &result.height);
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &result.width,
+ &result.width);
+ }
+ } break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward: {
+ moz_gtk_get_tab_scroll_arrow_size(&result.width, &result.height);
+ } break;
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ const ToggleGTKMetrics* metrics = GetToggleMetrics(
+ aAppearance == StyleAppearance::Radio ? MOZ_GTK_RADIOBUTTON
+ : MOZ_GTK_CHECKBUTTON);
+ result.width = metrics->minSizeWithBorder.width;
+ result.height = metrics->minSizeWithBorder.height;
+ } break;
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious: {
+ moz_gtk_get_arrow_size(MOZ_GTK_TOOLBARBUTTON_ARROW, &result.width,
+ &result.height);
+ } break;
+ case StyleAppearance::MozWindowButtonClose: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ result.width = metrics->minSizeWithBorderMargin.width;
+ result.height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMinimize: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+ result.width = metrics->minSizeWithBorderMargin.width;
+ result.height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
+ result.width = metrics->minSizeWithBorderMargin.width;
+ result.height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton: {
+ if (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) {
+ // Include the arrow size.
+ moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &result.width, &result.height);
+ }
+ // else the minimum size is missing consideration of container
+ // descendants; the value returned here will not be helpful, but the
+ // box model may consider border and padding with child minimum sizes.
+
+ CSSIntMargin border =
+ GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame));
+ result.width += border.LeftRight();
+ result.height += border.TopBottom();
+ } break;
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield: {
+ gint contentHeight = 0;
+ gint borderPaddingHeight = 0;
+ moz_gtk_get_entry_min_height(&contentHeight, &borderPaddingHeight);
+
+ // Scale the min content height proportionately with the font-size if it's
+ // smaller than the default one. This prevents <input type=text
+ // style="font-size: .5em"> from keeping a ridiculously large size, for
+ // example.
+ const gfxFloat fieldFontSizeInCSSPixels = [] {
+ gfxFontStyle fieldFontStyle;
+ nsAutoString unusedFontName;
+ DebugOnly<bool> result = LookAndFeel::GetFont(
+ LookAndFeel::FontID::MozField, unusedFontName, fieldFontStyle);
+ MOZ_ASSERT(result, "GTK look and feel supports the field font");
+ // NOTE: GetFont returns font sizes in CSS pixels, and we want just
+ // that.
+ return fieldFontStyle.size;
+ }();
+
+ const gfxFloat fontSize = aFrame->StyleFont()->mFont.size.ToCSSPixels();
+ if (fieldFontSizeInCSSPixels > fontSize) {
+ contentHeight =
+ std::round(contentHeight * fontSize / fieldFontSizeInCSSPixels);
+ }
+
+ gint height = contentHeight + borderPaddingHeight;
+ if (aFrame->GetWritingMode().IsVertical()) {
+ result.width = height;
+ } else {
+ result.height = height;
+ }
+ } break;
+ case StyleAppearance::Spinner:
+ // hard code these sizes
+ result.width = 14;
+ result.height = 26;
+ break;
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ // hard code these sizes
+ result.width = 14;
+ result.height = 13;
+ break;
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen: {
+ gint expander_size;
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ result.width = result.height = expander_size;
+ } break;
+ default:
+ break;
+ }
+
+ return LayoutDeviceIntSize::Round(CSSSize(result) *
+ GetWidgetScaleFactor(aFrame));
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ *aShouldRepaint = false;
+
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::WidgetStateChanged(aFrame, aAppearance, aAttribute,
+ aShouldRepaint, aOldValue);
+ }
+
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Tooltip ||
+ aAppearance == StyleAppearance::MozWindowDecorations) {
+ return NS_OK;
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // XXXdwh Not sure what can really be done here. Can at least guess for
+ // specific widgets that they're highly unlikely to have certain states.
+ // For example, a toolbar doesn't care about any states.
+ if (!aAttribute) {
+ // Hover/focus/active changed. Always repaint.
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ // Check the attribute to see if it's relevant.
+ // disabled, checked, dlgtype, default, etc.
+ *aShouldRepaint = false;
+ if (aAttribute == nsGkAtoms::disabled || aAttribute == nsGkAtoms::checked ||
+ aAttribute == nsGkAtoms::selected ||
+ aAttribute == nsGkAtoms::visuallyselected ||
+ aAttribute == nsGkAtoms::focused || aAttribute == nsGkAtoms::readonly ||
+ aAttribute == nsGkAtoms::_default ||
+ aAttribute == nsGkAtoms::menuactive || aAttribute == nsGkAtoms::open) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::ThemeChanged() {
+ memset(mDisabledWidgetTypes, 0, sizeof(mDisabledWidgetTypes));
+ memset(mSafeWidgetStates, 0, sizeof(mSafeWidgetStates));
+ memset(mBorderCacheValid, 0, sizeof(mBorderCacheValid));
+ return NS_OK;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::ThemeSupportsWidget(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetTypeDisabled(mDisabledWidgetTypes, aAppearance)) {
+ return false;
+ }
+
+ if (IsWidgetAlwaysNonNative(aFrame, aAppearance)) {
+ return Theme::ThemeSupportsWidget(aPresContext, aFrame, aAppearance);
+ }
+
+ switch (aAppearance) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ [[fallthrough]];
+
+ case StyleAppearance::Button:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Toolbox: // N/A
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Dualbutton: // so we can override the border with 0
+ case StyleAppearance::ToolbarbuttonDropdown:
+ case StyleAppearance::ButtonArrowUp:
+ case StyleAppearance::ButtonArrowDown:
+ case StyleAppearance::ButtonArrowNext:
+ case StyleAppearance::ButtonArrowPrevious:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ // case StyleAppearance::Treeitem:
+ case StyleAppearance::Treetwisty:
+ // case StyleAppearance::Treeline:
+ // case StyleAppearance::Treeheader:
+ case StyleAppearance::Treetwistyopen:
+ case StyleAppearance::ProgressBar:
+ case StyleAppearance::Progresschunk:
+ case StyleAppearance::Tab:
+ // case StyleAppearance::Tabpanel:
+ case StyleAppearance::Tabpanels:
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward:
+ case StyleAppearance::Tooltip:
+ case StyleAppearance::Spinner:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ case StyleAppearance::SpinnerTextfield:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::Splitter:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ case StyleAppearance::MozWindowDecorations:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+ default:
+ break;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) {
+ // XXXdwh At some point flesh all of this out.
+ if (aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::RangeThumb ||
+ aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::TabScrollArrowBack ||
+ aAppearance == StyleAppearance::TabScrollArrowForward ||
+ aAppearance == StyleAppearance::ButtonArrowUp ||
+ aAppearance == StyleAppearance::ButtonArrowDown ||
+ aAppearance == StyleAppearance::ButtonArrowNext ||
+ aAppearance == StyleAppearance::ButtonArrowPrevious)
+ return false;
+ return true;
+}
+
+bool nsNativeThemeGTK::ThemeDrawsFocusForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::ThemeDrawsFocusForWidget(aFrame, aAppearance);
+ }
+ switch (aAppearance) {
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ // These are drawn only for non-XUL elements, but in XUL the label has
+ // the focus ring.
+ return true;
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
+
+nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ if (IsWidgetNonNative(aFrame, aAppearance) != NonNative::No) {
+ return Theme::GetWidgetTransparency(aFrame, aAppearance);
+ }
+
+ switch (aAppearance) {
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case StyleAppearance::Tooltip:
+ return eTransparent;
+ default:
+ return eUnknownTransparency;
+ }
+}
+
+already_AddRefed<Theme> do_CreateNativeThemeDoNotUseDirectly() {
+ if (gfxPlatform::IsHeadless()) {
+ return do_AddRef(new Theme(Theme::ScrollbarStyle()));
+ }
+ return do_AddRef(new nsNativeThemeGTK());
+}
diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h
new file mode 100644
index 0000000000..2d0878290e
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.h
@@ -0,0 +1,119 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef _GTK_NSNATIVETHEMEGTK_H_
+#define _GTK_NSNATIVETHEMEGTK_H_
+
+#include "nsITheme.h"
+#include "nsCOMPtr.h"
+#include "nsAtom.h"
+#include "Theme.h"
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+class nsNativeThemeGTK final : public mozilla::widget::Theme {
+ using Theme = mozilla::widget::Theme;
+
+ public:
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect, const nsRect& aDirtyRect,
+ DrawOverflow) override;
+
+ bool CreateWebRenderCommandsForWidget(
+ mozilla::wr::DisplayListBuilder& aBuilder,
+ mozilla::wr::IpcResourceUpdateQueue& aResources,
+ const mozilla::layers::StackingContextHelper& aSc,
+ mozilla::layers::RenderRootStateManager* aManager, nsIFrame*,
+ StyleAppearance, const nsRect& aRect) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+
+ // Whether we draw a non-native widget.
+ //
+ // We always draw scrollbars as non-native so that all of Firefox has
+ // consistent scrollbar styles both in chrome and content (plus, the
+ // non-native scrollbars support scrollbar-width, auto-darkening...).
+ //
+ // We draw other widgets as non-native when their color-scheme doesn't match
+ // the current GTK theme's color-scheme. We do that because frequently
+ // switching GTK themes at runtime is prohibitively expensive. In that case
+ // (`BecauseColorMismatch`) we don't call into the non-native theme for sizing
+ // information (GetWidgetPadding/Border and GetMinimumWidgetSize), to avoid
+ // subtle sizing changes. The non-native theme can basically draw at any size,
+ // so we prefer to have consistent sizing information.
+ enum class NonNative { No, Always, BecauseColorMismatch };
+ static bool IsWidgetAlwaysNonNative(nsIFrame*, StyleAppearance);
+ NonNative IsWidgetNonNative(nsIFrame*, StyleAppearance);
+
+ mozilla::LayoutDeviceIntSize GetMinimumWidgetSize(
+ nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ NS_IMETHOD WidgetStateChanged(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) override;
+
+ NS_IMETHOD ThemeChanged() override;
+
+ NS_IMETHOD_(bool)
+ ThemeSupportsWidget(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ NS_IMETHOD_(bool) WidgetIsContainer(StyleAppearance aAppearance) override;
+
+ bool ThemeDrawsFocusForWidget(nsIFrame*, StyleAppearance) override;
+
+ bool ThemeNeedsComboboxDropmarker() override;
+ Transparency GetWidgetTransparency(nsIFrame*, StyleAppearance) override;
+
+ nsNativeThemeGTK();
+
+ protected:
+ virtual ~nsNativeThemeGTK();
+
+ private:
+ GtkTextDirection GetTextDirection(nsIFrame* aFrame);
+ gint GetTabMarginPixels(nsIFrame* aFrame);
+ bool GetGtkWidgetAndState(StyleAppearance aAppearance, nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState, gint* aWidgetFlags);
+ mozilla::CSSIntMargin GetExtraSizeForWidget(nsIFrame*, StyleAppearance);
+ bool IsWidgetVisible(StyleAppearance aAppearance);
+
+ void RefreshWidgetWindow(nsIFrame* aFrame);
+ WidgetNodeType NativeThemeToGtkTheme(StyleAppearance aAppearance,
+ nsIFrame* aFrame);
+
+ uint8_t mDisabledWidgetTypes
+ [(static_cast<size_t>(mozilla::StyleAppearance::Count) + 7) / 8];
+ uint8_t
+ mSafeWidgetStates[static_cast<size_t>(mozilla::StyleAppearance::Count) *
+ 4]; // 32 bits per widget
+ static const char* sDisabledEngines[];
+
+ // Because moz_gtk_get_widget_border can be slow, we cache its results
+ // by widget type. Each bit in mBorderCacheValid says whether the
+ // corresponding entry in mBorderCache is valid.
+ mozilla::CSSIntMargin GetCachedWidgetBorder(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ GtkTextDirection aDirection);
+ uint8_t mBorderCacheValid[(MOZ_GTK_WIDGET_NODE_COUNT + 7) / 8];
+ mozilla::CSSIntMargin mBorderCache[MOZ_GTK_WIDGET_NODE_COUNT];
+};
+
+#endif
diff --git a/widget/gtk/nsPrintDialogGTK.cpp b/widget/gtk/nsPrintDialogGTK.cpp
new file mode 100644
index 0000000000..3885a94c24
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -0,0 +1,621 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+#include <stdlib.h>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Services.h"
+
+#include "MozContainer.h"
+#include "nsIPrintSettings.h"
+#include "nsIWidget.h"
+#include "nsPrintDialogGTK.h"
+#include "nsPrintSettingsGTK.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsIStringBundle.h"
+#include "nsIPrintSettingsService.h"
+#include "nsPIDOMWindow.h"
+#include "nsPrintfCString.h"
+#include "nsIGIOService.h"
+#include "nsServiceManagerUtils.h"
+#include "WidgetUtils.h"
+#include "WidgetUtilsGtk.h"
+#include "nsIObserverService.h"
+
+// for gdk_x11_window_get_xid
+#include <gdk/gdk.h>
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+#endif
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+
+// for dlsym
+#include <dlfcn.h>
+#include "MainThreadUtils.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+static const char header_footer_tags[][4] = {"", "&T", "&U", "&D", "&P", "&PT"};
+
+#define CUSTOM_VALUE_INDEX gint(ArrayLength(header_footer_tags))
+
+static GtkWindow* get_gtk_window_for_nsiwidget(nsIWidget* widget) {
+ return GTK_WINDOW(widget->GetNativeData(NS_NATIVE_SHELLWIDGET));
+}
+
+static void ShowCustomDialog(GtkComboBox* changed_box, gpointer user_data) {
+ if (gtk_combo_box_get_active(changed_box) != CUSTOM_VALUE_INDEX) {
+ g_object_set_data(G_OBJECT(changed_box), "previous-active",
+ GINT_TO_POINTER(gtk_combo_box_get_active(changed_box)));
+ return;
+ }
+
+ GtkWindow* printDialog = GTK_WINDOW(user_data);
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+ nsAutoString intlString;
+
+ printBundle->GetStringFromName("headerFooterCustom", intlString);
+ GtkWidget* prompt_dialog = gtk_dialog_new_with_buttons(
+ NS_ConvertUTF16toUTF8(intlString).get(), printDialog,
+ (GtkDialogFlags)(GTK_DIALOG_MODAL), GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
+ GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, nullptr);
+ gtk_dialog_set_default_response(GTK_DIALOG(prompt_dialog),
+ GTK_RESPONSE_ACCEPT);
+ gtk_dialog_set_alternative_button_order(
+ GTK_DIALOG(prompt_dialog), GTK_RESPONSE_ACCEPT, GTK_RESPONSE_REJECT, -1);
+
+ printBundle->GetStringFromName("customHeaderFooterPrompt", intlString);
+ GtkWidget* custom_label =
+ gtk_label_new(NS_ConvertUTF16toUTF8(intlString).get());
+ GtkWidget* custom_entry = gtk_entry_new();
+ GtkWidget* question_icon =
+ gtk_image_new_from_stock(GTK_STOCK_DIALOG_QUESTION, GTK_ICON_SIZE_DIALOG);
+
+ // To be convenient, prefill the textbox with the existing value, if any, and
+ // select it all so they can easily both edit it and type in a new one.
+ const char* current_text =
+ (const char*)g_object_get_data(G_OBJECT(changed_box), "custom-text");
+ if (current_text) {
+ gtk_entry_set_text(GTK_ENTRY(custom_entry), current_text);
+ gtk_editable_select_region(GTK_EDITABLE(custom_entry), 0, -1);
+ }
+ gtk_entry_set_activates_default(GTK_ENTRY(custom_entry), TRUE);
+
+ GtkWidget* custom_vbox = gtk_vbox_new(TRUE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_vbox), custom_entry, FALSE, FALSE,
+ 5); // Make entry 5px underneath label
+ GtkWidget* custom_hbox = gtk_hbox_new(FALSE, 2);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), question_icon, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(custom_hbox), custom_vbox, FALSE, FALSE,
+ 10); // Make question icon 10px away from content
+ gtk_container_set_border_width(GTK_CONTAINER(custom_hbox), 2);
+ gtk_widget_show_all(custom_hbox);
+
+ gtk_box_pack_start(
+ GTK_BOX(gtk_dialog_get_content_area(GTK_DIALOG(prompt_dialog))),
+ custom_hbox, FALSE, FALSE, 0);
+ gint diag_response = gtk_dialog_run(GTK_DIALOG(prompt_dialog));
+
+ if (diag_response == GTK_RESPONSE_ACCEPT) {
+ const gchar* response_text = gtk_entry_get_text(GTK_ENTRY(custom_entry));
+ g_object_set_data_full(G_OBJECT(changed_box), "custom-text",
+ strdup(response_text), (GDestroyNotify)free);
+ g_object_set_data(G_OBJECT(changed_box), "previous-active",
+ GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ } else {
+ // Go back to the previous index
+ gint previous_active = GPOINTER_TO_INT(
+ g_object_get_data(G_OBJECT(changed_box), "previous-active"));
+ gtk_combo_box_set_active(changed_box, previous_active);
+ }
+
+ gtk_widget_destroy(prompt_dialog);
+}
+
+class nsPrintDialogWidgetGTK {
+ public:
+ nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent, bool aHaveSelection,
+ nsIPrintSettings* aPrintSettings);
+ ~nsPrintDialogWidgetGTK() { gtk_widget_destroy(dialog); }
+ NS_ConvertUTF16toUTF8 GetUTF8FromBundle(const char* aKey);
+ gint Run();
+
+ nsresult ImportSettings(nsIPrintSettings* aNSSettings);
+ nsresult ExportSettings(nsIPrintSettings* aNSSettings);
+
+ private:
+ GtkWidget* dialog;
+ GtkWidget* shrink_to_fit_toggle;
+ GtkWidget* print_bg_colors_toggle;
+ GtkWidget* print_bg_images_toggle;
+ GtkWidget* selection_only_toggle;
+ GtkWidget* header_dropdown[3]; // {left, center, right}
+ GtkWidget* footer_dropdown[3];
+
+ nsCOMPtr<nsIStringBundle> printBundle;
+
+ bool useNativeSelection;
+
+ GtkWidget* ConstructHeaderFooterDropdown(const char16_t* currentString);
+ const char* OptionWidgetToString(GtkWidget* dropdown);
+
+ /* Code to copy between GTK and NS print settings structures.
+ * In the following,
+ * "Import" means to copy from NS to GTK
+ * "Export" means to copy from GTK to NS
+ */
+ void ExportHeaderFooter(nsIPrintSettings* aNS);
+};
+
+nsPrintDialogWidgetGTK::nsPrintDialogWidgetGTK(nsPIDOMWindowOuter* aParent,
+ bool aHaveSelection,
+ nsIPrintSettings* aSettings) {
+ nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(aParent);
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+
+ dialog = gtk_print_unix_dialog_new(GetUTF8FromBundle("printTitleGTK").get(),
+ gtkParent);
+
+ gtk_print_unix_dialog_set_manual_capabilities(
+ GTK_PRINT_UNIX_DIALOG(dialog),
+ GtkPrintCapabilities(
+ GTK_PRINT_CAPABILITY_COPIES | GTK_PRINT_CAPABILITY_COLLATE |
+ GTK_PRINT_CAPABILITY_REVERSE | GTK_PRINT_CAPABILITY_SCALE |
+ GTK_PRINT_CAPABILITY_GENERATE_PDF));
+
+ // The vast majority of magic numbers in this widget construction are padding.
+ // e.g. for the set_border_width below, 12px matches that of just about every
+ // other window.
+ GtkWidget* custom_options_tab = gtk_vbox_new(FALSE, 0);
+ gtk_container_set_border_width(GTK_CONTAINER(custom_options_tab), 12);
+ GtkWidget* tab_label =
+ gtk_label_new(GetUTF8FromBundle("optionsTabLabelGTK").get());
+
+ // Check buttons for shrink-to-fit and print selection
+ GtkWidget* check_buttons_container = gtk_vbox_new(TRUE, 2);
+ shrink_to_fit_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("shrinkToFit").get());
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), shrink_to_fit_toggle,
+ FALSE, FALSE, 0);
+
+ // GTK+2.18 and above allow us to add a "Selection" option to the main
+ // settings screen, rather than adding an option on a custom tab like we must
+ // do on older versions.
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 18)) {
+ useNativeSelection = true;
+ g_object_set(dialog, "support-selection", TRUE, "has-selection",
+ aHaveSelection, "embed-page-setup", TRUE, nullptr);
+ } else {
+ useNativeSelection = false;
+ selection_only_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("selectionOnly").get());
+ gtk_widget_set_sensitive(selection_only_toggle, aHaveSelection);
+ gtk_box_pack_start(GTK_BOX(check_buttons_container), selection_only_toggle,
+ FALSE, FALSE, 0);
+ }
+
+ // Check buttons for printing background
+ GtkWidget* appearance_buttons_container = gtk_vbox_new(TRUE, 2);
+ print_bg_colors_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("printBGColors").get());
+ print_bg_images_toggle = gtk_check_button_new_with_mnemonic(
+ GetUTF8FromBundle("printBGImages").get());
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
+ print_bg_colors_toggle, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_buttons_container),
+ print_bg_images_toggle, FALSE, FALSE, 0);
+
+ // "Appearance" options label, bold and center-aligned
+ GtkWidget* appearance_label = gtk_label_new(nullptr);
+ char* pangoMarkup = g_markup_printf_escaped(
+ "<b>%s</b>", GetUTF8FromBundle("printBGOptions").get());
+ gtk_label_set_markup(GTK_LABEL(appearance_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(appearance_label), 0, 0);
+
+ GtkWidget* appearance_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(appearance_container), 8, 0, 12, 0);
+ gtk_container_add(GTK_CONTAINER(appearance_container),
+ appearance_buttons_container);
+
+ GtkWidget* appearance_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher), appearance_label,
+ FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(appearance_vertical_squasher),
+ appearance_container, FALSE, FALSE, 0);
+
+ // "Header & Footer" options label, bold and center-aligned
+ GtkWidget* header_footer_label = gtk_label_new(nullptr);
+ pangoMarkup = g_markup_printf_escaped(
+ "<b>%s</b>", GetUTF8FromBundle("headerFooter").get());
+ gtk_label_set_markup(GTK_LABEL(header_footer_label), pangoMarkup);
+ g_free(pangoMarkup);
+ gtk_misc_set_alignment(GTK_MISC(header_footer_label), 0, 0);
+
+ GtkWidget* header_footer_container = gtk_alignment_new(0, 0, 0, 0);
+ gtk_alignment_set_padding(GTK_ALIGNMENT(header_footer_container), 8, 0, 12,
+ 0);
+
+ // --- Table for making the header and footer options ---
+ GtkWidget* header_footer_table = gtk_table_new(3, 3, FALSE); // 3x3 table
+ nsString header_footer_str[3];
+
+ aSettings->GetHeaderStrLeft(header_footer_str[0]);
+ aSettings->GetHeaderStrCenter(header_footer_str[1]);
+ aSettings->GetHeaderStrRight(header_footer_str[2]);
+
+ for (unsigned int i = 0; i < ArrayLength(header_dropdown); i++) {
+ header_dropdown[i] =
+ ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ // Those 4 magic numbers in the middle provide the position in the table.
+ // The last two numbers mean 2 px padding on every side.
+ gtk_table_attach(GTK_TABLE(header_footer_table), header_dropdown[i], i,
+ (i + 1), 0, 1, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+
+ const char labelKeys[][7] = {"left", "center", "right"};
+ for (unsigned int i = 0; i < ArrayLength(labelKeys); i++) {
+ gtk_table_attach(GTK_TABLE(header_footer_table),
+ gtk_label_new(GetUTF8FromBundle(labelKeys[i]).get()), i,
+ (i + 1), 1, 2, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+
+ aSettings->GetFooterStrLeft(header_footer_str[0]);
+ aSettings->GetFooterStrCenter(header_footer_str[1]);
+ aSettings->GetFooterStrRight(header_footer_str[2]);
+
+ for (unsigned int i = 0; i < ArrayLength(footer_dropdown); i++) {
+ footer_dropdown[i] =
+ ConstructHeaderFooterDropdown(header_footer_str[i].get());
+ gtk_table_attach(GTK_TABLE(header_footer_table), footer_dropdown[i], i,
+ (i + 1), 2, 3, (GtkAttachOptions)0, (GtkAttachOptions)0, 2,
+ 2);
+ }
+ // ---
+
+ gtk_container_add(GTK_CONTAINER(header_footer_container),
+ header_footer_table);
+
+ GtkWidget* header_footer_vertical_squasher = gtk_vbox_new(FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
+ header_footer_label, FALSE, FALSE, 0);
+ gtk_box_pack_start(GTK_BOX(header_footer_vertical_squasher),
+ header_footer_container, FALSE, FALSE, 0);
+
+ // Construction of everything
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), check_buttons_container,
+ FALSE, FALSE, 10); // 10px padding
+ gtk_box_pack_start(GTK_BOX(custom_options_tab), appearance_vertical_squasher,
+ FALSE, FALSE, 10);
+ gtk_box_pack_start(GTK_BOX(custom_options_tab),
+ header_footer_vertical_squasher, FALSE, FALSE, 0);
+
+ gtk_print_unix_dialog_add_custom_tab(GTK_PRINT_UNIX_DIALOG(dialog),
+ custom_options_tab, tab_label);
+ gtk_widget_show_all(custom_options_tab);
+}
+
+NS_ConvertUTF16toUTF8 nsPrintDialogWidgetGTK::GetUTF8FromBundle(
+ const char* aKey) {
+ nsAutoString intlString;
+ printBundle->GetStringFromName(aKey, intlString);
+ return NS_ConvertUTF16toUTF8(
+ intlString); // Return the actual object so we don't lose reference
+}
+
+const char* nsPrintDialogWidgetGTK::OptionWidgetToString(GtkWidget* dropdown) {
+ gint index = gtk_combo_box_get_active(GTK_COMBO_BOX(dropdown));
+
+ NS_ASSERTION(index <= CUSTOM_VALUE_INDEX,
+ "Index of dropdown is higher than expected!");
+
+ if (index == CUSTOM_VALUE_INDEX)
+ return (const char*)g_object_get_data(G_OBJECT(dropdown), "custom-text");
+ return header_footer_tags[index];
+}
+
+gint nsPrintDialogWidgetGTK::Run() {
+ const gint response = gtk_dialog_run(GTK_DIALOG(dialog));
+ gtk_widget_hide(dialog);
+ return response;
+}
+
+void nsPrintDialogWidgetGTK::ExportHeaderFooter(nsIPrintSettings* aNS) {
+ const char* header_footer_str;
+ header_footer_str = OptionWidgetToString(header_dropdown[0]);
+ aNS->SetHeaderStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(header_dropdown[1]);
+ aNS->SetHeaderStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(header_dropdown[2]);
+ aNS->SetHeaderStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[0]);
+ aNS->SetFooterStrLeft(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[1]);
+ aNS->SetFooterStrCenter(NS_ConvertUTF8toUTF16(header_footer_str));
+
+ header_footer_str = OptionWidgetToString(footer_dropdown[2]);
+ aNS->SetFooterStrRight(NS_ConvertUTF8toUTF16(header_footer_str));
+}
+
+nsresult nsPrintDialogWidgetGTK::ImportSettings(nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
+
+ GtkPrintSettings* settings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* setup = aNSSettingsGTK->GetGtkPageSetup();
+
+ // Set our custom fields:
+
+ bool geckoBool;
+ aNSSettings->GetShrinkToFit(&geckoBool);
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle),
+ geckoBool);
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_colors_toggle),
+ aNSSettings->GetPrintBGColors());
+
+ gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(print_bg_images_toggle),
+ aNSSettings->GetPrintBGImages());
+
+ // Temporarily set the pages-per-sheet on the GtkPrintSettings:
+ int32_t pagesPerSide;
+ aNSSettings->GetNumPagesPerSheet(&pagesPerSide);
+ gtk_print_settings_set_number_up(settings, pagesPerSide);
+
+ gtk_print_unix_dialog_set_settings(GTK_PRINT_UNIX_DIALOG(dialog), settings);
+ gtk_print_unix_dialog_set_page_setup(GTK_PRINT_UNIX_DIALOG(dialog), setup);
+
+ return NS_OK;
+}
+
+nsresult nsPrintDialogWidgetGTK::ExportSettings(nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ GtkPrintSettings* settings =
+ gtk_print_unix_dialog_get_settings(GTK_PRINT_UNIX_DIALOG(dialog));
+ GtkPageSetup* setup =
+ gtk_print_unix_dialog_get_page_setup(GTK_PRINT_UNIX_DIALOG(dialog));
+ GtkPrinter* printer =
+ gtk_print_unix_dialog_get_selected_printer(GTK_PRINT_UNIX_DIALOG(dialog));
+ if (settings && setup && printer) {
+ ExportHeaderFooter(aNSSettings);
+
+ aNSSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+
+ // Print-to-file is true by default. This must be turned off or else
+ // printing won't occur! (We manually copy the spool file when this flag is
+ // set, because we love our embedders) Even if it is print-to-file in GTK's
+ // case, GTK does The Right Thing when we send the job.
+ aNSSettings->SetOutputDestination(
+ nsIPrintSettings::kOutputDestinationPrinter);
+
+ aNSSettings->SetShrinkToFit(
+ gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(shrink_to_fit_toggle)));
+
+ aNSSettings->SetPrintBGColors(gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(print_bg_colors_toggle)));
+ aNSSettings->SetPrintBGImages(gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(print_bg_images_toggle)));
+
+ // Move any pages-per-sheet value from the GtkPrintSettings to our
+ // nsIPrintSettings. (We handle pages-per-sheet internally and don't want
+ // the native Linux printing code to doubly apply that value!)
+ int32_t pagesPerSide = gtk_print_settings_get_number_up(settings);
+ gtk_print_settings_set_number_up(settings, 1);
+ aNSSettings->SetNumPagesPerSheet(pagesPerSide);
+
+ // Try to save native settings in the session object
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (aNSSettingsGTK) {
+ aNSSettingsGTK->SetGtkPrintSettings(settings);
+ aNSSettingsGTK->SetGtkPageSetup(setup);
+ aNSSettingsGTK->SetGtkPrinter(printer);
+ bool printSelectionOnly;
+ if (useNativeSelection) {
+ _GtkPrintPages pageSetting =
+ (_GtkPrintPages)gtk_print_settings_get_print_pages(settings);
+ printSelectionOnly = (pageSetting == _GTK_PRINT_PAGES_SELECTION);
+ } else {
+ printSelectionOnly = gtk_toggle_button_get_active(
+ GTK_TOGGLE_BUTTON(selection_only_toggle));
+ }
+ aNSSettingsGTK->SetPrintSelectionOnly(printSelectionOnly);
+ }
+ }
+
+ if (settings) g_object_unref(settings);
+ return NS_OK;
+}
+
+GtkWidget* nsPrintDialogWidgetGTK::ConstructHeaderFooterDropdown(
+ const char16_t* currentString) {
+ GtkWidget* dropdown = gtk_combo_box_text_new();
+ const char hf_options[][22] = {"headerFooterBlank", "headerFooterTitle",
+ "headerFooterURL", "headerFooterDate",
+ "headerFooterPage", "headerFooterPageTotal",
+ "headerFooterCustom"};
+
+ for (unsigned int i = 0; i < ArrayLength(hf_options); i++) {
+ gtk_combo_box_text_append(GTK_COMBO_BOX_TEXT(dropdown), nullptr,
+ GetUTF8FromBundle(hf_options[i]).get());
+ }
+
+ bool shouldBeCustom = true;
+ NS_ConvertUTF16toUTF8 currentStringUTF8(currentString);
+
+ for (unsigned int i = 0; i < ArrayLength(header_footer_tags); i++) {
+ if (!strcmp(currentStringUTF8.get(), header_footer_tags[i])) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), i);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active",
+ GINT_TO_POINTER(i));
+ shouldBeCustom = false;
+ break;
+ }
+ }
+
+ if (shouldBeCustom) {
+ gtk_combo_box_set_active(GTK_COMBO_BOX(dropdown), CUSTOM_VALUE_INDEX);
+ g_object_set_data(G_OBJECT(dropdown), "previous-active",
+ GINT_TO_POINTER(CUSTOM_VALUE_INDEX));
+ char* custom_string = strdup(currentStringUTF8.get());
+ g_object_set_data_full(G_OBJECT(dropdown), "custom-text", custom_string,
+ (GDestroyNotify)free);
+ }
+
+ g_signal_connect(dropdown, "changed", (GCallback)ShowCustomDialog, dialog);
+ return dropdown;
+}
+
+NS_IMPL_ISUPPORTS(nsPrintDialogServiceGTK, nsIPrintDialogService)
+
+nsPrintDialogServiceGTK::nsPrintDialogServiceGTK() = default;
+
+nsPrintDialogServiceGTK::~nsPrintDialogServiceGTK() = default;
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Init() { return NS_OK; }
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::ShowPrintDialog(mozIDOMWindowProxy* aParent,
+ bool aHaveSelection,
+ nsIPrintSettings* aSettings) {
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aSettings, "aSettings must not be null");
+
+ nsPrintDialogWidgetGTK printDialog(nsPIDOMWindowOuter::From(aParent),
+ aHaveSelection, aSettings);
+ nsresult rv = printDialog.ImportSettings(aSettings);
+
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ const gint response = printDialog.Run();
+
+ // Handle the result
+ switch (response) {
+ case GTK_RESPONSE_OK: // Proceed
+ rv = printDialog.ExportSettings(aSettings);
+ break;
+
+ case GTK_RESPONSE_CANCEL:
+ case GTK_RESPONSE_CLOSE:
+ case GTK_RESPONSE_DELETE_EVENT:
+ case GTK_RESPONSE_NONE:
+ rv = NS_ERROR_ABORT;
+ break;
+
+ case GTK_RESPONSE_APPLY: // Print preview
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::ShowPageSetupDialog(mozIDOMWindowProxy* aParent,
+ nsIPrintSettings* aNSSettings) {
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aNSSettings, "aSettings must not be null");
+ NS_ENSURE_TRUE(aNSSettings, NS_ERROR_FAILURE);
+
+ nsCOMPtr<nsIWidget> widget =
+ WidgetUtils::DOMWindowToWidget(nsPIDOMWindowOuter::From(aParent));
+ NS_ASSERTION(widget, "Need a widget for dialog to be modal.");
+ GtkWindow* gtkParent = get_gtk_window_for_nsiwidget(widget);
+ NS_ASSERTION(gtkParent, "Need a GTK window for dialog to be modal.");
+
+ nsCOMPtr<nsPrintSettingsGTK> aNSSettingsGTK(do_QueryInterface(aNSSettings));
+ if (!aNSSettingsGTK) return NS_ERROR_FAILURE;
+
+ // We need to init the prefs here because aNSSettings in its current form is a
+ // dummy in both uses of the word
+ nsCOMPtr<nsIPrintSettingsService> psService =
+ do_GetService("@mozilla.org/gfx/printsettings-service;1");
+ if (psService) {
+ nsString printName;
+ aNSSettings->GetPrinterName(printName);
+ if (printName.IsVoid()) {
+ psService->GetLastUsedPrinterName(printName);
+ aNSSettings->SetPrinterName(printName);
+ }
+ psService->InitPrintSettingsFromPrefs(aNSSettings, true,
+ nsIPrintSettings::kInitSaveAll);
+ }
+
+ // Frustratingly, gtk_print_run_page_setup_dialog doesn't tell us whether
+ // the user cancelled or confirmed the dialog! So to avoid needlessly
+ // refreshing the preview when Page Setup was cancelled, we compare the
+ // serializations of old and new settings; if they're the same, bail out.
+ GtkPrintSettings* gtkSettings = aNSSettingsGTK->GetGtkPrintSettings();
+ GtkPageSetup* oldPageSetup = aNSSettingsGTK->GetGtkPageSetup();
+ GKeyFile* oldKeyFile = g_key_file_new();
+ gtk_page_setup_to_key_file(oldPageSetup, oldKeyFile, nullptr);
+ gsize oldLength;
+ gchar* oldData = g_key_file_to_data(oldKeyFile, &oldLength, nullptr);
+ g_key_file_free(oldKeyFile);
+
+ GtkPageSetup* newPageSetup =
+ gtk_print_run_page_setup_dialog(gtkParent, oldPageSetup, gtkSettings);
+
+ GKeyFile* newKeyFile = g_key_file_new();
+ gtk_page_setup_to_key_file(newPageSetup, newKeyFile, nullptr);
+ gsize newLength;
+ gchar* newData = g_key_file_to_data(newKeyFile, &newLength, nullptr);
+ g_key_file_free(newKeyFile);
+
+ bool unchanged =
+ (oldLength == newLength && !memcmp(oldData, newData, oldLength));
+ g_free(oldData);
+ g_free(newData);
+ if (unchanged) {
+ g_object_unref(newPageSetup);
+ return NS_ERROR_ABORT;
+ }
+
+ aNSSettingsGTK->SetGtkPageSetup(newPageSetup);
+
+ // Now newPageSetup has a refcount of 2 (SetGtkPageSetup will addref), put it
+ // to 1 so if this gets replaced we don't leak.
+ g_object_unref(newPageSetup);
+
+ if (psService)
+ psService->MaybeSavePrintSettingsToPrefs(
+ aNSSettings, nsIPrintSettings::kInitSaveOrientation |
+ nsIPrintSettings::kInitSavePaperSize |
+ nsIPrintSettings::kInitSaveUnwriteableMargins);
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintDialogGTK.h b/widget/gtk/nsPrintDialogGTK.h
new file mode 100644
index 0000000000..4388962858
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.h
@@ -0,0 +1,35 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintDialog_h__
+#define nsPrintDialog_h__
+
+#include "nsIPrintDialogService.h"
+
+class nsIPrintSettings;
+
+// Copy the print pages enum here because not all versions
+// have SELECTION, which we will use
+typedef enum {
+ _GTK_PRINT_PAGES_ALL,
+ _GTK_PRINT_PAGES_CURRENT,
+ _GTK_PRINT_PAGES_RANGES,
+ _GTK_PRINT_PAGES_SELECTION
+} _GtkPrintPages;
+
+class nsPrintDialogServiceGTK final : public nsIPrintDialogService {
+ virtual ~nsPrintDialogServiceGTK();
+
+ public:
+ nsPrintDialogServiceGTK();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIPRINTDIALOGSERVICE
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintDialogServiceGTK,
+ NS_IPRINTDIALOGSERVICE_IID)
+
+#endif
diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp
new file mode 100644
index 0000000000..a101f44fcb
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -0,0 +1,681 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsGTK.h"
+#include "nsIFile.h"
+#include "nsNetUtil.h"
+#include <stdlib.h>
+#include <algorithm>
+
+// These constants are the the strings that GTK expects as key-value pairs for
+// setting CUPS duplex modes. These are not universal to all CUPS systems, which
+// is why they are local to this file.
+static constexpr gchar kCupsDuplex[] = "cups-Duplex";
+static constexpr gchar kCupsDuplexNone[] = "None";
+static constexpr gchar kCupsDuplexNoTumble[] = "DuplexNoTumble";
+static constexpr gchar kCupsDuplexTumble[] = "DuplexTumble";
+
+static GtkPaperSize* moz_gtk_paper_size_copy_to_new_custom(
+ GtkPaperSize* oldPaperSize) {
+ // We make a "custom-ified" copy of the paper size so it can be changed later.
+ return gtk_paper_size_new_custom(
+ gtk_paper_size_get_name(oldPaperSize),
+ gtk_paper_size_get_display_name(oldPaperSize),
+ gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH),
+ gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH), GTK_UNIT_INCH);
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsPrintSettingsGTK, nsPrintSettings,
+ nsPrintSettingsGTK)
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK()
+ : mPageSetup(nullptr), mPrintSettings(nullptr), mGTKPrinter(nullptr) {
+ // The aim here is to set up the objects enough that silent printing works
+ // well. These will be replaced anyway if the print dialog is used.
+ mPrintSettings = gtk_print_settings_new();
+ GtkPageSetup* pageSetup = gtk_page_setup_new();
+ SetGtkPageSetup(pageSetup);
+ g_object_unref(pageSetup);
+
+ SetOutputFormat(nsIPrintSettings::kOutputFormatNative);
+}
+
+already_AddRefed<nsIPrintSettings> CreatePlatformPrintSettings(
+ const mozilla::PrintSettingsInitializer& aSettings) {
+ RefPtr<nsPrintSettings> settings = new nsPrintSettingsGTK();
+ settings->InitWithInitializer(aSettings);
+ settings->SetDefaultFileName();
+ return settings.forget();
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::~nsPrintSettingsGTK() {
+ if (mPageSetup) {
+ g_object_unref(mPageSetup);
+ mPageSetup = nullptr;
+ }
+ if (mPrintSettings) {
+ g_object_unref(mPrintSettings);
+ mPrintSettings = nullptr;
+ }
+ if (mGTKPrinter) {
+ g_object_unref(mGTKPrinter);
+ mGTKPrinter = nullptr;
+ }
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK::nsPrintSettingsGTK(const nsPrintSettingsGTK& aPS)
+ : mPageSetup(nullptr), mPrintSettings(nullptr), mGTKPrinter(nullptr) {
+ *this = aPS;
+}
+
+/** ---------------------------------------------------
+ */
+nsPrintSettingsGTK& nsPrintSettingsGTK::operator=(
+ const nsPrintSettingsGTK& rhs) {
+ if (this == &rhs) {
+ return *this;
+ }
+
+ nsPrintSettings::operator=(rhs);
+
+ if (mPageSetup) g_object_unref(mPageSetup);
+ mPageSetup = gtk_page_setup_copy(rhs.mPageSetup);
+ // NOTE: No need to re-initialize mUnwriteableMargin here (even
+ // though mPageSetup is changing). It'll be copied correctly by
+ // nsPrintSettings::operator=.
+
+ if (mPrintSettings) g_object_unref(mPrintSettings);
+ mPrintSettings = gtk_print_settings_copy(rhs.mPrintSettings);
+
+ if (mGTKPrinter) g_object_unref(mGTKPrinter);
+
+ if (rhs.mGTKPrinter) {
+ g_object_ref(rhs.mGTKPrinter);
+ }
+ mGTKPrinter = rhs.mGTKPrinter;
+
+ return *this;
+}
+
+/** -------------------------------------------
+ */
+nsresult nsPrintSettingsGTK::_Clone(nsIPrintSettings** _retval) {
+ NS_ENSURE_ARG_POINTER(_retval);
+ *_retval = nullptr;
+
+ nsPrintSettingsGTK* newSettings = new nsPrintSettingsGTK(*this);
+ if (!newSettings) return NS_ERROR_FAILURE;
+ *_retval = newSettings;
+ NS_ADDREF(*_retval);
+ return NS_OK;
+}
+
+/** -------------------------------------------
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::_Assign(nsIPrintSettings* aPS) {
+ nsPrintSettingsGTK* printSettingsGTK = static_cast<nsPrintSettingsGTK*>(aPS);
+ if (!printSettingsGTK) return NS_ERROR_UNEXPECTED;
+ *this = *printSettingsGTK;
+ return NS_OK;
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPageSetup(GtkPageSetup* aPageSetup) {
+ if (mPageSetup) g_object_unref(mPageSetup);
+
+ mPageSetup = (GtkPageSetup*)g_object_ref(aPageSetup);
+ InitUnwriteableMargin();
+
+ // If the paper size is not custom, then we make a custom copy of the
+ // GtkPaperSize, so it can be mutable. If a GtkPaperSize wasn't made as
+ // custom, its properties are immutable.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(aPageSetup);
+ if (!gtk_paper_size_is_custom(paperSize)) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ }
+ SaveNewPageSize();
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPrintSettings(GtkPrintSettings* aPrintSettings) {
+ if (mPrintSettings) g_object_unref(mPrintSettings);
+
+ mPrintSettings = (GtkPrintSettings*)g_object_ref(aPrintSettings);
+
+ GtkPaperSize* paperSize = gtk_print_settings_get_paper_size(aPrintSettings);
+ if (paperSize) {
+ GtkPaperSize* customPaperSize =
+ moz_gtk_paper_size_copy_to_new_custom(paperSize);
+ gtk_paper_size_free(paperSize);
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ } else {
+ // paperSize was null, and so we add the paper size in the GtkPageSetup to
+ // the settings.
+ SaveNewPageSize();
+ }
+}
+
+/** ---------------------------------------------------
+ */
+void nsPrintSettingsGTK::SetGtkPrinter(GtkPrinter* aPrinter) {
+ if (mGTKPrinter) g_object_unref(mGTKPrinter);
+
+ mGTKPrinter = (GtkPrinter*)g_object_ref(aPrinter);
+}
+
+NS_IMETHODIMP nsPrintSettingsGTK::GetOutputFormat(int16_t* aOutputFormat) {
+ NS_ENSURE_ARG_POINTER(aOutputFormat);
+
+ int16_t format;
+ nsresult rv = nsPrintSettings::GetOutputFormat(&format);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ if (format == nsIPrintSettings::kOutputFormatNative &&
+ GTK_IS_PRINTER(mGTKPrinter)) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ }
+
+ *aOutputFormat = format;
+ return NS_OK;
+}
+
+/**
+ * Reimplementation of nsPrintSettings functions so that we get the values
+ * from the GTK objects rather than our own variables.
+ */
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPageRanges(const nsTArray<int32_t>& aRanges) {
+ if (aRanges.Length() % 2 != 0) {
+ return NS_ERROR_FAILURE;
+ }
+
+ gtk_print_settings_set_print_pages(
+ mPrintSettings,
+ aRanges.IsEmpty() ? GTK_PRINT_PAGES_ALL : GTK_PRINT_PAGES_RANGES);
+
+ nsTArray<GtkPageRange> ranges;
+ ranges.SetCapacity(aRanges.Length() / 2);
+ for (size_t i = 0; i < aRanges.Length(); i += 2) {
+ GtkPageRange* gtkRange = ranges.AppendElement();
+ gtkRange->start = aRanges[i] - 1;
+ gtkRange->end = aRanges[i + 1] - 1;
+ }
+
+ gtk_print_settings_set_page_ranges(mPrintSettings, ranges.Elements(),
+ ranges.Length());
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintReversed(bool* aPrintReversed) {
+ *aPrintReversed = gtk_print_settings_get_reverse(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintReversed(bool aPrintReversed) {
+ gtk_print_settings_set_reverse(mPrintSettings, aPrintReversed);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrintInColor(bool* aPrintInColor) {
+ *aPrintInColor = gtk_print_settings_get_use_color(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrintInColor(bool aPrintInColor) {
+ gtk_print_settings_set_use_color(mPrintSettings, aPrintInColor);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetOrientation(int32_t* aOrientation) {
+ NS_ENSURE_ARG_POINTER(aOrientation);
+
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+ switch (gtkOrient) {
+ case GTK_PAGE_ORIENTATION_LANDSCAPE:
+ case GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE:
+ *aOrientation = kLandscapeOrientation;
+ break;
+
+ case GTK_PAGE_ORIENTATION_PORTRAIT:
+ case GTK_PAGE_ORIENTATION_REVERSE_PORTRAIT:
+ default:
+ *aOrientation = kPortraitOrientation;
+ }
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetOrientation(int32_t aOrientation) {
+ GtkPageOrientation gtkOrient;
+ if (aOrientation == kLandscapeOrientation)
+ gtkOrient = GTK_PAGE_ORIENTATION_LANDSCAPE;
+ else
+ gtkOrient = GTK_PAGE_ORIENTATION_PORTRAIT;
+
+ gtk_print_settings_set_orientation(mPrintSettings, gtkOrient);
+ gtk_page_setup_set_orientation(mPageSetup, gtkOrient);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetToFileName(nsAString& aToFileName) {
+ // Get the gtk output filename
+ const char* gtk_output_uri =
+ gtk_print_settings_get(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI);
+ if (!gtk_output_uri) {
+ aToFileName = mToFileName;
+ return NS_OK;
+ }
+
+ // Convert to an nsIFile
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_GetFileFromURLSpec(nsDependentCString(gtk_output_uri),
+ getter_AddRefs(file));
+ if (NS_FAILED(rv)) return rv;
+
+ // Extract the path
+ return file->GetPath(aToFileName);
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetToFileName(const nsAString& aToFileName) {
+ if (aToFileName.IsEmpty()) {
+ mToFileName.SetLength(0);
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ nullptr);
+ return NS_OK;
+ }
+
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_FILE_FORMAT,
+ "pdf");
+
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = NS_NewLocalFile(aToFileName, true, getter_AddRefs(file));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Convert the nsIFile to a URL
+ nsAutoCString url;
+ rv = NS_GetURLSpecFromFile(file, url);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ gtk_print_settings_set(mPrintSettings, GTK_PRINT_SETTINGS_OUTPUT_URI,
+ url.get());
+ mToFileName = aToFileName;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPrinterName(nsAString& aPrinter) {
+ const char* gtkPrintName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!gtkPrintName) {
+ if (GTK_IS_PRINTER(mGTKPrinter)) {
+ gtkPrintName = gtk_printer_get_name(mGTKPrinter);
+ } else {
+ // This mimics what nsPrintSettingsImpl does when we try to Get before we
+ // Set
+ aPrinter.Truncate();
+ return NS_OK;
+ }
+ }
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(gtkPrintName), aPrinter);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPrinterName(const nsAString& aPrinter) {
+ NS_ConvertUTF16toUTF8 gtkPrinter(aPrinter);
+
+ if (StringBeginsWith(gtkPrinter, "CUPS/"_ns)) {
+ // Strip off "CUPS/"; GTK might recognize the rest
+ gtkPrinter.Cut(0, strlen("CUPS/"));
+ }
+
+ // Give mPrintSettings the passed-in printer name if either...
+ // - it has no printer name stored yet
+ // - it has an existing printer name that's different from
+ // the name passed to this function.
+ const char* oldPrinterName = gtk_print_settings_get_printer(mPrintSettings);
+ if (!oldPrinterName || !gtkPrinter.Equals(oldPrinterName)) {
+ mIsInitedFromPrinter = false;
+ mIsInitedFromPrefs = false;
+ gtk_print_settings_set_printer(mPrintSettings, gtkPrinter.get());
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetNumCopies(int32_t* aNumCopies) {
+ NS_ENSURE_ARG_POINTER(aNumCopies);
+ *aNumCopies = gtk_print_settings_get_n_copies(mPrintSettings);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetNumCopies(int32_t aNumCopies) {
+ gtk_print_settings_set_n_copies(mPrintSettings, aNumCopies);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetScaling(double* aScaling) {
+ *aScaling = gtk_print_settings_get_scale(mPrintSettings) / 100.0;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetScaling(double aScaling) {
+ gtk_print_settings_set_scale(mPrintSettings, aScaling * 100.0);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperId(nsAString& aPaperId) {
+ const gchar* name =
+ gtk_paper_size_get_name(gtk_page_setup_get_paper_size(mPageSetup));
+ CopyUTF8toUTF16(mozilla::MakeStringSpan(name), aPaperId);
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperId(const nsAString& aPaperId) {
+ NS_ConvertUTF16toUTF8 gtkPaperName(aPaperId);
+
+ // Convert these Gecko names to GTK names
+ // XXX (jfkthame): is this still relevant?
+ if (gtkPaperName.EqualsIgnoreCase("letter"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LETTER);
+ else if (gtkPaperName.EqualsIgnoreCase("legal"))
+ gtkPaperName.AssignLiteral(GTK_PAPER_NAME_LEGAL);
+
+ GtkPaperSize* oldPaperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gdouble width = gtk_paper_size_get_width(oldPaperSize, GTK_UNIT_INCH);
+ gdouble height = gtk_paper_size_get_height(oldPaperSize, GTK_UNIT_INCH);
+
+ // Try to get the display name from the name so our paper size fits in the
+ // Page Setup dialog.
+ GtkPaperSize* paperSize = gtk_paper_size_new(gtkPaperName.get());
+ GtkPaperSize* customPaperSize = gtk_paper_size_new_custom(
+ gtkPaperName.get(), gtk_paper_size_get_display_name(paperSize), width,
+ height, GTK_UNIT_INCH);
+ gtk_paper_size_free(paperSize);
+
+ gtk_page_setup_set_paper_size(mPageSetup, customPaperSize);
+ gtk_paper_size_free(customPaperSize);
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+GtkUnit nsPrintSettingsGTK::GetGTKUnit(int16_t aGeckoUnit) {
+ if (aGeckoUnit == kPaperSizeMillimeters)
+ return GTK_UNIT_MM;
+ else
+ return GTK_UNIT_INCH;
+}
+
+void nsPrintSettingsGTK::SaveNewPageSize() {
+ gtk_print_settings_set_paper_size(mPrintSettings,
+ gtk_page_setup_get_paper_size(mPageSetup));
+}
+
+void nsPrintSettingsGTK::InitUnwriteableMargin() {
+ mUnwriteableMargin.SizeTo(
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_top_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_right_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_bottom_margin(mPageSetup, GTK_UNIT_INCH)),
+ NS_INCHES_TO_INT_TWIPS(
+ gtk_page_setup_get_left_margin(mPageSetup, GTK_UNIT_INCH)));
+}
+
+/**
+ * NOTE: Need a custom set of SetUnwriteableMargin functions, because
+ * whenever we change mUnwriteableMargin, we must pass the change
+ * down to our GTKPageSetup object. (This is needed in order for us
+ * to give the correct default values in nsPrintDialogGTK.)
+ *
+ * It's important that the following functions pass
+ * mUnwriteableMargin values rather than aUnwriteableMargin values
+ * to gtk_page_setup_set_[blank]_margin, because the two may not be
+ * the same. (Specifically, negative values of aUnwriteableMargin
+ * are ignored by the nsPrintSettings::SetUnwriteableMargin functions.)
+ */
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginInTwips(
+ nsIntMargin& aUnwriteableMargin) {
+ nsPrintSettings::SetUnwriteableMarginInTwips(aUnwriteableMargin);
+ gtk_page_setup_set_top_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ gtk_page_setup_set_left_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ gtk_page_setup_set_bottom_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ gtk_page_setup_set_right_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginTop(double aUnwriteableMarginTop) {
+ nsPrintSettings::SetUnwriteableMarginTop(aUnwriteableMarginTop);
+ gtk_page_setup_set_top_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.top), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) {
+ nsPrintSettings::SetUnwriteableMarginLeft(aUnwriteableMarginLeft);
+ gtk_page_setup_set_left_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.left), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginBottom(
+ double aUnwriteableMarginBottom) {
+ nsPrintSettings::SetUnwriteableMarginBottom(aUnwriteableMarginBottom);
+ gtk_page_setup_set_bottom_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.bottom), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetUnwriteableMarginRight(double aUnwriteableMarginRight) {
+ nsPrintSettings::SetUnwriteableMarginRight(aUnwriteableMarginRight);
+ gtk_page_setup_set_right_margin(
+ mPageSetup, NS_TWIPS_TO_INCHES(mUnwriteableMargin.right), GTK_UNIT_INCH);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperWidth(double* aPaperWidth) {
+ NS_ENSURE_ARG_POINTER(aPaperWidth);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperWidth =
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperWidth(double aPaperWidth) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize, aPaperWidth,
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPaperHeight(double* aPaperHeight) {
+ NS_ENSURE_ARG_POINTER(aPaperHeight);
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ *aPaperHeight =
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit));
+ return NS_OK;
+}
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperHeight(double aPaperHeight) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ aPaperHeight, GetGTKUnit(mPaperSizeUnit));
+ SaveNewPageSize();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetPaperSizeUnit(int16_t aPaperSizeUnit) {
+ // Convert units internally. e.g. they might have set the values while we're
+ // still in mm but they change to inch just afterwards, expecting that their
+ // sizes are in inches.
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ gtk_paper_size_set_size(
+ paperSize,
+ gtk_paper_size_get_width(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ gtk_paper_size_get_height(paperSize, GetGTKUnit(mPaperSizeUnit)),
+ GetGTKUnit(aPaperSizeUnit));
+ SaveNewPageSize();
+
+ mPaperSizeUnit = aPaperSizeUnit;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetEffectivePageSize(double* aWidth, double* aHeight) {
+ GtkPaperSize* paperSize = gtk_page_setup_get_paper_size(mPageSetup);
+ if (mPaperSizeUnit == kPaperSizeInches) {
+ *aWidth =
+ NS_INCHES_TO_TWIPS(gtk_paper_size_get_width(paperSize, GTK_UNIT_INCH));
+ *aHeight =
+ NS_INCHES_TO_TWIPS(gtk_paper_size_get_height(paperSize, GTK_UNIT_INCH));
+ } else {
+ MOZ_ASSERT(mPaperSizeUnit == kPaperSizeMillimeters,
+ "unexpected paper size unit");
+ *aWidth = NS_MILLIMETERS_TO_TWIPS(
+ gtk_paper_size_get_width(paperSize, GTK_UNIT_MM));
+ *aHeight = NS_MILLIMETERS_TO_TWIPS(
+ gtk_paper_size_get_height(paperSize, GTK_UNIT_MM));
+ }
+ GtkPageOrientation gtkOrient = gtk_page_setup_get_orientation(mPageSetup);
+
+ if (gtkOrient == GTK_PAGE_ORIENTATION_LANDSCAPE ||
+ gtkOrient == GTK_PAGE_ORIENTATION_REVERSE_LANDSCAPE) {
+ double temp = *aWidth;
+ *aWidth = *aHeight;
+ *aHeight = temp;
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetPageRanges(nsTArray<int32_t>& aPages) {
+ GtkPrintPages gtkRange = gtk_print_settings_get_print_pages(mPrintSettings);
+ if (gtkRange != GTK_PRINT_PAGES_RANGES) {
+ aPages.Clear();
+ return NS_OK;
+ }
+
+ gint ctRanges;
+ GtkPageRange* lstRanges =
+ gtk_print_settings_get_page_ranges(mPrintSettings, &ctRanges);
+
+ aPages.Clear();
+
+ for (gint i = 0; i < ctRanges; i++) {
+ aPages.AppendElement(lstRanges[i].start + 1);
+ aPages.AppendElement(lstRanges[i].end + 1);
+ }
+
+ g_free(lstRanges);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetResolution(int32_t* aResolution) {
+ *aResolution = gtk_print_settings_get_resolution(mPrintSettings);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetResolution(int32_t aResolution) {
+ gtk_print_settings_set_resolution(mPrintSettings, aResolution);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::GetDuplex(int32_t* aDuplex) {
+ NS_ENSURE_ARG_POINTER(aDuplex);
+
+ // Default to DuplexNone.
+ *aDuplex = kDuplexNone;
+
+ if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_DUPLEX)) {
+ return NS_OK;
+ }
+
+ switch (gtk_print_settings_get_duplex(mPrintSettings)) {
+ case GTK_PRINT_DUPLEX_SIMPLEX:
+ *aDuplex = kDuplexNone;
+ break;
+ case GTK_PRINT_DUPLEX_HORIZONTAL:
+ *aDuplex = kDuplexFlipOnLongEdge;
+ break;
+ case GTK_PRINT_DUPLEX_VERTICAL:
+ *aDuplex = kDuplexFlipOnShortEdge;
+ break;
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetDuplex(int32_t aDuplex) {
+ uint32_t duplex = static_cast<uint32_t>(aDuplex);
+ MOZ_ASSERT(duplex <= kDuplexFlipOnShortEdge,
+ "value is out of bounds for duplex enum");
+
+ // We want to set the GTK CUPS Duplex setting in addition to calling
+ // gtk_print_settings_set_duplex(). Some systems may look for one, or the
+ // other, so it is best to set them both consistently.
+ switch (duplex) {
+ case kDuplexNone:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, kCupsDuplexNone);
+ gtk_print_settings_set_duplex(mPrintSettings, GTK_PRINT_DUPLEX_SIMPLEX);
+ break;
+ case kDuplexFlipOnLongEdge:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, kCupsDuplexNoTumble);
+ gtk_print_settings_set_duplex(mPrintSettings,
+ GTK_PRINT_DUPLEX_HORIZONTAL);
+ break;
+ case kDuplexFlipOnShortEdge:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, kCupsDuplexTumble);
+ gtk_print_settings_set_duplex(mPrintSettings, GTK_PRINT_DUPLEX_VERTICAL);
+ break;
+ }
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h
new file mode 100644
index 0000000000..c2d97a6208
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -0,0 +1,147 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintSettingsGTK_h_
+#define nsPrintSettingsGTK_h_
+
+#include "nsPrintSettingsImpl.h"
+
+extern "C" {
+#include <gtk/gtk.h>
+#include <gtk/gtkunixprint.h>
+}
+
+#define NS_PRINTSETTINGSGTK_IID \
+ { \
+ 0x758df520, 0xc7c3, 0x11dc, { \
+ 0x95, 0xff, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66 \
+ } \
+ }
+
+//*****************************************************************************
+//*** nsPrintSettingsGTK
+//*****************************************************************************
+
+class nsPrintSettingsGTK : public nsPrintSettings {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+ NS_DECLARE_STATIC_IID_ACCESSOR(NS_PRINTSETTINGSGTK_IID)
+
+ nsPrintSettingsGTK();
+ explicit nsPrintSettingsGTK(const PrintSettingsInitializer& aSettings);
+
+ static nsPrintSettingsGTK* From(nsIPrintSettings* aPrintSettings) {
+ return static_cast<nsPrintSettingsGTK*>(aPrintSettings);
+ }
+
+ // We're overriding these methods because we want to read/write with GTK
+ // objects, not local variables. This allows a simpler settings implementation
+ // between Gecko and GTK.
+
+ GtkPageSetup* GetGtkPageSetup() { return mPageSetup; };
+ void SetGtkPageSetup(GtkPageSetup* aPageSetup);
+
+ GtkPrintSettings* GetGtkPrintSettings() { return mPrintSettings; };
+ void SetGtkPrintSettings(GtkPrintSettings* aPrintSettings);
+
+ GtkPrinter* GetGtkPrinter() { return mGTKPrinter; };
+ void SetGtkPrinter(GtkPrinter* aPrinter);
+
+ // Reversed, color, orientation and file name are all stored in the
+ // GtkPrintSettings. Orientation is also stored in the GtkPageSetup and its
+ // setting takes priority when getting the orientation.
+ NS_IMETHOD GetPrintReversed(bool* aPrintReversed) override;
+ NS_IMETHOD SetPrintReversed(bool aPrintReversed) override;
+
+ NS_IMETHOD GetPrintInColor(bool* aPrintInColor) override;
+ NS_IMETHOD SetPrintInColor(bool aPrintInColor) override;
+
+ NS_IMETHOD GetOrientation(int32_t* aOrientation) override;
+ NS_IMETHOD SetOrientation(int32_t aOrientation) override;
+
+ NS_IMETHOD GetToFileName(nsAString& aToFileName) override;
+ NS_IMETHOD SetToFileName(const nsAString& aToFileName) override;
+
+ // Gets/Sets the printer name in the GtkPrintSettings. If no printer name is
+ // specified there, you will get back the name of the current internal
+ // GtkPrinter.
+ NS_IMETHOD GetPrinterName(nsAString& Printer) override;
+ NS_IMETHOD SetPrinterName(const nsAString& aPrinter) override;
+
+ // Number of copies is stored/gotten from the GtkPrintSettings.
+ NS_IMETHOD GetNumCopies(int32_t* aNumCopies) override;
+ NS_IMETHOD SetNumCopies(int32_t aNumCopies) override;
+
+ NS_IMETHOD GetScaling(double* aScaling) override;
+ NS_IMETHOD SetScaling(double aScaling) override;
+
+ // A name recognised by GTK is strongly advised here, as this is used to
+ // create a GtkPaperSize.
+ NS_IMETHOD GetPaperId(nsAString& aPaperId) override;
+ NS_IMETHOD SetPaperId(const nsAString& aPaperId) override;
+
+ NS_IMETHOD SetUnwriteableMarginInTwips(
+ nsIntMargin& aUnwriteableMargin) override;
+ NS_IMETHOD SetUnwriteableMarginTop(double aUnwriteableMarginTop) override;
+ NS_IMETHOD SetUnwriteableMarginLeft(double aUnwriteableMarginLeft) override;
+ NS_IMETHOD SetUnwriteableMarginBottom(
+ double aUnwriteableMarginBottom) override;
+ NS_IMETHOD SetUnwriteableMarginRight(double aUnwriteableMarginRight) override;
+
+ NS_IMETHOD GetPaperWidth(double* aPaperWidth) override;
+ NS_IMETHOD SetPaperWidth(double aPaperWidth) override;
+
+ NS_IMETHOD GetPaperHeight(double* aPaperHeight) override;
+ NS_IMETHOD SetPaperHeight(double aPaperHeight) override;
+
+ NS_IMETHOD SetPaperSizeUnit(int16_t aPaperSizeUnit) override;
+
+ NS_IMETHOD GetEffectivePageSize(double* aWidth, double* aHeight) override;
+
+ NS_IMETHOD SetPageRanges(const nsTArray<int32_t>&) override;
+ NS_IMETHOD GetPageRanges(nsTArray<int32_t>&) override;
+
+ NS_IMETHOD GetResolution(int32_t* aResolution) override;
+ NS_IMETHOD SetResolution(int32_t aResolution) override;
+
+ NS_IMETHOD GetDuplex(int32_t* aDuplex) override;
+ NS_IMETHOD SetDuplex(int32_t aDuplex) override;
+
+ NS_IMETHOD GetOutputFormat(int16_t* aOutputFormat) override;
+
+ protected:
+ virtual ~nsPrintSettingsGTK();
+
+ nsPrintSettingsGTK(const nsPrintSettingsGTK& src);
+ nsPrintSettingsGTK& operator=(const nsPrintSettingsGTK& rhs);
+
+ virtual nsresult _Clone(nsIPrintSettings** _retval) override;
+ virtual nsresult _Assign(nsIPrintSettings* aPS) override;
+
+ GtkUnit GetGTKUnit(int16_t aGeckoUnit);
+ void SaveNewPageSize();
+
+ /**
+ * Re-initialize mUnwriteableMargin with values from mPageSetup.
+ * Should be called whenever mPageSetup is initialized or overwritten.
+ */
+ void InitUnwriteableMargin();
+
+ /**
+ * On construction:
+ * - mPrintSettings and mPageSetup are just new objects with defaults
+ * determined by GTK.
+ * - mGTKPrinter is nullptr!!! Remember to be careful when accessing this
+ * property.
+ */
+ GtkPageSetup* mPageSetup;
+ GtkPrintSettings* mPrintSettings;
+ GtkPrinter* mGTKPrinter;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsPrintSettingsGTK, NS_PRINTSETTINGSGTK_IID)
+
+#endif // nsPrintSettingsGTK_h_
diff --git a/widget/gtk/nsPrintSettingsServiceGTK.cpp b/widget/gtk/nsPrintSettingsServiceGTK.cpp
new file mode 100644
index 0000000000..9d0dfc9379
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsServiceGTK.cpp
@@ -0,0 +1,80 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsPrintSettingsServiceGTK.h"
+
+#include "mozilla/embedding/PPrintingTypes.h"
+#include "nsPrintSettingsGTK.h"
+
+using namespace mozilla::embedding;
+
+static void serialize_gtk_printsettings_to_printdata(const gchar* key,
+ const gchar* value,
+ gpointer aData) {
+ PrintData* data = (PrintData*)aData;
+ CStringKeyValue pair;
+ pair.key() = key;
+ pair.value() = value;
+ data->GTKPrintSettings().AppendElement(pair);
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceGTK::SerializeToPrintData(nsIPrintSettings* aSettings,
+ PrintData* data) {
+ nsresult rv = nsPrintSettingsService::SerializeToPrintData(aSettings, data);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(aSettings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ GtkPrintSettings* gtkPrintSettings = settingsGTK->GetGtkPrintSettings();
+ NS_ENSURE_STATE(gtkPrintSettings);
+
+ gtk_print_settings_foreach(gtkPrintSettings,
+ serialize_gtk_printsettings_to_printdata, data);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsServiceGTK::DeserializeToPrintSettings(
+ const PrintData& data, nsIPrintSettings* settings) {
+ nsCOMPtr<nsPrintSettingsGTK> settingsGTK(do_QueryInterface(settings));
+ NS_ENSURE_STATE(settingsGTK);
+
+ nsresult rv =
+ nsPrintSettingsService::DeserializeToPrintSettings(data, settings);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Instead of re-using the GtkPrintSettings that nsIPrintSettings is
+ // wrapping, we'll create a new one to deserialize to and replace it
+ // within nsIPrintSettings.
+ GtkPrintSettings* newGtkPrintSettings = gtk_print_settings_new();
+
+ for (uint32_t i = 0; i < data.GTKPrintSettings().Length(); ++i) {
+ CStringKeyValue pair = data.GTKPrintSettings()[i];
+ gtk_print_settings_set(newGtkPrintSettings, pair.key().get(),
+ pair.value().get());
+ }
+
+ settingsGTK->SetGtkPrintSettings(newGtkPrintSettings);
+
+ // nsPrintSettingsGTK is holding a reference to newGtkPrintSettings
+ g_object_unref(newGtkPrintSettings);
+ newGtkPrintSettings = nullptr;
+ return NS_OK;
+}
+
+nsresult nsPrintSettingsServiceGTK::_CreatePrintSettings(
+ nsIPrintSettings** _retval) {
+ *_retval = nullptr;
+ nsPrintSettingsGTK* printSettings =
+ new nsPrintSettingsGTK(); // does not initially ref count
+ NS_ENSURE_TRUE(printSettings, NS_ERROR_OUT_OF_MEMORY);
+
+ NS_ADDREF(*_retval = printSettings); // ref count
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintSettingsServiceGTK.h b/widget/gtk/nsPrintSettingsServiceGTK.h
new file mode 100644
index 0000000000..d26f543110
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsServiceGTK.h
@@ -0,0 +1,33 @@
+/* -*- Mode: IDL; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsPrintSettingsServiceGTK_h
+#define nsPrintSettingsServiceGTK_h
+
+#include "nsPrintSettingsService.h"
+
+namespace mozilla {
+namespace embedding {
+class PrintData;
+} // namespace embedding
+} // namespace mozilla
+
+class nsPrintSettingsServiceGTK final : public nsPrintSettingsService {
+ public:
+ nsPrintSettingsServiceGTK() = default;
+
+ NS_IMETHODIMP SerializeToPrintData(
+ nsIPrintSettings* aSettings,
+ mozilla::embedding::PrintData* data) override;
+
+ NS_IMETHODIMP DeserializeToPrintSettings(
+ const mozilla::embedding::PrintData& data,
+ nsIPrintSettings* settings) override;
+
+ virtual nsresult _CreatePrintSettings(nsIPrintSettings** _retval) override;
+};
+
+#endif // nsPrintSettingsServiceGTK_h
diff --git a/widget/gtk/nsShmImage.cpp b/widget/gtk/nsShmImage.cpp
new file mode 100644
index 0000000000..7208b7075f
--- /dev/null
+++ b/widget/gtk/nsShmImage.cpp
@@ -0,0 +1,326 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsShmImage.h"
+
+#ifdef MOZ_HAVE_SHMIMAGE
+# include "mozilla/X11Util.h"
+# include "mozilla/gfx/gfxVars.h"
+# include "mozilla/ipc/SharedMemory.h"
+# include "gfxPlatform.h"
+# include "nsPrintfCString.h"
+# include "nsTArray.h"
+
+# include <dlfcn.h>
+# include <errno.h>
+# include <string.h>
+# include <sys/ipc.h>
+# include <sys/shm.h>
+
+extern "C" {
+# include <X11/ImUtil.h>
+}
+
+using namespace mozilla::ipc;
+using namespace mozilla::gfx;
+
+nsShmImage::nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth)
+ : mDisplay(aDisplay),
+ mConnection(XGetXCBConnection(aDisplay)),
+ mWindow(aWindow),
+ mVisual(aVisual),
+ mDepth(aDepth),
+ mFormat(mozilla::gfx::SurfaceFormat::UNKNOWN),
+ mSize(0, 0),
+ mStride(0),
+ mPixmap(XCB_NONE),
+ mGC(XCB_NONE),
+ mRequestPending(false),
+ mShmSeg(XCB_NONE),
+ mShmId(-1),
+ mShmAddr(nullptr) {
+ mozilla::PodZero(&mSyncRequest);
+}
+
+nsShmImage::~nsShmImage() { DestroyImage(); }
+
+// If XShm isn't available to our client, we'll try XShm once, fail,
+// set this to false and then never try again.
+static bool gShmAvailable = true;
+bool nsShmImage::UseShm() { return gShmAvailable; }
+
+bool nsShmImage::CreateShmSegment() {
+ size_t size = SharedMemory::PageAlignedSize(mStride * mSize.height);
+
+# if defined(__OpenBSD__) && defined(MOZ_SANDBOX)
+ static mozilla::LazyLogModule sPledgeLog("SandboxPledge");
+ MOZ_LOG(sPledgeLog, mozilla::LogLevel::Debug,
+ ("%s called when pledged, returning false\n", __func__));
+ return false;
+# endif
+ mShmId = shmget(IPC_PRIVATE, size, IPC_CREAT | 0600);
+ if (mShmId == -1) {
+ return false;
+ }
+ mShmAddr = (uint8_t*)shmat(mShmId, nullptr, 0);
+ mShmSeg = xcb_generate_id(mConnection);
+
+ // Mark the handle removed so that it will destroy the segment when unmapped.
+ shmctl(mShmId, IPC_RMID, nullptr);
+
+ if (mShmAddr == (void*)-1) {
+ // Since mapping failed, the segment is already destroyed.
+ mShmId = -1;
+
+ nsPrintfCString warning("shmat(): %s (%d)\n", strerror(errno), errno);
+ NS_WARNING(warning.get());
+ return false;
+ }
+
+# ifdef DEBUG
+ struct shmid_ds info;
+ if (shmctl(mShmId, IPC_STAT, &info) < 0) {
+ return false;
+ }
+
+ MOZ_ASSERT(size <= info.shm_segsz, "Segment doesn't have enough space!");
+# endif
+
+ return true;
+}
+
+void nsShmImage::DestroyShmSegment() {
+ if (mShmId != -1) {
+ shmdt(mShmAddr);
+ mShmId = -1;
+ }
+}
+
+static bool gShmInitialized = false;
+static bool gUseShmPixmaps = false;
+
+bool nsShmImage::InitExtension() {
+ if (gShmInitialized) {
+ return gShmAvailable;
+ }
+
+ gShmInitialized = true;
+
+ // Bugs 1397918, 1293474 - race condition in libxcb fixed upstream as of
+ // version 1.11. Since we can't query libxcb's version directly, the only
+ // other option is to check for symbols that were added after 1.11.
+ // xcb_discard_reply64 was added in 1.11.1, so check for existence of
+ // that to verify we are using a version of libxcb with the bug fixed.
+ // Otherwise, we can't risk using libxcb due to aforementioned crashes.
+ if (!dlsym(RTLD_DEFAULT, "xcb_discard_reply64")) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ const xcb_query_extension_reply_t* extReply;
+ extReply = xcb_get_extension_data(mConnection, &xcb_shm_id);
+ if (!extReply || !extReply->present) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ xcb_shm_query_version_reply_t* shmReply = xcb_shm_query_version_reply(
+ mConnection, xcb_shm_query_version(mConnection), nullptr);
+
+ if (!shmReply) {
+ gShmAvailable = false;
+ return false;
+ }
+
+ gUseShmPixmaps = shmReply->shared_pixmaps &&
+ shmReply->pixmap_format == XCB_IMAGE_FORMAT_Z_PIXMAP;
+
+ free(shmReply);
+
+ return true;
+}
+
+bool nsShmImage::CreateImage(const IntSize& aSize) {
+ MOZ_ASSERT(mConnection && mVisual);
+
+ if (!InitExtension()) {
+ return false;
+ }
+
+ mSize = aSize;
+
+ BackendType backend = gfxVars::ContentBackend();
+
+ mFormat = SurfaceFormat::UNKNOWN;
+ switch (mDepth) {
+ case 32:
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 24:
+ // Only support the BGRX layout, and report it as BGRA to the compositor.
+ // The alpha channel will be discarded when we put the image.
+ // Cairo/pixman lacks some fast paths for compositing BGRX onto BGRA, so
+ // just report it as BGRX directly in that case.
+ if (mVisual->red_mask == 0xff0000 && mVisual->green_mask == 0xff00 &&
+ mVisual->blue_mask == 0xff) {
+ mFormat = backend == BackendType::CAIRO ? SurfaceFormat::B8G8R8X8
+ : SurfaceFormat::B8G8R8A8;
+ }
+ break;
+ case 16:
+ if (mVisual->red_mask == 0xf800 && mVisual->green_mask == 0x07e0 &&
+ mVisual->blue_mask == 0x1f) {
+ mFormat = SurfaceFormat::R5G6B5_UINT16;
+ }
+ break;
+ }
+
+ if (mFormat == SurfaceFormat::UNKNOWN) {
+ NS_WARNING("Unsupported XShm Image format!");
+ gShmAvailable = false;
+ return false;
+ }
+
+ // Round up stride to the display's scanline pad (in bits) as XShm expects.
+ int scanlinePad = _XGetScanlinePad(mDisplay, mDepth);
+ int bitsPerPixel = _XGetBitsPerPixel(mDisplay, mDepth);
+ int bitsPerLine =
+ ((bitsPerPixel * aSize.width + scanlinePad - 1) / scanlinePad) *
+ scanlinePad;
+ mStride = bitsPerLine / 8;
+
+ if (!CreateShmSegment()) {
+ DestroyImage();
+ return false;
+ }
+
+ xcb_generic_error_t* error;
+ xcb_void_cookie_t cookie;
+
+ cookie = xcb_shm_attach_checked(mConnection, mShmSeg, mShmId, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ NS_WARNING("Failed to attach MIT-SHM segment.");
+ DestroyImage();
+ gShmAvailable = false;
+ free(error);
+ return false;
+ }
+
+ if (gUseShmPixmaps) {
+ mPixmap = xcb_generate_id(mConnection);
+ cookie = xcb_shm_create_pixmap_checked(mConnection, mPixmap, mWindow,
+ aSize.width, aSize.height, mDepth,
+ mShmSeg, 0);
+
+ if ((error = xcb_request_check(mConnection, cookie))) {
+ // Disable shared pixmaps permanently if creation failed.
+ mPixmap = XCB_NONE;
+ gUseShmPixmaps = false;
+ free(error);
+ }
+ }
+
+ return true;
+}
+
+void nsShmImage::DestroyImage() {
+ if (mGC) {
+ xcb_free_gc(mConnection, mGC);
+ mGC = XCB_NONE;
+ }
+ if (mPixmap != XCB_NONE) {
+ xcb_free_pixmap(mConnection, mPixmap);
+ mPixmap = XCB_NONE;
+ }
+ if (mShmSeg != XCB_NONE) {
+ xcb_shm_detach_checked(mConnection, mShmSeg);
+ mShmSeg = XCB_NONE;
+ }
+ DestroyShmSegment();
+ // Avoid leaking any pending reply. No real need to wait but CentOS 6 build
+ // machines don't have xcb_discard_reply().
+ WaitIfPendingReply();
+}
+
+// Wait for any in-flight shm-affected requests to complete.
+// Typically X clients would wait for a XShmCompletionEvent to be received,
+// but this works as it's sent immediately after the request is sent.
+void nsShmImage::WaitIfPendingReply() {
+ if (mRequestPending) {
+ xcb_get_input_focus_reply_t* reply =
+ xcb_get_input_focus_reply(mConnection, mSyncRequest, nullptr);
+ free(reply);
+ mRequestPending = false;
+ }
+}
+
+already_AddRefed<DrawTarget> nsShmImage::CreateDrawTarget(
+ const mozilla::LayoutDeviceIntRegion& aRegion) {
+ WaitIfPendingReply();
+
+ // Due to bug 1205045, we must avoid making GTK calls off the main thread to
+ // query window size. Instead we just track the largest offset within the
+ // image we are drawing to and grow the image to accomodate it. Since usually
+ // the entire window is invalidated on the first paint to it, this should grow
+ // the image to the necessary size quickly without many intermediate
+ // reallocations.
+ IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ IntSize size(bounds.XMost(), bounds.YMost());
+ if (size.width > mSize.width || size.height > mSize.height) {
+ DestroyImage();
+ if (!CreateImage(size)) {
+ return nullptr;
+ }
+ }
+
+ return gfxPlatform::CreateDrawTargetForData(
+ reinterpret_cast<unsigned char*>(mShmAddr) + bounds.y * mStride +
+ bounds.x * BytesPerPixel(mFormat),
+ bounds.Size(), mStride, mFormat);
+}
+
+void nsShmImage::Put(const mozilla::LayoutDeviceIntRegion& aRegion) {
+ AutoTArray<xcb_rectangle_t, 32> xrects;
+ xrects.SetCapacity(aRegion.GetNumRects());
+
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const mozilla::LayoutDeviceIntRect& r = iter.Get();
+ xcb_rectangle_t xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
+ (unsigned short)r.height};
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = xcb_generate_id(mConnection);
+ xcb_create_gc(mConnection, mGC, mWindow, 0, nullptr);
+ }
+
+ xcb_set_clip_rectangles(mConnection, XCB_CLIP_ORDERING_YX_BANDED, mGC, 0, 0,
+ xrects.Length(), xrects.Elements());
+
+ if (mPixmap != XCB_NONE) {
+ xcb_copy_area(mConnection, mPixmap, mWindow, mGC, 0, 0, 0, 0, mSize.width,
+ mSize.height);
+ } else {
+ xcb_shm_put_image(mConnection, mWindow, mGC, mSize.width, mSize.height, 0,
+ 0, mSize.width, mSize.height, 0, 0, mDepth,
+ XCB_IMAGE_FORMAT_Z_PIXMAP, 0, mShmSeg, 0);
+ }
+
+ // Send a request that returns a response so that we don't have to start a
+ // sync in nsShmImage::CreateDrawTarget.
+ mSyncRequest = xcb_get_input_focus(mConnection);
+ mRequestPending = true;
+
+ xcb_flush(mConnection);
+}
+
+#endif // MOZ_HAVE_SHMIMAGE
diff --git a/widget/gtk/nsShmImage.h b/widget/gtk/nsShmImage.h
new file mode 100644
index 0000000000..2d92c39e0d
--- /dev/null
+++ b/widget/gtk/nsShmImage.h
@@ -0,0 +1,75 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __mozilla_widget_nsShmImage_h__
+#define __mozilla_widget_nsShmImage_h__
+
+#if defined(MOZ_X11)
+# define MOZ_HAVE_SHMIMAGE
+#endif
+
+#ifdef MOZ_HAVE_SHMIMAGE
+
+# include "mozilla/gfx/2D.h"
+# include "nsIWidget.h"
+# include "Units.h"
+
+# include <X11/Xlib-xcb.h>
+# include <xcb/shm.h>
+
+class nsShmImage {
+ // bug 1168843, compositor thread may create shared memory instances that are
+ // destroyed by main thread on shutdown, so this must use thread-safe RC to
+ // avoid hitting assertion
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsShmImage)
+
+ public:
+ static bool UseShm();
+
+ already_AddRefed<mozilla::gfx::DrawTarget> CreateDrawTarget(
+ const mozilla::LayoutDeviceIntRegion& aRegion);
+
+ void Put(const mozilla::LayoutDeviceIntRegion& aRegion);
+
+ nsShmImage(Display* aDisplay, Drawable aWindow, Visual* aVisual,
+ unsigned int aDepth);
+
+ private:
+ ~nsShmImage();
+
+ bool InitExtension();
+
+ bool CreateShmSegment();
+ void DestroyShmSegment();
+
+ bool CreateImage(const mozilla::gfx::IntSize& aSize);
+ void DestroyImage();
+
+ void WaitIfPendingReply();
+
+ Display* mDisplay;
+ xcb_connection_t* mConnection;
+ Window mWindow;
+ Visual* mVisual;
+ unsigned int mDepth;
+
+ mozilla::gfx::SurfaceFormat mFormat;
+ mozilla::gfx::IntSize mSize;
+ int mStride;
+
+ xcb_pixmap_t mPixmap;
+ xcb_gcontext_t mGC;
+ xcb_get_input_focus_cookie_t mSyncRequest;
+ bool mRequestPending;
+
+ xcb_shm_seg_t mShmSeg;
+ int mShmId;
+ uint8_t* mShmAddr;
+};
+
+#endif // MOZ_HAVE_SHMIMAGE
+
+#endif
diff --git a/widget/gtk/nsSound.cpp b/widget/gtk/nsSound.cpp
new file mode 100644
index 0000000000..0530c73224
--- /dev/null
+++ b/widget/gtk/nsSound.cpp
@@ -0,0 +1,397 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <string.h>
+
+#include "nscore.h"
+#include "prlink.h"
+
+#include "nsSound.h"
+
+#include "HeadlessSound.h"
+#include "nsIURL.h"
+#include "nsNetUtil.h"
+#include "nsIChannel.h"
+#include "nsCOMPtr.h"
+#include "nsString.h"
+#include "nsDirectoryService.h"
+#include "nsDirectoryServiceDefs.h"
+#include "mozilla/FileUtils.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+#include "nsIXULAppInfo.h"
+#include "nsContentUtils.h"
+#include "gfxPlatform.h"
+#include "mozilla/ClearOnShutdown.h"
+
+#include <stdio.h>
+#include <unistd.h>
+
+#include <gtk/gtk.h>
+static PRLibrary* libcanberra = nullptr;
+
+/* used to play sounds with libcanberra. */
+typedef struct _ca_context ca_context;
+typedef struct _ca_proplist ca_proplist;
+
+typedef void (*ca_finish_callback_t)(ca_context* c, uint32_t id, int error_code,
+ void* userdata);
+
+typedef int (*ca_context_create_fn)(ca_context**);
+typedef int (*ca_context_destroy_fn)(ca_context*);
+typedef int (*ca_context_play_fn)(ca_context* c, uint32_t id, ...);
+typedef int (*ca_context_change_props_fn)(ca_context* c, ...);
+typedef int (*ca_proplist_create_fn)(ca_proplist**);
+typedef int (*ca_proplist_destroy_fn)(ca_proplist*);
+typedef int (*ca_proplist_sets_fn)(ca_proplist* c, const char* key,
+ const char* value);
+typedef int (*ca_context_play_full_fn)(ca_context* c, uint32_t id,
+ ca_proplist* p, ca_finish_callback_t cb,
+ void* userdata);
+
+static ca_context_create_fn ca_context_create;
+static ca_context_destroy_fn ca_context_destroy;
+static ca_context_play_fn ca_context_play;
+static ca_context_change_props_fn ca_context_change_props;
+static ca_proplist_create_fn ca_proplist_create;
+static ca_proplist_destroy_fn ca_proplist_destroy;
+static ca_proplist_sets_fn ca_proplist_sets;
+static ca_context_play_full_fn ca_context_play_full;
+
+struct ScopedCanberraFile {
+ explicit ScopedCanberraFile(nsIFile* file) : mFile(file){};
+
+ ~ScopedCanberraFile() {
+ if (mFile) {
+ mFile->Remove(false);
+ }
+ }
+
+ void forget() { mozilla::Unused << mFile.forget(); }
+ nsIFile* operator->() { return mFile; }
+ operator nsIFile*() { return mFile; }
+
+ nsCOMPtr<nsIFile> mFile;
+};
+
+static ca_context* ca_context_get_default() {
+ // This allows us to avoid race conditions with freeing the context by handing
+ // that responsibility to Glib, and still use one context at a time
+ static GPrivate ctx_private =
+ G_PRIVATE_INIT((GDestroyNotify)ca_context_destroy);
+
+ ca_context* ctx = (ca_context*)g_private_get(&ctx_private);
+
+ if (ctx) {
+ return ctx;
+ }
+
+ ca_context_create(&ctx);
+ if (!ctx) {
+ return nullptr;
+ }
+
+ g_private_set(&ctx_private, ctx);
+
+ GtkSettings* settings = gtk_settings_get_default();
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-sound-theme-name")) {
+ gchar* sound_theme_name = nullptr;
+ g_object_get(settings, "gtk-sound-theme-name", &sound_theme_name, nullptr);
+
+ if (sound_theme_name) {
+ ca_context_change_props(ctx, "canberra.xdg-theme.name", sound_theme_name,
+ nullptr);
+ g_free(sound_theme_name);
+ }
+ }
+
+ nsAutoString wbrand;
+ mozilla::widget::WidgetUtils::GetBrandShortName(wbrand);
+ ca_context_change_props(ctx, "application.name",
+ NS_ConvertUTF16toUTF8(wbrand).get(), nullptr);
+
+ nsCOMPtr<nsIXULAppInfo> appInfo =
+ do_GetService("@mozilla.org/xre/app-info;1");
+ if (appInfo) {
+ nsAutoCString version;
+ appInfo->GetVersion(version);
+
+ ca_context_change_props(ctx, "application.version", version.get(), nullptr);
+ }
+
+ ca_context_change_props(ctx, "application.icon_name", MOZ_APP_NAME, nullptr);
+
+ return ctx;
+}
+
+static void ca_finish_cb(ca_context* c, uint32_t id, int error_code,
+ void* userdata) {
+ nsIFile* file = reinterpret_cast<nsIFile*>(userdata);
+ if (file) {
+ file->Remove(false);
+ NS_RELEASE(file);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsSound, nsISound, nsIStreamLoaderObserver)
+
+////////////////////////////////////////////////////////////////////////
+nsSound::nsSound() { mInited = false; }
+
+nsSound::~nsSound() = default;
+
+NS_IMETHODIMP
+nsSound::Init() {
+ // This function is designed so that no library is compulsory, and
+ // one library missing doesn't cause the other(s) to not be used.
+ if (mInited) return NS_OK;
+
+ mInited = true;
+
+ if (!libcanberra) {
+ libcanberra = PR_LoadLibrary("libcanberra.so.0");
+ if (libcanberra) {
+ ca_context_create = (ca_context_create_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_create");
+ if (!ca_context_create) {
+#ifdef MOZ_TSAN
+ // With TSan, we cannot unload libcanberra once we have loaded it
+ // because TSan does not support unloading libraries that are matched
+ // from its suppression list. Hence we just keep the library loaded in
+ // TSan builds.
+ libcanberra = nullptr;
+ return NS_OK;
+#endif
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ } else {
+ // at this point we know we have a good libcanberra library
+ ca_context_destroy = (ca_context_destroy_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_destroy");
+ ca_context_play = (ca_context_play_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_play");
+ ca_context_change_props =
+ (ca_context_change_props_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_change_props");
+ ca_proplist_create = (ca_proplist_create_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_create");
+ ca_proplist_destroy = (ca_proplist_destroy_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_destroy");
+ ca_proplist_sets = (ca_proplist_sets_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_proplist_sets");
+ ca_context_play_full = (ca_context_play_full_fn)PR_FindFunctionSymbol(
+ libcanberra, "ca_context_play_full");
+ }
+ }
+ }
+
+ return NS_OK;
+}
+
+/* static */
+void nsSound::Shutdown() {
+#ifndef MOZ_TSAN
+ if (libcanberra) {
+ PR_UnloadLibrary(libcanberra);
+ libcanberra = nullptr;
+ }
+#endif
+}
+
+namespace mozilla {
+namespace sound {
+StaticRefPtr<nsISound> sInstance;
+}
+} // namespace mozilla
+/* static */
+already_AddRefed<nsISound> nsSound::GetInstance() {
+ using namespace mozilla::sound;
+
+ if (!sInstance) {
+ if (gfxPlatform::IsHeadless()) {
+ sInstance = new mozilla::widget::HeadlessSound();
+ } else {
+ sInstance = new nsSound();
+ }
+ ClearOnShutdown(&sInstance);
+ }
+
+ RefPtr<nsISound> service = sInstance.get();
+ return service.forget();
+}
+
+NS_IMETHODIMP nsSound::OnStreamComplete(nsIStreamLoader* aLoader,
+ nsISupports* context, nsresult aStatus,
+ uint32_t dataLen, const uint8_t* data) {
+ // print a load error on bad status, and return
+ if (NS_FAILED(aStatus)) {
+#ifdef DEBUG
+ if (aLoader) {
+ nsCOMPtr<nsIRequest> request;
+ aLoader->GetRequest(getter_AddRefs(request));
+ if (request) {
+ nsCOMPtr<nsIURI> uri;
+ nsCOMPtr<nsIChannel> channel = do_QueryInterface(request);
+ if (channel) {
+ channel->GetURI(getter_AddRefs(uri));
+ if (uri) {
+ printf("Failed to load %s\n", uri->GetSpecOrDefault().get());
+ }
+ }
+ }
+ }
+#endif
+ return aStatus;
+ }
+
+ nsCOMPtr<nsIFile> tmpFile;
+ nsDirectoryService::gService->Get(NS_OS_TEMP_DIR, NS_GET_IID(nsIFile),
+ getter_AddRefs(tmpFile));
+
+ nsresult rv =
+ tmpFile->AppendNative(nsDependentCString("mozilla_audio_sample"));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ rv = tmpFile->CreateUnique(nsIFile::NORMAL_FILE_TYPE, PR_IRUSR | PR_IWUSR);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ScopedCanberraFile canberraFile(tmpFile);
+
+ mozilla::AutoFDClose fd;
+ rv = canberraFile->OpenNSPRFileDesc(PR_WRONLY, PR_IRUSR | PR_IWUSR,
+ getter_Transfers(fd));
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ // XXX: Should we do this on another thread?
+ uint32_t length = dataLen;
+ while (length > 0) {
+ int32_t amount = PR_Write(fd.get(), data, length);
+ if (amount < 0) {
+ return NS_ERROR_FAILURE;
+ }
+ length -= amount;
+ data += amount;
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ ca_proplist* p;
+ ca_proplist_create(&p);
+ if (!p) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString path;
+ rv = canberraFile->GetNativePath(path);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ ca_proplist_sets(p, "media.filename", path.get());
+ if (ca_context_play_full(ctx, 0, p, ca_finish_cb, canberraFile) >= 0) {
+ // Don't delete the temporary file here if ca_context_play_full succeeds
+ canberraFile.forget();
+ }
+ ca_proplist_destroy(p);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Beep() {
+ ::gdk_beep();
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsSound::Play(nsIURL* aURL) {
+ if (!mInited) Init();
+
+ if (!libcanberra) return NS_ERROR_NOT_AVAILABLE;
+
+ nsresult rv;
+ if (aURL->SchemeIs("file")) {
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ nsAutoCString spec;
+ rv = aURL->GetSpec(spec);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+ gchar* path = g_filename_from_uri(spec.get(), nullptr, nullptr);
+ if (!path) {
+ return NS_ERROR_FILE_UNRECOGNIZED_PATH;
+ }
+
+ ca_context_play(ctx, 0, "media.filename", path, nullptr);
+ g_free(path);
+ } else {
+ nsCOMPtr<nsIStreamLoader> loader;
+ rv = NS_NewStreamLoader(
+ getter_AddRefs(loader), aURL,
+ this, // aObserver
+ nsContentUtils::GetSystemPrincipal(),
+ nsILoadInfo::SEC_ALLOW_CROSS_ORIGIN_SEC_CONTEXT_IS_NULL,
+ nsIContentPolicy::TYPE_OTHER);
+ }
+
+ return rv;
+}
+
+NS_IMETHODIMP nsSound::PlayEventSound(uint32_t aEventId) {
+ if (!mInited) Init();
+
+ if (!libcanberra) return NS_OK;
+
+ // Do we even want alert sounds?
+ GtkSettings* settings = gtk_settings_get_default();
+
+ if (g_object_class_find_property(G_OBJECT_GET_CLASS(settings),
+ "gtk-enable-event-sounds")) {
+ gboolean enable_sounds = TRUE;
+ g_object_get(settings, "gtk-enable-event-sounds", &enable_sounds, nullptr);
+
+ if (!enable_sounds) {
+ return NS_OK;
+ }
+ }
+
+ ca_context* ctx = ca_context_get_default();
+ if (!ctx) {
+ return NS_ERROR_OUT_OF_MEMORY;
+ }
+
+ switch (aEventId) {
+ case EVENT_ALERT_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-warning", nullptr);
+ break;
+ case EVENT_CONFIRM_DIALOG_OPEN:
+ ca_context_play(ctx, 0, "event.id", "dialog-question", nullptr);
+ break;
+ case EVENT_NEW_MAIL_RECEIVED:
+ ca_context_play(ctx, 0, "event.id", "message-new-email", nullptr);
+ break;
+ case EVENT_MENU_EXECUTE:
+ ca_context_play(ctx, 0, "event.id", "menu-click", nullptr);
+ break;
+ case EVENT_MENU_POPUP:
+ ca_context_play(ctx, 0, "event.id", "menu-popup", nullptr);
+ break;
+ }
+ return NS_OK;
+}
diff --git a/widget/gtk/nsSound.h b/widget/gtk/nsSound.h
new file mode 100644
index 0000000000..8f4fe5a04b
--- /dev/null
+++ b/widget/gtk/nsSound.h
@@ -0,0 +1,33 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsSound_h__
+#define __nsSound_h__
+
+#include "nsISound.h"
+#include "nsIStreamLoader.h"
+
+#include <gtk/gtk.h>
+
+class nsSound : public nsISound, public nsIStreamLoaderObserver {
+ public:
+ nsSound();
+
+ static void Shutdown();
+ static already_AddRefed<nsISound> GetInstance();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSISOUND
+ NS_DECL_NSISTREAMLOADEROBSERVER
+
+ private:
+ virtual ~nsSound();
+
+ bool mInited;
+};
+
+#endif /* __nsSound_h__ */
diff --git a/widget/gtk/nsToolkit.cpp b/widget/gtk/nsToolkit.cpp
new file mode 100644
index 0000000000..aaf13239e0
--- /dev/null
+++ b/widget/gtk/nsToolkit.cpp
@@ -0,0 +1,24 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nscore.h" // needed for 'nullptr'
+#include "nsGTKToolkit.h"
+
+nsGTKToolkit* nsGTKToolkit::gToolkit = nullptr;
+
+//-------------------------------------------------------------------------------
+// Return the toolkit. If a toolkit does not yet exist, then one will be
+// created.
+//-------------------------------------------------------------------------------
+// static
+nsGTKToolkit* nsGTKToolkit::GetToolkit() {
+ if (!gToolkit) {
+ gToolkit = new nsGTKToolkit();
+ }
+
+ return gToolkit;
+}
diff --git a/widget/gtk/nsUserIdleServiceGTK.cpp b/widget/gtk/nsUserIdleServiceGTK.cpp
new file mode 100644
index 0000000000..4cdc5ba13d
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.cpp
@@ -0,0 +1,317 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <gtk/gtk.h>
+
+#include "nsUserIdleServiceGTK.h"
+#include "nsDebug.h"
+#include "prlink.h"
+#include "mozilla/Logging.h"
+#include "WidgetUtilsGtk.h"
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+# include <X11/Xutil.h>
+# include <gdk/gdkx.h>
+#endif
+#ifdef MOZ_ENABLE_DBUS
+# include <gio/gio.h>
+# include "AsyncDBus.h"
+# include "WakeLockListener.h"
+# include "nsIObserverService.h"
+# include "mozilla/UniquePtrExtensions.h"
+#endif
+
+using mozilla::LogLevel;
+static mozilla::LazyLogModule sIdleLog("nsIUserIdleService");
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+#ifdef MOZ_X11
+typedef struct {
+ Window window; // Screen saver window
+ int state; // ScreenSaver(Off,On,Disabled)
+ int kind; // ScreenSaver(Blanked,Internal,External)
+ unsigned long til_or_since; // milliseconds since/til screensaver kicks in
+ unsigned long idle; // milliseconds idle
+ unsigned long event_mask; // event stuff
+} XScreenSaverInfo;
+
+typedef bool (*_XScreenSaverQueryExtension_fn)(Display* dpy, int* event_base,
+ int* error_base);
+typedef XScreenSaverInfo* (*_XScreenSaverAllocInfo_fn)(void);
+typedef void (*_XScreenSaverQueryInfo_fn)(Display* dpy, Drawable drw,
+ XScreenSaverInfo* info);
+
+class UserIdleServiceX11 : public UserIdleServiceImpl {
+ public:
+ bool PollIdleTime(uint32_t* aIdleTime) override {
+ // Ask xscreensaver about idle time:
+ *aIdleTime = 0;
+
+ // We might not have a display (cf. in xpcshell)
+ Display* dplay = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ if (!dplay) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("No display found!\n"));
+ return false;
+ }
+
+ int event_base, error_base;
+ if (mXSSQueryExtension(dplay, &event_base, &error_base)) {
+ if (!mXssInfo) mXssInfo = mXSSAllocInfo();
+ if (!mXssInfo) return false;
+ mXSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
+ *aIdleTime = mXssInfo->idle;
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("UserIdleServiceX11::PollIdleTime() %d\n", *aIdleTime));
+ return true;
+ }
+ // If we get here, we couldn't get to XScreenSaver:
+ MOZ_LOG(sIdleLog, LogLevel::Warning,
+ ("XSSQueryExtension returned false!\n"));
+ return false;
+ }
+
+ bool ProbeImplementation() override {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("UserIdleServiceX11::UserIdleServiceX11()\n"));
+
+ if (!mozilla::widget::GdkIsX11Display()) {
+ return false;
+ }
+
+ // This will leak - See comments in ~UserIdleServiceX11().
+ PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1");
+ if (!xsslib) // ouch.
+ {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n"));
+ return false;
+ }
+
+ mXSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryExtension");
+ mXSSAllocInfo = (_XScreenSaverAllocInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverAllocInfo");
+ mXSSQueryInfo = (_XScreenSaverQueryInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryInfo");
+
+ if (!mXSSQueryExtension) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning,
+ ("Failed to get XSSQueryExtension!\n"));
+ }
+ if (!mXSSAllocInfo) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n"));
+ }
+ if (!mXSSQueryInfo) {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n"));
+ }
+ if (!mXSSQueryExtension || !mXSSAllocInfo || !mXSSQueryInfo) {
+ // We're missing X11 symbols
+ return false;
+ }
+
+ // UserIdleServiceX11 uses sync init, confirm it now.
+ mUserIdleServiceGTK->AcceptServiceCallback();
+ return true;
+ }
+
+ explicit UserIdleServiceX11(nsUserIdleServiceGTK* aUserIdleService)
+ : UserIdleServiceImpl(aUserIdleService){};
+
+ ~UserIdleServiceX11() {
+# ifdef MOZ_X11
+ if (mXssInfo) {
+ XFree(mXssInfo);
+ }
+# endif
+
+// It is not safe to unload libXScrnSaver until each display is closed because
+// the library registers callbacks through XESetCloseDisplay (Bug 397607).
+// (Also the library and its functions are scoped for the file not the object.)
+# if 0
+ if (xsslib) {
+ PR_UnloadLibrary(xsslib);
+ xsslib = nullptr;
+ }
+# endif
+ }
+
+ private:
+ XScreenSaverInfo* mXssInfo = nullptr;
+ _XScreenSaverQueryExtension_fn mXSSQueryExtension = nullptr;
+ _XScreenSaverAllocInfo_fn mXSSAllocInfo = nullptr;
+ _XScreenSaverQueryInfo_fn mXSSQueryInfo = nullptr;
+};
+#endif
+
+#ifdef MOZ_ENABLE_DBUS
+class UserIdleServiceMutter : public UserIdleServiceImpl {
+ public:
+ bool PollIdleTime(uint32_t* aIdleTime) override {
+ MOZ_LOG(sIdleLog, LogLevel::Info, ("PollIdleTime() request\n"));
+
+ // We're not ready yet
+ if (!mProxy) {
+ return false;
+ }
+
+ if (!mPollInProgress) {
+ mPollInProgress = true;
+ DBusProxyCall(mProxy, "GetIdletime", nullptr, G_DBUS_CALL_FLAGS_NONE, -1,
+ mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ // It's safe to capture this as we use mCancellable to stop
+ // listening.
+ [this](RefPtr<GVariant>&& aResult) {
+ if (!g_variant_is_of_type(aResult, G_VARIANT_TYPE_TUPLE) ||
+ g_variant_n_children(aResult) != 1) {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("PollIdleTime() Unexpected params type: %s\n",
+ g_variant_get_type_string(aResult)));
+ mLastIdleTime = 0;
+ return;
+ }
+ RefPtr<GVariant> iTime =
+ dont_AddRef(g_variant_get_child_value(aResult, 0));
+ if (!g_variant_is_of_type(iTime, G_VARIANT_TYPE_UINT64)) {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("PollIdleTime() Unexpected params type: %s\n",
+ g_variant_get_type_string(aResult)));
+ mLastIdleTime = 0;
+ return;
+ }
+ uint64_t idleTime = g_variant_get_uint64(iTime);
+ if (idleTime > std::numeric_limits<uint32_t>::max()) {
+ idleTime = std::numeric_limits<uint32_t>::max();
+ }
+ mLastIdleTime = idleTime;
+ mPollInProgress = false;
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("Async handler got %d\n", mLastIdleTime));
+ },
+ [this](GUniquePtr<GError>&& aError) {
+ mPollInProgress = false;
+ if (!IsCancelledGError(aError.get())) {
+ MOZ_LOG(
+ sIdleLog, LogLevel::Warning,
+ ("Failed to call GetIdletime(): %s\n", aError->message));
+ mUserIdleServiceGTK->RejectAndTryNextServiceCallback();
+ }
+ });
+ }
+
+ *aIdleTime = mLastIdleTime;
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("PollIdleTime() returns %d\n", *aIdleTime));
+ return true;
+ }
+
+ bool ProbeImplementation() override {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("UserIdleServiceMutter::UserIdleServiceMutter()\n"));
+
+ mCancellable = dont_AddRef(g_cancellable_new());
+ CreateDBusProxyForBus(
+ G_BUS_TYPE_SESSION,
+ GDBusProxyFlags(G_DBUS_PROXY_FLAGS_DO_NOT_CONNECT_SIGNALS |
+ G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES),
+ nullptr, "org.gnome.Mutter.IdleMonitor",
+ "/org/gnome/Mutter/IdleMonitor/Core", "org.gnome.Mutter.IdleMonitor",
+ mCancellable)
+ ->Then(
+ GetCurrentSerialEventTarget(), __func__,
+ [this](RefPtr<GDBusProxy>&& aProxy) {
+ mProxy = std::move(aProxy);
+ mUserIdleServiceGTK->AcceptServiceCallback();
+ },
+ [this](GUniquePtr<GError>&& aError) {
+ if (!IsCancelledGError(aError.get())) {
+ mUserIdleServiceGTK->RejectAndTryNextServiceCallback();
+ }
+ });
+ return true;
+ }
+
+ explicit UserIdleServiceMutter(nsUserIdleServiceGTK* aUserIdleService)
+ : UserIdleServiceImpl(aUserIdleService){};
+
+ ~UserIdleServiceMutter() {
+ if (mCancellable) {
+ g_cancellable_cancel(mCancellable);
+ mCancellable = nullptr;
+ }
+ mProxy = nullptr;
+ }
+
+ private:
+ RefPtr<GDBusProxy> mProxy;
+ RefPtr<GCancellable> mCancellable;
+ uint32_t mLastIdleTime = 0;
+ bool mPollInProgress = false;
+};
+#endif
+
+void nsUserIdleServiceGTK::ProbeService() {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("nsUserIdleServiceGTK::ProbeService() mIdleServiceType %d\n",
+ mIdleServiceType));
+ MOZ_ASSERT(!mIdleService);
+
+ switch (mIdleServiceType) {
+#ifdef MOZ_ENABLE_DBUS
+ case IDLE_SERVICE_MUTTER:
+ mIdleService = MakeUnique<UserIdleServiceMutter>(this);
+ break;
+#endif
+#ifdef MOZ_X11
+ case IDLE_SERVICE_XSCREENSAVER:
+ mIdleService = MakeUnique<UserIdleServiceX11>(this);
+ break;
+#endif
+ default:
+ return;
+ }
+
+ if (!mIdleService->ProbeImplementation()) {
+ RejectAndTryNextServiceCallback();
+ }
+}
+
+void nsUserIdleServiceGTK::AcceptServiceCallback() {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("nsUserIdleServiceGTK::AcceptServiceCallback() type %d\n",
+ mIdleServiceType));
+ mIdleServiceInitialized = true;
+}
+
+void nsUserIdleServiceGTK::RejectAndTryNextServiceCallback() {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("nsUserIdleServiceGTK::RejectAndTryNextServiceCallback() type %d\n",
+ mIdleServiceType));
+
+ // Delete recent non-working service
+ MOZ_ASSERT(mIdleService, "Nothing to reject?");
+ mIdleService = nullptr;
+ mIdleServiceInitialized = false;
+
+ mIdleServiceType++;
+ if (mIdleServiceType < IDLE_SERVICE_NONE) {
+ MOZ_LOG(sIdleLog, LogLevel::Info,
+ ("nsUserIdleServiceGTK try next idle service\n"));
+ ProbeService();
+ } else {
+ MOZ_LOG(sIdleLog, LogLevel::Info, ("nsUserIdleServiceGTK failed\n"));
+ }
+}
+
+bool nsUserIdleServiceGTK::PollIdleTime(uint32_t* aIdleTime) {
+ if (!mIdleServiceInitialized) {
+ return false;
+ }
+ return mIdleService->PollIdleTime(aIdleTime);
+}
diff --git a/widget/gtk/nsUserIdleServiceGTK.h b/widget/gtk/nsUserIdleServiceGTK.h
new file mode 100644
index 0000000000..1a5ed97840
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.h
@@ -0,0 +1,78 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef nsUserIdleServiceGTK_h__
+#define nsUserIdleServiceGTK_h__
+
+#include "nsUserIdleService.h"
+#include "mozilla/AppShutdown.h"
+#include "mozilla/UniquePtr.h"
+
+class nsUserIdleServiceGTK;
+
+class UserIdleServiceImpl {
+ public:
+ explicit UserIdleServiceImpl(nsUserIdleServiceGTK* aUserIdleService)
+ : mUserIdleServiceGTK(aUserIdleService){};
+
+ virtual bool PollIdleTime(uint32_t* aIdleTime) = 0;
+ virtual bool ProbeImplementation() = 0;
+
+ virtual ~UserIdleServiceImpl() = default;
+
+ protected:
+ nsUserIdleServiceGTK* mUserIdleServiceGTK;
+};
+
+#define IDLE_SERVICE_MUTTER 0
+#define IDLE_SERVICE_XSCREENSAVER 1
+#define IDLE_SERVICE_NONE 2
+
+class nsUserIdleServiceGTK : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceGTK, nsUserIdleService)
+
+ // The idle time in ms
+ virtual bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceGTK> GetInstance() {
+ RefPtr<nsUserIdleServiceGTK> idleService =
+ nsUserIdleService::GetInstance().downcast<nsUserIdleServiceGTK>();
+ if (!idleService) {
+ // Avoid late instantiation or resurrection during shutdown.
+ if (mozilla::AppShutdown::IsInOrBeyond(
+ mozilla::ShutdownPhase::AppShutdownConfirmed)) {
+ return nullptr;
+ }
+ idleService = new nsUserIdleServiceGTK();
+ idleService->ProbeService();
+ }
+
+ return idleService.forget();
+ }
+
+ void ProbeService();
+ void AcceptServiceCallback();
+ void RejectAndTryNextServiceCallback();
+
+ protected:
+ nsUserIdleServiceGTK() = default;
+
+ private:
+ ~nsUserIdleServiceGTK() = default;
+
+ mozilla::UniquePtr<UserIdleServiceImpl> mIdleService;
+#ifdef MOZ_ENABLE_DBUS
+ int mIdleServiceType = IDLE_SERVICE_MUTTER;
+#else
+ int mIdleServiceType = IDLE_SERVICE_XSCREENSAVER;
+#endif
+ // We have a working idle service.
+ bool mIdleServiceInitialized = false;
+};
+
+#endif // nsUserIdleServiceGTK_h__
diff --git a/widget/gtk/nsWaylandDisplay.cpp b/widget/gtk/nsWaylandDisplay.cpp
new file mode 100644
index 0000000000..2a1021457a
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.cpp
@@ -0,0 +1,204 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWaylandDisplay.h"
+
+#include <dlfcn.h>
+
+#include "base/message_loop.h" // for MessageLoop
+#include "base/task.h" // for NewRunnableMethod, etc
+#include "mozilla/gfx/Logging.h" // for gfxCriticalNote
+#include "mozilla/StaticMutex.h"
+#include "mozilla/Array.h"
+#include "mozilla/StaticPtr.h"
+#include "mozilla/ThreadLocal.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Sprintf.h"
+#include "WidgetUtilsGtk.h"
+#include "nsGtkKeyUtils.h"
+
+namespace mozilla::widget {
+
+static nsWaylandDisplay* gWaylandDisplay;
+
+void WaylandDisplayRelease() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "WaylandDisplay can be released in main thread only!");
+ if (!gWaylandDisplay) {
+ NS_WARNING("WaylandDisplayRelease(): Wayland display is missing!");
+ return;
+ }
+ delete gWaylandDisplay;
+ gWaylandDisplay = nullptr;
+}
+
+wl_display* WaylandDisplayGetWLDisplay() {
+ GdkDisplay* disp = gdk_display_get_default();
+ if (!GdkIsWaylandDisplay(disp)) {
+ return nullptr;
+ }
+ return gdk_wayland_display_get_wl_display(disp);
+}
+
+nsWaylandDisplay* WaylandDisplayGet() {
+ if (!gWaylandDisplay) {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread(),
+ "WaylandDisplay can be created in main thread only!");
+ wl_display* waylandDisplay = WaylandDisplayGetWLDisplay();
+ if (!waylandDisplay) {
+ return nullptr;
+ }
+ gWaylandDisplay = new nsWaylandDisplay(waylandDisplay);
+ }
+ return gWaylandDisplay;
+}
+
+void nsWaylandDisplay::SetShm(wl_shm* aShm) { mShm = aShm; }
+
+void nsWaylandDisplay::SetCompositor(wl_compositor* aCompositor) {
+ mCompositor = aCompositor;
+}
+
+void nsWaylandDisplay::SetSubcompositor(wl_subcompositor* aSubcompositor) {
+ mSubcompositor = aSubcompositor;
+}
+
+void nsWaylandDisplay::SetIdleInhibitManager(
+ zwp_idle_inhibit_manager_v1* aIdleInhibitManager) {
+ mIdleInhibitManager = aIdleInhibitManager;
+}
+
+void nsWaylandDisplay::SetViewporter(wp_viewporter* aViewporter) {
+ mViewporter = aViewporter;
+}
+
+void nsWaylandDisplay::SetRelativePointerManager(
+ zwp_relative_pointer_manager_v1* aRelativePointerManager) {
+ mRelativePointerManager = aRelativePointerManager;
+}
+
+void nsWaylandDisplay::SetPointerConstraints(
+ zwp_pointer_constraints_v1* aPointerConstraints) {
+ mPointerConstraints = aPointerConstraints;
+}
+
+void nsWaylandDisplay::SetDmabuf(zwp_linux_dmabuf_v1* aDmabuf) {
+ mDmabuf = aDmabuf;
+}
+
+void nsWaylandDisplay::SetXdgActivation(xdg_activation_v1* aXdgActivation) {
+ mXdgActivation = aXdgActivation;
+}
+
+static void global_registry_handler(void* data, wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ auto* display = static_cast<nsWaylandDisplay*>(data);
+ if (!display) {
+ return;
+ }
+
+ nsDependentCString iface(interface);
+ if (iface.EqualsLiteral("wl_shm")) {
+ auto* shm = WaylandRegistryBind<wl_shm>(registry, id, &wl_shm_interface, 1);
+ display->SetShm(shm);
+ } else if (iface.EqualsLiteral("zwp_idle_inhibit_manager_v1")) {
+ auto* idle_inhibit_manager =
+ WaylandRegistryBind<zwp_idle_inhibit_manager_v1>(
+ registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
+ display->SetIdleInhibitManager(idle_inhibit_manager);
+ } else if (iface.EqualsLiteral("zwp_relative_pointer_manager_v1")) {
+ auto* relative_pointer_manager =
+ WaylandRegistryBind<zwp_relative_pointer_manager_v1>(
+ registry, id, &zwp_relative_pointer_manager_v1_interface, 1);
+ display->SetRelativePointerManager(relative_pointer_manager);
+ } else if (iface.EqualsLiteral("zwp_pointer_constraints_v1")) {
+ auto* pointer_constraints = WaylandRegistryBind<zwp_pointer_constraints_v1>(
+ registry, id, &zwp_pointer_constraints_v1_interface, 1);
+ display->SetPointerConstraints(pointer_constraints);
+ } else if (iface.EqualsLiteral("wl_compositor")) {
+ // Requested wl_compositor version 4 as we need wl_surface_damage_buffer().
+ auto* compositor = WaylandRegistryBind<wl_compositor>(
+ registry, id, &wl_compositor_interface, 4);
+ display->SetCompositor(compositor);
+ } else if (iface.EqualsLiteral("wl_subcompositor")) {
+ auto* subcompositor = WaylandRegistryBind<wl_subcompositor>(
+ registry, id, &wl_subcompositor_interface, 1);
+ display->SetSubcompositor(subcompositor);
+ } else if (iface.EqualsLiteral("wp_viewporter")) {
+ auto* viewporter = WaylandRegistryBind<wp_viewporter>(
+ registry, id, &wp_viewporter_interface, 1);
+ display->SetViewporter(viewporter);
+ } else if (iface.EqualsLiteral("zwp_linux_dmabuf_v1") && version > 2) {
+ auto* dmabuf = WaylandRegistryBind<zwp_linux_dmabuf_v1>(
+ registry, id, &zwp_linux_dmabuf_v1_interface, 3);
+ display->SetDmabuf(dmabuf);
+ } else if (iface.EqualsLiteral("xdg_activation_v1")) {
+ auto* activation = WaylandRegistryBind<xdg_activation_v1>(
+ registry, id, &xdg_activation_v1_interface, 1);
+ display->SetXdgActivation(activation);
+ } else if (iface.EqualsLiteral("wl_seat")) {
+ // Install keyboard handlers for main thread only
+ auto* seat =
+ WaylandRegistryBind<wl_seat>(registry, id, &wl_seat_interface, 1);
+ KeymapWrapper::SetSeat(seat, id);
+ } else if (iface.EqualsLiteral("wp_fractional_scale_manager_v1")) {
+ auto* manager = WaylandRegistryBind<wp_fractional_scale_manager_v1>(
+ registry, id, &wp_fractional_scale_manager_v1_interface, 1);
+ display->SetFractionalScaleManager(manager);
+ } else if (iface.EqualsLiteral("gtk_primary_selection_device_manager") ||
+ iface.EqualsLiteral("zwp_primary_selection_device_manager_v1")) {
+ display->EnablePrimarySelection();
+ }
+}
+
+static void global_registry_remover(void* data, wl_registry* registry,
+ uint32_t id) {
+ KeymapWrapper::ClearSeat(id);
+}
+
+static const struct wl_registry_listener registry_listener = {
+ global_registry_handler, global_registry_remover};
+
+nsWaylandDisplay::~nsWaylandDisplay() {}
+
+static void WlLogHandler(const char* format, va_list args) {
+ char error[1000];
+ VsprintfLiteral(error, format, args);
+ gfxCriticalNote << "Wayland protocol error: " << error;
+
+ // See Bug 1826583 and Bug 1844653 for reference.
+ // "warning: queue %p destroyed while proxies still attached" and variants
+ // like "zwp_linux_dmabuf_feedback_v1@%d still attached" are exceptions on
+ // Wayland and non-fatal. They are triggered in certain versions of Mesa or
+ // the proprietary Nvidia driver and we don't want to crash because of them.
+ if (strstr(error, "still attached")) {
+ return;
+ }
+
+ MOZ_CRASH_UNSAFE(error);
+}
+
+nsWaylandDisplay::nsWaylandDisplay(wl_display* aDisplay)
+ : mThreadId(PR_GetCurrentThread()), mDisplay(aDisplay) {
+ // GTK sets the log handler on display creation, thus we overwrite it here
+ // in a similar fashion
+ wl_log_set_handler_client(WlLogHandler);
+
+ mRegistry = wl_display_get_registry(mDisplay);
+ wl_registry_add_listener(mRegistry, &registry_listener, this);
+ wl_display_roundtrip(mDisplay);
+ wl_display_roundtrip(mDisplay);
+
+ // Check we have critical Wayland interfaces.
+ // Missing ones indicates a compositor bug and we can't continue.
+ MOZ_DIAGNOSTIC_ASSERT(GetShm(), "We're missing shm interface!");
+ MOZ_DIAGNOSTIC_ASSERT(GetCompositor(), "We're missing compositor interface!");
+ MOZ_DIAGNOSTIC_ASSERT(GetSubcompositor(),
+ "We're missing subcompositor interface!");
+}
+
+} // namespace mozilla::widget
diff --git a/widget/gtk/nsWaylandDisplay.h b/widget/gtk/nsWaylandDisplay.h
new file mode 100644
index 0000000000..cd8124d97f
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.h
@@ -0,0 +1,120 @@
+/* -*- Mode: C; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __MOZ_WAYLAND_DISPLAY_H__
+#define __MOZ_WAYLAND_DISPLAY_H__
+
+#include "DMABufLibWrapper.h"
+
+#include "mozilla/widget/mozwayland.h"
+#include "mozilla/widget/gbm.h"
+#include "mozilla/widget/fractional-scale-v1-client-protocol.h"
+#include "mozilla/widget/idle-inhibit-unstable-v1-client-protocol.h"
+#include "mozilla/widget/relative-pointer-unstable-v1-client-protocol.h"
+#include "mozilla/widget/pointer-constraints-unstable-v1-client-protocol.h"
+#include "mozilla/widget/linux-dmabuf-unstable-v1-client-protocol.h"
+#include "mozilla/widget/viewporter-client-protocol.h"
+#include "mozilla/widget/xdg-activation-v1-client-protocol.h"
+#include "mozilla/widget/xdg-output-unstable-v1-client-protocol.h"
+
+namespace mozilla::widget {
+
+// Our general connection to Wayland display server,
+// holds our display connection and runs event loop.
+// We have a global nsWaylandDisplay object for each thread.
+class nsWaylandDisplay {
+ public:
+ // Create nsWaylandDisplay object on top of native Wayland wl_display
+ // connection.
+ explicit nsWaylandDisplay(wl_display* aDisplay);
+
+ wl_display* GetDisplay() { return mDisplay; };
+ wl_compositor* GetCompositor() { return mCompositor; };
+ wl_subcompositor* GetSubcompositor() { return mSubcompositor; };
+ wl_shm* GetShm() { return mShm; };
+ zwp_idle_inhibit_manager_v1* GetIdleInhibitManager() {
+ return mIdleInhibitManager;
+ }
+ wp_viewporter* GetViewporter() { return mViewporter; };
+ zwp_relative_pointer_manager_v1* GetRelativePointerManager() {
+ return mRelativePointerManager;
+ }
+ zwp_pointer_constraints_v1* GetPointerConstraints() {
+ return mPointerConstraints;
+ }
+ zwp_linux_dmabuf_v1* GetDmabuf() { return mDmabuf; };
+ xdg_activation_v1* GetXdgActivation() { return mXdgActivation; };
+ wp_fractional_scale_manager_v1* GetFractionalScaleManager() {
+ return mFractionalScaleManager;
+ }
+ bool IsPrimarySelectionEnabled() { return mIsPrimarySelectionEnabled; }
+
+ void SetShm(wl_shm* aShm);
+ void SetCompositor(wl_compositor* aCompositor);
+ void SetSubcompositor(wl_subcompositor* aSubcompositor);
+ void SetDataDeviceManager(wl_data_device_manager* aDataDeviceManager);
+ void SetIdleInhibitManager(zwp_idle_inhibit_manager_v1* aIdleInhibitManager);
+ void SetViewporter(wp_viewporter* aViewporter);
+ void SetRelativePointerManager(
+ zwp_relative_pointer_manager_v1* aRelativePointerManager);
+ void SetPointerConstraints(zwp_pointer_constraints_v1* aPointerConstraints);
+ void SetDmabuf(zwp_linux_dmabuf_v1* aDmabuf);
+ void SetXdgActivation(xdg_activation_v1* aXdgActivation);
+ void SetFractionalScaleManager(wp_fractional_scale_manager_v1* aManager) {
+ mFractionalScaleManager = aManager;
+ }
+ void EnablePrimarySelection() { mIsPrimarySelectionEnabled = true; }
+
+ ~nsWaylandDisplay();
+
+ private:
+ PRThread* mThreadId = nullptr;
+ wl_registry* mRegistry = nullptr;
+ wl_display* mDisplay = nullptr;
+ wl_compositor* mCompositor = nullptr;
+ wl_subcompositor* mSubcompositor = nullptr;
+ wl_shm* mShm = nullptr;
+ zwp_idle_inhibit_manager_v1* mIdleInhibitManager = nullptr;
+ zwp_relative_pointer_manager_v1* mRelativePointerManager = nullptr;
+ zwp_pointer_constraints_v1* mPointerConstraints = nullptr;
+ wp_viewporter* mViewporter = nullptr;
+ zwp_linux_dmabuf_v1* mDmabuf = nullptr;
+ xdg_activation_v1* mXdgActivation = nullptr;
+ wp_fractional_scale_manager_v1* mFractionalScaleManager = nullptr;
+ bool mExplicitSync = false;
+ bool mIsPrimarySelectionEnabled = false;
+};
+
+wl_display* WaylandDisplayGetWLDisplay();
+nsWaylandDisplay* WaylandDisplayGet();
+void WaylandDisplayRelease();
+
+} // namespace mozilla::widget
+
+template <class T>
+static inline T* WaylandRegistryBind(struct wl_registry* wl_registry,
+ uint32_t name,
+ const struct wl_interface* interface,
+ uint32_t version) {
+ struct wl_proxy* id;
+
+ // When libwayland-client does not provide this symbol, it will be
+ // linked to the fallback in libmozwayland, which returns NULL.
+ id = wl_proxy_marshal_constructor_versioned(
+ (struct wl_proxy*)wl_registry, WL_REGISTRY_BIND, interface, version, name,
+ interface->name, version, nullptr);
+
+ if (id == nullptr) {
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)wl_registry,
+ WL_REGISTRY_BIND, interface, name,
+ interface->name, version, nullptr);
+ }
+
+ return reinterpret_cast<T*>(id);
+}
+
+#endif // __MOZ_WAYLAND_DISPLAY_H__
diff --git a/widget/gtk/nsWidgetFactory.cpp b/widget/gtk/nsWidgetFactory.cpp
new file mode 100644
index 0000000000..7f06e8f9f3
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -0,0 +1,67 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWidgetFactory.h"
+
+#include "mozilla/Components.h"
+#include "mozilla/WidgetUtils.h"
+#include "NativeKeyBindings.h"
+#include "nsWidgetsCID.h"
+#include "nsAppShell.h"
+#include "nsAppShellSingleton.h"
+#include "nsBaseWidget.h"
+#include "nsGtkKeyUtils.h"
+#include "nsLookAndFeel.h"
+#include "nsWindow.h"
+#include "nsHTMLFormatConverter.h"
+#include "HeadlessClipboard.h"
+#include "IMContextWrapper.h"
+#include "nsClipboard.h"
+#include "TaskbarProgress.h"
+#include "nsFilePicker.h"
+#include "nsSound.h"
+#include "nsGTKToolkit.h"
+#include "WakeLockListener.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/widget/ScreenManager.h"
+#include <gtk/gtk.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_COMPONENT_FACTORY(nsIClipboard) {
+ nsCOMPtr<nsIClipboard> inst;
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessClipboard();
+ } else {
+ auto clipboard = MakeRefPtr<nsClipboard>();
+ if (NS_FAILED(clipboard->Init())) {
+ return nullptr;
+ }
+ inst = std::move(clipboard);
+ }
+
+ return inst.forget().downcast<nsISupports>();
+}
+
+nsresult nsWidgetGtk2ModuleCtor() { return nsAppShellInit(); }
+
+void nsWidgetGtk2ModuleDtor() {
+ // Shutdown all XP level widget classes.
+ WidgetUtils::Shutdown();
+
+ NativeKeyBindings::Shutdown();
+ nsLookAndFeel::Shutdown();
+ nsFilePicker::Shutdown();
+ nsSound::Shutdown();
+ nsWindow::ReleaseGlobals();
+ IMContextWrapper::Shutdown();
+ KeymapWrapper::Shutdown();
+ nsGTKToolkit::Shutdown();
+ nsAppShellShutdown();
+}
diff --git a/widget/gtk/nsWidgetFactory.h b/widget/gtk/nsWidgetFactory.h
new file mode 100644
index 0000000000..f9312f6274
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=4:tabstop=4:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef widget_gtk_nsWidgetFactory_h
+#define widget_gtk_nsWidgetFactory_h
+
+#include "nscore.h"
+#include "nsID.h"
+
+class nsISupports;
+
+nsresult nsAppShellConstructor(const nsIID& iid, void** result);
+
+nsresult nsWidgetGtk2ModuleCtor();
+void nsWidgetGtk2ModuleDtor();
+
+#endif // defined widget_gtk_nsWidgetFactory_h
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp
new file mode 100644
index 0000000000..8185c7bda9
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,10028 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsWindow.h"
+
+#include <algorithm>
+#include <cstdint>
+#include <dlfcn.h>
+#include <gdk/gdkkeysyms.h>
+#include <wchar.h>
+
+#include "VsyncSource.h"
+#include "gfx2DGlue.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxPlatformGtk.h"
+#include "gfxUtils.h"
+#include "GLContextProvider.h"
+#include "GLContext.h"
+#include "GtkCompositorWidget.h"
+#include "gtkdrawing.h"
+#include "imgIContainer.h"
+#include "InputData.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Components.h"
+#include "mozilla/GRefPtr.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/gfx/GPUProcessManager.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/layers/APZThreadUtils.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/layers/CompositorBridgeChild.h"
+#include "mozilla/layers/CompositorBridgeParent.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/layers/KnowsCompositor.h"
+#include "mozilla/layers/WebRenderBridgeChild.h"
+#include "mozilla/layers/WebRenderLayerManager.h"
+#include "mozilla/layers/APZInputBridge.h"
+#include "mozilla/layers/IAPZCTreeManager.h"
+#include "mozilla/Likely.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/NativeKeyBindingsType.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_mozilla.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/SwipeTracker.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/WritingModes.h"
+#ifdef MOZ_X11
+# include "mozilla/X11Util.h"
+#endif
+#include "mozilla/XREAppData.h"
+#include "NativeKeyBindings.h"
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsAppRunner.h"
+#include "nsDragService.h"
+#include "nsGTKToolkit.h"
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsIFile.h"
+#include "nsIGSettingsService.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "nsImageToPixbuf.h"
+#include "nsINode.h"
+#include "nsIRollupListener.h"
+#include "nsIScreenManager.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "nsIWidgetListener.h"
+#include "nsLayoutUtils.h"
+#include "nsMenuPopupFrame.h"
+#include "nsPresContext.h"
+#include "nsShmImage.h"
+#include "nsString.h"
+#include "nsWidgetsCID.h"
+#include "nsViewManager.h"
+#include "nsXPLookAndFeel.h"
+#include "prlink.h"
+#include "Screen.h"
+#include "ScreenHelperGTK.h"
+#include "SystemTimeConverter.h"
+#include "WidgetUtilsGtk.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/LocalAccessible.h"
+# include "mozilla/a11y/Platform.h"
+# include "nsAccessibilityService.h"
+#endif
+
+#ifdef MOZ_X11
+# include <gdk/gdkkeysyms-compat.h>
+# include <X11/Xatom.h>
+# include <X11/extensions/XShm.h>
+# include <X11/extensions/shape.h>
+# include "gfxXlibSurface.h"
+# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
+# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
+# include "WindowSurfaceX11Image.h"
+# include "WindowSurfaceX11SHM.h"
+#endif
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkkeysyms-compat.h>
+# include "nsIClipboard.h"
+# include "nsView.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::layers;
+using namespace mozilla::widget;
+#ifdef MOZ_X11
+using mozilla::gl::GLContextEGL;
+using mozilla::gl::GLContextGLX;
+#endif
+
+// Don't put more than this many rects in the dirty region, just fluff
+// out to the bounding-box if there are more
+#define MAX_RECTS_IN_REGION 100
+
+#if !GTK_CHECK_VERSION(3, 18, 0)
+
+struct _GdkEventTouchpadPinch {
+ GdkEventType type;
+ GdkWindow* window;
+ gint8 send_event;
+ gint8 phase;
+ gint8 n_fingers;
+ guint32 time;
+ gdouble x;
+ gdouble y;
+ gdouble dx;
+ gdouble dy;
+ gdouble angle_delta;
+ gdouble scale;
+ gdouble x_root, y_root;
+ guint state;
+};
+
+typedef enum {
+ GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
+ GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
+ GDK_TOUCHPAD_GESTURE_PHASE_END,
+ GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
+} GdkTouchpadGesturePhase;
+
+gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
+GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
+
+#endif
+
+const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
+ GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
+
+/* utility functions */
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY);
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
+static nsWindow* get_window_for_gdk_window(GdkWindow* window);
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
+static GdkCursor* get_gtk_cursor(nsCursor aCursor);
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety);
+
+/* callbacks from widgets */
+static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
+static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
+static void widget_map_cb(GtkWidget* widget);
+static void widget_unmap_cb(GtkWidget* widget);
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation);
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event);
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event);
+MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
+ GdkEventButton* event);
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event);
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
+static gboolean property_notify_event_cb(GtkWidget* widget,
+ GdkEventProperty* event);
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
+static gboolean visibility_notify_event_cb(GtkWidget* widget,
+ GdkEventVisibility* event);
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel);
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event);
+static void settings_xft_dpi_changed_cb(GtkSettings* settings,
+ GParamSpec* pspec, nsWindow* data);
+static void check_resize_cb(GtkContainer* container, gpointer user_data);
+static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
+static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer);
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+#ifdef MOZ_X11
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data);
+#endif /* MOZ_X11 */
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData);
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData);
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime,
+ gpointer aData);
+
+/* initialization static functions */
+static nsresult initialize_prefs(void);
+
+static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
+
+static SystemTimeConverter<guint32>& TimeConverter() {
+ static SystemTimeConverter<guint32> sTimeConverterSingleton;
+ return sTimeConverterSingleton;
+}
+
+bool nsWindow::sTransparentMainWindow = false;
+
+// forward declare from mozgtk
+extern "C" MOZ_EXPORT void mozgtk_linker_holder();
+
+namespace mozilla {
+
+#ifdef MOZ_X11
+class CurrentX11TimeGetter {
+ public:
+ explicit CurrentX11TimeGetter(GdkWindow* aWindow) : mWindow(aWindow) {}
+
+ guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
+
+ void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
+ // Check for in-flight request
+ if (!mAsyncUpdateStart.IsNull()) {
+ return;
+ }
+ mAsyncUpdateStart = aNow;
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
+ Window xWindow = GDK_WINDOW_XID(mWindow);
+ unsigned char c = 'a';
+ Atom timeStampPropAtom = TimeStampPropAtom();
+ XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
+ PropModeReplace, &c, 1);
+ XFlush(xDisplay);
+ }
+
+ gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
+ if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
+ return FALSE;
+ }
+
+ guint32 eventTime = aEvent->time;
+ TimeStamp lowerBound = mAsyncUpdateStart;
+
+ TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
+ mAsyncUpdateStart = TimeStamp();
+ return TRUE;
+ }
+
+ private:
+ static Atom TimeStampPropAtom() {
+ return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
+ "GDK_TIMESTAMP_PROP");
+ }
+
+ // This is safe because this class is stored as a member of mWindow and
+ // won't outlive it.
+ GdkWindow* mWindow;
+ TimeStamp mAsyncUpdateStart;
+};
+#endif
+
+} // namespace mozilla
+
+// The window from which the focus manager asks us to dispatch key events.
+static nsWindow* gFocusWindow = nullptr;
+static bool gBlockActivateEvent = false;
+static bool gGlobalsInitialized = false;
+static bool gUseAspectRatio = true;
+static uint32_t gLastTouchID = 0;
+// See Bug 1777269 for details. We don't know if the suspected leave notify
+// event is a correct one when we get it.
+// Store it and issue it later from enter notify event if it's correct,
+// throw it away otherwise.
+static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
+
+#define NS_WINDOW_TITLE_MAX_LENGTH 4095
+
+// cursor cache
+static GdkCursor* gCursorCache[eCursorCount];
+
+// Sometimes this actually also includes the state of the modifier keys, but
+// only the button state bits are used.
+static guint gButtonState;
+
+static inline int32_t GetBitmapStride(int32_t width) {
+#if defined(MOZ_X11)
+ return (width + 7) / 8;
+#else
+ return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
+#endif
+}
+
+static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
+ // Timestamps are just the least significant bits of a monotonically
+ // increasing function, and so the use of unsigned overflow arithmetic.
+ return a - b <= G_MAXUINT32 / 2;
+}
+
+static void UpdateLastInputEventTime(void* aGdkEvent) {
+ nsCOMPtr<nsIUserIdleServiceInternal> idleService =
+ do_GetService("@mozilla.org/widget/useridleservice;1");
+ if (idleService) {
+ idleService->ResetIdleTimeOut(0);
+ }
+
+ guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
+ if (timestamp == GDK_CURRENT_TIME) {
+ return;
+ }
+
+ sLastUserInputTime = timestamp;
+}
+
+// Don't set parent (transient for) if nothing changes.
+// gtk_window_set_transient_for() blows up wl_subsurfaces used by aWindow
+// even if aParent is the same.
+static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) {
+ GtkWindow* parent = gtk_window_get_transient_for(aWindow);
+ if (parent != aParent) {
+ gtk_window_set_transient_for(aWindow, aParent);
+ }
+}
+
+#define gtk_window_set_transient_for(a, b) \
+ { \
+ MOZ_ASSERT_UNREACHABLE( \
+ "gtk_window_set_transient_for() can't be used directly."); \
+ }
+
+nsWindow::nsWindow()
+ : mTitlebarRectMutex("nsWindow::mTitlebarRectMutex"),
+ mDestroyMutex("nsWindow::mDestroyMutex"),
+ mIsDestroyed(false),
+ mIsShown(false),
+ mNeedsShow(false),
+ mIsMapped(false),
+ mEnabled(true),
+ mCreated(false),
+ mHandleTouchEvent(false),
+ mIsDragPopup(false),
+ mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
+ mIsAccelerated(false),
+ mWindowShouldStartDragging(false),
+ mHasMappedToplevel(false),
+ mRetryPointerGrab(false),
+ mPanInProgress(false),
+ mTitlebarBackdropState(false),
+ mIsChildWindow(false),
+ mAlwaysOnTop(false),
+ mNoAutoHide(false),
+ mIsTransparent(false),
+ mHasReceivedSizeAllocate(false),
+ mWidgetCursorLocked(false),
+ mUndecorated(false),
+ mPopupTrackInHierarchy(false),
+ mPopupTrackInHierarchyConfigured(false),
+ mHiddenPopupPositioned(false),
+ mTransparencyBitmapForTitlebar(false),
+ mHasAlphaVisual(false),
+ mPopupAnchored(false),
+ mPopupContextMenu(false),
+ mPopupMatchesLayout(false),
+ mPopupChanged(false),
+ mPopupTemporaryHidden(false),
+ mPopupClosed(false),
+ mPopupUseMoveToRect(false),
+ mWaitingForMoveToRectCallback(false),
+ mMovedAfterMoveToRect(false),
+ mResizedAfterMoveToRect(false),
+ mConfiguredClearColor(false),
+ mGotNonBlankPaint(false),
+ mNeedsToRetryCapturingMouse(false) {
+ mWindowType = WindowType::Child;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+ if (!gGlobalsInitialized) {
+ gGlobalsInitialized = true;
+
+ // It's OK if either of these fail, but it may not be one day.
+ initialize_prefs();
+
+#ifdef MOZ_WAYLAND
+ // Wayland provides clipboard data to application on focus-in event
+ // so we need to init our clipboard hooks before we create window
+ // and get focus.
+ if (GdkIsWaylandDisplay()) {
+ nsCOMPtr<nsIClipboard> clipboard =
+ do_GetService("@mozilla.org/widget/clipboard;1");
+ NS_ASSERTION(clipboard, "Failed to init clipboard!");
+ }
+#endif
+ }
+ // Dummy call to mozgtk to prevent the linker from removing
+ // the dependency with --as-needed.
+ // see toolkit/library/moz.build for details.
+ mozgtk_linker_holder();
+}
+
+nsWindow::~nsWindow() {
+ LOG("nsWindow::~nsWindow()");
+ Destroy();
+}
+
+/* static */
+void nsWindow::ReleaseGlobals() {
+ for (auto& cursor : gCursorCache) {
+ if (cursor) {
+ g_object_unref(cursor);
+ cursor = nullptr;
+ }
+ }
+}
+
+void nsWindow::DispatchActivateEvent(void) {
+#ifdef ACCESSIBILITY
+ DispatchActivateEventAccessible();
+#endif // ACCESSIBILITY
+
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void nsWindow::DispatchDeactivateEvent() {
+ if (mWidgetListener) {
+ mWidgetListener->WindowDeactivated();
+ }
+
+#ifdef ACCESSIBILITY
+ DispatchDeactivateEventAccessible();
+#endif // ACCESSIBILITY
+}
+
+void nsWindow::DispatchResized() {
+ LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
+ (int)(mBounds.height));
+
+ mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+}
+
+void nsWindow::MaybeDispatchResized() {
+ if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1) && !mIsDestroyed) {
+ mBounds.SizeTo(mNeedsDispatchSize);
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized "
+ << mBounds << " size state " << mSizeMode;
+ }
+
+ if (IsTopLevelWindowType()) {
+ UpdateTopLevelOpaqueRegion();
+ }
+
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+
+ DispatchResized();
+ }
+}
+
+nsIWidgetListener* nsWindow::GetListener() {
+ return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
+}
+
+nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) {
+#ifdef DEBUG
+ debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
+#endif
+ aStatus = nsEventStatus_eIgnore;
+ nsIWidgetListener* listener = GetListener();
+ if (listener) {
+ aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
+ }
+
+ return NS_OK;
+}
+
+void nsWindow::OnDestroy(void) {
+ if (mOnDestroyCalled) {
+ return;
+ }
+
+ mOnDestroyCalled = true;
+
+ // Prevent deletion.
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+
+ // release references to children, device context, toolkit + app shell
+ nsBaseWidget::OnDestroy();
+
+ // Remove association between this object and its parent and siblings.
+ nsBaseWidget::Destroy();
+ mParent = nullptr;
+
+ NotifyWindowDestroyed();
+}
+
+bool nsWindow::AreBoundsSane() {
+ // Check requested size, as mBounds might not have been updated.
+ return !mLastSizeRequest.IsEmpty();
+}
+
+// Walk the list of child windows and call destroy on them.
+void nsWindow::DestroyChildWindows() {
+ LOG("nsWindow::DestroyChildWindows()");
+ if (!mGdkWindow) {
+ return;
+ }
+ while (GList* children = gdk_window_peek_children(mGdkWindow)) {
+ GdkWindow* child = GDK_WINDOW(children->data);
+ nsWindow* kid = get_window_for_gdk_window(child);
+ if (kid) {
+ kid->Destroy();
+ }
+ }
+}
+
+void nsWindow::Destroy() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+
+ if (mIsDestroyed || !mCreated) {
+ return;
+ }
+
+ LOG("nsWindow::Destroy\n");
+
+ MutexAutoLock lock(mDestroyMutex);
+ mIsDestroyed = true;
+ mCreated = false;
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+#ifdef MOZ_WAYLAND
+ // Shut down our local vsync source
+ if (mWaylandVsyncSource) {
+ mWaylandVsyncSource->Shutdown();
+ mWaylandVsyncSource = nullptr;
+ }
+ mWaylandVsyncDispatcher = nullptr;
+#endif
+
+ // dragService will be null after shutdown of the service manager.
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ if (dragService && this == dragService->GetMostRecentDestWindow()) {
+ dragService->ScheduleLeaveEvent();
+ }
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (static_cast<nsIWidget*>(this) == rollupWidget) {
+ rollupListener->Rollup({});
+ }
+ }
+
+ NativeShow(false);
+
+ ClearTransparencyBitmap();
+
+ DestroyLayerManager();
+
+ // mSurfaceProvider holds reference to this nsWindow so we need to explicitly
+ // clear it here to avoid nsWindow leak.
+ mSurfaceProvider.CleanupResources();
+
+ g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
+
+ if (mIMContext) {
+ mIMContext->OnDestroyWindow(this);
+ }
+
+ // make sure that we remove ourself as the focus window
+ if (gFocusWindow == this) {
+ LOG("automatically losing focus...\n");
+ gFocusWindow = nullptr;
+ }
+
+ if (sStoredLeaveNotifyEvent) {
+ nsWindow* window =
+ get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
+ if (window == this) {
+ sStoredLeaveNotifyEvent = nullptr;
+ }
+ }
+
+ // We need to detach accessible object here because mContainer is a custom
+ // widget and doesn't call gtk_widget_real_destroy() from destroy handler
+ // as regular widgets.
+ if (AtkObject* ac = gtk_widget_get_accessible(GTK_WIDGET(mContainer))) {
+ gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr);
+ }
+
+ gtk_widget_destroy(mShell);
+ mShell = nullptr;
+ mContainer = nullptr;
+
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+
+#ifdef ACCESSIBILITY
+ if (mRootAccessible) {
+ mRootAccessible = nullptr;
+ }
+#endif
+
+ // Save until last because OnDestroy() may cause us to be deleted.
+ OnDestroy();
+}
+
+nsIWidget* nsWindow::GetParent() { return mParent; }
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ return DesktopToLayoutDeviceScale(FractionalScaleFactor());
+ }
+#endif
+
+ // In Gtk/X11, we manage windows using device pixels.
+ return DesktopToLayoutDeviceScale(1.0);
+}
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
+#ifdef MOZ_WAYLAND
+ // In Wayland there's no way to get absolute position of the window and use it
+ // to determine the screen factor of the monitor on which the window is
+ // placed. The window is notified of the current scale factor but not at this
+ // point, so the GdkScaleFactor can return wrong value which can lead to wrong
+ // popup placement. We need to use parent's window scale factor for the new
+ // one.
+ if (GdkIsWaylandDisplay()) {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ return DesktopToLayoutDeviceScale(
+ parentWidget->RoundsWidgetCoordinatesTo());
+ }
+ NS_WARNING("Widget has no parent");
+ }
+ } else {
+ NS_WARNING("Cannot find widget view");
+ }
+ }
+#endif
+ return nsBaseWidget::GetDesktopToDeviceScale();
+}
+
+// Reparent a child window to a new parent.
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ LOG("nsWindow::SetParent() new parent %p", aNewParent);
+ if (!mIsChildWindow) {
+ NS_WARNING("Used by child widgets only");
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ mParent = aNewParent;
+
+ // We're already deleted, quit.
+ if (!mGdkWindow || mIsDestroyed || !aNewParent) {
+ return;
+ }
+ aNewParent->AddChild(this);
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+
+ // New parent is deleted, quit.
+ if (newParent->mIsDestroyed) {
+ Destroy();
+ return;
+ }
+
+ GdkWindow* window = GetToplevelGdkWindow();
+ GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
+ LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
+ gdk_window_reparent(window, parentWindow, 0, 0);
+ SetHasMappedToplevel(newParent && newParent->mHasMappedToplevel);
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ if (mWindowType == WindowType::Invisible) {
+ return false;
+ }
+
+ if (IsSmallPopup()) {
+ return false;
+ }
+ // Workaround for Bug 1479135
+ // We draw transparent popups on non-compositing screens by SW as we don't
+ // implement X shape masks in WebRender.
+ if (mWindowType == WindowType::Popup) {
+ return HasRemoteContent() && mCompositedScreen;
+ }
+
+ return true;
+}
+
+void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
+ MOZ_ASSERT(aNewParent, "null widget");
+ MOZ_ASSERT(!mIsDestroyed, "");
+ MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
+ MOZ_ASSERT(
+ !mParent,
+ "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+ GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
+
+ LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell), newParentWidget);
+}
+
+void nsWindow::SetModal(bool aModal) {
+ LOG("nsWindow::SetModal %d\n", aModal);
+ if (mIsDestroyed) {
+ return;
+ }
+
+ gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
+}
+
+// nsIWidget method, which means IsShown.
+bool nsWindow::IsVisible() const { return mIsShown; }
+
+bool nsWindow::IsMapped() const { return mIsMapped; }
+
+void nsWindow::RegisterTouchWindow() {
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+}
+
+LayoutDeviceIntPoint nsWindow::GetScreenEdgeSlop() {
+ if (DrawsToCSDTitlebar()) {
+ return GetClientOffset();
+ }
+ return {};
+}
+
+void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
+ if (!mShell || GdkIsWaylandDisplay()) {
+ return;
+ }
+
+ double dpiScale = GetDefaultScale().scale;
+
+ // we need to use the window size in logical screen pixels
+ int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
+ int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
+
+ /* get our playing field. use the current screen, or failing that
+ for any reason, use device caps for the default screen. */
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenmgr) {
+ return;
+ }
+ nsCOMPtr<nsIScreen> screen;
+ screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
+ getter_AddRefs(screen));
+ // We don't have any screen so leave the coordinates as is
+ if (!screen) {
+ return;
+ }
+
+ // For normalized windows, use the desktop work area.
+ // For full screen windows, use the desktop.
+ DesktopIntRect screenRect = mSizeMode == nsSizeMode_Fullscreen
+ ? screen->GetRectDisplayPix()
+ : screen->GetAvailRectDisplayPix();
+
+ // Expand for the decoration size if needed.
+ auto slop =
+ DesktopIntPoint::Round(GetScreenEdgeSlop() / GetDesktopToDeviceScale());
+ screenRect.Inflate(slop.x, slop.y);
+
+ if (aPoint.x < screenRect.x) {
+ aPoint.x = screenRect.x;
+ } else if (aPoint.x >= screenRect.XMost() - logWidth) {
+ aPoint.x = screenRect.XMost() - logWidth;
+ }
+
+ if (aPoint.y < screenRect.y) {
+ aPoint.y = screenRect.y;
+ } else if (aPoint.y >= screenRect.YMost() - logHeight) {
+ aPoint.y = screenRect.YMost() - logHeight;
+ }
+}
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+
+ ApplySizeConstraints();
+}
+
+bool nsWindow::DrawsToCSDTitlebar() const {
+ return mSizeMode == nsSizeMode_Normal &&
+ mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar;
+}
+
+void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
+ if (mSizeMode != nsSizeMode_Normal || mUndecorated ||
+ mGtkWindowDecoration != GTK_DECORATION_CLIENT || !GdkIsWaylandDisplay() ||
+ !IsGnomeDesktopEnvironment()) {
+ return;
+ }
+
+ GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
+ *aWidth += decorationSize.left + decorationSize.right;
+ *aHeight += decorationSize.top + decorationSize.bottom;
+}
+
+#ifdef MOZ_WAYLAND
+bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
+ if (!DrawsToCSDTitlebar()) {
+ return false;
+ }
+ GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
+ *aDx = decorationSize.left;
+ *aDy = decorationSize.top;
+ return true;
+}
+#endif
+
+void nsWindow::ApplySizeConstraints() {
+ if (mShell) {
+ GdkGeometry geometry;
+ geometry.min_width =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
+ geometry.min_height =
+ DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
+ geometry.max_width =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
+ geometry.max_height =
+ DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
+
+ uint32_t hints = 0;
+ if (mSizeConstraints.mMinSize != LayoutDeviceIntSize()) {
+ if (GdkIsWaylandDisplay()) {
+ gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
+ geometry.min_height);
+ }
+ AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
+ hints |= GDK_HINT_MIN_SIZE;
+ }
+ if (mSizeConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
+ hints |= GDK_HINT_MAX_SIZE;
+ }
+
+ if (mAspectRatio != 0.0f && !mAspectResizer) {
+ geometry.min_aspect = mAspectRatio;
+ geometry.max_aspect = mAspectRatio;
+ hints |= GDK_HINT_ASPECT;
+ }
+
+ gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
+ GdkWindowHints(hints));
+ }
+}
+
+void nsWindow::Show(bool aState) {
+ if (aState == mIsShown) {
+ return;
+ }
+
+ mIsShown = aState;
+
+#ifdef MOZ_LOGGING
+ LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
+ if (!aState && mSourceDragContext && GdkIsWaylandDisplay()) {
+ LOG(" closing Drag&Drop source window, D&D will be canceled!");
+ }
+#endif
+
+ // Ok, someone called show on a window that isn't sized to a sane
+ // value. Mark this window as needing to have Show() called on it
+ // and return.
+ if ((aState && !AreBoundsSane()) || !mCreated) {
+ LOG("\tbounds are insane or window hasn't been created yet\n");
+ mNeedsShow = true;
+ return;
+ }
+
+ // If someone is hiding this widget, clear any needing show flag.
+ if (!aState) mNeedsShow = false;
+
+#ifdef ACCESSIBILITY
+ if (aState && a11y::ShouldA11yBeEnabled()) CreateRootAccessible();
+#endif
+
+ NativeShow(aState);
+ RefreshWindowClass();
+}
+
+void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
+ LayoutDeviceIntSize aSize) {
+ LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
+ const bool moved = aMove && *aMove != mBounds.TopLeft();
+ if (moved) {
+ mBounds.MoveTo(*aMove);
+ LOG(" with move to left:%d top:%d", aMove->x.value, aMove->y.value);
+ }
+
+ ConstrainSize(&aSize.width, &aSize.height);
+ LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
+
+ const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
+#if MOZ_LOGGING
+ LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
+ resized, aSize.width, aSize.height, mLastSizeRequest.width,
+ mLastSizeRequest.height, mBounds.width, mBounds.height);
+#endif
+
+ // For top-level windows, aSize should possibly be
+ // interpreted as frame bounds, but NativeMoveResize treats these as window
+ // bounds (Bug 581866).
+ mLastSizeRequest = aSize;
+ // Check size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
+ gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
+ << " size state " << mSizeMode;
+ }
+
+ // Recalculate aspect ratio when resized from DOM
+ if (mAspectRatio != 0.0) {
+ LockAspectRatio(true);
+ }
+
+ if (!mCreated) {
+ return;
+ }
+
+ if (!moved && !resized) {
+ LOG(" not moved or resized, quit");
+ return;
+ }
+
+ NativeMoveResize(moved, resized);
+
+ // We optimistically assume size changes immediately in two cases:
+ // 1. Override-redirect window: Size is controlled by only us.
+ // 2. Managed window that has not not yet received a size-allocate event:
+ // Resize() Callers expect initial sizes to be applied synchronously.
+ // If the size request is not honored, then we'll correct in
+ // OnSizeAllocate().
+ //
+ // When a managed window has already received a size-allocate, we cannot
+ // assume we'll always get a notification if our request does not get
+ // honored: "If the configure request has not changed, we don't ever resend
+ // it, because it could mean fighting the user or window manager."
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/3.24.31/gtk/gtkwindow.c#L9782
+ // So we don't update mBounds until OnSizeAllocate() when we know the
+ // request is granted.
+ bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
+ if (!isOrWillBeVisible ||
+ gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
+ mBounds.SizeTo(aSize);
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(aSize);
+ }
+ DispatchResized();
+ }
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
+
+ ResizeInt(Nothing(), size);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
+ aHeight, aRepaint);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
+ auto topLeft = LayoutDeviceIntPoint::Round(scale * aX, scale * aY);
+
+ ResizeInt(Some(topLeft), size);
+}
+
+void nsWindow::Enable(bool aState) { mEnabled = aState; }
+
+bool nsWindow::IsEnabled() const { return mEnabled; }
+
+void nsWindow::Move(double aX, double aY) {
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ LOG("nsWindow::Move to %d x %d\n", x, y);
+
+ if (mSizeMode != nsSizeMode_Normal && IsTopLevelWindowType()) {
+ LOG(" size state is not normal, bailing");
+ return;
+ }
+
+ // Since a popup window's x/y coordinates are in relation to to
+ // the parent, the parent might have moved so we always move a
+ // popup window.
+ LOG(" bounds %d x %d\n", mBounds.x, mBounds.y);
+ if (x == mBounds.x && y == mBounds.y && mWindowType != WindowType::Popup) {
+ LOG(" position is the same, return\n");
+ return;
+ }
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated) {
+ LOG(" is not created, return.\n");
+ return;
+ }
+
+ NativeMoveResize(/* move */ true, /* resize */ false);
+}
+
+bool nsWindow::IsPopup() const { return mWindowType == WindowType::Popup; }
+
+bool nsWindow::IsWaylandPopup() const {
+ return GdkIsWaylandDisplay() && IsPopup();
+}
+
+static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
+ return do_QueryFrame(aFrame);
+}
+
+void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
+ mWaylandToplevel = aToplevelWindow;
+
+ nsWindow* popup = aToplevelWindow;
+ while (popup && popup->mWaylandPopupNext) {
+ popup = popup->mWaylandPopupNext;
+ }
+ popup->mWaylandPopupNext = this;
+
+ mWaylandPopupPrev = popup;
+ mWaylandPopupNext = nullptr;
+ mPopupChanged = true;
+ mPopupClosed = false;
+}
+
+void nsWindow::RemovePopupFromHierarchyList() {
+ // We're already removed from the popup hierarchy
+ if (!IsInPopupHierarchy()) {
+ return;
+ }
+ mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
+ if (mWaylandPopupNext) {
+ mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
+ mWaylandPopupNext->mPopupChanged = true;
+ }
+ mWaylandPopupNext = mWaylandPopupPrev = nullptr;
+}
+
+// Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
+// see https://gitlab.gnome.org/GNOME/gtk/-/issues/4071
+// as a workaround just fool around and place the popup temporary to 0,0.
+bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/4071 applies to temporary
+ // windows only
+ GdkWindow* window = GetToplevelGdkWindow();
+ if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) {
+ return false;
+ }
+
+ LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
+
+ int x, y;
+ gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
+ bool moveBack = (x < 0 && y < 0);
+ if (moveBack) {
+ gtk_window_move(GTK_WINDOW(mShell), 0, 0);
+ if (aX) {
+ *aX = x;
+ }
+ if (aY) {
+ *aY = y;
+ }
+ }
+
+ gdk_window_get_geometry(window, &x, &y, nullptr, nullptr);
+ if (x < 0 && y < 0) {
+ gdk_window_move(window, 0, 0);
+ }
+
+ return moveBack;
+}
+
+void nsWindow::ShowWaylandPopupWindow() {
+ LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
+ MOZ_ASSERT(IsWaylandPopup());
+
+ if (!mPopupTrackInHierarchy) {
+ LOG(" popup is not tracked in popup hierarchy, show it now");
+ gtk_widget_show(mShell);
+ return;
+ }
+
+ // Popup position was checked before gdk_window_move_to_rect() callback
+ // so just show it.
+ if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
+ LOG(" active move-to-rect callback, show it as is");
+ gtk_widget_show(mShell);
+ return;
+ }
+
+ if (gtk_widget_is_visible(mShell)) {
+ LOG(" is already visible, quit");
+ return;
+ }
+
+ int x, y;
+ bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
+ gtk_widget_show(mShell);
+ if (moved) {
+ LOG(" move back to (%d, %d) and show", x, y);
+ gtk_window_move(GTK_WINDOW(mShell), x, y);
+ }
+}
+
+void nsWindow::WaylandPopupMarkAsClosed() {
+ LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
+ mPopupClosed = true;
+ // If we have any child popup window notify it about
+ // parent switch.
+ if (mWaylandPopupNext) {
+ mWaylandPopupNext->mPopupChanged = true;
+ }
+}
+
+nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
+ while (aPopup && aPopup->mWaylandPopupNext) {
+ aPopup = aPopup->mWaylandPopupNext;
+ }
+ return aPopup;
+}
+
+// Hide and potentially removes popup from popup hierarchy.
+void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
+ bool aRemoveFromPopupList) {
+ LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
+ aRemoveFromPopupList);
+ if (aRemoveFromPopupList) {
+ RemovePopupFromHierarchyList();
+ }
+
+ if (!mPopupClosed) {
+ mPopupClosed = !aTemporaryHide;
+ }
+
+ bool visible = gtk_widget_is_visible(mShell);
+ LOG(" gtk_widget_is_visible() = %d\n", visible);
+
+ // Restore only popups which are really visible
+ mPopupTemporaryHidden = aTemporaryHide && visible;
+
+ // Hide only visible popups or popups closed pernamently.
+ if (visible) {
+ gtk_widget_hide(mShell);
+
+ // If there's pending Move-To-Rect callback and we hide the popup
+ // the callback won't be called any more.
+ mWaitingForMoveToRectCallback = false;
+ }
+
+ if (mPopupClosed) {
+ LOG(" Clearing mMoveToRectPopupSize\n");
+ mMoveToRectPopupSize = {};
+#ifdef MOZ_WAYLAND
+ if (moz_container_wayland_is_waiting_to_show(mContainer)) {
+ // We need to clear rendering queue, see Bug 1782948.
+ LOG(" popup failed to show by Wayland compositor, clear rendering "
+ "queue.");
+ moz_container_wayland_clear_waiting_to_show_flag(mContainer);
+ ClearRenderingQueue();
+ }
+#endif
+ }
+}
+
+void nsWindow::HideWaylandToplevelWindow() {
+ LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
+ if (mWaylandPopupNext) {
+ nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
+ while (popup->mWaylandToplevel != nullptr) {
+ nsWindow* prev = popup->mWaylandPopupPrev;
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ popup = prev;
+ }
+ }
+ WaylandStopVsync();
+ gtk_widget_hide(mShell);
+}
+
+void nsWindow::ShowWaylandToplevelWindow() {
+ MOZ_ASSERT(!IsWaylandPopup());
+ LOG("nsWindow::ShowWaylandToplevelWindow");
+ gtk_widget_show(mShell);
+}
+
+void nsWindow::WaylandPopupRemoveClosedPopups() {
+ LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
+ nsWindow* popup = this;
+ while (popup) {
+ nsWindow* next = popup->mWaylandPopupNext;
+ if (popup->mPopupClosed) {
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ }
+ popup = next;
+ }
+}
+
+// Hide all tooltips except the latest one.
+void nsWindow::WaylandPopupHideTooltips() {
+ LOG("nsWindow::WaylandPopupHideTooltips");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup && popup->mWaylandPopupNext) {
+ if (popup->mPopupType == PopupType::Tooltip) {
+ LOG(" hidding tooltip [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupCloseOrphanedPopups() {
+#ifdef MOZ_WAYLAND
+ LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ nsWindow* popup = mWaylandPopupNext;
+ bool dangling = false;
+ while (popup) {
+ if (!dangling &&
+ moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) {
+ LOG(" popup [%p] is waiting to show, close all child popups", popup);
+ dangling = true;
+ } else if (dangling) {
+ popup->WaylandPopupMarkAsClosed();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+#endif
+}
+
+// We can't show popups with remote content or overflow popups
+// on top of regular ones.
+// If there's any remote popup opened, close all parent popups of it.
+void nsWindow::CloseAllPopupsBeforeRemotePopup() {
+ LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ // Don't waste time when there's only one popup opened.
+ if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
+ return;
+ }
+
+ // Find the first opened remote content popup
+ nsWindow* remotePopup = mWaylandPopupNext;
+ while (remotePopup) {
+ if (remotePopup->HasRemoteContent() ||
+ remotePopup->IsWidgetOverflowWindow()) {
+ LOG(" remote popup [%p]", remotePopup);
+ break;
+ }
+ remotePopup = remotePopup->mWaylandPopupNext;
+ }
+
+ if (!remotePopup) {
+ return;
+ }
+
+ // ...hide opened popups before the remote one.
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup && popup != remotePopup) {
+ LOG(" hidding popup [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+static void GetLayoutPopupWidgetChain(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
+ aLayoutWidgetHierarchy->Reverse();
+}
+
+// Compare 'this' popup position in Wayland widget hierarchy
+// (mWaylandPopupPrev/mWaylandPopupNext) with
+// 'this' popup position in layout hierarchy.
+//
+// When aMustMatchParent is true we also request
+// 'this' parents match, i.e. 'this' has the same parent in
+// both layout and widget hierarchy.
+bool nsWindow::IsPopupInLayoutPopupChain(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
+ int len = (int)aLayoutWidgetHierarchy->Length();
+ for (int i = 0; i < len; i++) {
+ if (this == (*aLayoutWidgetHierarchy)[i]) {
+ if (!aMustMatchParent) {
+ return true;
+ }
+
+ // Find correct parent popup for 'this' according to widget
+ // hierarchy. That means we need to skip closed popups.
+ nsWindow* parentPopup = nullptr;
+ if (mWaylandPopupPrev != mWaylandToplevel) {
+ parentPopup = mWaylandPopupPrev;
+ while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
+ parentPopup = parentPopup->mWaylandPopupPrev;
+ }
+ }
+
+ if (i == 0) {
+ // We found 'this' popups as a first popup in layout hierarchy.
+ // It matches layout hierarchy if it's first widget also in
+ // wayland widget hierarchy (i.e. parent is null).
+ return parentPopup == nullptr;
+ }
+
+ return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
+ }
+ }
+ return false;
+}
+
+// Hide popups which are not in popup chain.
+void nsWindow::WaylandPopupHierarchyHideByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
+ MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
+
+ // Hide all popups which are not in layout popup chain
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup) {
+ // Don't check closed popups and drag source popups and tooltips.
+ if (!popup->mPopupClosed && popup->mPopupType != PopupType::Tooltip &&
+ !popup->mSourceDragContext) {
+ if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
+ /* aMustMatchParent */ false)) {
+ LOG(" hidding popup [%p]", popup);
+ popup->WaylandPopupMarkAsClosed();
+ }
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+// Mark popups outside of layout hierarchy
+void nsWindow::WaylandPopupHierarchyValidateByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
+ LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
+ nsWindow* popup = mWaylandPopupNext;
+ while (popup) {
+ if (popup->mPopupType == PopupType::Tooltip) {
+ popup->mPopupMatchesLayout = true;
+ } else if (!popup->mPopupClosed) {
+ popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
+ aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
+ LOG(" popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
+ (void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyHideTemporary() {
+ LOG("nsWindow::WaylandPopupHierarchyHideTemporary()");
+ nsWindow* popup = WaylandPopupFindLast(this);
+ while (popup && popup != this) {
+ LOG(" temporary hidding popup [%p]", popup);
+ nsWindow* prev = popup->mWaylandPopupPrev;
+ popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ popup = prev;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
+ LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
+ nsWindow* popup = this;
+ while (popup) {
+ if (popup->mPopupTemporaryHidden) {
+ popup->mPopupTemporaryHidden = false;
+ LOG(" showing temporary hidden popup [%p]", popup);
+ popup->ShowWaylandPopupWindow();
+ }
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+void nsWindow::WaylandPopupHierarchyCalculatePositions() {
+ LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
+
+ // Set widget hierarchy in Gtk
+ nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
+ while (popup) {
+ LOG(" popup [%p] set parent window [%p]", (void*)popup,
+ (void*)popup->mWaylandPopupPrev);
+ GtkWindowSetTransientFor(GTK_WINDOW(popup->mShell),
+ GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
+ popup = popup->mWaylandPopupNext;
+ }
+
+ popup = this;
+ while (popup) {
+ // Anchored window has mPopupPosition already calculated against
+ // its parent, no need to recalculate.
+ LOG(" popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
+ (int)(popup->mBounds.x / FractionalScaleFactor()),
+ (int)(popup->mBounds.y / FractionalScaleFactor()),
+ (int)(popup->mBounds.width / FractionalScaleFactor()),
+ (int)(popup->mBounds.height / FractionalScaleFactor()));
+#ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
+ auto r = LayoutDeviceRect::FromAppUnitsRounded(
+ popupFrame->GetRect(),
+ popupFrame->PresContext()->AppUnitsPerDevPixel());
+ LOG(" popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
+ r.width, r.height);
+ }
+ }
+#endif
+ if (popup->WaylandPopupIsFirst()) {
+ LOG(" popup [%p] has toplevel as parent", popup);
+ popup->mRelativePopupPosition = popup->mPopupPosition;
+ } else {
+ if (popup->mPopupAnchored) {
+ LOG(" popup [%p] is anchored", popup);
+ if (!popup->mPopupMatchesLayout) {
+ NS_WARNING("Anchored popup does not match layout!");
+ }
+ }
+ GdkPoint parent = popup->WaylandGetParentPosition();
+
+ LOG(" popup [%p] uses transformed coordinates\n", popup);
+ LOG(" parent position [%d, %d]\n", parent.x, parent.y);
+ LOG(" popup position [%d, %d]\n", popup->mPopupPosition.x,
+ popup->mPopupPosition.y);
+
+ popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
+ popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
+ }
+ LOG(" popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
+ popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
+ popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
+ popup = popup->mWaylandPopupNext;
+ }
+}
+
+// The MenuList popups are used as dropdown menus for example in WebRTC
+// microphone/camera chooser or autocomplete widgets.
+bool nsWindow::WaylandPopupIsMenu() {
+ nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
+ if (menuPopupFrame) {
+ return mPopupType == PopupType::Menu && !menuPopupFrame->IsMenuList();
+ }
+ return false;
+}
+
+bool nsWindow::WaylandPopupIsContextMenu() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ return false;
+ }
+ return popupFrame->IsContextMenu();
+}
+
+bool nsWindow::WaylandPopupIsPermanent() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ // We can always hide popups without frames.
+ return false;
+ }
+ return popupFrame->IsNoAutoHide();
+}
+
+bool nsWindow::WaylandPopupIsAnchored() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ // We can always hide popups without frames.
+ return false;
+ }
+ return !!popupFrame->GetAnchor();
+}
+
+bool nsWindow::IsWidgetOverflowWindow() {
+ if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
+ nsCString nodeId;
+ this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
+ return nodeId.Equals("widget-overflow");
+ }
+ return false;
+}
+
+bool nsWindow::WaylandPopupIsFirst() {
+ return !mWaylandPopupPrev || !mWaylandPopupPrev->mWaylandToplevel;
+}
+
+nsWindow* nsWindow::GetEffectiveParent() {
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ return nullptr;
+ }
+ return get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
+}
+
+GdkPoint nsWindow::WaylandGetParentPosition() {
+ GdkPoint topLeft = {0, 0};
+ nsWindow* window = GetEffectiveParent();
+ if (window->IsPopup()) {
+ topLeft = DevicePixelsToGdkPointRoundDown(window->mBounds.TopLeft());
+ }
+ LOG("nsWindow::WaylandGetParentPosition() [%d, %d]\n", topLeft.x, topLeft.y);
+ return topLeft;
+}
+
+#ifdef MOZ_LOGGING
+void nsWindow::LogPopupHierarchy() {
+ if (!LOG_ENABLED()) {
+ return;
+ }
+
+ LOG("Widget Popup Hierarchy:\n");
+ if (!mWaylandToplevel->mWaylandPopupNext) {
+ LOG(" Empty\n");
+ } else {
+ int indent = 4;
+ nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
+ while (popup) {
+ nsPrintfCString indentString("%*s", indent, " ");
+ LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
+ "Anchored %d Visible %d MovedByRect %d\n",
+ indentString.get(), popup->GetFrameTag().get(),
+ popup->GetPopupTypeName().get(), popup, popup->WaylandPopupIsMenu(),
+ popup->WaylandPopupIsPermanent(), popup->mPopupContextMenu,
+ popup->mPopupAnchored, gtk_widget_is_visible(popup->mShell),
+ popup->mPopupUseMoveToRect);
+ indent += 4;
+ popup = popup->mWaylandPopupNext;
+ }
+ }
+
+ LOG("Layout Popup Hierarchy:\n");
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ GetLayoutPopupWidgetChain(&widgetChain);
+ if (widgetChain.Length() == 0) {
+ LOG(" Empty\n");
+ } else {
+ for (unsigned long i = 0; i < widgetChain.Length(); i++) {
+ nsWindow* window = static_cast<nsWindow*>(widgetChain[i]);
+ nsPrintfCString indentString("%*s", (int)(i + 1) * 4, " ");
+ if (window) {
+ LOG("%s %s %s nsWindow [%p] Menu %d Permanent %d ContextMenu %d "
+ "Anchored %d Visible %d MovedByRect %d\n",
+ indentString.get(), window->GetFrameTag().get(),
+ window->GetPopupTypeName().get(), window,
+ window->WaylandPopupIsMenu(), window->WaylandPopupIsPermanent(),
+ window->mPopupContextMenu, window->mPopupAnchored,
+ gtk_widget_is_visible(window->mShell), window->mPopupUseMoveToRect);
+ } else {
+ LOG("%s null window\n", indentString.get());
+ }
+ }
+ }
+}
+#endif
+
+nsWindow* nsWindow::GetTopmostWindow() {
+ if (nsView* view = nsView::GetViewFor(this)) {
+ if (nsView* parentView = view->GetParent()) {
+ if (nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr)) {
+ return static_cast<nsWindow*>(parentWidget);
+ }
+ }
+ }
+ return nullptr;
+}
+
+// Configure Wayland popup. If true is returned we need to track popup
+// in popup hierarchy. Otherwise we just show it as is.
+bool nsWindow::WaylandPopupConfigure() {
+ if (mIsDragPopup) {
+ return false;
+ }
+
+ // Don't track popups without frame
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!popupFrame) {
+ return false;
+ }
+
+ // Popup state can be changed, see Bug 1728952.
+ bool permanentStateMatches =
+ mPopupTrackInHierarchy == !WaylandPopupIsPermanent();
+
+ // Popup permanent state (noautohide attribute) can change during popup life.
+ if (mPopupTrackInHierarchyConfigured && permanentStateMatches) {
+ return mPopupTrackInHierarchy;
+ }
+
+ // Configure persistent popup params only once.
+ // WaylandPopupIsAnchored() can give it wrong value after
+ // nsMenuPopupFrame::MoveTo() call which we use in move-to-rect callback
+ // to position popup after wayland position change.
+ if (!mPopupTrackInHierarchyConfigured) {
+ mPopupAnchored = WaylandPopupIsAnchored();
+ mPopupContextMenu = WaylandPopupIsContextMenu();
+ }
+
+ LOG("nsWindow::WaylandPopupConfigure tracked %d anchored %d hint %d\n",
+ mPopupTrackInHierarchy, mPopupAnchored, int(mPopupType));
+
+ // Permanent state changed and popup is mapped.
+ // We need to switch popup type but that's done when popup is mapped
+ // by Gtk so we need to unmap the popup here.
+ // It will be mapped again by gtk_widget_show().
+ if (!permanentStateMatches && mIsMapped) {
+ LOG(" permanent state change from %d to %d, unmapping",
+ mPopupTrackInHierarchy, !WaylandPopupIsPermanent());
+ gtk_widget_unmap(mShell);
+ }
+
+ mPopupTrackInHierarchy = !WaylandPopupIsPermanent();
+ LOG(" tracked in hierarchy %d\n", mPopupTrackInHierarchy);
+
+ // See gdkwindow-wayland.c and
+ // should_map_as_popup()/should_map_as_subsurface()
+ GdkWindowTypeHint gtkTypeHint;
+ switch (mPopupType) {
+ case PopupType::Menu:
+ // GDK_WINDOW_TYPE_HINT_POPUP_MENU is mapped as xdg_popup by default.
+ // We use this type for all menu popups.
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ LOG(" popup type Menu");
+ break;
+ case PopupType::Tooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ LOG(" popup type Tooltip");
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ LOG(" popup type Utility");
+ break;
+ }
+
+ if (!mPopupTrackInHierarchy) {
+ // GDK_WINDOW_TYPE_HINT_UTILITY is mapped as wl_subsurface
+ // by default.
+ LOG(" not tracked in popup hierarchy, switch to Utility");
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+
+ mPopupTrackInHierarchyConfigured = true;
+ return mPopupTrackInHierarchy;
+}
+
+bool nsWindow::IsInPopupHierarchy() {
+ return mPopupTrackInHierarchy && mWaylandToplevel && mWaylandPopupPrev;
+}
+
+void nsWindow::AddWindowToPopupHierarchy() {
+ LOG("nsWindow::AddWindowToPopupHierarchy\n");
+ if (!GetFrame()) {
+ LOG(" Window without frame cannot be added as popup!\n");
+ return;
+ }
+
+ // Check if we're already in the hierarchy
+ if (!IsInPopupHierarchy()) {
+ mWaylandToplevel = GetTopmostWindow();
+ AppendPopupToHierarchyList(mWaylandToplevel);
+ }
+}
+
+// Wayland keeps strong popup window hierarchy. We need to track active
+// (visible) popup windows and make sure we hide popup on the same level
+// before we open another one on that level. It means that every open
+// popup needs to have an unique parent.
+void nsWindow::UpdateWaylandPopupHierarchy() {
+ LOG("nsWindow::UpdateWaylandPopupHierarchy\n");
+
+ // This popup hasn't been added to popup hierarchy yet so no need to
+ // do any configurations.
+ if (!IsInPopupHierarchy()) {
+ LOG(" popup isn't in hierarchy\n");
+ return;
+ }
+
+#ifdef MOZ_LOGGING
+ LogPopupHierarchy();
+ auto printPopupHierarchy = MakeScopeExit([&] { LogPopupHierarchy(); });
+#endif
+
+ // Hide all tooltips without the last one. Tooltip can't be popup parent.
+ mWaylandToplevel->WaylandPopupHideTooltips();
+
+ // See Bug 1709254 / https://gitlab.gnome.org/GNOME/gtk/-/issues/5092
+ // It's possible that Wayland compositor refuses to show
+ // a popup although Gtk claims it's visible.
+ // We don't know if the popup is shown or not.
+ // To avoid application crash refuse to create any child of such invisible
+ // popup and close any child of it now.
+ mWaylandToplevel->WaylandPopupCloseOrphanedPopups();
+
+ // Check if we have any remote content / overflow window in hierarchy.
+ // We can't attach such widget on top of other popup.
+ mWaylandToplevel->CloseAllPopupsBeforeRemotePopup();
+
+ // Check if your popup hierarchy matches layout hierarchy.
+ // For instance we should not connect hamburger menu on top
+ // of context menu.
+ // Close all popups from different layout chains if possible.
+ AutoTArray<nsIWidget*, 5> layoutPopupWidgetChain;
+ GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
+
+ mWaylandToplevel->WaylandPopupHierarchyHideByLayout(&layoutPopupWidgetChain);
+ mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
+ &layoutPopupWidgetChain);
+
+ // Now we have Popup hierarchy complete.
+ // Find first unchanged (and still open) popup to start with hierarchy
+ // changes.
+ nsWindow* changedPopup = mWaylandToplevel->mWaylandPopupNext;
+ while (changedPopup) {
+ // Stop when parent of this popup was changed and we need to recalc
+ // popup position.
+ if (changedPopup->mPopupChanged) {
+ break;
+ }
+ // Stop when this popup is closed.
+ if (changedPopup->mPopupClosed) {
+ break;
+ }
+ changedPopup = changedPopup->mWaylandPopupNext;
+ }
+
+ // We don't need to recompute popup positions, quit now.
+ if (!changedPopup) {
+ LOG(" changed Popup is null, quit.\n");
+ return;
+ }
+
+ LOG(" first changed popup [%p]\n", (void*)changedPopup);
+
+ // Hide parent popups if necessary (there are layout discontinuity)
+ // reposition the popup and show them again.
+ changedPopup->WaylandPopupHierarchyHideTemporary();
+
+ nsWindow* parentOfchangedPopup = nullptr;
+ if (changedPopup->mPopupClosed) {
+ parentOfchangedPopup = changedPopup->mWaylandPopupPrev;
+ }
+ changedPopup->WaylandPopupRemoveClosedPopups();
+
+ // It's possible that changedPopup was removed from widget hierarchy,
+ // in such case use child popup of the removed one if there's any.
+ if (!changedPopup->IsInPopupHierarchy()) {
+ if (!parentOfchangedPopup || !parentOfchangedPopup->mWaylandPopupNext) {
+ LOG(" last popup was removed, quit.\n");
+ return;
+ }
+ changedPopup = parentOfchangedPopup->mWaylandPopupNext;
+ }
+
+ GetLayoutPopupWidgetChain(&layoutPopupWidgetChain);
+ mWaylandToplevel->WaylandPopupHierarchyValidateByLayout(
+ &layoutPopupWidgetChain);
+
+ changedPopup->WaylandPopupHierarchyCalculatePositions();
+
+ nsWindow* popup = changedPopup;
+ while (popup) {
+ const bool useMoveToRect = [&] {
+ if (!StaticPrefs::widget_wayland_use_move_to_rect_AtStartup()) {
+ return false; // Not available.
+ }
+ if (!popup->mPopupMatchesLayout) {
+ // We can use move_to_rect only when popups in popup hierarchy matches
+ // layout hierarchy as move_to_rect request that parent/child
+ // popups are adjacent.
+ return false;
+ }
+ if (popup->mPopupType == PopupType::Panel &&
+ popup->WaylandPopupIsFirst() &&
+ popup->WaylandPopupFitsToplevelWindow(/* aMove */ true)) {
+ // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/issues/1986
+ //
+ // PopupType::Panel types are used for extension popups which may be
+ // resized. If such popup uses move-to-rect, we need to hide it before
+ // resize and show it again. That leads to massive flickering
+ // so use plain move if possible to avoid it.
+ //
+ // Bug 1760276 - don't use move-to-rect when popup is inside main
+ // Firefox window.
+ //
+ // Use it for first popups only due to another mutter bug
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/5089
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1784873
+ return false;
+ }
+ if (!popup->WaylandPopupIsFirst() &&
+ !popup->mWaylandPopupPrev->WaylandPopupIsFirst() &&
+ !popup->mWaylandPopupPrev->mPopupUseMoveToRect) {
+ // We can't use move-to-rect if there are more parents of
+ // wl_subsurface popups types.
+ //
+ // It's because wl_subsurface is ignored by xgd_popup
+ // (created by move-to-rect) so our popup scenario:
+ //
+ // toplevel -> xgd_popup(1) -> wl_subsurface(2) -> xgd_popup(3)
+ //
+ // looks for Wayland compositor as:
+ //
+ // toplevel -> xgd_popup(1) -> xgd_popup(3)
+ //
+ // If xgd_popup(1) and xgd_popup(3) are not connected
+ // move-to-rect applied to xgd_popup(3) fails and we get missing popup.
+ return false;
+ }
+ return true;
+ }();
+
+ LOG(" popup [%p] matches layout [%d] anchored [%d] first popup [%d] use "
+ "move-to-rect %d\n",
+ popup, popup->mPopupMatchesLayout, popup->mPopupAnchored,
+ popup->WaylandPopupIsFirst(), useMoveToRect);
+
+ popup->mPopupUseMoveToRect = useMoveToRect;
+ popup->WaylandPopupMoveImpl();
+ popup->mPopupChanged = false;
+ popup = popup->mWaylandPopupNext;
+ }
+
+ changedPopup->WaylandPopupHierarchyShowTemporaryHidden();
+}
+
+static void NativeMoveResizeCallback(GdkWindow* window,
+ const GdkRectangle* flipped_rect,
+ const GdkRectangle* final_rect,
+ gboolean flipped_x, gboolean flipped_y,
+ void* aWindow) {
+ LOG_POPUP("[%p] NativeMoveResizeCallback flipped_x %d flipped_y %d\n",
+ aWindow, flipped_x, flipped_y);
+ LOG_POPUP("[%p] new position [%d, %d] -> [%d x %d]", aWindow,
+ final_rect->x, final_rect->y, final_rect->width,
+ final_rect->height);
+ nsWindow* wnd = get_window_for_gdk_window(window);
+
+ wnd->NativeMoveResizeWaylandPopupCallback(final_rect, flipped_x, flipped_y);
+}
+
+// When popup is repositioned by widget code, we need to notify
+// layout about it. It's because we control popup placement
+// on widget on Wayland so layout may have old popup size/coordinates.
+void nsWindow::WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize) {
+ LOG("nsWindow::WaylandPopupPropagateChangesToLayout()");
+
+ if (aResize) {
+ LOG(" needSizeUpdate\n");
+ if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
+ RefPtr<PresShell> presShell = popupFrame->PresShell();
+ presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::None,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+ if (aMove) {
+ LOG(" needPositionUpdate, bounds [%d, %d]", mBounds.x, mBounds.y);
+ NotifyWindowMoved(mBounds.x, mBounds.y, ByMoveToRect::Yes);
+ }
+}
+
+void nsWindow::NativeMoveResizeWaylandPopupCallback(
+ const GdkRectangle* aFinalSize, bool aFlippedX, bool aFlippedY) {
+ // We're getting move-to-rect callback without move-to-rect call.
+ // That indicates a compositor bug. It happens when a window is hidden and
+ // shown again before move-to-rect callback is fired.
+ // It may lead to incorrect popup placement as we may call
+ // gtk_window_move() between hide & show.
+ // See Bug 1777919, 1789581.
+#if MOZ_LOGGING
+ if (!mWaitingForMoveToRectCallback) {
+ LOG(" Bogus move-to-rect callback! Expect wrong popup coordinates.");
+ }
+#endif
+
+ mWaitingForMoveToRectCallback = false;
+
+ bool movedByLayout = mMovedAfterMoveToRect;
+ bool resizedByLayout = mResizedAfterMoveToRect;
+
+ // Popup was moved between move-to-rect call and move-to-rect callback
+ // and the coordinates from move-to-rect callback are outdated.
+ if (movedByLayout || resizedByLayout) {
+ LOG(" Another move/resize called during waiting for callback\n");
+ mMovedAfterMoveToRect = false;
+ mResizedAfterMoveToRect = false;
+ // Fire another round of move/resize to reflect latest request
+ // from layout.
+ NativeMoveResize(movedByLayout, resizedByLayout);
+ return;
+ }
+
+ LOG(" orig mBounds [%d, %d] -> [%d x %d]\n", mBounds.x, mBounds.y,
+ mBounds.width, mBounds.height);
+
+ LayoutDeviceIntRect newBounds = [&] {
+ GdkRectangle finalRect = *aFinalSize;
+ GdkPoint parent = WaylandGetParentPosition();
+ finalRect.x += parent.x;
+ finalRect.y += parent.y;
+ return GdkRectToDevicePixels(finalRect);
+ }();
+
+ LOG(" new mBounds [%d, %d] -> [%d x %d]", newBounds.x, newBounds.y,
+ newBounds.width, newBounds.height);
+
+ bool needsPositionUpdate = newBounds.TopLeft() != mBounds.TopLeft();
+ bool needsSizeUpdate = newBounds.Size() != mLastSizeRequest;
+
+ if (needsSizeUpdate) {
+ // Wayland compositor changed popup size request from layout.
+ // Set the constraints to use them in nsMenuPopupFrame::SetPopupPosition().
+ // Beware that gtk_window_resize() requests sizes asynchronously and so
+ // newBounds might not have the size from the most recent
+ // gtk_window_resize().
+ if (newBounds.width < mLastSizeRequest.width) {
+ mMoveToRectPopupSize.width = newBounds.width;
+ }
+ if (newBounds.height < mLastSizeRequest.height) {
+ mMoveToRectPopupSize.height = newBounds.height;
+ }
+ LOG(" mMoveToRectPopupSize set to [%d, %d]", mMoveToRectPopupSize.width,
+ mMoveToRectPopupSize.height);
+ }
+ mBounds = newBounds;
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in PopupCallback " << mBounds
+ << " size state " << mSizeMode;
+ }
+ WaylandPopupPropagateChangesToLayout(needsPositionUpdate, needsSizeUpdate);
+}
+
+static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ return GDK_GRAVITY_NORTH_WEST;
+ case POPUPALIGNMENT_TOPLEFT:
+ return GDK_GRAVITY_NORTH_WEST;
+ case POPUPALIGNMENT_TOPRIGHT:
+ return GDK_GRAVITY_NORTH_EAST;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return GDK_GRAVITY_SOUTH_WEST;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return GDK_GRAVITY_SOUTH_EAST;
+ case POPUPALIGNMENT_LEFTCENTER:
+ return GDK_GRAVITY_WEST;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return GDK_GRAVITY_EAST;
+ case POPUPALIGNMENT_TOPCENTER:
+ return GDK_GRAVITY_NORTH;
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return GDK_GRAVITY_SOUTH;
+ }
+ return GDK_GRAVITY_STATIC;
+}
+
+bool nsWindow::IsPopupDirectionRTL() {
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ return popupFrame && popupFrame->IsDirectionRTL();
+}
+
+// Position the popup directly by gtk_window_move() and try to keep it
+// on screen by just moving it in scope of it's parent window.
+//
+// It's used when we position noautihode popup and we don't use xdg_positioner.
+// See Bug 1718867
+void nsWindow::WaylandPopupSetDirectPosition() {
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::WaylandPopupSetDirectPosition %d,%d -> %d x %d\n", topLeft.x,
+ topLeft.y, size.width, size.height);
+
+ mPopupPosition = {topLeft.x, topLeft.y};
+
+ if (mIsDragPopup) {
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ // DND window is placed inside container so we need to make hard size
+ // request to ensure parent container is resized too.
+ gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width, size.height);
+ return;
+ }
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ nsWindow* window = get_window_for_gtk_widget(GTK_WIDGET(parentGtkWindow));
+ if (!window) {
+ return;
+ }
+ GdkWindow* gdkWindow = window->GetGdkWindow();
+ if (!gdkWindow) {
+ return;
+ }
+
+ int parentWidth = gdk_window_get_width(gdkWindow);
+ int popupWidth = size.width;
+
+ int x;
+ gdk_window_get_position(gdkWindow, &x, nullptr);
+
+ // If popup is bigger than main window just center it.
+ if (popupWidth > parentWidth) {
+ mPopupPosition.x = -(parentWidth - popupWidth) / 2 + x;
+ } else {
+ if (IsPopupDirectionRTL()) {
+ // Stick with right window edge
+ if (mPopupPosition.x < x) {
+ mPopupPosition.x = x;
+ }
+ } else {
+ // Stick with left window edge
+ if (mPopupPosition.x + popupWidth > parentWidth + x) {
+ mPopupPosition.x = parentWidth + x - popupWidth;
+ }
+ }
+ }
+
+ LOG(" set position [%d, %d]\n", mPopupPosition.x, mPopupPosition.y);
+ gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
+
+ LOG(" set size [%d, %d]\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+
+ if (mPopupPosition.x != topLeft.x) {
+ mBounds.MoveTo(GdkPointToDevicePixels(mPopupPosition));
+ LOG(" setting new bounds [%d, %d]\n", mBounds.x, mBounds.y);
+ WaylandPopupPropagateChangesToLayout(/* move */ true, /* resize */ false);
+ }
+}
+
+bool nsWindow::WaylandPopupFitsToplevelWindow(bool aMove) {
+ LOG("nsWindow::WaylandPopupFitsToplevelWindow() move %d", aMove);
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ GtkWindow* tmp = parent;
+ while ((tmp = gtk_window_get_transient_for(GTK_WINDOW(parent)))) {
+ parent = tmp;
+ }
+ GdkWindow* toplevelGdkWindow = gtk_widget_get_window(GTK_WIDGET(parent));
+ if (!toplevelGdkWindow) {
+ NS_WARNING("Toplevel widget without GdkWindow?");
+ return false;
+ }
+
+ int parentWidth = gdk_window_get_width(toplevelGdkWindow);
+ int parentHeight = gdk_window_get_height(toplevelGdkWindow);
+ LOG(" parent size %d x %d", parentWidth, parentHeight);
+
+ GdkPoint topLeft = aMove ? mPopupPosition
+ : DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+ LOG(" popup topleft %d, %d size %d x %d", topLeft.x, topLeft.y, size.width,
+ size.height);
+ int fits = topLeft.x >= 0 && topLeft.y >= 0 &&
+ topLeft.x + size.width <= parentWidth &&
+ topLeft.y + size.height <= parentHeight;
+
+ LOG(" fits %d", fits);
+ return fits;
+}
+
+void nsWindow::NativeMoveResizeWaylandPopup(bool aMove, bool aResize) {
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::NativeMoveResizeWaylandPopup Bounds %d,%d -> %d x %d move %d "
+ "resize %d\n",
+ topLeft.x, topLeft.y, size.width, size.height, aMove, aResize);
+
+ // Compositor may be confused by windows with width/height = 0
+ // and positioning such windows leads to Bug 1555866.
+ if (!AreBoundsSane()) {
+ LOG(" Bounds are not sane (width: %d height: %d)\n",
+ mLastSizeRequest.width, mLastSizeRequest.height);
+ return;
+ }
+
+ if (mWaitingForMoveToRectCallback) {
+ LOG(" waiting for move to rect, scheduling");
+ // mBounds position must not be overwritten before it is applied.
+ // OnConfigureEvent() will not set mBounds to an old position for
+ // GTK_WINDOW_POPUP.
+ MOZ_ASSERT(gtk_window_get_window_type(GTK_WINDOW(mShell)) ==
+ GTK_WINDOW_POPUP);
+ mMovedAfterMoveToRect = aMove;
+ mResizedAfterMoveToRect = aResize;
+ return;
+ }
+
+ mMovedAfterMoveToRect = false;
+ mResizedAfterMoveToRect = false;
+
+ bool trackedInHierarchy = WaylandPopupConfigure();
+
+ // Read popup position from layout if it was moved or newly created.
+ // This position is used by move-to-rect method as we need anchor and other
+ // info to place popup correctly.
+ // We need WaylandPopupConfigure() to be called before to have all needed
+ // popup info in place (mainly the anchored flag).
+ if (aMove) {
+ mPopupMoveToRectParams = WaylandPopupGetPositionFromLayout();
+ }
+
+ if (!trackedInHierarchy) {
+ WaylandPopupSetDirectPosition();
+ return;
+ }
+
+ if (aResize) {
+ LOG(" set size [%d, %d]\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+
+ if (!aMove && WaylandPopupFitsToplevelWindow(aMove)) {
+ // Popup position has not been changed and its position/size fits
+ // parent window so no need to reposition the window.
+ LOG(" fits parent window size, just resize\n");
+ return;
+ }
+
+ // Mark popup as changed as we're updating position/size.
+ mPopupChanged = true;
+
+ // Save popup position for former re-calculations when popup hierarchy
+ // is changed.
+ LOG(" popup position changed from [%d, %d] to [%d, %d]\n", mPopupPosition.x,
+ mPopupPosition.y, topLeft.x, topLeft.y);
+ mPopupPosition = {topLeft.x, topLeft.y};
+
+ UpdateWaylandPopupHierarchy();
+}
+
+struct PopupSides {
+ Maybe<Side> mVertical;
+ Maybe<Side> mHorizontal;
+};
+
+static PopupSides SidesForPopupAlignment(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ return {Some(eSideTop), Some(eSideLeft)};
+ case POPUPALIGNMENT_TOPRIGHT:
+ return {Some(eSideTop), Some(eSideRight)};
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return {Some(eSideBottom), Some(eSideLeft)};
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return {Some(eSideBottom), Some(eSideRight)};
+ case POPUPALIGNMENT_LEFTCENTER:
+ return {Nothing(), Some(eSideLeft)};
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return {Nothing(), Some(eSideRight)};
+ case POPUPALIGNMENT_TOPCENTER:
+ return {Some(eSideTop), Nothing()};
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return {Some(eSideBottom), Nothing()};
+ }
+ return {};
+}
+
+// We want to apply margins based on popup alignment (which would generally be
+// just an offset to apply to the popup). However, to deal with flipping
+// correctly, we apply the margin to the anchor when possible.
+struct ResolvedPopupMargin {
+ // A margin to be applied to the anchor.
+ nsMargin mAnchorMargin;
+ // An offset in app units to be applied to the popup for when we need to tell
+ // GTK to center inside the anchor precisely (so we can't really do better in
+ // presence of flips).
+ nsPoint mPopupOffset;
+};
+
+static ResolvedPopupMargin ResolveMargin(nsMenuPopupFrame* aFrame,
+ int8_t aPopupAlign,
+ int8_t aAnchorAlign,
+ bool aAnchoredToPoint,
+ bool aIsContextMenu) {
+ nsMargin margin = aFrame->GetMargin();
+ nsPoint offset;
+
+ if (aAnchoredToPoint) {
+ // Since GTK doesn't allow us to specify margins itself, when anchored to a
+ // point we can just assume we'll be aligned correctly... This is kind of
+ // annoying but alas.
+ //
+ // This calculation must match the relevant unanchored popup calculation in
+ // nsMenuPopupFrame::SetPopupPosition(), which should itself be the inverse
+ // inverse of nsMenuPopupFrame::MoveTo().
+ if (aIsContextMenu && aFrame->IsDirectionRTL()) {
+ offset.x = -margin.right;
+ } else {
+ offset.x = margin.left;
+ }
+ offset.y = margin.top;
+ return {nsMargin(), offset};
+ }
+
+ auto popupSides = SidesForPopupAlignment(aPopupAlign);
+ auto anchorSides = SidesForPopupAlignment(aAnchorAlign);
+ // Matched sides: Invert the margin, so that we pull in the right direction.
+ // Popup not aligned to any anchor side: We give up and use the offset,
+ // applying the margin from the popup side.
+ // Mismatched sides: We swap the margins so that we pull in the right
+ // direction, e.g. margin-left: -10px should shrink 10px the _right_ of the
+ // box, not the left of the box.
+ if (popupSides.mHorizontal == anchorSides.mHorizontal) {
+ margin.left = -margin.left;
+ margin.right = -margin.right;
+ } else if (!anchorSides.mHorizontal) {
+ auto popupSide = *popupSides.mHorizontal;
+ offset.x += popupSide == eSideRight ? -margin.Side(popupSide)
+ : margin.Side(popupSide);
+ margin.left = margin.right = 0;
+ } else {
+ std::swap(margin.left, margin.right);
+ }
+
+ // Same logic as above, but in the vertical direction.
+ if (popupSides.mVertical == anchorSides.mVertical) {
+ margin.top = -margin.top;
+ margin.bottom = -margin.bottom;
+ } else if (!anchorSides.mVertical) {
+ auto popupSide = *popupSides.mVertical;
+ offset.y += popupSide == eSideBottom ? -margin.Side(popupSide)
+ : margin.Side(popupSide);
+ margin.top = margin.bottom = 0;
+ } else {
+ std::swap(margin.top, margin.bottom);
+ }
+
+ return {margin, offset};
+}
+
+#ifdef MOZ_LOGGING
+void nsWindow::LogPopupAnchorHints(int aHints) {
+ static struct hints_ {
+ int hint;
+ char name[100];
+ } hints[] = {
+ {GDK_ANCHOR_FLIP_X, "GDK_ANCHOR_FLIP_X"},
+ {GDK_ANCHOR_FLIP_Y, "GDK_ANCHOR_FLIP_Y"},
+ {GDK_ANCHOR_SLIDE_X, "GDK_ANCHOR_SLIDE_X"},
+ {GDK_ANCHOR_SLIDE_Y, "GDK_ANCHOR_SLIDE_Y"},
+ {GDK_ANCHOR_RESIZE_X, "GDK_ANCHOR_RESIZE_X"},
+ {GDK_ANCHOR_RESIZE_Y, "GDK_ANCHOR_RESIZE_X"},
+ };
+
+ LOG(" PopupAnchorHints");
+ for (const auto& hint : hints) {
+ if (hint.hint & aHints) {
+ LOG(" %s", hint.name);
+ }
+ }
+}
+
+void nsWindow::LogPopupGravity(GdkGravity aGravity) {
+ static char gravity[][100]{"NONE",
+ "GDK_GRAVITY_NORTH_WEST",
+ "GDK_GRAVITY_NORTH",
+ "GDK_GRAVITY_NORTH_EAST",
+ "GDK_GRAVITY_WEST",
+ "GDK_GRAVITY_CENTER",
+ "GDK_GRAVITY_EAST",
+ "GDK_GRAVITY_SOUTH_WEST",
+ "GDK_GRAVITY_SOUTH",
+ "GDK_GRAVITY_SOUTH_EAST",
+ "GDK_GRAVITY_STATIC"};
+ LOG(" %s", gravity[aGravity]);
+}
+#endif
+
+const nsWindow::WaylandPopupMoveToRectParams
+nsWindow::WaylandPopupGetPositionFromLayout() {
+ LOG("nsWindow::WaylandPopupGetPositionFromLayout\n");
+
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+
+ const bool isTopContextMenu = mPopupContextMenu && !mPopupAnchored;
+ const bool isRTL = IsPopupDirectionRTL();
+ const bool anchored = popupFrame->IsAnchored();
+ int8_t popupAlign = POPUPALIGNMENT_TOPLEFT;
+ int8_t anchorAlign = POPUPALIGNMENT_BOTTOMRIGHT;
+ if (anchored) {
+ // See nsMenuPopupFrame::AdjustPositionForAnchorAlign.
+ popupAlign = popupFrame->GetPopupAlignment();
+ anchorAlign = popupFrame->GetPopupAnchor();
+ }
+ if (isRTL && (anchored || isTopContextMenu)) {
+ popupAlign = -popupAlign;
+ anchorAlign = -anchorAlign;
+ }
+
+ // Although we have mPopupPosition / mRelativePopupPosition here
+ // we can't use it. move-to-rect needs anchor rectangle to position a popup
+ // but we have only a point from Resize().
+ //
+ // So we need to extract popup position from nsMenuPopupFrame() and duplicate
+ // the layout work here.
+ LayoutDeviceIntRect anchorRect;
+ ResolvedPopupMargin popupMargin;
+ {
+ nsRect anchorRectAppUnits = popupFrame->GetUntransformedAnchorRect();
+ // This is a somewhat hacky way of applying the popup margin. We don't know
+ // if GTK will end up flipping the popup, in which case the offset we
+ // compute is just wrong / applied to the wrong side.
+ //
+ // Instead, we tell it to anchor us at a smaller or bigger rect depending on
+ // the margin, which achieves the same result if the popup is positioned
+ // correctly, but doesn't misposition the popup when flipped across the
+ // anchor.
+ popupMargin = ResolveMargin(popupFrame, popupAlign, anchorAlign,
+ anchorRectAppUnits.IsEmpty(), isTopContextMenu);
+ LOG(" layout popup CSS anchor (%d, %d) %s, margin %s offset %s\n",
+ popupAlign, anchorAlign, ToString(anchorRectAppUnits).c_str(),
+ ToString(popupMargin.mAnchorMargin).c_str(),
+ ToString(popupMargin.mPopupOffset).c_str());
+ anchorRectAppUnits.Inflate(popupMargin.mAnchorMargin);
+ LOG(" after margins %s\n", ToString(anchorRectAppUnits).c_str());
+ nscoord auPerDev = popupFrame->PresContext()->AppUnitsPerDevPixel();
+ anchorRect = LayoutDeviceIntRect::FromAppUnitsToNearest(anchorRectAppUnits,
+ auPerDev);
+ if (anchorRect.width < 0) {
+ auto w = -anchorRect.width;
+ anchorRect.width += w + 1;
+ anchorRect.x += w;
+ }
+ LOG(" final %s\n", ToString(anchorRect).c_str());
+ }
+
+ LOG(" relative popup rect position [%d, %d] -> [%d x %d]\n", anchorRect.x,
+ anchorRect.y, anchorRect.width, anchorRect.height);
+
+ // Get gravity and flip type
+ GdkGravity rectAnchor = PopupAlignmentToGdkGravity(anchorAlign);
+ GdkGravity menuAnchor = PopupAlignmentToGdkGravity(popupAlign);
+
+ LOG(" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor);
+
+ // Gtk default is: GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE | GDK_ANCHOR_RESIZE.
+ // We want to SLIDE_X menu on the dual monitor setup rather than resize it
+ // on the other monitor.
+ GdkAnchorHints hints =
+ GdkAnchorHints(GDK_ANCHOR_FLIP | GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_RESIZE);
+
+ // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
+ int8_t position = popupFrame->GetAlignmentPosition();
+ if (position >= POPUPPOSITION_BEFORESTART &&
+ position <= POPUPPOSITION_AFTEREND) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_X);
+ }
+ // slideVertical from nsMenuPopupFrame::SetPopupPosition
+ if (position >= POPUPPOSITION_STARTBEFORE &&
+ position <= POPUPPOSITION_ENDAFTER) {
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE_Y);
+ }
+
+ FlipType flipType = popupFrame->GetFlipType();
+ if (rectAnchor == GDK_GRAVITY_CENTER && menuAnchor == GDK_GRAVITY_CENTER) {
+ // only slide
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ } else {
+ switch (flipType) {
+ case FlipType_Both:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ case FlipType_Slide:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ break;
+ case FlipType_Default:
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_FLIP);
+ break;
+ default:
+ break;
+ }
+ }
+ if (!WaylandPopupIsMenu()) {
+ // we don't want to slide menus to fit the screen rather resize them
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ }
+
+ // We want tooltips to flip verticaly or slide only.
+ // See nsMenuPopupFrame::SetPopupPosition().
+ // https://searchfox.org/mozilla-central/rev/d0f5bc50aff3462c9d1546b88d60c5cb020eb15c/layout/xul/nsMenuPopupFrame.cpp#1603
+ if (mPopupType == PopupType::Tooltip) {
+ hints = GdkAnchorHints(GDK_ANCHOR_FLIP_Y | GDK_ANCHOR_SLIDE);
+ }
+
+ return {
+ anchorRect,
+ rectAnchor,
+ menuAnchor,
+ hints,
+ DevicePixelsToGdkPointRoundDown(LayoutDevicePoint::FromAppUnitsToNearest(
+ popupMargin.mPopupOffset,
+ popupFrame->PresContext()->AppUnitsPerDevPixel())),
+ true};
+}
+
+bool nsWindow::WaylandPopupAnchorAdjustForParentPopup(
+ GdkRectangle* aPopupAnchor, GdkPoint* aOffset) {
+ LOG("nsWindow::WaylandPopupAnchorAdjustForParentPopup");
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ NS_WARNING("Popup has no parent!");
+ return false;
+ }
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(parentGtkWindow));
+ if (!window) {
+ NS_WARNING("Popup parrent is not mapped!");
+ return false;
+ }
+
+ GdkRectangle parentWindowRect = {0, 0, gdk_window_get_width(window),
+ gdk_window_get_height(window)};
+ LOG(" parent window size %d x %d", parentWindowRect.width,
+ parentWindowRect.height);
+
+ // We can't have rectangle anchor with zero width/height.
+ if (!aPopupAnchor->width) {
+ aPopupAnchor->width = 1;
+ }
+ if (!aPopupAnchor->height) {
+ aPopupAnchor->height = 1;
+ }
+
+ GdkRectangle finalRect;
+ if (!gdk_rectangle_intersect(aPopupAnchor, &parentWindowRect, &finalRect)) {
+ return false;
+ }
+ *aPopupAnchor = finalRect;
+ LOG(" anchor is correct %d,%d -> %d x %d", finalRect.x, finalRect.y,
+ finalRect.width, finalRect.height);
+
+ *aOffset = mPopupMoveToRectParams.mOffset;
+ LOG(" anchor offset %d, %d", aOffset->x, aOffset->y);
+ return true;
+}
+
+bool nsWindow::WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
+ GdkPoint* aOffset) {
+ LOG("nsWindow::WaylandPopupCheckAndGetAnchor");
+
+ GdkWindow* gdkWindow = GetToplevelGdkWindow();
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (!gdkWindow || !popupFrame) {
+ LOG(" can't use move-to-rect due missing gdkWindow or popupFrame");
+ return false;
+ }
+
+ if (popupFrame->IsConstrainedByLayout()) {
+ LOG(" can't use move-to-rect, flipped / constrained by layout");
+ return false;
+ }
+
+ if (!mPopupMoveToRectParams.mAnchorSet) {
+ LOG(" can't use move-to-rect due missing anchor");
+ return false;
+ }
+ // Update popup layout coordinates from layout by recent popup hierarchy
+ // (calculate correct position according to parent window)
+ // and convert to Gtk coordinates.
+ LayoutDeviceIntRect anchorRect = mPopupMoveToRectParams.mAnchorRect;
+ if (!WaylandPopupIsFirst()) {
+ GdkPoint parent = WaylandGetParentPosition();
+ LOG(" subtract parent position from anchor [%d, %d]\n", parent.x,
+ parent.y);
+ anchorRect.MoveBy(-GdkPointToDevicePixels(parent));
+ }
+
+ *aPopupAnchor = DevicePixelsToGdkRectRoundOut(anchorRect);
+ LOG(" anchored to rectangle [%d, %d] -> [%d x %d]", aPopupAnchor->x,
+ aPopupAnchor->y, aPopupAnchor->width, aPopupAnchor->height);
+
+ if (!WaylandPopupAnchorAdjustForParentPopup(aPopupAnchor, aOffset)) {
+ LOG(" can't use move-to-rect, anchor is not placed inside of parent "
+ "window");
+ return false;
+ }
+
+ return true;
+}
+
+void nsWindow::WaylandPopupPrepareForMove() {
+ LOG("nsWindow::WaylandPopupPrepareForMove()");
+
+ if (mPopupType == PopupType::Tooltip) {
+ // Don't fiddle with tooltips type, just hide it before move-to-rect
+ if (mPopupUseMoveToRect && gtk_widget_is_visible(mShell)) {
+ HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ }
+ LOG(" it's tooltip, quit");
+ return;
+ }
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1785185#c8
+ // gtk_window_move() needs GDK_WINDOW_TYPE_HINT_UTILITY popup type.
+ // move-to-rect requires GDK_WINDOW_TYPE_HINT_POPUP_MENU popups type.
+ // We need to set it before map event when popup is hidden.
+ const GdkWindowTypeHint currentType =
+ gtk_window_get_type_hint(GTK_WINDOW(mShell));
+ const GdkWindowTypeHint requiredType = mPopupUseMoveToRect
+ ? GDK_WINDOW_TYPE_HINT_POPUP_MENU
+ : GDK_WINDOW_TYPE_HINT_UTILITY;
+
+ if (!mPopupUseMoveToRect && currentType == requiredType) {
+ LOG(" type matches and we're not forced to hide it, quit.");
+ return;
+ }
+
+ if (gtk_widget_is_visible(mShell)) {
+ HideWaylandPopupWindow(/* aTemporaryHide */ true,
+ /* aRemoveFromPopupList */ false);
+ }
+
+ if (currentType != requiredType) {
+ LOG(" set type %s",
+ requiredType == GDK_WINDOW_TYPE_HINT_POPUP_MENU ? "MENU" : "UTILITY");
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), requiredType);
+ }
+}
+
+// Plain popup move on Wayland - simply place popup on given location.
+// We can't just call gtk_window_move() as it's not effective on visible
+// popups.
+void nsWindow::WaylandPopupMovePlain(int aX, int aY) {
+ LOG("nsWindow::WaylandPopupMovePlain(%d, %d)", aX, aY);
+
+ // We can directly move only popups based on wl_subsurface type.
+ MOZ_DIAGNOSTIC_ASSERT(gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY ||
+ gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_TOOLTIP);
+
+ gtk_window_move(GTK_WINDOW(mShell), aX, aY);
+
+ // gtk_window_move() can trick us. When widget is hidden gtk_window_move()
+ // does not move the widget but sets new widget coordinates when widget
+ // is mapped again.
+ //
+ // If popup used move-to-rect before
+ // (GdkWindow has POSITION_METHOD_MOVE_TO_RECT set), popup will use
+ // move-to-rect again when it's mapped and we'll get bogus move-to-rect
+ // callback.
+ //
+ // gdk_window_move() sets position_method to POSITION_METHOD_MOVE_RESIZE
+ // so we'll use plain move when popup is shown.
+ if (!gtk_widget_get_mapped(mShell)) {
+ if (GdkWindow* window = GetToplevelGdkWindow()) {
+ gdk_window_move(window, aX, aY);
+ }
+ }
+}
+
+void nsWindow::WaylandPopupMoveImpl() {
+ // Available as of GTK 3.24+
+ static auto sGdkWindowMoveToRect = (void (*)(
+ GdkWindow*, const GdkRectangle*, GdkGravity, GdkGravity, GdkAnchorHints,
+ gint, gint))dlsym(RTLD_DEFAULT, "gdk_window_move_to_rect");
+
+ if (mPopupUseMoveToRect && !sGdkWindowMoveToRect) {
+ LOG("can't use move-to-rect due missing gdk_window_move_to_rect()");
+ mPopupUseMoveToRect = false;
+ }
+
+ GdkRectangle gtkAnchorRect;
+ GdkPoint offset;
+ if (mPopupUseMoveToRect) {
+ mPopupUseMoveToRect =
+ WaylandPopupCheckAndGetAnchor(&gtkAnchorRect, &offset);
+ }
+
+ LOG("nsWindow::WaylandPopupMove");
+ LOG(" original widget popup position [%d, %d]\n", mPopupPosition.x,
+ mPopupPosition.y);
+ LOG(" relative widget popup position [%d, %d]\n", mRelativePopupPosition.x,
+ mRelativePopupPosition.y);
+ LOG(" popup use move to rect %d", mPopupUseMoveToRect);
+
+ WaylandPopupPrepareForMove();
+
+ if (!mPopupUseMoveToRect) {
+ WaylandPopupMovePlain(mRelativePopupPosition.x, mRelativePopupPosition.y);
+ // Layout already should be aware of our bounds, since we didn't change it
+ // from the widget side for flipping or so.
+ return;
+ }
+
+ // Correct popup position now. It will be updated by gdk_window_move_to_rect()
+ // anyway but we need to set it now to avoid a race condition here.
+ WaylandPopupRemoveNegativePosition();
+
+ GdkWindow* gdkWindow = GetToplevelGdkWindow();
+ if (!g_signal_handler_find(gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeCallback), this)) {
+ g_signal_connect(gdkWindow, "moved-to-rect",
+ G_CALLBACK(NativeMoveResizeCallback), this);
+ }
+ mWaitingForMoveToRectCallback = true;
+
+#ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ LOG(" Call move-to-rect");
+ LOG(" Anchor rect [%d, %d] -> [%d x %d]", gtkAnchorRect.x, gtkAnchorRect.y,
+ gtkAnchorRect.width, gtkAnchorRect.height);
+ LOG(" Offset [%d, %d]", offset.x, offset.y);
+ LOG(" AnchorType");
+ LogPopupGravity(mPopupMoveToRectParams.mAnchorRectType);
+ LOG(" PopupAnchorType");
+ LogPopupGravity(mPopupMoveToRectParams.mPopupAnchorType);
+ LogPopupAnchorHints(mPopupMoveToRectParams.mHints);
+ }
+#endif
+
+ sGdkWindowMoveToRect(gdkWindow, &gtkAnchorRect,
+ mPopupMoveToRectParams.mAnchorRectType,
+ mPopupMoveToRectParams.mPopupAnchorType,
+ mPopupMoveToRectParams.mHints, offset.x, offset.y);
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ nsIWidget* oldPrev = GetPrevSibling();
+
+ nsBaseWidget::SetZIndex(aZIndex);
+
+ if (GetPrevSibling() == oldPrev) {
+ return;
+ }
+
+ // We skip the nsWindows that don't have mGdkWindows.
+ // These are probably in the process of being destroyed.
+ if (!mGdkWindow) {
+ return;
+ }
+
+ if (!GetNextSibling()) {
+ // We're to be on top.
+ if (mGdkWindow) {
+ gdk_window_raise(mGdkWindow);
+ }
+ } else {
+ // All the siblings before us need to be below our widget.
+ for (nsWindow* w = this; w;
+ w = static_cast<nsWindow*>(w->GetPrevSibling())) {
+ if (w->mGdkWindow) {
+ gdk_window_lower(w->mGdkWindow);
+ }
+ }
+ }
+}
+
+void nsWindow::SetSizeMode(nsSizeMode aMode) {
+ LOG("nsWindow::SetSizeMode %d\n", aMode);
+
+ // Return if there's no shell or our current state is the same as the mode we
+ // were just set to.
+ if (!mShell) {
+ LOG(" no shell");
+ return;
+ }
+
+ if (mSizeMode == aMode && mLastSizeModeRequest == aMode) {
+ LOG(" already set");
+ return;
+ }
+
+ // It is tempting to try to optimize calls below based only on current
+ // mSizeMode, but that wouldn't work if there's a size-request in flight
+ // (specially before show). See bug 1789823.
+ const auto SizeModeMightBe = [&](nsSizeMode aModeToTest) {
+ if (mSizeMode != mLastSizeModeRequest) {
+ // Arbitrary size mode requests might be ongoing.
+ return true;
+ }
+ return mSizeMode == aModeToTest;
+ };
+
+ if (aMode != nsSizeMode_Fullscreen && aMode != nsSizeMode_Minimized) {
+ // Fullscreen and minimized are compatible.
+ if (SizeModeMightBe(nsSizeMode_Fullscreen)) {
+ MakeFullScreen(false);
+ }
+ }
+
+ switch (aMode) {
+ case nsSizeMode_Maximized:
+ LOG(" set maximized");
+ gtk_window_maximize(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Minimized:
+ LOG(" set minimized");
+ gtk_window_iconify(GTK_WINDOW(mShell));
+ break;
+ case nsSizeMode_Fullscreen:
+ LOG(" set fullscreen");
+ MakeFullScreen(true);
+ break;
+ default:
+ MOZ_FALLTHROUGH_ASSERT("Unknown size mode");
+ case nsSizeMode_Normal:
+ LOG(" set normal");
+ if (SizeModeMightBe(nsSizeMode_Maximized)) {
+ gtk_window_unmaximize(GTK_WINDOW(mShell));
+ }
+ if (SizeModeMightBe(nsSizeMode_Minimized)) {
+ gtk_window_deiconify(GTK_WINDOW(mShell));
+ // We need this for actual deiconification on mutter.
+ gtk_window_present(GTK_WINDOW(mShell));
+ }
+ break;
+ }
+ mLastSizeModeRequest = aMode;
+}
+
+#define kDesktopMutterSchema "org.gnome.mutter"_ns
+#define kDesktopDynamicWorkspacesKey "dynamic-workspaces"_ns
+
+static bool WorkspaceManagementDisabled(GdkScreen* screen) {
+ if (Preferences::GetBool("widget.disable-workspace-management", false)) {
+ return true;
+ }
+ if (Preferences::HasUserValue("widget.workspace-management")) {
+ return Preferences::GetBool("widget.workspace-management");
+ }
+
+ if (IsGnomeDesktopEnvironment()) {
+ // Gnome uses dynamic workspaces by default so disable workspace management
+ // in that case.
+ bool usesDynamicWorkspaces = true;
+ nsCOMPtr<nsIGSettingsService> gsettings =
+ do_GetService(NS_GSETTINGSSERVICE_CONTRACTID);
+ if (gsettings) {
+ nsCOMPtr<nsIGSettingsCollection> mutterSettings;
+ gsettings->GetCollectionForSchema(kDesktopMutterSchema,
+ getter_AddRefs(mutterSettings));
+ if (mutterSettings) {
+ mutterSettings->GetBoolean(kDesktopDynamicWorkspacesKey,
+ &usesDynamicWorkspaces);
+ }
+ }
+ return usesDynamicWorkspaces;
+ }
+
+ const auto& desktop = GetDesktopEnvironmentIdentifier();
+ return desktop.EqualsLiteral("bspwm") || desktop.EqualsLiteral("i3");
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+
+ if (!GdkIsX11Display() || !mShell) {
+ return;
+ }
+
+#ifdef MOZ_X11
+ LOG("nsWindow::GetWorkspaceID()\n");
+
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = GetToplevelGdkWindow();
+ if (!gdk_window) {
+ LOG(" missing Gdk window, quit.");
+ return;
+ }
+
+ if (WorkspaceManagementDisabled(gdk_window_get_screen(gdk_window))) {
+ LOG(" WorkspaceManagementDisabled, quit.");
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long* wm_desktop;
+
+ if (!gdk_property_get(gdk_window, gdk_atom_intern("_NET_WM_DESKTOP", FALSE),
+ cardinal_atom,
+ 0, // offset
+ INT32_MAX, // length
+ FALSE, // delete
+ &type_returned, &format_returned, &length_returned,
+ (guchar**)&wm_desktop)) {
+ LOG(" gdk_property_get() failed, quit.");
+ return;
+ }
+
+ LOG(" got workspace ID %d", (int32_t)wm_desktop[0]);
+ workspaceID.AppendInt((int32_t)wm_desktop[0]);
+ g_free(wm_desktop);
+#endif
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
+ nsresult rv = NS_OK;
+ int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
+
+ LOG("nsWindow::MoveToWorkspace() ID %d", workspaceID);
+ if (NS_FAILED(rv) || !workspaceID || !GdkIsX11Display() || !mShell) {
+ LOG(" MoveToWorkspace disabled, quit");
+ return;
+ }
+
+#ifdef MOZ_X11
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = GetToplevelGdkWindow();
+ if (!gdk_window) {
+ LOG(" failed to get GdkWindow, quit.");
+ return;
+ }
+
+ // This code is inspired by some found in the 'gxtuner' project.
+ // https://github.com/brummer10/gxtuner/blob/792d453da0f3a599408008f0f1107823939d730d/deskpager.cpp#L50
+ XEvent xevent;
+ Display* xdisplay = gdk_x11_get_default_xdisplay();
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ Window root_win = GDK_WINDOW_XID(gdk_screen_get_root_window(screen));
+ GdkDisplay* display = gdk_window_get_display(gdk_window);
+ Atom type = gdk_x11_get_xatom_by_name_for_display(display, "_NET_WM_DESKTOP");
+
+ xevent.type = ClientMessage;
+ xevent.xclient.type = ClientMessage;
+ xevent.xclient.serial = 0;
+ xevent.xclient.send_event = TRUE;
+ xevent.xclient.display = xdisplay;
+ xevent.xclient.window = GDK_WINDOW_XID(gdk_window);
+ xevent.xclient.message_type = type;
+ xevent.xclient.format = 32;
+ xevent.xclient.data.l[0] = workspaceID;
+ xevent.xclient.data.l[1] = X11CurrentTime;
+ xevent.xclient.data.l[2] = 0;
+ xevent.xclient.data.l[3] = 0;
+ xevent.xclient.data.l[4] = 0;
+
+ XSendEvent(xdisplay, root_win, FALSE,
+ SubstructureNotifyMask | SubstructureRedirectMask, &xevent);
+
+ XFlush(xdisplay);
+ LOG(" moved to workspace");
+#endif
+}
+
+void nsWindow::SetUserTimeAndStartupTokenForActivatedWindow() {
+ nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit();
+ if (!toolkit || MOZ_UNLIKELY(mWindowType == WindowType::Invisible)) {
+ return;
+ }
+
+ mWindowActivationTokenFromEnv = toolkit->GetStartupToken();
+ if (!mWindowActivationTokenFromEnv.IsEmpty()) {
+ if (!GdkIsWaylandDisplay()) {
+ gtk_window_set_startup_id(GTK_WINDOW(mShell),
+ mWindowActivationTokenFromEnv.get());
+ // In the case of X11, the above call is all we need. For wayland we need
+ // to keep the token around until we take it in
+ // TransferFocusToWaylandWindow.
+ mWindowActivationTokenFromEnv.Truncate();
+ }
+ } else if (uint32_t timestamp = toolkit->GetFocusTimestamp()) {
+ // We don't have the data we need. Fall back to an
+ // approximation ... using the timestamp of the remote command
+ // being received as a guess for the timestamp of the user event
+ // that triggered it.
+ gdk_window_focus(GetToplevelGdkWindow(), timestamp);
+ }
+
+ // If we used the startup ID, that already contains the focus timestamp;
+ // we don't want to reuse the timestamp next time we raise the window
+ toolkit->SetFocusTimestamp(0);
+ toolkit->SetStartupToken(""_ns);
+}
+
+/* static */
+guint32 nsWindow::GetLastUserInputTime() {
+ // gdk_x11_display_get_user_time/gtk_get_current_event_time tracks
+ // button and key presses, DESKTOP_STARTUP_ID used to start the app,
+ // drop events from external drags,
+ // WM_DELETE_WINDOW delete events, but not usually mouse motion nor
+ // button and key releases. Therefore use the most recent of
+ // gdk_x11_display_get_user_time and the last time that we have seen.
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ guint32 timestamp = GdkIsX11Display(gdkDisplay)
+ ? gdk_x11_display_get_user_time(gdkDisplay)
+ : gtk_get_current_event_time();
+#else
+ guint32 timestamp = gtk_get_current_event_time();
+#endif
+
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::FocusWaylandWindow(const char* aTokenID) {
+ MOZ_DIAGNOSTIC_ASSERT(aTokenID);
+
+ LOG("nsWindow::FocusWaylandWindow(%s)", aTokenID);
+ if (IsDestroyed()) {
+ LOG(" already destroyed, quit.");
+ return;
+ }
+ wl_surface* surface =
+ mGdkWindow ? gdk_wayland_window_get_wl_surface(mGdkWindow) : nullptr;
+ if (!surface) {
+ LOG(" mGdkWindow is not visible, quit.");
+ return;
+ }
+
+ LOG(" requesting xdg-activation, surface ID %d",
+ wl_proxy_get_id((struct wl_proxy*)surface));
+ xdg_activation_v1* xdg_activation = WaylandDisplayGet()->GetXdgActivation();
+ if (!xdg_activation) {
+ return;
+ }
+ xdg_activation_v1_activate(xdg_activation, aTokenID, surface);
+}
+
+// Transfer focus from gFocusWindow to aWindow and use xdg_activation
+// protocol for it.
+void nsWindow::TransferFocusToWaylandWindow(nsWindow* aWindow) {
+ LOGW("nsWindow::TransferFocusToWaylandWindow(%p) gFocusWindow %p", aWindow,
+ gFocusWindow);
+ auto promise = mozilla::widget::RequestWaylandFocusPromise();
+ if (NS_WARN_IF(!promise)) {
+ LOGW(" quit, failed to create TransferFocusToWaylandWindow [%p]", aWindow);
+ return;
+ }
+ promise->Then(
+ GetMainThreadSerialEventTarget(), __func__,
+ /* resolve */
+ [window = RefPtr{aWindow}](nsCString token) {
+ window->FocusWaylandWindow(token.get());
+ },
+ /* reject */
+ [window = RefPtr{aWindow}](bool state) {
+ LOGW("TransferFocusToWaylandWindow [%p] failed", window.get());
+ });
+}
+#endif
+
+// Request activation of this window or give focus to this widget.
+// aRaise means whether we should request activation of this widget's
+// toplevel window.
+//
+// nsWindow::SetFocus(Raise::Yes) - Raise and give focus to toplevel window.
+// nsWindow::SetFocus(Raise::No) - Give focus to this window.
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ LOG("nsWindow::SetFocus Raise %d\n", aRaise == Raise::Yes);
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(GTK_WIDGET(mContainer));
+
+ LOG(" gFocusWindow [%p]\n", gFocusWindow);
+ LOG(" mContainer [%p]\n", GTK_WIDGET(mContainer));
+ LOG(" Toplevel widget [%p]\n", toplevelWidget);
+
+ // Make sure that our owning widget has focus. If it doesn't try to
+ // grab it. Note that we don't set our focus flag in this case.
+ if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
+ aRaise == Raise::Yes && toplevelWidget &&
+ !gtk_widget_has_focus(toplevelWidget)) {
+ if (gtk_widget_get_visible(mShell)) {
+ LOG(" toplevel is not focused");
+ gdk_window_show_unraised(GetToplevelGdkWindow());
+ // Unset the urgency hint if possible.
+ SetUrgencyHint(mShell, false);
+ }
+ }
+
+ RefPtr<nsWindow> toplevelWindow = get_window_for_gtk_widget(toplevelWidget);
+ if (!toplevelWindow) {
+ LOG(" missing toplevel nsWindow, quit\n");
+ return;
+ }
+
+ if (aRaise == Raise::Yes) {
+ // means request toplevel activation.
+
+ // This is asynchronous. If and when the window manager accepts the request,
+ // then the focus widget will get a focus-in-event signal.
+ if (StaticPrefs::mozilla_widget_raise_on_setfocus_AtStartup() &&
+ toplevelWindow->mIsShown && toplevelWindow->mShell &&
+ !gtk_window_is_active(GTK_WINDOW(toplevelWindow->mShell))) {
+ LOG(" toplevel is visible but not active, requesting activation [%p]",
+ toplevelWindow.get());
+
+ // Take the time here explicitly for the call below.
+ const uint32_t timestamp = [&] {
+ if (nsGTKToolkit* toolkit = nsGTKToolkit::GetToolkit()) {
+ if (uint32_t t = toolkit->GetFocusTimestamp()) {
+ toolkit->SetFocusTimestamp(0);
+ return t;
+ }
+ }
+ return GetLastUserInputTime();
+ }();
+
+ toplevelWindow->SetUserTimeAndStartupTokenForActivatedWindow();
+ gtk_window_present_with_time(GTK_WINDOW(toplevelWindow->mShell),
+ timestamp);
+
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ auto existingToken =
+ std::move(toplevelWindow->mWindowActivationTokenFromEnv);
+ if (!existingToken.IsEmpty()) {
+ LOG(" has existing activation token.");
+ toplevelWindow->FocusWaylandWindow(existingToken.get());
+ } else {
+ LOG(" missing activation token, try to transfer from focused "
+ "window");
+ TransferFocusToWaylandWindow(toplevelWindow);
+ }
+ }
+#endif
+ }
+ return;
+ }
+
+ // aRaise == No means that keyboard events should be dispatched from this
+ // widget.
+
+ // Ensure GTK_WIDGET(mContainer) is the focused GtkWidget within its toplevel
+ // window.
+ //
+ // For WindowType::Popup, this GtkWidget may not actually be the one that
+ // receives the key events as it may be the parent window that is active.
+ if (!gtk_widget_is_focus(GTK_WIDGET(mContainer))) {
+ // This is synchronous. It takes focus from a plugin or from a widget
+ // in an embedder. The focus manager already knows that this window
+ // is active so gBlockActivateEvent avoids another (unnecessary)
+ // activate notification.
+ gBlockActivateEvent = true;
+ gtk_widget_grab_focus(GTK_WIDGET(mContainer));
+ gBlockActivateEvent = false;
+ }
+
+ // If this is the widget that already has focus, return.
+ if (gFocusWindow == this) {
+ LOG(" already have focus");
+ return;
+ }
+
+ // Set this window to be the focused child window
+ gFocusWindow = this;
+
+ if (mIMContext) {
+ mIMContext->OnFocusWindow(this);
+ }
+
+ LOG(" widget now has focus in SetFocus()");
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ if (!mGdkWindow) {
+ return mBounds;
+ }
+
+ const LayoutDeviceIntPoint origin = [&] {
+ gint x, y;
+ gdk_window_get_root_origin(mGdkWindow, &x, &y);
+
+ // Workaround for https://gitlab.gnome.org/GNOME/gtk/-/merge_requests/4820
+ // Bug 1775017 Gtk < 3.24.35 returns scaled values for
+ // override redirected window on X11.
+ if (gtk_check_version(3, 24, 35) != nullptr && GdkIsX11Display() &&
+ gdk_window_get_window_type(mGdkWindow) == GDK_WINDOW_TEMP) {
+ return LayoutDeviceIntPoint(x, y);
+ }
+ return GdkPointToDevicePixels({x, y});
+ }();
+
+ // mBounds.Size() is the window bounds, not the window-manager frame
+ // bounds (bug 581863). gdk_window_get_frame_extents would give the
+ // frame bounds, but mBounds.Size() is returned here for consistency
+ // with Resize.
+ const LayoutDeviceIntRect rect(origin, mBounds.Size());
+#if MOZ_LOGGING
+ if (MOZ_LOG_TEST(IsPopup() ? gWidgetPopupLog : gWidgetLog,
+ LogLevel::Verbose)) {
+ gint scale = GdkCeiledScaleFactor();
+ if (mLastLoggedScale != scale || !(mLastLoggedBoundSize == rect)) {
+ mLastLoggedScale = scale;
+ mLastLoggedBoundSize = rect;
+ LOG("GetScreenBounds %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
+ rect.x, rect.y, rect.width, rect.height, rect.x / scale,
+ rect.y / scale, rect.width / scale, rect.height / scale);
+ }
+ }
+#endif
+ return rect;
+}
+
+LayoutDeviceIntSize nsWindow::GetClientSize() {
+ return LayoutDeviceIntSize(mBounds.width, mBounds.height);
+}
+
+LayoutDeviceIntRect nsWindow::GetClientBounds() {
+ // GetBounds returns a rect whose top left represents the top left of the
+ // outer bounds, but whose width/height represent the size of the inner
+ // bounds (which is messed up).
+ LayoutDeviceIntRect rect = GetBounds();
+ rect.MoveBy(GetClientOffset());
+ return rect;
+}
+
+void nsWindow::RecomputeClientOffset(bool aNotify) {
+ if (!IsTopLevelWindowType()) {
+ return;
+ }
+
+ auto oldOffset = mClientOffset;
+
+ mClientOffset = WidgetToScreenOffset() - mBounds.TopLeft();
+
+ if (aNotify && mClientOffset != oldOffset) {
+ // Send a WindowMoved notification. This ensures that BrowserParent picks up
+ // the new client offset and sends it to the child process if appropriate.
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+ }
+}
+
+gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ RecomputeClientOffset(/* aNotify = */ true);
+ return FALSE;
+ }
+ if (!mGdkWindow) {
+ return FALSE;
+ }
+#ifdef MOZ_X11
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+#endif
+ return FALSE;
+}
+
+static GdkCursor* GetCursorForImage(const nsIWidget::Cursor& aCursor,
+ int32_t aWidgetScaleFactor) {
+ if (!aCursor.IsCustom()) {
+ return nullptr;
+ }
+ nsIntSize size = nsIWidget::CustomCursorSize(aCursor);
+
+ // NOTE: GTK only allows integer scale factors, so we ceil to the larger scale
+ // factor and then tell gtk to scale it down. We ensure to scale at least to
+ // the GDK scale factor, so that cursors aren't downsized in HiDPI on wayland,
+ // see bug 1707533.
+ int32_t gtkScale = std::max(
+ aWidgetScaleFactor, int32_t(std::ceil(std::max(aCursor.mResolution.mX,
+ aCursor.mResolution.mY))));
+
+ // Reject cursors greater than 128 pixels in some direction, to prevent
+ // spoofing.
+ // XXX ideally we should rescale. Also, we could modify the API to
+ // allow trusted content to set larger cursors.
+ //
+ // TODO(emilio, bug 1445844): Unify the solution for this with other
+ // platforms.
+ if (size.width > 128 || size.height > 128) {
+ return nullptr;
+ }
+
+ nsIntSize rasterSize = size * gtkScale;
+ RefPtr<GdkPixbuf> pixbuf =
+ nsImageToPixbuf::ImageToPixbuf(aCursor.mContainer, Some(rasterSize));
+ if (!pixbuf) {
+ return nullptr;
+ }
+
+ // Looks like all cursors need an alpha channel (tested on Gtk 2.4.4). This
+ // is of course not documented anywhere...
+ // So add one if there isn't one yet
+ if (!gdk_pixbuf_get_has_alpha(pixbuf)) {
+ RefPtr<GdkPixbuf> alphaBuf =
+ dont_AddRef(gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0));
+ pixbuf = std::move(alphaBuf);
+ if (!pixbuf) {
+ return nullptr;
+ }
+ }
+
+ cairo_surface_t* surface =
+ gdk_cairo_surface_create_from_pixbuf(pixbuf, gtkScale, nullptr);
+ if (!surface) {
+ return nullptr;
+ }
+
+ auto CleanupSurface =
+ MakeScopeExit([&]() { cairo_surface_destroy(surface); });
+
+ return gdk_cursor_new_from_surface(gdk_display_get_default(), surface,
+ aCursor.mHotspotX, aCursor.mHotspotY);
+}
+
+void nsWindow::SetCursor(const Cursor& aCursor) {
+ if (mWidgetCursorLocked || !mGdkWindow) {
+ return;
+ }
+
+ // Only change cursor if it's actually been changed
+ if (!mUpdateCursor && mCursor == aCursor) {
+ return;
+ }
+
+ mUpdateCursor = false;
+ mCursor = aCursor;
+
+ // Try to set the cursor image first, and fall back to the numeric cursor.
+ GdkCursor* imageCursor = nullptr;
+ if (mCustomCursorAllowed) {
+ imageCursor = GetCursorForImage(aCursor, GdkCeiledScaleFactor());
+ }
+
+ // When using a custom cursor, clear the cursor first using eCursor_none, in
+ // order to work around https://gitlab.gnome.org/GNOME/gtk/-/issues/6242
+ GdkCursor* nonImageCursor =
+ get_gtk_cursor(imageCursor ? eCursor_none : aCursor.mDefaultCursor);
+ auto CleanupCursor = mozilla::MakeScopeExit([&]() {
+ // get_gtk_cursor returns a weak reference, which we shouldn't unref.
+ if (imageCursor) {
+ g_object_unref(imageCursor);
+ }
+ });
+
+ gdk_window_set_cursor(mGdkWindow, nonImageCursor);
+ if (imageCursor) {
+ gdk_window_set_cursor(mGdkWindow, imageCursor);
+ }
+}
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (!mGdkWindow) {
+ return;
+ }
+
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+
+ LOG("Invalidate (rect): %d %d %d %d\n", rect.x, rect.y, rect.width,
+ rect.height);
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET: {
+ return mGdkWindow;
+ }
+
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+ if (!mGdkWindow) {
+ return nullptr;
+ }
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ }
+#endif
+ NS_WARNING(
+ "nsWindow::GetNativeData(): NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID is not "
+ "handled on Wayland!");
+ return nullptr;
+ case NS_RAW_NATIVE_IME_CONTEXT: {
+ void* pseudoIMEContext = GetPseudoIMEContext();
+ if (pseudoIMEContext) {
+ return pseudoIMEContext;
+ }
+ // If IME context isn't available on this widget, we should set |this|
+ // instead of nullptr.
+ if (!mIMContext) {
+ return this;
+ }
+ return mIMContext.get();
+ }
+ case NS_NATIVE_OPENGL_CONTEXT:
+ return nullptr;
+ case NS_NATIVE_EGL_WINDOW: {
+ void* eglWindow = nullptr;
+
+ // We can't block on mutex here as it leads to a deadlock:
+ // 1) mutex is taken at nsWindow::Destroy()
+ // 2) NS_NATIVE_EGL_WINDOW is called from compositor/rendering thread,
+ // blocking on mutex.
+ // 3) DestroyCompositor() is called by nsWindow::Destroy(). As a sync
+ // call it waits to compositor/rendering threads,
+ // but they're blocked at 2).
+ // It's fine if we return null EGL window during DestroyCompositor(),
+ // in such case compositor painting is skipped.
+ if (mDestroyMutex.TryLock()) {
+ if (mGdkWindow && !mIsDestroyed) {
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ eglWindow = (void*)GDK_WINDOW_XID(mGdkWindow);
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ bool hiddenWindow =
+ mCompositorWidgetDelegate &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()->IsHidden();
+ if (!hiddenWindow) {
+ eglWindow = moz_container_wayland_get_egl_window(
+ mContainer, FractionalScaleFactor());
+ }
+ }
+#endif
+ }
+ mDestroyMutex.Unlock();
+ }
+
+ LOG("Get NS_NATIVE_EGL_WINDOW mGdkWindow %p returned eglWindow %p",
+ mGdkWindow, eglWindow);
+ return eglWindow;
+ }
+ default:
+ NS_WARNING("nsWindow::GetNativeData called with bad value");
+ return nullptr;
+ }
+}
+
+nsresult nsWindow::SetTitle(const nsAString& aTitle) {
+ if (!mShell) {
+ return NS_OK;
+ }
+
+ // convert the string into utf8 and set the title.
+#define UTF8_FOLLOWBYTE(ch) (((ch) & 0xC0) == 0x80)
+ NS_ConvertUTF16toUTF8 titleUTF8(aTitle);
+ if (titleUTF8.Length() > NS_WINDOW_TITLE_MAX_LENGTH) {
+ // Truncate overlong titles (bug 167315). Make sure we chop after a
+ // complete sequence by making sure the next char isn't a follow-byte.
+ uint32_t len = NS_WINDOW_TITLE_MAX_LENGTH;
+ while (UTF8_FOLLOWBYTE(titleUTF8[len])) --len;
+ titleUTF8.Truncate(len);
+ }
+ gtk_window_set_title(GTK_WINDOW(mShell), (const char*)titleUTF8.get());
+
+ return NS_OK;
+}
+
+void nsWindow::SetIcon(const nsAString& aIconSpec) {
+ if (!mShell) {
+ return;
+ }
+
+ nsAutoCString iconName;
+
+ if (aIconSpec.EqualsLiteral("default")) {
+ nsAutoString brandName;
+ WidgetUtils::GetBrandShortName(brandName);
+ if (brandName.IsEmpty()) {
+ brandName.AssignLiteral(u"Mozilla");
+ }
+ AppendUTF16toUTF8(brandName, iconName);
+ ToLowerCase(iconName);
+ } else {
+ AppendUTF16toUTF8(aIconSpec, iconName);
+ }
+
+ nsCOMPtr<nsIFile> iconFile;
+ nsAutoCString path;
+
+ gint* iconSizes = gtk_icon_theme_get_icon_sizes(gtk_icon_theme_get_default(),
+ iconName.get());
+ bool foundIcon = (iconSizes[0] != 0);
+ g_free(iconSizes);
+
+ if (!foundIcon) {
+ // Look for icons with the following suffixes appended to the base name
+ // The last two entries (for the old XPM format) will be ignored unless
+ // no icons are found using other suffixes. XPM icons are deprecated.
+
+ const char16_t extensions[9][8] = {u".png", u"16.png", u"32.png",
+ u"48.png", u"64.png", u"128.png",
+ u"256.png", u".xpm", u"16.xpm"};
+
+ for (uint32_t i = 0; i < ArrayLength(extensions); i++) {
+ // Don't bother looking for XPM versions if we found a PNG.
+ if (i == ArrayLength(extensions) - 2 && foundIcon) break;
+
+ ResolveIconName(aIconSpec, nsDependentString(extensions[i]),
+ getter_AddRefs(iconFile));
+ if (iconFile) {
+ iconFile->GetNativePath(path);
+ GdkPixbuf* icon = gdk_pixbuf_new_from_file(path.get(), nullptr);
+ if (icon) {
+ gtk_icon_theme_add_builtin_icon(iconName.get(),
+ gdk_pixbuf_get_height(icon), icon);
+ g_object_unref(icon);
+ foundIcon = true;
+ }
+ }
+ }
+ }
+
+ // leave the default icon intact if no matching icons were found
+ if (foundIcon) {
+ gtk_window_set_icon_name(GTK_WINDOW(mShell), iconName.get());
+ }
+}
+
+/* TODO(bug 1655924): gdk_window_get_origin is can block waiting for the X
+ server for a long time, we would like to use the implementation below
+ instead. However, removing the synchronous x server queries causes a race
+ condition to surface, causing issues such as bug 1652743 and bug 1653711.
+
+
+ This code can be used instead of gdk_window_get_origin() but it cuases
+ such issues:
+
+ *aX = 0;
+ *aY = 0;
+ if (!aWindow) {
+ return;
+ }
+
+ GdkWindow* current = aWindow;
+ while (GdkWindow* parent = gdk_window_get_parent(current)) {
+ if (parent == current) {
+ break;
+ }
+
+ int x = 0;
+ int y = 0;
+ gdk_window_get_position(current, &x, &y);
+ *aX += x;
+ *aY += y;
+
+ current = parent;
+ }
+*/
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ // Don't use gdk_window_get_origin() on wl_subsurface Wayland popups
+ // https://gitlab.gnome.org/GNOME/gtk/-/issues/5287
+ if (IsWaylandPopup() && !mPopupUseMoveToRect) {
+ return mBounds.TopLeft();
+ }
+ nsIntPoint origin(0, 0);
+ if (mGdkWindow) {
+ gdk_window_get_origin(mGdkWindow, &origin.x.value, &origin.y.value);
+ }
+ return GdkPointToDevicePixels({origin.x, origin.y});
+}
+
+void nsWindow::CaptureRollupEvents(bool aDoCapture) {
+ LOG("CaptureRollupEvents(%d)\n", aDoCapture);
+ if (mIsDestroyed) {
+ return;
+ }
+
+ static constexpr auto kCaptureEventsMask =
+ GdkEventMask(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK);
+
+ static bool sSystemNeedsPointerGrab = [&] {
+ if (GdkIsWaylandDisplay()) {
+ return false;
+ }
+ // We only need to grab the pointer for X servers that move the focus with
+ // the pointer (like twm, sawfish...). Since we roll up popups on focus out,
+ // not grabbing the pointer triggers rollup when the mouse enters the popup
+ // and leaves the main window, see bug 1807482.
+ //
+ // FVWM is also affected but less severely: the pointer can enter the
+ // popup, but if it briefly moves out of the popup and over the main window
+ // then we see a focus change and roll up the popup.
+ //
+ // We don't do it for most common desktops, if only because it causes X11
+ // crashes like bug 1607713.
+ const auto& desktop = GetDesktopEnvironmentIdentifier();
+ return desktop.EqualsLiteral("twm") || desktop.EqualsLiteral("sawfish") ||
+ StringBeginsWith(desktop, "fvwm"_ns);
+ }();
+
+ const bool grabPointer = [] {
+ switch (StaticPrefs::widget_gtk_grab_pointer()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return sSystemNeedsPointerGrab;
+ }
+ }();
+
+ if (!grabPointer) {
+ return;
+ }
+
+ mNeedsToRetryCapturingMouse = false;
+ if (aDoCapture) {
+ if (mIsDragPopup || DragInProgress()) {
+ // Don't add a grab if a drag is in progress, or if the widget is a drag
+ // feedback popup. (panels with type="drag").
+ return;
+ }
+
+ if (!mHasMappedToplevel) {
+ // On X, capturing an unmapped window is pointless (returns
+ // GDK_GRAB_NOT_VIEWABLE). Avoid the X server round-trip and just retry
+ // when we're mapped.
+ mNeedsToRetryCapturingMouse = true;
+ return;
+ }
+
+ // gdk_pointer_grab is deprecated in favor of gdk_device_grab, but that
+ // causes a strange bug on X11, most obviously with nested popup menus:
+ // we somehow take the pointer position relative to the top left of the
+ // outer menu and use it as if it were relative to the submenu. This
+ // doesn't happen with gdk_pointer_grab even though the code is very
+ // similar. See the video attached to bug 1750721 for a demonstration,
+ // and see also bug 1820542 for when the same thing happened with
+ // another attempt to use gdk_device_grab.
+ //
+ // (gdk_device_grab is deprecated in favor of gdk_seat_grab as of 3.20,
+ // but at the time of this writing we still support older versions of
+ // GTK 3.)
+ GdkGrabStatus status =
+ gdk_pointer_grab(GetToplevelGdkWindow(),
+ /* owner_events = */ true, kCaptureEventsMask,
+ /* confine_to = */ nullptr,
+ /* cursor = */ nullptr, GetLastUserInputTime());
+ Unused << NS_WARN_IF(status != GDK_GRAB_SUCCESS);
+ LOG(" > pointer grab with status %d", int(status));
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ } else {
+ // There may not have been a drag in process when aDoCapture was set,
+ // so make sure to remove any added grab. This is a no-op if the grab
+ // was not added to this widget.
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ gdk_pointer_ungrab(GetLastUserInputTime());
+ }
+}
+
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ LOG("nsWindow::GetAttention");
+
+ GtkWidget* top_window = GetToplevelWidget();
+ GtkWidget* top_focused_window =
+ gFocusWindow ? gFocusWindow->GetToplevelWidget() : nullptr;
+
+ // Don't get attention if the window is focused anyway.
+ if (top_window && (gtk_widget_get_visible(top_window)) &&
+ top_window != top_focused_window) {
+ SetUrgencyHint(top_window, true);
+ }
+
+ return NS_OK;
+}
+
+bool nsWindow::HasPendingInputEvent() {
+ // This sucks, but gtk/gdk has no way to answer the question we want while
+ // excluding paint events, and there's no X API that will let us peek
+ // without blocking or removing. To prevent event reordering, peek
+ // anything except expose events. Reordering expose and others should be
+ // ok, hopefully.
+ bool haveEvent = false;
+#ifdef MOZ_X11
+ XEvent ev;
+ if (GdkIsX11Display()) {
+ Display* display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default());
+ haveEvent = XCheckMaskEvent(
+ display,
+ KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask |
+ EnterWindowMask | LeaveWindowMask | PointerMotionMask |
+ PointerMotionHintMask | Button1MotionMask | Button2MotionMask |
+ Button3MotionMask | Button4MotionMask | Button5MotionMask |
+ ButtonMotionMask | KeymapStateMask | VisibilityChangeMask |
+ StructureNotifyMask | ResizeRedirectMask | SubstructureNotifyMask |
+ SubstructureRedirectMask | FocusChangeMask | PropertyChangeMask |
+ ColormapChangeMask | OwnerGrabButtonMask,
+ &ev);
+ if (haveEvent) {
+ XPutBackEvent(display, &ev);
+ }
+ }
+#endif
+ return haveEvent;
+}
+
+#ifdef cairo_copy_clip_rectangle_list
+# error "Looks like we're including Mozilla's cairo instead of system cairo"
+#endif
+static bool ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, cairo_t* cr) {
+ cairo_rectangle_list_t* rects = cairo_copy_clip_rectangle_list(cr);
+ if (rects->status != CAIRO_STATUS_SUCCESS) {
+ NS_WARNING("Failed to obtain cairo rectangle list.");
+ return false;
+ }
+
+ for (int i = 0; i < rects->num_rectangles; i++) {
+ const cairo_rectangle_t& r = rects->rectangles[i];
+ aRegion.Or(aRegion,
+ LayoutDeviceIntRect::Truncate((float)r.x, (float)r.y,
+ (float)r.width, (float)r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::CreateCompositorVsyncDispatcher() {
+ LOG_VSYNC("nsWindow::CreateCompositorVsyncDispatcher()");
+ if (!mWaylandVsyncSource) {
+ LOG_VSYNC(
+ " mWaylandVsyncSource is missing, create "
+ "nsBaseWidget::CompositorVsyncDispatcher()");
+ nsBaseWidget::CreateCompositorVsyncDispatcher();
+ return;
+ }
+ if (!mCompositorVsyncDispatcherLock) {
+ mCompositorVsyncDispatcherLock =
+ MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
+ }
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
+ if (!mCompositorVsyncDispatcher) {
+ LOG_VSYNC(" create CompositorVsyncDispatcher()");
+ mCompositorVsyncDispatcher =
+ new CompositorVsyncDispatcher(mWaylandVsyncDispatcher);
+ }
+}
+#endif
+
+void nsWindow::RequestRepaint(LayoutDeviceIntRegion& aRepaintRegion) {
+ WindowRenderer* renderer = GetWindowRenderer();
+ WebRenderLayerManager* layerManager = renderer->AsWebRender();
+ KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
+
+ if (knowsCompositor && layerManager && mCompositorSession) {
+ if (!mConfiguredClearColor && !IsPopup()) {
+ layerManager->WrBridge()->SendSetDefaultClearColor(LookAndFeel::Color(
+ LookAndFeel::ColorID::Window, PreferenceSheet::ColorSchemeForChrome(),
+ LookAndFeel::UseStandins::No));
+ mConfiguredClearColor = true;
+ }
+
+ // We need to paint to the screen even if nothing changed, since if we
+ // don't have a compositing window manager, our pixels could be stale.
+ layerManager->SetNeedsComposite(true);
+ layerManager->SendInvalidRegion(aRepaintRegion.ToUnknownRegion());
+ }
+}
+
+gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
+ // This might destroy us.
+ NotifyOcclusionState(OcclusionState::VISIBLE);
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Send any pending resize events so that layout can update.
+ // May run event loop and destroy us.
+ MaybeDispatchResized();
+ if (mIsDestroyed) {
+ return FALSE;
+ }
+
+ // Windows that are not visible will be painted after they become visible.
+ if (!mGdkWindow || !mHasMappedToplevel) {
+ return FALSE;
+ }
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay() && !moz_container_wayland_can_draw(mContainer)) {
+ return FALSE;
+ }
+#endif
+
+ if (!GetListener()) {
+ return FALSE;
+ }
+
+ LOG("nsWindow::OnExposeEvent GdkWindow [%p] XID [0x%lx]", mGdkWindow,
+ GetX11Window());
+
+ LayoutDeviceIntRegion exposeRegion;
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+ LOG(" no rects, quit");
+ return FALSE;
+ }
+
+ gint scale = GdkCeiledScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ RequestRepaint(region);
+
+ RefPtr<nsWindow> strongThis(this);
+
+ // Dispatch WillPaintWindow notification to allow scripts etc. to run
+ // before we paint. It also spins event loop which may show/hide the window
+ // so we may have new renderer etc.
+ GetListener()->WillPaintWindow(this);
+
+ // If the window has been destroyed during the will paint notification,
+ // there is nothing left to do.
+ if (!mGdkWindow || mIsDestroyed) {
+ return TRUE;
+ }
+
+ // Re-get all rendering components since the will paint notification
+ // might have killed it.
+ nsIWidgetListener* listener = GetListener();
+ if (!listener) return FALSE;
+
+ WindowRenderer* renderer = GetWindowRenderer();
+ WebRenderLayerManager* layerManager = renderer->AsWebRender();
+ KnowsCompositor* knowsCompositor = renderer->AsKnowsCompositor();
+
+ if (knowsCompositor && layerManager && layerManager->NeedsComposite()) {
+ layerManager->ScheduleComposite(wr::RenderReasons::WIDGET);
+ layerManager->SetNeedsComposite(false);
+ }
+
+ // Our bounds may have changed after calling WillPaintWindow. Clip
+ // to the new bounds here. The region is relative to this
+ // window.
+ region.And(region, LayoutDeviceIntRect(0, 0, mBounds.width, mBounds.height));
+
+ bool shaped = false;
+ if (TransparencyMode::Transparent == GetTransparencyMode()) {
+ auto* window = static_cast<nsWindow*>(GetTopLevelWidget());
+ if (mTransparencyBitmapForTitlebar) {
+ if (mSizeMode == nsSizeMode_Normal) {
+ window->UpdateTitlebarTransparencyBitmap();
+ } else {
+ window->ClearTransparencyBitmap();
+ }
+ } else {
+ if (mHasAlphaVisual) {
+ // Remove possible shape mask from when window manger was not
+ // previously compositing.
+ window->ClearTransparencyBitmap();
+ } else {
+ shaped = true;
+ }
+ }
+ }
+
+ if (region.IsEmpty()) {
+ return TRUE;
+ }
+
+ // If this widget uses OMTC...
+ if (renderer->GetBackendType() == LayersBackend::LAYERS_WR) {
+ listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) {
+ return TRUE;
+ }
+
+ listener->DidPaintWindow();
+ return TRUE;
+ }
+
+ BufferMode layerBuffering = BufferMode::BUFFERED;
+ RefPtr<DrawTarget> dt = StartRemoteDrawingInRegion(region, &layerBuffering);
+ if (!dt || !dt->IsValid()) {
+ return FALSE;
+ }
+ Maybe<gfxContext> ctx;
+ IntRect boundsRect = region.GetBounds().ToUnknownRect();
+ IntPoint offset(0, 0);
+ if (dt->GetSize() == boundsRect.Size()) {
+ offset = boundsRect.TopLeft();
+ dt->SetTransform(Matrix::Translation(-offset));
+ }
+
+#ifdef MOZ_X11
+ if (shaped) {
+ // Collapse update area to the bounding box. This is so we only have to
+ // call UpdateTranslucentWindowAlpha once. After we have dropped
+ // support for non-Thebes graphics, UpdateTranslucentWindowAlpha will be
+ // our private interface so we can rework things to avoid this.
+ dt->PushClipRect(Rect(boundsRect));
+
+ // The double buffering is done here to extract the shape mask.
+ // (The shape mask won't be necessary when a visual with an alpha
+ // channel is used on compositing window managers.)
+ layerBuffering = BufferMode::BUFFER_NONE;
+ RefPtr<DrawTarget> destDT =
+ dt->CreateSimilarDrawTarget(boundsRect.Size(), SurfaceFormat::B8G8R8A8);
+ if (!destDT || !destDT->IsValid()) {
+ return FALSE;
+ }
+ destDT->SetTransform(Matrix::Translation(-boundsRect.TopLeft()));
+ ctx.emplace(destDT, /* aPreserveTransform */ true);
+ } else {
+ gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+ ctx.emplace(dt, /* aPreserveTransform */ true);
+ }
+
+# if 0
+ // NOTE: Paint flashing region would be wrong for cairo, since
+ // cairo inflates the update region, etc. So don't paint flash
+ // for cairo.
+# ifdef DEBUG
+ // XXX aEvent->region may refer to a newly-invalid area. FIXME
+ if (0 && WANT_PAINT_FLASHING && gtk_widget_get_window(aEvent))
+ gdk_window_flash(mGdkWindow, 1, 100, aEvent->region);
+# endif
+# endif
+
+#endif // MOZ_X11
+
+ bool painted = false;
+ {
+ if (renderer->GetBackendType() == LayersBackend::LAYERS_NONE) {
+ if (GetTransparencyMode() == TransparencyMode::Transparent &&
+ layerBuffering == BufferMode::BUFFER_NONE && mHasAlphaVisual) {
+ // If our draw target is unbuffered and we use an alpha channel,
+ // clear the image beforehand to ensure we don't get artifacts from a
+ // reused SHM image. See bug 1258086.
+ dt->ClearRect(Rect(boundsRect));
+ }
+ AutoLayerManagerSetup setupLayerManager(
+ this, ctx.isNothing() ? nullptr : &ctx.ref(), layerBuffering);
+ painted = listener->PaintWindow(this, region);
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) {
+ return TRUE;
+ }
+ }
+ }
+
+#ifdef MOZ_X11
+ // PaintWindow can Destroy us (bug 378273), avoid doing any paint
+ // operations below if that happened - it will lead to XError and exit().
+ if (shaped) {
+ if (MOZ_LIKELY(!mIsDestroyed)) {
+ if (painted) {
+ RefPtr<SourceSurface> surf = ctx->GetDrawTarget()->Snapshot();
+
+ UpdateAlpha(surf, boundsRect);
+
+ dt->DrawSurface(surf, Rect(boundsRect),
+ Rect(0, 0, boundsRect.width, boundsRect.height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ }
+
+ ctx.reset();
+ dt->PopClip();
+
+#endif // MOZ_X11
+
+ EndRemoteDrawingInRegion(dt, region);
+
+ listener->DidPaintWindow();
+
+ // Synchronously flush any new dirty areas
+ cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow);
+
+ if (dirtyArea) {
+ gdk_window_invalidate_region(mGdkWindow, dirtyArea, false);
+ cairo_region_destroy(dirtyArea);
+ gdk_window_process_updates(mGdkWindow, false);
+ }
+
+ // check the return value!
+ return TRUE;
+}
+
+void nsWindow::UpdateAlpha(SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect) {
+ // We need to create our own buffer to force the stride to match the
+ // expected stride.
+ int32_t stride =
+ GetAlignedStride<4>(aBoundsRect.width, BytesPerPixel(SurfaceFormat::A8));
+ if (stride == 0) {
+ return;
+ }
+ int32_t bufferSize = stride * aBoundsRect.height;
+ auto imageBuffer = MakeUniqueFallible<uint8_t[]>(bufferSize);
+ {
+ RefPtr<DrawTarget> drawTarget = gfxPlatform::CreateDrawTargetForData(
+ imageBuffer.get(), aBoundsRect.Size(), stride, SurfaceFormat::A8);
+
+ if (drawTarget) {
+ drawTarget->DrawSurface(aSourceSurface,
+ Rect(0, 0, aBoundsRect.width, aBoundsRect.height),
+ Rect(0, 0, aSourceSurface->GetSize().width,
+ aSourceSurface->GetSize().height),
+ DrawSurfaceOptions(SamplingFilter::POINT),
+ DrawOptions(1.0f, CompositionOp::OP_SOURCE));
+ }
+ }
+ UpdateTranslucentWindowAlphaInternal(aBoundsRect, imageBuffer.get(), stride);
+}
+
+gboolean nsWindow::OnConfigureEvent(GtkWidget* aWidget,
+ GdkEventConfigure* aEvent) {
+ // These events are only received on toplevel windows.
+ //
+ // GDK ensures that the coordinates are the client window top-left wrt the
+ // root window.
+ //
+ // GDK calculates the cordinates for real ConfigureNotify events on
+ // managed windows (that would normally be relative to the parent
+ // window).
+ //
+ // Synthetic ConfigureNotify events are from the window manager and
+ // already relative to the root window. GDK creates all X windows with
+ // border_width = 0, so synthetic events also indicate the top-left of
+ // the client window.
+ //
+ // Override-redirect windows are children of the root window so parent
+ // coordinates are root coordinates.
+
+#ifdef MOZ_LOGGING
+ int scale = mGdkWindow ? gdk_window_get_scale_factor(mGdkWindow) : -1;
+ LOG("configure event %d,%d -> %d x %d direct mGdkWindow scale %d (scaled "
+ "size %d x %d)\n",
+ aEvent->x, aEvent->y, aEvent->width, aEvent->height, scale,
+ aEvent->width * scale, aEvent->height * scale);
+#endif
+
+ if (mPendingConfigures > 0) {
+ mPendingConfigures--;
+ }
+
+ // Don't fire configure event for scale changes, we handle that
+ // OnScaleChanged event. Skip that for toplevel windows only.
+ if (mGdkWindow && IsTopLevelWindowType()) {
+ if (mCeiledScaleFactor != gdk_window_get_scale_factor(mGdkWindow)) {
+ LOG(" scale factor changed to %d,return early",
+ gdk_window_get_scale_factor(mGdkWindow));
+ return FALSE;
+ }
+ }
+
+ LayoutDeviceIntRect screenBounds = GetScreenBounds();
+
+ if (IsTopLevelWindowType()) {
+ // This check avoids unwanted rollup on spurious configure events from
+ // Cygwin/X (bug 672103).
+ if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
+ RollupAllMenus();
+ }
+ }
+
+ NS_ASSERTION(GTK_IS_WINDOW(aWidget),
+ "Configure event on widget that is not a GtkWindow");
+ if (mGdkWindow &&
+ gtk_window_get_window_type(GTK_WINDOW(aWidget)) == GTK_WINDOW_POPUP) {
+ // Override-redirect window
+ //
+ // These windows should not be moved by the window manager, and so any
+ // change in position is a result of our direction. mBounds has
+ // already been set in std::move() or Resize(), and that is more
+ // up-to-date than the position in the ConfigureNotify event if the
+ // event is from an earlier window move.
+ //
+ // Skipping the WindowMoved call saves context menus from an infinite
+ // loop when nsXULPopupManager::PopupMoved moves the window to the new
+ // position and nsMenuPopupFrame::SetPopupPosition adds
+ // offsetForContextMenu on each iteration.
+
+ // Our back buffer might have been invalidated while we drew the last
+ // frame, and its contents might be incorrect. See bug 1280653 comment 7
+ // and comment 10. Specifically we must ensure we recomposite the frame
+ // as soon as possible to avoid the corrupted frame being displayed.
+ GetWindowRenderer()->FlushRendering(wr::RenderReasons::WIDGET);
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+ RecomputeClientOffset(/* aNotify = */ false);
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ return FALSE;
+}
+
+void nsWindow::OnMap() {
+ LOG("nsWindow::OnMap");
+ // Gtk mapped widget to screen. Configure underlying GdkWindow properly
+ // as our rendering target.
+ // This call means we have X11 (or Wayland) window we can render to by GL
+ // so we need to notify compositor about it.
+ mIsMapped = true;
+ ConfigureGdkWindow();
+}
+
+void nsWindow::OnUnmap() {
+ LOG("nsWindow::OnUnmap");
+
+ mIsMapped = false;
+
+ if (mSourceDragContext) {
+ static auto sGtkDragCancel =
+ (void (*)(GdkDragContext*))dlsym(RTLD_DEFAULT, "gtk_drag_cancel");
+ if (sGtkDragCancel) {
+ sGtkDragCancel(mSourceDragContext);
+ mSourceDragContext = nullptr;
+ }
+ }
+}
+
+void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
+ LOG("nsWindow::OnSizeAllocate %d,%d -> %d x %d\n", aAllocation->x,
+ aAllocation->y, aAllocation->width, aAllocation->height);
+
+ // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
+ // is enabled. In either cases (Wayland or system titlebar is off on X11)
+ // we don't get _NET_FRAME_EXTENTS X11 property notification so we derive
+ // it from mContainer position.
+ RecomputeClientOffset(/* aNotify = */ true);
+
+ mHasReceivedSizeAllocate = true;
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+
+ // Sometimes the window manager gives us garbage sizes (way past the maximum
+ // texture size) causing crashes if we don't enforce size constraints again
+ // here.
+ ConstrainSize(&size.width, &size.height);
+
+ if (mBounds.Size() == size) {
+ LOG(" Already the same size");
+ // mBounds was set at Create() or Resize().
+ if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1)) {
+ LOG(" No longer needs to dispatch %dx%d", mNeedsDispatchSize.width,
+ mNeedsDispatchSize.height);
+ mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
+ }
+ return;
+ }
+
+ // Invalidate the new part of the window now for the pending paint to
+ // minimize background flashes (GDK does not do this for external resizes
+ // of toplevels.)
+ if (mGdkWindow) {
+ if (mBounds.width < size.width) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ mBounds.width, 0, size.width - mBounds.width, size.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ if (mBounds.height < size.height) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect(
+ 0, mBounds.height, size.width, size.height - mBounds.height));
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+ }
+ }
+
+ // If we update mBounds here, then inner/outerHeight are out of sync until
+ // we call WindowResized.
+ mNeedsDispatchSize = size;
+
+ // Gecko permits running nested event loops during processing of events,
+ // GtkWindow callers of gtk_widget_size_allocate expect the signal
+ // handlers to return sometime in the near future.
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
+}
+
+void nsWindow::OnDeleteEvent() {
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+}
+
+void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
+ LOG("enter notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
+ aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
+ aEvent->detail);
+ // This skips NotifyVirtual and NotifyNonlinearVirtual enter notify events
+ // when the pointer enters a child window. If the destination window is a
+ // Gecko window then we'll catch the corresponding event on that window,
+ // but we won't notice when the pointer directly enters a foreign (plugin)
+ // child window without passing over a visible portion of a Gecko window.
+ if (aEvent->subwindow) {
+ return;
+ }
+
+ // Check before checking for ungrab as the button state may have
+ // changed while a non-Gecko ancestor window had a pointer grab.
+ DispatchMissedButtonReleases(aEvent);
+
+ WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ LOG("OnEnterNotify");
+
+ DispatchInputEvent(&event);
+}
+
+// Some window managers send a bogus top-level leave-notify event on every
+// click. That confuses our event handling code in ways that can break websites,
+// see bug 1805939 for details.
+//
+// Make sure to only check this on bogus environments, since for environments
+// with CSD, gdk_device_get_window_at_position could return the window even when
+// the pointer is in the decoration area.
+static bool IsBogusLeaveNotifyEvent(GdkWindow* aWindow,
+ GdkEventCrossing* aEvent) {
+ static bool sBogusWm = [] {
+ if (GdkIsWaylandDisplay()) {
+ return false;
+ }
+ const auto& desktopEnv = GetDesktopEnvironmentIdentifier();
+ return desktopEnv.EqualsLiteral("fluxbox") || // Bug 1805939 comment 0.
+ desktopEnv.EqualsLiteral("blackbox") || // Bug 1805939 comment 32.
+ desktopEnv.EqualsLiteral("lg3d") || // Bug 1820405.
+ desktopEnv.EqualsLiteral("pekwm") || // Bug 1822911.
+ StringBeginsWith(desktopEnv, "fvwm"_ns);
+ }();
+
+ const bool shouldCheck = [] {
+ switch (StaticPrefs::widget_gtk_ignore_bogus_leave_notify()) {
+ case 0:
+ return false;
+ case 1:
+ return true;
+ default:
+ return sBogusWm;
+ }
+ }();
+
+ if (!shouldCheck || !aWindow) {
+ return false;
+ }
+ GdkDevice* pointer = GdkGetPointer();
+ GdkWindow* winAtPt =
+ gdk_device_get_window_at_position(pointer, nullptr, nullptr);
+ if (!winAtPt) {
+ return false;
+ }
+ // We're still in the same top level window, ignore this leave notify event.
+ GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
+ GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
+ return topLevelAtPt == topLevelWidget;
+}
+
+void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
+ LOG("leave notify (win=%p, sub=%p): %f, %f mode %d, detail %d\n",
+ aEvent->window, aEvent->subwindow, aEvent->x, aEvent->y, aEvent->mode,
+ aEvent->detail);
+
+ // This ignores NotifyVirtual and NotifyNonlinearVirtual leave notify
+ // events when the pointer leaves a child window. If the destination
+ // window is a Gecko window then we'll catch the corresponding event on
+ // that window.
+ //
+ // XXXkt However, we will miss toplevel exits when the pointer directly
+ // leaves a foreign (plugin) child window without passing over a visible
+ // portion of a Gecko window.
+ if (aEvent->subwindow) {
+ return;
+ }
+
+ // The filter out for subwindows should make sure that this is targeted to
+ // this nsWindow.
+ const bool leavingTopLevel = IsTopLevelWindowType();
+ if (leavingTopLevel && IsBogusLeaveNotifyEvent(mGdkWindow, aEvent)) {
+ return;
+ }
+
+ WidgetMouseEvent event(true, eMouseExitFromWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ event.mExitFrom = Some(leavingTopLevel ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+
+ LOG("OnLeaveNotify");
+
+ DispatchInputEvent(&event);
+}
+
+Maybe<GdkWindowEdge> nsWindow::CheckResizerEdge(
+ const LayoutDeviceIntPoint& aPoint) {
+ const bool canResize = [&] {
+ // Don't allow resizing maximized/fullscreen windows.
+ if (mSizeMode != nsSizeMode_Normal) {
+ return false;
+ }
+ if (mIsPIPWindow) {
+ // Note that since we do show resizers on left/right sides on PIP windows,
+ // we still want the resizers there, even when tiled.
+ return true;
+ }
+ if (!mDrawInTitlebar) {
+ return false;
+ }
+ // On KDE, allow for 1 extra pixel at the top of regular windows when
+ // drawing to the titlebar. This matches the native titlebar behavior on
+ // that environment. See bug 1813554.
+ //
+ // Don't do that on GNOME (see bug 1822764). If we wanted to do this on
+ // GNOME we'd need an extra check for mIsTiled, since the window is "stuck"
+ // to the top and bottom.
+ //
+ // Other DEs are untested.
+ return mDrawInTitlebar && IsKdeDesktopEnvironment();
+ }();
+
+ if (!canResize) {
+ return Nothing();
+ }
+
+ // If we're not in a PiP window, allow 1px resizer edge from the top edge,
+ // and nothing else.
+ // This is to allow resizes of tiled windows on KDE, see bug 1813554.
+ const int resizerHeight = (mIsPIPWindow ? 15 : 1) * GdkCeiledScaleFactor();
+ const int resizerWidth = resizerHeight * 4;
+
+ const int topDist = aPoint.y;
+ const int leftDist = aPoint.x;
+ const int rightDist = mBounds.width - aPoint.x;
+ const int bottomDist = mBounds.height - aPoint.y;
+
+ // We can't emulate resize of North/West edges on Wayland as we can't shift
+ // toplevel window.
+ bool waylandLimitedResize = mAspectRatio != 0.0f && GdkIsWaylandDisplay();
+
+ if (topDist <= resizerHeight) {
+ if (rightDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_NORTH_EAST);
+ }
+ if (leftDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_NORTH_WEST);
+ }
+ return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_NORTH);
+ }
+
+ if (!mIsPIPWindow) {
+ return Nothing();
+ }
+
+ if (bottomDist <= resizerHeight) {
+ if (leftDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
+ }
+ if (rightDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
+ }
+ return Some(GDK_WINDOW_EDGE_SOUTH);
+ }
+
+ if (leftDist <= resizerHeight) {
+ if (topDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_NORTH_WEST);
+ }
+ if (bottomDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_WEST);
+ }
+ return waylandLimitedResize ? Nothing() : Some(GDK_WINDOW_EDGE_WEST);
+ }
+
+ if (rightDist <= resizerHeight) {
+ if (topDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_NORTH_EAST);
+ }
+ if (bottomDist <= resizerWidth) {
+ return Some(GDK_WINDOW_EDGE_SOUTH_EAST);
+ }
+ return Some(GDK_WINDOW_EDGE_EAST);
+ }
+ return Nothing();
+}
+
+template <typename Event>
+static LayoutDeviceIntPoint GetRefPoint(nsWindow* aWindow, Event* aEvent) {
+ if (aEvent->window == aWindow->GetGdkWindow()) {
+ // we are the window that the event happened on so no need for expensive
+ // WidgetToScreenOffset
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ }
+ // XXX we're never quite sure which GdkWindow the event came from due to our
+ // custom bubbling in scroll_event_cb(), so use ScreenToWidget to translate
+ // the screen root coordinates into coordinates relative to this widget.
+ return aWindow->GdkEventCoordsToDevicePixels(aEvent->x_root, aEvent->y_root) -
+ aWindow->WidgetToScreenOffset();
+}
+
+void nsWindow::EmulateResizeDrag(GdkEventMotion* aEvent) {
+ auto newPoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
+ LayoutDeviceIntPoint diff = newPoint - mLastResizePoint;
+ mLastResizePoint = newPoint;
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ LayoutDeviceIntSize newSize(size.width + diff.x, size.height + diff.y);
+
+ if (mAspectResizer.value() == GTK_ORIENTATION_VERTICAL) {
+ newSize.width = int(newSize.height * mAspectRatio);
+ } else { // GTK_ORIENTATION_HORIZONTAL
+ newSize.height = int(newSize.width / mAspectRatio);
+ }
+ LOG(" aspect ratio correction %d x %d aspect %f\n", newSize.width,
+ newSize.height, mAspectRatio);
+ gtk_window_resize(GTK_WINDOW(mShell), newSize.width, newSize.height);
+}
+
+void nsWindow::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
+ if (!mGdkWindow) {
+ return;
+ }
+
+ // Emulate gdk_window_begin_resize_drag() for windows
+ // with fixed aspect ratio on Wayland.
+ if (mAspectResizer && mAspectRatio != 0.0f) {
+ EmulateResizeDrag(aEvent);
+ return;
+ }
+
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ GdkWindow* dragWindow = nullptr;
+
+ // find the top-level window
+ if (mGdkWindow) {
+ dragWindow = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(dragWindow, "gdk_window_get_toplevel should not return null");
+ }
+
+#ifdef MOZ_X11
+ if (dragWindow && GdkIsX11Display()) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ GdkScreen* screen = gdk_window_get_screen(dragWindow);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ dragWindow = nullptr;
+ }
+ }
+#endif
+
+ if (dragWindow) {
+ gdk_window_begin_move_drag(dragWindow, 1, aEvent->x_root, aEvent->y_root,
+ aEvent->time);
+ return;
+ }
+ }
+
+ mWidgetCursorLocked = false;
+ const auto refPoint = GetRefPoint(this, aEvent);
+ if (auto edge = CheckResizerEdge(refPoint)) {
+ nsCursor cursor = eCursor_none;
+ switch (*edge) {
+ case GDK_WINDOW_EDGE_SOUTH:
+ case GDK_WINDOW_EDGE_NORTH:
+ cursor = eCursor_ns_resize;
+ break;
+ case GDK_WINDOW_EDGE_WEST:
+ case GDK_WINDOW_EDGE_EAST:
+ cursor = eCursor_ew_resize;
+ break;
+ case GDK_WINDOW_EDGE_NORTH_WEST:
+ case GDK_WINDOW_EDGE_SOUTH_EAST:
+ cursor = eCursor_nwse_resize;
+ break;
+ case GDK_WINDOW_EDGE_NORTH_EAST:
+ case GDK_WINDOW_EDGE_SOUTH_WEST:
+ cursor = eCursor_nesw_resize;
+ break;
+ }
+ SetCursor(Cursor{cursor});
+ // If we set resize cursor on widget level keep it locked and prevent layout
+ // to switch it back to default (by synthetic mouse events for instance)
+ // until resize is finished. This affects PIP windows only.
+ if (mIsPIPWindow) {
+ mWidgetCursorLocked = true;
+ }
+ return;
+ }
+
+ WidgetMouseEvent event(true, eMouseMove, this, WidgetMouseEvent::eReal);
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ // Sometime gdk generate 0 pressure value between normal values
+ // We have to ignore that and use last valid value
+ if (pressure) {
+ mLastMotionPressure = pressure;
+ }
+ event.mPressure = mLastMotionPressure;
+ event.mRefPoint = refPoint;
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+
+ DispatchInputEvent(&event);
+}
+
+// If the automatic pointer grab on ButtonPress has deactivated before
+// ButtonRelease, and the mouse button is released while the pointer is not
+// over any a Gecko window, then the ButtonRelease event will not be received.
+// (A similar situation exists when the pointer is grabbed with owner_events
+// True as the ButtonRelease may be received on a foreign [plugin] window).
+// Use this method to check for released buttons when the pointer returns to a
+// Gecko window.
+void nsWindow::DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent) {
+ guint changed = aGdkEvent->state ^ gButtonState;
+ // Only consider button releases.
+ // (Ignore button presses that occurred outside Gecko.)
+ guint released = changed & gButtonState;
+ gButtonState = aGdkEvent->state;
+
+ // Loop over each button, excluding mouse wheel buttons 4 and 5 for which
+ // GDK ignores releases.
+ for (guint buttonMask = GDK_BUTTON1_MASK; buttonMask <= GDK_BUTTON3_MASK;
+ buttonMask <<= 1) {
+ if (released & buttonMask) {
+ int16_t buttonType;
+ switch (buttonMask) {
+ case GDK_BUTTON1_MASK:
+ buttonType = MouseButton::ePrimary;
+ break;
+ case GDK_BUTTON2_MASK:
+ buttonType = MouseButton::eMiddle;
+ break;
+ default:
+ NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
+ "Unexpected button mask");
+ buttonType = MouseButton::eSecondary;
+ }
+
+ LOG("Synthesized button %u release", guint(buttonType + 1));
+
+ // Dispatch a synthesized button up event to tell Gecko about the
+ // change in state. This event is marked as synthesized so that
+ // it is not dispatched as a DOM event, because we don't know the
+ // position, widget, modifiers, or time/order.
+ WidgetMouseEvent synthEvent(true, eMouseUp, this,
+ WidgetMouseEvent::eSynthesized);
+ synthEvent.mButton = buttonType;
+ DispatchInputEvent(&synthEvent);
+ }
+ }
+}
+
+void nsWindow::InitButtonEvent(WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent,
+ const LayoutDeviceIntPoint& aRefPoint) {
+ aEvent.mRefPoint = aRefPoint;
+
+ guint modifierState = aGdkEvent->state;
+ // aEvent's state includes the button state from immediately before this
+ // event. If aEvent is a mousedown or mouseup event, we need to update
+ // the button state.
+ guint buttonMask = 0;
+ switch (aGdkEvent->button) {
+ case 1:
+ buttonMask = GDK_BUTTON1_MASK;
+ break;
+ case 2:
+ buttonMask = GDK_BUTTON2_MASK;
+ break;
+ case 3:
+ buttonMask = GDK_BUTTON3_MASK;
+ break;
+ }
+ if (aGdkEvent->type == GDK_BUTTON_RELEASE) {
+ modifierState &= ~buttonMask;
+ } else {
+ modifierState |= buttonMask;
+ }
+
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+
+ aEvent.AssignEventTime(GetWidgetEventTime(aGdkEvent->time));
+
+ switch (aGdkEvent->type) {
+ case GDK_2BUTTON_PRESS:
+ aEvent.mClickCount = 2;
+ break;
+ case GDK_3BUTTON_PRESS:
+ aEvent.mClickCount = 3;
+ break;
+ // default is one click
+ default:
+ aEvent.mClickCount = 1;
+ }
+}
+
+static guint ButtonMaskFromGDKButton(guint button) {
+ return GDK_BUTTON1_MASK << (button - 1);
+}
+
+void nsWindow::DispatchContextMenuEventFromMouseEvent(
+ uint16_t domButton, GdkEventButton* aEvent,
+ const LayoutDeviceIntPoint& aRefPoint) {
+ if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent, aRefPoint);
+ contextMenuEvent.mPressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void nsWindow::TryToShowNativeWindowMenu(GdkEventButton* aEvent) {
+ if (!gdk_window_show_window_menu(GetToplevelGdkWindow(), (GdkEvent*)aEvent)) {
+ NS_WARNING("Native context menu wasn't shown");
+ }
+}
+
+bool nsWindow::DoTitlebarAction(LookAndFeel::TitlebarEvent aEvent,
+ GdkEventButton* aButtonEvent) {
+ LOG("DoTitlebarAction %s click",
+ aEvent == LookAndFeel::TitlebarEvent::Double_Click ? "double" : "middle");
+ switch (LookAndFeel::GetTitlebarAction(aEvent)) {
+ case LookAndFeel::TitlebarAction::WindowMenu:
+ // Titlebar app menu
+ LOG(" action menu");
+ TryToShowNativeWindowMenu(aButtonEvent);
+ break;
+ // Lower is part of gtk_surface1 protocol which we can't support
+ // as Gtk keeps it private. So emulate it by minimize.
+ case LookAndFeel::TitlebarAction::WindowLower:
+ case LookAndFeel::TitlebarAction::WindowMinimize:
+ LOG(" action minimize");
+ SetSizeMode(nsSizeMode_Minimized);
+ break;
+ case LookAndFeel::TitlebarAction::WindowMaximize:
+ LOG(" action maximize");
+ SetSizeMode(nsSizeMode_Maximized);
+ break;
+ case LookAndFeel::TitlebarAction::WindowMaximizeToggle:
+ LOG(" action toggle maximize");
+ if (mSizeMode == nsSizeMode_Maximized) {
+ SetSizeMode(nsSizeMode_Normal);
+ } else if (mSizeMode == nsSizeMode_Normal) {
+ SetSizeMode(nsSizeMode_Maximized);
+ }
+ break;
+ case LookAndFeel::TitlebarAction::None:
+ default:
+ LOG(" action none");
+ return false;
+ }
+ return true;
+}
+
+void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
+ LOG("Button %u press\n", aEvent->button);
+
+ SetLastMousePressEvent((GdkEvent*)aEvent);
+
+ // If you double click in GDK, it will actually generate a second
+ // GDK_BUTTON_PRESS before sending the GDK_2BUTTON_PRESS, and this is
+ // different than the DOM spec. GDK puts this in the queue
+ // programatically, so it's safe to assume that if there's a
+ // double click in the queue, it was generated so we can just drop
+ // this click.
+ GUniquePtr<GdkEvent> peekedEvent(gdk_event_peek());
+ if (peekedEvent) {
+ GdkEventType type = peekedEvent->any.type;
+ if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) {
+ return;
+ }
+ }
+
+ nsWindow* containerWindow = GetContainerWindow();
+ if (!gFocusWindow && containerWindow) {
+ containerWindow->DispatchActivateEvent();
+ }
+
+ const auto refPoint = GetRefPoint(this, aEvent);
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
+ if (aEvent->button == 3 && mDraggableRegion.Contains(refPoint)) {
+ GUniquePtr<GdkEvent> eventCopy;
+ if (aEvent->type != GDK_BUTTON_PRESS) {
+ // If the user double-clicks too fast we'll get a 2BUTTON_PRESS event
+ // instead, and that isn't handled by open_window_menu, so coerce it
+ // into a regular press.
+ eventCopy.reset(gdk_event_copy((GdkEvent*)aEvent));
+ eventCopy->type = GDK_BUTTON_PRESS;
+ }
+ TryToShowNativeWindowMenu(eventCopy ? &eventCopy->button : aEvent);
+ }
+ return;
+ }
+
+ // Check to see if the event is within our window's resize region
+ if (auto edge = CheckResizerEdge(refPoint)) {
+ // On Wayland Gtk fails to vertically/horizontally resize windows
+ // with fixed aspect ratio. We need to emulate
+ // gdk_window_begin_resize_drag() at OnMotionNotifyEvent().
+ if (mAspectRatio != 0.0f && GdkIsWaylandDisplay()) {
+ mLastResizePoint = LayoutDeviceIntPoint::Floor(aEvent->x, aEvent->y);
+ switch (*edge) {
+ case GDK_WINDOW_EDGE_SOUTH:
+ mAspectResizer = Some(GTK_ORIENTATION_VERTICAL);
+ break;
+ case GDK_WINDOW_EDGE_EAST:
+ mAspectResizer = Some(GTK_ORIENTATION_HORIZONTAL);
+ break;
+ default:
+ mAspectResizer.reset();
+ break;
+ }
+ ApplySizeConstraints();
+ }
+ if (!mAspectResizer) {
+ gdk_window_begin_resize_drag(GetToplevelGdkWindow(), *edge,
+ aEvent->button, aEvent->x_root,
+ aEvent->y_root, aEvent->time);
+ }
+ return;
+ }
+
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ mLastMotionPressure = pressure;
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ // These are mapped to horizontal scroll
+ case 6:
+ case 7:
+ NS_WARNING("We're not supporting legacy horizontal scroll event");
+ return;
+ // Map buttons 8-9(10) to back/forward
+ case 8:
+ if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ case 10:
+ if (!Preferences::GetBool("mousebutton.5th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Forward);
+ return;
+ default:
+ return;
+ }
+
+ gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
+
+ WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent, refPoint);
+ event.mPressure = mLastMotionPressure;
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ const bool defaultPrevented =
+ eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
+
+ if (!defaultPrevented && mDraggableRegion.Contains(refPoint)) {
+ if (domButton == MouseButton::ePrimary) {
+ mWindowShouldStartDragging = true;
+ } else if (domButton == MouseButton::eMiddle &&
+ StaticPrefs::widget_gtk_titlebar_action_middle_click_enabled()) {
+ DoTitlebarAction(nsXPLookAndFeel::TitlebarEvent::Middle_Click, aEvent);
+ }
+ }
+
+ // right menu click on linux should also pop up a context menu
+ if (!StaticPrefs::ui_context_menus_after_mouseup() &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
+ }
+}
+
+void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
+ LOG("Button %u release\n", aEvent->button);
+
+ SetLastMousePressEvent(nullptr);
+
+ if (!mGdkWindow) {
+ return;
+ }
+
+ if (mAspectResizer) {
+ mAspectResizer = Nothing();
+ return;
+ }
+
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ }
+
+ uint16_t domButton;
+ switch (aEvent->button) {
+ case 1:
+ domButton = MouseButton::ePrimary;
+ break;
+ case 2:
+ domButton = MouseButton::eMiddle;
+ break;
+ case 3:
+ domButton = MouseButton::eSecondary;
+ break;
+ default:
+ return;
+ }
+
+ gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
+
+ const auto refPoint = GetRefPoint(this, aEvent);
+
+ WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent, refPoint);
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.mPressure = pressure ? (float)pressure : (float)mLastMotionPressure;
+
+ // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
+ // to use it for the doubleclick position check.
+ const LayoutDeviceIntPoint pos = event.mRefPoint;
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ const bool defaultPrevented =
+ eventStatus.mContentStatus == nsEventStatus_eConsumeNoDefault;
+ // Check if mouse position in titlebar and doubleclick happened to
+ // trigger defined action.
+ if (!defaultPrevented && mDrawInTitlebar &&
+ event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
+ mDraggableRegion.Contains(pos)) {
+ DoTitlebarAction(nsXPLookAndFeel::TitlebarEvent::Double_Click, aEvent);
+ }
+ mLastMotionPressure = pressure;
+
+ // right menu click on linux should also pop up a context menu
+ if (StaticPrefs::ui_context_menus_after_mouseup() &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent, refPoint);
+ }
+
+ // Open window manager menu on PIP window to allow user
+ // to place it on top / all workspaces.
+ if (mAlwaysOnTop && aEvent->button == 3) {
+ TryToShowNativeWindowMenu(aEvent);
+ }
+}
+
+void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
+ LOG("OnContainerFocusInEvent");
+
+ // Unset the urgency hint, if possible
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window))) {
+ SetUrgencyHint(top_window, false);
+ }
+
+ // Return if being called within SetFocus because the focus manager
+ // already knows that the window is active.
+ if (gBlockActivateEvent) {
+ LOG("activated notification is blocked");
+ return;
+ }
+
+ // If keyboard input will be accepted, the focus manager will call
+ // SetFocus to set the correct window.
+ gFocusWindow = nullptr;
+
+ DispatchActivateEvent();
+
+ if (!gFocusWindow) {
+ // We don't really have a window for dispatching key events, but
+ // setting a non-nullptr value here prevents OnButtonPressEvent() from
+ // dispatching an activation notification if the widget is already
+ // active.
+ gFocusWindow = this;
+ }
+
+ LOG("Events sent from focus in event");
+}
+
+void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
+ LOG("OnContainerFocusOutEvent");
+
+ if (IsTopLevelWindowType()) {
+ // Rollup menus when a window is focused out unless a drag is occurring.
+ // This check is because drags grab the keyboard and cause a focus out on
+ // versions of GTK before 2.18.
+ const bool shouldRollupMenus = [&] {
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+ if (!dragSession) {
+ return true;
+ }
+ // We also roll up when a drag is from a different application
+ nsCOMPtr<nsINode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ return !sourceNode;
+ }();
+
+ if (shouldRollupMenus) {
+ RollupAllMenus();
+ }
+
+ if (RefPtr pm = nsXULPopupManager::GetInstance()) {
+ pm->RollupTooltips();
+ }
+ }
+
+ if (gFocusWindow) {
+ RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow;
+ if (gFocusWindow->mIMContext) {
+ gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow);
+ }
+ gFocusWindow = nullptr;
+ }
+
+ DispatchDeactivateEvent();
+
+ if (IsChromeWindowTitlebar()) {
+ // DispatchDeactivateEvent() ultimately results in a call to
+ // BrowsingContext::SetIsActiveBrowserWindow(), which resets
+ // the state. We call UpdateMozWindowActive() to keep it in
+ // sync with GDK_WINDOW_STATE_FOCUSED.
+ UpdateMozWindowActive();
+ }
+
+ LOG("Done with container focus out");
+}
+
+bool nsWindow::DispatchCommandEvent(nsAtom* aCommand) {
+ nsEventStatus status;
+ WidgetCommandEvent appCommandEvent(true, aCommand, this);
+ DispatchEvent(&appCommandEvent, status);
+ return TRUE;
+}
+
+bool nsWindow::DispatchContentCommandEvent(EventMessage aMsg) {
+ nsEventStatus status;
+ WidgetContentCommandEvent event(true, aMsg, this);
+ DispatchEvent(&event, status);
+ return TRUE;
+}
+
+WidgetEventTime nsWindow::GetWidgetEventTime(guint32 aEventTime) {
+ return WidgetEventTime(GetEventTimeStamp(aEventTime));
+}
+
+TimeStamp nsWindow::GetEventTimeStamp(guint32 aEventTime) {
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ // nsWindow has been Destroy()ed.
+ return TimeStamp::Now();
+ }
+ if (aEventTime == 0) {
+ // Some X11 and GDK events may be received with a time of 0 to indicate
+ // that they are synthetic events. Some input method editors do this.
+ // In this case too, just return the current timestamp.
+ return TimeStamp::Now();
+ }
+
+ TimeStamp eventTimeStamp;
+
+ if (GdkIsWaylandDisplay()) {
+ // Wayland compositors use monotonic time to set timestamps.
+ int64_t timestampTime = g_get_monotonic_time() / 1000;
+ guint32 refTimeTruncated = guint32(timestampTime);
+
+ timestampTime -= refTimeTruncated - aEventTime;
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
+ eventTimeStamp = TimeStamp::FromSystemTime(tick);
+ } else {
+#ifdef MOZ_X11
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ eventTimeStamp =
+ TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
+#endif
+ }
+ return eventTimeStamp;
+}
+
+#ifdef MOZ_X11
+mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
+ MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
+ if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
+ mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
+ }
+ return mCurrentTimeGetter.get();
+}
+#endif
+
+gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
+ LOG("OnKeyPressEvent");
+
+ KeymapWrapper::HandleKeyPressEvent(this, aEvent);
+ return TRUE;
+}
+
+gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
+ LOG("OnKeyReleaseEvent");
+ if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(this, aEvent))) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
+ LOG("OnScrollEvent");
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, true, false)) {
+ return;
+ }
+
+ // check for duplicate legacy scroll event, see GNOME bug 726878
+ if (aEvent->direction != GDK_SCROLL_SMOOTH &&
+ mLastScrollEventTime == aEvent->time) {
+ LOG("[%d] duplicate legacy scroll event %d\n", aEvent->time,
+ aEvent->direction);
+ return;
+ }
+ WidgetWheelEvent wheelEvent(true, eWheel, this);
+ wheelEvent.mDeltaMode = dom::WheelEvent_Binding::DOM_DELTA_LINE;
+ switch (aEvent->direction) {
+ case GDK_SCROLL_SMOOTH: {
+ // As of GTK 3.4, all directional scroll events are provided by
+ // the GDK_SCROLL_SMOOTH direction on XInput2 and Wayland devices.
+ mLastScrollEventTime = aEvent->time;
+
+ // Special handling for touchpads to support flings
+ // (also known as kinetic/inertial/momentum scrolling)
+ GdkDevice* device = gdk_event_get_source_device((GdkEvent*)aEvent);
+ GdkInputSource source = gdk_device_get_source(device);
+ if (source == GDK_SOURCE_TOUCHSCREEN || source == GDK_SOURCE_TOUCHPAD ||
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.isSome()) {
+ if (StaticPrefs::apz_gtk_pangesture_enabled() &&
+ gtk_check_version(3, 20, 0) == nullptr) {
+ static auto sGdkEventIsScrollStopEvent =
+ (gboolean(*)(const GdkEvent*))dlsym(
+ RTLD_DEFAULT, "gdk_event_is_scroll_stop_event");
+
+ LOG("[%d] pan smooth event dx=%f dy=%f inprogress=%d\n", aEvent->time,
+ aEvent->delta_x, aEvent->delta_y, mPanInProgress);
+ auto eventType = PanGestureInput::PANGESTURE_PAN;
+ if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ } else if (!mPanInProgress) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ } else if (mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase
+ .isSome()) {
+ switch (*mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase) {
+ case PHASE_BEGIN:
+ // we should never hit this because we'll hit the above case
+ // before this.
+ MOZ_ASSERT_UNREACHABLE();
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ break;
+ case PHASE_UPDATE:
+ // nothing to do here, eventtype should already be set
+ MOZ_ASSERT(mPanInProgress);
+ MOZ_ASSERT(eventType == PanGestureInput::PANGESTURE_PAN);
+ eventType = PanGestureInput::PANGESTURE_PAN;
+ break;
+ case PHASE_END:
+ MOZ_ASSERT(mPanInProgress);
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE();
+ break;
+ }
+ }
+
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase.reset();
+
+ const bool isPageMode =
+#ifdef NIGHTLY_BUILD
+ StaticPrefs::apz_gtk_pangesture_delta_mode() == 1;
+#else
+ StaticPrefs::apz_gtk_pangesture_delta_mode() != 2;
+#endif
+ const double multiplier =
+ isPageMode
+ ? StaticPrefs::apz_gtk_pangesture_page_delta_mode_multiplier()
+ : StaticPrefs::
+ apz_gtk_pangesture_pixel_delta_mode_multiplier() *
+ FractionalScaleFactor();
+
+ ScreenPoint deltas(float(aEvent->delta_x * multiplier),
+ float(aEvent->delta_y * multiplier));
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+ PanGestureInput panEvent(
+ eventType, GetEventTimeStamp(aEvent->time),
+ ScreenPoint(touchPoint.x, touchPoint.y), deltas,
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+ panEvent.mDeltaType = isPageMode ? PanGestureInput::PANDELTA_PAGE
+ : PanGestureInput::PANDELTA_PIXEL;
+ panEvent.mSimulateMomentum =
+ StaticPrefs::apz_gtk_kinetic_scroll_enabled();
+
+ DispatchPanGesture(panEvent);
+
+ if (mCurrentSynthesizedTouchpadPan.mSavedObserver != 0) {
+ mozilla::widget::AutoObserverNotifier::NotifySavedObserver(
+ mCurrentSynthesizedTouchpadPan.mSavedObserver,
+ "touchpadpanevent");
+ mCurrentSynthesizedTouchpadPan.mSavedObserver = 0;
+ }
+
+ return;
+ }
+
+ // Older GTK doesn't support stop events, so we can't support fling
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSLY;
+ }
+
+ // TODO - use a more appropriate scrolling unit than lines.
+ // Multiply event deltas by 3 to emulate legacy behaviour.
+ wheelEvent.mDeltaX = aEvent->delta_x * 3;
+ wheelEvent.mDeltaY = aEvent->delta_y * 3;
+ wheelEvent.mWheelTicksX = aEvent->delta_x;
+ wheelEvent.mWheelTicksY = aEvent->delta_y;
+ wheelEvent.mIsNoLineOrPageDelta = true;
+
+ break;
+ }
+ case GDK_SCROLL_UP:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
+ wheelEvent.mWheelTicksY = -1;
+ break;
+ case GDK_SCROLL_DOWN:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
+ wheelEvent.mWheelTicksY = 1;
+ break;
+ case GDK_SCROLL_LEFT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
+ wheelEvent.mWheelTicksX = -1;
+ break;
+ case GDK_SCROLL_RIGHT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
+ wheelEvent.mWheelTicksX = 1;
+ break;
+ }
+
+ wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
+
+ KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
+
+ wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ DispatchInputEvent(&wheelEvent);
+}
+
+void nsWindow::DispatchPanGesture(PanGestureInput& aPanInput) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ if (mSwipeTracker) {
+ // Give the swipe tracker a first pass at the event. If a new pan gesture
+ // has been started since the beginning of the swipe, the swipe tracker
+ // will know to ignore the event.
+ nsEventStatus status = mSwipeTracker->ProcessEvent(aPanInput);
+ if (status == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ APZEventResult result;
+ if (mAPZC) {
+ MOZ_ASSERT(APZThreadUtils::IsControllerThread());
+
+ result = mAPZC->InputBridge()->ReceiveInputEvent(aPanInput);
+ if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
+ return;
+ }
+ }
+
+ WidgetWheelEvent event = aPanInput.ToWidgetEvent(this);
+ if (!mAPZC) {
+ if (MayStartSwipeForNonAPZ(aPanInput)) {
+ return;
+ }
+ } else {
+ event = MayStartSwipeForAPZ(aPanInput, result);
+ }
+
+ ProcessUntransformedAPZEvent(&event, result);
+}
+
+void nsWindow::OnVisibilityNotifyEvent(GdkVisibilityState aState) {
+ LOG("nsWindow::OnVisibilityNotifyEvent [%p] state 0x%x\n", this, aState);
+ auto state = aState == GDK_VISIBILITY_FULLY_OBSCURED
+ ? OcclusionState::OCCLUDED
+ : OcclusionState::UNKNOWN;
+ NotifyOcclusionState(state);
+}
+
+void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
+ GdkEventWindowState* aEvent) {
+ LOG("nsWindow::OnWindowStateEvent for %p changed 0x%x new_window_state "
+ "0x%x\n",
+ aWidget, aEvent->changed_mask, aEvent->new_window_state);
+
+ if (IS_MOZ_CONTAINER(aWidget)) {
+ // This event is notifying the container widget of changes to the
+ // toplevel window. Just detect changes affecting whether windows are
+ // viewable.
+ //
+ // (A visibility notify event is sent to each window that becomes
+ // viewable when the toplevel is mapped, but we can't rely on that for
+ // setting mHasMappedToplevel because these toplevel window state
+ // events are asynchronous. The windows in the hierarchy now may not
+ // be the same windows as when the toplevel was mapped, so they may
+ // not get VisibilityNotify events.)
+ bool mapped = !(aEvent->new_window_state &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_WITHDRAWN));
+ SetHasMappedToplevel(mapped);
+ LOG("\tquick return because IS_MOZ_CONTAINER(aWidget) is true\n");
+ return;
+ }
+ // else the widget is a shell widget.
+
+ // The block below is a bit evil.
+ //
+ // When a window is resized before it is shown, gtk_window_resize() delays
+ // resizes until the window is shown. If gtk_window_state_event() sees a
+ // GDK_WINDOW_STATE_MAXIMIZED change [1] before the window is shown, then
+ // gtk_window_compute_configure_request_size() ignores the values from the
+ // resize [2]. See bug 1449166 for an example of how this could happen.
+ //
+ // [1] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L7967
+ // [2] https://gitlab.gnome.org/GNOME/gtk/blob/3.22.30/gtk/gtkwindow.c#L9377
+ //
+ // In order to provide a sensible size for the window when the user exits
+ // maximized state, we hide the GDK_WINDOW_STATE_MAXIMIZED change from
+ // gtk_window_state_event() so as to trick GTK into using the values from
+ // gtk_window_resize() in its configure request.
+ //
+ // We instead notify gtk_window_state_event() of the maximized state change
+ // once the window is shown.
+ //
+ // See https://gitlab.gnome.org/GNOME/gtk/issues/1044
+ //
+ // This may be fixed in Gtk 3.24+ but it's still live and kicking
+ // (Bug 1791779).
+ if (!mIsShown) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask & ~GDK_WINDOW_STATE_MAXIMIZED);
+ } else if (aEvent->changed_mask & GDK_WINDOW_STATE_WITHDRAWN &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ aEvent->changed_mask = static_cast<GdkWindowState>(
+ aEvent->changed_mask | GDK_WINDOW_STATE_MAXIMIZED);
+ }
+
+ // This is a workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1395
+ // Gtk+ controls window active appearance by window-state-event signal.
+ if (IsChromeWindowTitlebar() &&
+ (aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED)) {
+ // Emulate what Gtk+ does at gtk_window_state_event().
+ // We can't check GTK_STATE_FLAG_BACKDROP directly as it's set by Gtk+
+ // *after* this window-state-event handler.
+ mTitlebarBackdropState =
+ !(aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED);
+
+ // keep IsActiveBrowserWindow in sync with GDK_WINDOW_STATE_FOCUSED
+ UpdateMozWindowActive();
+
+ ForceTitlebarRedraw();
+ }
+
+ // We don't care about anything but changes in the maximized/icon/fullscreen
+ // states but we need a workaround for bug in Wayland:
+ // https://gitlab.gnome.org/GNOME/gtk/issues/67
+ // Under wayland the gtk_window_iconify implementation does NOT synthetize
+ // window_state_event where the GDK_WINDOW_STATE_ICONIFIED is set.
+ // During restore we won't get aEvent->changed_mask with
+ // the GDK_WINDOW_STATE_ICONIFIED so to detect that change we use the stored
+ // mSizeMode and obtaining a focus.
+ bool waylandWasIconified =
+ (GdkIsWaylandDisplay() &&
+ aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
+ mSizeMode == nsSizeMode_Minimized);
+ if (!waylandWasIconified &&
+ (aEvent->changed_mask &
+ (GDK_WINDOW_STATE_ICONIFIED | GDK_WINDOW_STATE_MAXIMIZED |
+ GDK_WINDOW_STATE_TILED | GDK_WINDOW_STATE_FULLSCREEN)) == 0) {
+ LOG("\tearly return because no interesting bits changed\n");
+ return;
+ }
+
+ auto oldSizeMode = mSizeMode;
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
+ LOG("\tIconified\n");
+ mSizeMode = nsSizeMode_Minimized;
+#ifdef ACCESSIBILITY
+ DispatchMinimizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
+ LOG("\tFullscreen\n");
+ mSizeMode = nsSizeMode_Fullscreen;
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ LOG("\tMaximized\n");
+ mSizeMode = nsSizeMode_Maximized;
+#ifdef ACCESSIBILITY
+ DispatchMaximizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else {
+ LOG("\tNormal\n");
+ mSizeMode = nsSizeMode_Normal;
+#ifdef ACCESSIBILITY
+ DispatchRestoreEventAccessible();
+#endif // ACCESSIBILITY
+ }
+
+ mIsTiled = aEvent->new_window_state & GDK_WINDOW_STATE_TILED;
+ LOG("\tTiled: %d\n", int(mIsTiled));
+
+ if (mWidgetListener && mSizeMode != oldSizeMode) {
+ mWidgetListener->SizeModeChanged(mSizeMode);
+ }
+
+ if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
+ if (mSizeMode == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ } else {
+ SetTitlebarRect();
+ }
+}
+
+void nsWindow::OnDPIChanged() {
+ // Update menu's font size etc.
+ // This affects style / layout because it affects system font sizes.
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ mWidgetListener->UIResolutionChanged();
+ }
+}
+
+void nsWindow::OnCheckResize() { mPendingConfigures++; }
+
+void nsWindow::OnCompositedChanged() {
+ // Update CSD after the change in alpha visibility. This only affects
+ // system metrics, not other theme shenanigans.
+ NotifyThemeChanged(ThemeChangeKind::MediaQueriesOnly);
+ mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
+}
+
+void nsWindow::OnScaleChanged(bool aNotify) {
+ if (!IsTopLevelWindowType()) {
+ return;
+ }
+ if (!mGdkWindow) {
+ return; // We'll get there again when we configure the window.
+ }
+ gint newCeiled = gdk_window_get_scale_factor(mGdkWindow);
+ double newFractional = [&] {
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ return moz_container_wayland_get_fractional_scale(mContainer);
+ }
+#endif
+ return 0.0;
+ }();
+
+ if (mCeiledScaleFactor == newCeiled &&
+ mFractionalScaleFactor == newFractional) {
+ return;
+ }
+
+ LOG("OnScaleChanged %d, %f -> %d, %f\n", int(mCeiledScaleFactor),
+ mFractionalScaleFactor, newCeiled, newFractional);
+
+ mCeiledScaleFactor = newCeiled;
+ mFractionalScaleFactor = newFractional;
+
+ if (!aNotify) {
+ return;
+ }
+
+ // We pause compositor to avoid rendering of obsoleted remote content which
+ // produces flickering.
+ // Re-enable compositor again when remote content is updated or timeout
+ // happens.
+ PauseCompositorFlickering();
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(allocation).Size();
+ mBounds.SizeTo(size);
+ // Check mBounds size
+ if (mCompositorSession &&
+ !wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
+ gfxCriticalNoteOnce << "Invalid mBounds in OnScaleChanged " << mBounds;
+ }
+
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ }
+ }
+
+ DispatchResized();
+
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+
+ if (mCursor.IsCustom()) {
+ mUpdateCursor = true;
+ SetCursor(Cursor{mCursor});
+ }
+}
+
+void nsWindow::DispatchDragEvent(EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime) {
+ LOGDRAG("nsWindow::DispatchDragEvent");
+ WidgetDragEvent event(true, aMsg, this);
+
+ InitDragEvent(event);
+
+ event.mRefPoint = aRefPoint;
+ event.AssignEventTime(GetWidgetEventTime(aTime));
+
+ DispatchInputEvent(&event);
+}
+
+void nsWindow::OnDragDataReceivedEvent(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ LOGDRAG("nsWindow::OnDragDataReceived");
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime);
+}
+
+nsWindow* nsWindow::GetTransientForWindowIfPopup() {
+ if (mWindowType != WindowType::Popup) {
+ return nullptr;
+ }
+ GtkWindow* toplevel = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (toplevel) {
+ return get_window_for_gtk_widget(GTK_WIDGET(toplevel));
+ }
+ return nullptr;
+}
+
+bool nsWindow::IsHandlingTouchSequence(GdkEventSequence* aSequence) {
+ return mHandleTouchEvent && mTouches.Contains(aSequence);
+}
+
+gboolean nsWindow::OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent) {
+ if (!StaticPrefs::apz_gtk_touchpad_pinch_enabled()) {
+ return TRUE;
+ }
+ // Do not respond to pinch gestures involving more than two fingers
+ // unless specifically preffed on. These are sometimes hooked up to other
+ // actions at the desktop environment level and having the browser also
+ // pinch can be undesirable.
+ if (aEvent->n_fingers > 2 &&
+ !StaticPrefs::apz_gtk_touchpad_pinch_three_fingers_enabled()) {
+ return FALSE;
+ }
+ auto pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ ScreenCoord currentSpan;
+ ScreenCoord previousSpan;
+
+ switch (aEvent->phase) {
+ case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ currentSpan = aEvent->scale;
+ mCurrentTouchpadFocus = ViewAs<ScreenPixel>(
+ GetRefPoint(this, aEvent),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ // Assign PreviousSpan --> 0.999 to make mDeltaY field of the
+ // WidgetWheelEvent that this PinchGestureInput event will be converted
+ // to not equal Zero as our discussion because we observed that the
+ // scale of the PHASE_BEGIN event is 1.
+ previousSpan = 0.999;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ mCurrentTouchpadFocus += ScreenPoint(aEvent->dx, aEvent->dy);
+ if (aEvent->scale == mLastPinchEventSpan) {
+ return FALSE;
+ }
+ currentSpan = aEvent->scale;
+ previousSpan = mLastPinchEventSpan;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_END:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ currentSpan = aEvent->scale;
+ previousSpan = mLastPinchEventSpan;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ PinchGestureInput event(
+ pinchGestureType, PinchGestureInput::TRACKPAD,
+ GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
+ mCurrentTouchpadFocus,
+ 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
+ ? ScreenCoord(1.f)
+ : currentSpan),
+ 100.0 * ((aEvent->phase == GDK_TOUCHPAD_GESTURE_PHASE_END)
+ ? ScreenCoord(1.f)
+ : previousSpan),
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+
+ if (!event.SetLineOrPageDeltaY(this)) {
+ return FALSE;
+ }
+
+ mLastPinchEventSpan = aEvent->scale;
+ DispatchPinchGestureInput(event);
+ return TRUE;
+}
+
+gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
+ LOG("OnTouchEvent: x=%f y=%f type=%d\n", aEvent->x, aEvent->y, aEvent->type);
+ if (!mHandleTouchEvent) {
+ // If a popup window was spawned (e.g. as the result of a long-press)
+ // and touch events got diverted to that window within a touch sequence,
+ // ensure the touch event gets sent to the original window instead. We
+ // keep the checks here very conservative so that we only redirect
+ // events in this specific scenario.
+ nsWindow* targetWindow = GetTransientForWindowIfPopup();
+ if (targetWindow &&
+ targetWindow->IsHandlingTouchSequence(aEvent->sequence)) {
+ return targetWindow->OnTouchEvent(aEvent);
+ }
+
+ return FALSE;
+ }
+
+ EventMessage msg;
+ switch (aEvent->type) {
+ case GDK_TOUCH_BEGIN:
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) {
+ return FALSE;
+ }
+ msg = eTouchStart;
+ break;
+ case GDK_TOUCH_UPDATE:
+ msg = eTouchMove;
+ // Start dragging when motion events happens in the dragging area
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ if (mGdkWindow) {
+ GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(gdk_window,
+ "gdk_window_get_toplevel should not return null");
+
+ LOG(" start window dragging window\n");
+ gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root,
+ aEvent->y_root, aEvent->time);
+
+ // Cancel the event sequence. gdk will steal all subsequent events
+ // (including TOUCH_END).
+ msg = eTouchCancel;
+ }
+ }
+ break;
+ case GDK_TOUCH_END:
+ msg = eTouchEnd;
+ if (mWindowShouldStartDragging) {
+ LOG(" end of window dragging window\n");
+ mWindowShouldStartDragging = false;
+ }
+ break;
+ case GDK_TOUCH_CANCEL:
+ msg = eTouchCancel;
+ break;
+ default:
+ return FALSE;
+ }
+
+ const LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+
+ int32_t id;
+ RefPtr<dom::Touch> touch;
+ if (mTouches.Remove(aEvent->sequence, getter_AddRefs(touch))) {
+ id = touch->mIdentifier;
+ } else {
+ id = ++gLastTouchID & 0x7FFFFFFF;
+ }
+
+ touch =
+ new dom::Touch(id, touchPoint, LayoutDeviceIntPoint(1, 1), 0.0f, 0.0f);
+
+ WidgetTouchEvent event(true, msg, this);
+ KeymapWrapper::InitInputEvent(event, aEvent->state);
+
+ if (msg == eTouchStart || msg == eTouchMove) {
+ mTouches.InsertOrUpdate(aEvent->sequence, std::move(touch));
+ // add all touch points to event object
+ for (const auto& data : mTouches.Values()) {
+ event.mTouches.AppendElement(new dom::Touch(*data));
+ }
+ } else if (msg == eTouchEnd || msg == eTouchCancel) {
+ *event.mTouches.AppendElement() = std::move(touch);
+ }
+
+ nsIWidget::ContentAndAPZEventStatus eventStatus = DispatchInputEvent(&event);
+
+ // There's a chance that we are in drag area and the event is not consumed
+ // by something on it.
+ if (msg == eTouchStart && mDraggableRegion.Contains(touchPoint) &&
+ eventStatus.mApzStatus != nsEventStatus_eConsumeNoDefault) {
+ mWindowShouldStartDragging = true;
+ }
+ return TRUE;
+}
+
+// Return true if toplevel window is transparent.
+// It's transparent when we're running on composited screens
+// and we can draw main window without system titlebar.
+bool nsWindow::IsToplevelWindowTransparent() {
+ static bool transparencyConfigured = false;
+
+ if (!transparencyConfigured) {
+ if (gdk_screen_is_composited(gdk_screen_get_default())) {
+ // Some Gtk+ themes use non-rectangular toplevel windows. To fully
+ // support such themes we need to make toplevel window transparent
+ // with ARGB visual.
+ // It may cause performanance issue so make it configurable
+ // and enable it by default for selected window managers.
+ if (Preferences::HasUserValue("mozilla.widget.use-argb-visuals")) {
+ // argb visual is explicitly required so use it
+ sTransparentMainWindow =
+ Preferences::GetBool("mozilla.widget.use-argb-visuals");
+ } else {
+ // Enable transparent toplevel window if we can draw main window
+ // without system titlebar as Gtk+ themes use titlebar round corners.
+ sTransparentMainWindow =
+ GetSystemGtkWindowDecoration() != GTK_DECORATION_NONE;
+ }
+ }
+ transparencyConfigured = true;
+ }
+
+ return sTransparentMainWindow;
+}
+
+#ifdef MOZ_X11
+// Configure GL visual on X11.
+bool nsWindow::ConfigureX11GLVisual() {
+ auto* screen = gtk_widget_get_screen(mShell);
+ int visualId = 0;
+ bool haveVisual = false;
+
+ if (gfxVars::UseEGL()) {
+ haveVisual = GLContextEGL::FindVisual(&visualId);
+ }
+
+ // We are on GLX or use it as a fallback on Mesa, see
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
+ if (!haveVisual) {
+ auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
+ int screenNumber = GDK_SCREEN_XNUMBER(screen);
+ haveVisual = GLContextGLX::FindVisual(display, screenNumber, &visualId);
+ }
+
+ GdkVisual* gdkVisual = nullptr;
+ if (haveVisual) {
+ // If we're using CSD, rendering will go through mContainer, but
+ // it will inherit this visual as it is a child of mShell.
+ gdkVisual = gdk_x11_screen_lookup_visual(screen, visualId);
+ }
+ if (!gdkVisual) {
+ NS_WARNING("We're missing X11 Visual!");
+ // We try to use a fallback alpha visual
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ gdkVisual = gdk_screen_get_rgba_visual(screen);
+ }
+ if (gdkVisual) {
+ gtk_widget_set_visual(mShell, gdkVisual);
+ mHasAlphaVisual = true;
+ return true;
+ }
+
+ return false;
+}
+#endif
+
+nsAutoCString nsWindow::GetFrameTag() const {
+ if (nsIFrame* frame = GetFrame()) {
+#ifdef DEBUG_FRAME_DUMP
+ return frame->ListTag();
+#else
+ nsAutoCString buf;
+ buf.AppendPrintf("Frame(%p)", frame);
+ if (nsIContent* content = frame->GetContent()) {
+ buf.Append(' ');
+ AppendUTF16toUTF8(content->NodeName(), buf);
+ }
+ return buf;
+#endif
+ }
+ return nsAutoCString("(no frame)");
+}
+
+nsCString nsWindow::GetPopupTypeName() {
+ switch (mPopupType) {
+ case PopupType::Menu:
+ return nsCString("Menu");
+ case PopupType::Tooltip:
+ return nsCString("Tooltip");
+ case PopupType::Panel:
+ return nsCString("Panel/Utility");
+ default:
+ return nsCString("Unknown");
+ }
+}
+
+// Disables all rendering of GtkWidget from Gtk side.
+// We do our best to persuade Gtk/Gdk to ignore all painting
+// to the widget.
+static void GtkWidgetDisableUpdates(GtkWidget* aWidget) {
+ // Clear exposure mask - it disabled synthesized events.
+ GdkWindow* window = gtk_widget_get_window(aWidget);
+ if (!window) {
+ return;
+ }
+ gdk_window_set_events(window, (GdkEventMask)(gdk_window_get_events(window) &
+ (~GDK_EXPOSURE_MASK)));
+
+ // Remove before/after paint handles from frame clock.
+ // It disables widget content updates.
+ GdkFrameClock* frame_clock = gdk_window_get_frame_clock(window);
+ g_signal_handlers_disconnect_by_data(frame_clock, window);
+}
+
+Window nsWindow::GetX11Window() {
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ return mGdkWindow ? gdk_x11_window_get_xid(mGdkWindow) : X11None;
+ }
+#endif
+ return (Window) nullptr;
+}
+
+void nsWindow::EnsureGdkWindow() {
+ MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
+ if (!mGdkWindow) {
+ mGdkWindow = gtk_widget_get_window(GTK_WIDGET(mContainer));
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
+ if (mIMContext) {
+ mIMContext->SetGdkWindow(mGdkWindow);
+ }
+ }
+}
+
+bool nsWindow::GetShapedState() {
+ return mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
+}
+
+void nsWindow::ConfigureCompositor() {
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorState == COMPOSITOR_ENABLED);
+ MOZ_DIAGNOSTIC_ASSERT(mIsMapped);
+
+ LOG("nsWindow::ConfigureCompositor()");
+ auto startCompositing = [self = RefPtr{this}, this]() -> void {
+ LOG(" moz_container_wayland_add_or_fire_initial_draw_callback "
+ "ConfigureCompositor");
+
+ // too late
+ if (mIsDestroyed || !mIsMapped) {
+ LOG(" quit, mIsDestroyed = %d mIsMapped = %d", !!mIsDestroyed,
+ mIsMapped);
+ return;
+ }
+ // Compositor will be resumed later by ResumeCompositorFlickering().
+ if (mCompositorState == COMPOSITOR_PAUSED_FLICKERING) {
+ LOG(" quit, will be resumed by ResumeCompositorFlickering.");
+ return;
+ }
+ // Compositor will be resumed at nsWindow::SetCompositorWidgetDelegate().
+ if (!mCompositorWidgetDelegate) {
+ LOG(" quit, missing mCompositorWidgetDelegate");
+ return;
+ }
+
+ ResumeCompositorImpl();
+ };
+
+ if (GdkIsWaylandDisplay()) {
+#ifdef MOZ_WAYLAND
+ moz_container_wayland_add_or_fire_initial_draw_callback(mContainer,
+ startCompositing);
+#endif
+ } else {
+ startCompositing();
+ }
+}
+
+void nsWindow::ConfigureGdkWindow() {
+ LOG("nsWindow::ConfigureGdkWindow()");
+
+ EnsureGdkWindow();
+ OnScaleChanged(/* aNotify = */ false);
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
+ Visual* visual = gdk_x11_visual_get_xvisual(gdkVisual);
+ int depth = gdk_visual_get_depth(gdkVisual);
+ mSurfaceProvider.Initialize(GetX11Window(), visual, depth,
+ GetShapedState());
+
+ // Set window manager hint to keep fullscreen windows composited.
+ //
+ // If the window were to get unredirected, there could be visible
+ // tearing because Gecko does not align its framebuffer updates with
+ // vblank.
+ SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ mSurfaceProvider.Initialize(this);
+ }
+#endif
+
+ if (mIsDragPopup) {
+ if (GdkIsWaylandDisplay()) {
+ // Disable painting to the widget on Wayland as we paint directly to the
+ // widget. Wayland compositors does not paint wl_subsurface
+ // of D&D widget.
+ if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
+ GtkWidgetDisableUpdates(parent);
+ }
+ GtkWidgetDisableUpdates(mShell);
+ GtkWidgetDisableUpdates(GTK_WIDGET(mContainer));
+ } else {
+ // Disable rendering of parent container on X11 to avoid flickering.
+ if (GtkWidget* parent = gtk_widget_get_parent(mShell)) {
+ gtk_widget_set_opacity(parent, 0.0);
+ }
+ }
+ }
+
+ if (mWindowType == WindowType::Popup) {
+ if (mNoAutoHide) {
+ gint wmd = ConvertBorderStyles(mBorderStyle);
+ if (wmd != -1) {
+ gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration)wmd);
+ }
+ }
+ // If the popup ignores mouse events, set an empty input shape.
+ SetInputRegion(mInputRegion);
+ }
+
+ RefreshWindowClass();
+
+ // We're not mapped yet but we have already created compositor.
+ if (mCompositorWidgetDelegate) {
+ ConfigureCompositor();
+ }
+
+ LOG(" finished, new GdkWindow %p XID 0x%lx\n", mGdkWindow, GetX11Window());
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ widget::InitData* aInitData) {
+ LOG("nsWindow::Create\n");
+
+ // only set the base parent if we're going to be a dialog or a
+ // toplevel
+ nsIWidget* baseParent =
+ aInitData && (aInitData->mWindowType == WindowType::Dialog ||
+ aInitData->mWindowType == WindowType::TopLevel ||
+ aInitData->mWindowType == WindowType::Invisible)
+ ? nullptr
+ : aParent;
+
+#ifdef ACCESSIBILITY
+ // Send a DBus message to check whether a11y is enabled
+ a11y::PreInit();
+#endif
+
+#ifdef MOZ_WAYLAND
+ // Ensure that KeymapWrapper is created on Wayland as we need it for
+ // keyboard focus tracking.
+ if (GdkIsWaylandDisplay()) {
+ KeymapWrapper::EnsureInstance();
+ }
+#endif
+
+ // Ensure that the toolkit is created.
+ nsGTKToolkit::GetToolkit();
+
+ // initialize all the common bits of this class
+ BaseCreate(baseParent, aInitData);
+
+ // and do our common creation
+ mParent = aParent;
+ // save our bounds
+ mBounds = aRect;
+ LOG(" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
+ mBounds.height);
+
+ ConstrainSize(&mBounds.width, &mBounds.height);
+ mLastSizeRequest = mBounds.Size();
+
+ bool popupNeedsAlphaVisual = mWindowType == WindowType::Popup &&
+ (aInitData && aInitData->mTransparencyMode ==
+ TransparencyMode::Transparent);
+
+ // Figure out our parent window - only used for WindowType::Child
+ nsWindow* parentnsWindow = nullptr;
+
+ if (aParent) {
+ parentnsWindow = static_cast<nsWindow*>(aParent);
+ } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
+ parentnsWindow = get_window_for_gdk_window(GDK_WINDOW(aNativeParent));
+ if (!parentnsWindow) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ if (mWindowType == WindowType::Child) {
+ // We don't support WindowType::Child directly but emulate it by popup
+ // windows.
+ mWindowType = WindowType::Popup;
+ if (!parentnsWindow) {
+ if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
+ parentnsWindow = get_window_for_gtk_widget(GTK_WIDGET(aNativeParent));
+ }
+ }
+ mIsChildWindow = true;
+ LOG(" child widget, switch to popup. parent nsWindow %p", parentnsWindow);
+ }
+
+ MOZ_ASSERT_IF(mWindowType == WindowType::Popup, parentnsWindow);
+
+ if (mWindowType != WindowType::Dialog && mWindowType != WindowType::Popup &&
+ mWindowType != WindowType::TopLevel &&
+ mWindowType != WindowType::Invisible) {
+ MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType");
+ return NS_ERROR_FAILURE;
+ }
+
+ mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
+ // mNoAutoHide seems to be always false here.
+ // The mNoAutoHide state is set later on nsMenuPopupFrame level
+ // and can be changed so we use WaylandPopupIsPermanent() to get
+ // recent popup config (Bug 1728952).
+ mNoAutoHide = aInitData && aInitData->mNoAutoHide;
+
+ // Popups that are not noautohide are only temporary. The are used
+ // for menus and the like and disappear when another window is used.
+ // For most popups, use the standard GtkWindowType GTK_WINDOW_POPUP,
+ // which will use a Window with the override-redirect attribute
+ // (for temporary windows).
+ // For long-lived windows, their stacking order is managed by the
+ // window manager, as indicated by GTK_WINDOW_TOPLEVEL.
+ // For Wayland we have to always use GTK_WINDOW_POPUP to control
+ // popup window position.
+ GtkWindowType type = GTK_WINDOW_TOPLEVEL;
+ if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ type = GTK_WINDOW_POPUP;
+ if (GdkIsX11Display() && mNoAutoHide) {
+ type = GTK_WINDOW_TOPLEVEL;
+ }
+ }
+ mShell = gtk_window_new(type);
+
+ // Ensure gfxPlatform is initialized, since that is what initializes
+ // gfxVars, used below.
+ Unused << gfxPlatform::GetPlatform();
+
+ if (IsTopLevelWindowType()) {
+ mGtkWindowDecoration = GetSystemGtkWindowDecoration();
+ // Inherit initial scale from our parent, or use the default monitor scale
+ // otherwise.
+ mCeiledScaleFactor = parentnsWindow
+ ? int32_t(parentnsWindow->mCeiledScaleFactor)
+ : ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ }
+
+ // Don't use transparency for PictureInPicture windows.
+ bool toplevelNeedsAlphaVisual = false;
+ if (mWindowType == WindowType::TopLevel && !mIsPIPWindow) {
+ toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
+ }
+
+ bool isGLVisualSet = false;
+ mIsAccelerated = ComputeShouldAccelerate();
+#ifdef MOZ_X11
+ if (GdkIsX11Display() && mIsAccelerated) {
+ isGLVisualSet = ConfigureX11GLVisual();
+ }
+#endif
+ if (!isGLVisualSet && (popupNeedsAlphaVisual || toplevelNeedsAlphaVisual)) {
+ // We're running on composited screen so we can use alpha visual
+ // for both toplevel and popups.
+ if (mCompositedScreen) {
+ GdkVisual* visual =
+ gdk_screen_get_rgba_visual(gtk_widget_get_screen(mShell));
+ if (visual) {
+ gtk_widget_set_visual(mShell, visual);
+ mHasAlphaVisual = true;
+ }
+ }
+ }
+
+ // Use X shape mask to draw round corners of Firefox titlebar.
+ // We don't use shape masks any more as we switched to ARGB visual
+ // by default and non-compositing screens use solid-csd decorations
+ // without round corners.
+ // Leave the shape mask code here as it can be used to draw round
+ // corners on EGL (https://gitlab.freedesktop.org/mesa/mesa/-/issues/149)
+ // or when custom titlebar theme is used.
+ mTransparencyBitmapForTitlebar = TitlebarUseShapeMask();
+
+ // We have a toplevel window with transparency.
+ // Calls to UpdateTitlebarTransparencyBitmap() from OnExposeEvent()
+ // occur before SetTransparencyMode() receives TransparencyMode::Transparent
+ // from layout, so set mIsTransparent here.
+ if (mWindowType == WindowType::TopLevel &&
+ (mHasAlphaVisual || mTransparencyBitmapForTitlebar)) {
+ mIsTransparent = true;
+ }
+
+ // We only move a general managed toplevel window if someone has
+ // actually placed the window somewhere. If no placement has taken
+ // place, we just let the window manager Do The Right Thing.
+ if (AreBoundsSane()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ LOG("nsWindow::Create() Initial resize to %d x %d\n", size.width,
+ size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+ if (mIsPIPWindow) {
+ LOG(" Is PIP window\n");
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_UTILITY);
+ } else if (aInitData && aInitData->mIsAlert) {
+ LOG(" Is alert window\n");
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_NOTIFICATION);
+ } else if (mWindowType == WindowType::Dialog) {
+ mGtkWindowRoleName = "Dialog";
+
+ SetDefaultIcon();
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DIALOG);
+ LOG("nsWindow::Create(): dialog");
+ if (parentnsWindow) {
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell),
+ GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ LOG(" set parent window [%p]\n", parentnsWindow);
+ }
+ } else if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ mGtkWindowRoleName = "Popup";
+
+ LOG("nsWindow::Create() Popup");
+
+ if (mNoAutoHide) {
+ // ... but the window manager does not decorate this window,
+ // nor provide a separate taskbar icon.
+ if (mBorderStyle == BorderStyle::Default) {
+ gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
+ } else {
+ bool decorate = bool(mBorderStyle & BorderStyle::Title);
+ gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
+ if (decorate) {
+ gtk_window_set_deletable(GTK_WINDOW(mShell),
+ bool(mBorderStyle & BorderStyle::Close));
+ }
+ }
+ gtk_window_set_skip_taskbar_hint(GTK_WINDOW(mShell), TRUE);
+ // Element focus is managed by the parent window so the
+ // WM_HINTS input field is set to False to tell the window
+ // manager not to set input focus to this window ...
+ gtk_window_set_accept_focus(GTK_WINDOW(mShell), FALSE);
+#ifdef MOZ_X11
+ // ... but when the window manager offers focus through
+ // WM_TAKE_FOCUS, focus is requested on the parent window.
+ if (GdkIsX11Display()) {
+ gtk_widget_realize(mShell);
+ gdk_window_add_filter(GetToplevelGdkWindow(), popup_take_focus_filter,
+ nullptr);
+ }
+#endif
+ }
+
+ if (aInitData->mIsDragPopup) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_DND);
+ mIsDragPopup = true;
+ LOG("nsWindow::Create() Drag popup\n");
+ } else if (GdkIsX11Display()) {
+ // Set the window hints on X11 only. Wayland popups are configured
+ // at WaylandPopupConfigure().
+ GdkWindowTypeHint gtkTypeHint;
+ switch (mPopupType) {
+ case PopupType::Menu:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ break;
+ case PopupType::Tooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ break;
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+ LOG("nsWindow::Create() popup type %s", GetPopupTypeName().get());
+ }
+ if (parentnsWindow) {
+ LOG(" set parent window [%p] %s", parentnsWindow,
+ parentnsWindow->mGtkWindowRoleName.get());
+ GtkWindow* parentWidget = GTK_WINDOW(parentnsWindow->GetGtkWidget());
+ GtkWindowSetTransientFor(GTK_WINDOW(mShell), parentWidget);
+
+ // If popup parent is modal, we need to make popup modal too.
+ if (mPopupType != PopupType::Tooltip &&
+ gtk_window_get_modal(parentWidget)) {
+ gtk_window_set_modal(GTK_WINDOW(mShell), true);
+ }
+ }
+
+ // We need realized mShell at NativeMoveResize().
+ gtk_widget_realize(mShell);
+
+ // With popup windows, we want to set their position.
+ // Place them immediately on X11 and save initial popup position
+ // on Wayland as we place Wayland popup on show.
+ if (GdkIsX11Display()) {
+ NativeMoveResize(/* move */ true, /* resize */ false);
+ } else if (AreBoundsSane()) {
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(mBounds);
+ mPopupPosition = {rect.x, rect.y};
+ }
+ } else { // must be WindowType::TopLevel
+ mGtkWindowRoleName = "Toplevel";
+ SetDefaultIcon();
+
+ LOG("nsWindow::Create() Toplevel\n");
+
+ // each toplevel window gets its own window group
+ GtkWindowGroup* group = gtk_window_group_new();
+ gtk_window_group_add_window(group, GTK_WINDOW(mShell));
+ g_object_unref(group);
+ }
+
+ if (mAlwaysOnTop) {
+ gtk_window_set_keep_above(GTK_WINDOW(mShell), TRUE);
+ }
+
+ // It is important that this happens before the realize() call below, so that
+ // we don't get bogus CSD margins on Wayland, see bug 1794577.
+ mUndecorated = IsAlwaysUndecoratedWindow();
+ if (mUndecorated) {
+ LOG(" Is undecorated Window\n");
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), gtk_fixed_new());
+ gtk_window_set_decorated(GTK_WINDOW(mShell), false);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+
+ // Prevent GtkWindow from painting a background to avoid flickering.
+ gtk_widget_set_app_paintable(
+ GTK_WIDGET(mContainer),
+ StaticPrefs::widget_transparent_windows_AtStartup());
+
+ gtk_widget_add_events(GTK_WIDGET(mContainer), kEvents);
+ gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
+ gtk_widget_set_app_paintable(
+ mShell, StaticPrefs::widget_transparent_windows_AtStartup());
+
+ if (mTransparencyBitmapForTitlebar) {
+ moz_container_force_default_visual(mContainer);
+ }
+
+ // If we draw to mContainer window then configure it now because
+ // gtk_container_add() realizes the child widget.
+ gtk_widget_set_has_window(container, true);
+ gtk_container_add(GTK_CONTAINER(mShell), container);
+
+ // alwaysontop windows are generally used for peripheral indicators,
+ // so we don't focus them by default.
+ if (mAlwaysOnTop) {
+ gtk_window_set_focus_on_map(GTK_WINDOW(mShell), FALSE);
+ }
+
+ gtk_widget_realize(container);
+
+ // make sure this is the focus widget in the container
+ gtk_widget_show(container);
+
+ if (!mAlwaysOnTop) {
+ gtk_widget_grab_focus(container);
+ }
+
+#ifdef MOZ_WAYLAND
+ if (mIsDragPopup && GdkIsWaylandDisplay()) {
+ LOG(" set commit to parent");
+ moz_container_wayland_set_commit_to_parent(mContainer);
+ }
+#endif
+
+ if (mWindowType == WindowType::TopLevel && gKioskMode) {
+ if (gKioskMonitor != -1) {
+ mKioskMonitor = Some(gKioskMonitor);
+ LOG(" set kiosk mode monitor %d", mKioskMonitor.value());
+ } else {
+ LOG(" set kiosk mode");
+ }
+ // Kiosk mode always use fullscreen.
+ MakeFullScreen(/* aFullScreen */ true);
+ }
+
+ if (mWindowType == WindowType::Popup) {
+ MOZ_ASSERT(aInitData);
+ // gdk does not automatically set the cursor for "temporary"
+ // windows, which are what gtk uses for popups.
+
+ // force SetCursor to actually set the cursor, even though our internal
+ // state indicates that we already have the standard cursor.
+ mUpdateCursor = true;
+ SetCursor(Cursor{eCursor_standard});
+ }
+
+ if (mIsChildWindow && parentnsWindow) {
+ GdkWindow* window = GetToplevelGdkWindow();
+ GdkWindow* parentWindow = parentnsWindow->GetToplevelGdkWindow();
+ LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
+ gdk_window_reparent(window, parentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+
+ // Also label mShell toplevel window,
+ // property_notify_event_cb callback also needs to find its way home
+ g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
+ g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
+ g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
+
+ // attach listeners for events
+ g_signal_connect(mShell, "configure_event", G_CALLBACK(configure_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "delete_event", G_CALLBACK(delete_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "window_state_event",
+ G_CALLBACK(window_state_event_cb), nullptr);
+ g_signal_connect(mShell, "visibility-notify-event",
+ G_CALLBACK(visibility_notify_event_cb), nullptr);
+ g_signal_connect(mShell, "check-resize", G_CALLBACK(check_resize_cb),
+ nullptr);
+ g_signal_connect(mShell, "composited-changed",
+ G_CALLBACK(widget_composited_changed_cb), nullptr);
+ g_signal_connect(mShell, "property-notify-event",
+ G_CALLBACK(property_notify_event_cb), nullptr);
+
+ if (mWindowType == WindowType::TopLevel) {
+ g_signal_connect_after(mShell, "size_allocate",
+ G_CALLBACK(toplevel_window_size_allocate_cb),
+ nullptr);
+ }
+
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ if (!g_signal_handler_find(screen, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(screen_composited_changed_cb),
+ nullptr)) {
+ g_signal_connect(screen, "composited-changed",
+ G_CALLBACK(screen_composited_changed_cb), nullptr);
+ }
+
+ gtk_drag_dest_set((GtkWidget*)mShell, (GtkDestDefaults)0, nullptr, 0,
+ (GdkDragAction)0);
+ g_signal_connect(mShell, "drag_motion", G_CALLBACK(drag_motion_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_leave", G_CALLBACK(drag_leave_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_drop", G_CALLBACK(drag_drop_event_cb),
+ nullptr);
+ g_signal_connect(mShell, "drag_data_received",
+ G_CALLBACK(drag_data_received_event_cb), nullptr);
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
+ G_CALLBACK(settings_xft_dpi_changed_cb), this);
+
+ // Widget signals
+ g_signal_connect(mContainer, "map", G_CALLBACK(widget_map_cb), nullptr);
+ g_signal_connect(mContainer, "unmap", G_CALLBACK(widget_unmap_cb), nullptr);
+ g_signal_connect_after(mContainer, "size_allocate",
+ G_CALLBACK(size_allocate_cb), nullptr);
+ g_signal_connect(mContainer, "hierarchy-changed",
+ G_CALLBACK(hierarchy_changed_cb), nullptr);
+ g_signal_connect(mContainer, "notify::scale-factor",
+ G_CALLBACK(scale_changed_cb), nullptr);
+ // Initialize mHasMappedToplevel.
+ hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr);
+ // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW
+ // widgets.
+ g_signal_connect(G_OBJECT(mContainer), "draw", G_CALLBACK(expose_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "focus_in_event", G_CALLBACK(focus_in_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "focus_out_event",
+ G_CALLBACK(focus_out_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_press_event",
+ G_CALLBACK(key_press_event_cb), nullptr);
+ g_signal_connect(mContainer, "key_release_event",
+ G_CALLBACK(key_release_event_cb), nullptr);
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ gtk_widget_set_double_buffered(GTK_WIDGET(mContainer), FALSE);
+ }
+#endif
+#ifdef MOZ_WAYLAND
+ // Initialize the window specific VsyncSource early in order to avoid races
+ // with BrowserParent::UpdateVsyncParentVsyncDispatcher().
+ // Only use for toplevel windows for now, see bug 1619246.
+ if (GdkIsWaylandDisplay() &&
+ StaticPrefs::widget_wayland_vsync_enabled_AtStartup() &&
+ IsTopLevelWindowType()) {
+ mWaylandVsyncSource = new WaylandVsyncSource(this);
+ mWaylandVsyncDispatcher = new VsyncDispatcher(mWaylandVsyncSource);
+ LOG_VSYNC(" created WaylandVsyncSource");
+ }
+#endif
+
+ // We create input contexts for all containers, except for
+ // toplevel popup windows
+ if (mWindowType != WindowType::Popup) {
+ mIMContext = new IMContextWrapper(this);
+ }
+
+ // These events are sent to the owning widget of the relevant window
+ // and propagate up to the first widget that handles the events, so we
+ // need only connect on mShell, if it exists, to catch events on its
+ // window and windows of mContainer.
+ g_signal_connect(mContainer, "enter-notify-event",
+ G_CALLBACK(enter_notify_event_cb), nullptr);
+ g_signal_connect(mContainer, "leave-notify-event",
+ G_CALLBACK(leave_notify_event_cb), nullptr);
+ g_signal_connect(mContainer, "motion-notify-event",
+ G_CALLBACK(motion_notify_event_cb), nullptr);
+ g_signal_connect(mContainer, "button-press-event",
+ G_CALLBACK(button_press_event_cb), nullptr);
+ g_signal_connect(mContainer, "button-release-event",
+ G_CALLBACK(button_release_event_cb), nullptr);
+ g_signal_connect(mContainer, "scroll-event", G_CALLBACK(scroll_event_cb),
+ nullptr);
+ if (gtk_check_version(3, 18, 0) == nullptr) {
+ g_signal_connect(mContainer, "event", G_CALLBACK(generic_event_cb),
+ nullptr);
+ }
+ g_signal_connect(mContainer, "touch-event", G_CALLBACK(touch_event_cb),
+ nullptr);
+
+ LOG(" nsWindow type %d %s\n", int(mWindowType),
+ mIsPIPWindow ? "PIP window" : "");
+ LOG(" mShell %p (window %p) mContainer %p mGdkWindow %p XID 0x%lx\n", mShell,
+ GetToplevelGdkWindow(), mContainer, mGdkWindow, GetX11Window());
+
+ // Set default application name when it's empty.
+ if (mGtkWindowAppName.IsEmpty()) {
+ mGtkWindowAppName = gAppData->name;
+ }
+
+ mCreated = true;
+ return NS_OK;
+}
+
+void nsWindow::RefreshWindowClass(void) {
+ GdkWindow* gdkWindow = GetToplevelGdkWindow();
+ if (!gdkWindow) {
+ return;
+ }
+
+ if (!mGtkWindowRoleName.IsEmpty()) {
+ gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
+ }
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ XClassHint* class_hint = XAllocClassHint();
+ if (!class_hint) {
+ return;
+ }
+
+ const char* res_name =
+ !mGtkWindowAppName.IsEmpty() ? mGtkWindowAppName.get() : gAppData->name;
+
+ const char* res_class = !mGtkWindowAppClass.IsEmpty()
+ ? mGtkWindowAppClass.get()
+ : gdk_get_program_class();
+
+ if (!res_name || !res_class) {
+ XFree(class_hint);
+ return;
+ }
+
+ class_hint->res_name = const_cast<char*>(res_name);
+ class_hint->res_class = const_cast<char*>(res_class);
+
+ // Can't use gtk_window_set_wmclass() for this; it prints
+ // a warning & refuses to make the change.
+ GdkDisplay* display = gdk_display_get_default();
+ XSetClassHint(GDK_DISPLAY_XDISPLAY(display),
+ gdk_x11_window_get_xid(gdkWindow), class_hint);
+ XFree(class_hint);
+ }
+#endif /* MOZ_X11 */
+
+#ifdef MOZ_WAYLAND
+ static auto sGdkWaylandWindowSetApplicationId =
+ (void (*)(GdkWindow*, const char*))dlsym(
+ RTLD_DEFAULT, "gdk_wayland_window_set_application_id");
+
+ if (GdkIsWaylandDisplay() && sGdkWaylandWindowSetApplicationId &&
+ !mGtkWindowAppClass.IsEmpty()) {
+ sGdkWaylandWindowSetApplicationId(gdkWindow, mGtkWindowAppClass.get());
+ }
+#endif /* MOZ_WAYLAND */
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType,
+ const nsAString& xulWinClass,
+ const nsAString& xulWinName) {
+ if (!mShell) {
+ return;
+ }
+
+ // If window type attribute is set, parse it into name and role
+ if (!xulWinType.IsEmpty()) {
+ char* res_name = ToNewCString(xulWinType, mozilla::fallible);
+ const char* role = nullptr;
+
+ if (res_name) {
+ // Parse res_name into a name and role. Characters other than
+ // [A-Za-z0-9_-] are converted to '_'. Anything after the first
+ // colon is assigned to role; if there's no colon, assign the
+ // whole thing to both role and res_name.
+ for (char* c = res_name; *c; c++) {
+ if (':' == *c) {
+ *c = 0;
+ role = c + 1;
+ } else if (!isascii(*c) ||
+ (!isalnum(*c) && ('_' != *c) && ('-' != *c))) {
+ *c = '_';
+ }
+ }
+ res_name[0] = (char)toupper(res_name[0]);
+ if (!role) role = res_name;
+
+ mGtkWindowAppName = res_name;
+ mGtkWindowRoleName = role;
+ free(res_name);
+ }
+ }
+
+ // If window class attribute is set, store it as app class
+ // If this attribute is not set, reset app class to default
+ if (!xulWinClass.IsEmpty()) {
+ CopyUTF16toUTF8(xulWinClass, mGtkWindowAppClass);
+ } else {
+ mGtkWindowAppClass = nullptr;
+ }
+
+ // If window class attribute is set, store it as app name
+ // If both name and type are not set, reset app name to default
+ if (!xulWinName.IsEmpty()) {
+ CopyUTF16toUTF8(xulWinName, mGtkWindowAppName);
+ } else if (xulWinType.IsEmpty()) {
+ mGtkWindowAppClass = nullptr;
+ }
+
+ RefreshWindowClass();
+}
+
+nsAutoCString nsWindow::GetDebugTag() const {
+ nsAutoCString tag;
+ tag.AppendPrintf("[%p]", this);
+ return tag;
+}
+
+void nsWindow::NativeMoveResize(bool aMoved, bool aResized) {
+ GdkPoint topLeft = [&] {
+ auto target = mBounds.TopLeft();
+ // gtk_window_move will undo the csd offset, but nothing else, so only add
+ // the client offset if drawing to the csd titlebar.
+ if (DrawsToCSDTitlebar()) {
+ target += mClientOffset;
+ }
+ return DevicePixelsToGdkPointRoundDown(target);
+ }();
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mLastSizeRequest);
+
+ LOG("nsWindow::NativeMoveResize move %d resize %d to %d,%d -> %d x %d\n",
+ aMoved, aResized, topLeft.x, topLeft.y, size.width, size.height);
+
+ if (aResized && !AreBoundsSane()) {
+ LOG(" bounds are insane, hidding the window");
+ // We have been resized but to incorrect size.
+ // If someone has set this so that the needs show flag is false
+ // and it needs to be hidden, update the flag and hide the
+ // window. This flag will be cleared the next time someone
+ // hides the window or shows it. It also prevents us from
+ // calling NativeShow(false) excessively on the window which
+ // causes unneeded X traffic.
+ if (!mNeedsShow && mIsShown) {
+ mNeedsShow = true;
+ NativeShow(false);
+ }
+ if (aMoved) {
+ LOG(" moving to %d x %d", topLeft.x, topLeft.y);
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ }
+ return;
+ }
+
+ // Set position to hidden window on X11 may fail, so save the position
+ // and move it when it's shown.
+ if (aMoved && GdkIsX11Display() && IsPopup() &&
+ !gtk_widget_get_visible(GTK_WIDGET(mShell))) {
+ LOG(" store position of hidden popup window");
+ mHiddenPopupPositioned = true;
+ mPopupPosition = {topLeft.x, topLeft.y};
+ }
+
+ if (IsWaylandPopup()) {
+ NativeMoveResizeWaylandPopup(aMoved, aResized);
+ } else {
+ // x and y give the position of the window manager frame top-left.
+ if (aMoved) {
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ }
+ if (aResized) {
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ if (mIsDragPopup) {
+ // DND window is placed inside container so we need to make hard size
+ // request to ensure parent container is resized too.
+ gtk_widget_set_size_request(GTK_WIDGET(mShell), size.width,
+ size.height);
+ }
+ }
+ }
+
+ if (aResized) {
+ // Recompute the input region, in case the window grew or shrunk.
+ SetInputRegion(mInputRegion);
+ }
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown && aResized) {
+ NativeShow(true);
+ }
+}
+
+// We pause compositor to avoid rendering of obsoleted remote content which
+// produces flickering.
+// Re-enable compositor again when remote content is updated or
+// timeout happens.
+
+// Define maximal compositor pause when it's paused to avoid flickering,
+// in milliseconds.
+#define COMPOSITOR_PAUSE_TIMEOUT (1000)
+
+void nsWindow::PauseCompositorFlickering() {
+ bool pauseCompositor = IsTopLevelWindowType() &&
+ mCompositorState == COMPOSITOR_ENABLED &&
+ mCompositorWidgetDelegate && !mIsDestroyed;
+ if (!pauseCompositor) {
+ return;
+ }
+
+ LOG("nsWindow::PauseCompositorFlickering()");
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ if (remoteRenderer) {
+ remoteRenderer->SendPause();
+ mCompositorState = COMPOSITOR_PAUSED_FLICKERING;
+ mCompositorPauseTimeoutID = (int)g_timeout_add(
+ COMPOSITOR_PAUSE_TIMEOUT,
+ [](void* data) -> gint {
+ nsWindow* window = static_cast<nsWindow*>(data);
+ if (!window->IsDestroyed()) {
+ window->ResumeCompositorFlickering();
+ }
+ return true;
+ },
+ this);
+ }
+}
+
+bool nsWindow::IsWaitingForCompositorResume() {
+ return mCompositorState == COMPOSITOR_PAUSED_FLICKERING;
+}
+
+void nsWindow::ResumeCompositorFlickering() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ LOG("nsWindow::ResumeCompositorFlickering()\n");
+
+ if (mIsDestroyed || !IsWaitingForCompositorResume()) {
+ LOG(" early quit\n");
+ return;
+ }
+
+ MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
+
+ // mCompositorWidgetDelegate can be deleted during timeout.
+ // In such case just flip compositor back to enabled and let
+ // SetCompositorWidgetDelegate() or Map event resume it.
+ if (!mCompositorWidgetDelegate) {
+ mCompositorState = COMPOSITOR_ENABLED;
+ return;
+ }
+
+ ResumeCompositorImpl();
+}
+
+void nsWindow::ResumeCompositorFromCompositorThread() {
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsWindow::ResumeCompositorFlickering", this,
+ &nsWindow::ResumeCompositorFlickering);
+ NS_DispatchToMainThread(event.forget());
+}
+
+void nsWindow::ResumeCompositorImpl() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ LOG("nsWindow::ResumeCompositorImpl()\n");
+
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
+
+ // DisableRendering() clears stored X11Window so we're sure EnableRendering()
+ // really updates it.
+ mCompositorWidgetDelegate->DisableRendering();
+ mCompositorWidgetDelegate->EnableRendering(GetX11Window(), GetShapedState());
+
+ // As WaylandStartVsync needs mCompositorWidgetDelegate this is the right
+ // time to start it.
+ WaylandStartVsync();
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ MOZ_RELEASE_ASSERT(remoteRenderer);
+ remoteRenderer->SendResume();
+ remoteRenderer->SendForcePresent(wr::RenderReasons::WIDGET);
+ mCompositorState = COMPOSITOR_ENABLED;
+}
+
+void nsWindow::WaylandStartVsync() {
+#ifdef MOZ_WAYLAND
+ if (!mWaylandVsyncSource) {
+ return;
+ }
+
+ LOG_VSYNC("nsWindow::WaylandStartVsync");
+
+ MOZ_DIAGNOSTIC_ASSERT(mCompositorWidgetDelegate);
+ if (mCompositorWidgetDelegate->AsGtkCompositorWidget() &&
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()
+ ->GetNativeLayerRoot()) {
+ LOG_VSYNC(" use source NativeLayerRootWayland");
+ mWaylandVsyncSource->MaybeUpdateSource(
+ mCompositorWidgetDelegate->AsGtkCompositorWidget()
+ ->GetNativeLayerRoot()
+ ->AsNativeLayerRootWayland());
+ } else {
+ LOG_VSYNC(" use source mContainer");
+ mWaylandVsyncSource->MaybeUpdateSource(mContainer);
+ }
+
+ mWaylandVsyncSource->EnableMonitor();
+#endif
+}
+
+void nsWindow::WaylandStopVsync() {
+#ifdef MOZ_WAYLAND
+ if (!mWaylandVsyncSource) {
+ return;
+ }
+
+ LOG_VSYNC("nsWindow::WaylandStopVsync");
+
+ // The widget is going to be hidden, so clear the surface of our
+ // vsync source.
+ mWaylandVsyncSource->DisableMonitor();
+ mWaylandVsyncSource->MaybeUpdateSource(nullptr);
+#endif
+}
+
+void nsWindow::NativeShow(bool aAction) {
+ if (aAction) {
+ // unset our flag now that our window has been shown
+ mNeedsShow = true;
+ auto removeShow = MakeScopeExit([&] { mNeedsShow = false; });
+
+ LOG("nsWindow::NativeShow show\n");
+
+ if (IsWaylandPopup()) {
+ mPopupClosed = false;
+ if (WaylandPopupConfigure()) {
+ AddWindowToPopupHierarchy();
+ UpdateWaylandPopupHierarchy();
+ if (mPopupClosed) {
+ return;
+ }
+ }
+ }
+ // Set up usertime/startupID metadata for the created window.
+ // On X11 we use gtk_window_set_startup_id() so we need to call it
+ // before show.
+ if (GdkIsX11Display()) {
+ SetUserTimeAndStartupTokenForActivatedWindow();
+ }
+ if (GdkIsWaylandDisplay()) {
+ if (IsWaylandPopup()) {
+ ShowWaylandPopupWindow();
+ } else {
+ ShowWaylandToplevelWindow();
+ }
+ } else {
+ LOG(" calling gtk_widget_show(mShell)\n");
+ gtk_widget_show(mShell);
+ }
+ if (GdkIsWaylandDisplay()) {
+ SetUserTimeAndStartupTokenForActivatedWindow();
+#ifdef MOZ_WAYLAND
+ auto token = std::move(mWindowActivationTokenFromEnv);
+ if (!token.IsEmpty()) {
+ FocusWaylandWindow(token.get());
+ }
+#endif
+ }
+ if (mHiddenPopupPositioned && IsPopup()) {
+ LOG(" re-position hidden popup window");
+ gtk_window_move(GTK_WINDOW(mShell), mPopupPosition.x, mPopupPosition.y);
+ mHiddenPopupPositioned = false;
+ }
+ } else {
+ LOG("nsWindow::NativeShow hide\n");
+ if (GdkIsWaylandDisplay()) {
+ if (IsWaylandPopup()) {
+ // We can't close tracked popups directly as they may have visible
+ // child popups. Just mark is as closed and let
+ // UpdateWaylandPopupHierarchy() do the job.
+ if (IsInPopupHierarchy()) {
+ WaylandPopupMarkAsClosed();
+ UpdateWaylandPopupHierarchy();
+ } else {
+ // Close untracked popups directly.
+ HideWaylandPopupWindow(/* aTemporaryHide */ false,
+ /* aRemoveFromPopupList */ true);
+ }
+ } else {
+ HideWaylandToplevelWindow();
+ }
+ } else {
+ // Workaround window freezes on GTK versions before 3.21.2 by
+ // ensuring that configure events get dispatched to windows before
+ // they are unmapped. See bug 1225044.
+ if (gtk_check_version(3, 21, 2) != nullptr && mPendingConfigures > 0) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mShell), &allocation);
+
+ GdkEventConfigure event;
+ PodZero(&event);
+ event.type = GDK_CONFIGURE;
+ event.window = mGdkWindow;
+ event.send_event = TRUE;
+ event.x = allocation.x;
+ event.y = allocation.y;
+ event.width = allocation.width;
+ event.height = allocation.height;
+
+ auto* shellClass = GTK_WIDGET_GET_CLASS(mShell);
+ for (unsigned int i = 0; i < mPendingConfigures; i++) {
+ Unused << shellClass->configure_event(mShell, &event);
+ }
+ mPendingConfigures = 0;
+ }
+ gtk_widget_hide(mShell);
+
+ ClearTransparencyBitmap(); // Release some resources
+ }
+ }
+}
+
+void nsWindow::SetHasMappedToplevel(bool aState) {
+ LOG("nsWindow::SetHasMappedToplevel(%d)", aState);
+ if (aState == mHasMappedToplevel) {
+ return;
+ }
+ // Even when aState == mHasMappedToplevel (as when this method is called
+ // from Show()), child windows need to have their state checked, so don't
+ // return early.
+ mHasMappedToplevel = aState;
+ if (aState && mNeedsToRetryCapturingMouse) {
+ CaptureRollupEvents(true);
+ MOZ_ASSERT(!mNeedsToRetryCapturingMouse);
+ }
+}
+
+LayoutDeviceIntSize nsWindow::GetSafeWindowSize(LayoutDeviceIntSize aSize) {
+ // The X protocol uses CARD32 for window sizes, but the server (1.11.3)
+ // reads it as CARD16. Sizes of pixmaps, used for drawing, are (unsigned)
+ // CARD16 in the protocol, but the server's ProcCreatePixmap returns
+ // BadAlloc if dimensions cannot be represented by signed shorts.
+ // Because we are creating Cairo surfaces to represent window buffers,
+ // we also must ensure that the window can fit in a Cairo surface.
+ LayoutDeviceIntSize result = aSize;
+ int32_t maxSize = 32767;
+ if (mWindowRenderer && mWindowRenderer->AsKnowsCompositor()) {
+ maxSize = std::min(
+ maxSize, mWindowRenderer->AsKnowsCompositor()->GetMaxTextureSize());
+ }
+ if (result.width > maxSize) {
+ result.width = maxSize;
+ }
+ if (result.height > maxSize) {
+ result.height = maxSize;
+ }
+ return result;
+}
+
+void nsWindow::SetTransparencyMode(TransparencyMode aMode) {
+ bool isTransparent = aMode == TransparencyMode::Transparent;
+
+ if (mIsTransparent == isTransparent) {
+ return;
+ }
+
+ if (mWindowType != WindowType::Popup) {
+ // https://bugzilla.mozilla.org/show_bug.cgi?id=1344839 reported
+ // problems cleaning the layer manager for toplevel windows.
+ // Ignore the request so as to workaround that.
+ // mIsTransparent is set in Create() if transparency may be required.
+ if (isTransparent) {
+ NS_WARNING("Transparent mode not supported on non-popup windows.");
+ }
+ return;
+ }
+
+ if (!isTransparent) {
+ ClearTransparencyBitmap();
+ } // else the new default alpha values are "all 1", so we don't
+ // need to change anything yet
+
+ mIsTransparent = isTransparent;
+
+ if (!mHasAlphaVisual) {
+ // The choice of layer manager depends on
+ // GtkCompositorWidgetInitData::Shaped(), which will need to change, so
+ // clean out the old layer manager.
+ DestroyLayerManager();
+ }
+}
+
+TransparencyMode nsWindow::GetTransparencyMode() {
+ return mIsTransparent ? TransparencyMode::Transparent
+ : TransparencyMode::Opaque;
+}
+
+gint nsWindow::GetInputRegionMarginInGdkCoords() {
+ return DevicePixelsToGdkCoordRoundDown(mInputRegion.mMargin);
+}
+
+void nsWindow::SetInputRegion(const InputRegion& aInputRegion) {
+ mInputRegion = aInputRegion;
+
+ GdkWindow* window = GetToplevelGdkWindow();
+ if (!window) {
+ return;
+ }
+
+ LOG("nsWindow::SetInputRegion(%d, %d)", aInputRegion.mFullyTransparent,
+ int(aInputRegion.mMargin));
+
+ cairo_rectangle_int_t rect = {0, 0, 0, 0};
+ cairo_region_t* region = nullptr;
+ auto releaseRegion = MakeScopeExit([&] {
+ if (region) {
+ cairo_region_destroy(region);
+ }
+ });
+
+ if (aInputRegion.mFullyTransparent) {
+ region = cairo_region_create_rectangle(&rect);
+ } else if (aInputRegion.mMargin != 0) {
+ LayoutDeviceIntRect inputRegion(LayoutDeviceIntPoint(), mLastSizeRequest);
+ inputRegion.Deflate(aInputRegion.mMargin);
+ GdkRectangle gdkRect = DevicePixelsToGdkRectRoundOut(inputRegion);
+ rect = {gdkRect.x, gdkRect.y, gdkRect.width, gdkRect.height};
+ region = cairo_region_create_rectangle(&rect);
+ }
+
+ gdk_window_input_shape_combine_region(window, region, 0, 0);
+
+ // On Wayland gdk_window_input_shape_combine_region() call is cached and
+ // applied to underlying wl_surface when GdkWindow is repainted.
+ // Force repaint of GdkWindow to apply the change immediately.
+ if (GdkIsWaylandDisplay()) {
+ gdk_window_invalidate_rect(window, nullptr, false);
+ }
+}
+
+// For setting the draggable titlebar region from CSS
+// with -moz-window-dragging: drag.
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+LayoutDeviceIntCoord nsWindow::GetTitlebarRadius() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ int32_t cssCoord = LookAndFeel::GetInt(LookAndFeel::IntID::TitlebarRadius);
+ return GdkCoordToDevicePixels(cssCoord);
+}
+
+// See subtract_corners_from_region() at gtk/gtkwindow.c
+// We need to subtract corners from toplevel window opaque region
+// to draw transparent corners of default Gtk titlebar.
+// Both implementations (cairo_region_t and wl_region) needs to be synced.
+static void SubtractTitlebarCorners(cairo_region_t* aRegion, int aX, int aY,
+ int aWindowWidth, int aWindowHeight,
+ int aTitlebarRadius) {
+ if (!aTitlebarRadius) {
+ return;
+ }
+ cairo_rectangle_int_t rect = {aX, aY, aTitlebarRadius, aTitlebarRadius};
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX + aWindowWidth - aTitlebarRadius,
+ aY,
+ aTitlebarRadius,
+ aTitlebarRadius,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX,
+ aY + aWindowHeight - aTitlebarRadius,
+ aTitlebarRadius,
+ aTitlebarRadius,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX + aWindowWidth - aTitlebarRadius,
+ aY + aWindowHeight - aTitlebarRadius,
+ aTitlebarRadius,
+ aTitlebarRadius,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+}
+
+void nsWindow::UpdateTopLevelOpaqueRegion() {
+ if (!mCompositedScreen) {
+ return;
+ }
+
+ GdkWindow* window = GetToplevelGdkWindow();
+ if (!window) {
+ return;
+ }
+ MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
+
+ int x = 0;
+ int y = 0;
+
+ gdk_window_get_position(mGdkWindow, &x, &y);
+
+ int width = DevicePixelsToGdkCoordRoundDown(mBounds.width);
+ int height = DevicePixelsToGdkCoordRoundDown(mBounds.height);
+
+ cairo_region_t* region = cairo_region_create();
+ cairo_rectangle_int_t rect = {x, y, width, height};
+ cairo_region_union_rectangle(region, &rect);
+
+ // TODO: We actually could get a proper opaque region from layout, see
+ // nsIWidget::UpdateOpaqueRegion. This could simplify titlebar drawing.
+ int radius = DoDrawTilebarCorners() ? int(GetTitlebarRadius()) : 0;
+ SubtractTitlebarCorners(region, x, y, width, height, radius);
+
+ gdk_window_set_opaque_region(window, region);
+
+ cairo_region_destroy(region);
+
+#ifdef MOZ_WAYLAND
+ if (GdkIsWaylandDisplay()) {
+ moz_container_wayland_update_opaque_region(mContainer, radius);
+ }
+#endif
+
+ SetTitlebarRect();
+}
+
+bool nsWindow::IsChromeWindowTitlebar() {
+ return mDrawInTitlebar && !mIsPIPWindow &&
+ mWindowType == WindowType::TopLevel;
+}
+
+bool nsWindow::DoDrawTilebarCorners() {
+ return IsChromeWindowTitlebar() && mSizeMode == nsSizeMode_Normal &&
+ !mIsTiled;
+}
+
+void nsWindow::ResizeTransparencyBitmap() {
+ if (!mTransparencyBitmap) {
+ return;
+ }
+
+ if (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight) {
+ return;
+ }
+
+ int32_t newRowBytes = GetBitmapStride(mBounds.width);
+ int32_t newSize = newRowBytes * mBounds.height;
+ auto* newBits = new gchar[newSize];
+ // fill new mask with "transparent", first
+ memset(newBits, 0, newSize);
+
+ // Now copy the intersection of the old and new areas into the new mask
+ int32_t copyWidth = std::min(mBounds.width, mTransparencyBitmapWidth);
+ int32_t copyHeight = std::min(mBounds.height, mTransparencyBitmapHeight);
+ int32_t oldRowBytes = GetBitmapStride(mTransparencyBitmapWidth);
+ int32_t copyBytes = GetBitmapStride(copyWidth);
+
+ int32_t i;
+ gchar* fromPtr = mTransparencyBitmap;
+ gchar* toPtr = newBits;
+ for (i = 0; i < copyHeight; i++) {
+ memcpy(toPtr, fromPtr, copyBytes);
+ fromPtr += oldRowBytes;
+ toPtr += newRowBytes;
+ }
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = newBits;
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+}
+
+static bool ChangedMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar maskByte = maskBytes[x >> 3];
+ bool maskBit = (maskByte & (1 << (x & 7))) != 0;
+
+ if (maskBit != newBit) {
+ return true;
+ }
+ }
+ aAlphas += aStride;
+ }
+
+ return false;
+}
+
+static void UpdateMaskBits(gchar* aMaskBits, int32_t aMaskWidth,
+ int32_t aMaskHeight, const nsIntRect& aRect,
+ uint8_t* aAlphas, int32_t aStride) {
+ int32_t x, y, xMax = aRect.XMost(), yMax = aRect.YMost();
+ int32_t maskBytesPerRow = GetBitmapStride(aMaskWidth);
+ for (y = aRect.y; y < yMax; y++) {
+ gchar* maskBytes = aMaskBits + y * maskBytesPerRow;
+ uint8_t* alphas = aAlphas;
+ for (x = aRect.x; x < xMax; x++) {
+ bool newBit = *alphas > 0x7f;
+ alphas++;
+
+ gchar mask = 1 << (x & 7);
+ gchar maskByte = maskBytes[x >> 3];
+ // Note: '-newBit' turns 0 into 00...00 and 1 into 11...11
+ maskBytes[x >> 3] = (maskByte & ~mask) | (-newBit & mask);
+ }
+ aAlphas += aStride;
+ }
+}
+
+void nsWindow::ApplyTransparencyBitmap() {
+#ifdef MOZ_X11
+ // We use X11 calls where possible, because GDK handles expose events
+ // for shaped windows in a way that's incompatible with us (Bug 635903).
+ // It doesn't occur when the shapes are set through X.
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ mTransparencyBitmapHeight);
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+ XFreePixmap(xDisplay, maskPixmap);
+#endif // MOZ_X11
+}
+
+void nsWindow::ClearTransparencyBitmap() {
+ if (!mTransparencyBitmap) {
+ return;
+ }
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ if (!mShell) {
+ return;
+ }
+
+#ifdef MOZ_X11
+ if (MOZ_UNLIKELY(!mGdkWindow)) {
+ return;
+ }
+
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ XShapeCombineMask(xDisplay, xWindow, ShapeBounding, 0, 0, X11None, ShapeSet);
+#endif
+}
+
+nsresult nsWindow::UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride) {
+ NS_ASSERTION(mIsTransparent, "Window is not transparent");
+ NS_ASSERTION(!mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used for titlebar rendering");
+
+ if (mTransparencyBitmap == nullptr) {
+ int32_t size = GetBitmapStride(mBounds.width) * mBounds.height;
+ mTransparencyBitmap = new gchar[size];
+ memset(mTransparencyBitmap, 255, size);
+ mTransparencyBitmapWidth = mBounds.width;
+ mTransparencyBitmapHeight = mBounds.height;
+ } else {
+ ResizeTransparencyBitmap();
+ }
+
+ nsIntRect rect;
+ rect.IntersectRect(aRect, nsIntRect(0, 0, mBounds.width, mBounds.height));
+
+ if (!ChangedMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride)) {
+ // skip the expensive stuff if the mask bits haven't changed; hopefully
+ // this is the common case
+ return NS_OK;
+ }
+
+ UpdateMaskBits(mTransparencyBitmap, mBounds.width, mBounds.height, rect,
+ aAlphas, aStride);
+
+ if (!mNeedsShow) {
+ ApplyTransparencyBitmap();
+ }
+ return NS_OK;
+}
+
+#define TITLEBAR_HEIGHT 10
+
+void nsWindow::SetTitlebarRect() {
+ MutexAutoLock lock(mTitlebarRectMutex);
+
+ if (!mGdkWindow || !DoDrawTilebarCorners()) {
+ mTitlebarRect = LayoutDeviceIntRect();
+ return;
+ }
+ mTitlebarRect = LayoutDeviceIntRect(0, 0, mBounds.width,
+ GdkCeiledScaleFactor() * TITLEBAR_HEIGHT);
+}
+
+LayoutDeviceIntRect nsWindow::GetTitlebarRect() {
+ MutexAutoLock lock(mTitlebarRectMutex);
+ return mTitlebarRect;
+}
+
+void nsWindow::UpdateTitlebarTransparencyBitmap() {
+ NS_ASSERTION(mTransparencyBitmapForTitlebar,
+ "Transparency bitmap is already used to draw window shape");
+
+ if (!mGdkWindow || !mDrawInTitlebar ||
+ (mBounds.width == mTransparencyBitmapWidth &&
+ mBounds.height == mTransparencyBitmapHeight)) {
+ return;
+ }
+
+ bool maskCreate =
+ !mTransparencyBitmap || mBounds.width > mTransparencyBitmapWidth;
+
+ bool maskUpdate =
+ !mTransparencyBitmap || mBounds.width != mTransparencyBitmapWidth;
+
+ LayoutDeviceIntCoord radius = GetTitlebarRadius();
+ if (maskCreate) {
+ delete[] mTransparencyBitmap;
+ int32_t size = GetBitmapStride(mBounds.width) * radius;
+ mTransparencyBitmap = new gchar[size];
+ mTransparencyBitmapWidth = mBounds.width;
+ } else {
+ mTransparencyBitmapWidth = mBounds.width;
+ }
+ mTransparencyBitmapHeight = mBounds.height;
+
+ if (maskUpdate) {
+ cairo_surface_t* surface = cairo_image_surface_create(
+ CAIRO_FORMAT_A8, mTransparencyBitmapWidth, radius);
+ if (!surface) {
+ return;
+ }
+
+ cairo_t* cr = cairo_create(surface);
+
+ GtkWidgetState state;
+ memset((void*)&state, 0, sizeof(state));
+ GdkRectangle rect = {0, 0, mTransparencyBitmapWidth, radius};
+
+ moz_gtk_widget_paint(MOZ_GTK_HEADER_BAR, cr, &rect, &state, 0,
+ GTK_TEXT_DIR_NONE);
+
+ cairo_destroy(cr);
+ cairo_surface_mark_dirty(surface);
+ cairo_surface_flush(surface);
+
+ UpdateMaskBits(mTransparencyBitmap, mTransparencyBitmapWidth, radius,
+ nsIntRect(0, 0, mTransparencyBitmapWidth, radius),
+ cairo_image_surface_get_data(surface),
+ cairo_format_stride_for_width(CAIRO_FORMAT_A8,
+ mTransparencyBitmapWidth));
+
+ cairo_surface_destroy(surface);
+ }
+
+#ifdef MOZ_X11
+ if (!mNeedsShow) {
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+
+ Pixmap maskPixmap =
+ XCreateBitmapFromData(xDisplay, xDrawable, mTransparencyBitmap,
+ mTransparencyBitmapWidth, radius);
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+
+ if (mTransparencyBitmapHeight > radius) {
+ XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
+ (unsigned short)(mTransparencyBitmapHeight - radius)};
+ XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0, radius,
+ &rect, 1, ShapeUnion, 0);
+ }
+
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+#endif
+}
+
+GtkWidget* nsWindow::GetToplevelWidget() const { return mShell; }
+
+GdkWindow* nsWindow::GetToplevelGdkWindow() const {
+ return gtk_widget_get_window(mShell);
+}
+
+nsWindow* nsWindow::GetContainerWindow() const {
+ GtkWidget* owningWidget = GTK_WIDGET(mContainer);
+ if (!owningWidget) {
+ return nullptr;
+ }
+
+ nsWindow* window = get_window_for_gtk_widget(owningWidget);
+ NS_ASSERTION(window, "No nsWindow for container widget");
+ return window;
+}
+
+void nsWindow::SetUrgencyHint(GtkWidget* top_window, bool state) {
+ LOG(" nsWindow::SetUrgencyHint widget %p\n", top_window);
+ if (!top_window) {
+ return;
+ }
+ GdkWindow* window = gtk_widget_get_window(top_window);
+ if (!window) {
+ return;
+ }
+ // TODO: Use xdg-activation on Wayland?
+ gdk_window_set_urgency_hint(window, state);
+}
+
+void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
+
+gint nsWindow::ConvertBorderStyles(BorderStyle aStyle) {
+ gint w = 0;
+
+ if (aStyle == BorderStyle::Default) {
+ return -1;
+ }
+
+ // note that we don't handle BorderStyle::Close yet
+ if (aStyle & BorderStyle::All) w |= GDK_DECOR_ALL;
+ if (aStyle & BorderStyle::Border) w |= GDK_DECOR_BORDER;
+ if (aStyle & BorderStyle::ResizeH) w |= GDK_DECOR_RESIZEH;
+ if (aStyle & BorderStyle::Title) w |= GDK_DECOR_TITLE;
+ if (aStyle & BorderStyle::Menu) w |= GDK_DECOR_MENU;
+ if (aStyle & BorderStyle::Minimize) w |= GDK_DECOR_MINIMIZE;
+ if (aStyle & BorderStyle::Maximize) w |= GDK_DECOR_MAXIMIZE;
+
+ return w;
+}
+
+class FullscreenTransitionWindow final : public nsISupports {
+ public:
+ NS_DECL_ISUPPORTS
+
+ explicit FullscreenTransitionWindow(GtkWidget* aWidget);
+
+ GtkWidget* mWindow;
+
+ private:
+ ~FullscreenTransitionWindow();
+};
+
+NS_IMPL_ISUPPORTS0(FullscreenTransitionWindow)
+
+FullscreenTransitionWindow::FullscreenTransitionWindow(GtkWidget* aWidget) {
+ mWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ GtkWindow* gtkWin = GTK_WINDOW(mWindow);
+
+ gtk_window_set_type_hint(gtkWin, GDK_WINDOW_TYPE_HINT_SPLASHSCREEN);
+ GtkWindowSetTransientFor(gtkWin, GTK_WINDOW(aWidget));
+ gtk_window_set_decorated(gtkWin, false);
+
+ GdkWindow* gdkWin = gtk_widget_get_window(aWidget);
+ GdkScreen* screen = gtk_widget_get_screen(aWidget);
+ gint monitorNum = gdk_screen_get_monitor_at_window(screen, gdkWin);
+ GdkRectangle monitorRect;
+ gdk_screen_get_monitor_geometry(screen, monitorNum, &monitorRect);
+ gtk_window_set_screen(gtkWin, screen);
+ gtk_window_move(gtkWin, monitorRect.x, monitorRect.y);
+ MOZ_ASSERT(monitorRect.width > 0 && monitorRect.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(gtkWin, monitorRect.width, monitorRect.height);
+
+ GdkRGBA bgColor;
+ bgColor.red = bgColor.green = bgColor.blue = 0.0;
+ bgColor.alpha = 1.0;
+ gtk_widget_override_background_color(mWindow, GTK_STATE_FLAG_NORMAL,
+ &bgColor);
+
+ gtk_widget_set_opacity(mWindow, 0.0);
+ gtk_widget_show(mWindow);
+}
+
+FullscreenTransitionWindow::~FullscreenTransitionWindow() {
+ gtk_widget_destroy(mWindow);
+}
+
+class FullscreenTransitionData {
+ public:
+ FullscreenTransitionData(nsIWidget::FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsIRunnable* aCallback,
+ FullscreenTransitionWindow* aWindow)
+ : mStage(aStage),
+ mStartTime(TimeStamp::Now()),
+ mDuration(TimeDuration::FromMilliseconds(aDuration)),
+ mCallback(aCallback),
+ mWindow(aWindow) {}
+
+ static const guint sInterval = 1000 / 30; // 30fps
+ static gboolean TimeoutCallback(gpointer aData);
+
+ private:
+ nsIWidget::FullscreenTransitionStage mStage;
+ TimeStamp mStartTime;
+ TimeDuration mDuration;
+ nsCOMPtr<nsIRunnable> mCallback;
+ RefPtr<FullscreenTransitionWindow> mWindow;
+};
+
+/* static */
+gboolean FullscreenTransitionData::TimeoutCallback(gpointer aData) {
+ bool finishing = false;
+ auto* data = static_cast<FullscreenTransitionData*>(aData);
+ gdouble opacity = (TimeStamp::Now() - data->mStartTime) / data->mDuration;
+ if (opacity >= 1.0) {
+ opacity = 1.0;
+ finishing = true;
+ }
+ if (data->mStage == nsIWidget::eAfterFullscreenToggle) {
+ opacity = 1.0 - opacity;
+ }
+ gtk_widget_set_opacity(data->mWindow->mWindow, opacity);
+
+ if (!finishing) {
+ return TRUE;
+ }
+ NS_DispatchToMainThread(data->mCallback.forget());
+ delete data;
+ return FALSE;
+}
+
+/* virtual */
+bool nsWindow::PrepareForFullscreenTransition(nsISupports** aData) {
+ if (!mCompositedScreen) {
+ return false;
+ }
+ *aData = do_AddRef(new FullscreenTransitionWindow(mShell)).take();
+ return true;
+}
+
+/* virtual */
+void nsWindow::PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) {
+ auto* data = static_cast<FullscreenTransitionWindow*>(aData);
+ // This will be released at the end of the last timeout callback for it.
+ auto* transitionData =
+ new FullscreenTransitionData(aStage, aDuration, aCallback, data);
+ g_timeout_add_full(G_PRIORITY_HIGH, FullscreenTransitionData::sInterval,
+ FullscreenTransitionData::TimeoutCallback, transitionData,
+ nullptr);
+}
+
+already_AddRefed<widget::Screen> nsWindow::GetWidgetScreen() {
+ // Wayland can read screen directly
+ if (GdkIsWaylandDisplay()) {
+ if (RefPtr<Screen> screen = ScreenHelperGTK::GetScreenForWindow(this)) {
+ return screen.forget();
+ }
+ }
+
+ // GetScreenBounds() is slow for the GTK port so we override and use
+ // mBounds directly.
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ LayoutDeviceIntRect bounds = mBounds;
+ DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ return screenManager.ScreenForRect(deskBounds);
+}
+
+RefPtr<VsyncDispatcher> nsWindow::GetVsyncDispatcher() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncDispatcher) {
+ return mWaylandVsyncDispatcher;
+ }
+#endif
+ return nullptr;
+}
+
+bool nsWindow::SynchronouslyRepaintOnResize() {
+ if (GdkIsWaylandDisplay()) {
+ // See Bug 1734368
+ // Don't request synchronous repaint on HW accelerated backend - mesa can be
+ // deadlocked when it's missing back buffer and main event loop is blocked.
+ return false;
+ }
+
+ // default is synced repaint.
+ return true;
+}
+
+void nsWindow::KioskLockOnMonitor() {
+ // Available as of GTK 3.18+
+ static auto sGdkWindowFullscreenOnMonitor =
+ (void (*)(GdkWindow* window, gint monitor))dlsym(
+ RTLD_DEFAULT, "gdk_window_fullscreen_on_monitor");
+
+ if (!sGdkWindowFullscreenOnMonitor) {
+ return;
+ }
+
+ int monitor = mKioskMonitor.value();
+ if (monitor < 0 || monitor >= ScreenHelperGTK::GetMonitorCount()) {
+ LOG("nsWindow::KioskLockOnMonitor() wrong monitor number! (%d)\n", monitor);
+ return;
+ }
+
+ LOG("nsWindow::KioskLockOnMonitor() locked on %d\n", monitor);
+ sGdkWindowFullscreenOnMonitor(GetToplevelGdkWindow(), monitor);
+}
+
+static bool IsFullscreenSupported(GtkWidget* aShell) {
+#ifdef MOZ_X11
+ GdkScreen* screen = gtk_widget_get_screen(aShell);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_STATE_FULLSCREEN", FALSE);
+ return gdk_x11_screen_supports_net_wm_hint(screen, atom);
+#else
+ return true;
+#endif
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen) {
+ LOG("nsWindow::MakeFullScreen aFullScreen %d\n", aFullScreen);
+
+ if (GdkIsX11Display() && !IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen &&
+ mSizeMode != nsSizeMode_Minimized) {
+ mLastSizeModeBeforeFullscreen = mSizeMode;
+ }
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
+ if (gUseAspectRatio) {
+ mAspectRatioSaved = mAspectRatio;
+ mAspectRatio = 0.0f;
+ ApplySizeConstraints();
+ }
+ }
+
+ if (mKioskMonitor.isSome()) {
+ KioskLockOnMonitor();
+ } else {
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ }
+ } else {
+ // Kiosk mode always use fullscreen mode.
+ if (gKioskMode) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+
+ if (mIsPIPWindow && gUseAspectRatio) {
+ mAspectRatio = mAspectRatioSaved;
+ // ApplySizeConstraints();
+ }
+ }
+
+ MOZ_ASSERT(mLastSizeModeBeforeFullscreen != nsSizeMode_Fullscreen);
+ return NS_OK;
+}
+
+void nsWindow::SetWindowDecoration(BorderStyle aStyle) {
+ LOG("nsWindow::SetWindowDecoration() Border style %x\n", int(aStyle));
+
+ // We can't use mGdkWindow directly here as it can be
+ // derived from mContainer which is not a top-level GdkWindow.
+ GdkWindow* window = GetToplevelGdkWindow();
+
+ // Sawfish, metacity, and presumably other window managers get
+ // confused if we change the window decorations while the window
+ // is visible.
+ bool wasVisible = false;
+ if (gdk_window_is_visible(window)) {
+ gdk_window_hide(window);
+ wasVisible = true;
+ }
+
+ gint wmd = ConvertBorderStyles(aStyle);
+ if (wmd != -1) gdk_window_set_decorations(window, (GdkWMDecoration)wmd);
+
+ if (wasVisible) gdk_window_show(window);
+
+ // For some window managers, adding or removing window decorations
+ // requires unmapping and remapping our toplevel window. Go ahead
+ // and flush the queue here so that we don't end up with a BadWindow
+ // error later when this happens (when the persistence timer fires
+ // and GetWindowPos is called)
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
+ } else
+#endif /* MOZ_X11 */
+ {
+ gdk_flush();
+ }
+}
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ SetWindowDecoration(aShouldHide ? BorderStyle::None : mBorderStyle);
+}
+
+bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup) {
+ LOG("nsWindow::CheckForRollup() aAlwaysRollup %d", aAlwaysRollup);
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (!rollupWidget) {
+ return false;
+ }
+
+ auto* rollupWindow =
+ (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (!aAlwaysRollup && is_mouse_in_window(rollupWindow, aMouseX, aMouseY)) {
+ return false;
+ }
+ bool retVal = false;
+ if (aIsWheel) {
+ retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ if (!rollupListener->ShouldRollupOnMouseWheelEvent()) {
+ return retVal;
+ }
+ }
+ LayoutDeviceIntPoint point;
+ nsIRollupListener::RollupOptions options{0,
+ nsIRollupListener::FlushViews::Yes};
+ // if we're dealing with menus, we probably have submenus and
+ // we don't want to rollup if the click is in a parent menu of
+ // the current submenu
+ if (!aAlwaysRollup) {
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ uint32_t sameTypeCount =
+ rollupListener->GetSubmenuWidgetChain(&widgetChain);
+ for (unsigned long i = 0; i < widgetChain.Length(); ++i) {
+ nsIWidget* widget = widgetChain[i];
+ auto* currWindow = (GdkWindow*)widget->GetNativeData(NS_NATIVE_WINDOW);
+ if (is_mouse_in_window(currWindow, aMouseX, aMouseY)) {
+ // Don't roll up if the mouse event occurred within a menu of the same
+ // type.
+ // If the mouse event occurred in a menu higher than that, roll up, but
+ // pass the number of popups to Rollup so that only those of the same
+ // type close up.
+ if (i < sameTypeCount) {
+ return retVal;
+ }
+ options.mCount = sameTypeCount;
+ break;
+ }
+ } // foreach parent menu widget
+ if (!aIsWheel) {
+ point = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
+ options.mPoint = &point;
+ }
+ }
+
+ if (mSizeMode == nsSizeMode_Minimized) {
+ // When we try to rollup in a minimized window, transitionend events for
+ // panels might not fire and thus we might not hide the popup after all,
+ // see bug 1810797.
+ options.mAllowAnimations = nsIRollupListener::AllowAnimations::No;
+ }
+
+ if (rollupListener->Rollup(options)) {
+ retVal = true;
+ }
+ return retVal;
+}
+
+/* static */
+bool nsWindow::DragInProgress() {
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (!dragService) {
+ return false;
+ }
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+ return !!currentDragSession;
+}
+
+// This is an ugly workaround for
+// https://bugzilla.mozilla.org/show_bug.cgi?id=1622107
+// We try to detect when Wayland compositor / gtk fails to deliver
+// info about finished D&D operations and cancel it on our own.
+MOZ_CAN_RUN_SCRIPT static void WaylandDragWorkaround(GdkEventButton* aEvent) {
+ static int buttonPressCountWithDrag = 0;
+
+ // We track only left button state as Firefox performs D&D on left
+ // button only.
+ if (aEvent->button != 1 || aEvent->type != GDK_BUTTON_PRESS) {
+ return;
+ }
+
+ nsCOMPtr<nsIDragService> dragService =
+ do_GetService("@mozilla.org/widget/dragservice;1");
+ if (!dragService) {
+ return;
+ }
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ if (!currentDragSession) {
+ buttonPressCountWithDrag = 0;
+ return;
+ }
+
+ buttonPressCountWithDrag++;
+ if (buttonPressCountWithDrag > 1) {
+ NS_WARNING(
+ "Quit unfinished Wayland Drag and Drop operation. Buggy Wayland "
+ "compositor?");
+ buttonPressCountWithDrag = 0;
+ dragService->EndDragSession(false, 0);
+ }
+}
+
+static nsWindow* get_window_for_gtk_widget(GtkWidget* widget) {
+ gpointer user_data = g_object_get_data(G_OBJECT(widget), "nsWindow");
+ return static_cast<nsWindow*>(user_data);
+}
+
+static nsWindow* get_window_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = g_object_get_data(G_OBJECT(window), "nsWindow");
+ return static_cast<nsWindow*>(user_data);
+}
+
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY) {
+ GdkWindow* window = aWindow;
+ if (!window) {
+ return false;
+ }
+
+ gint x = 0;
+ gint y = 0;
+
+ {
+ gint offsetX = 0;
+ gint offsetY = 0;
+
+ while (window) {
+ gint tmpX = 0;
+ gint tmpY = 0;
+
+ gdk_window_get_position(window, &tmpX, &tmpY);
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(window);
+
+ // if this is a window, compute x and y given its origin and our
+ // offset
+ if (GTK_IS_WINDOW(widget)) {
+ x = tmpX + offsetX;
+ y = tmpY + offsetY;
+ break;
+ }
+
+ offsetX += tmpX;
+ offsetY += tmpY;
+ window = gdk_window_get_parent(window);
+ }
+ }
+
+ gint margin = 0;
+ if (nsWindow* w = get_window_for_gdk_window(aWindow)) {
+ margin = w->GetInputRegionMarginInGdkCoords();
+ }
+
+ x += margin;
+ y += margin;
+
+ gint w = gdk_window_get_width(aWindow) - margin;
+ gint h = gdk_window_get_height(aWindow) - margin;
+
+ return aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h;
+}
+
+static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window) {
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(window, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+static GdkCursor* get_gtk_cursor(nsCursor aCursor) {
+ GdkCursor* gdkcursor = nullptr;
+ uint8_t newType = 0xff;
+
+ if ((gdkcursor = gCursorCache[aCursor])) {
+ return gdkcursor;
+ }
+
+ GdkDisplay* defaultDisplay = gdk_display_get_default();
+
+ switch (aCursor) {
+ case eCursor_standard:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
+ break;
+ case eCursor_wait:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "wait");
+ break;
+ case eCursor_select:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "text");
+ break;
+ case eCursor_hyperlink:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "pointer");
+ break;
+ case eCursor_n_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "n-resize");
+ break;
+ case eCursor_s_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "s-resize");
+ break;
+ case eCursor_w_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "w-resize");
+ break;
+ case eCursor_e_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "e-resize");
+ break;
+ case eCursor_nw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nw-resize");
+ break;
+ case eCursor_se_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "se-resize");
+ break;
+ case eCursor_ne_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ne-resize");
+ break;
+ case eCursor_sw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "sw-resize");
+ break;
+ case eCursor_crosshair:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crosshair");
+ break;
+ case eCursor_move:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
+ break;
+ case eCursor_help:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "help");
+ break;
+ case eCursor_copy:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "copy");
+ if (!gdkcursor) newType = MOZ_CURSOR_COPY;
+ break;
+ case eCursor_alias:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "alias");
+ if (!gdkcursor) newType = MOZ_CURSOR_ALIAS;
+ break;
+ case eCursor_context_menu:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "context-menu");
+ if (!gdkcursor) newType = MOZ_CURSOR_CONTEXT_MENU;
+ break;
+ case eCursor_cell:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "cell");
+ break;
+ case eCursor_grab:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grab");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
+ break;
+ case eCursor_grabbing:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "grabbing");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRABBING;
+ break;
+ case eCursor_spinning:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "progress");
+ if (!gdkcursor) newType = MOZ_CURSOR_SPINNING;
+ break;
+ case eCursor_zoom_in:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-in");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_IN;
+ break;
+ case eCursor_zoom_out:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "zoom-out");
+ if (!gdkcursor) newType = MOZ_CURSOR_ZOOM_OUT;
+ break;
+ case eCursor_not_allowed:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "not-allowed");
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_no_drop:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "no-drop");
+ if (!gdkcursor) { // this nonstandard sequence makes it work on KDE and
+ // GNOME
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "forbidden");
+ }
+ if (!gdkcursor) {
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "circle");
+ }
+ if (!gdkcursor) newType = MOZ_CURSOR_NOT_ALLOWED;
+ break;
+ case eCursor_vertical_text:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "vertical-text");
+ if (!gdkcursor) {
+ newType = MOZ_CURSOR_VERTICAL_TEXT;
+ }
+ break;
+ case eCursor_all_scroll:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "move");
+ break;
+ case eCursor_nesw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nesw-resize");
+ if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
+ break;
+ case eCursor_nwse_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "nwse-resize");
+ if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
+ break;
+ case eCursor_ns_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ns-resize");
+ break;
+ case eCursor_ew_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "ew-resize");
+ break;
+ case eCursor_row_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "row-resize");
+ break;
+ case eCursor_col_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "col-resize");
+ break;
+ case eCursor_none:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "none");
+ if (!gdkcursor) newType = MOZ_CURSOR_NONE;
+ break;
+ default:
+ NS_ASSERTION(aCursor, "Invalid cursor type");
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "default");
+ break;
+ }
+
+ // If by now we don't have a xcursor, this means we have to make a custom
+ // one. First, we try creating a named cursor based on the hash of our
+ // custom bitmap, as libXcursor has some magic to convert bitmapped cursors
+ // to themed cursors
+ if (newType != 0xFF && GtkCursors[newType].hash) {
+ gdkcursor =
+ gdk_cursor_new_from_name(defaultDisplay, GtkCursors[newType].hash);
+ }
+
+ // If we still don't have a xcursor, we now really create a bitmap cursor
+ if (newType != 0xff && !gdkcursor) {
+ GdkPixbuf* cursor_pixbuf =
+ gdk_pixbuf_new(GDK_COLORSPACE_RGB, TRUE, 8, 32, 32);
+ if (!cursor_pixbuf) {
+ return nullptr;
+ }
+
+ guchar* data = gdk_pixbuf_get_pixels(cursor_pixbuf);
+
+ // Read data from GtkCursors and compose RGBA surface from 1bit bitmap and
+ // mask GtkCursors bits and mask are 32x32 monochrome bitmaps (1 bit for
+ // each pixel) so it's 128 byte array (4 bytes for are one bitmap row and
+ // there are 32 rows here).
+ const unsigned char* bits = GtkCursors[newType].bits;
+ const unsigned char* mask_bits = GtkCursors[newType].mask_bits;
+
+ for (int i = 0; i < 128; i++) {
+ char bit = (char)*bits++;
+ char mask = (char)*mask_bits++;
+ for (int j = 0; j < 8; j++) {
+ unsigned char pix = ~(((bit >> j) & 0x01) * 0xff);
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = pix;
+ *data++ = (((mask >> j) & 0x01) * 0xff);
+ }
+ }
+
+ gdkcursor = gdk_cursor_new_from_pixbuf(
+ gdk_display_get_default(), cursor_pixbuf, GtkCursors[newType].hot_x,
+ GtkCursors[newType].hot_y);
+
+ g_object_unref(cursor_pixbuf);
+ }
+
+ gCursorCache[aCursor] = gdkcursor;
+
+ return gdkcursor;
+}
+
+// gtk callbacks
+
+void draw_window_of_widget(GtkWidget* widget, GdkWindow* aWindow, cairo_t* cr) {
+ if (gtk_cairo_should_draw_window(cr, aWindow)) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ NS_WARNING("Cannot get nsWindow from GtkWidget");
+ } else {
+ cairo_save(cr);
+ gtk_cairo_transform_to_window(cr, widget, aWindow);
+ // TODO - window->OnExposeEvent() can destroy this or other windows,
+ // do we need to handle it somehow?
+ window->OnExposeEvent(cr);
+ cairo_restore(cr);
+ }
+ }
+}
+
+/* static */
+gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr) {
+ draw_window_of_widget(widget, gtk_widget_get_window(widget), cr);
+
+ // A strong reference is already held during "draw" signal emission,
+ // but GTK+ 3.4 wants the object to live a little longer than that
+ // (bug 1225970).
+ g_object_ref(widget);
+ g_idle_add(
+ [](gpointer data) -> gboolean {
+ g_object_unref(data);
+ return G_SOURCE_REMOVE;
+ },
+ widget);
+
+ return FALSE;
+}
+
+static gboolean configure_event_cb(GtkWidget* widget,
+ GdkEventConfigure* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnConfigureEvent(widget, event);
+}
+
+// Some Gtk widget code may call gtk_widget_unrealize() which destroys
+// mGdkWindow. We need to listen on this signal and re-create
+// mGdkWindow when we're already mapped.
+static void widget_map_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnMap();
+}
+
+static void widget_unmap_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnUnmap();
+}
+
+static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnSizeAllocate(allocation);
+}
+
+static void toplevel_window_size_allocate_cb(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->UpdateTopLevelOpaqueRegion();
+}
+
+static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnDeleteEvent();
+
+ return TRUE;
+}
+
+static gboolean enter_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ // We have stored leave notify - check if it's the correct one and
+ // fire it before enter notify in such case.
+ if (sStoredLeaveNotifyEvent) {
+ auto clearNofityEvent =
+ MakeScopeExit([&] { sStoredLeaveNotifyEvent = nullptr; });
+ if (event->x_root == sStoredLeaveNotifyEvent->x_root &&
+ event->y_root == sStoredLeaveNotifyEvent->y_root &&
+ window->ApplyEnterLeaveMutterWorkaround()) {
+ // Enter/Leave notify events has the same coordinates
+ // and uses know buggy window config.
+ // Consider it as a bogus one.
+ return TRUE;
+ }
+ RefPtr<nsWindow> leftWindow =
+ get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
+ if (leftWindow) {
+ leftWindow->OnLeaveNotifyEvent(sStoredLeaveNotifyEvent.get());
+ }
+ }
+
+ window->OnEnterNotifyEvent(event);
+ return TRUE;
+}
+
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return TRUE;
+ }
+
+ if (window->ApplyEnterLeaveMutterWorkaround()) {
+ // The leave event is potentially wrong, don't fire it now but store
+ // it for further check at enter_notify_event_cb().
+ sStoredLeaveNotifyEvent.reset(reinterpret_cast<GdkEventCrossing*>(
+ gdk_event_copy(reinterpret_cast<GdkEvent*>(event))));
+ } else {
+ sStoredLeaveNotifyEvent = nullptr;
+ window->OnLeaveNotifyEvent(event);
+ }
+
+ return TRUE;
+}
+
+static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow) {
+ nsWindow* window;
+ while (!(window = get_window_for_gdk_window(aGdkWindow))) {
+ // The event has bubbled to the moz_container widget as passed into each
+ // caller's *widget parameter, but its corresponding nsWindow is an ancestor
+ // of the window that we need. Instead, look at event->window and find the
+ // first ancestor nsWindow of it because event->window may be in a plugin.
+ aGdkWindow = gdk_window_get_parent(aGdkWindow);
+ if (!aGdkWindow) {
+ window = nullptr;
+ break;
+ }
+ }
+ return window;
+}
+
+static gboolean motion_notify_event_cb(GtkWidget* widget,
+ GdkEventMotion* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnMotionNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean button_press_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnButtonPressEvent(event);
+
+ if (GdkIsWaylandDisplay()) {
+ WaylandDragWorkaround(event);
+ }
+
+ return TRUE;
+}
+
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnButtonReleaseEvent(event);
+
+ return TRUE;
+}
+
+static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnContainerFocusInEvent(event);
+
+ return FALSE;
+}
+
+static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnContainerFocusOutEvent(event);
+
+ return FALSE;
+}
+
+#ifdef MOZ_X11
+// For long-lived popup windows that don't really take focus themselves but
+// may have elements that accept keyboard input when the parent window is
+// active, focus is handled specially. These windows include noautohide
+// panels. (This special handling is not necessary for temporary popups where
+// the keyboard is grabbed.)
+//
+// Mousing over or clicking on these windows should not cause them to steal
+// focus from their parent windows, so, the input field of WM_HINTS is set to
+// False to request that the window manager not set the input focus to this
+// window. http://tronche.com/gui/x/icccm/sec-4.html#s-4.1.7
+//
+// However, these windows can still receive WM_TAKE_FOCUS messages from the
+// window manager, so they can still detect when the user has indicated that
+// they wish to direct keyboard input at these windows. When the window
+// manager offers focus to these windows (after a mouse over or click, for
+// example), a request to make the parent window active is issued. When the
+// parent window becomes active, keyboard events will be received.
+
+static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
+ GdkEvent* event, gpointer data) {
+ auto* xevent = static_cast<XEvent*>(gdk_xevent);
+ if (xevent->type != ClientMessage) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ XClientMessageEvent& xclient = xevent->xclient;
+ if (xclient.message_type != gdk_x11_get_xatom_by_name("WM_PROTOCOLS")) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ Atom atom = xclient.data.l[0];
+ if (atom != gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ guint32 timestamp = xclient.data.l[1];
+
+ GtkWidget* widget = get_gtk_widget_for_gdk_window(event->any.window);
+ if (!widget) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ GtkWindow* parent = gtk_window_get_transient_for(GTK_WINDOW(widget));
+ if (!parent) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ if (gtk_window_is_active(parent)) {
+ return GDK_FILTER_REMOVE; // leave input focus on the parent
+ }
+
+ GdkWindow* parent_window = gtk_widget_get_window(GTK_WIDGET(parent));
+ if (!parent_window) {
+ return GDK_FILTER_CONTINUE;
+ }
+
+ // In case the parent has not been deiconified.
+ gdk_window_show_unraised(parent_window);
+
+ // Request focus on the parent window.
+ // Use gdk_window_focus rather than gtk_window_present to avoid
+ // raising the parent window.
+ gdk_window_focus(parent_window, timestamp);
+ return GDK_FILTER_REMOVE;
+}
+#endif /* MOZ_X11 */
+
+static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOGW("key_press_event_cb\n");
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+
+#ifdef MOZ_X11
+ // Keyboard repeat can cause key press events to queue up when there are
+ // slow event handlers (bug 301029). Throttle these events by removing
+ // consecutive pending duplicate KeyPress events to the same window.
+ // We use the event time of the last one.
+ // Note: GDK calls XkbSetDetectableAutorepeat so that KeyRelease events
+ // are generated only when the key is physically released.
+# define NS_GDKEVENT_MATCH_MASK 0x1FFF // GDK_SHIFT_MASK .. GDK_BUTTON5_MASK
+ // Our headers undefine X11 KeyPress - let's redefine it here.
+# ifndef KeyPress
+# define KeyPress 2
+# endif
+ GdkDisplay* gdkDisplay = gtk_widget_get_display(widget);
+ if (GdkIsX11Display(gdkDisplay)) {
+ Display* dpy = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ while (XPending(dpy)) {
+ XEvent next_event;
+ XPeekEvent(dpy, &next_event);
+ GdkWindow* nextGdkWindow =
+ gdk_x11_window_lookup_for_display(gdkDisplay, next_event.xany.window);
+ if (nextGdkWindow != event->window || next_event.type != KeyPress ||
+ next_event.xkey.keycode != event->hardware_keycode ||
+ next_event.xkey.state != (event->state & NS_GDKEVENT_MATCH_MASK)) {
+ break;
+ }
+ XNextEvent(dpy, &next_event);
+ event->time = next_event.xkey.time;
+ }
+ }
+#endif
+
+ return focusWindow->OnKeyPressEvent(event);
+}
+
+static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event) {
+ LOGW("key_release_event_cb\n");
+
+ UpdateLastInputEventTime(event);
+
+ // find the window with focus and dispatch this event to that widget
+ nsWindow* window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ RefPtr<nsWindow> focusWindow = gFocusWindow ? gFocusWindow : window;
+ return focusWindow->OnKeyReleaseEvent(event);
+}
+
+static gboolean property_notify_event_cb(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnPropertyNotifyEvent(aWidget, aEvent);
+}
+
+static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event) {
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+ if (NS_WARN_IF(!window)) {
+ return FALSE;
+ }
+
+ window->OnScrollEvent(event);
+
+ return TRUE;
+}
+
+static gboolean visibility_notify_event_cb(GtkWidget* widget,
+ GdkEventVisibility* event) {
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) {
+ return FALSE;
+ }
+ window->OnVisibilityNotifyEvent(event->state);
+ return TRUE;
+}
+
+static void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel) {
+ GtkWidget* toplevel = gtk_widget_get_toplevel(widget);
+ GdkWindowState old_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+ GdkEventWindowState event;
+
+ event.new_window_state = GDK_WINDOW_STATE_WITHDRAWN;
+
+ if (GTK_IS_WINDOW(previous_toplevel)) {
+ g_signal_handlers_disconnect_by_func(
+ previous_toplevel, FuncToGpointer(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(previous_toplevel);
+ if (win) {
+ old_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ if (GTK_IS_WINDOW(toplevel)) {
+ g_signal_connect_swapped(toplevel, "window-state-event",
+ G_CALLBACK(window_state_event_cb), widget);
+ GdkWindow* win = gtk_widget_get_window(toplevel);
+ if (win) {
+ event.new_window_state = gdk_window_get_state(win);
+ }
+ }
+
+ event.changed_mask =
+ static_cast<GdkWindowState>(old_window_state ^ event.new_window_state);
+
+ if (event.changed_mask) {
+ event.type = GDK_WINDOW_STATE;
+ event.window = nullptr;
+ event.send_event = TRUE;
+ window_state_event_cb(widget, &event);
+ }
+}
+
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return FALSE;
+ }
+
+ window->OnWindowStateEvent(widget, event);
+
+ return FALSE;
+}
+
+static void settings_xft_dpi_changed_cb(GtkSettings* gtk_settings,
+ GParamSpec* pspec, nsWindow* data) {
+ RefPtr<nsWindow> window = data;
+ window->OnDPIChanged();
+ // Even though the window size in screen pixels has not changed,
+ // nsViewManager stores the dimensions in app units.
+ // DispatchResized() updates those.
+ window->DispatchResized();
+}
+
+static void check_resize_cb(GtkContainer* container, gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(GTK_WIDGET(container));
+ if (!window) {
+ return;
+ }
+ window->OnCheckResize();
+}
+
+static void screen_composited_changed_cb(GdkScreen* screen,
+ gpointer user_data) {
+ // This callback can run before gfxPlatform::Init() in rare
+ // cases involving the profile manager. When this happens,
+ // we have no reason to reset any compositors as graphics
+ // hasn't been initialized yet.
+ if (GPUProcessManager::Get()) {
+ GPUProcessManager::Get()->ResetCompositors();
+ }
+}
+
+static void widget_composited_changed_cb(GtkWidget* widget,
+ gpointer user_data) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+ window->OnCompositedChanged();
+}
+
+static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
+ gpointer aPointer) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnScaleChanged(/* aNotify = */ true);
+}
+
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
+ UpdateLastInputEventTime(aEvent);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(aEvent->window);
+ if (!window) {
+ return FALSE;
+ }
+
+ return window->OnTouchEvent(aEvent);
+}
+
+// This function called generic because there is no signal specific to touchpad
+// pinch events.
+static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent) {
+ if (aEvent->type != GDK_TOUCHPAD_PINCH) {
+ return FALSE;
+ }
+ // Using reinterpret_cast because the touchpad_pinch field of GdkEvent is not
+ // available in GTK+ versions lower than v3.18
+ GdkEventTouchpadPinch* event =
+ reinterpret_cast<GdkEventTouchpadPinch*>(aEvent);
+
+ RefPtr<nsWindow> window = GetFirstNSWindowForGDKWindow(event->window);
+
+ if (!window) {
+ return FALSE;
+ }
+ return window->OnTouchpadPinchEvent(event);
+}
+
+//////////////////////////////////////////////////////////////////////
+// These are all of our drag and drop operations
+
+void nsWindow::InitDragEvent(WidgetDragEvent& aEvent) {
+ // set the keyboard modifiers
+ guint modifierState = KeymapWrapper::GetCurrentModifierState();
+ KeymapWrapper::InitInputEvent(aEvent, modifierState);
+}
+
+static LayoutDeviceIntPoint GetWindowDropPosition(nsWindow* aWindow, int aX,
+ int aY) {
+ // Workaround for Bug 1710344
+ // Caused by Gtk issue https://gitlab.gnome.org/GNOME/gtk/-/issues/4437
+ if (aWindow->IsWaylandPopup()) {
+ int tx = 0, ty = 0;
+ gdk_window_get_position(aWindow->GetToplevelGdkWindow(), &tx, &ty);
+ aX += tx;
+ aY += ty;
+ }
+ LOGDRAG("WindowDropPosition [%d, %d]", aX, aY);
+ return aWindow->GdkPointToDevicePixels({aX, aY});
+}
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX, gint aY,
+ guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ return FALSE;
+ }
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+ LOGDRAG("WindowDragMotionHandler target nsWindow [%p]",
+ innerMostWindow.get());
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ if (!dragService->ScheduleMotionEvent(
+ innerMostWindow, aDragContext,
+ GetWindowDropPosition(innerMostWindow, retx, rety), aTime)) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragMotionHandler(aWidget, aDragContext, aX, aY, aTime);
+}
+
+void WindowDragLeaveHandler(GtkWidget* aWidget) {
+ LOGDRAG("WindowDragLeaveHandler()\n");
+
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ LOGDRAG(" Failed - can't find nsWindow!\n");
+ return;
+ }
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+
+ nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
+ if (!mostRecentDragWindow) {
+ // This can happen when the target will not accept a drop. A GTK drag
+ // source sends the leave message to the destination before the
+ // drag-failed signal on the source widget, but the leave message goes
+ // via the X server, and so doesn't get processed at least until the
+ // event loop runs again.
+ LOGDRAG(" Failed - GetMostRecentDestWindow()!\n");
+ return;
+ }
+
+ if (aWidget != window->GetGtkWidget()) {
+ // When the drag moves between widgets, GTK can send leave signal for
+ // the old widget after the motion or drop signal for the new widget.
+ // We'll send the leave event when the motion or drop event is run.
+ LOGDRAG(" Failed - GtkWidget mismatch!\n");
+ return;
+ }
+
+ LOGDRAG("WindowDragLeaveHandler nsWindow %p\n", (void*)mostRecentDragWindow);
+ dragService->ScheduleLeaveEvent();
+}
+
+static void drag_leave_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, guint aTime,
+ gpointer aData) {
+ WindowDragLeaveHandler(aWidget);
+}
+
+gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ gint aX, gint aY, guint aTime) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ return FALSE;
+ }
+
+ // figure out which internal widget this drag motion actually happened on
+ nscoord retx = 0;
+ nscoord rety = 0;
+
+ GdkWindow* innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget),
+ aX, aY, &retx, &rety);
+ RefPtr<nsWindow> innerMostWindow = get_window_for_gdk_window(innerWindow);
+
+ if (!innerMostWindow) {
+ innerMostWindow = window;
+ }
+
+ LOGDRAG("WindowDragDropHandler nsWindow [%p]", innerMostWindow.get());
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ nsDragService::AutoEventLoop loop(dragService);
+ return dragService->ScheduleDropEvent(
+ innerMostWindow, aDragContext,
+ GetWindowDropPosition(innerMostWindow, retx, rety), aTime);
+}
+
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragDropHandler(aWidget, aDragContext, aX, aY, aTime);
+}
+
+static void drag_data_received_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint aTime,
+ gpointer aData) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) {
+ return;
+ }
+
+ window->OnDragDataReceivedEvent(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime, aData);
+}
+
+static nsresult initialize_prefs(void) {
+ if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
+ gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
+ } else {
+ gUseAspectRatio = IsGnomeDesktopEnvironment() || IsKdeDesktopEnvironment();
+ }
+ return NS_OK;
+}
+
+// TODO: Can we simplify it for mShell/mContainer only scenario?
+static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
+ gint* retx, gint* rety) {
+ gint cx, cy, cw, ch;
+ GList* children = gdk_window_peek_children(aWindow);
+ for (GList* child = g_list_last(children); child;
+ child = g_list_previous(child)) {
+ auto* childWindow = (GdkWindow*)child->data;
+ if (get_window_for_gdk_window(childWindow)) {
+ gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch);
+ if ((cx < x) && (x < (cx + cw)) && (cy < y) && (y < (cy + ch)) &&
+ gdk_window_is_visible(childWindow)) {
+ return get_inner_gdk_window(childWindow, x - cx, y - cy, retx, rety);
+ }
+ }
+ }
+ *retx = x;
+ *rety = y;
+ return aWindow;
+}
+
+#ifdef ACCESSIBILITY
+void nsWindow::CreateRootAccessible() {
+ if (!mRootAccessible) {
+ LOG("nsWindow:: Create Toplevel Accessibility\n");
+ mRootAccessible = GetRootAccessible();
+ }
+}
+
+void nsWindow::DispatchEventToRootAccessible(uint32_t aEventType) {
+ if (!a11y::ShouldA11yBeEnabled()) {
+ return;
+ }
+
+ nsAccessibilityService* accService = GetOrCreateAccService();
+ if (!accService) {
+ return;
+ }
+
+ // Get the root document accessible and fire event to it.
+ a11y::LocalAccessible* acc = GetRootAccessible();
+ if (acc) {
+ accService->FireAccessibleEvent(aEventType, acc);
+ }
+}
+
+void nsWindow::DispatchActivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_ACTIVATE);
+}
+
+void nsWindow::DispatchDeactivateEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_DEACTIVATE);
+}
+
+void nsWindow::DispatchMaximizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MAXIMIZE);
+}
+
+void nsWindow::DispatchMinimizeEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_MINIMIZE);
+}
+
+void nsWindow::DispatchRestoreEventAccessible(void) {
+ DispatchEventToRootAccessible(nsIAccessibleEvent::EVENT_WINDOW_RESTORE);
+}
+
+#endif /* #ifdef ACCESSIBILITY */
+
+void nsWindow::SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) {
+ if (!mIMContext) {
+ return;
+ }
+ mIMContext->SetInputContext(this, &aContext, &aAction);
+}
+
+InputContext nsWindow::GetInputContext() {
+ InputContext context;
+ if (!mIMContext) {
+ context.mIMEState.mEnabled = IMEEnabled::Disabled;
+ context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED;
+ } else {
+ context = mIMContext->GetInputContext();
+ }
+ return context;
+}
+
+TextEventDispatcherListener* nsWindow::GetNativeTextEventDispatcherListener() {
+ if (NS_WARN_IF(!mIMContext)) {
+ return nullptr;
+ }
+ return mIMContext;
+}
+
+bool nsWindow::GetEditCommands(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands) {
+ // Validate the arguments.
+ if (NS_WARN_IF(!nsIWidget::GetEditCommands(aType, aEvent, aCommands))) {
+ return false;
+ }
+
+ Maybe<WritingMode> writingMode;
+ if (aEvent.NeedsToRemapNavigationKey()) {
+ if (RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher()) {
+ writingMode = dispatcher->MaybeQueryWritingModeAtSelection();
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, writingMode, aCommands);
+ return true;
+}
+
+already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void nsWindow::EndRemoteDrawingInRegion(
+ DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+bool nsWindow::GetDragInfo(WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY) {
+ if (aMouseEvent->mButton != MouseButton::ePrimary) {
+ // we can only begin a move drag with the left mouse button
+ return false;
+ }
+ *aButton = 1;
+
+ // get the gdk window for this widget
+ GdkWindow* gdk_window = mGdkWindow;
+ if (!gdk_window) {
+ return false;
+ }
+#ifdef DEBUG
+ // GDK_IS_WINDOW(...) expands to a statement-expression, and
+ // statement-expressions are not allowed in template-argument lists. So we
+ // have to make the MOZ_ASSERT condition indirect.
+ if (!GDK_IS_WINDOW(gdk_window)) {
+ MOZ_ASSERT(false, "must really be window");
+ }
+#endif
+
+ // find the top-level window
+ gdk_window = gdk_window_get_toplevel(gdk_window);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+ *aWindow = gdk_window;
+
+ if (!aMouseEvent->mWidget) {
+ return false;
+ }
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
+ // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
+ // See _should_perform_ewmh_drag() at gdkwindow-x11.c
+ // XXXsmaug remove this old hack. gtk should be fixed now.
+ GdkScreen* screen = gdk_window_get_screen(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ static TimeStamp lastTimeStamp;
+ if (lastTimeStamp != aMouseEvent->mTimeStamp) {
+ lastTimeStamp = aMouseEvent->mTimeStamp;
+ } else {
+ return false;
+ }
+ }
+ }
+#endif
+
+ // FIXME: It would be nice to have the widget position at the time
+ // of the event, but it's relatively unlikely that the widget has
+ // moved since the mousedown. (On the other hand, it's quite likely
+ // that the mouse has moved, which is why we use the mouse position
+ // from the event.)
+ LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
+ *aRootX = aMouseEvent->mRefPoint.x + offset.x;
+ *aRootY = aMouseEvent->mRefPoint.y + offset.y;
+
+ return true;
+}
+
+nsIWidget::WindowRenderer* nsWindow::GetWindowRenderer() {
+ if (mIsDestroyed) {
+ // Prevent external code from triggering the re-creation of the
+ // LayerManager/Compositor during shutdown. Just return what we currently
+ // have, which is most likely null.
+ return mWindowRenderer;
+ }
+
+ return nsBaseWidget::GetWindowRenderer();
+}
+
+void nsWindow::DidGetNonBlankPaint() {
+ if (mGotNonBlankPaint) {
+ return;
+ }
+ mGotNonBlankPaint = true;
+ if (!mConfiguredClearColor) {
+ // Nothing to do, we hadn't overridden the clear color.
+ mConfiguredClearColor = true;
+ return;
+ }
+ // Reset the clear color set in the expose event to transparent.
+ GetWindowRenderer()->AsWebRender()->WrBridge()->SendSetDefaultClearColor(
+ NS_TRANSPARENT);
+}
+
+/* nsWindow::SetCompositorWidgetDelegate() sets remote GtkCompositorWidget
+ * to render into with compositor.
+ *
+ * SetCompositorWidgetDelegate(delegate) is called from
+ * nsBaseWidget::CreateCompositor(), i.e. nsWindow::GetWindowRenderer().
+ *
+ * SetCompositorWidgetDelegate(null) is called from
+ * nsBaseWidget::DestroyCompositor().
+ */
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ LOG("nsWindow::SetCompositorWidgetDelegate %p mIsMapped %d "
+ "mCompositorWidgetDelegate %p\n",
+ delegate, mIsMapped, mCompositorWidgetDelegate);
+
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+ if (mIsMapped) {
+ ConfigureCompositor();
+ }
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+nsresult nsWindow::SetNonClientMargins(const LayoutDeviceIntMargin& aMargins) {
+ SetDrawsInTitlebar(aMargins.top == 0);
+ return NS_OK;
+}
+
+bool nsWindow::IsAlwaysUndecoratedWindow() const {
+ if (mIsPIPWindow || gKioskMode) {
+ return true;
+ }
+ if (mWindowType == WindowType::Dialog &&
+ mBorderStyle != BorderStyle::Default &&
+ mBorderStyle != BorderStyle::All &&
+ !(mBorderStyle & BorderStyle::Title) &&
+ !(mBorderStyle & BorderStyle::ResizeH)) {
+ return true;
+ }
+ return false;
+}
+
+void nsWindow::SetDrawsInTitlebar(bool aState) {
+ LOG("nsWindow::SetDrawsInTitlebar() State %d mGtkWindowDecoration %d\n",
+ aState, (int)mGtkWindowDecoration);
+
+ if (mGtkWindowDecoration == GTK_DECORATION_NONE ||
+ aState == mDrawInTitlebar) {
+ LOG(" already set, quit");
+ return;
+ }
+
+ if (mUndecorated) {
+ MOZ_ASSERT(aState, "Unexpected decoration request");
+ MOZ_ASSERT(!gtk_window_get_decorated(GTK_WINDOW(mShell)));
+ return;
+ }
+
+ mDrawInTitlebar = aState;
+
+ if (mGtkWindowDecoration == GTK_DECORATION_SYSTEM) {
+ SetWindowDecoration(aState ? BorderStyle::Border : mBorderStyle);
+ } else if (mGtkWindowDecoration == GTK_DECORATION_CLIENT) {
+ LOG(" Using CSD mode\n");
+
+ if (!gtk_widget_get_realized(GTK_WIDGET(mShell))) {
+ LOG(" Using CSD mode fast path\n");
+ gtk_window_set_titlebar(GTK_WINDOW(mShell),
+ aState ? gtk_fixed_new() : nullptr);
+ return;
+ }
+
+ /* Window manager does not support GDK_DECOR_BORDER,
+ * emulate it by CSD.
+ *
+ * gtk_window_set_titlebar() works on unrealized widgets only,
+ * we need to handle mShell carefully here.
+ * When CSD is enabled mGdkWindow is owned by mContainer which is good
+ * as we can't delete our mGdkWindow. To make mShell unrealized while
+ * mContainer is preserved we temporary reparent mContainer to an
+ * invisible GtkWindow.
+ */
+ bool visible = !mNeedsShow && mIsShown;
+ if (visible) {
+ NativeShow(false);
+ }
+
+ // Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* tmpWindow = gtk_window_new(GTK_WINDOW_POPUP);
+ gtk_widget_realize(tmpWindow);
+
+ gtk_widget_reparent(GTK_WIDGET(mContainer), tmpWindow);
+ gtk_widget_unrealize(GTK_WIDGET(mShell));
+
+ // Add a hidden titlebar widget to trigger CSD, but disable the default
+ // titlebar. GtkFixed is a somewhat random choice for a simple unused
+ // widget. gtk_window_set_titlebar() takes ownership of the titlebar
+ // widget.
+ gtk_window_set_titlebar(GTK_WINDOW(mShell),
+ aState ? gtk_fixed_new() : nullptr);
+
+ /* A workaround for https://bugzilla.gnome.org/show_bug.cgi?id=791081
+ * gtk_widget_realize() throws:
+ * "In pixman_region32_init_rect: Invalid rectangle passed"
+ * when mShell has default 1x1 size.
+ */
+ GtkAllocation allocation = {0, 0, 0, 0};
+ gtk_widget_get_preferred_width(GTK_WIDGET(mShell), nullptr,
+ &allocation.width);
+ gtk_widget_get_preferred_height(GTK_WIDGET(mShell), nullptr,
+ &allocation.height);
+ gtk_widget_size_allocate(GTK_WIDGET(mShell), &allocation);
+
+ gtk_widget_realize(GTK_WIDGET(mShell));
+ gtk_widget_reparent(GTK_WIDGET(mContainer), GTK_WIDGET(mShell));
+
+ // Label mShell toplevel window so property_notify_event_cb callback
+ // can find its way home.
+ g_object_set_data(G_OBJECT(GetToplevelGdkWindow()), "nsWindow", this);
+
+ if (AreBoundsSane()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ LOG(" resize to %d x %d\n", size.width, size.height);
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ }
+
+ if (visible) {
+ mNeedsShow = true;
+ NativeShow(true);
+ }
+
+ gtk_widget_destroy(tmpWindow);
+ }
+
+ if (mTransparencyBitmapForTitlebar) {
+ if (mDrawInTitlebar && mSizeMode == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ } else {
+ SetTitlebarRect();
+ }
+}
+
+GtkWindow* nsWindow::GetCurrentTopmostWindow() const {
+ GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
+ GtkWindow* topmostParentWindow = nullptr;
+ while (parentWindow) {
+ topmostParentWindow = parentWindow;
+ parentWindow = gtk_window_get_transient_for(parentWindow);
+ }
+ return topmostParentWindow;
+}
+
+gint nsWindow::GdkCeiledScaleFactor() {
+ if (IsTopLevelWindowType()) {
+ return mCeiledScaleFactor;
+ }
+ if (nsWindow* topmost = GetTopmostWindow()) {
+ return topmost->mCeiledScaleFactor;
+ }
+ return ScreenHelperGTK::GetGTKMonitorScaleFactor();
+}
+
+double nsWindow::FractionalScaleFactor() {
+#ifdef MOZ_WAYLAND
+ double fractional_scale = [&] {
+ if (IsTopLevelWindowType()) {
+ return mFractionalScaleFactor;
+ }
+ if (nsWindow* topmost = GetTopmostWindow()) {
+ return topmost->mFractionalScaleFactor;
+ }
+ return 0.0;
+ }();
+ if (fractional_scale != 0.0) {
+ return fractional_scale;
+ }
+#endif
+ return GdkCeiledScaleFactor();
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundUp(int aPixels) {
+ double scale = FractionalScaleFactor();
+ return ceil(aPixels / scale);
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundDown(int aPixels) {
+ double scale = FractionalScaleFactor();
+ return floor(aPixels / scale);
+}
+
+GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(
+ const LayoutDeviceIntPoint& aPoint) {
+ double scale = FractionalScaleFactor();
+ return {int(aPoint.x / scale), int(aPoint.y / scale)};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(
+ const LayoutDeviceIntRect& aRect) {
+ double scale = FractionalScaleFactor();
+ int x = floor(aRect.x / scale);
+ int y = floor(aRect.y / scale);
+ int right = ceil((aRect.x + aRect.width) / scale);
+ int bottom = ceil((aRect.y + aRect.height) / scale);
+ return {x, y, right - x, bottom - y};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
+ const LayoutDeviceIntSize& aSize) {
+ double scale = FractionalScaleFactor();
+ gint width = ceil(aSize.width / scale);
+ gint height = ceil(aSize.height / scale);
+ return {0, 0, width, height};
+}
+
+int nsWindow::GdkCoordToDevicePixels(gint aCoord) {
+ return (int)(aCoord * FractionalScaleFactor());
+}
+
+LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble aX,
+ gdouble aY) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntPoint::Floor((float)(aX * scale), (float)(aY * scale));
+}
+
+LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(const GdkPoint& aPoint) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntPoint::Floor((float)(aPoint.x * scale),
+ (float)(aPoint.y * scale));
+}
+
+LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(const GdkRectangle& aRect) {
+ double scale = FractionalScaleFactor();
+ return LayoutDeviceIntRect::RoundIn(
+ (float)(aRect.x * scale), (float)(aRect.y * scale),
+ (float)(aRect.width * scale), (float)(aRect.height * scale));
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(
+ LayoutDeviceIntPoint aPoint, NativeMouseMessage aNativeMessage,
+ MouseButton aButton, nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) {
+ LOG("SynthesizeNativeMouseEvent(%d, %d, %d, %d, %d)", aPoint.x.value,
+ aPoint.y.value, int(aNativeMessage), int(aButton), int(aModifierFlags));
+
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ // When a button-press/release event is requested, create it here and put it
+ // in the event queue. This will not emit a motion event - this needs to be
+ // done explicitly *before* requesting a button-press/release. You will also
+ // need to wait for the motion event to be dispatched before requesting a
+ // button-press/release event to maintain the desired event order.
+ switch (aNativeMessage) {
+ case NativeMouseMessage::ButtonDown:
+ case NativeMouseMessage::ButtonUp: {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = aNativeMessage == NativeMouseMessage::ButtonDown
+ ? GDK_BUTTON_PRESS
+ : GDK_BUTTON_RELEASE;
+ switch (aButton) {
+ case MouseButton::ePrimary:
+ case MouseButton::eMiddle:
+ case MouseButton::eSecondary:
+ case MouseButton::eX1:
+ case MouseButton::eX2:
+ event.button.button = aButton + 1;
+ break;
+ default:
+ return NS_ERROR_INVALID_ARG;
+ }
+ event.button.state =
+ KeymapWrapper::ConvertWidgetModifierToGdkState(aModifierFlags);
+ event.button.window = mGdkWindow;
+ event.button.time = GDK_CURRENT_TIME;
+
+ // Get device for event source
+ event.button.device = GdkGetPointer();
+
+ event.button.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.button.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.button.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.button.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+ return NS_OK;
+ }
+ case NativeMouseMessage::Move: {
+ // We don't support specific events other than button-press/release. In
+ // all other cases we'll synthesize a motion event that will be emitted by
+ // gdk_display_warp_pointer().
+ // XXX How to activate native modifier for the other events?
+#ifdef MOZ_WAYLAND
+ // Impossible to warp the pointer on Wayland.
+ // For pointer lock, pointer-constraints and relative-pointer are used.
+ if (GdkIsWaylandDisplay()) {
+ return NS_OK;
+ }
+#endif
+ GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
+ gdk_device_warp(GdkGetPointer(), screen, point.x, point.y);
+ return NS_OK;
+ }
+ case NativeMouseMessage::EnterWindow:
+ case NativeMouseMessage::LeaveWindow:
+ MOZ_ASSERT_UNREACHABLE("Non supported mouse event on Linux");
+ return NS_ERROR_INVALID_ARG;
+ }
+ return NS_ERROR_UNEXPECTED;
+}
+
+void nsWindow::CreateAndPutGdkScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY) {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_SCROLL;
+ event.scroll.window = mGdkWindow;
+ event.scroll.time = GDK_CURRENT_TIME;
+ // Get device for event source
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ // See note in nsWindow::SynthesizeNativeTouchpadPan about the device we use
+ // here.
+ event.scroll.device = gdk_device_manager_get_client_pointer(device_manager);
+ event.scroll.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.scroll.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.scroll.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.scroll.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ // The delta values are backwards on Linux compared to Windows and Cocoa,
+ // hence the negation.
+ event.scroll.direction = GDK_SCROLL_SMOOTH;
+ event.scroll.delta_x = -aDeltaX;
+ event.scroll.delta_y = -aDeltaY;
+
+ gdk_event_put(&event);
+}
+
+nsresult nsWindow::SynthesizeNativeMouseScrollEvent(
+ mozilla::LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage,
+ double aDeltaX, double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mousescrollevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpoint");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ static std::map<uint32_t, GdkEventSequence*> sKnownPointers;
+
+ auto result = sKnownPointers.find(aPointerId);
+ switch (aPointerState) {
+ case TOUCH_CONTACT:
+ if (result == sKnownPointers.end()) {
+ // GdkEventSequence isn't a thing we can instantiate, and never gets
+ // dereferenced in the gtk code. It's an opaque pointer, the only
+ // requirement is that it be distinct from other instances of
+ // GdkEventSequence*.
+ event.touch.sequence = (GdkEventSequence*)((uintptr_t)aPointerId);
+ sKnownPointers[aPointerId] = event.touch.sequence;
+ event.type = GDK_TOUCH_BEGIN;
+ } else {
+ event.touch.sequence = result->second;
+ event.type = GDK_TOUCH_UPDATE;
+ }
+ break;
+ case TOUCH_REMOVE:
+ event.type = GDK_TOUCH_END;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-end for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_CANCEL:
+ event.type = GDK_TOUCH_CANCEL;
+ if (result == sKnownPointers.end()) {
+ NS_WARNING("Tried to synthesize touch-cancel for unknown pointer!");
+ return NS_ERROR_UNEXPECTED;
+ }
+ event.touch.sequence = result->second;
+ sKnownPointers.erase(result);
+ break;
+ case TOUCH_HOVER:
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ event.touch.window = mGdkWindow;
+ event.touch.time = GDK_CURRENT_TIME;
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.touch.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ event.touch.x_root = DevicePixelsToGdkCoordRoundDown(aPoint.x);
+ event.touch.y_root = DevicePixelsToGdkCoordRoundDown(aPoint.y);
+
+ LayoutDeviceIntPoint pointInWindow = aPoint - WidgetToScreenOffset();
+ event.touch.x = DevicePixelsToGdkCoordRoundDown(pointInWindow.x);
+ event.touch.y = DevicePixelsToGdkCoordRoundDown(pointInWindow.y);
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchPadPinch(
+ TouchpadGesturePhase aEventPhase, float aScale, LayoutDeviceIntPoint aPoint,
+ int32_t aModifierFlags) {
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+
+ GdkEventTouchpadPinch* touchpad_event =
+ reinterpret_cast<GdkEventTouchpadPinch*>(&event);
+ touchpad_event->type = GDK_TOUCHPAD_PINCH;
+
+ const ScreenIntPoint widgetToScreenOffset = ViewAs<ScreenPixel>(
+ WidgetToScreenOffset(),
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent);
+
+ ScreenPoint pointInWindow =
+ ViewAs<ScreenPixel>(
+ aPoint,
+ PixelCastJustification::LayoutDeviceIsScreenForUntransformedEvent) -
+ widgetToScreenOffset;
+
+ gdouble dx = 0, dy = 0;
+
+ switch (aEventPhase) {
+ case PHASE_BEGIN:
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_BEGIN;
+ mCurrentSynthesizedTouchpadPinch = {pointInWindow, pointInWindow};
+ break;
+ case PHASE_UPDATE:
+ dx = pointInWindow.x - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.x;
+ dy = pointInWindow.y - mCurrentSynthesizedTouchpadPinch.mCurrentFocus.y;
+ mCurrentSynthesizedTouchpadPinch.mCurrentFocus = pointInWindow;
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_UPDATE;
+ break;
+ case PHASE_END:
+ touchpad_event->phase = GDK_TOUCHPAD_GESTURE_PHASE_END;
+ break;
+
+ default:
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ touchpad_event->window = mGdkWindow;
+ // We only set the fields of GdkEventTouchpadPinch which are
+ // actually used in OnTouchpadPinchEvent().
+ // GdkEventTouchpadPinch has additional fields.
+ // If OnTouchpadPinchEvent() is changed to use other fields, this function
+ // will need to change to set them as well.
+ touchpad_event->time = GDK_CURRENT_TIME;
+ touchpad_event->scale = aScale;
+ touchpad_event->x_root = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.x +
+ ScreenCoord(widgetToScreenOffset.x));
+ touchpad_event->y_root = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.y +
+ ScreenCoord(widgetToScreenOffset.y));
+
+ touchpad_event->x = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.x);
+ touchpad_event->y = DevicePixelsToGdkCoordRoundDown(
+ mCurrentSynthesizedTouchpadPinch.mBeginFocus.y);
+
+ touchpad_event->dx = dx;
+ touchpad_event->dy = dy;
+
+ touchpad_event->state = aModifierFlags;
+
+ gdk_event_put(&event);
+
+ return NS_OK;
+}
+
+nsresult nsWindow::SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "touchpadpanevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ // This should/could maybe send GdkEventTouchpadSwipe events, however we don't
+ // currently consume those (either real user input or testing events). So we
+ // send gdk scroll events to be more like what we do for real user input. If
+ // we start consuming GdkEventTouchpadSwipe and get those hooked up to swipe
+ // to nav, then maybe we should test those too.
+
+ mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase = Some(aEventPhase);
+ MOZ_ASSERT(mCurrentSynthesizedTouchpadPan.mSavedObserver == 0);
+ mCurrentSynthesizedTouchpadPan.mSavedObserver = notifier.SaveObserver();
+
+ // Note that CreateAndPutGdkScrollEvent sets the device source for the created
+ // event as the "client pointer" (a kind of default device) which will
+ // probably be of type mouse. We would ideally want to set the device of the
+ // created event to be a touchpad, but the system might not have a touchpad.
+ // To get around this we use
+ // mCurrentSynthesizedTouchpadPan.mTouchpadGesturePhase being something to
+ // indicate that we should treat the source of the event as touchpad in
+ // OnScrollEvent.
+ CreateAndPutGdkScrollEvent(aPoint, aDeltaX, aDeltaY);
+
+ return NS_OK;
+}
+
+nsWindow::GtkWindowDecoration nsWindow::GetSystemGtkWindowDecoration() {
+ static GtkWindowDecoration sGtkWindowDecoration = [] {
+ // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
+ if (const char* decorationOverride =
+ getenv("MOZ_GTK_TITLEBAR_DECORATION")) {
+ if (strcmp(decorationOverride, "none") == 0) {
+ return GTK_DECORATION_NONE;
+ }
+ if (strcmp(decorationOverride, "client") == 0) {
+ return GTK_DECORATION_CLIENT;
+ }
+ if (strcmp(decorationOverride, "system") == 0) {
+ return GTK_DECORATION_SYSTEM;
+ }
+ }
+
+ // nsWindow::GetSystemGtkWindowDecoration can be called from various
+ // threads so we can't use gfxPlatformGtk here.
+ if (GdkIsWaylandDisplay()) {
+ return GTK_DECORATION_CLIENT;
+ }
+
+ // GTK_CSD forces CSD mode - use also CSD because window manager
+ // decorations does not work with CSD.
+ // We check GTK_CSD as well as gtk_window_should_use_csd() does.
+ if (const char* csdOverride = getenv("GTK_CSD")) {
+ return *csdOverride == '0' ? GTK_DECORATION_NONE : GTK_DECORATION_CLIENT;
+ }
+
+ // TODO: Consider switching this to GetDesktopEnvironmentIdentifier().
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (!currentDesktop) {
+ return GTK_DECORATION_NONE;
+ }
+ if (strstr(currentDesktop, "i3")) {
+ return GTK_DECORATION_NONE;
+ }
+
+ // Tested desktops: pop:GNOME, KDE, Enlightenment, LXDE, openbox, MATE,
+ // X-Cinnamon, Pantheon, Deepin, GNOME, LXQt, Unity.
+ return GTK_DECORATION_CLIENT;
+ }();
+ return sGtkWindowDecoration;
+}
+
+bool nsWindow::TitlebarUseShapeMask() {
+ static int useShapeMask = []() {
+ // Don't use titlebar shape mask on Wayland
+ if (!GdkIsX11Display()) {
+ return false;
+ }
+
+ // We can't use shape masks on Mutter/X.org as we can't resize Firefox
+ // window there (Bug 1530252).
+ if (IsGnomeDesktopEnvironment()) {
+ return false;
+ }
+
+ return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
+ }();
+ return useShapeMask;
+}
+
+int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkCeiledScaleFactor(); }
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ nsCString displayName;
+
+ LOG("nsWindow::GetCompositorWidgetInitData");
+
+ *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
+ GetX11Window(), displayName, GetShapedState(), GdkIsX11Display(),
+ GetClientSize());
+
+#ifdef MOZ_X11
+ if (GdkIsX11Display()) {
+ // Make sure the window XID is propagated to X server, we can fail otherwise
+ // in GPU process (Bug 1401634).
+ Display* display = DefaultXDisplay();
+ XFlush(display);
+ displayName = nsCString(XDisplayString(display));
+ }
+#endif
+}
+
+#ifdef MOZ_X11
+/* XApp progress support currently works by setting a property
+ * on a window with this Atom name. A supporting window manager
+ * will notice this and pass it along to whatever handling has
+ * been implemented on that end (e.g. passing it on to a taskbar
+ * widget.) There is no issue if WM support is lacking, this is
+ * simply ignored in that case.
+ *
+ * See https://github.com/linuxmint/xapps/blob/master/libxapp/xapp-gtk-window.c
+ * for further details.
+ */
+
+# define PROGRESS_HINT "_NET_WM_XAPP_PROGRESS"
+
+static void set_window_hint_cardinal(Window xid, const gchar* atom_name,
+ gulong cardinal) {
+ GdkDisplay* display;
+
+ display = gdk_display_get_default();
+
+ if (cardinal > 0) {
+ XChangeProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name),
+ XA_CARDINAL, 32, PropModeReplace, (guchar*)&cardinal, 1);
+ } else {
+ XDeleteProperty(GDK_DISPLAY_XDISPLAY(display), xid,
+ gdk_x11_get_xatom_by_name_for_display(display, atom_name));
+ }
+}
+#endif // MOZ_X11
+
+void nsWindow::SetProgress(unsigned long progressPercent) {
+#ifdef MOZ_X11
+
+ if (!GdkIsX11Display()) {
+ return;
+ }
+
+ if (!mShell) {
+ return;
+ }
+
+ progressPercent = MIN(progressPercent, 100);
+
+ set_window_hint_cardinal(GDK_WINDOW_XID(GetToplevelGdkWindow()),
+ PROGRESS_HINT, progressPercent);
+#endif // MOZ_X11
+}
+
+#ifdef MOZ_X11
+void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
+ if (!GdkIsX11Display()) {
+ return;
+ }
+
+ gulong value = aState;
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ gdk_property_change(GetToplevelGdkWindow(),
+ gdk_atom_intern("_NET_WM_BYPASS_COMPOSITOR", FALSE),
+ cardinal_atom,
+ 32, // format
+ GDK_PROP_MODE_REPLACE, (guchar*)&value, 1);
+}
+#endif
+
+nsresult nsWindow::SetSystemFont(const nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_set(settings, "gtk-font-name", aFontName.get(), nullptr);
+ return NS_OK;
+}
+
+nsresult nsWindow::GetSystemFont(nsCString& aFontName) {
+ GtkSettings* settings = gtk_settings_get_default();
+ gchar* fontName = nullptr;
+ g_object_get(settings, "gtk-font-name", &fontName, nullptr);
+ if (fontName) {
+ aFontName.Assign(fontName);
+ g_free(fontName);
+ }
+ return NS_OK;
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateTopLevelWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+already_AddRefed<nsIWidget> nsIWidget::CreateChildWindow() {
+ nsCOMPtr<nsIWidget> window = new nsWindow();
+ return window.forget();
+}
+
+#ifdef MOZ_WAYLAND
+static void relative_pointer_handle_relative_motion(
+ void* data, struct zwp_relative_pointer_v1* pointer, uint32_t time_hi,
+ uint32_t time_lo, wl_fixed_t dx_w, wl_fixed_t dy_w, wl_fixed_t dx_unaccel_w,
+ wl_fixed_t dy_unaccel_w) {
+ RefPtr<nsWindow> window(reinterpret_cast<nsWindow*>(data));
+
+ WidgetMouseEvent event(true, eMouseMove, window, WidgetMouseEvent::eReal);
+
+ double scale = window->FractionalScaleFactor();
+ event.mRefPoint = window->GetNativePointerLockCenter();
+ event.mRefPoint.x += int(wl_fixed_to_double(dx_w) * scale);
+ event.mRefPoint.y += int(wl_fixed_to_double(dy_w) * scale);
+
+ event.AssignEventTime(window->GetWidgetEventTime(time_lo));
+ window->DispatchInputEvent(&event);
+}
+
+static const struct zwp_relative_pointer_v1_listener relative_pointer_listener =
+ {
+ relative_pointer_handle_relative_motion,
+};
+
+void nsWindow::SetNativePointerLockCenter(
+ const LayoutDeviceIntPoint& aLockCenter) {
+ mNativePointerLockCenter = aLockCenter;
+}
+
+void nsWindow::LockNativePointer() {
+ if (!GdkIsWaylandDisplay()) {
+ return;
+ }
+
+ auto* waylandDisplay = WaylandDisplayGet();
+
+ auto* pointerConstraints = waylandDisplay->GetPointerConstraints();
+ if (!pointerConstraints) {
+ return;
+ }
+
+ auto* relativePointerMgr = waylandDisplay->GetRelativePointerManager();
+ if (!relativePointerMgr) {
+ return;
+ }
+
+ GdkDisplay* display = gdk_display_get_default();
+
+ GdkDeviceManager* manager = gdk_display_get_device_manager(display);
+ MOZ_ASSERT(manager);
+
+ GdkDevice* device = gdk_device_manager_get_client_pointer(manager);
+ if (!device) {
+ NS_WARNING("Could not find Wayland pointer to lock");
+ return;
+ }
+ wl_pointer* pointer = gdk_wayland_device_get_wl_pointer(device);
+ MOZ_ASSERT(pointer);
+
+ wl_surface* surface =
+ gdk_wayland_window_get_wl_surface(GetToplevelGdkWindow());
+ if (!surface) {
+ /* Can be null when the window is hidden.
+ * Though it's unlikely that a lock request comes in that case, be
+ * defensive. */
+ return;
+ }
+
+ if (mLockedPointer || mRelativePointer) {
+ UnlockNativePointer();
+ }
+
+ mLockedPointer = zwp_pointer_constraints_v1_lock_pointer(
+ pointerConstraints, surface, pointer, nullptr,
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT);
+ if (!mLockedPointer) {
+ NS_WARNING("Could not lock Wayland pointer");
+ return;
+ }
+
+ mRelativePointer = zwp_relative_pointer_manager_v1_get_relative_pointer(
+ relativePointerMgr, pointer);
+ if (!mRelativePointer) {
+ NS_WARNING("Could not create relative Wayland pointer");
+ zwp_locked_pointer_v1_destroy(mLockedPointer);
+ mLockedPointer = nullptr;
+ return;
+ }
+
+ zwp_relative_pointer_v1_add_listener(mRelativePointer,
+ &relative_pointer_listener, this);
+}
+
+void nsWindow::UnlockNativePointer() {
+ if (!GdkIsWaylandDisplay()) {
+ return;
+ }
+ if (mRelativePointer) {
+ zwp_relative_pointer_v1_destroy(mRelativePointer);
+ mRelativePointer = nullptr;
+ }
+ if (mLockedPointer) {
+ zwp_locked_pointer_v1_destroy(mLockedPointer);
+ mLockedPointer = nullptr;
+ }
+}
+#endif
+
+bool nsWindow::GetTopLevelWindowActiveState(nsIFrame* aFrame) {
+ // Used by window frame and button box rendering. We can end up in here in
+ // the content process when rendering one of these moz styles freely in a
+ // page. Fail in this case, there is no applicable window focus state.
+ if (!XRE_IsParentProcess()) {
+ return false;
+ }
+ // All headless windows are considered active so they are painted.
+ if (gfxPlatform::IsHeadless()) {
+ return true;
+ }
+ // Get the widget. nsIFrame's GetNearestWidget walks up the view chain
+ // until it finds a real window.
+ nsWindow* window = static_cast<nsWindow*>(aFrame->GetNearestWidget());
+ if (!window) {
+ return false;
+ }
+ return !window->mTitlebarBackdropState;
+}
+
+static nsIFrame* FindTitlebarFrame(nsIFrame* aFrame) {
+ for (nsIFrame* childFrame : aFrame->PrincipalChildList()) {
+ StyleAppearance appearance =
+ childFrame->StyleDisplay()->EffectiveAppearance();
+ if (appearance == StyleAppearance::MozWindowTitlebar ||
+ appearance == StyleAppearance::MozWindowTitlebarMaximized) {
+ return childFrame;
+ }
+
+ if (nsIFrame* foundFrame = FindTitlebarFrame(childFrame)) {
+ return foundFrame;
+ }
+ }
+ return nullptr;
+}
+
+nsIFrame* nsWindow::GetFrame() const {
+ nsView* view = nsView::GetViewFor(this);
+ if (!view) {
+ return nullptr;
+ }
+ return view->GetFrame();
+}
+
+void nsWindow::UpdateMozWindowActive() {
+ // Update activation state for the :-moz-window-inactive pseudoclass.
+ // Normally, this follows focus; we override it here to follow
+ // GDK_WINDOW_STATE_FOCUSED.
+ if (mozilla::dom::Document* document = GetDocument()) {
+ if (nsPIDOMWindowOuter* window = document->GetWindow()) {
+ if (RefPtr<mozilla::dom::BrowsingContext> bc =
+ window->GetBrowsingContext()) {
+ bc->SetIsActiveBrowserWindow(!mTitlebarBackdropState);
+ }
+ }
+ }
+}
+
+void nsWindow::ForceTitlebarRedraw() {
+ MOZ_ASSERT(mDrawInTitlebar, "We should not redraw invisible titlebar.");
+
+ if (!mWidgetListener || !mWidgetListener->GetPresShell()) {
+ return;
+ }
+
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ return;
+ }
+
+ frame = FindTitlebarFrame(frame);
+ if (frame) {
+ nsIContent* content = frame->GetContent();
+ if (content) {
+ nsLayoutUtils::PostRestyleEvent(content->AsElement(), RestyleHint{0},
+ nsChangeHint_RepaintFrame);
+ }
+ }
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (!gUseAspectRatio) {
+ return;
+ }
+
+ if (aShouldLock) {
+ int decWidth = 0, decHeight = 0;
+ AddCSDDecorationSize(&decWidth, &decHeight);
+
+ float width =
+ DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.width) + decWidth;
+ float height =
+ DevicePixelsToGdkCoordRoundDown(mLastSizeRequest.height) + decHeight;
+
+ mAspectRatio = width / height;
+ LOG("nsWindow::LockAspectRatio() width %f height %f aspect %f", width,
+ height, mAspectRatio);
+ } else {
+ mAspectRatio = 0.0;
+ LOG("nsWindow::LockAspectRatio() removed aspect ratio");
+ }
+
+ ApplySizeConstraints();
+}
+
+nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
+
+#ifdef MOZ_WAYLAND
+bool nsWindow::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (!GdkIsWaylandDisplay()) {
+ return true;
+ }
+
+ // SetEGLNativeWindowSize() is called from renderer/compositor thread,
+ // make sure nsWindow is not destroyed.
+ bool paint = false;
+
+ // See NS_NATIVE_EGL_WINDOW why we can't block here.
+ if (mDestroyMutex.TryLock()) {
+ if (!mIsDestroyed) {
+ gint scale = GdkCeiledScaleFactor();
+# ifdef MOZ_LOGGING
+ if (LOG_ENABLED()) {
+ static uintptr_t lastSizeLog = 0;
+ uintptr_t sizeLog = uintptr_t(this) + aEGLWindowSize.width +
+ aEGLWindowSize.height + scale +
+ aEGLWindowSize.width / scale +
+ aEGLWindowSize.height / scale;
+ if (lastSizeLog != sizeLog) {
+ lastSizeLog = sizeLog;
+ LOG("nsWindow::SetEGLNativeWindowSize() %d x %d scale %d (unscaled "
+ "%d x "
+ "%d)",
+ aEGLWindowSize.width, aEGLWindowSize.height, scale,
+ aEGLWindowSize.width / scale, aEGLWindowSize.height / scale);
+ }
+ }
+# endif
+ paint = moz_container_wayland_egl_window_set_size(
+ mContainer, aEGLWindowSize.ToUnknownSize(), scale);
+ }
+ mDestroyMutex.Unlock();
+ }
+ return paint;
+}
+#endif
+
+nsWindow* nsWindow::GetWindow(GdkWindow* window) {
+ return get_window_for_gdk_window(window);
+}
+
+void nsWindow::ClearRenderingQueue() {
+ LOG("nsWindow::ClearRenderingQueue()");
+
+ if (mWidgetListener) {
+ mWidgetListener->RequestWindowClose(this);
+ }
+ DestroyLayerManager();
+}
+
+void nsWindow::DisableRendering() {
+ LOG("nsWindow::DisableRendering()");
+
+ if (mGdkWindow) {
+ if (mIMContext) {
+ mIMContext->SetGdkWindow(nullptr);
+ }
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ mGdkWindow = nullptr;
+ }
+
+#ifdef MOZ_WAYLAND
+ // Widget is backed by OpenGL EGLSurface created over wl_surface
+ // owned by mContainer.
+ // RenderCompositorEGL::Resume() deletes recent EGLSurface based on
+ // wl_surface owned by mContainer and creates a new fallback EGLSurface.
+ // Then we can delete wl_surface in moz_container_wayland_unmap().
+ // We don't want to pause compositor as it may lead to whole
+ // browser freeze (Bug 1777664).
+ ///
+ // We don't need to do such operation for SW backend as
+ // WindowSurfaceWaylandMB::Commit() gets wl_surface from
+ // MozContainer every commit.
+ if (moz_container_wayland_has_egl_window(mContainer) &&
+ mCompositorWidgetDelegate) {
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ // Call DisableRendering() to make GtkCompositorWidget hidden.
+ // Then SendResume() will create fallback EGLSurface, see
+ // GLContextEGL::CreateEGLSurfaceForCompositorWidget().
+ mCompositorWidgetDelegate->DisableRendering();
+ remoteRenderer->SendResume();
+ mCompositorWidgetDelegate->EnableRendering(GetX11Window(),
+ GetShapedState());
+ }
+ }
+#endif
+}
+
+// Apply workaround for Mutter compositor bug (mzbz#1777269).
+//
+// When we open a popup window (tooltip for instance) attached to
+// GDK_WINDOW_TYPE_HINT_UTILITY parent popup, Mutter compositor sends bogus
+// leave/enter events to the GDK_WINDOW_TYPE_HINT_UTILITY popup.
+// That leads to immediate tooltip close. As a workaround ignore these
+// bogus events.
+//
+// We need to check two affected window types:
+//
+// - toplevel window with at least two child popups where the first one is
+// GDK_WINDOW_TYPE_HINT_UTILITY.
+// - GDK_WINDOW_TYPE_HINT_UTILITY popup with a child popup
+//
+// We need to mask two bogus leave/enter sequences:
+// 1) Leave (popup) -> Enter (toplevel)
+// 2) Leave (toplevel) -> Enter (popup)
+//
+// TODO: persistent (non-tracked) popups with tooltip/child popups?
+//
+bool nsWindow::ApplyEnterLeaveMutterWorkaround() {
+ // Leave (toplevel) case
+ if (mWindowType == WindowType::TopLevel && mWaylandPopupNext &&
+ mWaylandPopupNext->mWaylandPopupNext &&
+ gtk_window_get_type_hint(GTK_WINDOW(mWaylandPopupNext->GetGtkWidget())) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY) {
+ LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave toplevel");
+ return true;
+ }
+ // Leave (popup) case
+ if (IsWaylandPopup() && mWaylandPopupNext &&
+ gtk_window_get_type_hint(GTK_WINDOW(mShell)) ==
+ GDK_WINDOW_TYPE_HINT_UTILITY) {
+ LOG("nsWindow::ApplyEnterLeaveMutterWorkaround(): leave popup");
+ return true;
+ }
+ return false;
+}
+
+void nsWindow::NotifyOcclusionState(OcclusionState aState) {
+ if (!IsTopLevelWindowType()) {
+ return;
+ }
+
+ bool isFullyOccluded = aState == OcclusionState::OCCLUDED;
+ if (mIsFullyOccluded == isFullyOccluded) {
+ return;
+ }
+ mIsFullyOccluded = isFullyOccluded;
+
+ LOG("nsWindow::NotifyOcclusionState() mIsFullyOccluded %d", mIsFullyOccluded);
+ if (mWidgetListener) {
+ mWidgetListener->OcclusionStateChanged(mIsFullyOccluded);
+ }
+}
+
+void nsWindow::SetDragSource(GdkDragContext* aSourceDragContext) {
+ mSourceDragContext = aSourceDragContext;
+ if (IsPopup() &&
+ (widget::GdkIsWaylandDisplay() || widget::IsXWaylandProtocol())) {
+ if (auto* menuPopupFrame = GetMenuPopupFrame(GetFrame())) {
+ menuPopupFrame->SetIsDragSource(!!aSourceDragContext);
+ }
+ }
+}
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
new file mode 100644
index 0000000000..e235d12c08
--- /dev/null
+++ b/widget/gtk/nsWindow.h
@@ -0,0 +1,1016 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim:expandtab:shiftwidth=2:tabstop=2:
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef __nsWindow_h__
+#define __nsWindow_h__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+
+#include "CompositorWidget.h"
+#include "MozContainer.h"
+#include "VsyncSource.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/widget/WindowSurfaceProvider.h"
+#include "nsBaseWidget.h"
+#include "nsGkAtoms.h"
+#include "nsIDragService.h"
+#include "nsRefPtrHashtable.h"
+#include "IMContextWrapper.h"
+#include "LookAndFeel.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/LocalAccessible.h"
+#endif
+
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+#endif
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include "base/thread.h"
+# include "WaylandVsyncSource.h"
+# include "nsClipboardWayland.h"
+#endif
+
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+
+extern mozilla::LazyLogModule gWidgetLog;
+extern mozilla::LazyLogModule gWidgetDragLog;
+extern mozilla::LazyLogModule gWidgetPopupLog;
+extern mozilla::LazyLogModule gWidgetVsync;
+
+# define LOG(str, ...) \
+ MOZ_LOG(IsPopup() ? gWidgetPopupLog : gWidgetLog, \
+ mozilla::LogLevel::Debug, \
+ ("%s: " str, GetDebugTag().get(), ##__VA_ARGS__))
+# define LOGW(...) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOGDRAG(...) \
+ MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOG_POPUP(...) \
+ MOZ_LOG(gWidgetPopupLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOG_VSYNC(...) \
+ MOZ_LOG(gWidgetVsync, mozilla::LogLevel::Debug, (__VA_ARGS__))
+# define LOG_ENABLED() \
+ (MOZ_LOG_TEST(gWidgetPopupLog, mozilla::LogLevel::Debug) || \
+ MOZ_LOG_TEST(gWidgetLog, mozilla::LogLevel::Debug))
+
+#else
+
+# define LOG(...)
+# define LOGW(...)
+# define LOGDRAG(...)
+# define LOG_POPUP(...)
+# define LOG_ENABLED() false
+
+#endif /* MOZ_LOGGING */
+
+#if defined(MOZ_WAYLAND) && !defined(MOZ_X11)
+typedef uintptr_t Window;
+#endif
+
+class gfxPattern;
+class nsIFrame;
+#if !GTK_CHECK_VERSION(3, 18, 0)
+struct _GdkEventTouchpadPinch;
+typedef struct _GdkEventTouchpadPinch GdkEventTouchpadPinch;
+#endif
+
+#if !GTK_CHECK_VERSION(3, 22, 0)
+typedef enum {
+ GDK_ANCHOR_FLIP_X = 1 << 0,
+ GDK_ANCHOR_FLIP_Y = 1 << 1,
+ GDK_ANCHOR_SLIDE_X = 1 << 2,
+ GDK_ANCHOR_SLIDE_Y = 1 << 3,
+ GDK_ANCHOR_RESIZE_X = 1 << 4,
+ GDK_ANCHOR_RESIZE_Y = 1 << 5,
+ GDK_ANCHOR_FLIP = GDK_ANCHOR_FLIP_X | GDK_ANCHOR_FLIP_Y,
+ GDK_ANCHOR_SLIDE = GDK_ANCHOR_SLIDE_X | GDK_ANCHOR_SLIDE_Y,
+ GDK_ANCHOR_RESIZE = GDK_ANCHOR_RESIZE_X | GDK_ANCHOR_RESIZE_Y
+} GdkAnchorHints;
+#endif
+
+namespace mozilla {
+enum class NativeKeyBindingsType : uint8_t;
+
+class TimeStamp;
+#ifdef MOZ_X11
+class CurrentX11TimeGetter;
+#endif
+
+namespace widget {
+class Screen;
+} // namespace widget
+} // namespace mozilla
+
+class nsWindow final : public nsBaseWidget {
+ public:
+ typedef mozilla::gfx::DrawTarget DrawTarget;
+ typedef mozilla::WidgetEventTime WidgetEventTime;
+ typedef mozilla::WidgetKeyboardEvent WidgetKeyboardEvent;
+ typedef mozilla::widget::PlatformCompositorWidgetDelegate
+ PlatformCompositorWidgetDelegate;
+
+ nsWindow();
+
+ static void ReleaseGlobals();
+
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsWindow, nsBaseWidget)
+
+ nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ // called when we are destroyed
+ void OnDestroy() override;
+
+ // called to check and see if a widget's dimensions are sane
+ bool AreBoundsSane(void);
+
+ // nsIWidget
+ using nsBaseWidget::Create; // for Create signature not overridden here
+ [[nodiscard]] nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ InitData* aInitData) override;
+ void Destroy() override;
+ nsIWidget* GetParent() override;
+ float GetDPI() override;
+ double GetDefaultScaleInternal() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScaleByScreen()
+ override;
+ void SetParent(nsIWidget* aNewParent) override;
+ void SetModal(bool aModal) override;
+ bool IsVisible() const override;
+ bool IsMapped() const override;
+ void ConstrainPosition(DesktopIntPoint&) override;
+ void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ void LockAspectRatio(bool aShouldLock) override;
+ void Move(double aX, double aY) override;
+ void Show(bool aState) override;
+ void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ bool IsEnabled() const override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ nsSizeMode SizeMode() override { return mSizeMode; }
+ void SetSizeMode(nsSizeMode aMode) override;
+ void GetWorkspaceID(nsAString& workspaceID) override;
+ void MoveToWorkspace(const nsAString& workspaceID) override;
+ void Enable(bool aState) override;
+ void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ LayoutDeviceIntRect GetScreenBounds() override;
+ LayoutDeviceIntRect GetClientBounds() override;
+ LayoutDeviceIntSize GetClientSize() override;
+ LayoutDeviceIntPoint GetClientOffset() override { return mClientOffset; }
+ LayoutDeviceIntPoint GetScreenEdgeSlop() override;
+
+ // Recomputes the client offset according to our current window position.
+ // If aNotify is true, NotifyWindowMoved will be called on client offset
+ // changes.
+ //
+ // NOTE(emilio): It seems that as long any change here update either the size
+ // or the position of the window, we should be doing fine without notifying,
+ // but this is done to preserve existing behavior.
+ void RecomputeClientOffset(bool aNotify);
+
+ void SetCursor(const Cursor&) override;
+ void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ void* GetNativeData(uint32_t aDataType) override;
+ nsresult SetTitle(const nsAString& aTitle) override;
+ void SetIcon(const nsAString& aIconSpec) override;
+ void SetWindowClass(const nsAString& xulWinType, const nsAString& xulWinClass,
+ const nsAString& xulWinName) override;
+ LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ void CaptureRollupEvents(bool aDoCapture) override;
+ [[nodiscard]] nsresult GetAttention(int32_t aCycleCount) override;
+ bool HasPendingInputEvent() override;
+
+ bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration, nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ already_AddRefed<Screen> GetWidgetScreen() override;
+ nsresult MakeFullScreen(bool aFullScreen) override;
+ void HideWindowChrome(bool aShouldHide) override;
+
+ /**
+ * GetLastUserInputTime returns a timestamp for the most recent user input
+ * event. This is intended for pointer grab requests (including drags).
+ */
+ static guint32 GetLastUserInputTime();
+
+ // utility method, -1 if no change should be made, otherwise returns a
+ // value that can be passed to gdk_window_set_decorations
+ gint ConvertBorderStyles(BorderStyle aStyle);
+
+ mozilla::widget::IMContextWrapper* GetIMContext() const { return mIMContext; }
+
+ bool DispatchCommandEvent(nsAtom* aCommand);
+ bool DispatchContentCommandEvent(mozilla::EventMessage aMsg);
+
+ // event callbacks
+ gboolean OnExposeEvent(cairo_t* cr);
+ gboolean OnConfigureEvent(GtkWidget* aWidget, GdkEventConfigure* aEvent);
+ void OnMap();
+ void OnUnmap();
+ void OnSizeAllocate(GtkAllocation* aAllocation);
+ void OnDeleteEvent();
+ void OnEnterNotifyEvent(GdkEventCrossing* aEvent);
+ void OnLeaveNotifyEvent(GdkEventCrossing* aEvent);
+ void OnMotionNotifyEvent(GdkEventMotion* aEvent);
+ void OnButtonPressEvent(GdkEventButton* aEvent);
+ void OnButtonReleaseEvent(GdkEventButton* aEvent);
+ void OnContainerFocusInEvent(GdkEventFocus* aEvent);
+ void OnContainerFocusOutEvent(GdkEventFocus* aEvent);
+ gboolean OnKeyPressEvent(GdkEventKey* aEvent);
+ gboolean OnKeyReleaseEvent(GdkEventKey* aEvent);
+
+ void OnScrollEvent(GdkEventScroll* aEvent);
+
+ void OnVisibilityNotifyEvent(GdkVisibilityState aState);
+ void OnWindowStateEvent(GtkWidget* aWidget, GdkEventWindowState* aEvent);
+ void OnDragDataReceivedEvent(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ gint aX, gint aY,
+ GtkSelectionData* aSelectionData, guint aInfo,
+ guint aTime, gpointer aData);
+ gboolean OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent);
+ gboolean OnTouchEvent(GdkEventTouch* aEvent);
+ gboolean OnTouchpadPinchEvent(GdkEventTouchpadPinch* aEvent);
+
+ gint GetInputRegionMarginInGdkCoords();
+
+ void UpdateTopLevelOpaqueRegion();
+
+ already_AddRefed<mozilla::gfx::DrawTarget> StartRemoteDrawingInRegion(
+ const LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ mozilla::gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ void SetProgress(unsigned long progressPercent);
+
+ RefPtr<mozilla::VsyncDispatcher> GetVsyncDispatcher() override;
+ bool SynchronouslyRepaintOnResize() override;
+
+ void OnDPIChanged();
+ void OnCheckResize();
+ void OnCompositedChanged();
+ void OnScaleChanged(bool aNotify);
+ void DispatchResized();
+
+ static guint32 sLastButtonPressTime;
+
+ MozContainer* GetMozContainer() { return mContainer; }
+ GdkWindow* GetGdkWindow() const { return mGdkWindow; };
+ GdkWindow* GetToplevelGdkWindow() const;
+ GtkWidget* GetGtkWidget() const { return mShell; }
+ nsIFrame* GetFrame() const;
+ nsWindow* GetEffectiveParent();
+ bool IsDestroyed() const { return mIsDestroyed; }
+ bool IsPopup() const;
+ bool IsWaylandPopup() const;
+ bool IsDragPopup() { return mIsDragPopup; };
+
+ nsAutoCString GetDebugTag() const;
+
+ void DispatchDragEvent(mozilla::EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint, guint aTime);
+ static void UpdateDragStatus(GdkDragContext* aDragContext,
+ nsIDragService* aDragService);
+ void SetDragSource(GdkDragContext* aSourceDragContext);
+
+ WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
+ mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
+#ifdef MOZ_X11
+ mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
+#endif
+
+ void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ InputContext GetInputContext() override;
+ TextEventDispatcherListener* GetNativeTextEventDispatcherListener() override;
+ MOZ_CAN_RUN_SCRIPT bool GetEditCommands(
+ mozilla::NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+
+ // These methods are for toplevel windows only.
+ void ResizeTransparencyBitmap();
+ void ApplyTransparencyBitmap();
+ void ClearTransparencyBitmap();
+
+ void SetTransparencyMode(TransparencyMode aMode) override;
+ TransparencyMode GetTransparencyMode() override;
+ void SetInputRegion(const InputRegion&) override;
+ nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride);
+ void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ void UpdateTitlebarTransparencyBitmap();
+
+ nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ NativeMouseMessage aNativeMessage,
+ mozilla::MouseButton aButton,
+ nsIWidget::Modifiers aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override {
+ return SynthesizeNativeMouseEvent(
+ aPoint, NativeMouseMessage::Move, mozilla::MouseButton::eNotPressed,
+ nsIWidget::Modifiers::NO_MODIFIERS, aObserver);
+ }
+
+ nsresult SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+ nsresult SynthesizeNativeTouchPadPinch(TouchpadGesturePhase aEventPhase,
+ float aScale,
+ LayoutDeviceIntPoint aPoint,
+ int32_t aModifierFlags) override;
+
+ nsresult SynthesizeNativeTouchpadPan(TouchpadGesturePhase aEventPhase,
+ LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY,
+ int32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+
+ nsresult SetNonClientMargins(const LayoutDeviceIntMargin&) override;
+ void SetDrawsInTitlebar(bool aState);
+ void SetTitlebarRect();
+ mozilla::LayoutDeviceIntCoord GetTitlebarRadius();
+ LayoutDeviceIntRect GetTitlebarRect();
+ void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) override;
+
+ // HiDPI scale conversion
+ gint GdkCeiledScaleFactor();
+ double FractionalScaleFactor();
+
+ // To GDK
+ gint DevicePixelsToGdkCoordRoundUp(int);
+ gint DevicePixelsToGdkCoordRoundDown(int);
+ GdkPoint DevicePixelsToGdkPointRoundDown(const LayoutDeviceIntPoint&);
+ GdkRectangle DevicePixelsToGdkSizeRoundUp(const LayoutDeviceIntSize&);
+ GdkRectangle DevicePixelsToGdkRectRoundOut(const LayoutDeviceIntRect&);
+
+ // From GDK
+ int GdkCoordToDevicePixels(gint);
+ LayoutDeviceIntPoint GdkPointToDevicePixels(const GdkPoint&);
+ LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble aX, gdouble aY);
+ LayoutDeviceIntRect GdkRectToDevicePixels(const GdkRectangle&);
+
+ bool WidgetTypeSupportsAcceleration() override;
+
+ nsresult SetSystemFont(const nsCString& aFontName) override;
+ nsresult GetSystemFont(nsCString& aFontName) override;
+
+ typedef enum {
+ GTK_DECORATION_SYSTEM, // CSD including shadows
+ GTK_DECORATION_CLIENT, // CSD without shadows
+ GTK_DECORATION_NONE, // WM does not support CSD at all
+ } GtkWindowDecoration;
+ /**
+ * Get the support of Client Side Decoration by checking the desktop
+ * environment.
+ */
+ static GtkWindowDecoration GetSystemGtkWindowDecoration();
+
+ static bool GetTopLevelWindowActiveState(nsIFrame* aFrame);
+ static bool TitlebarUseShapeMask();
+ bool IsRemoteContent() { return HasRemoteContent(); }
+ void NativeMoveResizeWaylandPopupCallback(const GdkRectangle* aFinalSize,
+ bool aFlippedX, bool aFlippedY);
+ static bool IsToplevelWindowTransparent();
+
+ static nsWindow* GetFocusedWindow();
+
+#ifdef MOZ_WAYLAND
+ // Use xdg-activation protocol to transfer focus from gFocusWindow to aWindow.
+ static void TransferFocusToWaylandWindow(nsWindow* aWindow);
+ void FocusWaylandWindow(const char* aTokenID);
+
+ bool GetCSDDecorationOffset(int* aDx, int* aDy);
+ bool SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+ void WaylandDragWorkaround(GdkEventButton* aEvent);
+
+ void CreateCompositorVsyncDispatcher() override;
+ LayoutDeviceIntPoint GetNativePointerLockCenter() {
+ return mNativePointerLockCenter;
+ }
+ void SetNativePointerLockCenter(
+ const LayoutDeviceIntPoint& aLockCenter) override;
+ void LockNativePointer() override;
+ void UnlockNativePointer() override;
+ LayoutDeviceIntSize GetMoveToRectPopupSize() const override {
+ return mMoveToRectPopupSize;
+ };
+#endif
+
+ typedef enum {
+ // WebRender compositor is enabled
+ COMPOSITOR_ENABLED,
+ // WebRender compositor is paused as we're repainting whole window and
+ // we're waiting for content process to update page content.
+ COMPOSITOR_PAUSED_FLICKERING
+ } WindowCompositorState;
+
+ // Pause compositor to avoid rendering artifacts from content process.
+ void ResumeCompositorImpl();
+ void ResumeCompositorFlickering();
+ void ResumeCompositorFromCompositorThread();
+ void PauseCompositorFlickering();
+ bool IsWaitingForCompositorResume();
+
+ // Force hide this window, remove compositor etc. to avoid
+ // rendering queue blocking (see Bug 1782948).
+ void ClearRenderingQueue();
+
+ void DisableRendering();
+
+ bool ApplyEnterLeaveMutterWorkaround();
+
+ void NotifyOcclusionState(mozilla::widget::OcclusionState aState) override;
+
+ static nsWindow* GetWindow(GdkWindow* window);
+
+ protected:
+ virtual ~nsWindow();
+
+ // event handling code
+ void DispatchActivateEvent(void);
+ void DispatchDeactivateEvent(void);
+ void MaybeDispatchResized();
+ void DispatchPanGesture(mozilla::PanGestureInput& aPanInput);
+
+ void RegisterTouchWindow() override;
+
+ nsCOMPtr<nsIWidget> mParent;
+ mozilla::Atomic<int, mozilla::Relaxed> mCeiledScaleFactor{1};
+ double mFractionalScaleFactor = 0.0;
+
+ void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect);
+
+ void NativeMoveResize(bool aMoved, bool aResized);
+
+ void NativeShow(bool aAction);
+ void SetHasMappedToplevel(bool aState);
+ LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize);
+
+ void DispatchContextMenuEventFromMouseEvent(
+ uint16_t domButton, GdkEventButton* aEvent,
+ const mozilla::LayoutDeviceIntPoint& aRefPoint);
+
+ void TryToShowNativeWindowMenu(GdkEventButton* aEvent);
+
+ bool DoTitlebarAction(mozilla::LookAndFeel::TitlebarEvent aEvent,
+ GdkEventButton* aButtonEvent);
+
+ void WaylandStartVsync();
+ void WaylandStopVsync();
+ void DestroyChildWindows();
+ GtkWidget* GetToplevelWidget() const;
+ nsWindow* GetContainerWindow() const;
+ Window GetX11Window();
+ bool GetShapedState();
+ void EnsureGdkWindow();
+ void SetUrgencyHint(GtkWidget* top_window, bool state);
+ void SetDefaultIcon(void);
+ void SetWindowDecoration(BorderStyle aStyle);
+ void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent,
+ const mozilla::LayoutDeviceIntPoint& aRefPoint);
+ bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup);
+ void RollupAllMenus() { CheckForRollup(0, 0, false, true); }
+ void CheckForRollupDuringGrab() { RollupAllMenus(); }
+
+ bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY);
+ nsIWidgetListener* GetListener();
+
+ nsWindow* GetTransientForWindowIfPopup();
+ bool IsHandlingTouchSequence(GdkEventSequence* aSequence);
+
+ void ResizeInt(const mozilla::Maybe<LayoutDeviceIntPoint>& aMove,
+ LayoutDeviceIntSize aSize);
+ void NativeMoveResizeWaylandPopup(bool aMove, bool aResize);
+
+ // Returns a window edge if the given point (in device pixels) is within a
+ // resizer region of the window.
+ // Only used when drawing decorations client side.
+ mozilla::Maybe<GdkWindowEdge> CheckResizerEdge(const LayoutDeviceIntPoint&);
+
+ GtkTextDirection GetTextDirection();
+
+ bool DrawsToCSDTitlebar() const;
+ void AddCSDDecorationSize(int* aWidth, int* aHeight);
+
+ void CreateAndPutGdkScrollEvent(mozilla::LayoutDeviceIntPoint aPoint,
+ double aDeltaX, double aDeltaY);
+
+ nsCString mGtkWindowAppClass;
+ nsCString mGtkWindowAppName;
+ nsCString mGtkWindowRoleName;
+ void RefreshWindowClass();
+
+ GtkWidget* mShell = nullptr;
+ MozContainer* mContainer = nullptr;
+ GdkWindow* mGdkWindow = nullptr;
+ PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate = nullptr;
+ mozilla::Atomic<WindowCompositorState, mozilla::Relaxed> mCompositorState{
+ COMPOSITOR_ENABLED};
+ // This is used in COMPOSITOR_PAUSED_FLICKERING mode only to resume compositor
+ // in some reasonable time when page content is not updated.
+ guint mCompositorPauseTimeoutID = 0;
+
+ // The actual size mode that's in effect.
+ nsSizeMode mSizeMode = nsSizeMode_Normal;
+ // The last size mode we've requested. This might not match mSizeMode if
+ // there's a request to change the size mode in progress.
+ nsSizeMode mLastSizeModeRequest = nsSizeMode_Normal;
+ nsSizeMode mLastSizeModeBeforeFullscreen = nsSizeMode_Normal;
+
+ float mAspectRatio = 0.0f;
+ float mAspectRatioSaved = 0.0f;
+ mozilla::Maybe<GtkOrientation> mAspectResizer;
+ LayoutDeviceIntPoint mLastResizePoint;
+
+ // The size requested, which might not be reflected in mBounds. Used in
+ // WaylandPopupSetDirectPosition() to remember intended size for popup
+ // positioning, in LockAspect() to remember the intended aspect ratio, and
+ // to remember a size requested while waiting for moved-to-rect when
+ // OnSizeAllocate() might change mBounds.Size().
+ LayoutDeviceIntSize mLastSizeRequest;
+ LayoutDeviceIntPoint mClientOffset;
+ // Indicates a new size that still needs to be dispatched.
+ LayoutDeviceIntSize mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
+
+ // This field omits duplicate scroll events caused by GNOME bug 726878.
+ guint32 mLastScrollEventTime = GDK_CURRENT_TIME;
+ mozilla::ScreenCoord mLastPinchEventSpan;
+
+ struct TouchpadPinchGestureState {
+ // Focus point of the PHASE_BEGIN event
+ ScreenPoint mBeginFocus;
+
+ // Focus point of the most recent PHASE_UPDATE event
+ ScreenPoint mCurrentFocus;
+ };
+
+ // Used for handling touchpad pinch gestures
+ ScreenPoint mCurrentTouchpadFocus;
+
+ // Used for synthesizing touchpad pinch gestures
+ TouchpadPinchGestureState mCurrentSynthesizedTouchpadPinch;
+
+ // Used for synthesizing touchpad pan gestures
+ struct TouchpadPanGestureState {
+ mozilla::Maybe<TouchpadGesturePhase> mTouchpadGesturePhase;
+ uint64_t mSavedObserver = 0;
+ };
+
+ // Used for synthesizing touchpad pan gestures
+ TouchpadPanGestureState mCurrentSynthesizedTouchpadPan;
+
+ // for touch event handling
+ nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
+ mTouches;
+
+ // Upper bound on pending ConfigureNotify events to be dispatched to the
+ // window. See bug 1225044.
+ unsigned int mPendingConfigures = 0;
+
+ // Window titlebar rendering mode, GTK_DECORATION_NONE if it's disabled
+ // for this window.
+ GtkWindowDecoration mGtkWindowDecoration = GTK_DECORATION_NONE;
+
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+
+ // The cursor cache
+ static GdkCursor* gsGtkCursorCache[eCursorCount];
+
+ // If true, draw our own window titlebar.
+ bool mDrawInTitlebar = false;
+
+ mozilla::Mutex mTitlebarRectMutex;
+ LayoutDeviceIntRect mTitlebarRect MOZ_GUARDED_BY(mTitlebarRectMutex);
+
+ mozilla::Mutex mDestroyMutex;
+
+ // Has this widget been destroyed yet?
+ bool mIsDestroyed : 1;
+ // mIsShown tracks requested visible status from browser perspective, i.e.
+ // if the window should be visible or now.
+ bool mIsShown : 1;
+ // mNeedsShow is set when browser requested to show this window but we failed
+ // to do so for some reason (wrong window size for instance).
+ // In such case we set mIsShown = true and mNeedsShow = true to indicate
+ // that the window is not actually visible but we report to browser that
+ // it is visible (mIsShown == true).
+ bool mNeedsShow : 1;
+ // This track real window visibility from OS perspective.
+ // It's set by OnMap/OnUnmap which is based on Gtk events.
+ bool mIsMapped : 1;
+ // is this widget enabled?
+ bool mEnabled : 1;
+ // has the native window for this been created yet?
+ bool mCreated : 1;
+ // whether we handle touch event
+ bool mHandleTouchEvent : 1;
+ // true if this is a drag and drop feedback popup
+ bool mIsDragPopup : 1;
+ bool mCompositedScreen : 1;
+ bool mIsAccelerated : 1;
+ bool mWindowShouldStartDragging : 1;
+ bool mHasMappedToplevel : 1;
+ bool mRetryPointerGrab : 1;
+ bool mPanInProgress : 1;
+ // Draw titlebar with :backdrop css state (inactive/unfocused).
+ bool mTitlebarBackdropState : 1;
+ // It's child window, i.e. window which is nested in parent window.
+ // This is obsoleted and should not be used.
+ // We use GdkWindow hierarchy for such windows.
+ bool mIsChildWindow : 1;
+ bool mAlwaysOnTop : 1;
+ bool mNoAutoHide : 1;
+ bool mIsTransparent : 1;
+ // We can expect at least one size-allocate event after early resizes.
+ bool mHasReceivedSizeAllocate : 1;
+ bool mWidgetCursorLocked : 1;
+ bool mUndecorated : 1;
+
+ /* Gkt creates popup in two incarnations - wl_subsurface and xdg_popup.
+ * Kind of popup is choosen before GdkWindow is mapped so we can change
+ * it only when GdkWindow is hidden.
+ *
+ * Relevant Gtk code is at gdkwindow-wayland.c
+ * in should_map_as_popup() and should_map_as_subsurface()
+ *
+ * wl_subsurface:
+ * - can't be positioned by move-to-rect
+ * - can stand outside popup widget hierarchy (has toplevel as parent)
+ * - don't have child popup widgets
+ *
+ * xdg_popup:
+ * - can be positioned by move-to-rect
+ * - aligned in popup widget hierarchy, first one is attached to toplevel
+ * - has child (popup) widgets
+ *
+ * Thus we need to map Firefox popup type to desired Gtk one:
+ *
+ * wl_subsurface:
+ * - pernament panels
+ *
+ * xdg_popup:
+ * - menus
+ * - autohide popups (hamburger menu)
+ * - extension popups
+ * - tooltips
+ *
+ * We set mPopupTrackInHierarchy = false for pernament panels which
+ * are always mapped to toplevel and painted as wl_surfaces.
+ */
+ bool mPopupTrackInHierarchy : 1;
+ bool mPopupTrackInHierarchyConfigured : 1;
+
+ /* On X11 Gtk tends to ignore window position requests when gtk_window
+ * is hidden. Save the position requests at mPopupPosition and apply
+ * when the widget is shown.
+ */
+ bool mHiddenPopupPositioned : 1;
+
+ // The transparency bitmap is used instead of ARGB visual for toplevel
+ // window to draw titlebar.
+ bool mTransparencyBitmapForTitlebar : 1;
+
+ // True when we're on compositing window manager and this
+ // window is using visual with alpha channel.
+ bool mHasAlphaVisual : 1;
+
+ // When popup is anchored, mPopupPosition is relative to its parent popup.
+ bool mPopupAnchored : 1;
+
+ // When popup is context menu.
+ bool mPopupContextMenu : 1;
+
+ // Indicates that this popup matches layout setup so we can use parent popup
+ // coordinates reliably.
+ bool mPopupMatchesLayout : 1;
+
+ /* Indicates that popup setup was changed and
+ * we need to recalculate popup coordinates.
+ */
+ bool mPopupChanged : 1;
+
+ // Popup is hidden only as a part of hierarchy tree update.
+ bool mPopupTemporaryHidden : 1;
+
+ // Popup is going to be closed and removed.
+ bool mPopupClosed : 1;
+
+ // Popup is positioned by gdk_window_move_to_rect()
+ bool mPopupUseMoveToRect : 1;
+
+ /* mWaitingForMoveToRectCallback is set when move-to-rect is called
+ * and we're waiting for move-to-rect callback.
+ *
+ * If another position/resize request comes between move-to-rect call and
+ * move-to-rect callback we set mMovedAfterMoveToRect/mResizedAfterMoveToRect.
+ */
+ bool mWaitingForMoveToRectCallback : 1;
+ bool mMovedAfterMoveToRect : 1;
+ bool mResizedAfterMoveToRect : 1;
+
+ // Params used for popup placemend by GdkWindowMoveToRect.
+ // When popup is only resized and not positioned,
+ // we need to reuse last GdkWindowMoveToRect params to avoid
+ // popup movement.
+ struct WaylandPopupMoveToRectParams {
+ LayoutDeviceIntRect mAnchorRect = {0, 0, 0, 0};
+ GdkGravity mAnchorRectType = GDK_GRAVITY_NORTH_WEST;
+ GdkGravity mPopupAnchorType = GDK_GRAVITY_NORTH_WEST;
+ GdkAnchorHints mHints = GDK_ANCHOR_SLIDE;
+ GdkPoint mOffset = {0, 0};
+ bool mAnchorSet = false;
+ };
+
+ WaylandPopupMoveToRectParams mPopupMoveToRectParams;
+
+ // Whether we've configured default clear color already.
+ bool mConfiguredClearColor : 1;
+ // Whether we've received a non-blank paint in which case we can reset the
+ // clear color to transparent.
+ bool mGotNonBlankPaint : 1;
+
+ // Whether we need to retry capturing the mouse because we' re not mapped yet.
+ bool mNeedsToRetryCapturingMouse : 1;
+
+ // This bitmap tracks which pixels are transparent. We don't support
+ // full translucency at this time; each pixel is either fully opaque
+ // or fully transparent.
+ gchar* mTransparencyBitmap = nullptr;
+ int32_t mTransparencyBitmapWidth = 0;
+ int32_t mTransparencyBitmapHeight = 0;
+
+ // all of our DND stuff
+ void InitDragEvent(mozilla::WidgetDragEvent& aEvent);
+
+ float mLastMotionPressure = 0.0f;
+
+ InputRegion mInputRegion;
+
+ static bool DragInProgress(void);
+
+ void DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent);
+
+ // When window widget gets mapped/unmapped we need to configure
+ // underlying GdkWindow properly. Otherwise we'll end up with
+ // rendering to released window.
+ void ConfigureGdkWindow();
+ void ReleaseGdkWindow();
+ void ConfigureCompositor();
+
+ bool IsAlwaysUndecoratedWindow() const;
+
+ // nsBaseWidget
+ WindowRenderer* GetWindowRenderer() override;
+ void DidGetNonBlankPaint() override;
+
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+
+ int32_t RoundsWidgetCoordinatesTo() override;
+
+ void UpdateMozWindowActive();
+
+ void ForceTitlebarRedraw();
+ bool DoDrawTilebarCorners();
+ bool IsChromeWindowTitlebar();
+
+ void SetPopupWindowDecoration(bool aShowOnTaskbar);
+
+ void ApplySizeConstraints();
+
+ // Wayland Popup section
+ GdkPoint WaylandGetParentPosition();
+ bool WaylandPopupConfigure();
+ bool WaylandPopupIsAnchored();
+ bool WaylandPopupIsMenu();
+ bool WaylandPopupIsContextMenu();
+ bool WaylandPopupIsPermanent();
+ // First popup means it's attached directly to toplevel window
+ bool WaylandPopupIsFirst();
+ bool IsWidgetOverflowWindow();
+ void RemovePopupFromHierarchyList();
+ void ShowWaylandPopupWindow();
+ void HideWaylandPopupWindow(bool aTemporaryHidden, bool aRemoveFromPopupList);
+ void ShowWaylandToplevelWindow();
+ void HideWaylandToplevelWindow();
+ void WaylandPopupHideTooltips();
+ void WaylandPopupCloseOrphanedPopups();
+ void AppendPopupToHierarchyList(nsWindow* aToplevelWindow);
+ void WaylandPopupHierarchyHideTemporary();
+ void WaylandPopupHierarchyShowTemporaryHidden();
+ void WaylandPopupHierarchyCalculatePositions();
+ bool IsInPopupHierarchy();
+ void AddWindowToPopupHierarchy();
+ void UpdateWaylandPopupHierarchy();
+ void WaylandPopupHierarchyHideByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy);
+ void WaylandPopupHierarchyValidateByLayout(
+ nsTArray<nsIWidget*>* aLayoutWidgetHierarchy);
+ void CloseAllPopupsBeforeRemotePopup();
+ void WaylandPopupHideClosedPopups();
+ void WaylandPopupPrepareForMove();
+ void WaylandPopupMoveImpl();
+ void WaylandPopupMovePlain(int aX, int aY);
+ bool WaylandPopupRemoveNegativePosition(int* aX = nullptr, int* aY = nullptr);
+ bool WaylandPopupCheckAndGetAnchor(GdkRectangle* aPopupAnchor,
+ GdkPoint* aOffset);
+ bool WaylandPopupAnchorAdjustForParentPopup(GdkRectangle* aPopupAnchor,
+ GdkPoint* aOffset);
+ nsWindow* GetTopmostWindow();
+ bool IsPopupInLayoutPopupChain(nsTArray<nsIWidget*>* aLayoutWidgetHierarchy,
+ bool aMustMatchParent);
+ void WaylandPopupMarkAsClosed();
+ void WaylandPopupRemoveClosedPopups();
+ void WaylandPopupSetDirectPosition();
+ bool WaylandPopupFitsToplevelWindow(bool aMove);
+ const WaylandPopupMoveToRectParams WaylandPopupGetPositionFromLayout();
+ void WaylandPopupPropagateChangesToLayout(bool aMove, bool aResize);
+ nsWindow* WaylandPopupFindLast(nsWindow* aPopup);
+ GtkWindow* GetCurrentTopmostWindow() const;
+ nsAutoCString GetFrameTag() const;
+ nsCString GetPopupTypeName();
+ bool IsPopupDirectionRTL();
+
+#ifdef MOZ_LOGGING
+ void LogPopupHierarchy();
+ void LogPopupAnchorHints(int aHints);
+ void LogPopupGravity(GdkGravity aGravity);
+#endif
+
+ bool IsTopLevelWindowType() const {
+ return mWindowType == WindowType::TopLevel ||
+ mWindowType == WindowType::Dialog;
+ }
+
+ // mPopupPosition is the original popup position/size from layout, set by
+ // nsWindow::Move() or nsWindow::Resize().
+ // Popup position is relative to main (toplevel) window.
+ GdkPoint mPopupPosition{};
+
+ // mRelativePopupPosition is popup position calculated against
+ // recent popup parent window.
+ GdkPoint mRelativePopupPosition{};
+
+ // Toplevel window (first element) of linked list of Wayland popups. It's null
+ // if we're the toplevel.
+ RefPtr<nsWindow> mWaylandToplevel;
+
+ // Next/Previous popups in Wayland popup hierarchy.
+ RefPtr<nsWindow> mWaylandPopupNext;
+ RefPtr<nsWindow> mWaylandPopupPrev;
+
+ // When popup is resized by Gtk by move-to-rect callback,
+ // we store final popup size here. Then we use mMoveToRectPopupSize size
+ // in following popup operations unless mLayoutPopupSizeCleared is set.
+ LayoutDeviceIntSize mMoveToRectPopupSize;
+
+ /**
+ * |mIMContext| takes all IME related stuff.
+ *
+ * This is owned by the top-level nsWindow or the topmost child
+ * nsWindow embedded in a non-Gecko widget.
+ *
+ * The instance is created when the top level widget is created. And when
+ * the widget is destroyed, it's released. All child windows refer its
+ * ancestor widget's instance. So, one set of IM contexts is created for
+ * all windows in a hierarchy. If the children are released after the top
+ * level window is released, the children still have a valid pointer,
+ * however, IME doesn't work at that time.
+ */
+ RefPtr<mozilla::widget::IMContextWrapper> mIMContext;
+
+#ifdef MOZ_X11
+ mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+#endif
+ static GtkWindowDecoration sGtkWindowDecoration;
+
+ static bool sTransparentMainWindow;
+
+#ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::LocalAccessible> mRootAccessible;
+
+ /**
+ * Request to create the accessible for this window if it is top level.
+ */
+ void CreateRootAccessible();
+
+ /**
+ * Dispatch accessible event for the top level window accessible.
+ *
+ * @param aEventType [in] the accessible event type to dispatch
+ */
+ void DispatchEventToRootAccessible(uint32_t aEventType);
+
+ /**
+ * Dispatch accessible window activate event for the top level window
+ * accessible.
+ */
+ void DispatchActivateEventAccessible();
+
+ /**
+ * Dispatch accessible window deactivate event for the top level window
+ * accessible.
+ */
+ void DispatchDeactivateEventAccessible();
+
+ /**
+ * Dispatch accessible window maximize event for the top level window
+ * accessible.
+ */
+ void DispatchMaximizeEventAccessible();
+
+ /**
+ * Dispatch accessible window minize event for the top level window
+ * accessible.
+ */
+ void DispatchMinimizeEventAccessible();
+
+ /**
+ * Dispatch accessible window restore event for the top level window
+ * accessible.
+ */
+ void DispatchRestoreEventAccessible();
+#endif
+
+ void SetUserTimeAndStartupTokenForActivatedWindow();
+
+ void KioskLockOnMonitor();
+
+ void EmulateResizeDrag(GdkEventMotion* aEvent);
+
+ void RequestRepaint(LayoutDeviceIntRegion& aRepaintRegion);
+
+#ifdef MOZ_X11
+ typedef enum {GTK_WIDGET_COMPOSIDED_DEFAULT = 0,
+ GTK_WIDGET_COMPOSIDED_DISABLED = 1,
+ GTK_WIDGET_COMPOSIDED_ENABLED = 2} WindowComposeRequest;
+ void SetCompositorHint(WindowComposeRequest aState);
+ bool ConfigureX11GLVisual();
+#endif
+#ifdef MOZ_WAYLAND
+ RefPtr<mozilla::WaylandVsyncSource> mWaylandVsyncSource;
+ RefPtr<mozilla::VsyncDispatcher> mWaylandVsyncDispatcher;
+ LayoutDeviceIntPoint mNativePointerLockCenter;
+ zwp_locked_pointer_v1* mLockedPointer = nullptr;
+ zwp_relative_pointer_v1* mRelativePointer = nullptr;
+#endif
+ // An activation token from our environment (see handling of the
+ // XDG_ACTIVATION_TOKEN/DESKTOP_STARTUP_ID) env vars.
+ nsCString mWindowActivationTokenFromEnv;
+ mozilla::widget::WindowSurfaceProvider mSurfaceProvider;
+ GdkDragContext* mSourceDragContext = nullptr;
+#if MOZ_LOGGING
+ LayoutDeviceIntRect mLastLoggedBoundSize;
+ int mLastLoggedScale = -1;
+#endif
+ // Running in kiosk mode and requested to stay on specified monitor.
+ // If monitor is removed minimize the window.
+ mozilla::Maybe<int> mKioskMonitor;
+};
+
+#endif /* __nsWindow_h__ */
diff --git a/widget/gtk/v4l2test/moz.build b/widget/gtk/v4l2test/moz.build
new file mode 100644
index 0000000000..da22288f17
--- /dev/null
+++ b/widget/gtk/v4l2test/moz.build
@@ -0,0 +1,17 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Startup and Profile System")
+
+Program("v4l2test")
+SOURCES += [
+ "v4l2test.cpp",
+]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"]
+OS_LIBS += CONFIG["MOZ_X11_LIBS"]
+OS_LIBS += CONFIG["MOZ_GTK3_LIBS"]
diff --git a/widget/gtk/v4l2test/v4l2test.cpp b/widget/gtk/v4l2test/v4l2test.cpp
new file mode 100644
index 0000000000..6ee685d9ed
--- /dev/null
+++ b/widget/gtk/v4l2test/v4l2test.cpp
@@ -0,0 +1,188 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdio>
+#include <cstdlib>
+#include <errno.h>
+#include <fcntl.h>
+#if defined(__NetBSD__) || defined(__OpenBSD__)
+# include <sys/videoio.h>
+#elif defined(__sun)
+# include <sys/videodev2.h>
+#else
+# include <linux/videodev2.h>
+#endif
+#include <sys/ioctl.h>
+#include <unistd.h>
+#include <string.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#if defined(MOZ_ASAN) || defined(FUZZING)
+# include <signal.h>
+#endif
+
+#include "mozilla/ScopeExit.h"
+
+#ifdef __SUNPRO_CC
+# include <stdio.h>
+#endif
+
+#include "mozilla/GfxInfoUtils.h"
+
+// Print test results to stdout and logging to stderr
+#define OUTPUT_PIPE 1
+
+// Convert an integer pixfmt to a 4-character string. str must have a length
+// of at least 5 to include null-termination.
+static void v4l2_pixfmt_to_str(uint32_t pixfmt, char* str) {
+ for (int i = 0; i < 4; i++) {
+ str[i] = (pixfmt >> (i * 8)) & 0xff;
+ }
+ str[4] = 0;
+}
+
+// Enumerate the buffer formats supported on a V4L2 buffer queue. aTypeStr
+// is the queue type, i.e. CAPTURE or OUTPUT.
+static void v4l2_enumfmt(int aFd, int aType, const char* aTypeStr) {
+ struct v4l2_fmtdesc fmt {};
+ char pix_fmt_str[5];
+ fmt.type = aType;
+ record_value("V4L2_%s_FMTS\n", aTypeStr);
+ for (fmt.index = 0;; fmt.index++) {
+ int result = ioctl(aFd, VIDIOC_ENUM_FMT, &fmt);
+ if (result < 0) {
+ break;
+ }
+ v4l2_pixfmt_to_str(fmt.pixelformat, pix_fmt_str);
+ record_value(" %s", pix_fmt_str);
+ }
+ record_value("\n");
+}
+
+// Probe a V4L2 device to work out what it supports
+static void v4l2_check_device(const char* aVideoDevice) {
+ int fd = -1;
+ int result = -1;
+
+ log("v4l2test probing device '%s'\n", aVideoDevice);
+
+ auto autoRelease = mozilla::MakeScopeExit([&] {
+ if (fd >= 0) {
+ close(fd);
+ }
+ });
+
+ fd = open(aVideoDevice, O_RDWR | O_NONBLOCK, 0);
+ if (fd < 0) {
+ record_value("ERROR\nV4L2 failed to open device %s: %s\n", aVideoDevice,
+ strerror(errno));
+ return;
+ }
+
+ struct v4l2_capability cap {};
+ result = ioctl(fd, VIDIOC_QUERYCAP, &cap);
+ if (result < 0) {
+ record_value("ERROR\nV4L2 device %s failed to query capabilities\n",
+ aVideoDevice);
+ return;
+ }
+ log("v4l2test driver %s card %s bus_info %s version %d\n", cap.driver,
+ cap.card, cap.bus_info, cap.version);
+
+ if (!(cap.capabilities & V4L2_CAP_DEVICE_CAPS)) {
+ record_value("ERROR\nV4L2 device %s does not support DEVICE_CAPS\n",
+ aVideoDevice);
+ return;
+ }
+
+ if (!(cap.device_caps & V4L2_CAP_STREAMING)) {
+ record_value("ERROR\nV4L2 device %s does not support V4L2_CAP_STREAMING\n",
+ aVideoDevice);
+ return;
+ }
+
+ // Work out whether the device supports planar or multiplaner bitbuffers and
+ // framebuffers
+ bool splane = cap.device_caps & V4L2_CAP_VIDEO_M2M;
+ bool mplane = cap.device_caps & V4L2_CAP_VIDEO_M2M_MPLANE;
+ if (!splane && !mplane) {
+ record_value("ERROR\nV4L2 device %s does not support M2M modes\n",
+ aVideoDevice);
+ // (It's probably a webcam!)
+ return;
+ }
+ record_value("V4L2_SPLANE\n%s\n", splane ? "TRUE" : "FALSE");
+ record_value("V4L2_MPLANE\n%s\n", mplane ? "TRUE" : "FALSE");
+
+ // Now check the formats supported for CAPTURE and OUTPUT buffers.
+ // For a V4L2-M2M decoder, OUTPUT is actually the bitbuffers we put in and
+ // CAPTURE is the framebuffers we get out.
+ v4l2_enumfmt(
+ fd,
+ mplane ? V4L2_BUF_TYPE_VIDEO_CAPTURE_MPLANE : V4L2_BUF_TYPE_VIDEO_CAPTURE,
+ "CAPTURE");
+ v4l2_enumfmt(
+ fd,
+ mplane ? V4L2_BUF_TYPE_VIDEO_OUTPUT_MPLANE : V4L2_BUF_TYPE_VIDEO_OUTPUT,
+ "OUTPUT");
+
+ record_value("V4L2_SUPPORTED\nTRUE\n");
+}
+
+static void PrintUsage() {
+ printf(
+ "Firefox V4L2-M2M probe utility\n"
+ "\n"
+ "usage: v4l2test [options]\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " -h --help show this message\n"
+ " -d --device device Probe a v4l2 device (e.g. /dev/video10)\n"
+ "\n");
+}
+
+int main(int argc, char** argv) {
+ struct option longOptions[] = {{"help", no_argument, nullptr, 'h'},
+ {"device", required_argument, nullptr, 'd'},
+ {nullptr, 0, nullptr, 0}};
+ const char* shortOptions = "hd:";
+ int c;
+ const char* device = nullptr;
+ while ((c = getopt_long(argc, argv, shortOptions, longOptions, nullptr)) !=
+ -1) {
+ switch (c) {
+ case 'd':
+ device = optarg;
+ break;
+ case 'h':
+ default:
+ break;
+ }
+ }
+
+ if (device) {
+#if defined(MOZ_ASAN) || defined(FUZZING)
+ // If handle_segv=1 (default), then glxtest crash will print a sanitizer
+ // report which can confuse the harness in fuzzing automation.
+ signal(SIGSEGV, SIG_DFL);
+#endif
+ const char* env = getenv("MOZ_GFX_DEBUG");
+ enable_logging = env && *env == '1';
+ output_pipe = OUTPUT_PIPE;
+ if (!enable_logging) {
+ close_logging();
+ }
+ v4l2_check_device(device);
+ record_flush();
+ return EXIT_SUCCESS;
+ }
+ PrintUsage();
+ return 0;
+}
diff --git a/widget/gtk/va_drmcommon.h b/widget/gtk/va_drmcommon.h
new file mode 100644
index 0000000000..e16f244a46
--- /dev/null
+++ b/widget/gtk/va_drmcommon.h
@@ -0,0 +1,156 @@
+/*
+ * va_drmcommon.h - Common utilities for DRM-based drivers
+ *
+ * Copyright (c) 2012 Intel Corporation. All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL INTEL AND/OR ITS SUPPLIERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ */
+
+#ifndef VA_DRM_COMMON_H
+#define VA_DRM_COMMON_H
+
+#include <stdint.h>
+
+/** \brief DRM authentication type. */
+enum {
+ /** \brief Disconnected. */
+ VA_DRM_AUTH_NONE = 0,
+ /**
+ * \brief Connected. Authenticated with DRI1 protocol.
+ *
+ * @deprecated
+ * This is a deprecated authentication type. All DRI-based drivers have
+ * been migrated to use the DRI2 protocol. Newly written drivers shall
+ * use DRI2 protocol only, or a custom authentication means. e.g. opt
+ * for authenticating on the VA driver side, instead of libva side.
+ */
+ VA_DRM_AUTH_DRI1 = 1,
+ /**
+ * \brief Connected. Authenticated with DRI2 protocol.
+ *
+ * This is only useful to VA/X11 drivers. The libva-x11 library provides
+ * a helper function VA_DRI2Authenticate() for authenticating the
+ * connection. However, DRI2 conformant drivers don't need to call that
+ * function since authentication happens on the libva side, implicitly.
+ */
+ VA_DRM_AUTH_DRI2 = 2,
+ /**
+ * \brief Connected. Authenticated with some alternate raw protocol.
+ *
+ * This authentication mode is mainly used in non-VA/X11 drivers.
+ * Authentication happens through some alternative method, at the
+ * discretion of the VA driver implementation.
+ */
+ VA_DRM_AUTH_CUSTOM = 3
+};
+
+/** \brief Base DRM state. */
+struct drm_state {
+ /** \brief DRM connection descriptor. */
+ int fd;
+ /** \brief DRM authentication type. */
+ int auth_type;
+ /** \brief Reserved bytes for future use, must be zero */
+ int va_reserved[8];
+};
+
+/** \brief Kernel DRM buffer memory type. */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_KERNEL_DRM 0x10000000
+/** \brief DRM PRIME memory type (old version)
+ *
+ * This supports only single objects with restricted memory layout.
+ * Used with VASurfaceAttribExternalBuffers.
+ */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME 0x20000000
+/** \brief DRM PRIME memory type
+ *
+ * Used with VADRMPRIMESurfaceDescriptor.
+ */
+#define VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 0x40000000
+
+/**
+ * \brief External buffer descriptor for a DRM PRIME surface.
+ *
+ * For export, call vaExportSurfaceHandle() with mem_type set to
+ * VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and pass a pointer to an
+ * instance of this structure to fill.
+ * If VA_EXPORT_SURFACE_SEPARATE_LAYERS is specified on export, each
+ * layer will contain exactly one plane. For example, an NV12
+ * surface will be exported as two layers, one of DRM_FORMAT_R8 and
+ * one of DRM_FORMAT_GR88.
+ * If VA_EXPORT_SURFACE_COMPOSED_LAYERS is specified on export,
+ * there will be exactly one layer.
+ *
+ * For import, call vaCreateSurfaces() with the MemoryType attribute
+ * set to VA_SURFACE_ATTRIB_MEM_TYPE_DRM_PRIME_2 and the
+ * ExternalBufferDescriptor attribute set to point to an array of
+ * num_surfaces instances of this structure.
+ * The number of planes which need to be provided for a given layer
+ * is dependent on both the format and the format modifier used for
+ * the objects containing it. For example, the format DRM_FORMAT_RGBA
+ * normally requires one plane, but with the format modifier
+ * I915_FORMAT_MOD_Y_TILED_CCS it requires two planes - the first
+ * being the main data plane and the second containing the color
+ * control surface.
+ * Note that a given driver may only support a subset of possible
+ * representations of a particular format. For example, it may only
+ * support NV12 surfaces when they are contained within a single DRM
+ * object, and therefore fail to create such surfaces if the two
+ * planes are in different DRM objects.
+ */
+typedef struct _VADRMPRIMESurfaceDescriptor {
+ /** Pixel format fourcc of the whole surface (VA_FOURCC_*). */
+ uint32_t fourcc;
+ /** Width of the surface in pixels. */
+ uint32_t width;
+ /** Height of the surface in pixels. */
+ uint32_t height;
+ /** Number of distinct DRM objects making up the surface. */
+ uint32_t num_objects;
+ /** Description of each object. */
+ struct {
+ /** DRM PRIME file descriptor for this object. */
+ int fd;
+ /** Total size of this object (may include regions which are
+ * not part of the surface). */
+ uint32_t size;
+ /** Format modifier applied to this object. */
+ uint64_t drm_format_modifier;
+ } objects[4];
+ /** Number of layers making up the surface. */
+ uint32_t num_layers;
+ /** Description of each layer in the surface. */
+ struct {
+ /** DRM format fourcc of this layer (DRM_FOURCC_*). */
+ uint32_t drm_format;
+ /** Number of planes in this layer. */
+ uint32_t num_planes;
+ /** Index in the objects array of the object containing each
+ * plane. */
+ uint32_t object_index[4];
+ /** Offset within the object of each plane. */
+ uint32_t offset[4];
+ /** Pitch of each plane. */
+ uint32_t pitch[4];
+ } layers[4];
+} VADRMPRIMESurfaceDescriptor;
+
+#endif /* VA_DRM_COMMON_H */
diff --git a/widget/gtk/vaapitest/moz.build b/widget/gtk/vaapitest/moz.build
new file mode 100644
index 0000000000..d9ea481ff1
--- /dev/null
+++ b/widget/gtk/vaapitest/moz.build
@@ -0,0 +1,20 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Toolkit", "Startup and Profile System")
+
+Program("vaapitest")
+SOURCES += [
+ "vaapitest.cpp",
+]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"]
+OS_LIBS += CONFIG["MOZ_X11_LIBS"]
+OS_LIBS += CONFIG["MOZ_GTK3_LIBS"]
+
+USE_LIBS += ["mozva"]
+LOCAL_INCLUDES += ["/media/mozva"]
diff --git a/widget/gtk/vaapitest/vaapitest.cpp b/widget/gtk/vaapitest/vaapitest.cpp
new file mode 100644
index 0000000000..e2c0e0a742
--- /dev/null
+++ b/widget/gtk/vaapitest/vaapitest.cpp
@@ -0,0 +1,255 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ * vim: sw=2 ts=8 et :
+ */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <cstdio>
+#include <cstdlib>
+#include <dlfcn.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <string.h>
+#include <unistd.h>
+#include <getopt.h>
+#include <stdint.h>
+#include <stdarg.h>
+
+#if defined(MOZ_ASAN) || defined(FUZZING)
+# include <signal.h>
+#endif
+
+#include "mozilla/ScopeExit.h"
+
+#ifdef __SUNPRO_CC
+# include <stdio.h>
+#endif
+
+#include "prlink.h"
+#include "va/va.h"
+
+#include "mozilla/GfxInfoUtils.h"
+
+// Print VA-API test results to stdout and logging to stderr
+#define OUTPUT_PIPE 1
+
+#define MIN(a, b) ((a) < (b) ? (a) : (b))
+
+// bits to use decoding vaapitest() return values.
+constexpr int CODEC_HW_H264 = 1 << 4;
+constexpr int CODEC_HW_VP8 = 1 << 5;
+constexpr int CODEC_HW_VP9 = 1 << 6;
+constexpr int CODEC_HW_AV1 = 1 << 7;
+
+// childgltest is declared inside extern "C" so that the name is not mangled.
+// The name is used in build/valgrind/x86_64-pc-linux-gnu.sup to suppress
+// memory leak errors because we run it inside a short lived fork and we don't
+// care about leaking memory
+extern "C" {
+
+static constexpr struct {
+ VAProfile mVAProfile;
+ const char* mName;
+} kVAAPiProfileName[] = {
+#define MAP(v) \
+ { VAProfile##v, #v }
+ MAP(H264ConstrainedBaseline),
+ MAP(H264Main),
+ MAP(H264High),
+ MAP(VP8Version0_3),
+ MAP(VP9Profile0),
+ MAP(VP9Profile2),
+ MAP(AV1Profile0),
+ MAP(AV1Profile1),
+#undef MAP
+};
+
+static const char* VAProfileName(VAProfile aVAProfile) {
+ for (const auto& profile : kVAAPiProfileName) {
+ if (profile.mVAProfile == aVAProfile) {
+ return profile.mName;
+ }
+ }
+ return nullptr;
+}
+
+static void vaapitest(const char* aRenderDevicePath) {
+ int renderDeviceFD = -1;
+ VAProfile* profiles = nullptr;
+ VAEntrypoint* entryPoints = nullptr;
+ VADisplay display = nullptr;
+ void* libDrm = nullptr;
+
+ log("vaapitest start, device %s\n", aRenderDevicePath);
+
+ auto autoRelease = mozilla::MakeScopeExit([&] {
+ free(profiles);
+ free(entryPoints);
+ if (display) {
+ vaTerminate(display);
+ }
+ if (libDrm) {
+ dlclose(libDrm);
+ }
+ if (renderDeviceFD > -1) {
+ close(renderDeviceFD);
+ }
+ });
+
+ renderDeviceFD = open(aRenderDevicePath, O_RDWR);
+ if (renderDeviceFD == -1) {
+ record_error("VA-API test failed: failed to open renderDeviceFD.");
+ return;
+ }
+
+ libDrm = dlopen("libva-drm.so.2", RTLD_LAZY);
+ if (!libDrm) {
+ log("vaapitest failed: libva-drm.so.2 is missing\n");
+ return;
+ }
+
+ static auto sVaGetDisplayDRM =
+ (void* (*)(int fd))dlsym(libDrm, "vaGetDisplayDRM");
+ if (!sVaGetDisplayDRM) {
+ record_error("VA-API test failed: sVaGetDisplayDRM is missing.");
+ return;
+ }
+
+ display = sVaGetDisplayDRM(renderDeviceFD);
+ if (!display) {
+ record_error("VA-API test failed: vaGetDisplayDRM failed.");
+ return;
+ }
+
+ int major, minor;
+ VAStatus status = vaInitialize(display, &major, &minor);
+ if (status != VA_STATUS_SUCCESS) {
+ log("vaInitialize failed %d\n", status);
+ return;
+ } else {
+ log("vaInitialize finished\n");
+ }
+
+ int maxProfiles = vaMaxNumProfiles(display);
+ int maxEntryPoints = vaMaxNumEntrypoints(display);
+ if (maxProfiles <= 0 || maxEntryPoints <= 0) {
+ record_error("VA-API test failed: wrong VAAPI profiles/entry point nums.");
+ return;
+ }
+
+ profiles = (VAProfile*)malloc(sizeof(VAProfile) * maxProfiles);
+ int numProfiles = 0;
+ status = vaQueryConfigProfiles(display, profiles, &numProfiles);
+ if (status != VA_STATUS_SUCCESS) {
+ record_error("VA-API test failed: vaQueryConfigProfiles() failed.");
+ return;
+ }
+ numProfiles = MIN(numProfiles, maxProfiles);
+
+ entryPoints = (VAEntrypoint*)malloc(sizeof(VAEntrypoint) * maxEntryPoints);
+ int codecs = 0;
+ bool foundProfile = false;
+ for (int p = 0; p < numProfiles; p++) {
+ VAProfile profile = profiles[p];
+
+ // Check only supported profiles
+ if (!VAProfileName(profile)) {
+ continue;
+ }
+
+ int numEntryPoints = 0;
+ status = vaQueryConfigEntrypoints(display, profile, entryPoints,
+ &numEntryPoints);
+ if (status != VA_STATUS_SUCCESS) {
+ continue;
+ }
+ numEntryPoints = MIN(numEntryPoints, maxEntryPoints);
+ for (int entry = 0; entry < numEntryPoints; entry++) {
+ if (entryPoints[entry] != VAEntrypointVLD) {
+ continue;
+ }
+ VAConfigID config = VA_INVALID_ID;
+ status = vaCreateConfig(display, profile, entryPoints[entry], nullptr, 0,
+ &config);
+ if (status == VA_STATUS_SUCCESS) {
+ const char* profstr = VAProfileName(profile);
+ log("Profile: %s\n", profstr);
+ // VAProfileName returns null on failure, making the below calls safe
+ if (!strncmp(profstr, "H264", 4)) {
+ codecs |= CODEC_HW_H264;
+ } else if (!strncmp(profstr, "VP8", 3)) {
+ codecs |= CODEC_HW_VP8;
+ } else if (!strncmp(profstr, "VP9", 3)) {
+ codecs |= CODEC_HW_VP9;
+ } else if (!strncmp(profstr, "AV1", 3)) {
+ codecs |= CODEC_HW_AV1;
+ } else {
+ record_warning("VA-API test unknown profile.");
+ }
+ vaDestroyConfig(display, config);
+ foundProfile = true;
+ }
+ }
+ }
+ if (foundProfile) {
+ record_value("VAAPI_SUPPORTED\nTRUE\n");
+ record_value("VAAPI_HWCODECS\n%d\n", codecs);
+ } else {
+ record_value("VAAPI_SUPPORTED\nFALSE\n");
+ }
+ log("vaapitest finished\n");
+}
+
+} // extern "C"
+
+static void PrintUsage() {
+ printf(
+ "Firefox VA-API probe utility\n"
+ "\n"
+ "usage: vaapitest [options]\n"
+ "\n"
+ "Options:\n"
+ "\n"
+ " -h --help show this message\n"
+ " -d --drm drm_device probe VA-API on drm_device (may be "
+ "/dev/dri/renderD128)\n"
+ "\n");
+}
+
+int main(int argc, char** argv) {
+ struct option longOptions[] = {{"help", no_argument, NULL, 'h'},
+ {"drm", required_argument, NULL, 'd'},
+ {NULL, 0, NULL, 0}};
+ const char* shortOptions = "hd:";
+ int c;
+ const char* drmDevice = nullptr;
+ while ((c = getopt_long(argc, argv, shortOptions, longOptions, NULL)) != -1) {
+ switch (c) {
+ case 'd':
+ drmDevice = optarg;
+ break;
+ case 'h':
+ default:
+ break;
+ }
+ }
+ if (drmDevice) {
+#if defined(MOZ_ASAN) || defined(FUZZING)
+ // If handle_segv=1 (default), then glxtest crash will print a sanitizer
+ // report which can confuse the harness in fuzzing automation.
+ signal(SIGSEGV, SIG_DFL);
+#endif
+ const char* env = getenv("MOZ_GFX_DEBUG");
+ enable_logging = env && *env == '1';
+ output_pipe = OUTPUT_PIPE;
+ if (!enable_logging) {
+ close_logging();
+ }
+ vaapitest(drmDevice);
+ record_flush();
+ return EXIT_SUCCESS;
+ }
+ PrintUsage();
+ return 0;
+}
diff --git a/widget/gtk/wayland/fractional-scale-v1-client-protocol.h b/widget/gtk/wayland/fractional-scale-v1-client-protocol.h
new file mode 100644
index 0000000000..3db2eaad6b
--- /dev/null
+++ b/widget/gtk/wayland/fractional-scale-v1-client-protocol.h
@@ -0,0 +1,268 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H
+#define FRACTIONAL_SCALE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_fractional_scale_v1 The fractional_scale_v1 protocol
+ * Protocol for requesting fractional surface scales
+ *
+ * @section page_desc_fractional_scale_v1 Description
+ *
+ * This protocol allows a compositor to suggest for surfaces to render at
+ * fractional scales.
+ *
+ * A client can submit scaled content by utilizing wp_viewport. This is done by
+ * creating a wp_viewport object for the surface and setting the destination
+ * rectangle to the surface size before the scale factor is applied.
+ *
+ * The buffer size is calculated by multiplying the surface size by the
+ * intended scale.
+ *
+ * The wl_surface buffer scale should remain set to 1.
+ *
+ * If a surface has a surface-local size of 100 px by 50 px and wishes to
+ * submit buffers with a scale of 1.5, then a buffer of 150px by 75 px should
+ * be used and the wp_viewport destination rectangle should be 100 px by 50 px.
+ *
+ * For toplevel surfaces, the size is rounded halfway away from zero. The
+ * rounding algorithm for subsurface position and size is not defined.
+ *
+ * @section page_ifaces_fractional_scale_v1 Interfaces
+ * - @subpage page_iface_wp_fractional_scale_manager_v1 - fractional surface
+ * scale information
+ * - @subpage page_iface_wp_fractional_scale_v1 - fractional scale interface to
+ * a wl_surface
+ * @section page_copyright_fractional_scale_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2022 Kenny Levinsen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_surface;
+struct wp_fractional_scale_manager_v1;
+struct wp_fractional_scale_v1;
+
+#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE
+# define WP_FRACTIONAL_SCALE_MANAGER_V1_INTERFACE
+/**
+ * @page page_iface_wp_fractional_scale_manager_v1
+ * wp_fractional_scale_manager_v1
+ * @section page_iface_wp_fractional_scale_manager_v1_desc Description
+ *
+ * A global interface for requesting surfaces to use fractional scales.
+ * @section page_iface_wp_fractional_scale_manager_v1_api API
+ * See @ref iface_wp_fractional_scale_manager_v1.
+ */
+/**
+ * @defgroup iface_wp_fractional_scale_manager_v1 The
+ * wp_fractional_scale_manager_v1 interface
+ *
+ * A global interface for requesting surfaces to use fractional scales.
+ */
+extern const struct wl_interface wp_fractional_scale_manager_v1_interface;
+#endif
+#ifndef WP_FRACTIONAL_SCALE_V1_INTERFACE
+# define WP_FRACTIONAL_SCALE_V1_INTERFACE
+/**
+ * @page page_iface_wp_fractional_scale_v1 wp_fractional_scale_v1
+ * @section page_iface_wp_fractional_scale_v1_desc Description
+ *
+ * An additional interface to a wl_surface object which allows the compositor
+ * to inform the client of the preferred scale.
+ * @section page_iface_wp_fractional_scale_v1_api API
+ * See @ref iface_wp_fractional_scale_v1.
+ */
+/**
+ * @defgroup iface_wp_fractional_scale_v1 The wp_fractional_scale_v1 interface
+ *
+ * An additional interface to a wl_surface object which allows the compositor
+ * to inform the client of the preferred scale.
+ */
+extern const struct wl_interface wp_fractional_scale_v1_interface;
+#endif
+
+#ifndef WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM
+# define WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM
+enum wp_fractional_scale_manager_v1_error {
+ /**
+ * the surface already has a fractional_scale object associated
+ */
+ WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_FRACTIONAL_SCALE_EXISTS = 0,
+};
+#endif /* WP_FRACTIONAL_SCALE_MANAGER_V1_ERROR_ENUM */
+
+#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY 0
+#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE 1
+
+/**
+ * @ingroup iface_wp_fractional_scale_manager_v1
+ */
+#define WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_wp_fractional_scale_manager_v1
+ */
+#define WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE_SINCE_VERSION 1
+
+/** @ingroup iface_wp_fractional_scale_manager_v1 */
+static inline void wp_fractional_scale_manager_v1_set_user_data(
+ struct wp_fractional_scale_manager_v1* wp_fractional_scale_manager_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)wp_fractional_scale_manager_v1,
+ user_data);
+}
+
+/** @ingroup iface_wp_fractional_scale_manager_v1 */
+static inline void* wp_fractional_scale_manager_v1_get_user_data(
+ struct wp_fractional_scale_manager_v1* wp_fractional_scale_manager_v1) {
+ return wl_proxy_get_user_data(
+ (struct wl_proxy*)wp_fractional_scale_manager_v1);
+}
+
+static inline uint32_t wp_fractional_scale_manager_v1_get_version(
+ struct wp_fractional_scale_manager_v1* wp_fractional_scale_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)wp_fractional_scale_manager_v1);
+}
+
+/**
+ * @ingroup iface_wp_fractional_scale_manager_v1
+ *
+ * Informs the server that the client will not be using this protocol
+ * object anymore. This does not affect any other objects,
+ * wp_fractional_scale_v1 objects included.
+ */
+static inline void wp_fractional_scale_manager_v1_destroy(
+ struct wp_fractional_scale_manager_v1* wp_fractional_scale_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)wp_fractional_scale_manager_v1,
+ WP_FRACTIONAL_SCALE_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wp_fractional_scale_manager_v1);
+}
+
+/**
+ * @ingroup iface_wp_fractional_scale_manager_v1
+ *
+ * Create an add-on object for the the wl_surface to let the compositor
+ * request fractional scales. If the given wl_surface already has a
+ * wp_fractional_scale_v1 object associated, the fractional_scale_exists
+ * protocol error is raised.
+ */
+static inline struct wp_fractional_scale_v1*
+wp_fractional_scale_manager_v1_get_fractional_scale(
+ struct wp_fractional_scale_manager_v1* wp_fractional_scale_manager_v1,
+ struct wl_surface* surface) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)wp_fractional_scale_manager_v1,
+ WP_FRACTIONAL_SCALE_MANAGER_V1_GET_FRACTIONAL_SCALE,
+ &wp_fractional_scale_v1_interface, NULL, surface);
+
+ return (struct wp_fractional_scale_v1*)id;
+}
+
+/**
+ * @ingroup iface_wp_fractional_scale_v1
+ * @struct wp_fractional_scale_v1_listener
+ */
+struct wp_fractional_scale_v1_listener {
+ /**
+ * notify of new preferred scale
+ *
+ * Notification of a new preferred scale for this surface that
+ * the compositor suggests that the client should use.
+ *
+ * The sent scale is the numerator of a fraction with a denominator
+ * of 120.
+ * @param scale the new preferred scale
+ */
+ void (*preferred_scale)(void* data,
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1,
+ uint32_t scale);
+};
+
+/**
+ * @ingroup iface_wp_fractional_scale_v1
+ */
+static inline int wp_fractional_scale_v1_add_listener(
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1,
+ const struct wp_fractional_scale_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)wp_fractional_scale_v1,
+ (void (**)(void))listener, data);
+}
+
+#define WP_FRACTIONAL_SCALE_V1_DESTROY 0
+
+/**
+ * @ingroup iface_wp_fractional_scale_v1
+ */
+#define WP_FRACTIONAL_SCALE_V1_PREFERRED_SCALE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_wp_fractional_scale_v1
+ */
+#define WP_FRACTIONAL_SCALE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_wp_fractional_scale_v1 */
+static inline void wp_fractional_scale_v1_set_user_data(
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)wp_fractional_scale_v1, user_data);
+}
+
+/** @ingroup iface_wp_fractional_scale_v1 */
+static inline void* wp_fractional_scale_v1_get_user_data(
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)wp_fractional_scale_v1);
+}
+
+static inline uint32_t wp_fractional_scale_v1_get_version(
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)wp_fractional_scale_v1);
+}
+
+/**
+ * @ingroup iface_wp_fractional_scale_v1
+ *
+ * Destroy the fractional scale object. When this object is destroyed,
+ * preferred_scale events will no longer be sent.
+ */
+static inline void wp_fractional_scale_v1_destroy(
+ struct wp_fractional_scale_v1* wp_fractional_scale_v1) {
+ wl_proxy_marshal((struct wl_proxy*)wp_fractional_scale_v1,
+ WP_FRACTIONAL_SCALE_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wp_fractional_scale_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/fractional-scale-v1-protocol.c b/widget/gtk/wayland/fractional-scale-v1-protocol.c
new file mode 100644
index 0000000000..4841533289
--- /dev/null
+++ b/widget/gtk/wayland/fractional-scale-v1-protocol.c
@@ -0,0 +1,73 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2022 Kenny Levinsen
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+# define WL_PRIVATE __attribute__((visibility("hidden")))
+#else
+# define WL_PRIVATE
+#endif
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_surface_interface;
+#pragma GCC visibility pop
+extern const struct wl_interface wp_fractional_scale_v1_interface;
+
+static const struct wl_interface* fractional_scale_v1_types[] = {
+ NULL,
+ &wp_fractional_scale_v1_interface,
+ &wl_surface_interface,
+};
+
+static const struct wl_message wp_fractional_scale_manager_v1_requests[] = {
+ {"destroy", "", fractional_scale_v1_types + 0},
+ {"get_fractional_scale", "no", fractional_scale_v1_types + 1},
+};
+
+WL_PRIVATE const struct wl_interface wp_fractional_scale_manager_v1_interface =
+ {
+ "wp_fractional_scale_manager_v1", 1, 2,
+ wp_fractional_scale_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message wp_fractional_scale_v1_requests[] = {
+ {"destroy", "", fractional_scale_v1_types + 0},
+};
+
+static const struct wl_message wp_fractional_scale_v1_events[] = {
+ {"preferred_scale", "u", fractional_scale_v1_types + 0},
+};
+
+WL_PRIVATE const struct wl_interface wp_fractional_scale_v1_interface = {
+ "wp_fractional_scale_v1", 1, 1,
+ wp_fractional_scale_v1_requests, 1, wp_fractional_scale_v1_events,
+};
diff --git a/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h b/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..3ae2303b3c
--- /dev/null
+++ b/widget/gtk/wayland/idle-inhibit-unstable-v1-client-protocol.h
@@ -0,0 +1,228 @@
+/* Generated by wayland-scanner 1.16.0 */
+
+#ifndef IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define IDLE_INHIBIT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_idle_inhibit_unstable_v1 The idle_inhibit_unstable_v1 protocol
+ * @section page_ifaces_idle_inhibit_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_idle_inhibit_manager_v1 - control behavior when
+ * display idles
+ * - @subpage page_iface_zwp_idle_inhibitor_v1 - context object for inhibiting
+ * idle behavior
+ * @section page_copyright_idle_inhibit_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_surface;
+struct zwp_idle_inhibit_manager_v1;
+struct zwp_idle_inhibitor_v1;
+
+/**
+ * @page page_iface_zwp_idle_inhibit_manager_v1 zwp_idle_inhibit_manager_v1
+ * @section page_iface_zwp_idle_inhibit_manager_v1_desc Description
+ *
+ * This interface permits inhibiting the idle behavior such as screen
+ * blanking, locking, and screensaving. The client binds the idle manager
+ * globally, then creates idle-inhibitor objects for each surface.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zwp_idle_inhibit_manager_v1_api API
+ * See @ref iface_zwp_idle_inhibit_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_idle_inhibit_manager_v1 The zwp_idle_inhibit_manager_v1
+ * interface
+ *
+ * This interface permits inhibiting the idle behavior such as screen
+ * blanking, locking, and screensaving. The client binds the idle manager
+ * globally, then creates idle-inhibitor objects for each surface.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zwp_idle_inhibit_manager_v1_interface;
+/**
+ * @page page_iface_zwp_idle_inhibitor_v1 zwp_idle_inhibitor_v1
+ * @section page_iface_zwp_idle_inhibitor_v1_desc Description
+ *
+ * An idle inhibitor prevents the output that the associated surface is
+ * visible on from being set to a state where it is not visually usable due
+ * to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+ * save, etc.) Any screensaver processes are also blocked from displaying.
+ *
+ * If the surface is destroyed, unmapped, becomes occluded, loses
+ * visibility, or otherwise becomes not visually relevant for the user, the
+ * idle inhibitor will not be honored by the compositor; if the surface
+ * subsequently regains visibility the inhibitor takes effect once again.
+ * Likewise, the inhibitor isn't honored if the system was already idled at
+ * the time the inhibitor was established, although if the system later
+ * de-idles and re-idles the inhibitor will take effect.
+ * @section page_iface_zwp_idle_inhibitor_v1_api API
+ * See @ref iface_zwp_idle_inhibitor_v1.
+ */
+/**
+ * @defgroup iface_zwp_idle_inhibitor_v1 The zwp_idle_inhibitor_v1 interface
+ *
+ * An idle inhibitor prevents the output that the associated surface is
+ * visible on from being set to a state where it is not visually usable due
+ * to lack of user interaction (e.g. blanked, dimmed, locked, set to power
+ * save, etc.) Any screensaver processes are also blocked from displaying.
+ *
+ * If the surface is destroyed, unmapped, becomes occluded, loses
+ * visibility, or otherwise becomes not visually relevant for the user, the
+ * idle inhibitor will not be honored by the compositor; if the surface
+ * subsequently regains visibility the inhibitor takes effect once again.
+ * Likewise, the inhibitor isn't honored if the system was already idled at
+ * the time the inhibitor was established, although if the system later
+ * de-idles and re-idles the inhibitor will take effect.
+ */
+extern const struct wl_interface zwp_idle_inhibitor_v1_interface;
+
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY 0
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR 1
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ */
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ */
+#define ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_idle_inhibit_manager_v1 */
+static inline void zwp_idle_inhibit_manager_v1_set_user_data(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_idle_inhibit_manager_v1 */
+static inline void* zwp_idle_inhibit_manager_v1_get_user_data(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+static inline uint32_t zwp_idle_inhibit_manager_v1_get_version(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ *
+ * Destroy the inhibit manager.
+ */
+static inline void zwp_idle_inhibit_manager_v1_destroy(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ ZWP_IDLE_INHIBIT_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_idle_inhibit_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibit_manager_v1
+ *
+ * Create a new inhibitor object associated with the given surface.
+ */
+static inline struct zwp_idle_inhibitor_v1*
+zwp_idle_inhibit_manager_v1_create_inhibitor(
+ struct zwp_idle_inhibit_manager_v1* zwp_idle_inhibit_manager_v1,
+ struct wl_surface* surface) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_idle_inhibit_manager_v1,
+ ZWP_IDLE_INHIBIT_MANAGER_V1_CREATE_INHIBITOR,
+ &zwp_idle_inhibitor_v1_interface, NULL, surface);
+
+ return (struct zwp_idle_inhibitor_v1*)id;
+}
+
+#define ZWP_IDLE_INHIBITOR_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zwp_idle_inhibitor_v1
+ */
+#define ZWP_IDLE_INHIBITOR_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_idle_inhibitor_v1 */
+static inline void zwp_idle_inhibitor_v1_set_user_data(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_idle_inhibitor_v1, user_data);
+}
+
+/** @ingroup iface_zwp_idle_inhibitor_v1 */
+static inline void* zwp_idle_inhibitor_v1_get_user_data(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+static inline uint32_t zwp_idle_inhibitor_v1_get_version(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+/**
+ * @ingroup iface_zwp_idle_inhibitor_v1
+ *
+ * Remove the inhibitor effect from the associated wl_surface.
+ */
+static inline void zwp_idle_inhibitor_v1_destroy(
+ struct zwp_idle_inhibitor_v1* zwp_idle_inhibitor_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_idle_inhibitor_v1,
+ ZWP_IDLE_INHIBITOR_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_idle_inhibitor_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c b/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c
new file mode 100644
index 0000000000..579095e003
--- /dev/null
+++ b/widget/gtk/wayland/idle-inhibit-unstable-v1-protocol.c
@@ -0,0 +1,60 @@
+/* Generated by wayland-scanner 1.16.0 */
+
+/*
+ * Copyright © 2015 Samsung Electronics Co., Ltd
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface zwp_idle_inhibitor_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* types[] = {
+ &zwp_idle_inhibitor_v1_interface,
+ &wl_surface_interface,
+};
+
+static const struct wl_message zwp_idle_inhibit_manager_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"create_inhibitor", "no", types + 0},
+};
+
+const struct wl_interface zwp_idle_inhibit_manager_v1_interface = {
+ "zwp_idle_inhibit_manager_v1", 1, 2,
+ zwp_idle_inhibit_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zwp_idle_inhibitor_v1_requests[] = {
+ {"destroy", "", types + 0},
+};
+
+const struct wl_interface zwp_idle_inhibitor_v1_interface = {
+ "zwp_idle_inhibitor_v1", 1, 1, zwp_idle_inhibitor_v1_requests, 0, NULL,
+};
diff --git a/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h b/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..cff0426a9c
--- /dev/null
+++ b/widget/gtk/wayland/linux-dmabuf-unstable-v1-client-protocol.h
@@ -0,0 +1,650 @@
+/* Generated by wayland-scanner 1.17.0 */
+
+#ifndef LINUX_DMABUF_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define LINUX_DMABUF_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_linux_dmabuf_unstable_v1 The linux_dmabuf_unstable_v1 protocol
+ * @section page_ifaces_linux_dmabuf_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_linux_dmabuf_v1 - factory for creating dmabuf-based
+ * wl_buffers
+ * - @subpage page_iface_zwp_linux_buffer_params_v1 - parameters for creating a
+ * dmabuf-based wl_buffer
+ * @section page_copyright_linux_dmabuf_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_buffer;
+struct zwp_linux_buffer_params_v1;
+struct zwp_linux_dmabuf_v1;
+
+/**
+ * @page page_iface_zwp_linux_dmabuf_v1 zwp_linux_dmabuf_v1
+ * @section page_iface_zwp_linux_dmabuf_v1_desc Description
+ *
+ * Following the interfaces from:
+ * https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
+ * and the Linux DRM sub-system's AddFb2 ioctl.
+ *
+ * This interface offers ways to create generic dmabuf-based
+ * wl_buffers. Immediately after a client binds to this interface,
+ * the set of supported formats and format modifiers is sent with
+ * 'format' and 'modifier' events.
+ *
+ * The following are required from clients:
+ *
+ * - Clients must ensure that either all data in the dma-buf is
+ * coherent for all subsequent read access or that coherency is
+ * correctly handled by the underlying kernel-side dma-buf
+ * implementation.
+ *
+ * - Don't make any more attachments after sending the buffer to the
+ * compositor. Making more attachments later increases the risk of
+ * the compositor not being able to use (re-import) an existing
+ * dmabuf-based wl_buffer.
+ *
+ * The underlying graphics stack must ensure the following:
+ *
+ * - The dmabuf file descriptors relayed to the server will stay valid
+ * for the whole lifetime of the wl_buffer. This means the server may
+ * at any time use those fds to import the dmabuf into any kernel
+ * sub-system that might accept it.
+ *
+ * To create a wl_buffer from one or more dmabufs, a client creates a
+ * zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
+ * request. All planes required by the intended format are added with
+ * the 'add' request. Finally, a 'create' or 'create_immed' request is
+ * issued, which has the following outcome depending on the import success.
+ *
+ * The 'create' request,
+ * - on success, triggers a 'created' event which provides the final
+ * wl_buffer to the client.
+ * - on failure, triggers a 'failed' event to convey that the server
+ * cannot use the dmabufs received from the client.
+ *
+ * For the 'create_immed' request,
+ * - on success, the server immediately imports the added dmabufs to
+ * create a wl_buffer. No event is sent from the server in this case.
+ * - on failure, the server can choose to either:
+ * - terminate the client by raising a fatal error.
+ * - mark the wl_buffer as failed, and send a 'failed' event to the
+ * client. If the client uses a failed wl_buffer as an argument to any
+ * request, the behaviour is compositor implementation-defined.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ * @section page_iface_zwp_linux_dmabuf_v1_api API
+ * See @ref iface_zwp_linux_dmabuf_v1.
+ */
+/**
+ * @defgroup iface_zwp_linux_dmabuf_v1 The zwp_linux_dmabuf_v1 interface
+ *
+ * Following the interfaces from:
+ * https://www.khronos.org/registry/egl/extensions/EXT/EGL_EXT_image_dma_buf_import.txt
+ * and the Linux DRM sub-system's AddFb2 ioctl.
+ *
+ * This interface offers ways to create generic dmabuf-based
+ * wl_buffers. Immediately after a client binds to this interface,
+ * the set of supported formats and format modifiers is sent with
+ * 'format' and 'modifier' events.
+ *
+ * The following are required from clients:
+ *
+ * - Clients must ensure that either all data in the dma-buf is
+ * coherent for all subsequent read access or that coherency is
+ * correctly handled by the underlying kernel-side dma-buf
+ * implementation.
+ *
+ * - Don't make any more attachments after sending the buffer to the
+ * compositor. Making more attachments later increases the risk of
+ * the compositor not being able to use (re-import) an existing
+ * dmabuf-based wl_buffer.
+ *
+ * The underlying graphics stack must ensure the following:
+ *
+ * - The dmabuf file descriptors relayed to the server will stay valid
+ * for the whole lifetime of the wl_buffer. This means the server may
+ * at any time use those fds to import the dmabuf into any kernel
+ * sub-system that might accept it.
+ *
+ * To create a wl_buffer from one or more dmabufs, a client creates a
+ * zwp_linux_dmabuf_params_v1 object with a zwp_linux_dmabuf_v1.create_params
+ * request. All planes required by the intended format are added with
+ * the 'add' request. Finally, a 'create' or 'create_immed' request is
+ * issued, which has the following outcome depending on the import success.
+ *
+ * The 'create' request,
+ * - on success, triggers a 'created' event which provides the final
+ * wl_buffer to the client.
+ * - on failure, triggers a 'failed' event to convey that the server
+ * cannot use the dmabufs received from the client.
+ *
+ * For the 'create_immed' request,
+ * - on success, the server immediately imports the added dmabufs to
+ * create a wl_buffer. No event is sent from the server in this case.
+ * - on failure, the server can choose to either:
+ * - terminate the client by raising a fatal error.
+ * - mark the wl_buffer as failed, and send a 'failed' event to the
+ * client. If the client uses a failed wl_buffer as an argument to any
+ * request, the behaviour is compositor implementation-defined.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible changes
+ * may be added together with the corresponding interface version bump.
+ * Backward incompatible changes are done by bumping the version number in
+ * the protocol and interface names and resetting the interface version.
+ * Once the protocol is to be declared stable, the 'z' prefix and the
+ * version number in the protocol and interface names are removed and the
+ * interface version number is reset.
+ */
+extern const struct wl_interface zwp_linux_dmabuf_v1_interface;
+/**
+ * @page page_iface_zwp_linux_buffer_params_v1 zwp_linux_buffer_params_v1
+ * @section page_iface_zwp_linux_buffer_params_v1_desc Description
+ *
+ * This temporary object is a collection of dmabufs and other
+ * parameters that together form a single logical buffer. The temporary
+ * object may eventually create one wl_buffer unless cancelled by
+ * destroying it before requesting 'create'.
+ *
+ * Single-planar formats only require one dmabuf, however
+ * multi-planar formats may require more than one dmabuf. For all
+ * formats, an 'add' request must be called once per plane (even if the
+ * underlying dmabuf fd is identical).
+ *
+ * You must use consecutive plane indices ('plane_idx' argument for 'add')
+ * from zero to the number of planes used by the drm_fourcc format code.
+ * All planes required by the format must be given exactly once, but can
+ * be given in any order. Each plane index can be set only once.
+ * @section page_iface_zwp_linux_buffer_params_v1_api API
+ * See @ref iface_zwp_linux_buffer_params_v1.
+ */
+/**
+ * @defgroup iface_zwp_linux_buffer_params_v1 The zwp_linux_buffer_params_v1
+ * interface
+ *
+ * This temporary object is a collection of dmabufs and other
+ * parameters that together form a single logical buffer. The temporary
+ * object may eventually create one wl_buffer unless cancelled by
+ * destroying it before requesting 'create'.
+ *
+ * Single-planar formats only require one dmabuf, however
+ * multi-planar formats may require more than one dmabuf. For all
+ * formats, an 'add' request must be called once per plane (even if the
+ * underlying dmabuf fd is identical).
+ *
+ * You must use consecutive plane indices ('plane_idx' argument for 'add')
+ * from zero to the number of planes used by the drm_fourcc format code.
+ * All planes required by the format must be given exactly once, but can
+ * be given in any order. Each plane index can be set only once.
+ */
+extern const struct wl_interface zwp_linux_buffer_params_v1_interface;
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ * @struct zwp_linux_dmabuf_v1_listener
+ */
+struct zwp_linux_dmabuf_v1_listener {
+ /**
+ * supported buffer format
+ *
+ * This event advertises one buffer format that the server
+ * supports. All the supported formats are advertised once when the
+ * client binds to this interface. A roundtrip after binding
+ * guarantees that the client has received all supported formats.
+ *
+ * For the definition of the format codes, see the
+ * zwp_linux_buffer_params_v1::create request.
+ *
+ * Warning: the 'format' event is likely to be deprecated and
+ * replaced with the 'modifier' event introduced in
+ * zwp_linux_dmabuf_v1 version 3, described below. Please refrain
+ * from using the information received from this event.
+ * @param format DRM_FORMAT code
+ */
+ void (*format)(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ uint32_t format);
+ /**
+ * supported buffer format modifier
+ *
+ * This event advertises the formats that the server supports,
+ * along with the modifiers supported for each format. All the
+ * supported modifiers for all the supported formats are advertised
+ * once when the client binds to this interface. A roundtrip after
+ * binding guarantees that the client has received all supported
+ * format-modifier pairs.
+ *
+ * For the definition of the format and modifier codes, see the
+ * zwp_linux_buffer_params_v1::create request.
+ * @param format DRM_FORMAT code
+ * @param modifier_hi high 32 bits of layout modifier
+ * @param modifier_lo low 32 bits of layout modifier
+ * @since 3
+ */
+ void (*modifier)(void* data, struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ uint32_t format, uint32_t modifier_hi, uint32_t modifier_lo);
+};
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+static inline int zwp_linux_dmabuf_v1_add_listener(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1,
+ const struct zwp_linux_dmabuf_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_linux_dmabuf_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_LINUX_DMABUF_V1_DESTROY 0
+#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS 1
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_FORMAT_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_MODIFIER_SINCE_VERSION 3
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ */
+#define ZWP_LINUX_DMABUF_V1_CREATE_PARAMS_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_linux_dmabuf_v1 */
+static inline void zwp_linux_dmabuf_v1_set_user_data(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_linux_dmabuf_v1, user_data);
+}
+
+/** @ingroup iface_zwp_linux_dmabuf_v1 */
+static inline void* zwp_linux_dmabuf_v1_get_user_data(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+static inline uint32_t zwp_linux_dmabuf_v1_get_version(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ *
+ * Objects created through this interface, especially wl_buffers, will
+ * remain valid.
+ */
+static inline void zwp_linux_dmabuf_v1_destroy(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_dmabuf_v1,
+ ZWP_LINUX_DMABUF_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_linux_dmabuf_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_dmabuf_v1
+ *
+ * This temporary object is used to collect multiple dmabuf handles into
+ * a single batch to create a wl_buffer. It can only be used once and
+ * should be destroyed after a 'created' or 'failed' event has been
+ * received.
+ */
+static inline struct zwp_linux_buffer_params_v1*
+zwp_linux_dmabuf_v1_create_params(
+ struct zwp_linux_dmabuf_v1* zwp_linux_dmabuf_v1) {
+ struct wl_proxy* params_id;
+
+ params_id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_linux_dmabuf_v1, ZWP_LINUX_DMABUF_V1_CREATE_PARAMS,
+ &zwp_linux_buffer_params_v1_interface, NULL);
+
+ return (struct zwp_linux_buffer_params_v1*)params_id;
+}
+
+#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM
+# define ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM
+enum zwp_linux_buffer_params_v1_error {
+ /**
+ * the dmabuf_batch object has already been used to create a wl_buffer
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ALREADY_USED = 0,
+ /**
+ * plane index out of bounds
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_IDX = 1,
+ /**
+ * the plane index was already set
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_PLANE_SET = 2,
+ /**
+ * missing or too many planes to create a buffer
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INCOMPLETE = 3,
+ /**
+ * format not supported
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_FORMAT = 4,
+ /**
+ * invalid width or height
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_DIMENSIONS = 5,
+ /**
+ * offset + stride * height goes out of dmabuf bounds
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_OUT_OF_BOUNDS = 6,
+ /**
+ * invalid wl_buffer resulted from importing dmabufs via the
+ * create_immed request on given buffer_params
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_INVALID_WL_BUFFER = 7,
+};
+#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_ERROR_ENUM */
+
+#ifndef ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM
+# define ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM
+enum zwp_linux_buffer_params_v1_flags {
+ /**
+ * contents are y-inverted
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_Y_INVERT = 1,
+ /**
+ * content is interlaced
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_INTERLACED = 2,
+ /**
+ * bottom field first
+ */
+ ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_BOTTOM_FIRST = 4,
+};
+#endif /* ZWP_LINUX_BUFFER_PARAMS_V1_FLAGS_ENUM */
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ * @struct zwp_linux_buffer_params_v1_listener
+ */
+struct zwp_linux_buffer_params_v1_listener {
+ /**
+ * buffer creation succeeded
+ *
+ * This event indicates that the attempted buffer creation was
+ * successful. It provides the new wl_buffer referencing the
+ * dmabuf(s).
+ *
+ * Upon receiving this event, the client should destroy the
+ * zlinux_dmabuf_params object.
+ * @param buffer the newly created wl_buffer
+ */
+ void (*created)(void* data,
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ struct wl_buffer* buffer);
+ /**
+ * buffer creation failed
+ *
+ * This event indicates that the attempted buffer creation has
+ * failed. It usually means that one of the dmabuf constraints has
+ * not been fulfilled.
+ *
+ * Upon receiving this event, the client should destroy the
+ * zlinux_buffer_params object.
+ */
+ void (*failed)(void* data,
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1);
+};
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+static inline int zwp_linux_buffer_params_v1_add_listener(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ const struct zwp_linux_buffer_params_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY 0
+#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD 1
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE 2
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED 3
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_FAILED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_ADD_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ */
+#define ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED_SINCE_VERSION 2
+
+/** @ingroup iface_zwp_linux_buffer_params_v1 */
+static inline void zwp_linux_buffer_params_v1_set_user_data(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_linux_buffer_params_v1 */
+static inline void* zwp_linux_buffer_params_v1_get_user_data(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+static inline uint32_t zwp_linux_buffer_params_v1_get_version(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * Cleans up the temporary data sent to the server for dmabuf-based
+ * wl_buffer creation.
+ */
+static inline void zwp_linux_buffer_params_v1_destroy(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_linux_buffer_params_v1);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This request adds one dmabuf to the set in this
+ * zwp_linux_buffer_params_v1.
+ *
+ * The 64-bit unsigned value combined from modifier_hi and modifier_lo
+ * is the dmabuf layout modifier. DRM AddFB2 ioctl calls this the
+ * fb modifier, which is defined in drm_mode.h of Linux UAPI.
+ * This is an opaque token. Drivers use this token to express tiling,
+ * compression, etc. driver-specific modifications to the base format
+ * defined by the DRM fourcc code.
+ *
+ * This request raises the PLANE_IDX error if plane_idx is too large.
+ * The error PLANE_SET is raised if attempting to set a plane that
+ * was already set.
+ */
+static inline void zwp_linux_buffer_params_v1_add(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1, int32_t fd,
+ uint32_t plane_idx, uint32_t offset, uint32_t stride, uint32_t modifier_hi,
+ uint32_t modifier_lo) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_ADD, fd, plane_idx, offset,
+ stride, modifier_hi, modifier_lo);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This asks for creation of a wl_buffer from the added dmabuf
+ * buffers. The wl_buffer is not created immediately but returned via
+ * the 'created' event if the dmabuf sharing succeeds. The sharing
+ * may fail at runtime for reasons a client cannot predict, in
+ * which case the 'failed' event is triggered.
+ *
+ * The 'format' argument is a DRM_FORMAT code, as defined by the
+ * libdrm's drm_fourcc.h. The Linux kernel's DRM sub-system is the
+ * authoritative source on how the format codes should work.
+ *
+ * The 'flags' is a bitfield of the flags defined in enum "flags".
+ * 'y_invert' means the that the image needs to be y-flipped.
+ *
+ * Flag 'interlaced' means that the frame in the buffer is not
+ * progressive as usual, but interlaced. An interlaced buffer as
+ * supported here must always contain both top and bottom fields.
+ * The top field always begins on the first pixel row. The temporal
+ * ordering between the two fields is top field first, unless
+ * 'bottom_first' is specified. It is undefined whether 'bottom_first'
+ * is ignored if 'interlaced' is not set.
+ *
+ * This protocol does not convey any information about field rate,
+ * duration, or timing, other than the relative ordering between the
+ * two fields in one buffer. A compositor may have to estimate the
+ * intended field rate from the incoming buffer rate. It is undefined
+ * whether the time of receiving wl_surface.commit with a new buffer
+ * attached, applying the wl_surface state, wl_surface.frame callback
+ * trigger, presentation, or any other point in the compositor cycle
+ * is used to measure the frame or field times. There is no support
+ * for detecting missed or late frames/fields/buffers either, and
+ * there is no support whatsoever for cooperating with interlaced
+ * compositor output.
+ *
+ * The composited image quality resulting from the use of interlaced
+ * buffers is explicitly undefined. A compositor may use elaborate
+ * hardware features or software to deinterlace and create progressive
+ * output frames from a sequence of interlaced input buffers, or it
+ * may produce substandard image quality. However, compositors that
+ * cannot guarantee reasonable image quality in all cases are recommended
+ * to just reject all interlaced buffers.
+ *
+ * Any argument errors, including non-positive width or height,
+ * mismatch between the number of planes and the format, bad
+ * format, bad offset or stride, may be indicated by fatal protocol
+ * errors: INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS,
+ * OUT_OF_BOUNDS.
+ *
+ * Dmabuf import errors in the server that are not obvious client
+ * bugs are returned via the 'failed' event as non-fatal. This
+ * allows attempting dmabuf sharing and falling back in the client
+ * if it fails.
+ *
+ * This request can be sent only once in the object's lifetime, after
+ * which the only legal request is destroy. This object should be
+ * destroyed after issuing a 'create' request. Attempting to use this
+ * object after issuing 'create' raises ALREADY_USED protocol error.
+ *
+ * It is not mandatory to issue 'create'. If a client wants to
+ * cancel the buffer creation, it can just destroy this object.
+ */
+static inline void zwp_linux_buffer_params_v1_create(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ int32_t width, int32_t height, uint32_t format, uint32_t flags) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_CREATE, width, height, format,
+ flags);
+}
+
+/**
+ * @ingroup iface_zwp_linux_buffer_params_v1
+ *
+ * This asks for immediate creation of a wl_buffer by importing the
+ * added dmabufs.
+ *
+ * In case of import success, no event is sent from the server, and the
+ * wl_buffer is ready to be used by the client.
+ *
+ * Upon import failure, either of the following may happen, as seen fit
+ * by the implementation:
+ * - the client is terminated with one of the following fatal protocol
+ * errors:
+ * - INCOMPLETE, INVALID_FORMAT, INVALID_DIMENSIONS, OUT_OF_BOUNDS,
+ * in case of argument errors such as mismatch between the number
+ * of planes and the format, bad format, non-positive width or
+ * height, or bad offset or stride.
+ * - INVALID_WL_BUFFER, in case the cause for failure is unknown or
+ * plaform specific.
+ * - the server creates an invalid wl_buffer, marks it as failed and
+ * sends a 'failed' event to the client. The result of using this
+ * invalid wl_buffer as an argument in any request by the client is
+ * defined by the compositor implementation.
+ *
+ * This takes the same arguments as a 'create' request, and obeys the
+ * same restrictions.
+ */
+static inline struct wl_buffer* zwp_linux_buffer_params_v1_create_immed(
+ struct zwp_linux_buffer_params_v1* zwp_linux_buffer_params_v1,
+ int32_t width, int32_t height, uint32_t format, uint32_t flags) {
+ struct wl_proxy* buffer_id;
+
+ buffer_id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_linux_buffer_params_v1,
+ ZWP_LINUX_BUFFER_PARAMS_V1_CREATE_IMMED, &wl_buffer_interface, NULL,
+ width, height, format, flags);
+
+ return (struct wl_buffer*)buffer_id;
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c b/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c
new file mode 100644
index 0000000000..51c1e8e575
--- /dev/null
+++ b/widget/gtk/wayland/linux-dmabuf-unstable-v1-protocol.c
@@ -0,0 +1,81 @@
+/* Generated by wayland-scanner 1.17.0 */
+
+/*
+ * Copyright © 2014, 2015 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_buffer_interface;
+extern const struct wl_interface zwp_linux_buffer_params_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &zwp_linux_buffer_params_v1_interface,
+ &wl_buffer_interface,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &wl_buffer_interface,
+};
+
+static const struct wl_message zwp_linux_dmabuf_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"create_params", "n", types + 6},
+};
+
+static const struct wl_message zwp_linux_dmabuf_v1_events[] = {
+ {"format", "u", types + 0},
+ {"modifier", "3uuu", types + 0},
+};
+
+const struct wl_interface zwp_linux_dmabuf_v1_interface = {
+ "zwp_linux_dmabuf_v1", 3, 2,
+ zwp_linux_dmabuf_v1_requests, 2, zwp_linux_dmabuf_v1_events,
+};
+
+static const struct wl_message zwp_linux_buffer_params_v1_requests[] = {
+ {"destroy", "", types + 0},
+ {"add", "huuuuu", types + 0},
+ {"create", "iiuu", types + 0},
+ {"create_immed", "2niiuu", types + 7},
+};
+
+static const struct wl_message zwp_linux_buffer_params_v1_events[] = {
+ {"created", "n", types + 12},
+ {"failed", "", types + 0},
+};
+
+const struct wl_interface zwp_linux_buffer_params_v1_interface = {
+ "zwp_linux_buffer_params_v1", 3, 4,
+ zwp_linux_buffer_params_v1_requests, 2, zwp_linux_buffer_params_v1_events,
+};
diff --git a/widget/gtk/wayland/moz.build b/widget/gtk/wayland/moz.build
new file mode 100644
index 0000000000..e033187a3b
--- /dev/null
+++ b/widget/gtk/wayland/moz.build
@@ -0,0 +1,37 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "Widget: Gtk")
+
+SOURCES += [
+ "fractional-scale-v1-protocol.c",
+ "idle-inhibit-unstable-v1-protocol.c",
+ "linux-dmabuf-unstable-v1-protocol.c",
+ "pointer-constraints-unstable-v1-protocol.c",
+ "relative-pointer-unstable-v1-protocol.c",
+ "viewporter-protocol.c",
+ "xdg-activation-v1-protocol.c",
+ "xdg-output-unstable-v1-protocol.c",
+]
+
+EXPORTS.mozilla.widget += [
+ "fractional-scale-v1-client-protocol.h",
+ "idle-inhibit-unstable-v1-client-protocol.h",
+ "linux-dmabuf-unstable-v1-client-protocol.h",
+ "pointer-constraints-unstable-v1-client-protocol.h",
+ "relative-pointer-unstable-v1-client-protocol.h",
+ "viewporter-client-protocol.h",
+ "xdg-activation-v1-client-protocol.h",
+ "xdg-output-unstable-v1-client-protocol.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+CFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
diff --git a/widget/gtk/wayland/pointer-constraints-unstable-v1-client-protocol.h b/widget/gtk/wayland/pointer-constraints-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..0f38dee226
--- /dev/null
+++ b/widget/gtk/wayland/pointer-constraints-unstable-v1-client-protocol.h
@@ -0,0 +1,650 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define POINTER_CONSTRAINTS_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_pointer_constraints_unstable_v1 The
+ * pointer_constraints_unstable_v1 protocol protocol for constraining pointer
+ * motions
+ *
+ * @section page_desc_pointer_constraints_unstable_v1 Description
+ *
+ * This protocol specifies a set of interfaces used for adding constraints to
+ * the motion of a pointer. Possible constraints include confining pointer
+ * motions to a given region, or locking it to its current position.
+ *
+ * In order to constrain the pointer, a client must first bind the global
+ * interface "wp_pointer_constraints" which, if a compositor supports pointer
+ * constraints, is exposed by the registry. Using the bound global object, the
+ * client uses the request that corresponds to the type of constraint it wants
+ * to make. See wp_pointer_constraints for more details.
+ *
+ * Warning! The protocol described in this file is experimental and backward
+ * incompatible changes may be made. Backward compatible changes may be added
+ * together with the corresponding interface version bump. Backward
+ * incompatible changes are done by bumping the version number in the protocol
+ * and interface names and resetting the interface version. Once the protocol
+ * is to be declared stable, the 'z' prefix and the version number in the
+ * protocol and interface names are removed and the interface version number is
+ * reset.
+ *
+ * @section page_ifaces_pointer_constraints_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_pointer_constraints_v1 - constrain the movement of
+ * a pointer
+ * - @subpage page_iface_zwp_locked_pointer_v1 - receive relative pointer motion
+ * events
+ * - @subpage page_iface_zwp_confined_pointer_v1 - confined pointer object
+ * @section page_copyright_pointer_constraints_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2014 Jonas Ådahl
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_pointer;
+struct wl_region;
+struct wl_surface;
+struct zwp_confined_pointer_v1;
+struct zwp_locked_pointer_v1;
+struct zwp_pointer_constraints_v1;
+
+/**
+ * @page page_iface_zwp_pointer_constraints_v1 zwp_pointer_constraints_v1
+ * @section page_iface_zwp_pointer_constraints_v1_desc Description
+ *
+ * The global interface exposing pointer constraining functionality. It
+ * exposes two requests: lock_pointer for locking the pointer to its
+ * position, and confine_pointer for locking the pointer to a region.
+ *
+ * The lock_pointer and confine_pointer requests create the objects
+ * wp_locked_pointer and wp_confined_pointer respectively, and the client can
+ * use these objects to interact with the lock.
+ *
+ * For any surface, only one lock or confinement may be active across all
+ * wl_pointer objects of the same seat. If a lock or confinement is requested
+ * when another lock or confinement is active or requested on the same surface
+ * and with any of the wl_pointer objects of the same seat, an
+ * 'already_constrained' error will be raised.
+ * @section page_iface_zwp_pointer_constraints_v1_api API
+ * See @ref iface_zwp_pointer_constraints_v1.
+ */
+/**
+ * @defgroup iface_zwp_pointer_constraints_v1 The zwp_pointer_constraints_v1
+ * interface
+ *
+ * The global interface exposing pointer constraining functionality. It
+ * exposes two requests: lock_pointer for locking the pointer to its
+ * position, and confine_pointer for locking the pointer to a region.
+ *
+ * The lock_pointer and confine_pointer requests create the objects
+ * wp_locked_pointer and wp_confined_pointer respectively, and the client can
+ * use these objects to interact with the lock.
+ *
+ * For any surface, only one lock or confinement may be active across all
+ * wl_pointer objects of the same seat. If a lock or confinement is requested
+ * when another lock or confinement is active or requested on the same surface
+ * and with any of the wl_pointer objects of the same seat, an
+ * 'already_constrained' error will be raised.
+ */
+extern const struct wl_interface zwp_pointer_constraints_v1_interface;
+/**
+ * @page page_iface_zwp_locked_pointer_v1 zwp_locked_pointer_v1
+ * @section page_iface_zwp_locked_pointer_v1_desc Description
+ *
+ * The wp_locked_pointer interface represents a locked pointer state.
+ *
+ * While the lock of this object is active, the wl_pointer objects of the
+ * associated seat will not emit any wl_pointer.motion events.
+ *
+ * This object will send the event 'locked' when the lock is activated.
+ * Whenever the lock is activated, it is guaranteed that the locked surface
+ * will already have received pointer focus and that the pointer will be
+ * within the region passed to the request creating this object.
+ *
+ * To unlock the pointer, send the destroy request. This will also destroy
+ * the wp_locked_pointer object.
+ *
+ * If the compositor decides to unlock the pointer the unlocked event is
+ * sent. See wp_locked_pointer.unlock for details.
+ *
+ * When unlocking, the compositor may warp the cursor position to the set
+ * cursor position hint. If it does, it will not result in any relative
+ * motion events emitted via wp_relative_pointer.
+ *
+ * If the surface the lock was requested on is destroyed and the lock is not
+ * yet activated, the wp_locked_pointer object is now defunct and must be
+ * destroyed.
+ * @section page_iface_zwp_locked_pointer_v1_api API
+ * See @ref iface_zwp_locked_pointer_v1.
+ */
+/**
+ * @defgroup iface_zwp_locked_pointer_v1 The zwp_locked_pointer_v1 interface
+ *
+ * The wp_locked_pointer interface represents a locked pointer state.
+ *
+ * While the lock of this object is active, the wl_pointer objects of the
+ * associated seat will not emit any wl_pointer.motion events.
+ *
+ * This object will send the event 'locked' when the lock is activated.
+ * Whenever the lock is activated, it is guaranteed that the locked surface
+ * will already have received pointer focus and that the pointer will be
+ * within the region passed to the request creating this object.
+ *
+ * To unlock the pointer, send the destroy request. This will also destroy
+ * the wp_locked_pointer object.
+ *
+ * If the compositor decides to unlock the pointer the unlocked event is
+ * sent. See wp_locked_pointer.unlock for details.
+ *
+ * When unlocking, the compositor may warp the cursor position to the set
+ * cursor position hint. If it does, it will not result in any relative
+ * motion events emitted via wp_relative_pointer.
+ *
+ * If the surface the lock was requested on is destroyed and the lock is not
+ * yet activated, the wp_locked_pointer object is now defunct and must be
+ * destroyed.
+ */
+extern const struct wl_interface zwp_locked_pointer_v1_interface;
+/**
+ * @page page_iface_zwp_confined_pointer_v1 zwp_confined_pointer_v1
+ * @section page_iface_zwp_confined_pointer_v1_desc Description
+ *
+ * The wp_confined_pointer interface represents a confined pointer state.
+ *
+ * This object will send the event 'confined' when the confinement is
+ * activated. Whenever the confinement is activated, it is guaranteed that
+ * the surface the pointer is confined to will already have received pointer
+ * focus and that the pointer will be within the region passed to the request
+ * creating this object. It is up to the compositor to decide whether this
+ * requires some user interaction and if the pointer will warp to within the
+ * passed region if outside.
+ *
+ * To unconfine the pointer, send the destroy request. This will also destroy
+ * the wp_confined_pointer object.
+ *
+ * If the compositor decides to unconfine the pointer the unconfined event is
+ * sent. The wp_confined_pointer object is at this point defunct and should
+ * be destroyed.
+ * @section page_iface_zwp_confined_pointer_v1_api API
+ * See @ref iface_zwp_confined_pointer_v1.
+ */
+/**
+ * @defgroup iface_zwp_confined_pointer_v1 The zwp_confined_pointer_v1 interface
+ *
+ * The wp_confined_pointer interface represents a confined pointer state.
+ *
+ * This object will send the event 'confined' when the confinement is
+ * activated. Whenever the confinement is activated, it is guaranteed that
+ * the surface the pointer is confined to will already have received pointer
+ * focus and that the pointer will be within the region passed to the request
+ * creating this object. It is up to the compositor to decide whether this
+ * requires some user interaction and if the pointer will warp to within the
+ * passed region if outside.
+ *
+ * To unconfine the pointer, send the destroy request. This will also destroy
+ * the wp_confined_pointer object.
+ *
+ * If the compositor decides to unconfine the pointer the unconfined event is
+ * sent. The wp_confined_pointer object is at this point defunct and should
+ * be destroyed.
+ */
+extern const struct wl_interface zwp_confined_pointer_v1_interface;
+
+#ifndef ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM
+# define ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ * wp_pointer_constraints error values
+ *
+ * These errors can be emitted in response to wp_pointer_constraints
+ * requests.
+ */
+enum zwp_pointer_constraints_v1_error {
+ /**
+ * pointer constraint already requested on that surface
+ */
+ ZWP_POINTER_CONSTRAINTS_V1_ERROR_ALREADY_CONSTRAINED = 1,
+};
+#endif /* ZWP_POINTER_CONSTRAINTS_V1_ERROR_ENUM */
+
+#ifndef ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM
+# define ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ * the pointer constraint may reactivate
+ *
+ * A persistent pointer constraint may again reactivate once it has
+ * been deactivated. See the corresponding deactivation event
+ * (wp_locked_pointer.unlocked and wp_confined_pointer.unconfined) for
+ * details.
+ */
+enum zwp_pointer_constraints_v1_lifetime {
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT = 1,
+ ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_PERSISTENT = 2,
+};
+#endif /* ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ENUM */
+
+#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY 0
+#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER 1
+#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER 2
+
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ */
+#define ZWP_POINTER_CONSTRAINTS_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ */
+#define ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ */
+#define ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_pointer_constraints_v1 */
+static inline void zwp_pointer_constraints_v1_set_user_data(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_pointer_constraints_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_pointer_constraints_v1 */
+static inline void* zwp_pointer_constraints_v1_get_user_data(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_pointer_constraints_v1);
+}
+
+static inline uint32_t zwp_pointer_constraints_v1_get_version(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_pointer_constraints_v1);
+}
+
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ *
+ * Used by the client to notify the server that it will no longer use this
+ * pointer constraints object.
+ */
+static inline void zwp_pointer_constraints_v1_destroy(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_pointer_constraints_v1,
+ ZWP_POINTER_CONSTRAINTS_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_pointer_constraints_v1);
+}
+
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ *
+ * The lock_pointer request lets the client request to disable movements of
+ * the virtual pointer (i.e. the cursor), effectively locking the pointer
+ * to a position. This request may not take effect immediately; in the
+ * future, when the compositor deems implementation-specific constraints
+ * are satisfied, the pointer lock will be activated and the compositor
+ * sends a locked event.
+ *
+ * The protocol provides no guarantee that the constraints are ever
+ * satisfied, and does not require the compositor to send an error if the
+ * constraints cannot ever be satisfied. It is thus possible to request a
+ * lock that will never activate.
+ *
+ * There may not be another pointer constraint of any kind requested or
+ * active on the surface for any of the wl_pointer objects of the seat of
+ * the passed pointer when requesting a lock. If there is, an error will be
+ * raised. See general pointer lock documentation for more details.
+ *
+ * The intersection of the region passed with this request and the input
+ * region of the surface is used to determine where the pointer must be
+ * in order for the lock to activate. It is up to the compositor whether to
+ * warp the pointer or require some kind of user interaction for the lock
+ * to activate. If the region is null the surface input region is used.
+ *
+ * A surface may receive pointer focus without the lock being activated.
+ *
+ * The request creates a new object wp_locked_pointer which is used to
+ * interact with the lock as well as receive updates about its state. See
+ * the the description of wp_locked_pointer for further information.
+ *
+ * Note that while a pointer is locked, the wl_pointer objects of the
+ * corresponding seat will not emit any wl_pointer.motion events, but
+ * relative motion events will still be emitted via wp_relative_pointer
+ * objects of the same seat. wl_pointer.axis and wl_pointer.button events
+ * are unaffected.
+ */
+static inline struct zwp_locked_pointer_v1*
+zwp_pointer_constraints_v1_lock_pointer(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1,
+ struct wl_surface* surface, struct wl_pointer* pointer,
+ struct wl_region* region, uint32_t lifetime) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_pointer_constraints_v1,
+ ZWP_POINTER_CONSTRAINTS_V1_LOCK_POINTER, &zwp_locked_pointer_v1_interface,
+ NULL, surface, pointer, region, lifetime);
+
+ return (struct zwp_locked_pointer_v1*)id;
+}
+
+/**
+ * @ingroup iface_zwp_pointer_constraints_v1
+ *
+ * The confine_pointer request lets the client request to confine the
+ * pointer cursor to a given region. This request may not take effect
+ * immediately; in the future, when the compositor deems implementation-
+ * specific constraints are satisfied, the pointer confinement will be
+ * activated and the compositor sends a confined event.
+ *
+ * The intersection of the region passed with this request and the input
+ * region of the surface is used to determine where the pointer must be
+ * in order for the confinement to activate. It is up to the compositor
+ * whether to warp the pointer or require some kind of user interaction for
+ * the confinement to activate. If the region is null the surface input
+ * region is used.
+ *
+ * The request will create a new object wp_confined_pointer which is used
+ * to interact with the confinement as well as receive updates about its
+ * state. See the the description of wp_confined_pointer for further
+ * information.
+ */
+static inline struct zwp_confined_pointer_v1*
+zwp_pointer_constraints_v1_confine_pointer(
+ struct zwp_pointer_constraints_v1* zwp_pointer_constraints_v1,
+ struct wl_surface* surface, struct wl_pointer* pointer,
+ struct wl_region* region, uint32_t lifetime) {
+ struct wl_proxy* id;
+
+ id =
+ wl_proxy_marshal_constructor((struct wl_proxy*)zwp_pointer_constraints_v1,
+ ZWP_POINTER_CONSTRAINTS_V1_CONFINE_POINTER,
+ &zwp_confined_pointer_v1_interface, NULL,
+ surface, pointer, region, lifetime);
+
+ return (struct zwp_confined_pointer_v1*)id;
+}
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ * @struct zwp_locked_pointer_v1_listener
+ */
+struct zwp_locked_pointer_v1_listener {
+ /**
+ * lock activation event
+ *
+ * Notification that the pointer lock of the seat's pointer is
+ * activated.
+ */
+ void (*locked)(void* data,
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1);
+ /**
+ * lock deactivation event
+ *
+ * Notification that the pointer lock of the seat's pointer is no
+ * longer active. If this is a oneshot pointer lock (see
+ * wp_pointer_constraints.lifetime) this object is now defunct and
+ * should be destroyed. If this is a persistent pointer lock (see
+ * wp_pointer_constraints.lifetime) this pointer lock may again
+ * reactivate in the future.
+ */
+ void (*unlocked)(void* data,
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1);
+};
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+static inline int zwp_locked_pointer_v1_add_listener(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1,
+ const struct zwp_locked_pointer_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_locked_pointer_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_LOCKED_POINTER_V1_DESTROY 0
+#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT 1
+#define ZWP_LOCKED_POINTER_V1_SET_REGION 2
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+#define ZWP_LOCKED_POINTER_V1_LOCKED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+#define ZWP_LOCKED_POINTER_V1_UNLOCKED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+#define ZWP_LOCKED_POINTER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+#define ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ */
+#define ZWP_LOCKED_POINTER_V1_SET_REGION_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_locked_pointer_v1 */
+static inline void zwp_locked_pointer_v1_set_user_data(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_locked_pointer_v1, user_data);
+}
+
+/** @ingroup iface_zwp_locked_pointer_v1 */
+static inline void* zwp_locked_pointer_v1_get_user_data(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_locked_pointer_v1);
+}
+
+static inline uint32_t zwp_locked_pointer_v1_get_version(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_locked_pointer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ *
+ * Destroy the locked pointer object. If applicable, the compositor will
+ * unlock the pointer.
+ */
+static inline void zwp_locked_pointer_v1_destroy(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_locked_pointer_v1,
+ ZWP_LOCKED_POINTER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_locked_pointer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ *
+ * Set the cursor position hint relative to the top left corner of the
+ * surface.
+ *
+ * If the client is drawing its own cursor, it should update the position
+ * hint to the position of its own cursor. A compositor may use this
+ * information to warp the pointer upon unlock in order to avoid pointer
+ * jumps.
+ *
+ * The cursor position hint is double buffered. The new hint will only take
+ * effect when the associated surface gets it pending state applied. See
+ * wl_surface.commit for details.
+ */
+static inline void zwp_locked_pointer_v1_set_cursor_position_hint(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1, wl_fixed_t surface_x,
+ wl_fixed_t surface_y) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_locked_pointer_v1,
+ ZWP_LOCKED_POINTER_V1_SET_CURSOR_POSITION_HINT, surface_x,
+ surface_y);
+}
+
+/**
+ * @ingroup iface_zwp_locked_pointer_v1
+ *
+ * Set a new region used to lock the pointer.
+ *
+ * The new lock region is double-buffered. The new lock region will
+ * only take effect when the associated surface gets its pending state
+ * applied. See wl_surface.commit for details.
+ *
+ * For details about the lock region, see wp_locked_pointer.
+ */
+static inline void zwp_locked_pointer_v1_set_region(
+ struct zwp_locked_pointer_v1* zwp_locked_pointer_v1,
+ struct wl_region* region) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_locked_pointer_v1,
+ ZWP_LOCKED_POINTER_V1_SET_REGION, region);
+}
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ * @struct zwp_confined_pointer_v1_listener
+ */
+struct zwp_confined_pointer_v1_listener {
+ /**
+ * pointer confined
+ *
+ * Notification that the pointer confinement of the seat's
+ * pointer is activated.
+ */
+ void (*confined)(void* data,
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1);
+ /**
+ * pointer unconfined
+ *
+ * Notification that the pointer confinement of the seat's
+ * pointer is no longer active. If this is a oneshot pointer
+ * confinement (see wp_pointer_constraints.lifetime) this object is
+ * now defunct and should be destroyed. If this is a persistent
+ * pointer confinement (see wp_pointer_constraints.lifetime) this
+ * pointer confinement may again reactivate in the future.
+ */
+ void (*unconfined)(void* data,
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1);
+};
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ */
+static inline int zwp_confined_pointer_v1_add_listener(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1,
+ const struct zwp_confined_pointer_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_confined_pointer_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_CONFINED_POINTER_V1_DESTROY 0
+#define ZWP_CONFINED_POINTER_V1_SET_REGION 1
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ */
+#define ZWP_CONFINED_POINTER_V1_CONFINED_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ */
+#define ZWP_CONFINED_POINTER_V1_UNCONFINED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ */
+#define ZWP_CONFINED_POINTER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ */
+#define ZWP_CONFINED_POINTER_V1_SET_REGION_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_confined_pointer_v1 */
+static inline void zwp_confined_pointer_v1_set_user_data(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_confined_pointer_v1, user_data);
+}
+
+/** @ingroup iface_zwp_confined_pointer_v1 */
+static inline void* zwp_confined_pointer_v1_get_user_data(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_confined_pointer_v1);
+}
+
+static inline uint32_t zwp_confined_pointer_v1_get_version(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_confined_pointer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ *
+ * Destroy the confined pointer object. If applicable, the compositor will
+ * unconfine the pointer.
+ */
+static inline void zwp_confined_pointer_v1_destroy(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_confined_pointer_v1,
+ ZWP_CONFINED_POINTER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_confined_pointer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_confined_pointer_v1
+ *
+ * Set a new region used to confine the pointer.
+ *
+ * The new confine region is double-buffered. The new confine region will
+ * only take effect when the associated surface gets its pending state
+ * applied. See wl_surface.commit for details.
+ *
+ * If the confinement is active when the new confinement region is applied
+ * and the pointer ends up outside of newly applied region, the pointer may
+ * warped to a position within the new confinement region. If warped, a
+ * wl_pointer.motion event will be emitted, but no
+ * wp_relative_pointer.relative_motion event.
+ *
+ * The compositor may also, instead of using the new region, unconfine the
+ * pointer.
+ *
+ * For details about the confine region, see wp_confined_pointer.
+ */
+static inline void zwp_confined_pointer_v1_set_region(
+ struct zwp_confined_pointer_v1* zwp_confined_pointer_v1,
+ struct wl_region* region) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_confined_pointer_v1,
+ ZWP_CONFINED_POINTER_V1_SET_REGION, region);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/pointer-constraints-unstable-v1-protocol.c b/widget/gtk/wayland/pointer-constraints-unstable-v1-protocol.c
new file mode 100644
index 0000000000..c28ddf6918
--- /dev/null
+++ b/widget/gtk/wayland/pointer-constraints-unstable-v1-protocol.c
@@ -0,0 +1,97 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2014 Jonas Ådahl
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_pointer_interface;
+extern const struct wl_interface wl_region_interface;
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface zwp_confined_pointer_v1_interface;
+extern const struct wl_interface zwp_locked_pointer_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* pointer_constraints_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zwp_locked_pointer_v1_interface,
+ &wl_surface_interface,
+ &wl_pointer_interface,
+ &wl_region_interface,
+ NULL,
+ &zwp_confined_pointer_v1_interface,
+ &wl_surface_interface,
+ &wl_pointer_interface,
+ &wl_region_interface,
+ NULL,
+ &wl_region_interface,
+ &wl_region_interface,
+};
+
+static const struct wl_message zwp_pointer_constraints_v1_requests[] = {
+ {"destroy", "", pointer_constraints_unstable_v1_types + 0},
+ {"lock_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 2},
+ {"confine_pointer", "noo?ou", pointer_constraints_unstable_v1_types + 7},
+};
+
+WL_EXPORT const struct wl_interface zwp_pointer_constraints_v1_interface = {
+ "zwp_pointer_constraints_v1", 1, 3,
+ zwp_pointer_constraints_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zwp_locked_pointer_v1_requests[] = {
+ {"destroy", "", pointer_constraints_unstable_v1_types + 0},
+ {"set_cursor_position_hint", "ff",
+ pointer_constraints_unstable_v1_types + 0},
+ {"set_region", "?o", pointer_constraints_unstable_v1_types + 12},
+};
+
+static const struct wl_message zwp_locked_pointer_v1_events[] = {
+ {"locked", "", pointer_constraints_unstable_v1_types + 0},
+ {"unlocked", "", pointer_constraints_unstable_v1_types + 0},
+};
+
+WL_EXPORT const struct wl_interface zwp_locked_pointer_v1_interface = {
+ "zwp_locked_pointer_v1", 1, 3,
+ zwp_locked_pointer_v1_requests, 2, zwp_locked_pointer_v1_events,
+};
+
+static const struct wl_message zwp_confined_pointer_v1_requests[] = {
+ {"destroy", "", pointer_constraints_unstable_v1_types + 0},
+ {"set_region", "?o", pointer_constraints_unstable_v1_types + 13},
+};
+
+static const struct wl_message zwp_confined_pointer_v1_events[] = {
+ {"confined", "", pointer_constraints_unstable_v1_types + 0},
+ {"unconfined", "", pointer_constraints_unstable_v1_types + 0},
+};
+
+WL_EXPORT const struct wl_interface zwp_confined_pointer_v1_interface = {
+ "zwp_confined_pointer_v1", 1, 2,
+ zwp_confined_pointer_v1_requests, 2, zwp_confined_pointer_v1_events,
+};
diff --git a/widget/gtk/wayland/relative-pointer-unstable-v1-client-protocol.h b/widget/gtk/wayland/relative-pointer-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..dbae8081f4
--- /dev/null
+++ b/widget/gtk/wayland/relative-pointer-unstable-v1-client-protocol.h
@@ -0,0 +1,293 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define RELATIVE_POINTER_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_relative_pointer_unstable_v1 The relative_pointer_unstable_v1
+ * protocol protocol for relative pointer motion events
+ *
+ * @section page_desc_relative_pointer_unstable_v1 Description
+ *
+ * This protocol specifies a set of interfaces used for making clients able to
+ * receive relative pointer events not obstructed by barriers (such as the
+ * monitor edge or other pointer barriers).
+ *
+ * To start receiving relative pointer events, a client must first bind the
+ * global interface "wp_relative_pointer_manager" which, if a compositor
+ * supports relative pointer motion events, is exposed by the registry. After
+ * having created the relative pointer manager proxy object, the client uses
+ * it to create the actual relative pointer object using the
+ * "get_relative_pointer" request given a wl_pointer. The relative pointer
+ * motion events will then, when applicable, be transmitted via the proxy of
+ * the newly created relative pointer object. See the documentation of the
+ * relative pointer interface for more details.
+ *
+ * Warning! The protocol described in this file is experimental and backward
+ * incompatible changes may be made. Backward compatible changes may be added
+ * together with the corresponding interface version bump. Backward
+ * incompatible changes are done by bumping the version number in the protocol
+ * and interface names and resetting the interface version. Once the protocol
+ * is to be declared stable, the 'z' prefix and the version number in the
+ * protocol and interface names are removed and the interface version number is
+ * reset.
+ *
+ * @section page_ifaces_relative_pointer_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_relative_pointer_manager_v1 - get relative pointer
+ * objects
+ * - @subpage page_iface_zwp_relative_pointer_v1 - relative pointer object
+ * @section page_copyright_relative_pointer_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2014 Jonas Ådahl
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_pointer;
+struct zwp_relative_pointer_manager_v1;
+struct zwp_relative_pointer_v1;
+
+/**
+ * @page page_iface_zwp_relative_pointer_manager_v1
+ * zwp_relative_pointer_manager_v1
+ * @section page_iface_zwp_relative_pointer_manager_v1_desc Description
+ *
+ * A global interface used for getting the relative pointer object for a
+ * given pointer.
+ * @section page_iface_zwp_relative_pointer_manager_v1_api API
+ * See @ref iface_zwp_relative_pointer_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_relative_pointer_manager_v1 The
+ * zwp_relative_pointer_manager_v1 interface
+ *
+ * A global interface used for getting the relative pointer object for a
+ * given pointer.
+ */
+extern const struct wl_interface zwp_relative_pointer_manager_v1_interface;
+/**
+ * @page page_iface_zwp_relative_pointer_v1 zwp_relative_pointer_v1
+ * @section page_iface_zwp_relative_pointer_v1_desc Description
+ *
+ * A wp_relative_pointer object is an extension to the wl_pointer interface
+ * used for emitting relative pointer events. It shares the same focus as
+ * wl_pointer objects of the same seat and will only emit events when it has
+ * focus.
+ * @section page_iface_zwp_relative_pointer_v1_api API
+ * See @ref iface_zwp_relative_pointer_v1.
+ */
+/**
+ * @defgroup iface_zwp_relative_pointer_v1 The zwp_relative_pointer_v1 interface
+ *
+ * A wp_relative_pointer object is an extension to the wl_pointer interface
+ * used for emitting relative pointer events. It shares the same focus as
+ * wl_pointer objects of the same seat and will only emit events when it has
+ * focus.
+ */
+extern const struct wl_interface zwp_relative_pointer_v1_interface;
+
+#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY 0
+#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER 1
+
+/**
+ * @ingroup iface_zwp_relative_pointer_manager_v1
+ */
+#define ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_relative_pointer_manager_v1
+ */
+#define ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_relative_pointer_manager_v1 */
+static inline void zwp_relative_pointer_manager_v1_set_user_data(
+ struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_relative_pointer_manager_v1,
+ user_data);
+}
+
+/** @ingroup iface_zwp_relative_pointer_manager_v1 */
+static inline void* zwp_relative_pointer_manager_v1_get_user_data(
+ struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1) {
+ return wl_proxy_get_user_data(
+ (struct wl_proxy*)zwp_relative_pointer_manager_v1);
+}
+
+static inline uint32_t zwp_relative_pointer_manager_v1_get_version(
+ struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1) {
+ return wl_proxy_get_version(
+ (struct wl_proxy*)zwp_relative_pointer_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_relative_pointer_manager_v1
+ *
+ * Used by the client to notify the server that it will no longer use this
+ * relative pointer manager object.
+ */
+static inline void zwp_relative_pointer_manager_v1_destroy(
+ struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_relative_pointer_manager_v1,
+ ZWP_RELATIVE_POINTER_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_relative_pointer_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_relative_pointer_manager_v1
+ *
+ * Create a relative pointer interface given a wl_pointer object. See the
+ * wp_relative_pointer interface for more details.
+ */
+static inline struct zwp_relative_pointer_v1*
+zwp_relative_pointer_manager_v1_get_relative_pointer(
+ struct zwp_relative_pointer_manager_v1* zwp_relative_pointer_manager_v1,
+ struct wl_pointer* pointer) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor(
+ (struct wl_proxy*)zwp_relative_pointer_manager_v1,
+ ZWP_RELATIVE_POINTER_MANAGER_V1_GET_RELATIVE_POINTER,
+ &zwp_relative_pointer_v1_interface, NULL, pointer);
+
+ return (struct zwp_relative_pointer_v1*)id;
+}
+
+/**
+ * @ingroup iface_zwp_relative_pointer_v1
+ * @struct zwp_relative_pointer_v1_listener
+ */
+struct zwp_relative_pointer_v1_listener {
+ /**
+ * relative pointer motion
+ *
+ * Relative x/y pointer motion from the pointer of the seat
+ * associated with this object.
+ *
+ * A relative motion is in the same dimension as regular wl_pointer
+ * motion events, except they do not represent an absolute
+ * position. For example, moving a pointer from (x, y) to (x', y')
+ * would have the equivalent relative motion (x' - x, y' - y). If a
+ * pointer motion caused the absolute pointer position to be
+ * clipped by for example the edge of the monitor, the relative
+ * motion is unaffected by the clipping and will represent the
+ * unclipped motion.
+ *
+ * This event also contains non-accelerated motion deltas. The
+ * non-accelerated delta is, when applicable, the regular pointer
+ * motion delta as it was before having applied motion acceleration
+ * and other transformations such as normalization.
+ *
+ * Note that the non-accelerated delta does not represent 'raw'
+ * events as they were read from some device. Pointer motion
+ * acceleration is device- and configuration-specific and
+ * non-accelerated deltas and accelerated deltas may have the same
+ * value on some devices.
+ *
+ * Relative motions are not coupled to wl_pointer.motion events,
+ * and can be sent in combination with such events, but also
+ * independently. There may also be scenarios where
+ * wl_pointer.motion is sent, but there is no relative motion. The
+ * order of an absolute and relative motion event originating from
+ * the same physical motion is not guaranteed.
+ *
+ * If the client needs button events or focus state, it can receive
+ * them from a wl_pointer object of the same seat that the
+ * wp_relative_pointer object is associated with.
+ * @param utime_hi high 32 bits of a 64 bit timestamp with microsecond
+ * granularity
+ * @param utime_lo low 32 bits of a 64 bit timestamp with microsecond
+ * granularity
+ * @param dx the x component of the motion vector
+ * @param dy the y component of the motion vector
+ * @param dx_unaccel the x component of the unaccelerated motion vector
+ * @param dy_unaccel the y component of the unaccelerated motion vector
+ */
+ void (*relative_motion)(
+ void* data, struct zwp_relative_pointer_v1* zwp_relative_pointer_v1,
+ uint32_t utime_hi, uint32_t utime_lo, wl_fixed_t dx, wl_fixed_t dy,
+ wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel);
+};
+
+/**
+ * @ingroup iface_zwp_relative_pointer_v1
+ */
+static inline int zwp_relative_pointer_v1_add_listener(
+ struct zwp_relative_pointer_v1* zwp_relative_pointer_v1,
+ const struct zwp_relative_pointer_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zwp_relative_pointer_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZWP_RELATIVE_POINTER_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zwp_relative_pointer_v1
+ */
+#define ZWP_RELATIVE_POINTER_V1_RELATIVE_MOTION_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_relative_pointer_v1
+ */
+#define ZWP_RELATIVE_POINTER_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_relative_pointer_v1 */
+static inline void zwp_relative_pointer_v1_set_user_data(
+ struct zwp_relative_pointer_v1* zwp_relative_pointer_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zwp_relative_pointer_v1, user_data);
+}
+
+/** @ingroup iface_zwp_relative_pointer_v1 */
+static inline void* zwp_relative_pointer_v1_get_user_data(
+ struct zwp_relative_pointer_v1* zwp_relative_pointer_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zwp_relative_pointer_v1);
+}
+
+static inline uint32_t zwp_relative_pointer_v1_get_version(
+ struct zwp_relative_pointer_v1* zwp_relative_pointer_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zwp_relative_pointer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_relative_pointer_v1
+ */
+static inline void zwp_relative_pointer_v1_destroy(
+ struct zwp_relative_pointer_v1* zwp_relative_pointer_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zwp_relative_pointer_v1,
+ ZWP_RELATIVE_POINTER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zwp_relative_pointer_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/relative-pointer-unstable-v1-protocol.c b/widget/gtk/wayland/relative-pointer-unstable-v1-protocol.c
new file mode 100644
index 0000000000..3534686a3d
--- /dev/null
+++ b/widget/gtk/wayland/relative-pointer-unstable-v1-protocol.c
@@ -0,0 +1,69 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2014 Jonas Ådahl
+ * Copyright © 2015 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_pointer_interface;
+extern const struct wl_interface zwp_relative_pointer_v1_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* relative_pointer_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ NULL,
+ &zwp_relative_pointer_v1_interface,
+ &wl_pointer_interface,
+};
+
+static const struct wl_message zwp_relative_pointer_manager_v1_requests[] = {
+ {"destroy", "", relative_pointer_unstable_v1_types + 0},
+ {"get_relative_pointer", "no", relative_pointer_unstable_v1_types + 6},
+};
+
+WL_EXPORT const struct wl_interface zwp_relative_pointer_manager_v1_interface =
+ {
+ "zwp_relative_pointer_manager_v1", 1, 2,
+ zwp_relative_pointer_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zwp_relative_pointer_v1_requests[] = {
+ {"destroy", "", relative_pointer_unstable_v1_types + 0},
+};
+
+static const struct wl_message zwp_relative_pointer_v1_events[] = {
+ {"relative_motion", "uuffff", relative_pointer_unstable_v1_types + 0},
+};
+
+WL_EXPORT const struct wl_interface zwp_relative_pointer_v1_interface = {
+ "zwp_relative_pointer_v1", 1, 1,
+ zwp_relative_pointer_v1_requests, 1, zwp_relative_pointer_v1_events,
+};
diff --git a/widget/gtk/wayland/viewporter-client-protocol.h b/widget/gtk/wayland/viewporter-client-protocol.h
new file mode 100644
index 0000000000..4c6c5bd910
--- /dev/null
+++ b/widget/gtk/wayland/viewporter-client-protocol.h
@@ -0,0 +1,392 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef VIEWPORTER_CLIENT_PROTOCOL_H
+#define VIEWPORTER_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_viewporter The viewporter protocol
+ * @section page_ifaces_viewporter Interfaces
+ * - @subpage page_iface_wp_viewporter - surface cropping and scaling
+ * - @subpage page_iface_wp_viewport - crop and scale interface to a wl_surface
+ * @section page_copyright_viewporter Copyright
+ * <pre>
+ *
+ * Copyright © 2013-2016 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_surface;
+struct wp_viewport;
+struct wp_viewporter;
+
+/**
+ * @page page_iface_wp_viewporter wp_viewporter
+ * @section page_iface_wp_viewporter_desc Description
+ *
+ * The global interface exposing surface cropping and scaling
+ * capabilities is used to instantiate an interface extension for a
+ * wl_surface object. This extended interface will then allow
+ * cropping and scaling the surface contents, effectively
+ * disconnecting the direct relationship between the buffer and the
+ * surface size.
+ * @section page_iface_wp_viewporter_api API
+ * See @ref iface_wp_viewporter.
+ */
+/**
+ * @defgroup iface_wp_viewporter The wp_viewporter interface
+ *
+ * The global interface exposing surface cropping and scaling
+ * capabilities is used to instantiate an interface extension for a
+ * wl_surface object. This extended interface will then allow
+ * cropping and scaling the surface contents, effectively
+ * disconnecting the direct relationship between the buffer and the
+ * surface size.
+ */
+extern const struct wl_interface wp_viewporter_interface;
+/**
+ * @page page_iface_wp_viewport wp_viewport
+ * @section page_iface_wp_viewport_desc Description
+ *
+ * An additional interface to a wl_surface object, which allows the
+ * client to specify the cropping and scaling of the surface
+ * contents.
+ *
+ * This interface works with two concepts: the source rectangle (src_x,
+ * src_y, src_width, src_height), and the destination size (dst_width,
+ * dst_height). The contents of the source rectangle are scaled to the
+ * destination size, and content outside the source rectangle is ignored.
+ * This state is double-buffered, and is applied on the next
+ * wl_surface.commit.
+ *
+ * The two parts of crop and scale state are independent: the source
+ * rectangle, and the destination size. Initially both are unset, that
+ * is, no scaling is applied. The whole of the current wl_buffer is
+ * used as the source, and the surface size is as defined in
+ * wl_surface.attach.
+ *
+ * If the destination size is set, it causes the surface size to become
+ * dst_width, dst_height. The source (rectangle) is scaled to exactly
+ * this size. This overrides whatever the attached wl_buffer size is,
+ * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
+ * has no content and therefore no size. Otherwise, the size is always
+ * at least 1x1 in surface local coordinates.
+ *
+ * If the source rectangle is set, it defines what area of the wl_buffer is
+ * taken as the source. If the source rectangle is set and the destination
+ * size is not set, then src_width and src_height must be integers, and the
+ * surface size becomes the source rectangle size. This results in cropping
+ * without scaling. If src_width or src_height are not integers and
+ * destination size is not set, the bad_size protocol error is raised when
+ * the surface state is applied.
+ *
+ * The coordinate transformations from buffer pixel coordinates up to
+ * the surface-local coordinates happen in the following order:
+ * 1. buffer_transform (wl_surface.set_buffer_transform)
+ * 2. buffer_scale (wl_surface.set_buffer_scale)
+ * 3. crop and scale (wp_viewport.set*)
+ * This means, that the source rectangle coordinates of crop and scale
+ * are given in the coordinates after the buffer transform and scale,
+ * i.e. in the coordinates that would be the surface-local coordinates
+ * if the crop and scale was not applied.
+ *
+ * If src_x or src_y are negative, the bad_value protocol error is raised.
+ * Otherwise, if the source rectangle is partially or completely outside of
+ * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
+ * when the surface state is applied. A NULL wl_buffer does not raise the
+ * out_of_buffer error.
+ *
+ * The x, y arguments of wl_surface.attach are applied as normal to
+ * the surface. They indicate how many pixels to remove from the
+ * surface size from the left and the top. In other words, they are
+ * still in the surface-local coordinate system, just like dst_width
+ * and dst_height are.
+ *
+ * If the wl_surface associated with the wp_viewport is destroyed,
+ * all wp_viewport requests except 'destroy' raise the protocol error
+ * no_surface.
+ *
+ * If the wp_viewport object is destroyed, the crop and scale
+ * state is removed from the wl_surface. The change will be applied
+ * on the next wl_surface.commit.
+ * @section page_iface_wp_viewport_api API
+ * See @ref iface_wp_viewport.
+ */
+/**
+ * @defgroup iface_wp_viewport The wp_viewport interface
+ *
+ * An additional interface to a wl_surface object, which allows the
+ * client to specify the cropping and scaling of the surface
+ * contents.
+ *
+ * This interface works with two concepts: the source rectangle (src_x,
+ * src_y, src_width, src_height), and the destination size (dst_width,
+ * dst_height). The contents of the source rectangle are scaled to the
+ * destination size, and content outside the source rectangle is ignored.
+ * This state is double-buffered, and is applied on the next
+ * wl_surface.commit.
+ *
+ * The two parts of crop and scale state are independent: the source
+ * rectangle, and the destination size. Initially both are unset, that
+ * is, no scaling is applied. The whole of the current wl_buffer is
+ * used as the source, and the surface size is as defined in
+ * wl_surface.attach.
+ *
+ * If the destination size is set, it causes the surface size to become
+ * dst_width, dst_height. The source (rectangle) is scaled to exactly
+ * this size. This overrides whatever the attached wl_buffer size is,
+ * unless the wl_buffer is NULL. If the wl_buffer is NULL, the surface
+ * has no content and therefore no size. Otherwise, the size is always
+ * at least 1x1 in surface local coordinates.
+ *
+ * If the source rectangle is set, it defines what area of the wl_buffer is
+ * taken as the source. If the source rectangle is set and the destination
+ * size is not set, then src_width and src_height must be integers, and the
+ * surface size becomes the source rectangle size. This results in cropping
+ * without scaling. If src_width or src_height are not integers and
+ * destination size is not set, the bad_size protocol error is raised when
+ * the surface state is applied.
+ *
+ * The coordinate transformations from buffer pixel coordinates up to
+ * the surface-local coordinates happen in the following order:
+ * 1. buffer_transform (wl_surface.set_buffer_transform)
+ * 2. buffer_scale (wl_surface.set_buffer_scale)
+ * 3. crop and scale (wp_viewport.set*)
+ * This means, that the source rectangle coordinates of crop and scale
+ * are given in the coordinates after the buffer transform and scale,
+ * i.e. in the coordinates that would be the surface-local coordinates
+ * if the crop and scale was not applied.
+ *
+ * If src_x or src_y are negative, the bad_value protocol error is raised.
+ * Otherwise, if the source rectangle is partially or completely outside of
+ * the non-NULL wl_buffer, then the out_of_buffer protocol error is raised
+ * when the surface state is applied. A NULL wl_buffer does not raise the
+ * out_of_buffer error.
+ *
+ * The x, y arguments of wl_surface.attach are applied as normal to
+ * the surface. They indicate how many pixels to remove from the
+ * surface size from the left and the top. In other words, they are
+ * still in the surface-local coordinate system, just like dst_width
+ * and dst_height are.
+ *
+ * If the wl_surface associated with the wp_viewport is destroyed,
+ * all wp_viewport requests except 'destroy' raise the protocol error
+ * no_surface.
+ *
+ * If the wp_viewport object is destroyed, the crop and scale
+ * state is removed from the wl_surface. The change will be applied
+ * on the next wl_surface.commit.
+ */
+extern const struct wl_interface wp_viewport_interface;
+
+#ifndef WP_VIEWPORTER_ERROR_ENUM
+# define WP_VIEWPORTER_ERROR_ENUM
+enum wp_viewporter_error {
+ /**
+ * the surface already has a viewport object associated
+ */
+ WP_VIEWPORTER_ERROR_VIEWPORT_EXISTS = 0,
+};
+#endif /* WP_VIEWPORTER_ERROR_ENUM */
+
+#define WP_VIEWPORTER_DESTROY 0
+#define WP_VIEWPORTER_GET_VIEWPORT 1
+
+/**
+ * @ingroup iface_wp_viewporter
+ */
+#define WP_VIEWPORTER_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_wp_viewporter
+ */
+#define WP_VIEWPORTER_GET_VIEWPORT_SINCE_VERSION 1
+
+/** @ingroup iface_wp_viewporter */
+static inline void wp_viewporter_set_user_data(
+ struct wp_viewporter* wp_viewporter, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)wp_viewporter, user_data);
+}
+
+/** @ingroup iface_wp_viewporter */
+static inline void* wp_viewporter_get_user_data(
+ struct wp_viewporter* wp_viewporter) {
+ return wl_proxy_get_user_data((struct wl_proxy*)wp_viewporter);
+}
+
+static inline uint32_t wp_viewporter_get_version(
+ struct wp_viewporter* wp_viewporter) {
+ return wl_proxy_get_version((struct wl_proxy*)wp_viewporter);
+}
+
+/**
+ * @ingroup iface_wp_viewporter
+ *
+ * Informs the server that the client will not be using this
+ * protocol object anymore. This does not affect any other objects,
+ * wp_viewport objects included.
+ */
+static inline void wp_viewporter_destroy(struct wp_viewporter* wp_viewporter) {
+ wl_proxy_marshal((struct wl_proxy*)wp_viewporter, WP_VIEWPORTER_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wp_viewporter);
+}
+
+/**
+ * @ingroup iface_wp_viewporter
+ *
+ * Instantiate an interface extension for the given wl_surface to
+ * crop and scale its content. If the given wl_surface already has
+ * a wp_viewport object associated, the viewport_exists
+ * protocol error is raised.
+ */
+static inline struct wp_viewport* wp_viewporter_get_viewport(
+ struct wp_viewporter* wp_viewporter, struct wl_surface* surface) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)wp_viewporter,
+ WP_VIEWPORTER_GET_VIEWPORT,
+ &wp_viewport_interface, NULL, surface);
+
+ return (struct wp_viewport*)id;
+}
+
+#ifndef WP_VIEWPORT_ERROR_ENUM
+# define WP_VIEWPORT_ERROR_ENUM
+enum wp_viewport_error {
+ /**
+ * negative or zero values in width or height
+ */
+ WP_VIEWPORT_ERROR_BAD_VALUE = 0,
+ /**
+ * destination size is not integer
+ */
+ WP_VIEWPORT_ERROR_BAD_SIZE = 1,
+ /**
+ * source rectangle extends outside of the content area
+ */
+ WP_VIEWPORT_ERROR_OUT_OF_BUFFER = 2,
+ /**
+ * the wl_surface was destroyed
+ */
+ WP_VIEWPORT_ERROR_NO_SURFACE = 3,
+};
+#endif /* WP_VIEWPORT_ERROR_ENUM */
+
+#define WP_VIEWPORT_DESTROY 0
+#define WP_VIEWPORT_SET_SOURCE 1
+#define WP_VIEWPORT_SET_DESTINATION 2
+
+/**
+ * @ingroup iface_wp_viewport
+ */
+#define WP_VIEWPORT_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_wp_viewport
+ */
+#define WP_VIEWPORT_SET_SOURCE_SINCE_VERSION 1
+/**
+ * @ingroup iface_wp_viewport
+ */
+#define WP_VIEWPORT_SET_DESTINATION_SINCE_VERSION 1
+
+/** @ingroup iface_wp_viewport */
+static inline void wp_viewport_set_user_data(struct wp_viewport* wp_viewport,
+ void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)wp_viewport, user_data);
+}
+
+/** @ingroup iface_wp_viewport */
+static inline void* wp_viewport_get_user_data(struct wp_viewport* wp_viewport) {
+ return wl_proxy_get_user_data((struct wl_proxy*)wp_viewport);
+}
+
+static inline uint32_t wp_viewport_get_version(
+ struct wp_viewport* wp_viewport) {
+ return wl_proxy_get_version((struct wl_proxy*)wp_viewport);
+}
+
+/**
+ * @ingroup iface_wp_viewport
+ *
+ * The associated wl_surface's crop and scale state is removed.
+ * The change is applied on the next wl_surface.commit.
+ */
+static inline void wp_viewport_destroy(struct wp_viewport* wp_viewport) {
+ wl_proxy_marshal((struct wl_proxy*)wp_viewport, WP_VIEWPORT_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)wp_viewport);
+}
+
+/**
+ * @ingroup iface_wp_viewport
+ *
+ * Set the source rectangle of the associated wl_surface. See
+ * wp_viewport for the description, and relation to the wl_buffer
+ * size.
+ *
+ * If all of x, y, width and height are -1.0, the source rectangle is
+ * unset instead. Any other set of values where width or height are zero
+ * or negative, or x or y are negative, raise the bad_value protocol
+ * error.
+ *
+ * The crop and scale state is double-buffered state, and will be
+ * applied on the next wl_surface.commit.
+ */
+static inline void wp_viewport_set_source(struct wp_viewport* wp_viewport,
+ wl_fixed_t x, wl_fixed_t y,
+ wl_fixed_t width, wl_fixed_t height) {
+ wl_proxy_marshal((struct wl_proxy*)wp_viewport, WP_VIEWPORT_SET_SOURCE, x, y,
+ width, height);
+}
+
+/**
+ * @ingroup iface_wp_viewport
+ *
+ * Set the destination size of the associated wl_surface. See
+ * wp_viewport for the description, and relation to the wl_buffer
+ * size.
+ *
+ * If width is -1 and height is -1, the destination size is unset
+ * instead. Any other pair of values for width and height that
+ * contains zero or negative values raises the bad_value protocol
+ * error.
+ *
+ * The crop and scale state is double-buffered state, and will be
+ * applied on the next wl_surface.commit.
+ */
+static inline void wp_viewport_set_destination(struct wp_viewport* wp_viewport,
+ int32_t width, int32_t height) {
+ wl_proxy_marshal((struct wl_proxy*)wp_viewport, WP_VIEWPORT_SET_DESTINATION,
+ width, height);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/viewporter-protocol.c b/widget/gtk/wayland/viewporter-protocol.c
new file mode 100644
index 0000000000..06b7901426
--- /dev/null
+++ b/widget/gtk/wayland/viewporter-protocol.c
@@ -0,0 +1,56 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2013-2016 Collabora, Ltd.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_surface_interface;
+extern const struct wl_interface wp_viewport_interface;
+#pragma GCC visibility pop
+
+static const struct wl_interface* viewporter_types[] = {
+ NULL, NULL, NULL, NULL, &wp_viewport_interface, &wl_surface_interface,
+};
+
+static const struct wl_message wp_viewporter_requests[] = {
+ {"destroy", "", viewporter_types + 0},
+ {"get_viewport", "no", viewporter_types + 4},
+};
+
+const struct wl_interface wp_viewporter_interface = {
+ "wp_viewporter", 1, 2, wp_viewporter_requests, 0, NULL,
+};
+
+static const struct wl_message wp_viewport_requests[] = {
+ {"destroy", "", viewporter_types + 0},
+ {"set_source", "ffff", viewporter_types + 0},
+ {"set_destination", "ii", viewporter_types + 0},
+};
+
+const struct wl_interface wp_viewport_interface = {
+ "wp_viewport", 1, 3, wp_viewport_requests, 0, NULL,
+};
diff --git a/widget/gtk/wayland/xdg-activation-v1-client-protocol.h b/widget/gtk/wayland/xdg-activation-v1-client-protocol.h
new file mode 100644
index 0000000000..9bebcb6ec2
--- /dev/null
+++ b/widget/gtk/wayland/xdg-activation-v1-client-protocol.h
@@ -0,0 +1,409 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+#ifndef XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H
+#define XDG_ACTIVATION_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_activation_v1 The xdg_activation_v1 protocol
+ * Protocol for requesting activation of surfaces
+ *
+ * @section page_desc_xdg_activation_v1 Description
+ *
+ * The way for a client to pass focus to another toplevel is as follows.
+ *
+ * The client that intends to activate another toplevel uses the
+ * xdg_activation_v1.get_activation_token request to get an activation token.
+ * This token is then forwarded to the client, which is supposed to activate
+ * one of its surfaces, through a separate band of communication.
+ *
+ * One established way of doing this is through the XDG_ACTIVATION_TOKEN
+ * environment variable of a newly launched child process. The child process
+ * should unset the environment variable again right after reading it out in
+ * order to avoid propagating it to other child processes.
+ *
+ * Another established way exists for Applications implementing the D-Bus
+ * interface org.freedesktop.Application, which should get their token under
+ * XDG_ACTIVATION_TOKEN on their platform_data.
+ *
+ * In general activation tokens may be transferred across clients through
+ * means not described in this protocol.
+ *
+ * The client to be activated will then pass the token
+ * it received to the xdg_activation_v1.activate request. The compositor can
+ * then use this token to decide how to react to the activation request.
+ *
+ * The token the activating client gets may be ineffective either already at
+ * the time it receives it, for example if it was not focused, for focus
+ * stealing prevention. The activating client will have no way to discover
+ * the validity of the token, and may still forward it to the to be activated
+ * client.
+ *
+ * The created activation token may optionally get information attached to it
+ * that can be used by the compositor to identify the application that we
+ * intend to activate. This can for example be used to display a visual hint
+ * about what application is being started.
+ *
+ * Warning! The protocol described in this file is currently in the testing
+ * phase. Backward compatible changes may be added together with the
+ * corresponding interface version bump. Backward incompatible changes can
+ * only be done by creating a new major version of the extension.
+ *
+ * @section page_ifaces_xdg_activation_v1 Interfaces
+ * - @subpage page_iface_xdg_activation_v1 - interface for activating surfaces
+ * - @subpage page_iface_xdg_activation_token_v1 - an exported activation handle
+ * @section page_copyright_xdg_activation_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
+ * Copyright © 2020 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_seat;
+struct wl_surface;
+struct xdg_activation_token_v1;
+struct xdg_activation_v1;
+
+#ifndef XDG_ACTIVATION_V1_INTERFACE
+# define XDG_ACTIVATION_V1_INTERFACE
+/**
+ * @page page_iface_xdg_activation_v1 xdg_activation_v1
+ * @section page_iface_xdg_activation_v1_desc Description
+ *
+ * A global interface used for informing the compositor about applications
+ * being activated or started, or for applications to request to be
+ * activated.
+ * @section page_iface_xdg_activation_v1_api API
+ * See @ref iface_xdg_activation_v1.
+ */
+/**
+ * @defgroup iface_xdg_activation_v1 The xdg_activation_v1 interface
+ *
+ * A global interface used for informing the compositor about applications
+ * being activated or started, or for applications to request to be
+ * activated.
+ */
+extern const struct wl_interface xdg_activation_v1_interface;
+#endif
+#ifndef XDG_ACTIVATION_TOKEN_V1_INTERFACE
+# define XDG_ACTIVATION_TOKEN_V1_INTERFACE
+/**
+ * @page page_iface_xdg_activation_token_v1 xdg_activation_token_v1
+ * @section page_iface_xdg_activation_token_v1_desc Description
+ *
+ * An object for setting up a token and receiving a token handle that can
+ * be passed as an activation token to another client.
+ *
+ * The object is created using the xdg_activation_v1.get_activation_token
+ * request. This object should then be populated with the app_id, surface
+ * and serial information and committed. The compositor shall then issue a
+ * done event with the token. In case the request's parameters are invalid,
+ * the compositor will provide an invalid token.
+ * @section page_iface_xdg_activation_token_v1_api API
+ * See @ref iface_xdg_activation_token_v1.
+ */
+/**
+ * @defgroup iface_xdg_activation_token_v1 The xdg_activation_token_v1 interface
+ *
+ * An object for setting up a token and receiving a token handle that can
+ * be passed as an activation token to another client.
+ *
+ * The object is created using the xdg_activation_v1.get_activation_token
+ * request. This object should then be populated with the app_id, surface
+ * and serial information and committed. The compositor shall then issue a
+ * done event with the token. In case the request's parameters are invalid,
+ * the compositor will provide an invalid token.
+ */
+extern const struct wl_interface xdg_activation_token_v1_interface;
+#endif
+
+#define XDG_ACTIVATION_V1_DESTROY 0
+#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN 1
+#define XDG_ACTIVATION_V1_ACTIVATE 2
+
+/**
+ * @ingroup iface_xdg_activation_v1
+ */
+#define XDG_ACTIVATION_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_v1
+ */
+#define XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_v1
+ */
+#define XDG_ACTIVATION_V1_ACTIVATE_SINCE_VERSION 1
+
+/** @ingroup iface_xdg_activation_v1 */
+static inline void xdg_activation_v1_set_user_data(
+ struct xdg_activation_v1* xdg_activation_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)xdg_activation_v1, user_data);
+}
+
+/** @ingroup iface_xdg_activation_v1 */
+static inline void* xdg_activation_v1_get_user_data(
+ struct xdg_activation_v1* xdg_activation_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)xdg_activation_v1);
+}
+
+static inline uint32_t xdg_activation_v1_get_version(
+ struct xdg_activation_v1* xdg_activation_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)xdg_activation_v1);
+}
+
+/**
+ * @ingroup iface_xdg_activation_v1
+ *
+ * Notify the compositor that the xdg_activation object will no longer be
+ * used.
+ *
+ * The child objects created via this interface are unaffected and should
+ * be destroyed separately.
+ */
+static inline void xdg_activation_v1_destroy(
+ struct xdg_activation_v1* xdg_activation_v1) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_v1,
+ XDG_ACTIVATION_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)xdg_activation_v1);
+}
+
+/**
+ * @ingroup iface_xdg_activation_v1
+ *
+ * Creates an xdg_activation_token_v1 object that will provide
+ * the initiating client with a unique token for this activation. This
+ * token should be offered to the clients to be activated.
+ */
+static inline struct xdg_activation_token_v1*
+xdg_activation_v1_get_activation_token(
+ struct xdg_activation_v1* xdg_activation_v1) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)xdg_activation_v1,
+ XDG_ACTIVATION_V1_GET_ACTIVATION_TOKEN,
+ &xdg_activation_token_v1_interface, NULL);
+
+ return (struct xdg_activation_token_v1*)id;
+}
+
+/**
+ * @ingroup iface_xdg_activation_v1
+ *
+ * Requests surface activation. It's up to the compositor to display
+ * this information as desired, for example by placing the surface above
+ * the rest.
+ *
+ * The compositor may know who requested this by checking the activation
+ * token and might decide not to follow through with the activation if it's
+ * considered unwanted.
+ *
+ * Compositors can ignore unknown activation tokens when an invalid
+ * token is passed.
+ */
+static inline void xdg_activation_v1_activate(
+ struct xdg_activation_v1* xdg_activation_v1, const char* token,
+ struct wl_surface* surface) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_v1,
+ XDG_ACTIVATION_V1_ACTIVATE, token, surface);
+}
+
+#ifndef XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM
+# define XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM
+enum xdg_activation_token_v1_error {
+ /**
+ * The token has already been used previously
+ */
+ XDG_ACTIVATION_TOKEN_V1_ERROR_ALREADY_USED = 0,
+};
+#endif /* XDG_ACTIVATION_TOKEN_V1_ERROR_ENUM */
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ * @struct xdg_activation_token_v1_listener
+ */
+struct xdg_activation_token_v1_listener {
+ /**
+ * the exported activation token
+ *
+ * The 'done' event contains the unique token of this activation
+ * request and notifies that the provider is done.
+ * @param token the exported activation token
+ */
+ void (*done)(void* data,
+ struct xdg_activation_token_v1* xdg_activation_token_v1,
+ const char* token);
+};
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+static inline int xdg_activation_token_v1_add_listener(
+ struct xdg_activation_token_v1* xdg_activation_token_v1,
+ const struct xdg_activation_token_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)xdg_activation_token_v1,
+ (void (**)(void))listener, data);
+}
+
+#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL 0
+#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID 1
+#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE 2
+#define XDG_ACTIVATION_TOKEN_V1_COMMIT 3
+#define XDG_ACTIVATION_TOKEN_V1_DESTROY 4
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_DONE_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_SET_SERIAL_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_SET_APP_ID_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_SET_SURFACE_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_COMMIT_SINCE_VERSION 1
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ */
+#define XDG_ACTIVATION_TOKEN_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_xdg_activation_token_v1 */
+static inline void xdg_activation_token_v1_set_user_data(
+ struct xdg_activation_token_v1* xdg_activation_token_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)xdg_activation_token_v1, user_data);
+}
+
+/** @ingroup iface_xdg_activation_token_v1 */
+static inline void* xdg_activation_token_v1_get_user_data(
+ struct xdg_activation_token_v1* xdg_activation_token_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)xdg_activation_token_v1);
+}
+
+static inline uint32_t xdg_activation_token_v1_get_version(
+ struct xdg_activation_token_v1* xdg_activation_token_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)xdg_activation_token_v1);
+}
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ *
+ * Provides information about the seat and serial event that requested the
+ * token.
+ *
+ * The serial can come from an input or focus event. For instance, if a
+ * click triggers the launch of a third-party client, the launcher client
+ * should send a set_serial request with the serial and seat from the
+ * wl_pointer.button event.
+ *
+ * Some compositors might refuse to activate toplevels when the token
+ * doesn't have a valid and recent enough event serial.
+ *
+ * Must be sent before commit. This information is optional.
+ */
+static inline void xdg_activation_token_v1_set_serial(
+ struct xdg_activation_token_v1* xdg_activation_token_v1, uint32_t serial,
+ struct wl_seat* seat) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_token_v1,
+ XDG_ACTIVATION_TOKEN_V1_SET_SERIAL, serial, seat);
+}
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ *
+ * The requesting client can specify an app_id to associate the token
+ * being created with it.
+ *
+ * Must be sent before commit. This information is optional.
+ */
+static inline void xdg_activation_token_v1_set_app_id(
+ struct xdg_activation_token_v1* xdg_activation_token_v1,
+ const char* app_id) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_token_v1,
+ XDG_ACTIVATION_TOKEN_V1_SET_APP_ID, app_id);
+}
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ *
+ * This request sets the surface requesting the activation. Note, this is
+ * different from the surface that will be activated.
+ *
+ * Some compositors might refuse to activate toplevels when the token
+ * doesn't have a requesting surface.
+ *
+ * Must be sent before commit. This information is optional.
+ */
+static inline void xdg_activation_token_v1_set_surface(
+ struct xdg_activation_token_v1* xdg_activation_token_v1,
+ struct wl_surface* surface) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_token_v1,
+ XDG_ACTIVATION_TOKEN_V1_SET_SURFACE, surface);
+}
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ *
+ * Requests an activation token based on the different parameters that
+ * have been offered through set_serial, set_surface and set_app_id.
+ */
+static inline void xdg_activation_token_v1_commit(
+ struct xdg_activation_token_v1* xdg_activation_token_v1) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_token_v1,
+ XDG_ACTIVATION_TOKEN_V1_COMMIT);
+}
+
+/**
+ * @ingroup iface_xdg_activation_token_v1
+ *
+ * Notify the compositor that the xdg_activation_token_v1 object will no
+ * longer be used.
+ */
+static inline void xdg_activation_token_v1_destroy(
+ struct xdg_activation_token_v1* xdg_activation_token_v1) {
+ wl_proxy_marshal((struct wl_proxy*)xdg_activation_token_v1,
+ XDG_ACTIVATION_TOKEN_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)xdg_activation_token_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/xdg-activation-v1-protocol.c b/widget/gtk/wayland/xdg-activation-v1-protocol.c
new file mode 100644
index 0000000000..1fad6dbf97
--- /dev/null
+++ b/widget/gtk/wayland/xdg-activation-v1-protocol.c
@@ -0,0 +1,82 @@
+/* Generated by wayland-scanner 1.19.0 */
+
+/*
+ * Copyright © 2020 Aleix Pol Gonzalez <aleixpol@kde.org>
+ * Copyright © 2020 Carlos Garnacho <carlosg@gnome.org>
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+# define WL_PRIVATE __attribute__((visibility("hidden")))
+#else
+# define WL_PRIVATE
+#endif
+
+#pragma GCC visibility push(default)
+extern const struct wl_interface wl_seat_interface;
+extern const struct wl_interface wl_surface_interface;
+#pragma GCC visibility pop
+extern const struct wl_interface xdg_activation_token_v1_interface;
+
+static const struct wl_interface* xdg_activation_v1_types[] = {
+ NULL,
+ &xdg_activation_token_v1_interface,
+ NULL,
+ &wl_surface_interface,
+ NULL,
+ &wl_seat_interface,
+ &wl_surface_interface,
+};
+
+static const struct wl_message xdg_activation_v1_requests[] = {
+ {"destroy", "", xdg_activation_v1_types + 0},
+ {"get_activation_token", "n", xdg_activation_v1_types + 1},
+ {"activate", "so", xdg_activation_v1_types + 2},
+};
+
+WL_PRIVATE const struct wl_interface xdg_activation_v1_interface = {
+ "xdg_activation_v1", 1, 3, xdg_activation_v1_requests, 0, NULL,
+};
+
+static const struct wl_message xdg_activation_token_v1_requests[] = {
+ {"set_serial", "uo", xdg_activation_v1_types + 4},
+ {"set_app_id", "s", xdg_activation_v1_types + 0},
+ {"set_surface", "o", xdg_activation_v1_types + 6},
+ {"commit", "", xdg_activation_v1_types + 0},
+ {"destroy", "", xdg_activation_v1_types + 0},
+};
+
+static const struct wl_message xdg_activation_token_v1_events[] = {
+ {"done", "s", xdg_activation_v1_types + 0},
+};
+
+WL_PRIVATE const struct wl_interface xdg_activation_token_v1_interface = {
+ "xdg_activation_token_v1", 1, 5,
+ xdg_activation_token_v1_requests, 1, xdg_activation_token_v1_events,
+};
diff --git a/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h b/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..432057ccef
--- /dev/null
+++ b/widget/gtk/wayland/xdg-output-unstable-v1-client-protocol.h
@@ -0,0 +1,392 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define XDG_OUTPUT_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_xdg_output_unstable_v1 The xdg_output_unstable_v1 protocol
+ * Protocol to describe output regions
+ *
+ * @section page_desc_xdg_output_unstable_v1 Description
+ *
+ * This protocol aims at describing outputs in a way which is more in line
+ * with the concept of an output on desktop oriented systems.
+ *
+ * Some information are more specific to the concept of an output for
+ * a desktop oriented system and may not make sense in other applications,
+ * such as IVI systems for example.
+ *
+ * Typically, the global compositor space on a desktop system is made of
+ * a contiguous or overlapping set of rectangular regions.
+ *
+ * Some of the information provided in this protocol might be identical
+ * to their counterparts already available from wl_output, in which case
+ * the information provided by this protocol should be preferred to their
+ * equivalent in wl_output. The goal is to move the desktop specific
+ * concepts (such as output location within the global compositor space,
+ * the connector name and types, etc.) out of the core wl_output protocol.
+ *
+ * Warning! The protocol described in this file is experimental and
+ * backward incompatible changes may be made. Backward compatible
+ * changes may be added together with the corresponding interface
+ * version bump.
+ * Backward incompatible changes are done by bumping the version
+ * number in the protocol and interface names and resetting the
+ * interface version. Once the protocol is to be declared stable,
+ * the 'z' prefix and the version number in the protocol and
+ * interface names are removed and the interface version number is
+ * reset.
+ *
+ * @section page_ifaces_xdg_output_unstable_v1 Interfaces
+ * - @subpage page_iface_zxdg_output_manager_v1 - manage xdg_output objects
+ * - @subpage page_iface_zxdg_output_v1 - compositor logical output region
+ * @section page_copyright_xdg_output_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ * </pre>
+ */
+struct wl_output;
+struct zxdg_output_manager_v1;
+struct zxdg_output_v1;
+
+/**
+ * @page page_iface_zxdg_output_manager_v1 zxdg_output_manager_v1
+ * @section page_iface_zxdg_output_manager_v1_desc Description
+ *
+ * A global factory interface for xdg_output objects.
+ * @section page_iface_zxdg_output_manager_v1_api API
+ * See @ref iface_zxdg_output_manager_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_manager_v1 The zxdg_output_manager_v1 interface
+ *
+ * A global factory interface for xdg_output objects.
+ */
+extern const struct wl_interface zxdg_output_manager_v1_interface;
+/**
+ * @page page_iface_zxdg_output_v1 zxdg_output_v1
+ * @section page_iface_zxdg_output_v1_desc Description
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ * @section page_iface_zxdg_output_v1_api API
+ * See @ref iface_zxdg_output_v1.
+ */
+/**
+ * @defgroup iface_zxdg_output_v1 The zxdg_output_v1 interface
+ *
+ * An xdg_output describes part of the compositor geometry.
+ *
+ * This typically corresponds to a monitor that displays part of the
+ * compositor space.
+ *
+ * For objects version 3 onwards, after all xdg_output properties have been
+ * sent (when the object is created and when properties are updated), a
+ * wl_output.done event is sent. This allows changes to the output
+ * properties to be seen as atomic, even if they happen via multiple events.
+ */
+extern const struct wl_interface zxdg_output_v1_interface;
+
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY 0
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT 1
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_DESTROY_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ */
+#define ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_output_manager_v1 */
+static inline void zxdg_output_manager_v1_set_user_data(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zxdg_output_manager_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_output_manager_v1 */
+static inline void* zxdg_output_manager_v1_get_user_data(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+static inline uint32_t zxdg_output_manager_v1_get_version(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ *
+ * Using this request a client can tell the server that it is not
+ * going to use the xdg_output_manager object anymore.
+ *
+ * Any objects already created through this instance are not affected.
+ */
+static inline void zxdg_output_manager_v1_destroy(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zxdg_output_manager_v1,
+ ZXDG_OUTPUT_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zxdg_output_manager_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_manager_v1
+ *
+ * This creates a new xdg_output object for the given wl_output.
+ */
+static inline struct zxdg_output_v1* zxdg_output_manager_v1_get_xdg_output(
+ struct zxdg_output_manager_v1* zxdg_output_manager_v1,
+ struct wl_output* output) {
+ struct wl_proxy* id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy*)zxdg_output_manager_v1,
+ ZXDG_OUTPUT_MANAGER_V1_GET_XDG_OUTPUT,
+ &zxdg_output_v1_interface, NULL, output);
+
+ return (struct zxdg_output_v1*)id;
+}
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ * @struct zxdg_output_v1_listener
+ */
+struct zxdg_output_v1_listener {
+ /**
+ * position of the output within the global compositor space
+ *
+ * The position event describes the location of the wl_output
+ * within the global compositor space.
+ *
+ * The logical_position event is sent after creating an xdg_output
+ * (see xdg_output_manager.get_xdg_output) and whenever the
+ * location of the output changes within the global compositor
+ * space.
+ * @param x x position within the global compositor space
+ * @param y y position within the global compositor space
+ */
+ void (*logical_position)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ int32_t x, int32_t y);
+ /**
+ * size of the output in the global compositor space
+ *
+ * The logical_size event describes the size of the output in the
+ * global compositor space.
+ *
+ * For example, a surface without any buffer scale, transformation
+ * nor rotation set, with the size matching the logical_size will
+ * have the same size as the corresponding output when displayed.
+ *
+ * Most regular Wayland clients should not pay attention to the
+ * logical size and would rather rely on xdg_shell interfaces.
+ *
+ * Some clients such as Xwayland, however, need this to configure
+ * their surfaces in the global compositor space as the compositor
+ * may apply a different scale from what is advertised by the
+ * output scaling property (to achieve fractional scaling, for
+ * example).
+ *
+ * For example, for a wl_output mode 3840×2160 and a scale factor
+ * 2:
+ *
+ * - A compositor not scaling the surface buffers will advertise a
+ * logical size of 3840×2160,
+ *
+ * - A compositor automatically scaling the surface buffers will
+ * advertise a logical size of 1920×1080,
+ *
+ * - A compositor using a fractional scale of 1.5 will advertise a
+ * logical size to 2560×1620.
+ *
+ * For example, for a wl_output mode 1920×1080 and a 90 degree
+ * rotation, the compositor will advertise a logical size of
+ * 1080x1920.
+ *
+ * The logical_size event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the logical size
+ * of the output changes, either as a result of a change in the
+ * applied scale or because of a change in the corresponding output
+ * mode(see wl_output.mode) or transform (see wl_output.transform).
+ * @param width width in global compositor space
+ * @param height height in global compositor space
+ */
+ void (*logical_size)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ int32_t width, int32_t height);
+ /**
+ * all information about the output have been sent
+ *
+ * This event is sent after all other properties of an xdg_output
+ * have been sent.
+ *
+ * This allows changes to the xdg_output properties to be seen as
+ * atomic, even if they happen via multiple events.
+ *
+ * For objects version 3 onwards, this event is deprecated.
+ * Compositors are not required to send it anymore and must send
+ * wl_output.done instead.
+ */
+ void (*done)(void* data, struct zxdg_output_v1* zxdg_output_v1);
+ /**
+ * name of this output
+ *
+ * Many compositors will assign names to their outputs, show them
+ * to the user, allow them to be configured by name, etc. The
+ * client may wish to know this name as well to offer the user
+ * similar behaviors.
+ *
+ * The naming convention is compositor defined, but limited to
+ * alphanumeric characters and dashes (-). Each name is unique
+ * among all wl_output globals, but if a wl_output global is
+ * destroyed the same name may be reused later. The names will also
+ * remain consistent across sessions with the same hardware and
+ * software configuration.
+ *
+ * Examples of names include 'HDMI-A-1', 'WL-1', 'X11-1', etc.
+ * However, do not assume that the name is a reflection of an
+ * underlying DRM connector, X11 connection, etc.
+ *
+ * The name event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output). This event is only sent once
+ * per xdg_output, and the name does not change over the lifetime
+ * of the wl_output global.
+ * @param name output name
+ * @since 2
+ */
+ void (*name)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ const char* name);
+ /**
+ * human-readable description of this output
+ *
+ * Many compositors can produce human-readable descriptions of
+ * their outputs. The client may wish to know this description as
+ * well, to communicate the user for various purposes.
+ *
+ * The description is a UTF-8 string with no convention defined for
+ * its contents. Examples might include 'Foocorp 11" Display' or
+ * 'Virtual X11 output via :1'.
+ *
+ * The description event is sent after creating an xdg_output (see
+ * xdg_output_manager.get_xdg_output) and whenever the description
+ * changes. The description is optional, and may not be sent at
+ * all.
+ *
+ * For objects of version 2 and lower, this event is only sent once
+ * per xdg_output, and the description does not change over the
+ * lifetime of the wl_output global.
+ * @param description output description
+ * @since 2
+ */
+ void (*description)(void* data, struct zxdg_output_v1* zxdg_output_v1,
+ const char* description);
+};
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+static inline int zxdg_output_v1_add_listener(
+ struct zxdg_output_v1* zxdg_output_v1,
+ const struct zxdg_output_v1_listener* listener, void* data) {
+ return wl_proxy_add_listener((struct wl_proxy*)zxdg_output_v1,
+ (void (**)(void))listener, data);
+}
+
+#define ZXDG_OUTPUT_V1_DESTROY 0
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_POSITION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_LOGICAL_SIZE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DONE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_NAME_SINCE_VERSION 2
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESCRIPTION_SINCE_VERSION 2
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ */
+#define ZXDG_OUTPUT_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zxdg_output_v1 */
+static inline void zxdg_output_v1_set_user_data(
+ struct zxdg_output_v1* zxdg_output_v1, void* user_data) {
+ wl_proxy_set_user_data((struct wl_proxy*)zxdg_output_v1, user_data);
+}
+
+/** @ingroup iface_zxdg_output_v1 */
+static inline void* zxdg_output_v1_get_user_data(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ return wl_proxy_get_user_data((struct wl_proxy*)zxdg_output_v1);
+}
+
+static inline uint32_t zxdg_output_v1_get_version(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ return wl_proxy_get_version((struct wl_proxy*)zxdg_output_v1);
+}
+
+/**
+ * @ingroup iface_zxdg_output_v1
+ *
+ * Using this request a client can tell the server that it is not
+ * going to use the xdg_output object anymore.
+ */
+static inline void zxdg_output_v1_destroy(
+ struct zxdg_output_v1* zxdg_output_v1) {
+ wl_proxy_marshal((struct wl_proxy*)zxdg_output_v1, ZXDG_OUTPUT_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy*)zxdg_output_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c b/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c
new file mode 100644
index 0000000000..f80133f357
--- /dev/null
+++ b/widget/gtk/wayland/xdg-output-unstable-v1-protocol.c
@@ -0,0 +1,74 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2017 Red Hat Inc.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the "Software"),
+ * to deal in the Software without restriction, including without limitation
+ * the rights to use, copy, modify, merge, publish, distribute, sublicense,
+ * and/or sell copies of the Software, and to permit persons to whom the
+ * Software is furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the next
+ * paragraph) shall be included in all copies or substantial portions of the
+ * Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
+ * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
+ * DEALINGS IN THE SOFTWARE.
+ */
+
+#include <stdlib.h>
+#include <stdint.h>
+#include <gdk/gdkwayland.h>
+#include "wayland-util.h"
+
+#ifndef __has_attribute
+# define __has_attribute(x) 0 /* Compatibility with non-clang compilers. */
+#endif
+
+#if (__has_attribute(visibility) || defined(__GNUC__) && __GNUC__ >= 4)
+# define WL_PRIVATE __attribute__((visibility("hidden")))
+#else
+# define WL_PRIVATE
+#endif
+
+extern const struct wl_interface wl_output_interface;
+extern const struct wl_interface zxdg_output_v1_interface;
+
+static const struct wl_interface* xdg_output_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zxdg_output_v1_interface,
+ &wl_output_interface,
+};
+
+static const struct wl_message zxdg_output_manager_v1_requests[] = {
+ {"destroy", "", xdg_output_unstable_v1_types + 0},
+ {"get_xdg_output", "no", xdg_output_unstable_v1_types + 2},
+};
+
+WL_PRIVATE const struct wl_interface zxdg_output_manager_v1_interface = {
+ "zxdg_output_manager_v1", 3, 2, zxdg_output_manager_v1_requests, 0, NULL,
+};
+
+static const struct wl_message zxdg_output_v1_requests[] = {
+ {"destroy", "", xdg_output_unstable_v1_types + 0},
+};
+
+static const struct wl_message zxdg_output_v1_events[] = {
+ {"logical_position", "ii", xdg_output_unstable_v1_types + 0},
+ {"logical_size", "ii", xdg_output_unstable_v1_types + 0},
+ {"done", "", xdg_output_unstable_v1_types + 0},
+ {"name", "2s", xdg_output_unstable_v1_types + 0},
+ {"description", "2s", xdg_output_unstable_v1_types + 0},
+};
+
+WL_PRIVATE const struct wl_interface zxdg_output_v1_interface = {
+ "zxdg_output_v1", 3, 1, zxdg_output_v1_requests, 5, zxdg_output_v1_events,
+};