summaryrefslogtreecommitdiffstats
path: root/widget/gtk
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--widget/gtk/CompositorWidgetChild.cpp42
-rw-r--r--widget/gtk/CompositorWidgetChild.h39
-rw-r--r--widget/gtk/CompositorWidgetParent.cpp44
-rw-r--r--widget/gtk/CompositorWidgetParent.h38
-rw-r--r--widget/gtk/DMABufLibWrapper.cpp300
-rw-r--r--widget/gtk/DMABufLibWrapper.h168
-rw-r--r--widget/gtk/DMABufSurface.cpp1002
-rw-r--r--widget/gtk/DMABufSurface.h290
-rw-r--r--widget/gtk/GRefPtr.h32
-rw-r--r--widget/gtk/GtkCompositorWidget.cpp147
-rw-r--r--widget/gtk/GtkCompositorWidget.h103
-rw-r--r--widget/gtk/IMContextWrapper.cpp3169
-rw-r--r--widget/gtk/IMContextWrapper.h685
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.cpp45
-rw-r--r--widget/gtk/InProcessGtkCompositorWidget.h30
-rw-r--r--widget/gtk/MPRISInterfaceDescription.h91
-rw-r--r--widget/gtk/MPRISServiceHandler.cpp851
-rw-r--r--widget/gtk/MPRISServiceHandler.h188
-rw-r--r--widget/gtk/MediaKeysEventSourceFactory.cpp14
-rw-r--r--widget/gtk/MozContainer.cpp376
-rw-r--r--widget/gtk/MozContainer.h90
-rw-r--r--widget/gtk/MozContainerWayland.cpp514
-rw-r--r--widget/gtk/MozContainerWayland.h83
-rw-r--r--widget/gtk/NativeKeyBindings.cpp346
-rw-r--r--widget/gtk/NativeKeyBindings.h44
-rw-r--r--widget/gtk/PCompositorWidget.ipdl30
-rw-r--r--widget/gtk/PlatformWidgetTypes.ipdlh31
-rw-r--r--widget/gtk/ScreenHelperGTK.cpp195
-rw-r--r--widget/gtk/ScreenHelperGTK.h45
-rw-r--r--widget/gtk/TaskbarProgress.cpp108
-rw-r--r--widget/gtk/TaskbarProgress.h33
-rw-r--r--widget/gtk/WakeLockListener.cpp500
-rw-r--r--widget/gtk/WakeLockListener.h55
-rw-r--r--widget/gtk/WaylandVsyncSource.cpp214
-rw-r--r--widget/gtk/WaylandVsyncSource.h96
-rw-r--r--widget/gtk/WidgetStyleCache.cpp1464
-rw-r--r--widget/gtk/WidgetStyleCache.h59
-rw-r--r--widget/gtk/WidgetTraceEvent.cpp68
-rw-r--r--widget/gtk/WidgetUtilsGtk.cpp52
-rw-r--r--widget/gtk/WidgetUtilsGtk.h26
-rw-r--r--widget/gtk/WindowSurfaceProvider.cpp131
-rw-r--r--widget/gtk/WindowSurfaceProvider.h81
-rw-r--r--widget/gtk/WindowSurfaceWayland.cpp1116
-rw-r--r--widget/gtk/WindowSurfaceWayland.h269
-rw-r--r--widget/gtk/WindowSurfaceX11.cpp50
-rw-r--r--widget/gtk/WindowSurfaceX11.h40
-rw-r--r--widget/gtk/WindowSurfaceX11Image.cpp263
-rw-r--r--widget/gtk/WindowSurfaceX11Image.h48
-rw-r--r--widget/gtk/WindowSurfaceXRender.cpp75
-rw-r--r--widget/gtk/WindowSurfaceXRender.h37
-rw-r--r--widget/gtk/compat-gtk3/gdk/gdkversionmacros.h32
-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.h46
-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.conf166
-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/gtk3drawing.cpp3214
-rw-r--r--widget/gtk/gtkdrawing.h634
-rw-r--r--widget/gtk/maiRedundantObjectFactory.c81
-rw-r--r--widget/gtk/maiRedundantObjectFactory.h30
-rw-r--r--widget/gtk/moz.build178
-rw-r--r--widget/gtk/mozgtk/gtk2/moz.build40
-rw-r--r--widget/gtk/mozgtk/gtk3/moz.build38
-rw-r--r--widget/gtk/mozgtk/moz.build7
-rw-r--r--widget/gtk/mozgtk/mozgtk.c676
-rw-r--r--widget/gtk/mozgtk/stub/moz.build16
-rw-r--r--widget/gtk/mozwayland/moz.build16
-rw-r--r--widget/gtk/mozwayland/mozwayland.c201
-rw-r--r--widget/gtk/mozwayland/mozwayland.h134
-rw-r--r--widget/gtk/nsAppShell.cpp253
-rw-r--r--widget/gtk/nsAppShell.h34
-rw-r--r--widget/gtk/nsApplicationChooser.cpp131
-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.cpp822
-rw-r--r--widget/gtk/nsClipboard.h90
-rw-r--r--widget/gtk/nsClipboardWayland.cpp915
-rw-r--r--widget/gtk/nsClipboardWayland.h162
-rw-r--r--widget/gtk/nsClipboardX11.cpp340
-rw-r--r--widget/gtk/nsClipboardX11.h73
-rw-r--r--widget/gtk/nsColorPicker.cpp242
-rw-r--r--widget/gtk/nsColorPicker.h72
-rw-r--r--widget/gtk/nsDeviceContextSpecG.cpp341
-rw-r--r--widget/gtk/nsDeviceContextSpecG.h62
-rw-r--r--widget/gtk/nsDragService.cpp2122
-rw-r--r--widget/gtk/nsDragService.h210
-rw-r--r--widget/gtk/nsFilePicker.cpp646
-rw-r--r--widget/gtk/nsFilePicker.h87
-rw-r--r--widget/gtk/nsGTKToolkit.h53
-rw-r--r--widget/gtk/nsGtkCursors.h416
-rw-r--r--widget/gtk/nsGtkKeyUtils.cpp2377
-rw-r--r--widget/gtk/nsGtkKeyUtils.h467
-rw-r--r--widget/gtk/nsGtkUtils.h23
-rw-r--r--widget/gtk/nsIImageToPixbuf.h38
-rw-r--r--widget/gtk/nsImageToPixbuf.cpp108
-rw-r--r--widget/gtk/nsImageToPixbuf.h47
-rw-r--r--widget/gtk/nsLookAndFeel.cpp1536
-rw-r--r--widget/gtk/nsLookAndFeel.h131
-rw-r--r--widget/gtk/nsNativeBasicThemeGTK.cpp125
-rw-r--r--widget/gtk/nsNativeBasicThemeGTK.h44
-rw-r--r--widget/gtk/nsNativeThemeGTK.cpp2019
-rw-r--r--widget/gtk/nsNativeThemeGTK.h116
-rw-r--r--widget/gtk/nsPrintDialogGTK.cpp1054
-rw-r--r--widget/gtk/nsPrintDialogGTK.h37
-rw-r--r--widget/gtk/nsPrintSettingsGTK.cpp685
-rw-r--r--widget/gtk/nsPrintSettingsGTK.h145
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.cpp78
-rw-r--r--widget/gtk/nsPrintSettingsServiceGTK.h33
-rw-r--r--widget/gtk/nsSound.cpp398
-rw-r--r--widget/gtk/nsSound.h33
-rw-r--r--widget/gtk/nsToolkit.cpp31
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.cpp115
-rw-r--r--widget/gtk/nsUserIdleServiceGTK.h50
-rw-r--r--widget/gtk/nsWaylandDisplay.cpp309
-rw-r--r--widget/gtk/nsWaylandDisplay.h130
-rw-r--r--widget/gtk/nsWidgetFactory.cpp74
-rw-r--r--widget/gtk/nsWidgetFactory.h22
-rw-r--r--widget/gtk/nsWindow.cpp8420
-rw-r--r--widget/gtk/nsWindow.h719
-rw-r--r--widget/gtk/wayland/gbm.h480
-rw-r--r--widget/gtk/wayland/gtk-primary-selection-client-protocol.h580
-rw-r--r--widget/gtk/wayland/gtk-primary-selection-protocol.c115
-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.build35
-rw-r--r--widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h578
-rw-r--r--widget/gtk/wayland/primary-selection-unstable-v1-protocol.c115
-rw-r--r--widget/gtk/wayland/va_drmcommon.h156
-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
140 files changed, 49872 insertions, 0 deletions
diff --git a/widget/gtk/CompositorWidgetChild.cpp b/widget/gtk/CompositorWidgetChild.cpp
new file mode 100644
index 0000000000..ba51dda7a5
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.cpp
@@ -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/. */
+
+#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);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetChild.h b/widget/gtk/CompositorWidgetChild.h
new file mode 100644
index 0000000000..76adc95baf
--- /dev/null
+++ b/widget/gtk/CompositorWidgetChild.h
@@ -0,0 +1,39 @@
+/* -*- 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;
+
+ 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..adf49f3f13
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.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 "CompositorWidgetParent.h"
+#include "mozilla/Unused.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+
+namespace mozilla {
+namespace 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();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/CompositorWidgetParent.h b/widget/gtk/CompositorWidgetParent.h
new file mode 100644
index 0000000000..87e8d31d69
--- /dev/null
+++ b/widget/gtk/CompositorWidgetParent.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 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;
+
+ 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..7a9ae98ff8
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.cpp
@@ -0,0 +1,300 @@
+/* -*- 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 "nsWaylandDisplay.h"
+#include "DMABufLibWrapper.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/StaticPrefs_media.h"
+#include "mozilla/gfx/gfxVars.h"
+
+#include <gdk/gdk.h>
+#include <gdk/gdkx.h>
+
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <dlfcn.h>
+
+namespace mozilla {
+namespace widget {
+
+#define GBMLIB_NAME "libgbm.so.1"
+#define DRMLIB_NAME "libdrm.so.2"
+
+void* nsGbmLib::sGbmLibHandle = nullptr;
+void* nsGbmLib::sXf86DrmLibHandle = nullptr;
+bool nsGbmLib::sLibLoaded = false;
+CreateDeviceFunc nsGbmLib::sCreateDevice;
+CreateFunc nsGbmLib::sCreate;
+CreateWithModifiersFunc nsGbmLib::sCreateWithModifiers;
+GetModifierFunc nsGbmLib::sGetModifier;
+GetStrideFunc nsGbmLib::sGetStride;
+GetFdFunc nsGbmLib::sGetFd;
+DestroyFunc nsGbmLib::sDestroy;
+MapFunc nsGbmLib::sMap;
+UnmapFunc nsGbmLib::sUnmap;
+GetPlaneCountFunc nsGbmLib::sGetPlaneCount;
+GetHandleForPlaneFunc nsGbmLib::sGetHandleForPlane;
+GetStrideForPlaneFunc nsGbmLib::sGetStrideForPlane;
+GetOffsetFunc nsGbmLib::sGetOffset;
+DeviceIsFormatSupportedFunc nsGbmLib::sDeviceIsFormatSupported;
+DrmPrimeHandleToFDFunc nsGbmLib::sDrmPrimeHandleToFD;
+
+bool nsGbmLib::IsLoaded() {
+ return sCreateDevice != 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;
+}
+
+bool nsGbmLib::IsAvailable() {
+ if (!Load()) {
+ return false;
+ }
+ return IsLoaded();
+}
+
+bool nsGbmLib::Load() {
+ if (!sGbmLibHandle && !sLibLoaded) {
+ sLibLoaded = true;
+
+ 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");
+ 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");
+
+ 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");
+ if (!IsLoaded()) {
+ LOGDMABUF(("Failed to load all symbols from %s\n", GBMLIB_NAME));
+ }
+ }
+
+ return sGbmLibHandle;
+}
+
+gbm_device* nsDMABufDevice::GetGbmDevice() {
+ return IsDMABufEnabled() ? mGbmDevice : nullptr;
+}
+
+int nsDMABufDevice::GetGbmDeviceFd() { return IsDMABufEnabled() ? mGbmFd : -1; }
+
+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) {
+ auto* device = static_cast<nsDMABufDevice*>(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) {
+ auto* device = static_cast<nsDMABufDevice*>(data);
+ 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."));
+ device->ResetFormatsModifiers();
+ 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};
+
+nsDMABufDevice::nsDMABufDevice()
+ : mXRGBFormat({true, false, GBM_FORMAT_XRGB8888, nullptr, 0}),
+ mARGBFormat({true, true, GBM_FORMAT_ARGB8888, nullptr, 0}),
+ mGbmDevice(nullptr),
+ mGbmFd(-1) {
+ if (gdk_display_get_default() &&
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ wl_display* display = WaylandDisplayGetWLDisplay();
+ mRegistry = (void*)wl_display_get_registry(display);
+ wl_registry_add_listener((wl_registry*)mRegistry, &registry_listener, this);
+ wl_display_roundtrip(display);
+ wl_display_roundtrip(display);
+ }
+}
+
+nsDMABufDevice::~nsDMABufDevice() {
+ if (mRegistry) {
+ wl_registry_destroy((wl_registry*)mRegistry);
+ mRegistry = nullptr;
+ }
+}
+
+bool nsDMABufDevice::Configure() {
+ bool isDMABufUsed = (
+#ifdef NIGHTLY_BUILD
+ StaticPrefs::widget_dmabuf_textures_enabled() ||
+#endif
+ StaticPrefs::widget_dmabuf_webgl_enabled() ||
+ StaticPrefs::media_ffmpeg_vaapi_enabled() ||
+ StaticPrefs::media_ffmpeg_vaapi_drm_display_enabled());
+
+ if (!isDMABufUsed) {
+ // Disabled by user, just quit.
+ LOGDMABUF(("IsDMABufEnabled(): Disabled by preferences."));
+ return false;
+ }
+
+ if (!nsGbmLib::IsAvailable()) {
+ LOGDMABUF(("nsGbmLib is not available!"));
+ return false;
+ }
+
+ nsAutoCString drm_render_node(getenv("MOZ_WAYLAND_DRM_DEVICE"));
+ if (drm_render_node.IsEmpty()) {
+ drm_render_node.Assign(gfx::gfxVars::DrmRenderDevice());
+ if (drm_render_node.IsEmpty()) {
+ return false;
+ }
+ }
+
+ mGbmFd = open(drm_render_node.get(), O_RDWR);
+ if (mGbmFd < 0) {
+ LOGDMABUF(("Failed to open drm render node %s\n", drm_render_node.get()));
+ return false;
+ }
+
+ mGbmDevice = nsGbmLib::CreateDevice(mGbmFd);
+ if (!mGbmDevice) {
+ LOGDMABUF(
+ ("Failed to create drm render device %s\n", drm_render_node.get()));
+ close(mGbmFd);
+ mGbmFd = -1;
+ return false;
+ }
+
+ LOGDMABUF(("GBM device initialized"));
+ return true;
+}
+
+bool nsDMABufDevice::IsDMABufEnabled() {
+ static bool isDMABufEnabled = Configure();
+ return isDMABufEnabled;
+}
+
+#ifdef NIGHTLY_BUILD
+bool nsDMABufDevice::IsDMABufTexturesEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::widget_dmabuf_textures_enabled();
+}
+#else
+bool nsDMABufDevice::IsDMABufTexturesEnabled() { return false; }
+#endif
+bool nsDMABufDevice::IsDMABufVAAPIEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::media_ffmpeg_vaapi_enabled() &&
+ gfx::gfxVars::CanUseHardwareVideoDecoding() && !XRE_IsRDDProcess();
+}
+bool nsDMABufDevice::IsDMABufWebGLEnabled() {
+ return gfx::gfxVars::UseEGL() && IsDMABufEnabled() &&
+ StaticPrefs::widget_dmabuf_webgl_enabled();
+}
+
+GbmFormat* nsDMABufDevice::GetGbmFormat(bool aHasAlpha) {
+ GbmFormat* format = aHasAlpha ? &mARGBFormat : &mXRGBFormat;
+ return format->mIsSupported ? format : nullptr;
+}
+
+GbmFormat* nsDMABufDevice::GetExactGbmFormat(int aFormat) {
+ if (aFormat == mARGBFormat.mFormat) {
+ return &mARGBFormat;
+ } else if (aFormat == mXRGBFormat.mFormat) {
+ return &mXRGBFormat;
+ }
+
+ return nullptr;
+}
+
+void nsDMABufDevice::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->mModifiersCount++;
+ format->mModifiers =
+ (uint64_t*)realloc(format->mModifiers,
+ format->mModifiersCount * sizeof(*format->mModifiers));
+ format->mModifiers[format->mModifiersCount - 1] =
+ ((uint64_t)mModifierHi << 32) | mModifierLo;
+}
+
+void nsDMABufDevice::ResetFormatsModifiers() {
+ mARGBFormat.mModifiersCount = 0;
+ free(mARGBFormat.mModifiers);
+ mARGBFormat.mModifiers = nullptr;
+
+ mXRGBFormat.mModifiersCount = 0;
+ free(mXRGBFormat.mModifiers);
+ mXRGBFormat.mModifiers = nullptr;
+}
+
+nsDMABufDevice* GetDMABufDevice() {
+ static nsDMABufDevice dmaBufDevice;
+ return &dmaBufDevice;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/DMABufLibWrapper.h b/widget/gtk/DMABufLibWrapper.h
new file mode 100644
index 0000000000..99e82609ef
--- /dev/null
+++ b/widget/gtk/DMABufLibWrapper.h
@@ -0,0 +1,168 @@
+/* -*- 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 __MOZ_DMABUF_LIB_WRAPPER_H__
+#define __MOZ_DMABUF_LIB_WRAPPER_H__
+
+#include "mozilla/widget/gbm.h"
+
+#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 */
+
+namespace mozilla {
+namespace widget {
+
+typedef struct gbm_device* (*CreateDeviceFunc)(int);
+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*);
+
+class nsGbmLib {
+ public:
+ static bool Load();
+ static bool IsLoaded();
+ static bool IsAvailable();
+ static bool IsModifierAvailable();
+
+ static struct gbm_device* CreateDevice(int fd) { return sCreateDevice(fd); };
+ static struct gbm_bo* Create(struct gbm_device* gbm, uint32_t width,
+ uint32_t height, uint32_t format,
+ uint32_t flags) {
+ return sCreate(gbm, width, height, format, flags);
+ }
+ static void Destroy(struct gbm_bo* bo) { sDestroy(bo); }
+ static uint32_t GetStride(struct gbm_bo* bo) { return sGetStride(bo); }
+ static int GetFd(struct gbm_bo* bo) { 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) {
+ return sMap(bo, x, y, width, height, flags, stride, map_data);
+ }
+ static void Unmap(struct gbm_bo* bo, void* map_data) { 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) {
+ return sCreateWithModifiers(gbm, width, height, format, modifiers, count);
+ }
+ static uint64_t GetModifier(struct gbm_bo* bo) { return sGetModifier(bo); }
+ static int GetPlaneCount(struct gbm_bo* bo) { return sGetPlaneCount(bo); }
+ static union gbm_bo_handle GetHandleForPlane(struct gbm_bo* bo, int plane) {
+ return sGetHandleForPlane(bo, plane);
+ }
+ static uint32_t GetStrideForPlane(struct gbm_bo* bo, int plane) {
+ return sGetStrideForPlane(bo, plane);
+ }
+ static uint32_t GetOffset(struct gbm_bo* bo, int plane) {
+ return sGetOffset(bo, plane);
+ }
+ static int DeviceIsFormatSupported(struct gbm_device* gbm, uint32_t format,
+ uint32_t usage) {
+ return sDeviceIsFormatSupported(gbm, format, usage);
+ }
+
+ static int DrmPrimeHandleToFD(int fd, uint32_t handle, uint32_t flags,
+ int* prime_fd) {
+ return sDrmPrimeHandleToFD(fd, handle, flags, prime_fd);
+ }
+
+ private:
+ static CreateDeviceFunc sCreateDevice;
+ 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 void* sGbmLibHandle;
+ static void* sXf86DrmLibHandle;
+ static bool sLibLoaded;
+};
+
+struct GbmFormat {
+ bool mIsSupported;
+ bool mHasAlpha;
+ int mFormat;
+ uint64_t* mModifiers;
+ int mModifiersCount;
+};
+
+class nsDMABufDevice {
+ public:
+ nsDMABufDevice();
+ ~nsDMABufDevice();
+
+ gbm_device* GetGbmDevice();
+ // Returns -1 if we fails to gbm device file descriptor.
+ int GetGbmDeviceFd();
+
+ // Use dmabuf for WebRender general web content
+ bool IsDMABufTexturesEnabled();
+ // Use dmabuf for VA-API video playback
+ bool IsDMABufVAAPIEnabled();
+ // Use dmabuf for WebGL content
+ bool IsDMABufWebGLEnabled();
+
+ GbmFormat* GetGbmFormat(bool aHasAlpha);
+ GbmFormat* GetExactGbmFormat(int aFormat);
+ void ResetFormatsModifiers();
+ void AddFormatModifier(bool aHasAlpha, int aFormat, uint32_t mModifierHi,
+ uint32_t mModifierLo);
+
+ private:
+ bool IsDMABufEnabled();
+ bool Configure();
+
+ void* mRegistry;
+
+ GbmFormat mXRGBFormat;
+ GbmFormat mARGBFormat;
+
+ gbm_device* mGbmDevice;
+ int mGbmFd;
+};
+
+nsDMABufDevice* 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..1e1719780f
--- /dev/null
+++ b/widget/gtk/DMABufSurface.cpp
@@ -0,0 +1,1002 @@
+/* -*- 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 <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>
+#include <sys/eventfd.h>
+#include <poll.h>
+
+#include "mozilla/widget/gbm.h"
+#include "mozilla/widget/va_drmcommon.h"
+#include "GLContextTypes.h" // for GLContext, etc
+#include "GLContextEGL.h"
+#include "GLContextProvider.h"
+#include "ScopedGLHelpers.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;
+
+#ifndef DRM_FORMAT_MOD_INVALID
+# define DRM_FORMAT_MOD_INVALID ((1ULL << 56) - 1)
+#endif
+#define BUFFER_FLAGS 0
+
+#ifndef GBM_BO_USE_TEXTURING
+# define GBM_BO_USE_TEXTURING (1 << 5)
+#endif
+
+#ifndef VA_FOURCC_NV12
+# define VA_FOURCC_NV12 0x3231564E
+#endif
+
+#ifndef VA_FOURCC_YV12
+# define VA_FOURCC_YV12 0x32315659
+#endif
+
+static Atomic<int> gNewSurfaceUID(1);
+
+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() {
+ MOZ_ASSERT(mGlobalRefCountFd);
+ uint64_t counter;
+ if (read(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ // EAGAIN means the refcount is already zero. It happens when we release
+ // last reference to the surface.
+ if (errno != EAGAIN) {
+ NS_WARNING("Failed to unref dmabuf global ref count!");
+ }
+ }
+}
+
+void DMABufSurface::GlobalRefAdd() {
+ MOZ_ASSERT(mGlobalRefCountFd);
+ uint64_t counter = 1;
+ if (write(mGlobalRefCountFd, &counter, sizeof(counter)) != sizeof(counter)) {
+ NS_WARNING("Failed to ref dmabuf global ref count!");
+ }
+}
+
+void DMABufSurface::GlobalRefCountCreate() {
+ MOZ_ASSERT(!mGlobalRefCountFd);
+ mGlobalRefCountFd = eventfd(0, EFD_CLOEXEC | EFD_NONBLOCK | EFD_SEMAPHORE);
+ if (mGlobalRefCountFd < 0) {
+ NS_WARNING("Failed to create dmabuf global ref count!");
+ mGlobalRefCountFd = 0;
+ return;
+ }
+}
+
+void DMABufSurface::GlobalRefCountImport(int aFd) {
+ MOZ_ASSERT(!mGlobalRefCountFd);
+ mGlobalRefCountFd = aFd;
+ GlobalRefAdd();
+}
+
+void DMABufSurface::GlobalRefCountDelete() {
+ if (mGlobalRefCountFd) {
+ GlobalRefRelease();
+ close(mGlobalRefCountFd);
+ mGlobalRefCountFd = 0;
+ }
+}
+
+void DMABufSurface::ReleaseDMABuf() {
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ Unmap(i);
+
+ if (mDmabufFds[i] >= 0) {
+ close(mDmabufFds[i]);
+ mDmabufFds[i] = -1;
+ }
+ }
+
+ if (mGbmBufferObject[0]) {
+ nsGbmLib::Destroy(mGbmBufferObject[0]);
+ mGbmBufferObject[0] = nullptr;
+ }
+}
+
+DMABufSurface::DMABufSurface(SurfaceType aSurfaceType)
+ : mSurfaceType(aSurfaceType),
+ mBufferModifier(DRM_FORMAT_MOD_INVALID),
+ mBufferPlaneCount(0),
+ mDrmFormats(),
+ mStrides(),
+ mOffsets(),
+ mGbmBufferObject(),
+ mMappedRegion(),
+ mMappedRegionStride(),
+ mSyncFd(-1),
+ mSync(0),
+ mGlobalRefCountFd(0),
+ mUID(gNewSurfaceUID++) {
+ for (auto& slot : mDmabufFds) {
+ slot = -1;
+ }
+}
+
+DMABufSurface::~DMABufSurface() {
+ FenceDelete();
+ 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 (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mSyncFd > 0) {
+ close(mSyncFd);
+ mSyncFd = -1;
+ }
+
+ if (mSync) {
+ egl->fDestroySync(mSync);
+ mSync = nullptr;
+ }
+}
+
+void DMABufSurface::FenceSet() {
+ if (!mGL || !mGL->MakeCurrent()) {
+ 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) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (!mSync && mSyncFd > 0) {
+ FenceImportFromFd();
+ }
+
+ // Wait on the fence, because presumably we're going to want to read this
+ // surface
+ if (mSync) {
+ egl->fClientWaitSync(mSync, 0, LOCAL_EGL_FOREVER);
+ }
+}
+
+bool DMABufSurface::FenceImportFromFd() {
+ if (!mGL) return false;
+ 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};
+ mSync = egl->fCreateSync(LOCAL_EGL_SYNC_NATIVE_FENCE_ANDROID, attribs);
+ close(mSyncFd);
+ mSyncFd = -1;
+
+ if (!mSync) {
+ MOZ_ASSERT(false, "Failed to create GLFence!");
+ return false;
+ }
+
+ return true;
+}
+
+DMABufSurfaceRGBA::DMABufSurfaceRGBA()
+ : DMABufSurface(SURFACE_RGBA),
+ mSurfaceFlags(0),
+ mWidth(0),
+ mHeight(0),
+ mGmbFormat(nullptr),
+ mEGLImage(LOCAL_EGL_NO_IMAGE),
+ mTexture(0),
+ mGbmBufferFlags(0) {}
+
+DMABufSurfaceRGBA::~DMABufSurfaceRGBA() { ReleaseSurface(); }
+
+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));
+
+ mGmbFormat = GetDMABufDevice()->GetGbmFormat(mSurfaceFlags & DMABUF_ALPHA);
+ if (!mGmbFormat) {
+ // Requested DRM format is not supported.
+ return false;
+ }
+
+ bool useModifiers = (aDMABufSurfaceFlags & DMABUF_USE_MODIFIERS) &&
+ mGmbFormat->mModifiersCount > 0;
+ if (useModifiers) {
+ LOGDMABUF((" Creating with modifiers\n"));
+ mGbmBufferObject[0] = nsGbmLib::CreateWithModifiers(
+ GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight, mGmbFormat->mFormat,
+ mGmbFormat->mModifiers, mGmbFormat->mModifiersCount);
+ if (mGbmBufferObject[0]) {
+ mBufferModifier = nsGbmLib::GetModifier(mGbmBufferObject[0]);
+ }
+ }
+
+ // Create without modifiers - use plain/linear format.
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Creating without modifiers\n"));
+ mGbmBufferFlags = (GBM_BO_USE_SCANOUT | GBM_BO_USE_LINEAR);
+ if (mSurfaceFlags & DMABUF_TEXTURE) {
+ mGbmBufferFlags |= GBM_BO_USE_TEXTURING;
+ }
+
+ if (!nsGbmLib::DeviceIsFormatSupported(GetDMABufDevice()->GetGbmDevice(),
+ mGmbFormat->mFormat,
+ mGbmBufferFlags)) {
+ mGbmBufferFlags &= ~GBM_BO_USE_SCANOUT;
+ }
+
+ mGbmBufferObject[0] =
+ nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), mWidth, mHeight,
+ mGmbFormat->mFormat, mGbmBufferFlags);
+
+ mBufferModifier = DRM_FORMAT_MOD_INVALID;
+ }
+
+ if (!mGbmBufferObject[0]) {
+ LOGDMABUF((" Failed to create GbmBufferObject\n"));
+ return false;
+ }
+
+ if (mBufferModifier != DRM_FORMAT_MOD_INVALID) {
+ mBufferPlaneCount = nsGbmLib::GetPlaneCount(mGbmBufferObject[0]);
+ if (mBufferPlaneCount > DMABUF_BUFFER_PLANES) {
+ NS_WARNING("There's too many dmabuf planes!");
+ ReleaseSurface();
+ return false;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ uint32_t handle = nsGbmLib::GetHandleForPlane(mGbmBufferObject[0], i).u32;
+ int ret = nsGbmLib::DrmPrimeHandleToFD(
+ GetDMABufDevice()->GetGbmDeviceFd(), handle, 0, &mDmabufFds[i]);
+ if (ret < 0 || mDmabufFds[i] < 0) {
+ ReleaseSurface();
+ return false;
+ }
+ mStrides[i] = nsGbmLib::GetStrideForPlane(mGbmBufferObject[0], i);
+ mOffsets[i] = nsGbmLib::GetOffset(mGbmBufferObject[0], i);
+ }
+ } else {
+ mBufferPlaneCount = 1;
+ mStrides[0] = nsGbmLib::GetStride(mGbmBufferObject[0]);
+ mDmabufFds[0] = nsGbmLib::GetFd(mGbmBufferObject[0]);
+ if (mDmabufFds[0] < 0) {
+ ReleaseSurface();
+ return false;
+ }
+ }
+
+ LOGDMABUF((" Success\n"));
+ return true;
+}
+
+void DMABufSurfaceRGBA::ImportSurfaceDescriptor(
+ const SurfaceDescriptor& aDesc) {
+ const SurfaceDescriptorDMABuf& desc = aDesc.get_SurfaceDescriptorDMABuf();
+
+ mWidth = desc.width()[0];
+ mHeight = desc.height()[0];
+ mBufferModifier = desc.modifier();
+ if (mBufferModifier != DRM_FORMAT_MOD_INVALID) {
+ mGmbFormat = GetDMABufDevice()->GetExactGbmFormat(desc.format()[0]);
+ } else {
+ mDrmFormats[0] = desc.format()[0];
+ }
+ mBufferPlaneCount = desc.fds().Length();
+ mGbmBufferFlags = desc.flags();
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ mUID = desc.uid();
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = desc.fds()[i].ClonePlatformHandle().release();
+ mStrides[i] = desc.strides()[i];
+ mOffsets[i] = desc.offsets()[i];
+ }
+
+ if (desc.fence().Length() > 0) {
+ mSyncFd = desc.fence()[0].ClonePlatformHandle().release();
+ }
+
+ if (desc.refCount().Length() > 0) {
+ GlobalRefCountImport(desc.refCount()[0].ClonePlatformHandle().release());
+ }
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Import() UID %d\n", mUID));
+}
+
+bool DMABufSurfaceRGBA::Create(const SurfaceDescriptor& aDesc) {
+ ImportSurfaceDescriptor(aDesc);
+ return true;
+}
+
+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<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ LOGDMABUF(("DMABufSurfaceRGBA::Serialize() UID %d\n", mUID));
+
+ width.AppendElement(mWidth);
+ height.AppendElement(mHeight);
+ format.AppendElement(mGmbFormat->mFormat);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ }
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd));
+ }
+
+ aOutDescriptor =
+ SurfaceDescriptorDMABuf(mSurfaceType, mBufferModifier, mGbmBufferFlags,
+ fds, width, height, format, strides, offsets,
+ GetYUVColorSpace(), fenceFDs, mUID, refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceRGBA::CreateTexture(GLContext* aGLContext, int aPlane) {
+ 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);
+ if (mGmbFormat) {
+ attribs.AppendElement(mGmbFormat->mFormat);
+ } else {
+ 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 (mBufferModifier != DRM_FORMAT_MOD_INVALID) { \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_LO_EXT); \
+ attribs.AppendElement(mBufferModifier & 0xFFFFFFFF); \
+ attribs.AppendElement( \
+ LOCAL_EGL_DMA_BUF_PLANE##plane_idx##_MODIFIER_HI_EXT); \
+ attribs.AppendElement(mBufferModifier >> 32); \
+ } \
+ }
+ 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;
+ 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());
+ if (mEGLImage == LOCAL_EGL_NO_IMAGE) {
+ NS_WARNING("EGLImageKHR creation failed");
+ return false;
+ }
+
+ aGLContext->MakeCurrent();
+ 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() {
+ FenceDelete();
+
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (mTexture && mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(1, &mTexture);
+ mTexture = 0;
+ mGL = nullptr;
+ }
+
+ if (mEGLImage) {
+ egl->fDestroyImage(mEGLImage);
+ mEGLImage = nullptr;
+ }
+}
+
+void DMABufSurfaceRGBA::ReleaseSurface() {
+ MOZ_ASSERT(!IsMapped(), "We can't release mapped buffer!");
+
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
+
+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 size %d x %d -> %d x %d\n",
+ mUID, aX, aY, aWidth, aHeight));
+
+ mMappedRegionStride[aPlane] = 0;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegion[aPlane] = nsGbmLib::Map(
+ mGbmBufferObject[aPlane], aX, aY, aWidth, aHeight, aGbmFlags,
+ &mMappedRegionStride[aPlane], &mMappedRegionData[aPlane]);
+ if (aStride) {
+ *aStride = mMappedRegionStride[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]) {
+ nsGbmLib::Unmap(mGbmBufferObject[aPlane], mMappedRegionData[aPlane]);
+ mMappedRegion[aPlane] = nullptr;
+ mMappedRegionData[aPlane] = nullptr;
+ mMappedRegionStride[aPlane] = 0;
+ }
+}
+
+#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 : true;
+}
+
+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<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ const VADRMPRIMESurfaceDescriptor& aDesc) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ if (!surf->UpdateYUVData(aDesc)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+already_AddRefed<DMABufSurfaceYUV> DMABufSurfaceYUV::CreateYUVSurface(
+ int aWidth, int aHeight, void** aPixelData, int* aLineSizes) {
+ RefPtr<DMABufSurfaceYUV> surf = new DMABufSurfaceYUV();
+ if (!surf->Create(aWidth, aHeight, aPixelData, aLineSizes)) {
+ return nullptr;
+ }
+ return surf.forget();
+}
+
+DMABufSurfaceYUV::DMABufSurfaceYUV()
+ : DMABufSurface(SURFACE_NV12),
+ mWidth(),
+ mHeight(),
+ mTexture(),
+ mColorSpace(mozilla::gfx::YUVColorSpace::UNKNOWN) {
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mEGLImage[i] = LOCAL_EGL_NO_IMAGE;
+ }
+}
+
+DMABufSurfaceYUV::~DMABufSurfaceYUV() { ReleaseSurface(); }
+
+bool DMABufSurfaceYUV::UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc) {
+ if (aDesc.num_layers > DMABUF_BUFFER_PLANES ||
+ aDesc.num_objects > DMABUF_BUFFER_PLANES) {
+ return false;
+ }
+ if (mDmabufFds[0] >= 0) {
+ NS_WARNING("DMABufSurfaceYUV is already created!");
+ return false;
+ }
+ if (aDesc.fourcc == VA_FOURCC_NV12) {
+ mSurfaceType = SURFACE_NV12;
+ } else if (aDesc.fourcc == VA_FOURCC_YV12) {
+ mSurfaceType = SURFACE_YUV420;
+ } else {
+ NS_WARNING(
+ nsPrintfCString(
+ "UpdateYUVData(): Can't import surface data of 0x%x format\n",
+ aDesc.fourcc)
+ .get());
+ return false;
+ }
+
+ mBufferPlaneCount = aDesc.num_layers;
+ mBufferModifier = aDesc.objects[0].drm_format_modifier;
+
+ for (unsigned int i = 0; i < aDesc.num_layers; i++) {
+ // Intel exports VA-API surfaces in one object,planes have the same FD.
+ // AMD exports surfaces in two objects with different FDs.
+ bool dupFD = (aDesc.layers[i].object_index[0] != i);
+ int fd = aDesc.objects[aDesc.layers[i].object_index[0]].fd;
+ mDmabufFds[i] = dupFD ? dup(fd) : fd;
+
+ mDrmFormats[i] = aDesc.layers[i].drm_format;
+ mOffsets[i] = aDesc.layers[i].offset[0];
+ mStrides[i] = aDesc.layers[i].pitch[0];
+ mWidth[i] = aDesc.width >> i;
+ mHeight[i] = aDesc.height >> i;
+ }
+
+ return true;
+}
+
+bool DMABufSurfaceYUV::CreateYUVPlane(int aPlane, int aWidth, int aHeight,
+ int aDrmFormat) {
+ mWidth[aPlane] = aWidth;
+ mHeight[aPlane] = aHeight;
+ mDrmFormats[aPlane] = aDrmFormat;
+
+ mGbmBufferObject[aPlane] =
+ nsGbmLib::Create(GetDMABufDevice()->GetGbmDevice(), aWidth, aHeight,
+ aDrmFormat, GBM_BO_USE_LINEAR | GBM_BO_USE_TEXTURING);
+ if (!mGbmBufferObject[aPlane]) {
+ NS_WARNING("Failed to create GbmBufferObject!");
+ return false;
+ }
+
+ mStrides[aPlane] = nsGbmLib::GetStride(mGbmBufferObject[aPlane]);
+ mDmabufFds[aPlane] = nsGbmLib::GetFd(mGbmBufferObject[aPlane]);
+
+ return true;
+}
+
+void DMABufSurfaceYUV::UpdateYUVPlane(int aPlane, void* aPixelData,
+ int aLineSize) {
+ 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) {
+ if (mSurfaceType != SURFACE_YUV420) {
+ NS_WARNING("UpdateYUVData can upload YUV420 surface type only!");
+ return false;
+ }
+
+ if (mBufferPlaneCount != 3) {
+ NS_WARNING("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]) {
+ NS_WARNING("DMABufSurfaceYUV plane can't be mapped!");
+ return false;
+ }
+ if ((int)mMappedRegionStride[i] < mWidth[i]) {
+ NS_WARNING("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) {
+ mSurfaceType = SURFACE_YUV420;
+ mBufferPlaneCount = 3;
+
+ if (!CreateYUVPlane(0, aWidth, aHeight, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateYUVPlane(1, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+ if (!CreateYUVPlane(2, aWidth >> 1, aHeight >> 1, GBM_FORMAT_R8)) {
+ return false;
+ }
+
+ return aPixelData != nullptr && aLineSizes != nullptr
+ ? UpdateYUVData(aPixelData, aLineSizes)
+ : true;
+}
+
+bool DMABufSurfaceYUV::Create(const SurfaceDescriptor& aDesc) {
+ ImportSurfaceDescriptor(aDesc);
+ return true;
+}
+
+void DMABufSurfaceYUV::ImportSurfaceDescriptor(
+ const SurfaceDescriptorDMABuf& aDesc) {
+ mBufferPlaneCount = aDesc.fds().Length();
+ mSurfaceType = (mBufferPlaneCount == 2) ? SURFACE_NV12 : SURFACE_YUV420;
+ mBufferModifier = aDesc.modifier();
+ mColorSpace = aDesc.yUVColorSpace();
+ mUID = aDesc.uid();
+
+ MOZ_RELEASE_ASSERT(mBufferPlaneCount <= DMABUF_BUFFER_PLANES);
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ mDmabufFds[i] = aDesc.fds()[i].ClonePlatformHandle().release();
+ mWidth[i] = aDesc.width()[i];
+ mHeight[i] = aDesc.height()[i];
+ mDrmFormats[i] = aDesc.format()[i];
+ mStrides[i] = aDesc.strides()[i];
+ mOffsets[i] = aDesc.offsets()[i];
+ }
+
+ if (aDesc.fence().Length() > 0) {
+ mSyncFd = aDesc.fence()[0].ClonePlatformHandle().release();
+ }
+
+ if (aDesc.refCount().Length() > 0) {
+ GlobalRefCountImport(aDesc.refCount()[0].ClonePlatformHandle().release());
+ }
+}
+
+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> format;
+ AutoTArray<ipc::FileDescriptor, DMABUF_BUFFER_PLANES> fds;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> strides;
+ AutoTArray<uint32_t, DMABUF_BUFFER_PLANES> offsets;
+ AutoTArray<ipc::FileDescriptor, 1> fenceFDs;
+ AutoTArray<ipc::FileDescriptor, 1> refCountFDs;
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ width.AppendElement(mWidth[i]);
+ height.AppendElement(mHeight[i]);
+ format.AppendElement(mDrmFormats[i]);
+ fds.AppendElement(ipc::FileDescriptor(mDmabufFds[i]));
+ strides.AppendElement(mStrides[i]);
+ offsets.AppendElement(mOffsets[i]);
+ }
+
+ if (mSync) {
+ fenceFDs.AppendElement(ipc::FileDescriptor(mSyncFd));
+ }
+
+ if (mGlobalRefCountFd) {
+ refCountFDs.AppendElement(ipc::FileDescriptor(mGlobalRefCountFd));
+ }
+
+ aOutDescriptor = SurfaceDescriptorDMABuf(
+ mSurfaceType, mBufferModifier, 0, fds, width, height, format, strides,
+ offsets, GetYUVColorSpace(), fenceFDs, mUID, refCountFDs);
+ return true;
+}
+
+bool DMABufSurfaceYUV::CreateTexture(GLContext* aGLContext, int aPlane) {
+ MOZ_ASSERT(!mEGLImage[aPlane] && !mTexture[aPlane],
+ "EGLImage/Texture is already created!");
+
+ if (!aGLContext) return false;
+ const auto& gle = gl::GLContextEGL::Cast(aGLContext);
+ const auto& egl = gle->mEgl;
+
+ nsTArray<EGLint> attribs;
+ attribs.AppendElement(LOCAL_EGL_WIDTH);
+ attribs.AppendElement(mWidth[aPlane]);
+ attribs.AppendElement(LOCAL_EGL_HEIGHT);
+ attribs.AppendElement(mHeight[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]);
+ 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());
+ if (mEGLImage[aPlane] == LOCAL_EGL_NO_IMAGE) {
+ NS_WARNING("EGLImageKHR creation failed");
+ return false;
+ }
+
+ aGLContext->MakeCurrent();
+ 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() {
+ FenceDelete();
+
+ bool textureActive = false;
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mTexture[i]) {
+ textureActive = true;
+ break;
+ }
+ }
+
+ if (!mGL) return;
+ const auto& gle = gl::GLContextEGL::Cast(mGL);
+ const auto& egl = gle->mEgl;
+
+ if (textureActive && mGL->MakeCurrent()) {
+ mGL->fDeleteTextures(DMABUF_BUFFER_PLANES, mTexture);
+ for (int i = 0; i < DMABUF_BUFFER_PLANES; i++) {
+ mTexture[i] = 0;
+ }
+ mGL = nullptr;
+ }
+
+ for (int i = 0; i < mBufferPlaneCount; i++) {
+ if (mEGLImage[i]) {
+ egl->fDestroyImage(mEGLImage[i]);
+ mEGLImage[i] = nullptr;
+ }
+ }
+}
+
+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(); }
+
+uint32_t 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() {
+ ReleaseTextures();
+ ReleaseDMABuf();
+}
diff --git a/widget/gtk/DMABufSurface.h b/widget/gtk/DMABufSurface.h
new file mode 100644
index 0000000000..936c3c75a8
--- /dev/null
+++ b/widget/gtk/DMABufSurface.h
@@ -0,0 +1,290 @@
+/* -*- 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 <stdint.h>
+#include "mozilla/widget/nsWaylandDisplay.h"
+#include "mozilla/widget/va_drmcommon.h"
+#include "GLTypes.h"
+
+typedef void* EGLImageKHR;
+typedef void* EGLSyncKHR;
+
+#define DMABUF_BUFFER_PLANES 4
+
+namespace mozilla {
+namespace layers {
+class SurfaceDescriptor;
+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;
+
+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 uint32_t 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 mozilla::gfx::YUVColorSpace GetYUVColorSpace() {
+ return mozilla::gfx::YUVColorSpace::UNKNOWN;
+ };
+ virtual bool IsFullRange() { return false; };
+
+ 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();
+
+ // 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;
+ bool FenceImportFromFd();
+
+ void GlobalRefCountImport(int aFd);
+ void GlobalRefCountDelete();
+
+ void ReleaseDMABuf();
+
+ void* MapInternal(uint32_t aX, uint32_t aY, uint32_t aWidth, uint32_t aHeight,
+ uint32_t* aStride, int aGbmFlags, int aPlane = 0);
+
+ virtual ~DMABufSurface();
+
+ SurfaceType mSurfaceType;
+ uint64_t mBufferModifier;
+
+ int mBufferPlaneCount;
+ int mDmabufFds[DMABUF_BUFFER_PLANES];
+ uint32_t mDrmFormats[DMABUF_BUFFER_PLANES];
+ uint32_t mStrides[DMABUF_BUFFER_PLANES];
+ uint32_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;
+};
+
+class DMABufSurfaceRGBA : public DMABufSurface {
+ public:
+ static already_AddRefed<DMABufSurfaceRGBA> CreateDMABufSurface(
+ int aWidth, int aHeight, int aDMABufSurfaceFlags);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor);
+
+ DMABufSurfaceRGBA* GetAsDMABufSurfaceRGBA() { return this; }
+
+ void Clear();
+
+ void ReleaseSurface();
+
+ bool CopyFrom(class DMABufSurface* aSourceSurface);
+
+ int GetWidth(int aPlane = 0) { return mWidth; };
+ int GetHeight(int aPlane = 0) { return mHeight; };
+ mozilla::gfx::SurfaceFormat GetFormat();
+ mozilla::gfx::SurfaceFormat GetFormatGL();
+ 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);
+ void ReleaseTextures();
+ GLuint GetTexture(int aPlane = 0) { return mTexture; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) { return mEGLImage; };
+
+ uint32_t GetTextureCount() { return 1; };
+
+#ifdef DEBUG
+ virtual void DumpToFile(const char* pFile);
+#endif
+
+ DMABufSurfaceRGBA();
+
+ private:
+ ~DMABufSurfaceRGBA();
+
+ bool Create(int aWidth, int aHeight, int aDMABufSurfaceFlags);
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ void ImportSurfaceDescriptor(const mozilla::layers::SurfaceDescriptor& aDesc);
+
+ private:
+ int mSurfaceFlags;
+
+ int mWidth;
+ int mHeight;
+ mozilla::widget::GbmFormat* mGmbFormat;
+
+ EGLImageKHR mEGLImage;
+ GLuint mTexture;
+ uint32_t mGbmBufferFlags;
+};
+
+class DMABufSurfaceYUV : 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);
+
+ bool Serialize(mozilla::layers::SurfaceDescriptor& aOutDescriptor);
+
+ DMABufSurfaceYUV* GetAsDMABufSurfaceYUV() { return this; };
+
+ int GetWidth(int aPlane = 0) { return mWidth[aPlane]; }
+ int GetHeight(int aPlane = 0) { return mHeight[aPlane]; }
+ mozilla::gfx::SurfaceFormat GetFormat();
+ mozilla::gfx::SurfaceFormat GetFormatGL();
+
+ bool CreateTexture(mozilla::gl::GLContext* aGLContext, int aPlane = 0);
+ void ReleaseTextures();
+
+ void ReleaseSurface();
+
+ GLuint GetTexture(int aPlane = 0) { return mTexture[aPlane]; };
+ EGLImageKHR GetEGLImage(int aPlane = 0) { return mEGLImage[aPlane]; };
+
+ uint32_t GetTextureCount();
+
+ void SetYUVColorSpace(mozilla::gfx::YUVColorSpace aColorSpace) {
+ mColorSpace = aColorSpace;
+ }
+ mozilla::gfx::YUVColorSpace GetYUVColorSpace() { return mColorSpace; }
+
+ bool IsFullRange() { return true; }
+
+ DMABufSurfaceYUV();
+
+ bool UpdateYUVData(void** aPixelData, int* aLineSizes);
+ bool UpdateYUVData(const VADRMPRIMESurfaceDescriptor& aDesc);
+
+ private:
+ ~DMABufSurfaceYUV();
+
+ bool Create(const mozilla::layers::SurfaceDescriptor& aDesc);
+ bool Create(int aWidth, int aHeight, void** aPixelData, int* aLineSizes);
+ bool CreateYUVPlane(int aPlane, int aWidth, int aHeight, int aDrmFormat);
+ void UpdateYUVPlane(int aPlane, void* aPixelData, int aLineSize);
+
+ void ImportSurfaceDescriptor(
+ const mozilla::layers::SurfaceDescriptorDMABuf& aDesc);
+
+ int mWidth[DMABUF_BUFFER_PLANES];
+ int mHeight[DMABUF_BUFFER_PLANES];
+ EGLImageKHR mEGLImage[DMABUF_BUFFER_PLANES];
+ GLuint mTexture[DMABUF_BUFFER_PLANES];
+ mozilla::gfx::YUVColorSpace mColorSpace;
+};
+
+#endif
diff --git a/widget/gtk/GRefPtr.h b/widget/gtk/GRefPtr.h
new file mode 100644
index 0000000000..3705d5354d
--- /dev/null
+++ b/widget/gtk/GRefPtr.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 GRefPtr_h_
+#define GRefPtr_h_
+
+// Allows to use RefPtr<T> with various kinds of GObjects
+
+#include <gdk/gdk.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); }
+};
+
+template <>
+struct RefPtrTraits<GtkWidget> : public GObjectRefPtrTraits<GtkWidget> {};
+
+template <>
+struct RefPtrTraits<GdkDragContext>
+ : public GObjectRefPtrTraits<GdkDragContext> {};
+
+} // namespace mozilla
+
+#endif
diff --git a/widget/gtk/GtkCompositorWidget.cpp b/widget/gtk/GtkCompositorWidget.cpp
new file mode 100644
index 0000000000..bfb970ec47
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.cpp
@@ -0,0 +1,147 @@
+/* -*- 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 "gfxPlatformGtk.h"
+#include "mozilla/layers/CompositorThread.h"
+#include "mozilla/widget/InProcessCompositorWidget.h"
+#include "mozilla/widget/PlatformWidgetTypes.h"
+#include "nsWindow.h"
+
+namespace mozilla {
+namespace widget {
+
+GtkCompositorWidget::GtkCompositorWidget(
+ const GtkCompositorWidgetInitData& aInitData,
+ const layers::CompositorOptions& aOptions, nsWindow* aWindow)
+ : CompositorWidget(aOptions),
+ mWidget(aWindow),
+ mClientSize("GtkCompositorWidget::mClientSize") {
+#if defined(MOZ_WAYLAND)
+ if (!aInitData.IsX11Display()) {
+ if (!aWindow) {
+ NS_WARNING("GtkCompositorWidget: We're missing nsWindow!");
+ }
+ mProvider.Initialize(aWindow);
+ }
+#endif
+#if defined(MOZ_X11)
+ if (aInitData.IsX11Display()) {
+ // If we have a nsWindow, then grab the already existing display connection
+ // If we don't, then use the init data to connect to the display
+ if (aWindow) {
+ mXDisplay = aWindow->XDisplay();
+ } else {
+ mXDisplay = XOpenDisplay(aInitData.XDisplayString().get());
+ }
+ mXWindow = (Window)aInitData.XWindow();
+
+ // Grab the window's visual and depth
+ XWindowAttributes windowAttrs;
+ if (!XGetWindowAttributes(mXDisplay, mXWindow, &windowAttrs)) {
+ NS_WARNING("GtkCompositorWidget(): XGetWindowAttributes() failed!");
+ }
+
+ Visual* visual = windowAttrs.visual;
+ mDepth = windowAttrs.depth;
+
+ // Initialize the window surface provider
+ mProvider.Initialize(mXDisplay, mXWindow, visual, mDepth,
+ aInitData.Shaped());
+ }
+#endif
+ auto size = mClientSize.Lock();
+ *size = aInitData.InitialClientSize();
+}
+
+GtkCompositorWidget::~GtkCompositorWidget() {
+ mProvider.CleanupResources();
+
+#if defined(MOZ_X11)
+ // If we created our own display connection, we need to destroy it
+ if (!mWidget && mXDisplay) {
+ XCloseDisplay(mXDisplay);
+ mXDisplay = nullptr;
+ }
+#endif
+}
+
+already_AddRefed<gfx::DrawTarget> GtkCompositorWidget::StartRemoteDrawing() {
+ return nullptr;
+}
+void GtkCompositorWidget::EndRemoteDrawing() {}
+
+already_AddRefed<gfx::DrawTarget>
+GtkCompositorWidget::StartRemoteDrawingInRegion(
+ 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) {
+ auto size = mClientSize.Lock();
+ *size = aClientSize;
+}
+
+LayoutDeviceIntSize GtkCompositorWidget::GetClientSize() {
+ auto size = mClientSize.Lock();
+ return *size;
+}
+
+uintptr_t GtkCompositorWidget::GetWidgetKey() {
+ return reinterpret_cast<uintptr_t>(mWidget);
+}
+
+EGLNativeWindowType GtkCompositorWidget::GetEGLNativeWindow() {
+ if (mWidget) {
+ return (EGLNativeWindowType)mWidget->GetNativeData(NS_NATIVE_EGL_WINDOW);
+ }
+#if defined(MOZ_X11)
+ if (mXWindow) {
+ return (EGLNativeWindowType)mXWindow;
+ }
+#endif
+ return nullptr;
+}
+
+int32_t GtkCompositorWidget::GetDepth() { return mDepth; }
+
+#if defined(MOZ_WAYLAND)
+void GtkCompositorWidget::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (mWidget) {
+ mWidget->SetEGLNativeWindowSize(aEGLWindowSize);
+ }
+}
+#endif
+
+void GtkCompositorWidget::ClearBeforePaint(
+ RefPtr<gfx::DrawTarget> aTarget, const LayoutDeviceIntRegion& aRegion) {
+ // We need to clear target buffer alpha values of popup windows as
+ // SW-WR paints with alpha blending (see Bug 1674473).
+ if (mWidget->IsPopup()) {
+ for (auto iter = aRegion.RectIter(); !iter.Done(); iter.Next()) {
+ aTarget->ClearRect(gfx::Rect(iter.Get().ToUnknownRect()));
+ }
+ }
+
+ // Clear background of titlebar area to render titlebar
+ // transparent corners correctly.
+ gfx::Rect rect;
+ if (mWidget->GetTitlebarRect(rect)) {
+ aTarget->ClearRect(rect);
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/GtkCompositorWidget.h b/widget/gtk/GtkCompositorWidget.h
new file mode 100644
index 0000000000..f0cb15fbfc
--- /dev/null
+++ b/widget/gtk/GtkCompositorWidget.h
@@ -0,0 +1,103 @@
+/* -*- 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 widget {
+
+class PlatformCompositorWidgetDelegate : public CompositorWidgetDelegate {
+ public:
+ virtual void NotifyClientSizeChanged(
+ const LayoutDeviceIntSize& aClientSize) = 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,
+ nsWindow* aWindow /* = nullptr*/);
+ ~GtkCompositorWidget();
+
+ // CompositorWidget Overrides
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawing() override;
+ void EndRemoteDrawing() override;
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ layers::BufferMode* aBufferMode) override;
+ void EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+ uintptr_t GetWidgetKey() override;
+
+ LayoutDeviceIntSize GetClientSize() override;
+
+ nsIWidget* RealWidget() override;
+ GtkCompositorWidget* AsX11() override { return this; }
+ CompositorWidgetDelegate* AsDelegate() override { return this; }
+
+ EGLNativeWindowType GetEGLNativeWindow();
+ int32_t GetDepth();
+
+ void ClearBeforePaint(RefPtr<gfx::DrawTarget> aTarget,
+ const LayoutDeviceIntRegion& aRegion) override;
+
+#if defined(MOZ_X11)
+ Display* XDisplay() const { return mXDisplay; }
+ Window XWindow() const { return mXWindow; }
+#endif
+#if defined(MOZ_WAYLAND)
+ void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+#endif
+
+ // PlatformCompositorWidgetDelegate Overrides
+
+ void NotifyClientSizeChanged(const LayoutDeviceIntSize& aClientSize) override;
+
+ protected:
+ 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)
+ Display* mXDisplay = {};
+ Window mXWindow = {};
+#endif
+ int32_t mDepth = {};
+};
+
+} // 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..8600efb4c3
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.cpp
@@ -0,0 +1,3169 @@
+/* -*- 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 "prtime.h"
+
+#include "IMContextWrapper.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/Telemetry.h"
+#include "mozilla/TextEventDispatcher.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/ToString.h"
+#include "WritingModes.h"
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gGtkIMLog("nsGtkIMModuleWidgets");
+
+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 GetWritingModeName : public nsAutoCString {
+ public:
+ explicit GetWritingModeName(const WritingMode& aWritingMode) {
+ if (!aWritingMode.IsVertical()) {
+ AssignLiteral("Horizontal");
+ return;
+ }
+ if (aWritingMode.IsVerticalLR()) {
+ AssignLiteral("Vertical (LTR)");
+ return;
+ }
+ AssignLiteral("Vertical (RTL)");
+ }
+ virtual ~GetWritingModeName() = default;
+};
+
+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.
+ ******************************************************************************/
+
+class SelectionStyleProvider final {
+ public:
+ 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;
+ }
+
+ // aGDKWindow is a GTK window which will be associated with an IM context.
+ void AttachTo(GdkWindow* aGDKWindow) {
+ GtkWidget* widget = nullptr;
+ // gdk_window_get_user_data() typically returns pointer to widget that
+ // window belongs to. If it's widget, fcitx retrieves selection colors
+ // of them. So, we need to overwrite its style.
+ gdk_window_get_user_data(aGDKWindow, (gpointer*)&widget);
+ if (GTK_IS_WIDGET(widget)) {
+ gtk_style_context_add_provider(gtk_widget_get_style_context(widget),
+ 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.
+ nscolor selectionForegroundColor;
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectForeground,
+ &selectionForegroundColor))) {
+ 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(");");
+ }
+ nscolor selectionBackgroundColor;
+ if (NS_SUCCEEDED(
+ LookAndFeel::GetColor(LookAndFeel::ColorID::TextSelectBackground,
+ &selectionBackgroundColor))) {
+ 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),
+ mIsIMFocused(false),
+ mFallbackToKeyEvent(false),
+ mKeyboardEventWasDispatched(false),
+ mKeyboardEventWasConsumed(false),
+ mIsDeletingSurrounding(false),
+ mLayoutChanged(false),
+ mSetCursorPositionOnKeyEvent(true),
+ mPendingResettingIMContext(false),
+ mRetrieveSurroundingSignalReceived(false),
+ mMaybeInDeadKeySequence(false),
+ mIsIMInAsyncKeyHandlingMode(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("@", false, 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() {
+ MozContainer* container = mOwnerWindow->GetMozContainer();
+ MOZ_ASSERT(container, "container is null");
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(container));
+
+ // 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(gdkWindow);
+
+ // 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();
+ gtk_im_context_set_client_window(mContext, gdkWindow);
+ 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();
+ gtk_im_context_set_client_window(mSimpleContext, gdkWindow);
+ 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();
+ gtk_im_context_set_client_window(mDummyContext, gdkWindow);
+
+ MOZ_LOG(gGtkIMLog, 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() {
+ if (this == sLastFocusedContext) {
+ sLastFocusedContext = nullptr;
+ }
+ MOZ_LOG(gGtkIMLog, LogLevel::Info, ("0x%p ~IMContextWrapper()", this));
+}
+
+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 EndIMEComposition(window);
+ }
+ 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(
+ gGtkIMLog, 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) {
+ EndIMEComposition(aWindow);
+ if (mIsIMFocused) {
+ Blur();
+ }
+ mLastFocusedWindow = nullptr;
+ }
+
+ if (mOwnerWindow != aWindow) {
+ return;
+ }
+
+ if (sLastFocusedContext == this) {
+ sLastFocusedContext = nullptr;
+ }
+
+ /**
+ * NOTE:
+ * The given window is the owner of this, so, we must release 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_object_unref(mContext);
+ mContext = nullptr;
+ }
+
+ if (mSimpleContext) {
+ gtk_im_context_set_client_window(mSimpleContext, nullptr);
+ 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(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p PrepareToDestroyContext(), added to reference to "
+ "GtkIMContextIIIM class to prevent it from being unloaded",
+ this));
+ } else {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusWindow(aWindow=0x%p), mLastFocusedWindow=0x%p", this,
+ aWindow, mLastFocusedWindow));
+ mLastFocusedWindow = aWindow;
+ Focus();
+}
+
+void IMContextWrapper::OnBlurWindow(nsWindow* aWindow) {
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnBlurWindow(aWindow=0x%p), mLastFocusedWindow=0x%p, "
+ "mIsIMFocused=%s",
+ this, aWindow, mLastFocusedWindow, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused || mLastFocusedWindow != aWindow) {
+ return;
+ }
+
+ Blur();
+}
+
+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(gGtkIMLog, LogLevel::Info, (">>>>>>>>>>>>>>>>"));
+ MOZ_LOG(
+ gGtkIMLog, 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(
+ gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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;
+ }
+
+ MOZ_LOG(
+ gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p OnFocusChangeInGecko(aFocus=%s), "
+ "mCompositionState=%s, mIsIMFocused=%s",
+ this, ToChar(aFocus), GetCompositionStateName(), ToChar(mIsIMFocused)));
+
+ // We shouldn't carry over the removed string to another editor.
+ mSelectedStringRemovedByComposition.Truncate();
+ mSelection.Clear();
+
+ // 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 && EnsureToCacheSelection()) {
+ SetCursorPosition(GetActiveContext());
+ }
+}
+
+void IMContextWrapper::ResetIME() {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p ResetIME(), mCompositionState=%s, mIsIMFocused=%s", this,
+ GetCompositionStateName(), ToChar(mIsIMFocused)));
+
+ GtkIMContext* activeContext = GetActiveContext();
+ if (MOZ_UNLIKELY(!activeContext)) {
+ MOZ_LOG(gGtkIMLog, 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(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p ResetIME() called gtk_im_context_reset(), "
+ "activeContext=0x%p, mCompositionState=%s, compositionString=%s, "
+ "mIsIMFocused=%s",
+ this, activeContext, GetCompositionStateName(),
+ NS_ConvertUTF16toUTF8(compositionString).get(), ToChar(mIsIMFocused)));
+
+ // 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p EndIMEComposition(aCaller=0x%p), "
+ "mCompositionState=%s",
+ this, aCaller, GetCompositionStateName()));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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
+ mSelection.Clear();
+ EnsureToCacheSelection();
+ 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(gGtkIMLog, 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(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "the caller isn't focused window, mLastFocusedWindow=0x%p",
+ this, mLastFocusedWindow));
+ return;
+ }
+
+ if (!mContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetInputContext(), FAILED, "
+ "there are no context",
+ this));
+ return;
+ }
+
+ if (sLastFocusedContext != this) {
+ mInputContext = *aContext;
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p SetInputContext(), succeeded, "
+ "but we're not active",
+ this));
+ return;
+ }
+
+ bool changingEnabledState =
+ aContext->mIMEState.mEnabled != mInputContext.mIMEState.mEnabled ||
+ aContext->mHTMLInputType != mInputContext.mHTMLInputType;
+
+ // Release current IME focus if IME is enabled.
+ if (changingEnabledState && mInputContext.mIMEState.IsEditable()) {
+ EndIMEComposition(mLastFocusedWindow);
+ Blur();
+ }
+
+ mInputContext = *aContext;
+
+ if (changingEnabledState) {
+ if (mInputContext.mIMEState.IsEditable()) {
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (currentContext) {
+ 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.mHTMLInputInputmode.EqualsLiteral("decimal")) {
+ purpose = GTK_INPUT_PURPOSE_NUMBER;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("email")) {
+ purpose = GTK_INPUT_PURPOSE_EMAIL;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("numeric")) {
+ purpose = GTK_INPUT_PURPOSE_DIGITS;
+ } else if (mInputContext.mHTMLInputInputmode.EqualsLiteral("tel")) {
+ purpose = GTK_INPUT_PURPOSE_PHONE;
+ } else if (mInputContext.mHTMLInputInputmode.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.mHTMLInputInputmode.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);
+ }
+ }
+
+ // Even when aState is not enabled state, we need to set IME focus.
+ // Because some IMs are updating the status bar of them at this time.
+ // Be aware, don't use aWindow here because this method shouldn't move
+ // focus actually.
+ Focus();
+
+ // XXX Should we call Blur() when it's not editable? E.g., it might be
+ // better to close VKB automatically.
+ }
+}
+
+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::Focus() {
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p Focus(), sLastFocusedContext=0x%p", this, sLastFocusedContext));
+
+ if (mIsIMFocused) {
+ NS_ASSERTION(sLastFocusedContext == this,
+ "We're not active, but the IM was focused?");
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Focus(), FAILED, there are no context", this));
+ return;
+ }
+
+ if (sLastFocusedContext && sLastFocusedContext != this) {
+ sLastFocusedContext->Blur();
+ }
+
+ 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);
+ mIsIMFocused = true;
+ 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();
+ }
+}
+
+void IMContextWrapper::Blur() {
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p Blur(), mIsIMFocused=%s", this, ToChar(mIsIMFocused)));
+
+ if (!mIsIMFocused) {
+ return;
+ }
+
+ GtkIMContext* currentContext = GetCurrentContext();
+ if (!currentContext) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p Blur(), FAILED, there are no context", this));
+ return;
+ }
+
+ gtk_im_context_focus_out(currentContext);
+ mIsIMFocused = false;
+}
+
+void IMContextWrapper::OnSelectionChange(
+ nsWindow* aCaller, const IMENotification& aIMENotification) {
+ mSelection.Assign(aIMENotification);
+ bool retrievedSurroundingSignalReceived = mRetrieveSurroundingSignalReceived;
+ mRetrieveSurroundingSignalReceived = false;
+
+ if (MOZ_UNLIKELY(IsDestroyed())) {
+ return;
+ }
+
+ const IMENotification::SelectionChangeDataBase& selectionChangeData =
+ aIMENotification.mSelectionChangeData;
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnSelectionChange(aCaller=0x%p, aIMENotification={ "
+ "mSelectionChangeData=%s }), "
+ "mCompositionState=%s, mIsDeletingSurrounding=%s, "
+ "mRetrieveSurroundingSignalReceived=%s",
+ this, aCaller, ToString(selectionChangeData).c_str(),
+ GetCompositionStateName(), ToChar(mIsDeletingSurrounding),
+ ToChar(retrievedSurroundingSignalReceived)));
+
+ if (aCaller != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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(!mSelection.IsValid())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p OnSelectionChange(), FAILED, "
+ "new offset is too large, cannot keep composing",
+ this));
+ } else {
+ // Modify the selection start offset with new offset.
+ mCompositionStart = mSelection.mOffset;
+ // XXX We should modify mSelectedStringRemovedByComposition?
+ // But how?
+ MOZ_LOG(gGtkIMLog, 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;
+ }
+ // 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.
+ if (!selectionChangeData.mCausedByComposition &&
+ !selectionChangeData.mCausedBySelectionEvent &&
+ !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 (!SelectionStyleProvider::GetInstance()) {
+ return;
+ }
+ SelectionStyleProvider::GetInstance()->OnThemeChanged();
+}
+
+/* static */
+void IMContextWrapper::OnStartCompositionCallback(GtkIMContext* aContext,
+ IMContextWrapper* aModule) {
+ aModule->OnStartCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnStartCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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) {
+ aModule->OnChangeCompositionNative(aContext);
+}
+
+void IMContextWrapper::OnChangeCompositionNative(GtkIMContext* aContext) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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);
+
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p OnCommitCompositionNative(), "
+ "dispatched eKeyDown event for the committed character",
+ this));
+
+ // Next, dispatch eKeyPress event.
+ dispatcher->MaybeDispatchKeypressEvents(keyDownEvent, status,
+ mProcessingKeyEvent);
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), keydown or keyup "
+ "event is dispatched",
+ this));
+
+ if (!mProcessingKeyEvent) {
+ MOZ_LOG(gGtkIMLog, 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:
+ 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME("
+ "aFollowingEvent=%s), dispatch fake eKeyDown event",
+ this, ToChar(aFollowingEvent)));
+
+ KeymapWrapper::DispatchKeyDownOrKeyUpEvent(
+ lastFocusedWindow, fakeKeyDownEvent, &mKeyboardEventWasConsumed);
+ MOZ_LOG(gGtkIMLog, LogLevel::Info,
+ ("0x%p MaybeDispatchKeyEventAsProcessedByIME(), "
+ "fake keydown event is dispatched",
+ this));
+ }
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionStart(aContext=0x%p)", this, aContext));
+
+ if (IsComposing()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "we're already in composition",
+ this));
+ return true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionStart(), FAILED, "
+ "cannot query the selection offset",
+ 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 = mSelection.mOffset;
+ 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionChangeEvent(aContext=0x%p)", this, aContext));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "there are no focused window in this module",
+ this));
+ return false;
+ }
+
+ if (!IsComposing()) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(
+ !EnsureToCacheSelection(&mSelectedStringRemovedByComposition))) {
+ // XXX How should we behave in this case??
+ } else {
+ // XXX We should assume, for now, any web applications don't change
+ // selection at handling this compositionchange event.
+ mCompositionStart = mSelection.mOffset;
+ }
+ }
+
+ RefPtr<TextRangeArray> rangeArray =
+ CreateTextRangeArray(aContext, aCompositionString);
+
+ rv = dispatcher->SetPendingComposition(aCompositionString, rangeArray);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to FlushPendingComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p DispatchCompositionCommitEvent(aContext=0x%p, "
+ "aCommitString=0x%p, (\"%s\"))",
+ this, aContext, aCommitString,
+ aCommitString ? NS_ConvertUTF16toUTF8(*aCommitString).get() : ""));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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()?)
+ if (!IsComposing()) {
+ if (!aCommitString || aCommitString->IsEmpty()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "there is no composition and empty commit string",
+ this));
+ return true;
+ }
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Warning,
+ ("0x%p DispatchCompositionCommitEvent(), Warning, "
+ "MaybeDispatchKeyEventAsProcessedByIME() returned false",
+ this));
+ mCompositionState = eCompositionState_NotComposing;
+ return false;
+ }
+
+ RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher();
+ nsresult rv = dispatcher->BeginNativeInputTransaction();
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionCommitEvent(), FAILED, "
+ "due to BeginNativeInputTransaction() failure",
+ this));
+ return false;
+ }
+
+ RefPtr<nsWindow> lastFocusedWindow(mLastFocusedWindow);
+
+ // Emulate selection until receiving actual selection range.
+ mSelection.CollapseTo(
+ mCompositionStart + (aCommitString
+ ? aCommitString->Length()
+ : mDispatchedCompositionString.Length()),
+ mSelection.mWritingMode);
+
+ 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();
+
+ nsEventStatus status;
+ rv = dispatcher->CommitComposition(status, aCommitString);
+ if (NS_WARN_IF(NS_FAILED(rv))) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DispatchCompositionChangeEvent(), FAILED, "
+ "due to CommitComposition() failure",
+ this));
+ return false;
+ }
+
+ if (lastFocusedWindow->IsDestroyed() ||
+ lastFocusedWindow != mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p CreateTextRangeArray(aContext=0x%p, "
+ "aCompositionString=\"%s\" (Length()=%u))",
+ 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, LogLevel::Info,
+ ("0x%p SetCursorPosition(aContext=0x%p), "
+ "mCompositionTargetRange={ mOffset=%u, mLength=%u }"
+ "mSelection={ mOffset=%u, Length()=%u, mWritingMode=%s }",
+ this, aContext, mCompositionTargetRange.mOffset,
+ mCompositionTargetRange.mLength, mSelection.mOffset, mSelection.Length(),
+ GetWritingModeName(mSelection.mWritingMode).get()));
+
+ bool useCaret = false;
+ if (!mCompositionTargetRange.IsValid()) {
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, "
+ "mCompositionTargetRange and mSelection are invalid",
+ this));
+ return;
+ }
+ useCaret = true;
+ }
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no focused "
+ "window",
+ this));
+ return;
+ }
+
+ if (MOZ_UNLIKELY(!aContext)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p SetCursorPosition(), FAILED, due to no context", this));
+ return;
+ }
+
+ WidgetQueryContentEvent queryCaretOrTextRectEvent(
+ true, useCaret ? eQueryCaretRect : eQueryTextRect, mLastFocusedWindow);
+ if (useCaret) {
+ queryCaretOrTextRectEvent.InitForQueryCaretRect(mSelection.mOffset);
+ } else {
+ if (mSelection.mWritingMode.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);
+ }
+ }
+ InitEvent(queryCaretOrTextRectEvent);
+ nsEventStatus status;
+ mLastFocusedWindow->DispatchEvent(&queryCaretOrTextRectEvent, status);
+ if (queryCaretOrTextRectEvent.Failed()) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p GetCurrentParagraph(), mCompositionState=%s", this,
+ GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, 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(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p GetCurrentParagraph(), FAILED, due to no "
+ "valid selection information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+
+ selOffset = mSelection.mOffset;
+ selLength = mSelection.Length();
+ }
+
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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 =
+ (selOffset == 0) ? 0
+ : textContent.RFind("\n", false, selOffset - 1, -1) + 1;
+ int32_t parEnd = textContent.Find("\n", false, selOffset + selLength, -1);
+ if (parEnd < 0) {
+ parEnd = textContent.Length();
+ }
+ aText = nsDependentSubstring(textContent, parStart, parEnd - parStart);
+ aCursorPos = selOffset - uint32_t(parStart);
+
+ MOZ_LOG(
+ gGtkIMLog, LogLevel::Debug,
+ ("0x%p GetCurrentParagraph(), succeeded, aText=%s, "
+ "aText.Length()=%u, 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(gGtkIMLog, LogLevel::Info,
+ ("0x%p DeleteText(aContext=0x%p, aOffset=%d, aNChars=%u), "
+ "mCompositionState=%s",
+ this, aContext, aOffset, aNChars, GetCompositionStateName()));
+
+ if (!mLastFocusedWindow) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, there are no focused window "
+ "in this module",
+ this));
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ if (!aNChars) {
+ MOZ_LOG(gGtkIMLog, 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(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, quitting from DeletText", this));
+ return NS_ERROR_FAILURE;
+ }
+ } else {
+ if (NS_WARN_IF(!EnsureToCacheSelection())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, due to no valid selection "
+ "information",
+ this));
+ return NS_ERROR_FAILURE;
+ }
+ selOffset = mSelection.mOffset;
+ }
+
+ // 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(gGtkIMLog, 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(
+ gGtkIMLog, 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(
+ gGtkIMLog, LogLevel::Error,
+ ("0x%p DeleteText(), FAILED, restoring composition string", this));
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+void IMContextWrapper::InitEvent(WidgetGUIEvent& aEvent) {
+ aEvent.mTime = PR_Now() / 1000;
+}
+
+bool IMContextWrapper::EnsureToCacheSelection(nsAString* aSelectedString) {
+ if (aSelectedString) {
+ aSelectedString->Truncate();
+ }
+
+ if (mSelection.IsValid()) {
+ if (aSelectedString) {
+ *aSelectedString = mSelection.mString;
+ }
+ return true;
+ }
+
+ if (NS_WARN_IF(!mLastFocusedWindow)) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "no focused window",
+ this));
+ return false;
+ }
+
+ nsEventStatus status;
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ mLastFocusedWindow);
+ InitEvent(querySelectedTextEvent);
+ mLastFocusedWindow->DispatchEvent(&querySelectedTextEvent, status);
+ if (NS_WARN_IF(querySelectedTextEvent.Failed())) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event",
+ this));
+ return false;
+ }
+
+ mSelection.Assign(querySelectedTextEvent);
+ if (!mSelection.IsValid()) {
+ MOZ_LOG(gGtkIMLog, LogLevel::Error,
+ ("0x%p EnsureToCacheSelection(), FAILED, due to "
+ "failure of query selection event (invalid result)",
+ this));
+ return false;
+ }
+
+ if (!mSelection.Collapsed() && aSelectedString) {
+ aSelectedString->Assign(querySelectedTextEvent.mReply->DataRef());
+ }
+
+ MOZ_LOG(gGtkIMLog, LogLevel::Debug,
+ ("0x%p EnsureToCacheSelection(), Succeeded, mSelection="
+ "{ mOffset=%u, Length()=%u, mWritingMode=%s }",
+ this, mSelection.mOffset, mSelection.Length(),
+ GetWritingModeName(mSelection.mWritingMode).get()));
+ return true;
+}
+
+/******************************************************************************
+ * IMContextWrapper::Selection
+ ******************************************************************************/
+
+void IMContextWrapper::Selection::Assign(
+ const IMENotification& aIMENotification) {
+ MOZ_ASSERT(aIMENotification.mMessage == NOTIFY_IME_OF_SELECTION_CHANGE);
+ mString = aIMENotification.mSelectionChangeData.String();
+ mOffset = aIMENotification.mSelectionChangeData.mOffset;
+ mWritingMode = aIMENotification.mSelectionChangeData.GetWritingMode();
+}
+
+void IMContextWrapper::Selection::Assign(
+ const WidgetQueryContentEvent& aEvent) {
+ MOZ_ASSERT(aEvent.mMessage == eQuerySelectedText);
+ MOZ_ASSERT(aEvent.Succeeded());
+ MOZ_ASSERT(aEvent.mReply->mOffsetAndData.isSome());
+ mString = aEvent.mReply->DataRef();
+ mOffset = aEvent.mReply->StartOffset();
+ mWritingMode = aEvent.mReply->WritingModeRef();
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/IMContextWrapper.h b/widget/gtk/IMContextWrapper.h
new file mode 100644
index 0000000000..9ebb22fc3d
--- /dev/null
+++ b/widget/gtk/IMContextWrapper.h
@@ -0,0 +1,685 @@
+/* -*- 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 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/EventForwards.h"
+#include "mozilla/TextEventDispatcherListener.h"
+#include "WritingModes.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();
+
+ 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();
+
+ // 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() {
+ if (!mEvents.IsEmpty()) {
+ RemoveEventsAt(0, mEvents.Length());
+ }
+ }
+
+ /**
+ * 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;
+ }
+ RemoveEventsAt(0, index + 1);
+ }
+
+ /**
+ * FirstEvent() returns oldest event in the queue.
+ */
+ GdkEventKey* GetFirstEvent() const {
+ if (mEvents.IsEmpty()) {
+ return nullptr;
+ }
+ return mEvents[0];
+ }
+
+ 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];
+ // 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<GdkEventKey*> mEvents;
+
+ void RemoveEventsAt(size_t aStart, size_t aCount) {
+ for (size_t i = aStart; i < aStart + aCount; i++) {
+ gdk_event_free(reinterpret_cast<GdkEvent*>(mEvents[i]));
+ }
+ mEvents.RemoveElementsAt(aStart, aCount);
+ }
+ };
+ // 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;
+
+ struct Selection final {
+ nsString mString;
+ uint32_t mOffset;
+ WritingMode mWritingMode;
+
+ Selection() : mOffset(UINT32_MAX) {}
+
+ void Clear() {
+ mString.Truncate();
+ mOffset = UINT32_MAX;
+ mWritingMode = WritingMode();
+ }
+ void CollapseTo(uint32_t aOffset, const WritingMode& aWritingMode) {
+ mWritingMode = aWritingMode;
+ mOffset = aOffset;
+ mString.Truncate();
+ }
+
+ void Assign(const IMENotification& aIMENotification);
+ void Assign(const WidgetQueryContentEvent& aSelectedTextEvent);
+
+ bool IsValid() const { return mOffset != UINT32_MAX; }
+ bool Collapsed() const { return mString.IsEmpty(); }
+ uint32_t Length() const { return mString.Length(); }
+ uint32_t EndOffset() const {
+ if (NS_WARN_IF(!IsValid())) {
+ return UINT32_MAX;
+ }
+ CheckedInt<uint32_t> endOffset =
+ CheckedInt<uint32_t>(mOffset) + mString.Length();
+ if (NS_WARN_IF(!endOffset.isValid())) {
+ return UINT32_MAX;
+ }
+ return endOffset.value();
+ }
+ } mSelection;
+ bool EnsureToCacheSelection(nsAString* aSelectedString = nullptr);
+
+ // mIsIMFocused is set to TRUE when we call gtk_im_context_focus_in(). And
+ // it's set to FALSE when we call gtk_im_context_focus_out().
+ bool mIsIMFocused;
+ // 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;
+
+ // 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; }
+
+ // Sets focus to the instance of this class.
+ void Focus();
+
+ // Steals focus from the instance of this class.
+ void Blur();
+
+ // 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);
+
+ // Initializes the GUI event.
+ void InitEvent(WidgetGUIEvent& aEvent);
+
+ // 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..41581184fb
--- /dev/null
+++ b/widget/gtk/InProcessGtkCompositorWidget.cpp
@@ -0,0 +1,45 @@
+/* -*- 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 "nsWindow.h"
+
+namespace mozilla {
+namespace 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 widget
+} // namespace mozilla
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..c21b705b16
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.cpp
@@ -0,0 +1,851 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Sprintf.h"
+#include "nsIXULAppInfo.h"
+#include "nsIOutputStream.h"
+#include "nsNetUtil.h"
+#include "nsServiceManagerUtils.h"
+
+// avoid redefined macro in unified build
+#undef LOG
+#define LOG(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<mozilla::dom::MediaControlKey> GetMediaControlKey(
+ const gchar* aMethodName) {
+ const std::unordered_map<std::string, mozilla::dom::MediaControlKey> map = {
+ {"Raise", mozilla::dom::MediaControlKey::Focus},
+ {"Next", mozilla::dom::MediaControlKey::Nexttrack},
+ {"Previous", mozilla::dom::MediaControlKey::Previoustrack},
+ {"Pause", mozilla::dom::MediaControlKey::Pause},
+ {"PlayPause", mozilla::dom::MediaControlKey::Playpause},
+ {"Stop", mozilla::dom::MediaControlKey::Stop},
+ {"Play", mozilla::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<mozilla::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<mozilla::dom::MediaControlKey> GetPairedKey(
+ Property aProperty) {
+ switch (aProperty) {
+ case Property::eCanRaise:
+ return Some(mozilla::dom::MediaControlKey::Focus);
+ case Property::eCanGoNext:
+ return Some(mozilla::dom::MediaControlKey::Nexttrack);
+ case Property::eCanGoPrevious:
+ return Some(mozilla::dom::MediaControlKey::Previoustrack);
+ case Property::eCanPlay:
+ return Some(mozilla::dom::MediaControlKey::Play);
+ case Property::eCanPause:
+ return Some(mozilla::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<mozilla::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) {
+ LOG("OnNameAcquired: %s", aName);
+ mConnection = aConnection;
+}
+
+void MPRISServiceHandler::OnNameLost(GDBusConnection* aConnection,
+ const gchar* aName) {
+ LOG("OnNameLost: %s", aName);
+ mConnection = nullptr;
+ if (!mRootRegistrationId) {
+ 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.
+ LOG("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.
+ LOG("Unable to unregister object from within onNameLost!");
+ }
+}
+
+void MPRISServiceHandler::OnBusAcquired(GDBusConnection* aConnection,
+ const gchar* aName) {
+ GError* error = nullptr;
+ LOG("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 */
+ &error); /* GError** */
+
+ if (mRootRegistrationId == 0) {
+ LOG("Failed at root registration: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(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 */
+ &error); /* GError** */
+
+ if (mPlayerRegistrationId == 0) {
+ LOG("Failed at object registration: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ }
+}
+
+bool MPRISServiceHandler::Open() {
+ MOZ_ASSERT(!mInitialized);
+ MOZ_ASSERT(NS_IsMainThread());
+ GError* error = nullptr;
+ gchar serviceName[256];
+
+ InitIdentity();
+ SprintfLiteral(serviceName, DBUS_MPRIS_SERVICE_NAME ".instance%d", getpid());
+ mOwnerId =
+ g_bus_own_name(G_BUS_TYPE_SESSION, serviceName,
+ // 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, OnBusAcquiredStatic,
+ OnNameAcquiredStatic, OnNameLostStatic, this, nullptr);
+
+ /* parse introspection data */
+ mIntrospectionData = g_dbus_node_info_new_for_xml(introspection_xml, &error);
+
+ if (!mIntrospectionData) {
+ LOG("Failed at parsing XML Interface definition: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return false;
+ }
+
+ mInitialized = true;
+ return true;
+}
+
+MPRISServiceHandler::~MPRISServiceHandler() {
+ MOZ_ASSERT(!mInitialized); // Close hasn't been called!
+}
+
+void MPRISServiceHandler::Close() {
+ gchar serviceName[256];
+ SprintfLiteral(serviceName, DBUS_MPRIS_SERVICE_NAME ".instance%d", getpid());
+
+ // Reset playback state and metadata before disconnect from dbus.
+ SetPlaybackState(dom::MediaSessionPlaybackState::None);
+ ClearMetadata();
+
+ OnNameLost(mConnection, serviceName);
+
+ if (mOwnerId != 0) {
+ g_bus_unown_name(mOwnerId);
+ }
+ if (mIntrospectionData) {
+ g_dbus_node_info_unref(mIntrospectionData);
+ }
+
+ 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 {
+ MOZ_ASSERT(mInitialized);
+ return mIdentity.get();
+}
+
+const char* MPRISServiceHandler::DesktopEntry() const {
+ MOZ_ASSERT(mInitialized);
+ return mDesktopEntry.get();
+}
+
+bool MPRISServiceHandler::PressKey(mozilla::dom::MediaControlKey aKey) const {
+ MOZ_ASSERT(mInitialized);
+ if (!IsMediaKeySupported(aKey)) {
+ LOG("%s is not supported", ToMediaControlKeyStr(aKey));
+ return false;
+ }
+ LOG("Press %s", ToMediaControlKeyStr(aKey));
+ EmitEvent(aKey);
+ return true;
+}
+
+void MPRISServiceHandler::SetPlaybackState(
+ dom::MediaSessionPlaybackState aState) {
+ LOG("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);
+
+ LOG("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 (mozilla::dom::IsImageIn(aMetadata.mArtwork, mFetchingUrl)) {
+ LOG("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 (mozilla::dom::IsImageIn(aMetadata.mArtwork, mCurrentImageUrl)) {
+ LOG("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);
+
+ LOG("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()) {
+ LOG("Stop loading image to MPRIS. No available image");
+ mImageFetchRequest.DisconnectIfExists();
+ return;
+ }
+
+ const mozilla::dom::MediaImage& image = mMPRISMetadata.mArtwork[aIndex];
+
+ if (!mozilla::dom::IsValidImageUrl(image.mSrc)) {
+ LOG("Skip the image with invalid URL. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ mImageFetchRequest.DisconnectIfExists();
+ mFetchingUrl = image.mSrc;
+
+ mImageFetcher = mozilla::MakeUnique<mozilla::dom::FetchImageHelper>(image);
+ RefPtr<MPRISServiceHandler> self = this;
+ mImageFetcher->FetchImage()
+ ->Then(
+ AbstractThread::MainThread(), __func__,
+ [this, self](const nsCOMPtr<imgIContainer>& aImage) {
+ LOG("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 = mozilla::dom::GetEncodedImageBuffer(
+ aImage, mMimeType, getter_AddRefs(inputStream), &size, &data);
+ if (NS_FAILED(rv) || !inputStream || size == 0 || !data) {
+ LOG("Failed to get the image buffer info. Try next image");
+ LoadImageAtIndex(mNextImageIndex++);
+ return;
+ }
+
+ if (SetImageToDisplay(data, size)) {
+ mCurrentImageUrl = mFetchingUrl;
+ LOG("The MPRIS image is updated to the image from: %s",
+ NS_ConvertUTF16toUTF8(mCurrentImageUrl).get());
+ } else {
+ LOG("Failed to set image to MPRIS");
+ mCurrentImageUrl.Truncate();
+ }
+
+ mFetchingUrl.Truncate();
+ },
+ [this, self](bool) {
+ LOG("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());
+
+ LOG("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()) {
+ LOG("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) {
+ LOG("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)) {
+ LOG("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)) {
+ LOG("Failed to create an image filename");
+ return false;
+ }
+
+ rv = mLocalImageFile->Create(nsIFile::NORMAL_FILE_TYPE, 0600);
+ if (NS_FAILED(rv)) {
+ LOG("Failed to create an image file");
+ return false;
+ }
+
+ cleanup.release();
+ return true;
+}
+
+bool MPRISServiceHandler::InitLocalImageFolder() {
+ if (mLocalImageFolder && LocalImageFolderExists()) {
+ return true;
+ }
+
+ nsresult rv = NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR,
+ getter_AddRefs(mLocalImageFolder));
+ if (NS_FAILED(rv) || !mLocalImageFolder) {
+ LOG("Failed to get the image folder");
+ return false;
+ }
+
+ auto cleanup =
+ MakeScopeExit([this, self = RefPtr<MPRISServiceHandler>(this)] {
+ mLocalImageFolder = nullptr;
+ });
+
+ rv = mLocalImageFolder->Append(u"firefox-mpris"_ns);
+ if (NS_FAILED(rv)) {
+ LOG("Failed to name an image folder");
+ return false;
+ }
+
+ if (!LocalImageFolderExists()) {
+ rv = mLocalImageFolder->Create(nsIFile::DIRECTORY_TYPE, 0700);
+ if (NS_FAILED(rv)) {
+ LOG("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.
+ LOG("Failed to remove images");
+ }
+
+ LOG("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(mozilla::dom::MediaControlKey aKey) const {
+ for (const auto& listener : mListeners) {
+ listener->OnActionPerformed(mozilla::dom::MediaControlAction(aKey));
+ }
+}
+
+struct InterfaceProperty {
+ const char* interface;
+ const char* property;
+};
+static const std::unordered_map<mozilla::dom::MediaControlKey,
+ InterfaceProperty>
+ gKeyProperty = {{mozilla::dom::MediaControlKey::Focus,
+ {DBUS_MPRIS_INTERFACE, "CanRaise"}},
+ {mozilla::dom::MediaControlKey::Nexttrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoNext"}},
+ {mozilla::dom::MediaControlKey::Previoustrack,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanGoPrevious"}},
+ {mozilla::dom::MediaControlKey::Play,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanPlay"}},
+ {mozilla::dom::MediaControlKey::Pause,
+ {DBUS_MPRIS_PLAYER_INTERFACE, "CanPause"}}};
+
+void MPRISServiceHandler::SetSupportedMediaKeys(
+ const MediaKeysArray& aSupportedKeys) {
+ uint32_t supportedKeys = 0;
+ for (const mozilla::dom::MediaControlKey& key : aSupportedKeys) {
+ supportedKeys |= GetMediaKeyMask(key);
+ }
+
+ if (mSupportedKeys == supportedKeys) {
+ LOG("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) {
+ LOG("Emit PropertiesChanged signal: %s.%s=%s", it.second.interface,
+ it.second.property, keyIsSupported ? "true" : "false");
+ EmitSupportedKeyChanged(it.first, keyIsSupported);
+ }
+ }
+}
+
+bool MPRISServiceHandler::IsMediaKeySupported(
+ mozilla::dom::MediaControlKey aKey) const {
+ return mSupportedKeys & GetMediaKeyMask(aKey);
+}
+
+bool MPRISServiceHandler::EmitSupportedKeyChanged(
+ mozilla::dom::MediaControlKey aKey, bool aSupported) const {
+ auto it = gKeyProperty.find(aKey);
+ if (it == gKeyProperty.end()) {
+ LOG("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);
+
+ LOG("Emit MPRIS property changes for '%s.%s'", it->second.interface,
+ it->second.property);
+ return EmitPropertiesChangedSignal(parameters);
+}
+
+bool MPRISServiceHandler::EmitPropertiesChangedSignal(
+ GVariant* aParameters) const {
+ if (!mConnection) {
+ LOG("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)) {
+ LOG("Failed to emit MPRIS property changes: %s",
+ error ? error->message : "Unknown Error");
+ if (error) {
+ g_error_free(error);
+ }
+ return false;
+ }
+
+ return true;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/MPRISServiceHandler.h b/widget/gtk/MPRISServiceHandler.h
new file mode 100644
index 0000000000..6d6c898088
--- /dev/null
+++ b/widget/gtk/MPRISServiceHandler.h
@@ -0,0 +1,188 @@
+/* -*- 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.
+
+ // 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.
+ MPRISServiceHandler() : mMimeType(IMAGE_PNG){};
+ 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;
+
+ 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;
+ GDBusNodeInfo* mIntrospectionData = nullptr;
+ GDBusConnection* mConnection = nullptr;
+ bool mInitialized = false;
+ nsAutoCString mIdentity;
+ nsAutoCString mDesktopEntry;
+
+ nsCString mMimeType;
+
+ // 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;
+
+ mozilla::UniquePtr<mozilla::dom::FetchImageHelper> mImageFetcher;
+ mozilla::MozPromiseRequestHolder<mozilla::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(mozilla::dom::MediaControlKey aKey,
+ bool aSupported) const;
+
+ bool EmitPropertiesChangedSignal(GVariant* aParameters) const;
+
+ void ClearMetadata();
+};
+
+} // 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..334592eae1
--- /dev/null
+++ b/widget/gtk/MozContainer.cpp
@@ -0,0 +1,376 @@
+/* -*- 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 "MozContainer.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include <stdio.h>
+#ifdef MOZ_WAYLAND
+# include "gfxPlatformGtk.h"
+#endif
+
+#ifdef ACCESSIBILITY
+# include <atk/atk.h>
+# include "maiRedundantObjectFactory.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 */
+
+/* init methods */
+static void moz_container_class_init(MozContainerClass* klass);
+static void moz_container_init(MozContainer* container);
+
+/* widget class methods */
+static void moz_container_map(GtkWidget* widget);
+static void moz_container_unmap(GtkWidget* widget);
+static void moz_container_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation);
+void moz_container_realize(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 */
+ };
+
+#ifdef MOZ_WAYLAND
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_info.class_init =
+ (GClassInitFunc)moz_container_wayland_class_init;
+ }
+#endif
+
+ moz_container_type =
+ g_type_register_static(GTK_TYPE_CONTAINER, "MozContainer",
+ &moz_container_info, static_cast<GTypeFlags>(0));
+#ifdef ACCESSIBILITY
+ /* Set a factory to return accessible object with ROLE_REDUNDANT for
+ * MozContainer, so that gail won't send focus notification for it */
+ atk_registry_set_factory_type(atk_get_default_registry(),
+ moz_container_type,
+ mai_redundant_object_factory_get_type());
+#endif
+ }
+
+ 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->children = g_list_append(container->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));
+}
+
+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->map = moz_container_map;
+ widget_class->unmap = moz_container_unmap;
+ widget_class->realize = moz_container_realize;
+ widget_class->size_allocate = moz_container_size_allocate;
+
+ container_class->remove = moz_container_remove;
+ container_class->forall = moz_container_forall;
+ container_class->add = moz_container_add;
+}
+
+void moz_container_init(MozContainer* container) {
+ gtk_widget_set_can_focus(GTK_WIDGET(container), TRUE);
+ gtk_container_set_resize_mode(GTK_CONTAINER(container), GTK_RESIZE_IMMEDIATE);
+ gtk_widget_set_redraw_on_allocate(GTK_WIDGET(container), FALSE);
+#ifdef MOZ_WAYLAND
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_wayland_init(&container->wl_container);
+ }
+#endif
+ LOG(("%s [%p]\n", __FUNCTION__, (void*)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);
+
+ gtk_widget_set_mapped(widget, TRUE);
+
+ tmp_list = container->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));
+
+ 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);
+
+ if (gtk_widget_get_has_window(widget)) {
+ 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->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);
+
+ LOG(("moz_container_realize() [%p] GdkWindow %p\n", (void*)container,
+ (void*)window));
+
+ gdk_window_set_user_data(window, widget);
+ } else {
+ window = parent;
+ g_object_ref(window);
+ }
+
+ gtk_widget_set_window(widget, window);
+}
+
+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));
+
+ LOG(("moz_container_size_allocate [%p] %d,%d -> %d x %d\n", (void*)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->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->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->children = g_list_remove(moz_container->children, child);
+ g_free(child);
+}
+
+void moz_container_forall(GtkContainer* container, gboolean include_internals,
+ GtkCallback callback, gpointer callback_data) {
+ MozContainer* moz_container;
+ GList* tmp_list;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(container));
+ g_return_if_fail(callback != NULL);
+
+ moz_container = MOZ_CONTAINER(container);
+
+ tmp_list = moz_container->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;
+
+ tmp_list = container->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 NULL;
+}
+
+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->force_default_visual = true;
+}
diff --git a/widget/gtk/MozContainer.h b/widget/gtk/MozContainer.h
new file mode 100644
index 0000000000..9faf909179
--- /dev/null
+++ b/widget/gtk/MozContainer.h
@@ -0,0 +1,90 @@
+/* -*- 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 __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))
+
+// We need to shape only a few pixels of the titlebar as we care about
+// the corners only
+#define TITLEBAR_SHAPE_MASK_HEIGHT 10
+
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+struct _MozContainer {
+ GtkContainer container;
+ GList* children;
+ gboolean force_default_visual;
+#ifdef MOZ_WAYLAND
+ MozContainerWayland wl_container;
+#endif
+};
+
+struct _MozContainerClass {
+ GtkContainerClass parent_class;
+};
+
+GType moz_container_get_type(void);
+GtkWidget* moz_container_new(void);
+void moz_container_put(MozContainer* container, GtkWidget* child_widget, gint x,
+ gint y);
+void moz_container_force_default_visual(MozContainer* container);
+
+#endif /* __MOZ_CONTAINER_H__ */
diff --git a/widget/gtk/MozContainerWayland.cpp b/widget/gtk/MozContainerWayland.cpp
new file mode 100644
index 0000000000..88fbef7277
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.cpp
@@ -0,0 +1,514 @@
+/* -*- 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 "MozContainer.h"
+
+#include <glib.h>
+#include <gtk/gtk.h>
+#include <gdk/gdkx.h>
+#include "nsWaylandDisplay.h"
+#include "gfxPlatformGtk.h"
+#include <wayland-egl.h>
+#include <stdio.h>
+#include <dlfcn.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+# include "nsWindow.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(args) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+/* init methods */
+static void moz_container_wayland_destroy(GtkWidget* widget);
+
+/* widget class methods */
+static void moz_container_wayland_map(GtkWidget* widget);
+static gboolean moz_container_wayland_map_event(GtkWidget* widget,
+ GdkEventAny* event);
+static void moz_container_wayland_unmap(GtkWidget* widget);
+static void moz_container_wayland_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation);
+
+// Imlemented in MozContainer.cpp
+void moz_container_realize(GtkWidget* widget);
+
+static void moz_container_wayland_move_locked(MozContainer* container, int dx,
+ int dy) {
+ LOGWAYLAND(("moz_container_wayland_move_locked [%p] %d,%d\n",
+ (void*)container, dx, dy));
+
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ wl_container->subsurface_dx = dx;
+ wl_container->subsurface_dy = dy;
+ wl_container->surface_position_needs_update = true;
+
+ // Wayland subsurface is not created yet.
+ if (!wl_container->subsurface) {
+ return;
+ }
+
+ // wl_subsurface_set_position is actually property of parent surface
+ // which is effective when parent surface is commited.
+ wl_subsurface_set_position(wl_container->subsurface,
+ wl_container->subsurface_dx,
+ wl_container->subsurface_dy);
+ wl_container->surface_position_needs_update = false;
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ if (window) {
+ GdkRectangle rect = (GdkRectangle){0, 0, gdk_window_get_width(window),
+ gdk_window_get_height(window)};
+ gdk_window_invalidate_rect(window, &rect, false);
+ }
+}
+
+static void moz_container_wayland_move(MozContainer* container, int dx,
+ int dy) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ LOGWAYLAND(
+ ("moz_container_wayland_move [%p] %d,%d\n", (void*)container, dx, dy));
+ moz_container_wayland_move_locked(container, dx, dy);
+}
+
+// This is called from layout/compositor code only with
+// size equal to GL rendering context. Otherwise there are
+// rendering artifacts as wl_egl_window size does not match
+// GL rendering pipeline setup.
+void moz_container_wayland_egl_window_set_size(MozContainer* container,
+ int width, int height) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ MutexAutoLock lock(*wl_container->container_lock);
+ if (wl_container->eglwindow) {
+ wl_egl_window_resize(wl_container->eglwindow, width, height, 0, 0);
+ }
+}
+
+void moz_container_wayland_class_init(MozContainerClass* klass) {
+ /*GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
+ GtkObjectClass *object_class = GTK_OBJECT_CLASS (klass); */
+ GtkWidgetClass* widget_class = GTK_WIDGET_CLASS(klass);
+
+ widget_class->map = moz_container_wayland_map;
+ widget_class->map_event = moz_container_wayland_map_event;
+ widget_class->destroy = moz_container_wayland_destroy;
+ widget_class->unmap = moz_container_wayland_unmap;
+ widget_class->realize = moz_container_realize;
+ widget_class->size_allocate = moz_container_wayland_size_allocate;
+}
+
+void moz_container_wayland_init(MozContainerWayland* container) {
+ container->surface = nullptr;
+ container->subsurface = nullptr;
+ container->eglwindow = nullptr;
+ container->frame_callback_handler = nullptr;
+ container->frame_callback_handler_surface_id = -1;
+ container->ready_to_draw = false;
+ container->opaque_region_needs_update = false;
+ container->opaque_region_subtract_corners = false;
+ container->surface_needs_clear = true;
+ container->subsurface_dx = 0;
+ container->subsurface_dy = 0;
+ container->surface_position_needs_update = 0;
+ container->initial_draw_cbs.clear();
+ container->container_lock = new mozilla::Mutex("MozContainer lock");
+}
+
+static void moz_container_wayland_destroy(GtkWidget* widget) {
+ MozContainerWayland* container = &MOZ_CONTAINER(widget)->wl_container;
+ delete container->container_lock;
+ container->container_lock = nullptr;
+}
+
+void moz_container_wayland_add_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb) {
+ container->wl_container.initial_draw_cbs.push_back(initial_draw_cb);
+}
+
+wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget) {
+ GdkWindow* window = gtk_widget_get_window(aWidget);
+ wl_surface* surface = gdk_wayland_window_get_wl_surface(window);
+
+ LOGWAYLAND(("moz_gtk_widget_get_wl_surface [%p] wl_surface %p ID %d\n",
+ (void*)aWidget, (void*)surface,
+ surface ? wl_proxy_get_id((struct wl_proxy*)surface) : -1));
+
+ return surface;
+}
+
+static void moz_container_wayland_frame_callback_handler(
+ void* data, struct wl_callback* callback, uint32_t time) {
+ MozContainerWayland* wl_container = &MOZ_CONTAINER(data)->wl_container;
+
+ LOGWAYLAND(
+ ("%s [%p] frame_callback_handler %p ready_to_draw %d (set to true)"
+ " initial_draw callback %zd\n",
+ __FUNCTION__, (void*)MOZ_CONTAINER(data),
+ (void*)wl_container->frame_callback_handler, wl_container->ready_to_draw,
+ wl_container->initial_draw_cbs.size()));
+
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ wl_container->frame_callback_handler_surface_id = -1;
+
+ if (!wl_container->ready_to_draw) {
+ wl_container->ready_to_draw = true;
+ for (auto const& cb : wl_container->initial_draw_cbs) {
+ cb();
+ }
+ wl_container->initial_draw_cbs.clear();
+ }
+}
+
+static const struct wl_callback_listener moz_container_frame_listener = {
+ moz_container_wayland_frame_callback_handler};
+
+static void moz_container_wayland_request_parent_frame_callback(
+ MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ wl_surface* gtk_container_surface =
+ moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
+ int gtk_container_surface_id =
+ gtk_container_surface
+ ? wl_proxy_get_id((struct wl_proxy*)gtk_container_surface)
+ : -1;
+
+ LOGWAYLAND(
+ ("%s [%p] frame_callback_handler %p "
+ "frame_callback_handler_surface_id %d\n",
+ __FUNCTION__, (void*)container, wl_container->frame_callback_handler,
+ wl_container->frame_callback_handler_surface_id));
+
+ if (wl_container->frame_callback_handler &&
+ wl_container->frame_callback_handler_surface_id ==
+ gtk_container_surface_id) {
+ return;
+ }
+
+ // If there's pending frame callback, delete it.
+ if (wl_container->frame_callback_handler) {
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ }
+
+ if (gtk_container_surface) {
+ wl_container->frame_callback_handler_surface_id = gtk_container_surface_id;
+ wl_container->frame_callback_handler =
+ wl_surface_frame(gtk_container_surface);
+ wl_callback_add_listener(wl_container->frame_callback_handler,
+ &moz_container_frame_listener, container);
+ } else {
+ wl_container->frame_callback_handler_surface_id = -1;
+ }
+}
+
+static gboolean moz_container_wayland_map_event(GtkWidget* widget,
+ GdkEventAny* event) {
+ MozContainerWayland* wl_container = &MOZ_CONTAINER(widget)->wl_container;
+
+ LOGWAYLAND(("%s begin [%p] ready_to_draw %d\n", __FUNCTION__,
+ (void*)MOZ_CONTAINER(widget), wl_container->ready_to_draw));
+
+ if (wl_container->ready_to_draw) {
+ return FALSE;
+ }
+
+ moz_container_wayland_request_parent_frame_callback(MOZ_CONTAINER(widget));
+ return FALSE;
+}
+
+static void moz_container_wayland_unmap_internal(MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ MutexAutoLock lock(*wl_container->container_lock);
+
+ g_clear_pointer(&wl_container->eglwindow, wl_egl_window_destroy);
+ g_clear_pointer(&wl_container->subsurface, wl_subsurface_destroy);
+ g_clear_pointer(&wl_container->surface, wl_surface_destroy);
+ g_clear_pointer(&wl_container->frame_callback_handler, wl_callback_destroy);
+ wl_container->frame_callback_handler_surface_id = -1;
+
+ wl_container->surface_needs_clear = true;
+ wl_container->ready_to_draw = false;
+
+ LOGWAYLAND(("%s [%p]\n", __FUNCTION__, (void*)container));
+}
+
+void moz_container_wayland_map(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+ gtk_widget_set_mapped(widget, TRUE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_show(gtk_widget_get_window(widget));
+ moz_container_wayland_map_event(widget, nullptr);
+ }
+}
+
+void moz_container_wayland_unmap(GtkWidget* widget) {
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ gtk_widget_set_mapped(widget, FALSE);
+
+ if (gtk_widget_get_has_window(widget)) {
+ gdk_window_hide(gtk_widget_get_window(widget));
+ moz_container_wayland_unmap_internal(MOZ_CONTAINER(widget));
+ }
+}
+
+void moz_container_wayland_size_allocate(GtkWidget* widget,
+ GtkAllocation* allocation) {
+ MozContainer* container;
+ GtkAllocation tmp_allocation;
+
+ g_return_if_fail(IS_MOZ_CONTAINER(widget));
+
+ LOGWAYLAND(("moz_container_wayland_size_allocate [%p] %d,%d -> %d x %d\n",
+ (void*)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->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.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ moz_container_wayland_move(MOZ_CONTAINER(widget), allocation->x,
+ allocation->y);
+ }
+ }
+}
+
+static wl_region* moz_container_wayland_create_opaque_region(
+ int aX, int aY, int aWidth, int aHeight, bool aSubtractCorners) {
+ struct wl_compositor* compositor = WaylandDisplayGet()->GetCompositor();
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_region_add(region, aX, aY, aWidth, aHeight);
+ if (aSubtractCorners) {
+ wl_region_subtract(region, aX, aY, TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT);
+ wl_region_subtract(region, aX + aWidth - TITLEBAR_SHAPE_MASK_HEIGHT, aY,
+ TITLEBAR_SHAPE_MASK_HEIGHT, TITLEBAR_SHAPE_MASK_HEIGHT);
+ }
+ return region;
+}
+
+static void moz_container_wayland_set_opaque_region_locked(
+ MozContainer* container) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ if (!wl_container->opaque_region_needs_update || !wl_container->surface) {
+ 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_subtract_corners);
+ wl_surface_set_opaque_region(wl_container->surface, region);
+ wl_region_destroy(region);
+
+ wl_container->opaque_region_needs_update = false;
+}
+
+static void moz_container_wayland_set_opaque_region(MozContainer* container) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ moz_container_wayland_set_opaque_region_locked(container);
+}
+
+static void moz_container_wayland_set_scale_factor_locked(
+ MozContainer* container) {
+ if (!container->wl_container.surface) {
+ return;
+ }
+ gpointer user_data = g_object_get_data(G_OBJECT(container), "nsWindow");
+ nsWindow* wnd = static_cast<nsWindow*>(user_data);
+
+ int scale = 1;
+ if (wnd) {
+ scale = wnd->GdkScaleFactor();
+ }
+ wl_surface_set_buffer_scale(container->wl_container.surface, scale);
+}
+
+void moz_container_wayland_set_scale_factor(MozContainer* container) {
+ MutexAutoLock lock(*container->wl_container.container_lock);
+ moz_container_wayland_set_scale_factor_locked(container);
+}
+
+static struct wl_surface* moz_container_wayland_get_surface_locked(
+ MozContainer* container, nsWaylandDisplay* aWaylandDisplay) {
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ LOGWAYLAND(("%s [%p] surface %p ready_to_draw %d\n", __FUNCTION__,
+ (void*)container, (void*)wl_container->surface,
+ wl_container->ready_to_draw));
+
+ if (!wl_container->surface) {
+ if (!wl_container->ready_to_draw) {
+ moz_container_wayland_request_parent_frame_callback(container);
+ return nullptr;
+ }
+ wl_surface* parent_surface =
+ moz_gtk_widget_get_wl_surface(GTK_WIDGET(container));
+ if (!parent_surface) {
+ return nullptr;
+ }
+
+ // Available as of GTK 3.8+
+ struct wl_compositor* compositor = aWaylandDisplay->GetCompositor();
+ wl_container->surface = wl_compositor_create_surface(compositor);
+ if (!wl_container->surface) {
+ return nullptr;
+ }
+
+ wl_container->subsurface =
+ wl_subcompositor_get_subsurface(aWaylandDisplay->GetSubcompositor(),
+ wl_container->surface, parent_surface);
+ if (!wl_container->subsurface) {
+ g_clear_pointer(&wl_container->surface, wl_surface_destroy);
+ return nullptr;
+ }
+
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ gint x, y;
+ gdk_window_get_position(window, &x, &y);
+ moz_container_wayland_move_locked(container, x, y);
+ wl_subsurface_set_desync(wl_container->subsurface);
+
+ // Route input to parent wl_surface owned by Gtk+ so we get input
+ // events from Gtk+.
+ wl_region* region = wl_compositor_create_region(compositor);
+ wl_surface_set_input_region(wl_container->surface, region);
+ wl_region_destroy(region);
+
+ wl_surface_commit(wl_container->surface);
+ wl_display_flush(aWaylandDisplay->GetDisplay());
+
+ LOGWAYLAND(("%s [%p] created surface %p\n", __FUNCTION__, (void*)container,
+ (void*)wl_container->surface));
+ }
+
+ if (wl_container->surface_position_needs_update) {
+ moz_container_wayland_move_locked(container, wl_container->subsurface_dx,
+ wl_container->subsurface_dy);
+ }
+
+ moz_container_wayland_set_opaque_region_locked(container);
+ moz_container_wayland_set_scale_factor_locked(container);
+
+ return wl_container->surface;
+}
+
+struct wl_surface* moz_container_wayland_surface_lock(MozContainer* container) {
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet(display);
+
+ LOGWAYLAND(("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
+ (void*)container->wl_container.surface));
+
+ container->wl_container.container_lock->Lock();
+ struct wl_surface* surface =
+ moz_container_wayland_get_surface_locked(container, waylandDisplay);
+ if (surface == nullptr) {
+ container->wl_container.container_lock->Unlock();
+ }
+ return surface;
+}
+
+void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface) {
+ LOGWAYLAND(("%s [%p] surface %p\n", __FUNCTION__, (void*)container,
+ (void*)container->wl_container.surface));
+ if (*surface) {
+ container->wl_container.container_lock->Unlock();
+ *surface = nullptr;
+ }
+}
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, int scale) {
+ GdkDisplay* display = gtk_widget_get_display(GTK_WIDGET(container));
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet(display);
+ MozContainerWayland* wl_container = &container->wl_container;
+
+ LOGWAYLAND(("%s [%p] eglwindow %p\n", __FUNCTION__, (void*)container,
+ (void*)wl_container->eglwindow));
+
+ MutexAutoLock lock(*wl_container->container_lock);
+
+ // Always call moz_container_get_wl_surface() to ensure underlying
+ // container->surface has correct scale and position.
+ wl_surface* surface =
+ moz_container_wayland_get_surface_locked(container, waylandDisplay);
+ if (!surface) {
+ return nullptr;
+ }
+ if (!wl_container->eglwindow) {
+ GdkWindow* window = gtk_widget_get_window(GTK_WIDGET(container));
+ wl_container->eglwindow =
+ wl_egl_window_create(surface, gdk_window_get_width(window) * scale,
+ gdk_window_get_height(window) * scale);
+
+ LOGWAYLAND(("%s [%p] created eglwindow %p\n", __FUNCTION__,
+ (void*)container, (void*)wl_container->eglwindow));
+ }
+
+ return wl_container->eglwindow;
+}
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container) {
+ return container->wl_container.eglwindow ? true : false;
+}
+
+gboolean moz_container_wayland_surface_needs_clear(MozContainer* container) {
+ int ret = container->wl_container.surface_needs_clear;
+ container->wl_container.surface_needs_clear = false;
+ return ret;
+}
+
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ bool aSubtractCorners) {
+ MozContainerWayland* wl_container = &container->wl_container;
+ wl_container->opaque_region_needs_update = true;
+ wl_container->opaque_region_subtract_corners = aSubtractCorners;
+
+ // 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) {
+ return container ? container->wl_container.ready_to_draw : false;
+}
diff --git a/widget/gtk/MozContainerWayland.h b/widget/gtk/MozContainerWayland.h
new file mode 100644
index 0000000000..6decd5646c
--- /dev/null
+++ b/widget/gtk/MozContainerWayland.h
@@ -0,0 +1,83 @@
+/* -*- 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 __MOZ_CONTAINER_WAYLAND_H__
+#define __MOZ_CONTAINER_WAYLAND_H__
+
+#include <gtk/gtk.h>
+#include <functional>
+#include <vector>
+#include "mozilla/Mutex.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;
+ struct wl_subsurface* subsurface;
+ int subsurface_dx, subsurface_dy;
+ struct wl_egl_window* eglwindow;
+ struct wl_callback* frame_callback_handler;
+ int frame_callback_handler_surface_id;
+ gboolean opaque_region_needs_update;
+ gboolean opaque_region_subtract_corners;
+ gboolean opaque_region_fullscreen;
+ gboolean surface_position_needs_update;
+ gboolean surface_needs_clear;
+ gboolean ready_to_draw;
+ 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;
+};
+
+struct _MozContainer;
+struct _MozContainerClass;
+typedef struct _MozContainer MozContainer;
+typedef struct _MozContainerClass MozContainerClass;
+
+void moz_container_wayland_class_init(MozContainerClass* klass);
+void moz_container_wayland_init(MozContainerWayland* container);
+
+struct wl_surface* moz_container_wayland_surface_lock(MozContainer* container);
+void moz_container_wayland_surface_unlock(MozContainer* container,
+ struct wl_surface** surface);
+
+struct wl_egl_window* moz_container_wayland_get_egl_window(
+ MozContainer* container, int scale);
+
+gboolean moz_container_wayland_has_egl_window(MozContainer* container);
+gboolean moz_container_wayland_surface_needs_clear(MozContainer* container);
+void moz_container_wayland_move_resize(MozContainer* container, int dx, int dy,
+ int width, int height);
+void moz_container_wayland_egl_window_set_size(MozContainer* container,
+ int width, int height);
+void moz_container_wayland_set_scale_factor(MozContainer* container);
+void moz_container_wayland_add_initial_draw_callback(
+ MozContainer* container, const std::function<void(void)>& initial_draw_cb);
+wl_surface* moz_gtk_widget_get_wl_surface(GtkWidget* aWidget);
+void moz_container_wayland_update_opaque_region(MozContainer* container,
+ bool aSubtractCorners);
+gboolean moz_container_wayland_can_draw(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..65843d7768
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.cpp
@@ -0,0 +1,346 @@
+/* -*- 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/TextEvents.h"
+
+#include "NativeKeyBindings.h"
+#include "nsString.h"
+#include "nsMemory.h"
+#include "nsGtkKeyUtils.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdkkeysyms.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* w, gboolean select, gpointer user_data) {
+ AddCommand(Command::SelectAll);
+ g_signal_stop_emission_by_name(w, "select_all");
+ gHandled = true;
+}
+
+NativeKeyBindings* NativeKeyBindings::sInstanceForSingleLineEditor = nullptr;
+NativeKeyBindings* NativeKeyBindings::sInstanceForMultiLineEditor = nullptr;
+
+// static
+NativeKeyBindings* NativeKeyBindings::GetInstance(NativeKeyBindingsType aType) {
+ switch (aType) {
+ case nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ 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 nsIWidget::NativeKeyBindingsForMultiLineEditor:
+ case nsIWidget::NativeKeyBindingsForRichTextEditor:
+ 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 nsIWidget::NativeKeyBindingsForSingleLineEditor:
+ mNativeTarget = gtk_entry_new();
+ break;
+ default:
+ mNativeTarget = gtk_text_view_new();
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 &&
+ (gtk_minor_version > 2 ||
+ (gtk_minor_version == 2 && gtk_micro_version >= 2)))) {
+ // select_all only exists in gtk >= 2.2.2. Prior to that,
+ // ctrl+a is bound to (move to beginning, select to end).
+ 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,
+ nsTArray<CommandInt>& aCommands) {
+ // If the native key event is set, it must be synthesized for tests.
+ // We just ignore such events because this behavior depends on system
+ // settings.
+ if (!aEvent.mNativeKeyEvent) {
+ // It must be synthesized event or dispatched DOM event from chrome.
+ return;
+ }
+
+ guint keyval;
+
+ if (aEvent.mCharCode) {
+ keyval = gdk_unicode_to_keyval(aEvent.mCharCode);
+ } 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;
+ }
+ }
+ }
+
+ /*
+ 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;
+
+ MOZ_ASSERT(!gHandled || !aCommands.IsEmpty());
+
+ return gHandled;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/NativeKeyBindings.h b/widget/gtk/NativeKeyBindings.h
new file mode 100644
index 0000000000..ead3f350d5
--- /dev/null
+++ b/widget/gtk/NativeKeyBindings.h
@@ -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/. */
+
+#ifndef mozilla_widget_NativeKeyBindings_h_
+#define mozilla_widget_NativeKeyBindings_h_
+
+#include <gtk/gtk.h>
+#include "mozilla/Attributes.h"
+#include "mozilla/EventForwards.h"
+#include "nsIWidget.h"
+
+namespace mozilla {
+namespace widget {
+
+class NativeKeyBindings final {
+ typedef nsIWidget::NativeKeyBindingsType NativeKeyBindingsType;
+
+ public:
+ static NativeKeyBindings* GetInstance(NativeKeyBindingsType aType);
+ static void Shutdown();
+
+ void Init(NativeKeyBindingsType aType);
+
+ void GetEditCommands(const WidgetKeyboardEvent& aEvent,
+ 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 // mozilla_widget_NativeKeyBindings_h_
diff --git a/widget/gtk/PCompositorWidget.ipdl b/widget/gtk/PCompositorWidget.ipdl
new file mode 100644
index 0000000000..178fe78e4d
--- /dev/null
+++ b/widget/gtk/PCompositorWidget.ipdl
@@ -0,0 +1,30 @@
+/* -*- 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;
+
+using mozilla::LayoutDeviceIntSize from "Units.h";
+
+namespace mozilla {
+namespace widget {
+
+sync protocol PCompositorWidget
+{
+ manager PCompositorBridge;
+
+parent:
+ async __delete__();
+
+ async NotifyClientSizeChanged(LayoutDeviceIntSize aClientSize);
+
+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..2fe3ef148c
--- /dev/null
+++ b/widget/gtk/PlatformWidgetTypes.ipdlh
@@ -0,0 +1,31 @@
+/* -*- 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;
+
+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..67410b25a1
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.cpp
@@ -0,0 +1,195 @@
+/* -*- 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>
+#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 "nsGtkUtils.h"
+#include "nsTArray.h"
+
+namespace mozilla {
+namespace widget {
+
+static LazyLogModule sScreenLog("WidgetScreen");
+
+static void monitors_changed(GdkScreen* aScreen, gpointer aClosure) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Received monitors-changed event"));
+ ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
+ self->RefreshScreens();
+}
+
+static void screen_resolution_changed(GdkScreen* aScreen, GParamSpec* aPspec,
+ ScreenHelperGTK* self) {
+ self->RefreshScreens();
+}
+
+static GdkFilterReturn root_window_event_filter(GdkXEvent* aGdkXEvent,
+ GdkEvent* aGdkEvent,
+ gpointer aClosure) {
+#ifdef MOZ_X11
+ ScreenHelperGTK* self = static_cast<ScreenHelperGTK*>(aClosure);
+ XEvent* xevent = static_cast<XEvent*>(aGdkXEvent);
+
+ switch (xevent->type) {
+ case PropertyNotify: {
+ XPropertyEvent* propertyEvent = &xevent->xproperty;
+ if (propertyEvent->atom == self->NetWorkareaAtom()) {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Work area size changed"));
+ self->RefreshScreens();
+ }
+ } break;
+ default:
+ break;
+ }
+#endif
+
+ return GDK_FILTER_CONTINUE;
+}
+
+ScreenHelperGTK::ScreenHelperGTK()
+ : mRootWindow(nullptr)
+#ifdef MOZ_X11
+ ,
+ mNetWorkareaAtom(0)
+#endif
+{
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("ScreenHelperGTK 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 (GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ mNetWorkareaAtom = XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow),
+ "_NET_WORKAREA", X11False);
+ }
+#endif
+ RefreshScreens();
+}
+
+ScreenHelperGTK::~ScreenHelperGTK() {
+ 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;
+ }
+}
+
+gint ScreenHelperGTK::GetGTKMonitorScaleFactor(gint aMonitorNum) {
+ // Since GDK 3.10
+ static auto sGdkScreenGetMonitorScaleFactorPtr =
+ (gint(*)(GdkScreen*, gint))dlsym(RTLD_DEFAULT,
+ "gdk_screen_get_monitor_scale_factor");
+ if (sGdkScreenGetMonitorScaleFactorPtr) {
+ GdkScreen* screen = gdk_screen_get_default();
+ return sGdkScreenGetMonitorScaleFactorPtr(screen, aMonitorNum);
+ }
+ return 1;
+}
+
+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> MakeScreen(GdkScreen* aScreen,
+ gint aMonitorNum) {
+ GdkRectangle monitor;
+ GdkRectangle workarea;
+ gdk_screen_get_monitor_geometry(aScreen, aMonitorNum, &monitor);
+ gdk_screen_get_monitor_workarea(aScreen, aMonitorNum, &workarea);
+ 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.
+ LayoutDeviceIntRect rect(
+ monitor.x * gdkScaleFactor, monitor.y * gdkScaleFactor,
+ monitor.width * gdkScaleFactor, monitor.height * gdkScaleFactor);
+ LayoutDeviceIntRect availRect(
+ workarea.x * gdkScaleFactor, workarea.y * gdkScaleFactor,
+ workarea.width * gdkScaleFactor, workarea.height * gdkScaleFactor);
+ uint32_t pixelDepth = GetGTKPixelDepth();
+
+ // Use per-monitor scaling factor in gtk/wayland, or 1.0 otherwise.
+ DesktopToLayoutDeviceScale contentsScale(1.0);
+#ifdef MOZ_WAYLAND
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (!GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ contentsScale.scale = gdkScaleFactor;
+ }
+#endif
+
+ CSSToLayoutDeviceScale defaultCssScale(gdkScaleFactor *
+ gfxPlatformGtk::GetFontScaleFactor());
+
+ 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);
+ }
+
+ MOZ_LOG(sScreenLog, LogLevel::Debug,
+ ("New screen [%d %d %d %d (%d %d %d %d) %d %f %f %f]", rect.x, rect.y,
+ rect.width, rect.height, availRect.x, availRect.y, availRect.width,
+ availRect.height, pixelDepth, contentsScale.scale,
+ defaultCssScale.scale, dpi));
+ RefPtr<Screen> screen = new Screen(rect, availRect, pixelDepth, pixelDepth,
+ contentsScale, defaultCssScale, dpi);
+ return screen.forget();
+}
+
+void ScreenHelperGTK::RefreshScreens() {
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("Refreshing screens"));
+ AutoTArray<RefPtr<Screen>, 4> screenList;
+
+ GdkScreen* defaultScreen = gdk_screen_get_default();
+ gint numScreens = gdk_screen_get_n_monitors(defaultScreen);
+ MOZ_LOG(sScreenLog, LogLevel::Debug, ("GDK reports %d screens", numScreens));
+
+ for (gint i = 0; i < numScreens; i++) {
+ screenList.AppendElement(MakeScreen(defaultScreen, i));
+ }
+
+ ScreenManager& screenManager = ScreenManager::GetSingleton();
+ screenManager.Refresh(std::move(screenList));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/ScreenHelperGTK.h b/widget/gtk/ScreenHelperGTK.h
new file mode 100644
index 0000000000..78b4d7d823
--- /dev/null
+++ b/widget/gtk/ScreenHelperGTK.h
@@ -0,0 +1,45 @@
+/* -*- 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"
+#ifdef MOZ_X11
+# include <X11/Xlib.h>
+# include "X11UndefineNone.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+class ScreenHelperGTK final : public ScreenManager::Helper {
+ public:
+ ScreenHelperGTK();
+ ~ScreenHelperGTK() override;
+
+ static gint GetGTKMonitorScaleFactor(gint aMonitorNum = 0);
+
+#ifdef MOZ_X11
+ Atom NetWorkareaAtom() { return mNetWorkareaAtom; }
+#endif
+
+ // For internal use from signal callback functions
+ void RefreshScreens();
+
+ private:
+ GdkWindow* mRootWindow;
+#ifdef MOZ_X11
+ Atom mNetWorkareaAtom;
+#endif
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // mozilla_widget_gtk_ScreenHelperGTK_h
diff --git a/widget/gtk/TaskbarProgress.cpp b/widget/gtk/TaskbarProgress.cpp
new file mode 100644
index 0000000000..2aad109eab
--- /dev/null
+++ b/widget/gtk/TaskbarProgress.cpp
@@ -0,0 +1,108 @@
+/* -*- 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) {
+#ifdef MOZ_X11
+ 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);
+#endif
+
+ 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..2192e3f492
--- /dev/null
+++ b/widget/gtk/WakeLockListener.cpp
@@ -0,0 +1,500 @@
+/* -*- 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/. */
+
+#ifdef MOZ_ENABLE_DBUS
+
+# include "WakeLockListener.h"
+
+# include <dbus/dbus.h>
+# include <dbus/dbus-glib-lowlevel.h>
+
+# if defined(MOZ_X11)
+# include "gfxPlatformGtk.h"
+# include "prlink.h"
+# include <gdk/gdk.h>
+# include <gdk/gdkx.h>
+# endif
+
+# if defined(MOZ_WAYLAND)
+# include "mozilla/widget/nsWaylandDisplay.h"
+# include "nsWindow.h"
+# include "mozilla/dom/power/PowerManagerService.h"
+# endif
+
+# define FREEDESKTOP_SCREENSAVER_TARGET "org.freedesktop.ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_OBJECT "/ScreenSaver"
+# define FREEDESKTOP_SCREENSAVER_INTERFACE "org.freedesktop.ScreenSaver"
+
+# 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)
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+NS_IMPL_ISUPPORTS(WakeLockListener, nsIDOMMozWakeLockListener)
+
+StaticRefPtr<WakeLockListener> WakeLockListener::sSingleton;
+
+# define WAKE_LOCK_LOG(...) \
+ MOZ_LOG(gLinuxWakeLockLog, mozilla::LogLevel::Debug, (__VA_ARGS__))
+static mozilla::LazyLogModule gLinuxWakeLockLog("LinuxWakeLock");
+
+enum DesktopEnvironment {
+ FreeDesktop,
+ GNOME,
+# if defined(MOZ_X11)
+ XScreenSaver,
+# endif
+# if defined(MOZ_WAYLAND)
+ WaylandIdleInhibit,
+# endif
+ Unsupported,
+};
+
+class WakeLockTopic {
+ public:
+ WakeLockTopic(const nsAString& aTopic, DBusConnection* aConnection)
+ :
+# if defined(MOZ_WAYLAND)
+ mWaylandInhibitor(nullptr),
+# endif
+ mTopic(NS_ConvertUTF16toUTF8(aTopic)),
+ mConnection(aConnection),
+ mDesktopEnvironment(FreeDesktop),
+ mInhibitRequest(0),
+ mShouldInhibit(false),
+ mWaitingForReply(false) {
+ }
+
+ nsresult InhibitScreensaver(void);
+ nsresult UninhibitScreensaver(void);
+
+ private:
+ bool SendInhibit();
+ bool SendUninhibit();
+
+ bool SendFreeDesktopInhibitMessage();
+ bool SendGNOMEInhibitMessage();
+ bool SendMessage(DBusMessage* aMessage);
+
+# if defined(MOZ_X11)
+ static bool CheckXScreenSaverSupport();
+ static bool InhibitXScreenSaver(bool inhibit);
+# endif
+
+# if defined(MOZ_WAYLAND)
+ zwp_idle_inhibitor_v1* mWaylandInhibitor;
+ static bool CheckWaylandIdleInhibitSupport();
+ bool InhibitWaylandIdle();
+ bool UninhibitWaylandIdle();
+# endif
+
+ static void ReceiveInhibitReply(DBusPendingCall* aPending, void* aUserData);
+ void InhibitFailed();
+ void InhibitSucceeded(uint32_t aInhibitRequest);
+
+ nsCString mTopic;
+ RefPtr<DBusConnection> mConnection;
+
+ DesktopEnvironment mDesktopEnvironment;
+
+ uint32_t mInhibitRequest;
+
+ bool mShouldInhibit;
+ bool mWaitingForReply;
+};
+
+bool WakeLockTopic::SendMessage(DBusMessage* aMessage) {
+ // send message and get a handle for a reply
+ RefPtr<DBusPendingCall> reply;
+ dbus_connection_send_with_reply(mConnection, aMessage,
+ reply.StartAssignment(), DBUS_TIMEOUT);
+ if (!reply) {
+ return false;
+ }
+
+ dbus_pending_call_set_notify(reply, &ReceiveInhibitReply, this, NULL);
+
+ return true;
+}
+
+bool WakeLockTopic::SendFreeDesktopInhibitMessage() {
+ RefPtr<DBusMessage> message =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_STRING,
+ &topic, DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+bool WakeLockTopic::SendGNOMEInhibitMessage() {
+ RefPtr<DBusMessage> message =
+ already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE, "Inhibit"));
+
+ if (!message) {
+ return false;
+ }
+
+ static const uint32_t xid = 0;
+ static const uint32_t flags = (1 << 3); // Inhibit idle
+ const char* app = g_get_prgname();
+ const char* topic = mTopic.get();
+ dbus_message_append_args(message, DBUS_TYPE_STRING, &app, DBUS_TYPE_UINT32,
+ &xid, DBUS_TYPE_STRING, &topic, DBUS_TYPE_UINT32,
+ &flags, DBUS_TYPE_INVALID);
+
+ return SendMessage(message);
+}
+
+# if defined(MOZ_X11)
+
+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 (!gDisplay || !GDK_IS_X11_DISPLAY(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;
+
+ return true;
+}
+
+/* static */
+bool WakeLockTopic::InhibitXScreenSaver(bool 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 (!gDisplay || !GDK_IS_X11_DISPLAY(gDisplay)) {
+ return false;
+ }
+ Display* display = GDK_DISPLAY_XDISPLAY(gDisplay);
+ _XSSSuspend(display, inhibit);
+ return true;
+}
+
+# endif
+
+# if defined(MOZ_WAYLAND)
+
+/* static */
+bool WakeLockTopic::CheckWaylandIdleInhibitSupport() {
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
+ return waylandDisplay && waylandDisplay->GetIdleInhibitManager() != nullptr;
+}
+
+bool WakeLockTopic::InhibitWaylandIdle() {
+ RefPtr<nsWaylandDisplay> waylandDisplay = WaylandDisplayGet();
+ if (!waylandDisplay) {
+ return false;
+ }
+
+ nsWindow* focusedWindow = nsWindow::GetFocusedWindow();
+ if (!focusedWindow) {
+ return false;
+ }
+
+ UninhibitWaylandIdle();
+
+ MozContainer* container = focusedWindow->GetMozContainer();
+ wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+ if (waylandSurface) {
+ mWaylandInhibitor = zwp_idle_inhibit_manager_v1_create_inhibitor(
+ waylandDisplay->GetIdleInhibitManager(), waylandSurface);
+ moz_container_wayland_surface_unlock(container, &waylandSurface);
+ }
+ return true;
+}
+
+bool WakeLockTopic::UninhibitWaylandIdle() {
+ if (mWaylandInhibitor == nullptr) return false;
+
+ zwp_idle_inhibitor_v1_destroy(mWaylandInhibitor);
+ mWaylandInhibitor = nullptr;
+
+ return true;
+}
+
+# endif
+
+bool WakeLockTopic::SendInhibit() {
+ bool sendOk = false;
+
+ switch (mDesktopEnvironment) {
+ case FreeDesktop:
+ sendOk = SendFreeDesktopInhibitMessage();
+ break;
+ case GNOME:
+ sendOk = SendGNOMEInhibitMessage();
+ break;
+# if defined(MOZ_X11)
+ case XScreenSaver:
+ return InhibitXScreenSaver(true);
+# endif
+# if defined(MOZ_WAYLAND)
+ case WaylandIdleInhibit:
+ return InhibitWaylandIdle();
+# endif
+ case Unsupported:
+ return false;
+ }
+
+ if (sendOk) {
+ mWaitingForReply = true;
+ }
+
+ return sendOk;
+}
+
+bool WakeLockTopic::SendUninhibit() {
+ RefPtr<DBusMessage> message;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ FREEDESKTOP_SCREENSAVER_TARGET, FREEDESKTOP_SCREENSAVER_OBJECT,
+ FREEDESKTOP_SCREENSAVER_INTERFACE, "UnInhibit"));
+ } else if (mDesktopEnvironment == GNOME) {
+ message = already_AddRefed<DBusMessage>(dbus_message_new_method_call(
+ SESSION_MANAGER_TARGET, SESSION_MANAGER_OBJECT,
+ SESSION_MANAGER_INTERFACE, "Uninhibit"));
+ }
+# if defined(MOZ_X11)
+ else if (mDesktopEnvironment == XScreenSaver) {
+ return InhibitXScreenSaver(false);
+ }
+# endif
+# if defined(MOZ_WAYLAND)
+ else if (mDesktopEnvironment == WaylandIdleInhibit) {
+ return UninhibitWaylandIdle();
+ }
+# endif
+
+ if (!message) {
+ return false;
+ }
+
+ dbus_message_append_args(message, DBUS_TYPE_UINT32, &mInhibitRequest,
+ DBUS_TYPE_INVALID);
+
+ dbus_connection_send(mConnection, message, nullptr);
+ dbus_connection_flush(mConnection);
+
+ mInhibitRequest = 0;
+
+ return true;
+}
+
+nsresult WakeLockTopic::InhibitScreensaver() {
+ if (mShouldInhibit) {
+ // Screensaver is inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = true;
+
+ if (mWaitingForReply) {
+ // We already have a screensaver inhibit request pending. This can happen
+ // if InhibitScreensaver is called, then UninhibitScreensaver, then
+ // InhibitScreensaver again quickly.
+ return NS_OK;
+ }
+
+ return SendInhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+nsresult WakeLockTopic::UninhibitScreensaver() {
+ if (!mShouldInhibit) {
+ // Screensaver isn't inhibited. Nothing to do here.
+ return NS_OK;
+ }
+
+ mShouldInhibit = false;
+
+ if (mWaitingForReply) {
+ // If we're still waiting for a response to our inhibit request, we can't
+ // do anything until we get a dbus message back. The callbacks below will
+ // check |mShouldInhibit| and act accordingly.
+ return NS_OK;
+ }
+
+ return SendUninhibit() ? NS_OK : NS_ERROR_FAILURE;
+}
+
+void WakeLockTopic::InhibitFailed() {
+ mWaitingForReply = false;
+
+ if (mDesktopEnvironment == FreeDesktop) {
+ mDesktopEnvironment = GNOME;
+# if defined(MOZ_X11)
+ } else if (mDesktopEnvironment == GNOME && CheckXScreenSaverSupport()) {
+ mDesktopEnvironment = XScreenSaver;
+# endif
+# if defined(MOZ_WAYLAND)
+ } else if (mDesktopEnvironment == GNOME && CheckWaylandIdleInhibitSupport()) {
+ mDesktopEnvironment = WaylandIdleInhibit;
+# endif
+ } else {
+ mDesktopEnvironment = Unsupported;
+ mShouldInhibit = false;
+ }
+
+ if (!mShouldInhibit) {
+ // We were interrupted by UninhibitScreensaver() before we could find the
+ // correct desktop environment.
+ return;
+ }
+
+ SendInhibit();
+}
+
+void WakeLockTopic::InhibitSucceeded(uint32_t aInhibitRequest) {
+ mWaitingForReply = false;
+ mInhibitRequest = aInhibitRequest;
+
+ if (!mShouldInhibit) {
+ // We successfully inhibited the screensaver, but UninhibitScreensaver()
+ // was called while we were waiting for a reply.
+ SendUninhibit();
+ }
+}
+
+/* static */
+void WakeLockTopic::ReceiveInhibitReply(DBusPendingCall* pending,
+ void* user_data) {
+ if (!WakeLockListener::GetSingleton(false)) {
+ // The WakeLockListener (and therefore our topic) was deleted while we were
+ // waiting for a reply.
+ return;
+ }
+
+ WakeLockTopic* self = static_cast<WakeLockTopic*>(user_data);
+
+ RefPtr<DBusMessage> msg =
+ already_AddRefed<DBusMessage>(dbus_pending_call_steal_reply(pending));
+ if (!msg) {
+ return;
+ }
+
+ if (dbus_message_get_type(msg) == DBUS_MESSAGE_TYPE_METHOD_RETURN) {
+ uint32_t inhibitRequest;
+
+ if (dbus_message_get_args(msg, nullptr, DBUS_TYPE_UINT32, &inhibitRequest,
+ DBUS_TYPE_INVALID)) {
+ self->InhibitSucceeded(inhibitRequest);
+ }
+ } else {
+ self->InhibitFailed();
+ }
+}
+
+WakeLockListener::WakeLockListener() : mConnection(nullptr) {}
+
+/* static */
+WakeLockListener* WakeLockListener::GetSingleton(bool aCreate) {
+ if (!sSingleton && aCreate) {
+ sSingleton = new WakeLockListener();
+ }
+
+ return sSingleton;
+}
+
+/* static */
+void WakeLockListener::Shutdown() { sSingleton = nullptr; }
+
+bool WakeLockListener::EnsureDBusConnection() {
+ if (!mConnection) {
+ mConnection = already_AddRefed<DBusConnection>(
+ dbus_bus_get(DBUS_BUS_SESSION, nullptr));
+
+ if (mConnection) {
+ dbus_connection_set_exit_on_disconnect(mConnection, false);
+ dbus_connection_setup_with_g_main(mConnection, nullptr);
+ }
+ }
+
+ return mConnection != nullptr;
+}
+
+nsresult WakeLockListener::Callback(const nsAString& topic,
+ const nsAString& state) {
+ if (!EnsureDBusConnection()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (!topic.Equals(u"screen"_ns) && !topic.Equals(u"audio-playing"_ns) &&
+ !topic.Equals(u"video-playing"_ns))
+ return NS_OK;
+
+ WakeLockTopic* topicLock = mTopics.Get(topic);
+ if (!topicLock) {
+ topicLock = new WakeLockTopic(topic, mConnection);
+ mTopics.Put(topic, topicLock);
+ }
+
+ // Treat "locked-background" the same as "unlocked" on desktop linux.
+ bool shouldLock = state.EqualsLiteral("locked-foreground");
+ WAKE_LOCK_LOG("topic=%s, shouldLock=%d", NS_ConvertUTF16toUTF8(topic).get(),
+ shouldLock);
+
+ return shouldLock ? topicLock->InhibitScreensaver()
+ : topicLock->UninhibitScreensaver();
+}
+
+#endif
diff --git a/widget/gtk/WakeLockListener.h b/widget/gtk/WakeLockListener.h
new file mode 100644
index 0000000000..4f163434df
--- /dev/null
+++ b/widget/gtk/WakeLockListener.h
@@ -0,0 +1,55 @@
+/* -*- 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 <unistd.h>
+
+#include "mozilla/StaticPtr.h"
+#include "nsHashKeys.h"
+#include "nsClassHashtable.h"
+
+#include "nsIDOMWakeLockListener.h"
+
+#ifdef MOZ_ENABLE_DBUS
+# include "mozilla/DBusHelpers.h"
+#endif
+
+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;
+
+ static WakeLockListener* GetSingleton(bool aCreate = true);
+ static void Shutdown();
+
+ virtual nsresult Callback(const nsAString& topic,
+ const nsAString& state) override;
+
+ private:
+ WakeLockListener();
+ ~WakeLockListener() = default;
+
+ bool EnsureDBusConnection();
+
+ static mozilla::StaticRefPtr<WakeLockListener> sSingleton;
+
+#ifdef MOZ_ENABLE_DBUS
+ RefPtr<DBusConnection> mConnection;
+#endif
+ // Map of topic names to |WakeLockTopic|s.
+ // We assume a small, finite-sized set of topics.
+ nsClassHashtable<nsStringHashKey, WakeLockTopic> mTopics;
+};
+
+#endif // __WakeLockListener_h__
diff --git a/widget/gtk/WaylandVsyncSource.cpp b/widget/gtk/WaylandVsyncSource.cpp
new file mode 100644
index 0000000000..02f9d9c38b
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.cpp
@@ -0,0 +1,214 @@
+/* -*- 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 "nsThreadUtils.h"
+# include "nsISupportsImpl.h"
+# include "MainThreadUtils.h"
+
+# include <gdk/gdkwayland.h>
+
+using namespace mozilla::widget;
+
+namespace mozilla {
+
+static void WaylandVsyncSourceCallbackHandler(void* data,
+ struct wl_callback* callback,
+ uint32_t time) {
+ WaylandVsyncSource::WaylandDisplay* context =
+ (WaylandVsyncSource::WaylandDisplay*)data;
+ wl_callback_destroy(callback);
+ context->FrameCallback(time);
+}
+
+static const struct wl_callback_listener WaylandVsyncSourceCallbackListener = {
+ WaylandVsyncSourceCallbackHandler};
+
+WaylandVsyncSource::WaylandDisplay::WaylandDisplay(MozContainer* container)
+ : mEnabledLock("WaylandVsyncEnabledLock"),
+ mIsShutdown(false),
+ mVsyncEnabled(false),
+ mMonitorEnabled(false),
+ mCallback(nullptr),
+ mContainer(container),
+ mLastVsyncTimeStamp(TimeStamp::Now()) {
+ MOZ_ASSERT(NS_IsMainThread());
+
+ // We store the display here so all the frame callbacks won't have to look it
+ // up all the time.
+ mDisplay = WaylandDisplayGetWLDisplay();
+
+ mVsyncRate = TimeDuration::FromMilliseconds(1000.0 / 60.0);
+}
+
+void WaylandVsyncSource::WaylandDisplay::ClearFrameCallback() {
+ if (mCallback) {
+ wl_callback_destroy(mCallback);
+ mCallback = nullptr;
+ }
+}
+
+void WaylandVsyncSource::WaylandDisplay::Refresh() {
+ TimeStamp outputTimestamp;
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (!mMonitorEnabled || !mVsyncEnabled || mCallback) {
+ // 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.
+ return;
+ }
+
+ struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
+ if (!surface) {
+ // The surface hasn't been created yet. Try again when the surface is
+ // ready.
+ RefPtr<WaylandVsyncSource::WaylandDisplay> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void { self->Refresh(); });
+ return;
+ }
+ moz_container_wayland_surface_unlock(mContainer, &surface);
+
+ // Vsync is enabled, but we don't have a callback configured. Set one up so
+ // we can get to work.
+ SetupFrameCallback();
+ mLastVsyncTimeStamp = TimeStamp::Now();
+ outputTimestamp = mLastVsyncTimeStamp + GetVsyncRate();
+ }
+ NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
+}
+
+void WaylandVsyncSource::WaylandDisplay::EnableMonitor() {
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = true;
+ }
+ Refresh();
+}
+
+void WaylandVsyncSource::WaylandDisplay::DisableMonitor() {
+ MutexAutoLock lock(mEnabledLock);
+ if (!mMonitorEnabled) {
+ return;
+ }
+ mMonitorEnabled = false;
+ ClearFrameCallback();
+}
+
+void WaylandVsyncSource::WaylandDisplay::SetupFrameCallback() {
+ MOZ_ASSERT(mCallback == nullptr);
+ struct wl_surface* surface = moz_container_wayland_surface_lock(mContainer);
+ 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.
+ ClearFrameCallback();
+ return;
+ }
+
+ mCallback = wl_surface_frame(surface);
+ wl_callback_add_listener(mCallback, &WaylandVsyncSourceCallbackListener,
+ this);
+ wl_surface_commit(surface);
+ wl_display_flush(mDisplay);
+ moz_container_wayland_surface_unlock(mContainer, &surface);
+}
+
+void WaylandVsyncSource::WaylandDisplay::FrameCallback(uint32_t timestampTime) {
+ TimeStamp outputTimestamp;
+ {
+ MutexAutoLock lock(mEnabledLock);
+ mCallback = nullptr;
+
+ 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.
+ return;
+ }
+
+ // Configure our next frame callback.
+ SetupFrameCallback();
+
+ int64_t tick =
+ BaseTimeDurationPlatformUtils::TicksFromMilliseconds(timestampTime);
+ TimeStamp callbackTimeStamp = TimeStamp::FromSystemTime(tick);
+ double duration = (TimeStamp::Now() - callbackTimeStamp).ToMilliseconds();
+
+ TimeStamp vsyncTimestamp;
+ if (duration < 50 && duration > -50) {
+ vsyncTimestamp = callbackTimeStamp;
+ } else {
+ vsyncTimestamp = TimeStamp::Now();
+ }
+
+ CalculateVsyncRate(vsyncTimestamp);
+ mLastVsyncTimeStamp = vsyncTimestamp;
+ outputTimestamp = vsyncTimestamp + GetVsyncRate();
+ }
+ NotifyVsync(mLastVsyncTimeStamp, outputTimestamp);
+}
+
+TimeDuration WaylandVsyncSource::WaylandDisplay::GetVsyncRate() {
+ return mVsyncRate;
+}
+
+void WaylandVsyncSource::WaylandDisplay::CalculateVsyncRate(
+ TimeStamp vsyncTimestamp) {
+ double duration = (vsyncTimestamp - mLastVsyncTimeStamp).ToMilliseconds();
+ double curVsyncRate = mVsyncRate.ToMilliseconds();
+ 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);
+ }
+}
+
+void WaylandVsyncSource::WaylandDisplay::EnableVsync() {
+ MOZ_ASSERT(NS_IsMainThread());
+ {
+ MutexAutoLock lock(mEnabledLock);
+ if (mVsyncEnabled || mIsShutdown) {
+ return;
+ }
+ mVsyncEnabled = true;
+ }
+ Refresh();
+}
+
+void WaylandVsyncSource::WaylandDisplay::DisableVsync() {
+ MutexAutoLock lock(mEnabledLock);
+ mVsyncEnabled = false;
+ ClearFrameCallback();
+}
+
+bool WaylandVsyncSource::WaylandDisplay::IsVsyncEnabled() {
+ MutexAutoLock lock(mEnabledLock);
+ return mVsyncEnabled;
+}
+
+void WaylandVsyncSource::WaylandDisplay::Shutdown() {
+ MOZ_ASSERT(NS_IsMainThread());
+ MutexAutoLock lock(mEnabledLock);
+ mIsShutdown = true;
+ mVsyncEnabled = false;
+ ClearFrameCallback();
+ wl_display_roundtrip(mDisplay);
+}
+
+} // namespace mozilla
+
+#endif // MOZ_WAYLAND
diff --git a/widget/gtk/WaylandVsyncSource.h b/widget/gtk/WaylandVsyncSource.h
new file mode 100644
index 0000000000..fc6fcc10cf
--- /dev/null
+++ b/widget/gtk/WaylandVsyncSource.h
@@ -0,0 +1,96 @@
+/* -*- 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 "mozilla/RefPtr.h"
+#include "mozilla/Mutex.h"
+#include "mozilla/Monitor.h"
+#include "MozContainer.h"
+#include "VsyncSource.h"
+#include "base/thread.h"
+#include "nsWaylandDisplay.h"
+
+namespace mozilla {
+
+/*
+ * 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(MozContainer* container) {
+ MOZ_ASSERT(NS_IsMainThread());
+ mGlobalDisplay = new WaylandDisplay(container);
+ }
+
+ virtual ~WaylandVsyncSource() { MOZ_ASSERT(NS_IsMainThread()); }
+
+ virtual Display& GetGlobalDisplay() override { return *mGlobalDisplay; }
+
+ class WaylandDisplay final : public mozilla::gfx::VsyncSource::Display {
+ public:
+ explicit WaylandDisplay(MozContainer* container);
+
+ void EnableMonitor();
+ void DisableMonitor();
+
+ void FrameCallback(uint32_t timestampTime);
+ void Notify();
+
+ TimeDuration GetVsyncRate() override;
+
+ virtual void EnableVsync() override;
+
+ virtual void DisableVsync() override;
+
+ virtual bool IsVsyncEnabled() override;
+
+ virtual void Shutdown() override;
+
+ private:
+ virtual ~WaylandDisplay() = default;
+ void Refresh();
+ void SetupFrameCallback();
+ void ClearFrameCallback();
+ void CalculateVsyncRate(TimeStamp vsyncTimestamp);
+
+ Mutex mEnabledLock;
+ bool mIsShutdown;
+ bool mVsyncEnabled;
+ bool mMonitorEnabled;
+ struct wl_display* mDisplay;
+ struct wl_callback* mCallback;
+ MozContainer* mContainer;
+ TimeDuration mVsyncRate;
+ TimeStamp mLastVsyncTimeStamp;
+ };
+
+ private:
+ // We need a refcounted VsyncSource::Display to use chromium IPC runnables.
+ RefPtr<WaylandDisplay> mGlobalDisplay;
+};
+
+} // namespace mozilla
+
+#endif // _WaylandVsyncSource_h_
diff --git a/widget/gtk/WidgetStyleCache.cpp b/widget/gtk/WidgetStyleCache.cpp
new file mode 100644
index 0000000000..15f66f4876
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.cpp
@@ -0,0 +1,1464 @@
+/* -*- 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 "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");
+
+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* CreateMenuBarWidget() {
+ GtkWidget* widget = gtk_menu_bar_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateMenuPopupWidget() {
+ GtkWidget* widget = gtk_menu_new();
+ gtk_menu_attach_to_widget(GTK_MENU(widget), GetWidget(MOZ_GTK_WINDOW),
+ nullptr);
+ 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* CreateGripperWidget() {
+ GtkWidget* widget = gtk_handle_box_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateToolbarWidget() {
+ GtkWidget* widget = gtk_toolbar_new();
+ gtk_container_add(GTK_CONTAINER(GetWidget(MOZ_GTK_GRIPPER)), widget);
+ return widget;
+}
+
+static GtkWidget* CreateToolbarSeparatorWidget() {
+ GtkWidget* widget = GTK_WIDGET(gtk_separator_tool_item_new());
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+static GtkWidget* CreateInfoBarWidget() {
+ GtkWidget* widget = gtk_info_bar_new();
+ 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* CreateMenuSeparatorWidget() {
+ GtkWidget* widget = gtk_separator_menu_item_new();
+ gtk_menu_shell_append(GTK_MENU_SHELL(GetWidget(MOZ_GTK_MENUPOPUP)), 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* CreateTreeHeaderSortArrowWidget() {
+ /* TODO, but it can't be NULL */
+ GtkWidget* widget = gtk_button_new();
+ AddToWindowContainer(widget);
+ return widget;
+}
+
+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;
+}
+
+void GtkWindowSetTitlebar(GtkWindow* aWindow, GtkWidget* aWidget) {
+ static auto sGtkWindowSetTitlebar = (void (*)(GtkWindow*, GtkWidget*))dlsym(
+ RTLD_DEFAULT, "gtk_window_set_titlebar");
+ sGtkWindowSetTitlebar(aWindow, aWidget);
+}
+
+GtkWidget* GtkHeaderBarNew() {
+ static auto sGtkHeaderBarNewPtr =
+ (GtkWidget * (*)()) dlsym(RTLD_DEFAULT, "gtk_header_bar_new");
+ return sGtkHeaderBarNewPtr();
+}
+
+bool IsSolidCSDStyleUsed() {
+ static bool isSolidCSDStyleUsed = []() {
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkWindowSetTitlebar(GTK_WINDOW(window), GtkHeaderBarNew());
+ gtk_widget_realize(window);
+ GtkStyleContext* windowStyle = gtk_widget_get_style_context(window);
+ bool ret = gtk_style_context_has_class(windowStyle, "solid-csd");
+ gtk_widget_destroy(window);
+ return ret;
+ }();
+ return isSolidCSDStyleUsed;
+}
+
+static void CreateHeaderBarWidget(WidgetNodeType aAppearance) {
+ sWidgetStorage[aAppearance] = GtkHeaderBarNew();
+
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
+ GtkStyleContext* style = gtk_widget_get_style_context(window);
+
+ if (aAppearance == MOZ_GTK_HEADER_BAR_MAXIMIZED) {
+ gtk_style_context_add_class(style, "maximized");
+ MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] == nullptr,
+ "Window widget is already created!");
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW_MAXIMIZED] = window;
+ } else {
+ MOZ_ASSERT(sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] == nullptr,
+ "Window widget is already created!");
+ sWidgetStorage[MOZ_GTK_HEADERBAR_WINDOW] = window;
+ }
+
+ // Headerbar has to be placed to window with csd or solid-csd style
+ // to properly draw the decorated.
+ gtk_style_context_add_class(style,
+ IsSolidCSDStyleUsed() ? "solid-csd" : "csd");
+
+ GtkWidget* fixed = gtk_fixed_new();
+ gtk_container_add(GTK_CONTAINER(window), fixed);
+ gtk_container_add(GTK_CONTAINER(fixed), sWidgetStorage[aAppearance]);
+
+ // Emulate what create_titlebar() at gtkwindow.c does.
+ style = gtk_widget_get_style_context(sWidgetStorage[aAppearance]);
+ gtk_style_context_add_class(style, "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(style, "default-decoration");
+}
+
+#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 */
+ static auto sGtkIconThemeLookupIconForScalePtr =
+ (GtkIconInfo *
+ (*)(GtkIconTheme*, const gchar*, gint, gint, GtkIconLookupFlags))
+ dlsym(RTLD_DEFAULT, "gtk_icon_theme_lookup_icon_for_scale");
+ static auto sGdkCairoSurfaceCreateFromPixbufPtr =
+ (cairo_surface_t * (*)(const GdkPixbuf*, int, GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_cairo_surface_create_from_pixbuf");
+
+ for (int scale = 1; scale < ICON_SCALE_VARIANTS + 1; scale++) {
+ GtkIconInfo* gtkIconInfo = sGtkIconThemeLookupIconForScalePtr(
+ 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 =
+ sGdkCairoSurfaceCreateFromPixbufPtr(iconPixbuf, scale, nullptr);
+ g_object_unref(iconPixbuf);
+
+ nsAutoCString surfaceName;
+ surfaceName = nsPrintfCString("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;
+
+ nsAutoCString surfaceName;
+ surfaceName = nsPrintfCString("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] == nullptr,
+ "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;
+}
+
+static void CreateHeaderBarButtons() {
+ GtkWidget* headerBar = sWidgetStorage[MOZ_GTK_HEADER_BAR];
+ MOZ_ASSERT(headerBar != nullptr, "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(GetWidget(MOZ_GTK_HEADER_BAR)), 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),
+ 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_HORIZONTAL:
+ return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_HORIZONTAL);
+ case MOZ_GTK_SCROLLBAR_VERTICAL:
+ return CreateScrollbarWidget(aAppearance, GTK_ORIENTATION_VERTICAL);
+ case MOZ_GTK_MENUBAR:
+ return CreateMenuBarWidget();
+ case MOZ_GTK_MENUPOPUP:
+ return CreateMenuPopupWidget();
+ case MOZ_GTK_MENUSEPARATOR:
+ return CreateMenuSeparatorWidget();
+ case MOZ_GTK_EXPANDER:
+ return CreateExpanderWidget();
+ case MOZ_GTK_FRAME:
+ return CreateFrameWidget();
+ case MOZ_GTK_GRIPPER:
+ return CreateGripperWidget();
+ case MOZ_GTK_TOOLBAR:
+ return CreateToolbarWidget();
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return CreateToolbarSeparatorWidget();
+ case MOZ_GTK_INFO_BAR:
+ return CreateInfoBarWidget();
+ 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_TREE_HEADER_SORTARROW:
+ return CreateTreeHeaderSortArrowWidget();
+ 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_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_MENUBARITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUBAR);
+ break;
+ case MOZ_GTK_MENUITEM:
+ style = CreateStyleForWidget(gtk_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_CHECKMENUITEM:
+ style =
+ CreateStyleForWidget(gtk_check_menu_item_new(), MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_RADIOMENUITEM:
+ style = CreateStyleForWidget(gtk_radio_menu_item_new(nullptr),
+ MOZ_GTK_MENUPOPUP);
+ 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_HORIZONTAL:
+ style = CreateChildCSSNode("contents", MOZ_GTK_SCROLLBAR_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_TROUGH,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_SLIDER,
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL);
+ break;
+ 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_SCROLLBAR_BUTTON:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_BUTTON,
+ MOZ_GTK_SCROLLBAR_CONTENTS_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_RADIOMENUITEM_INDICATOR:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_RADIO, MOZ_GTK_RADIOMENUITEM);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ style = CreateChildCSSNode(GTK_STYLE_CLASS_CHECK, MOZ_GTK_CHECKMENUITEM);
+ 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_GRIPPER:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ // TODO - create from CSS node
+ style = CreateSubStyleWithClass(MOZ_GTK_INFO_BAR, GTK_STYLE_CLASS_INFO);
+ 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_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE: {
+ NS_ASSERTION(
+ false, "MOZ_GTK_HEADER_BAR_BUTTON_RESTORE is used as an icon only!");
+ return nullptr;
+ }
+ 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_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_TROUGH);
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ style = CreateSubStyleWithClass(MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ GTK_STYLE_CLASS_SLIDER);
+ break;
+ 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_RADIOMENUITEM_INDICATOR:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_RADIOMENUITEM, GTK_STYLE_CLASS_RADIO);
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ style =
+ CreateSubStyleWithClass(MOZ_GTK_CHECKMENUITEM, 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_GRIPPER:
+ style = CreateSubStyleWithClass(MOZ_GTK_GRIPPER, GTK_STYLE_CLASS_GRIP);
+ break;
+ case MOZ_GTK_INFO_BAR:
+ style = CreateSubStyleWithClass(MOZ_GTK_INFO_BAR, GTK_STYLE_CLASS_INFO);
+ 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(void) {
+ for (int i = 0; i < MOZ_GTK_WIDGET_NODE_COUNT; i++) {
+ if (sStyleStorage[i]) g_object_unref(sStyleStorage[i]);
+ }
+ mozilla::PodArrayZero(sStyleStorage);
+
+ /* 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);
+ }
+}
diff --git a/widget/gtk/WidgetStyleCache.h b/widget/gtk/WidgetStyleCache.h
new file mode 100644
index 0000000000..13fa53d4b4
--- /dev/null
+++ b/widget/gtk/WidgetStyleCache.h
@@ -0,0 +1,59 @@
+/* -*- 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(void);
+
+bool IsSolidCSDStyleUsed();
+
+void StyleContextSetScale(GtkStyleContext* style, gint aScaleFactor);
+
+#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..a0299bdf14
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.cpp
@@ -0,0 +1,52 @@
+/* -*- 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 "nsWindow.h"
+#include <gtk/gtk.h>
+
+namespace mozilla {
+
+namespace 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();
+}
+
+} // namespace widget
+
+} // namespace mozilla
diff --git a/widget/gtk/WidgetUtilsGtk.h b/widget/gtk/WidgetUtilsGtk.h
new file mode 100644
index 0000000000..5ede9aa520
--- /dev/null
+++ b/widget/gtk/WidgetUtilsGtk.h
@@ -0,0 +1,26 @@
+/* -*- 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 <stdint.h>
+
+namespace mozilla {
+namespace widget {
+
+class WidgetUtilsGTK {
+ public:
+ /* See WidgetUtils::IsTouchDeviceSupportPresent(). */
+ static int32_t IsTouchDeviceSupportPresent();
+};
+
+bool IsMainWindowTransparent();
+
+} // namespace widget
+
+} // namespace mozilla
+
+#endif // WidgetUtilsGtk_h__
diff --git a/widget/gtk/WindowSurfaceProvider.cpp b/widget/gtk/WindowSurfaceProvider.cpp
new file mode 100644
index 0000000000..282847a512
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "mozilla/gfx/Logging.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsWindow.h"
+#include "WindowSurfaceX11Image.h"
+#include "WindowSurfaceX11SHM.h"
+#include "WindowSurfaceXRender.h"
+#ifdef MOZ_WAYLAND
+# include "WindowSurfaceWayland.h"
+#endif
+
+namespace mozilla {
+namespace widget {
+
+using namespace mozilla::layers;
+
+WindowSurfaceProvider::WindowSurfaceProvider()
+ : mIsX11Display(false),
+ mXDisplay(nullptr),
+ mXWindow(0),
+ mXVisual(nullptr),
+ mXDepth(0),
+ mWindowSurface(nullptr)
+#ifdef MOZ_WAYLAND
+ ,
+ mWidget(nullptr)
+#endif
+ ,
+ mIsShaped(false) {
+}
+
+void WindowSurfaceProvider::Initialize(Display* aDisplay, Window aWindow,
+ Visual* aVisual, int aDepth,
+ bool aIsShaped) {
+ // We should not be initialized
+ MOZ_ASSERT(!mXDisplay);
+
+ // This should also be a valid initialization
+ MOZ_ASSERT(aDisplay && aWindow != X11None && aVisual);
+
+ mXDisplay = aDisplay;
+ mXWindow = aWindow;
+ mXVisual = aVisual;
+ mXDepth = aDepth;
+ mIsShaped = aIsShaped;
+ mIsX11Display = true;
+}
+
+#ifdef MOZ_WAYLAND
+void WindowSurfaceProvider::Initialize(nsWindow* aWidget) {
+ mWidget = aWidget;
+ mIsX11Display = false;
+}
+#endif
+
+void WindowSurfaceProvider::CleanupResources() { mWindowSurface = nullptr; }
+
+UniquePtr<WindowSurface> WindowSurfaceProvider::CreateWindowSurface() {
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display) {
+ LOGDRAW(("Drawing to nsWindow %p will use wl_surface\n", mWidget));
+ return MakeUnique<WindowSurfaceWayland>(mWidget);
+ }
+#endif
+
+ // We should be initialized
+ MOZ_ASSERT(mXDisplay);
+
+ // Blit to the window with the following priority:
+ // 1. XRender (iff XRender is enabled && we are in-process)
+ // 2. MIT-SHM
+ // 3. XPutImage
+ if (!mIsShaped && gfx::gfxVars::UseXRender()) {
+ LOGDRAW(("Drawing to Window 0x%lx will use XRender\n", mXWindow));
+ return MakeUnique<WindowSurfaceXRender>(mXDisplay, mXWindow, mXVisual,
+ mXDepth);
+ }
+
+#ifdef MOZ_HAVE_SHMIMAGE
+ if (!mIsShaped && nsShmImage::UseShm()) {
+ LOGDRAW(("Drawing to Window 0x%lx will use MIT-SHM\n", mXWindow));
+ return MakeUnique<WindowSurfaceX11SHM>(mXDisplay, mXWindow, mXVisual,
+ mXDepth);
+ }
+#endif // MOZ_HAVE_SHMIMAGE
+
+ LOGDRAW(("Drawing to Window 0x%lx will use XPutImage\n", mXWindow));
+ return MakeUnique<WindowSurfaceX11Image>(mXDisplay, mXWindow, mXVisual,
+ mXDepth, mIsShaped);
+}
+
+already_AddRefed<gfx::DrawTarget>
+WindowSurfaceProvider::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode) {
+ if (aInvalidRegion.IsEmpty()) return nullptr;
+
+ if (!mWindowSurface) {
+ mWindowSurface = CreateWindowSurface();
+ if (!mWindowSurface) return nullptr;
+ }
+
+ *aBufferMode = BufferMode::BUFFER_NONE;
+ RefPtr<gfx::DrawTarget> dt = nullptr;
+ if (!(dt = mWindowSurface->Lock(aInvalidRegion)) && mIsX11Display &&
+ !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 = MakeUnique<WindowSurfaceX11Image>(
+ mXDisplay, mXWindow, mXVisual, mXDepth, mIsShaped);
+ dt = mWindowSurface->Lock(aInvalidRegion);
+ }
+ return dt.forget();
+}
+
+void WindowSurfaceProvider::EndRemoteDrawingInRegion(
+ gfx::DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ if (mWindowSurface) 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..cbd36fa10b
--- /dev/null
+++ b/widget/gtk/WindowSurfaceProvider.h
@@ -0,0 +1,81 @@
+/* -*- 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 "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "Units.h"
+
+#include <gdk/gdk.h>
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif
+#include <X11/Xlib.h> // for Window, Display, Visual, etc.
+#include "X11UndefineNone.h"
+
+class nsWindow;
+
+namespace mozilla {
+namespace widget {
+
+/*
+ * 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();
+
+ /**
+ * 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.
+ */
+ void Initialize(Display* aDisplay, Window aWindow, Visual* aVisual,
+ int aDepth, bool aIsShaped);
+
+#ifdef MOZ_WAYLAND
+ void Initialize(nsWindow* aWidget);
+#endif
+
+ /**
+ * Releases any surfaces created by this provider.
+ * This is used by GtkCompositorWidget to get rid
+ * of resources before we close the display connection.
+ */
+ void CleanupResources();
+
+ already_AddRefed<gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, layers::BufferMode* aBufferMode);
+ void EndRemoteDrawingInRegion(gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion);
+
+ private:
+ UniquePtr<WindowSurface> CreateWindowSurface();
+
+ // Can we access X?
+ bool mIsX11Display;
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ UniquePtr<WindowSurface> mWindowSurface;
+#ifdef MOZ_WAYLAND
+ nsWindow* mWidget;
+#endif
+ bool mIsShaped;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_PROVIDER_H
diff --git a/widget/gtk/WindowSurfaceWayland.cpp b/widget/gtk/WindowSurfaceWayland.cpp
new file mode 100644
index 0000000000..2855ff7121
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWayland.cpp
@@ -0,0 +1,1116 @@
+/* -*- 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 "nsWaylandDisplay.h"
+#include "WindowSurfaceWayland.h"
+
+#include "nsPrintfCString.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Tools.h"
+#include "gfx2DGlue.h"
+#include "gfxPlatform.h"
+#include "MozContainer.h"
+#include "nsTArray.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+#include <sys/mman.h>
+#include <fcntl.h>
+#include <errno.h>
+
+#undef LOG
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gWidgetWaylandLog;
+# define LOGWAYLAND(args) \
+ MOZ_LOG(gWidgetWaylandLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGWAYLAND(args)
+#endif /* MOZ_LOGGING */
+
+// Maximal compositing timeout it miliseconds
+#define COMPOSITING_TIMEOUT 200
+
+namespace mozilla {
+namespace 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 |
+ | | | ------------------
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | | |
+ | | ----------------------- |
+ | | | WindowBackBuffer | |
+ | | | | |
+ | | | ------------------- | |
+ | | | | WaylandShmPool | | |
+ | | | ------------------- | |
+ | | ----------------------- |
+ | ---------------------------------
+ |
+ |
+ --------------------------------- ------------------
+ | WindowSurfaceWayland |<------>| nsWindow |
+ | | ------------------
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | WaylandShmPool | | |
+ | | ------------------- | |
+ | ----------------------- |
+ | |
+ | ----------------------- |
+ | | WindowBackBuffer | |
+ | | | |
+ | | ------------------- | |
+ | | | 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 WindowBackBuffer)
+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 availabe or discard the
+WindowImageSurface cache when whole screen is invalidated.
+
+WindowBackBuffer
+
+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.
+
+WindowBackBuffer 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 WindowBackBuffer.
+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 WindowBackBuffer/WindowSurfaceWayland
+(wl_buffer/wl_surface).
+*/
+
+#define EVENT_LOOP_DELAY (1000 / 240)
+
+#define BUFFER_BPP 4
+gfx::SurfaceFormat WindowBackBuffer::mFormat = gfx::SurfaceFormat::B8G8R8A8;
+
+int WindowBackBuffer::mDumpSerial =
+ PR_GetEnv("MOZ_WAYLAND_DUMP_WL_BUFFERS") ? 1 : 0;
+char* WindowBackBuffer::mDumpDir = PR_GetEnv("MOZ_WAYLAND_DUMP_DIR");
+
+RefPtr<nsWaylandDisplay> WindowBackBuffer::GetWaylandDisplay() {
+ return mWindowSurfaceWayland->GetWaylandDisplay();
+}
+
+static int WaylandAllocateShmMemory(int aSize) {
+ int fd = -1;
+ do {
+ static int counter = 0;
+ nsPrintfCString shmName("/wayland.mozilla.ipc.%d", counter++);
+ fd = shm_open(shmName.get(), O_CREAT | O_RDWR | O_EXCL, 0600);
+ if (fd >= 0) {
+ // We don't want to use leaked file
+ if (shm_unlink(shmName.get()) != 0) {
+ NS_WARNING("shm_unlink failed");
+ return -1;
+ }
+ }
+ } while (fd < 0 && errno == EEXIST);
+
+ if (fd < 0) {
+ NS_WARNING(nsPrintfCString("shm_open failed: %s", strerror(errno)).get());
+ return -1;
+ }
+
+ int ret = 0;
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ ret = posix_fallocate(fd, 0, aSize);
+ } while (ret == EINTR);
+ if (ret != 0) {
+ NS_WARNING(
+ nsPrintfCString("posix_fallocate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ return -1;
+ }
+#else
+ do {
+ ret = ftruncate(fd, aSize);
+ } while (ret < 0 && errno == EINTR);
+ if (ret < 0) {
+ NS_WARNING(nsPrintfCString("ftruncate() fails to allocate shm memory: %s",
+ strerror(ret))
+ .get());
+ close(fd);
+ fd = -1;
+ }
+#endif
+
+ return fd;
+}
+
+static bool WaylandReAllocateShmMemory(int aFd, int aSize) {
+ if (ftruncate(aFd, aSize) < 0) {
+ return false;
+ }
+#ifdef HAVE_POSIX_FALLOCATE
+ do {
+ errno = posix_fallocate(aFd, 0, aSize);
+ } while (errno == EINTR);
+ if (errno != 0) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+WaylandShmPool::WaylandShmPool()
+ : mShmPool(nullptr),
+ mShmPoolFd(-1),
+ mAllocatedSize(0),
+ mImageData(MAP_FAILED){};
+
+void WaylandShmPool::Release() {
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ mImageData = MAP_FAILED;
+ }
+ if (mShmPool) {
+ wl_shm_pool_destroy(mShmPool);
+ mShmPool = 0;
+ }
+ if (mShmPoolFd >= 0) {
+ close(mShmPoolFd);
+ mShmPoolFd = -1;
+ }
+}
+
+bool WaylandShmPool::Create(RefPtr<nsWaylandDisplay> aWaylandDisplay,
+ int aSize) {
+ // We do size increase only
+ if (aSize <= mAllocatedSize) {
+ return true;
+ }
+
+ if (mShmPoolFd < 0) {
+ mShmPoolFd = WaylandAllocateShmMemory(aSize);
+ if (mShmPoolFd < 0) {
+ return false;
+ }
+ } else {
+ if (!WaylandReAllocateShmMemory(mShmPoolFd, aSize)) {
+ Release();
+ return false;
+ }
+ }
+
+ if (mImageData != MAP_FAILED) {
+ munmap(mImageData, mAllocatedSize);
+ }
+ mImageData =
+ mmap(nullptr, aSize, PROT_READ | PROT_WRITE, MAP_SHARED, mShmPoolFd, 0);
+ if (mImageData == MAP_FAILED) {
+ NS_WARNING("Unable to map drawing surface!");
+ Release();
+ return false;
+ }
+
+ if (mShmPool) {
+ wl_shm_pool_resize(mShmPool, aSize);
+ } else {
+ mShmPool = wl_shm_create_pool(aWaylandDisplay->GetShm(), mShmPoolFd, aSize);
+ // We set our queue to get mShmPool events at compositor thread.
+ wl_proxy_set_queue((struct wl_proxy*)mShmPool,
+ aWaylandDisplay->GetEventQueue());
+ }
+
+ mAllocatedSize = aSize;
+ return true;
+}
+
+void WaylandShmPool::SetImageDataFromPool(class WaylandShmPool* aSourcePool,
+ int aImageDataSize) {
+ MOZ_ASSERT(mAllocatedSize >= aImageDataSize, "WaylandShmPool overflows!");
+ memcpy(mImageData, aSourcePool->GetImageData(), aImageDataSize);
+}
+
+WaylandShmPool::~WaylandShmPool() { Release(); }
+
+static void buffer_release(void* data, wl_buffer* buffer) {
+ auto surface = reinterpret_cast<WindowBackBuffer*>(data);
+ surface->Detach(buffer);
+}
+
+static const struct wl_buffer_listener buffer_listener = {buffer_release};
+
+bool WindowBackBuffer::Create(int aWidth, int aHeight) {
+ MOZ_ASSERT(!IsAttached(), "We can't create attached buffers.");
+
+ ReleaseWLBuffer();
+
+ int size = aWidth * aHeight * BUFFER_BPP;
+ if (!mShmPool.Create(GetWaylandDisplay(), size)) {
+ return false;
+ }
+
+ mWLBuffer =
+ wl_shm_pool_create_buffer(mShmPool.GetShmPool(), 0, aWidth, aHeight,
+ aWidth * BUFFER_BPP, WL_SHM_FORMAT_ARGB8888);
+ wl_proxy_set_queue((struct wl_proxy*)mWLBuffer,
+ GetWaylandDisplay()->GetEventQueue());
+ wl_buffer_add_listener(mWLBuffer, &buffer_listener, this);
+
+ mWidth = aWidth;
+ mHeight = aHeight;
+
+ LOGWAYLAND(("WindowBackBuffer::Create [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+ return true;
+}
+
+void WindowBackBuffer::ReleaseWLBuffer() {
+ LOGWAYLAND(("WindowBackBuffer::Release [%p]\n", (void*)this));
+ if (mWLBuffer) {
+ wl_buffer_destroy(mWLBuffer);
+ mWLBuffer = nullptr;
+ }
+ mWidth = mHeight = 0;
+}
+
+void WindowBackBuffer::Clear() {
+ memset(mShmPool.GetImageData(), 0, mHeight * mWidth * BUFFER_BPP);
+}
+
+WindowBackBuffer::WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland)
+ : mWindowSurfaceWayland(aWindowSurfaceWayland),
+ mShmPool(),
+ mWLBuffer(nullptr),
+ mWidth(0),
+ mHeight(0),
+ mAttached(false) {}
+
+WindowBackBuffer::~WindowBackBuffer() { ReleaseWLBuffer(); }
+
+bool WindowBackBuffer::Resize(int aWidth, int aHeight) {
+ if (aWidth == mWidth && aHeight == mHeight) {
+ return true;
+ }
+ LOGWAYLAND(
+ ("WindowBackBuffer::Resize [%p] %d %d\n", (void*)this, aWidth, aHeight));
+ Create(aWidth, aHeight);
+ return (mWLBuffer != nullptr);
+}
+
+void WindowBackBuffer::Attach(wl_surface* aSurface) {
+ LOGWAYLAND(
+ ("WindowBackBuffer::Attach [%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);
+ wl_display_flush(WaylandDisplayGetWLDisplay());
+ }
+}
+
+void WindowBackBuffer::Detach(wl_buffer* aBuffer) {
+ LOGWAYLAND(("WindowBackBuffer::Detach [%p] wl_buffer %p ID %d\n", (void*)this,
+ (void*)aBuffer,
+ aBuffer ? wl_proxy_get_id((struct wl_proxy*)aBuffer) : -1));
+ mAttached = false;
+
+ // Commit any potential cached drawings from latest Lock()/Commit() cycle.
+ mWindowSurfaceWayland->FlushPendingCommits();
+}
+
+bool WindowBackBuffer::SetImageDataFromBuffer(
+ class WindowBackBuffer* aSourceBuffer) {
+ auto sourceBuffer = static_cast<class WindowBackBuffer*>(aSourceBuffer);
+ if (!IsMatchingSize(sourceBuffer)) {
+ if (!Resize(sourceBuffer->mWidth, sourceBuffer->mHeight)) {
+ return false;
+ }
+ }
+
+ mShmPool.SetImageDataFromPool(
+ &sourceBuffer->mShmPool,
+ sourceBuffer->mWidth * sourceBuffer->mHeight * BUFFER_BPP);
+ return true;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowBackBuffer::Lock() {
+ LOGWAYLAND(("WindowBackBuffer::Lock [%p] [%d x %d] wl_buffer %p ID %d\n",
+ (void*)this, mWidth, mHeight, (void*)mWLBuffer,
+ mWLBuffer ? wl_proxy_get_id((struct wl_proxy*)mWLBuffer) : -1));
+
+ gfx::IntSize lockSize(mWidth, mHeight);
+ mIsLocked = true;
+ return gfxPlatform::CreateDrawTargetForData(
+ static_cast<unsigned char*>(mShmPool.GetImageData()), lockSize,
+ BUFFER_BPP * mWidth, GetSurfaceFormat());
+}
+
+#ifdef MOZ_LOGGING
+void WindowBackBuffer::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, mWidth,
+ mHeight, BUFFER_BPP * mWidth);
+ 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 void frame_callback_handler(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ auto surface = reinterpret_cast<WindowSurfaceWayland*>(data);
+ surface->FrameCallbackHandler();
+}
+
+static const struct wl_callback_listener frame_listener = {
+ frame_callback_handler};
+
+WindowSurfaceWayland::WindowSurfaceWayland(nsWindow* aWindow)
+ : mWindow(aWindow),
+ mWaylandDisplay(WaylandDisplayGet()),
+ mWaylandBuffer(nullptr),
+ mWaylandFullscreenDamage(false),
+ mFrameCallback(nullptr),
+ mLastCommittedSurface(nullptr),
+ mLastCommitTime(0),
+ mDrawToWaylandBufferDirectly(true),
+ mCanSwitchWaylandBuffer(true),
+ mBufferPendingCommit(false),
+ mBufferCommitAllowed(false),
+ mBufferNeedsClear(false),
+ mSmoothRendering(StaticPrefs::widget_wayland_smooth_rendering()),
+ mSurfaceReadyTimerID(),
+ mSurfaceLock("WindowSurfaceWayland lock") {
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ mShmBackupBuffer[i] = nullptr;
+ }
+}
+
+WindowSurfaceWayland::~WindowSurfaceWayland() {
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ if (mBufferPendingCommit) {
+ NS_WARNING("Deleted WindowSurfaceWayland with a pending commit!");
+ }
+
+ if (mFrameCallback) {
+ wl_callback_destroy(mFrameCallback);
+ }
+
+ mWaylandBuffer = nullptr;
+
+ for (int i = 0; i < BACK_BUFFER_NUM; i++) {
+ if (mShmBackupBuffer[i]) {
+ delete mShmBackupBuffer[i];
+ }
+ }
+}
+
+WindowBackBuffer* WindowSurfaceWayland::CreateWaylandBuffer(int aWidth,
+ int aHeight) {
+ int availableBuffer;
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ if (!mShmBackupBuffer[availableBuffer]) {
+ break;
+ }
+ }
+
+ // There isn't any free slot for additional buffer.
+ if (availableBuffer == BACK_BUFFER_NUM) {
+ return nullptr;
+ }
+
+ WindowBackBuffer* buffer = new WindowBackBuffer(this);
+ if (!buffer->Create(aWidth, aHeight)) {
+ delete buffer;
+ return nullptr;
+ }
+
+ mShmBackupBuffer[availableBuffer] = buffer;
+ return buffer;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::WaylandBufferFindAvailable(
+ int aWidth, int aHeight) {
+ int availableBuffer;
+ // Try to find a buffer which matches the size
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached() &&
+ buffer->IsMatchingSize(aWidth, aHeight)) {
+ return buffer;
+ }
+ }
+
+ // Try to find any buffer
+ for (availableBuffer = 0; availableBuffer < BACK_BUFFER_NUM;
+ availableBuffer++) {
+ WindowBackBuffer* buffer = mShmBackupBuffer[availableBuffer];
+ if (buffer && !buffer->IsAttached()) {
+ return buffer;
+ }
+ }
+
+ return nullptr;
+}
+
+WindowBackBuffer* WindowSurfaceWayland::SetNewWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::NewWaylandBuffer [%p] Requested buffer [%d "
+ "x %d]\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ mWaylandBuffer =
+ WaylandBufferFindAvailable(mWLBufferRect.width, mWLBufferRect.height);
+ if (mWaylandBuffer) {
+ if (!mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return nullptr;
+ }
+ return mWaylandBuffer;
+ }
+
+ mWaylandBuffer =
+ CreateWaylandBuffer(mWLBufferRect.width, mWLBufferRect.height);
+ return mWaylandBuffer;
+}
+
+// Recent
+WindowBackBuffer* WindowSurfaceWayland::GetWaylandBuffer() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::GetWaylandBuffer [%p] Requested buffer [%d "
+ "x %d] can switch %d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height,
+ mCanSwitchWaylandBuffer));
+
+ // There's no buffer created yet, create a new one for partial screen updates.
+ if (!mWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+
+ if (mWaylandBuffer->IsAttached()) {
+ if (mCanSwitchWaylandBuffer) {
+ return SetNewWaylandBuffer();
+ }
+ LOGWAYLAND((" Buffer is attached and we can't switch, return null\n"));
+ return nullptr;
+ }
+
+ if (mWaylandBuffer->IsMatchingSize(mWLBufferRect.width,
+ mWLBufferRect.height)) {
+ LOGWAYLAND((" Size is ok, use the buffer [%d x %d]\n",
+ mWLBufferRect.width, mWLBufferRect.height));
+ return mWaylandBuffer;
+ }
+
+ if (mCanSwitchWaylandBuffer) {
+ // Reuse existing buffer
+ LOGWAYLAND((" Reuse buffer with resize [%d x %d]\n", mWLBufferRect.width,
+ mWLBufferRect.height));
+ if (mWaylandBuffer->Resize(mWLBufferRect.width, mWLBufferRect.height)) {
+ return mWaylandBuffer;
+ }
+ // OOM here, just return null to skip this frame.
+ return nullptr;
+ }
+
+ LOGWAYLAND(
+ (" Buffer size does not match, requested %d x %d got %d x%d, return "
+ "null.\n",
+ mWaylandBuffer->GetWidth(), mWaylandBuffer->GetHeight(),
+ mWLBufferRect.width, mWLBufferRect.height));
+ return nullptr;
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockWaylandBuffer() {
+ // Allocated wayland buffer can't be bigger than mozilla widget size.
+ LayoutDeviceIntRegion region;
+ region.And(mLockedScreenRect, mWindow->GetMozContainerSize());
+ mWLBufferRect = LayoutDeviceIntRect(region.GetBounds());
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::LockWaylandBuffer [%p] Requesting buffer %d x "
+ "%d\n",
+ (void*)this, mWLBufferRect.width, mWLBufferRect.height));
+
+ WindowBackBuffer* buffer = GetWaylandBuffer();
+ LOGWAYLAND(("WindowSurfaceWayland::LockWaylandBuffer [%p] Got buffer %p\n",
+ (void*)this, (void*)buffer));
+
+ if (!buffer) {
+ if (mLastCommitTime && (g_get_monotonic_time() / 1000) - mLastCommitTime >
+ COMPOSITING_TIMEOUT) {
+ NS_WARNING(
+ "Slow response from Wayland compositor, visual glitches ahead.");
+ }
+ return nullptr;
+ }
+
+ mCanSwitchWaylandBuffer = false;
+
+ if (mBufferNeedsClear) {
+ buffer->Clear();
+ mBufferNeedsClear = false;
+ }
+
+ return buffer->Lock();
+}
+
+void WindowSurfaceWayland::UnlockWaylandBuffer() {
+ LOGWAYLAND(("WindowSurfaceWayland::UnlockWaylandBuffer [%p]\n", (void*)this));
+ mWaylandBuffer->Unlock();
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::LockImageSurface(
+ const gfx::IntSize& aLockSize) {
+ if (!mImageSurface || !(aLockSize <= mImageSurface->GetSize())) {
+ mImageSurface = gfx::Factory::CreateDataSourceSurface(
+ aLockSize, WindowBackBuffer::GetSurfaceFormat());
+ }
+ gfx::DataSourceSurface::MappedSurface map = {nullptr, 0};
+ if (!mImageSurface->Map(gfx::DataSourceSurface::READ_WRITE, &map)) {
+ return nullptr;
+ }
+ return gfxPlatform::CreateDrawTargetForData(
+ map.mData, mImageSurface->GetSize(), map.mStride,
+ WindowBackBuffer::GetSurfaceFormat());
+}
+
+static bool IsWindowFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ if (aUpdatedRegion.GetNumRects() > 1) return false;
+
+ gfx::IntRect rect = aUpdatedRegion.RectIter().Get().ToUnknownRect();
+ return (rect.x == 0 && rect.y == 0 && aScreenRect.width == rect.width &&
+ aScreenRect.height == rect.height);
+}
+
+static bool IsPopupFullScreenUpdate(
+ LayoutDeviceIntRect& aScreenRect,
+ const LayoutDeviceIntRegion& aUpdatedRegion) {
+ // We know that popups can be drawn from two parts; a panel and an arrow.
+ // Assume we redraw whole popups when we have two rects and bounding
+ // box is equal to window borders.
+ if (aUpdatedRegion.GetNumRects() > 2) return false;
+
+ gfx::IntRect lockSize = aUpdatedRegion.GetBounds().ToUnknownRect();
+ return (lockSize.x == 0 && lockSize.y == 0 &&
+ aScreenRect.width == lockSize.width &&
+ aScreenRect.height == lockSize.height);
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceWayland::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mWindow->WindowType() == eWindowType_invisible) {
+ return nullptr;
+ }
+
+ // Wait until all pending events are processed. There may be queued
+ // wl_buffer release event which releases our wl_buffer for further rendering.
+ mWaylandDisplay->WaitForSyncEnd();
+
+ // Lock the surface *after* WaitForSyncEnd() call as is can fire
+ // FlushPendingCommits().
+ MutexAutoLock lock(mSurfaceLock);
+
+ // Disable all commits (from potential frame callback/delayed handlers)
+ // until next WindowSurfaceWayland::Commit() call.
+ mBufferCommitAllowed = false;
+
+ LayoutDeviceIntRect lockedScreenRect = mWindow->GetBounds();
+ // The window bounds of popup windows contains relative position to
+ // the transient window. We need to remove that effect because by changing
+ // position of the popup window the buffer has not changed its size.
+ lockedScreenRect.x = lockedScreenRect.y = 0;
+ gfx::IntRect lockSize = aRegion.GetBounds().ToUnknownRect();
+
+ bool isTransparentPopup =
+ mWindow->IsWaylandPopup() &&
+ (eTransparencyTransparent == mWindow->GetTransparencyMode());
+
+ bool windowRedraw = isTransparentPopup
+ ? IsPopupFullScreenUpdate(lockedScreenRect, aRegion)
+ : IsWindowFullScreenUpdate(lockedScreenRect, aRegion);
+ if (windowRedraw) {
+ // Clear buffer when we (re)draw new transparent popup window,
+ // otherwise leave it as-is, mBufferNeedsClear can be set from previous
+ // (already pending) commits which are cached now.
+ mBufferNeedsClear =
+ mWindow->WaylandSurfaceNeedsClear() || isTransparentPopup;
+
+ // Store info that we can switch WaylandBuffer when we flush
+ // mImageSurface / mDelayedImageCommits. Don't clear it - it's cleared
+ // at LockWaylandBuffer() when we actualy switch the buffer.
+ mCanSwitchWaylandBuffer = true;
+
+ // We do full buffer repaint so clear our cached drawings.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ // Store info that we can safely invalidate whole screen.
+ mWaylandFullscreenDamage = true;
+ } else {
+ // We can switch buffer if there isn't any content committed
+ // to active buffer.
+ mCanSwitchWaylandBuffer = !mBufferPendingCommit;
+ }
+
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Lock [%p] [%d,%d] -> [%d x %d] rects %d "
+ "windowSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ aRegion.GetNumRects(), lockedScreenRect.width, lockedScreenRect.height));
+ LOGWAYLAND((" nsWindow = %p\n", mWindow));
+ LOGWAYLAND((" isPopup = %d\n", mWindow->IsWaylandPopup()));
+ LOGWAYLAND((" isTransparentPopup = %d\n", isTransparentPopup));
+ LOGWAYLAND((" IsPopupFullScreenUpdate = %d\n",
+ IsPopupFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" IsWindowFullScreenUpdate = %d\n",
+ IsWindowFullScreenUpdate(lockedScreenRect, aRegion)));
+ LOGWAYLAND((" mBufferNeedsClear = %d\n", mBufferNeedsClear));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" windowRedraw = %d\n", windowRedraw));
+
+ if (!(mLockedScreenRect == lockedScreenRect)) {
+ LOGWAYLAND((" screen size changed\n"));
+
+ // Screen (window) size changed and we still have some painting pending
+ // for the last window size. That can happen when window is resized.
+ // We can't commit them any more as they're for former window size, so
+ // scratch them.
+ mDelayedImageCommits.Clear();
+ mWaylandBufferDamage.SetEmpty();
+
+ if (!windowRedraw) {
+ NS_WARNING("Partial screen update when window is resized!");
+ // This should not happen. Screen size changed but we got only
+ // partal screen update instead of whole screen. Discard this painting
+ // as it produces artifacts.
+ return nullptr;
+ }
+ mLockedScreenRect = lockedScreenRect;
+ }
+
+ // We can draw directly only when widget has the same size as wl_buffer
+ LayoutDeviceIntRect size = mWindow->GetMozContainerSize();
+ mDrawToWaylandBufferDirectly = (size.width >= mLockedScreenRect.width &&
+ size.height >= mLockedScreenRect.height);
+
+ // We can draw directly only when we redraw significant part of the window
+ // to avoid flickering or do only fullscreen updates in smooth mode.
+ if (mDrawToWaylandBufferDirectly) {
+ mDrawToWaylandBufferDirectly =
+ mSmoothRendering
+ ? windowRedraw
+ : (windowRedraw || (lockSize.width * 2 > lockedScreenRect.width &&
+ lockSize.height * 2 > lockedScreenRect.height));
+ }
+ if (!mDrawToWaylandBufferDirectly) {
+ // Don't switch wl_buffers when we cache drawings.
+ mCanSwitchWaylandBuffer = false;
+ LOGWAYLAND((" Indirect drawing, mCanSwitchWaylandBuffer = %d\n",
+ mCanSwitchWaylandBuffer));
+ }
+
+ if (mDrawToWaylandBufferDirectly) {
+ LOGWAYLAND((" Direct drawing\n"));
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (dt) {
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock");
+#endif
+ if (!windowRedraw) {
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Lock-after-commit");
+#endif
+ }
+ mBufferPendingCommit = true;
+ return dt.forget();
+ }
+ }
+
+ // We do indirect drawing because there isn't any front buffer available.
+ // Do indirect drawing to mImageSurface which is commited to wayland
+ // wl_buffer by DrawDelayedImageCommits() later.
+ mDrawToWaylandBufferDirectly = false;
+
+ LOGWAYLAND((" Indirect drawing.\n"));
+ return LockImageSurface(gfx::IntSize(lockSize.XMost(), lockSize.YMost()));
+}
+
+bool WindowImageSurface::OverlapsSurface(
+ class WindowImageSurface& aBottomSurface) {
+ return mUpdateRegion.Contains(aBottomSurface.mUpdateRegion);
+}
+
+void WindowImageSurface::DrawToTarget(
+ gfx::DrawTarget* aDest, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = mUpdateRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowImageSurface::DrawToTarget\n"));
+ LOGWAYLAND((" rects num %d\n", mUpdateRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+ for (auto iter = mUpdateRegion.RectIter(); !iter.Done(); iter.Next()) {
+ gfx::IntRect r(iter.Get().ToUnknownRect());
+ LOGWAYLAND(
+ (" draw rect [%d,%d] -> [%d x %d]\n", r.x, r.y, r.width, r.height));
+ aDest->CopySurface(mImageSurface, r, gfx::IntPoint(r.x, r.y));
+ }
+ aWaylandBufferDamage.OrWith(mUpdateRegion);
+}
+
+WindowImageSurface::WindowImageSurface(
+ gfx::DataSourceSurface* aImageSurface,
+ const LayoutDeviceIntRegion& aUpdateRegion)
+ : mImageSurface(aImageSurface), mUpdateRegion(aUpdateRegion) {}
+
+void WindowSurfaceWayland::DrawDelayedImageCommits(
+ gfx::DrawTarget* aDrawTarget, LayoutDeviceIntRegion& aWaylandBufferDamage) {
+ unsigned int imagesNum = mDelayedImageCommits.Length();
+ LOGWAYLAND(("WindowSurfaceWayland::DrawDelayedImageCommits [%p] len %d\n",
+ (void*)this, imagesNum));
+ for (unsigned int i = 0; i < imagesNum; i++) {
+ mDelayedImageCommits[i].DrawToTarget(aDrawTarget, aWaylandBufferDamage);
+ }
+ mDelayedImageCommits.Clear();
+}
+
+void WindowSurfaceWayland::CacheImageSurface(
+ const LayoutDeviceIntRegion& aRegion) {
+#ifdef MOZ_LOGGING
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(("WindowSurfaceWayland::CacheImageSurface [%p]\n", (void*)this));
+ LOGWAYLAND((" rects num %d\n", aRegion.GetNumRects()));
+ LOGWAYLAND((" bounds [ %d, %d] -> [%d x %d]\n", bounds.x, bounds.y,
+ bounds.width, bounds.height));
+#endif
+
+ mImageSurface->Unmap();
+ WindowImageSurface surf = WindowImageSurface(mImageSurface, aRegion);
+
+ if (mDelayedImageCommits.Length()) {
+ auto lastSurf = mDelayedImageCommits.PopLastElement();
+ if (surf.OverlapsSurface(lastSurf)) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect size =
+ lastSurf.GetUpdateRegion()->GetBounds().ToUnknownRect();
+ LOGWAYLAND((" removing [ %d, %d] -> [%d x %d]\n", size.x, size.y,
+ size.width, size.height));
+ }
+#endif
+ } else {
+ mDelayedImageCommits.AppendElement(lastSurf);
+ }
+ }
+
+ mDelayedImageCommits.AppendElement(surf);
+ // mImageSurface is owned by mDelayedImageCommits
+ mImageSurface = nullptr;
+
+ LOGWAYLAND(
+ (" There's %d cached images\n", int(mDelayedImageCommits.Length())));
+}
+
+bool WindowSurfaceWayland::CommitImageCacheToWaylandBuffer() {
+ if (!mDelayedImageCommits.Length()) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mDrawToWaylandBufferDirectly);
+
+ RefPtr<gfx::DrawTarget> dt = LockWaylandBuffer();
+ if (!dt) {
+ return false;
+ }
+
+ LOGWAYLAND((" Flushing %ld cached WindowImageSurfaces to Wayland buffer\n",
+ long(mDelayedImageCommits.Length())));
+
+ DrawDelayedImageCommits(dt, mWaylandBufferDamage);
+ UnlockWaylandBuffer();
+
+ return true;
+}
+
+void WindowSurfaceWayland::FlushPendingCommits() {
+ MutexAutoLock lock(mSurfaceLock);
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+// When a new window is created we may not have a valid wl_surface
+// for drawing (Gtk haven't created it yet). All commits are queued
+// and FlushPendingCommitsLocked() is called by timer when wl_surface is ready
+// for drawing.
+static int WaylandBufferFlushPendingCommits(void* data) {
+ WindowSurfaceWayland* aSurface = static_cast<WindowSurfaceWayland*>(data);
+ aSurface->FlushPendingCommits();
+ return true;
+}
+
+bool WindowSurfaceWayland::FlushPendingCommitsLocked() {
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FlushPendingCommitsLocked [%p]\n", (void*)this));
+ LOGWAYLAND(
+ (" mDrawToWaylandBufferDirectly = %d\n", mDrawToWaylandBufferDirectly));
+ LOGWAYLAND((" mCanSwitchWaylandBuffer = %d\n", mCanSwitchWaylandBuffer));
+ LOGWAYLAND((" mFrameCallback = %p\n", mFrameCallback));
+ LOGWAYLAND((" mLastCommittedSurface = %p\n", mLastCommittedSurface));
+ LOGWAYLAND((" mBufferPendingCommit = %d\n", mBufferPendingCommit));
+ LOGWAYLAND((" mBufferCommitAllowed = %d\n", mBufferCommitAllowed));
+
+ if (!mBufferCommitAllowed) {
+ return false;
+ }
+
+ if (CommitImageCacheToWaylandBuffer()) {
+ mBufferPendingCommit = true;
+ }
+
+ // There's nothing to do here
+ if (!mBufferPendingCommit) {
+ return false;
+ }
+
+ MOZ_ASSERT(!mWaylandBuffer->IsAttached(),
+ "We can't draw to attached wayland buffer!");
+
+ MozContainer* container = mWindow->GetMozContainer();
+ wl_surface* waylandSurface = moz_container_wayland_surface_lock(container);
+ if (!waylandSurface) {
+ LOGWAYLAND((" [%p] mWindow->GetWaylandSurface() failed, delay commit.\n",
+ (void*)this));
+
+ // Target window is not created yet - delay the commit. This can happen only
+ // when the window is newly created and there's no active
+ // frame callback pending.
+ MOZ_ASSERT(!mFrameCallback || waylandSurface != mLastCommittedSurface,
+ "Missing wayland surface at frame callback!");
+
+ if (!mSurfaceReadyTimerID) {
+ mSurfaceReadyTimerID = g_timeout_add(
+ EVENT_LOOP_DELAY, &WaylandBufferFlushPendingCommits, this);
+ }
+ return true;
+ }
+ if (mSurfaceReadyTimerID) {
+ g_source_remove(mSurfaceReadyTimerID);
+ mSurfaceReadyTimerID = 0;
+ }
+
+ auto unlockContainer = MakeScopeExit([&] {
+ moz_container_wayland_surface_unlock(container, &waylandSurface);
+ });
+
+ wl_proxy_set_queue((struct wl_proxy*)waylandSurface,
+ mWaylandDisplay->GetEventQueue());
+
+ // We have an active frame callback request so handle it.
+ if (mFrameCallback) {
+ if (waylandSurface == mLastCommittedSurface) {
+ LOGWAYLAND((" [%p] wait for frame callback.\n", (void*)this));
+ // We have an active frame callback pending from our recent surface.
+ // It means we should defer the commit to FrameCallbackHandler().
+ return true;
+ }
+ // If our stored wl_surface does not match the actual one it means the frame
+ // callback is no longer active and we should release it.
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+ mLastCommittedSurface = nullptr;
+ }
+
+ if (mWaylandFullscreenDamage) {
+ LOGWAYLAND((" wl_surface_damage full screen\n"));
+ wl_surface_damage(waylandSurface, 0, 0, INT_MAX, INT_MAX);
+ } else {
+ for (auto iter = mWaylandBufferDamage.RectIter(); !iter.Done();
+ iter.Next()) {
+ mozilla::LayoutDeviceIntRect r = iter.Get();
+ LOGWAYLAND((" wl_surface_damage_buffer [%d, %d] -> [%d, %d]\n", r.x,
+ r.y, r.width, r.height));
+ wl_surface_damage_buffer(waylandSurface, r.x, r.y, r.width, r.height);
+ }
+ }
+
+#if MOZ_LOGGING
+ mWaylandBuffer->DumpToFile("Commit");
+#endif
+
+ // Clear all back buffer damage as we're committing
+ // all requested regions.
+ mWaylandFullscreenDamage = false;
+ mWaylandBufferDamage.SetEmpty();
+
+ mFrameCallback = wl_surface_frame(waylandSurface);
+ wl_callback_add_listener(mFrameCallback, &frame_listener, this);
+
+ mWaylandBuffer->Attach(waylandSurface);
+ mLastCommittedSurface = waylandSurface;
+ mLastCommitTime = g_get_monotonic_time() / 1000;
+
+ // There's no pending commit, all changes are sent to compositor.
+ mBufferPendingCommit = false;
+
+ return true;
+}
+
+void WindowSurfaceWayland::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+#ifdef MOZ_LOGGING
+ {
+ gfx::IntRect lockSize = aInvalidRegion.GetBounds().ToUnknownRect();
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::Commit [%p] damage size [%d, %d] -> [%d x %d]"
+ "screenSize [%d x %d]\n",
+ (void*)this, lockSize.x, lockSize.y, lockSize.width, lockSize.height,
+ mLockedScreenRect.width, mLockedScreenRect.height));
+ LOGWAYLAND((" mDrawToWaylandBufferDirectly = %d\n",
+ mDrawToWaylandBufferDirectly));
+ }
+#endif
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ if (mDrawToWaylandBufferDirectly) {
+ MOZ_ASSERT(mWaylandBuffer->IsLocked());
+ mWaylandBufferDamage.OrWith(aInvalidRegion);
+ UnlockWaylandBuffer();
+ } else {
+ CacheImageSurface(aInvalidRegion);
+ }
+
+ mBufferCommitAllowed = true;
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+void WindowSurfaceWayland::FrameCallbackHandler() {
+ MOZ_ASSERT(mFrameCallback != nullptr,
+ "FrameCallbackHandler() called without valid frame callback!");
+ MOZ_ASSERT(mLastCommittedSurface != nullptr,
+ "FrameCallbackHandler() called without valid wl_surface!");
+ LOGWAYLAND(
+ ("WindowSurfaceWayland::FrameCallbackHandler [%p]\n", (void*)this));
+
+ MutexAutoLock lock(mSurfaceLock);
+
+ wl_callback_destroy(mFrameCallback);
+ mFrameCallback = nullptr;
+
+ if (FlushPendingCommitsLocked()) {
+ mWaylandDisplay->QueueSyncBegin();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceWayland.h b/widget/gtk/WindowSurfaceWayland.h
new file mode 100644
index 0000000000..b34a9a7036
--- /dev/null
+++ b/widget/gtk/WindowSurfaceWayland.h
@@ -0,0 +1,269 @@
+/* -*- 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_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H
+
+#include <prthread.h>
+#include "gfxImageSurface.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "nsWaylandDisplay.h"
+#include "nsWindow.h"
+#include "WindowSurface.h"
+#include "mozilla/Mutex.h"
+
+#define BACK_BUFFER_NUM 3
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceWayland;
+
+// Allocates and owns shared memory for Wayland drawing surface
+class WaylandShmPool {
+ public:
+ bool Create(RefPtr<nsWaylandDisplay> aWaylandDisplay, int aSize);
+ void Release();
+ wl_shm_pool* GetShmPool() { return mShmPool; };
+ void* GetImageData() { return mImageData; };
+ void SetImageDataFromPool(class WaylandShmPool* aSourcePool,
+ int aImageDataSize);
+ WaylandShmPool();
+ ~WaylandShmPool();
+
+ private:
+ wl_shm_pool* mShmPool;
+ int mShmPoolFd;
+ int mAllocatedSize;
+ void* mImageData;
+};
+
+// Holds actual graphics data for wl_surface
+class WindowBackBuffer {
+ public:
+ explicit WindowBackBuffer(WindowSurfaceWayland* aWindowSurfaceWayland);
+ ~WindowBackBuffer();
+
+ already_AddRefed<gfx::DrawTarget> Lock();
+ bool IsLocked() { return mIsLocked; };
+ void Unlock() { mIsLocked = false; };
+
+ void Attach(wl_surface* aSurface);
+ void Detach(wl_buffer* aBuffer);
+ bool IsAttached() { return mAttached; }
+
+ void Clear();
+ bool Create(int aWidth, int aHeight);
+ bool Resize(int aWidth, int aHeight);
+ bool SetImageDataFromBuffer(class WindowBackBuffer* aSourceBuffer);
+
+ int GetWidth() { return mWidth; };
+ int GetHeight() { return mHeight; };
+
+ wl_buffer* GetWlBuffer() { return mWLBuffer; };
+
+ bool IsMatchingSize(int aWidth, int aHeight) {
+ return aWidth == GetWidth() && aHeight == GetHeight();
+ }
+ bool IsMatchingSize(class WindowBackBuffer* aBuffer) {
+ return aBuffer->IsMatchingSize(GetWidth(), GetHeight());
+ }
+ static gfx::SurfaceFormat GetSurfaceFormat() { return mFormat; }
+
+#ifdef MOZ_LOGGING
+ void DumpToFile(const char* aHint);
+#endif
+
+ RefPtr<nsWaylandDisplay> GetWaylandDisplay();
+
+ private:
+ void ReleaseWLBuffer();
+
+ static gfx::SurfaceFormat mFormat;
+ WindowSurfaceWayland* mWindowSurfaceWayland;
+
+ // WaylandShmPool provides actual shared memory we draw into
+ WaylandShmPool mShmPool;
+
+#ifdef MOZ_LOGGING
+ static int mDumpSerial;
+ static char* mDumpDir;
+#endif
+
+ // wl_buffer is a wayland object that encapsulates the shared memory
+ // and passes it to wayland compositor by wl_surface object.
+ wl_buffer* mWLBuffer;
+ int mWidth;
+ int mHeight;
+ bool mAttached;
+ bool mIsLocked;
+};
+
+class WindowImageSurface {
+ public:
+ void DrawToTarget(gfx::DrawTarget* aDest,
+ LayoutDeviceIntRegion& aWaylandBufferDamage);
+ WindowImageSurface(gfx::DataSourceSurface* aImageSurface,
+ const LayoutDeviceIntRegion& aUpdateRegion);
+ bool OverlapsSurface(class WindowImageSurface& aBottomSurface);
+
+ const LayoutDeviceIntRegion* GetUpdateRegion() { return &mUpdateRegion; };
+
+ private:
+ RefPtr<gfx::DataSourceSurface> mImageSurface;
+ const LayoutDeviceIntRegion mUpdateRegion;
+};
+
+// WindowSurfaceWayland is an abstraction for wl_surface
+// and related management
+class WindowSurfaceWayland : public WindowSurface {
+ public:
+ explicit WindowSurfaceWayland(nsWindow* aWindow);
+ ~WindowSurfaceWayland();
+
+ // 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<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) final;
+
+ // It's called from wayland compositor when there's the right
+ // time to send wl_buffer to display. It's no-op if there's no
+ // queued commits.
+ void FrameCallbackHandler();
+
+ // Try to commit all queued drawings to Wayland compositor. This is usually
+ // called from other routines but can be used to explicitly flush
+ // all drawings as we do when wl_buffer is released
+ // (see WindowBackBufferShm::Detach() for instance).
+ void FlushPendingCommits();
+
+ RefPtr<nsWaylandDisplay> GetWaylandDisplay() { return mWaylandDisplay; };
+
+ // Image cache mode can be set by widget.wayland_cache_mode
+ typedef enum {
+ // Cache and clip all drawings, default. It's slowest
+ // but also without any rendered artifacts.
+ CACHE_ALL = 0,
+ // Cache drawing only when back buffer is missing. May produce
+ // some rendering artifacts and flickering when partial screen update
+ // is rendered.
+ CACHE_MISSING = 1,
+ // Don't cache anything, draw only when back buffer is available.
+ CACHE_NONE = 2
+ } RenderingCacheMode;
+
+ private:
+ WindowBackBuffer* GetWaylandBuffer();
+ WindowBackBuffer* SetNewWaylandBuffer();
+ WindowBackBuffer* CreateWaylandBuffer(int aWidth, int aHeight);
+ WindowBackBuffer* WaylandBufferFindAvailable(int aWidth, int aHeight);
+
+ already_AddRefed<gfx::DrawTarget> LockWaylandBuffer();
+ void UnlockWaylandBuffer();
+
+ already_AddRefed<gfx::DrawTarget> LockImageSurface(
+ const gfx::IntSize& aLockSize);
+
+ void CacheImageSurface(const LayoutDeviceIntRegion& aRegion);
+ bool CommitImageCacheToWaylandBuffer();
+
+ void DrawDelayedImageCommits(gfx::DrawTarget* aDrawTarget,
+ LayoutDeviceIntRegion& aWaylandBufferDamage);
+ // Return true if we need to sync Wayland events after this call.
+ bool FlushPendingCommitsLocked();
+
+ // TODO: Do we need to hold a reference to nsWindow object?
+ nsWindow* mWindow;
+ // Buffer screen rects helps us understand if we operate on
+ // the same window size as we're called on WindowSurfaceWayland::Lock().
+ // mLockedScreenRect is window size when our wayland buffer was allocated.
+ LayoutDeviceIntRect mLockedScreenRect;
+
+ // mWLBufferRect is an intersection of mozcontainer widgetsize and
+ // mLockedScreenRect size. It can be different than mLockedScreenRect
+ // during resize when mBounds are updated immediately but actual
+ // GtkWidget size is updated asynchronously (see Bug 1489463).
+ LayoutDeviceIntRect mWLBufferRect;
+ RefPtr<nsWaylandDisplay> mWaylandDisplay;
+
+ // Actual buffer (backed by wl_buffer) where all drawings go into.
+ // Drawn areas are stored at mWaylandBufferDamage and if there's
+ // any uncommited drawings which needs to be send to wayland compositor
+ // the mBufferPendingCommit is set.
+ WindowBackBuffer* mWaylandBuffer;
+ WindowBackBuffer* mShmBackupBuffer[BACK_BUFFER_NUM];
+
+ // When mWaylandFullscreenDamage we invalidate whole surface,
+ // otherwise partial screen updates (mWaylandBufferDamage) are used.
+ bool mWaylandFullscreenDamage;
+ LayoutDeviceIntRegion mWaylandBufferDamage;
+
+ // After every commit to wayland compositor a frame callback is requested.
+ // Any next commit to wayland compositor will happen when frame callback
+ // comes from wayland compositor back as it's the best time to do the commit.
+ wl_callback* mFrameCallback;
+ wl_surface* mLastCommittedSurface;
+
+ // Cached drawings. If we can't get WaylandBuffer (wl_buffer) at
+ // WindowSurfaceWayland::Lock() we direct gecko rendering to
+ // mImageSurface.
+ // If we can't get WaylandBuffer at WindowSurfaceWayland::Commit()
+ // time, mImageSurface is moved to mDelayedImageCommits which
+ // holds all cached drawings.
+ // mDelayedImageCommits can be drawn by FrameCallbackHandler()
+ // or when WaylandBuffer is detached.
+ RefPtr<gfx::DataSourceSurface> mImageSurface;
+ AutoTArray<WindowImageSurface, 30> mDelayedImageCommits;
+
+ int64_t mLastCommitTime;
+
+ // Indicates that we don't have any cached drawings at mDelayedImageCommits
+ // and WindowSurfaceWayland::Lock() returned WaylandBuffer to gecko
+ // to draw into.
+ bool mDrawToWaylandBufferDirectly;
+
+ // Set when our cached drawings (mDelayedImageCommits) contains
+ // full screen damage. That means we can safely switch WaylandBuffer
+ // at LockWaylandBuffer().
+ bool mCanSwitchWaylandBuffer;
+
+ // Set when actual WaylandBuffer contains drawings which are not send to
+ // wayland compositor yet.
+ bool mBufferPendingCommit;
+
+ // We can't send WaylandBuffer (wl_buffer) to compositor when gecko
+ // is rendering into it (i.e. between WindowSurfaceWayland::Lock() /
+ // WindowSurfaceWayland::Commit()).
+ // Thus we use mBufferCommitAllowed to disable commit by
+ // FlushPendingCommits().
+ bool mBufferCommitAllowed;
+
+ // We need to clear WaylandBuffer when entire transparent window is repainted.
+ // This typically apply to popup windows.
+ bool mBufferNeedsClear;
+
+ // Cache all drawings except fullscreen updates.
+ // Avoid any rendering artifacts for significant performance penality.
+ bool mSmoothRendering;
+
+ gint mSurfaceReadyTimerID;
+ mozilla::Mutex mSurfaceLock;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_WAYLAND_H
diff --git a/widget/gtk/WindowSurfaceX11.cpp b/widget/gtk/WindowSurfaceX11.cpp
new file mode 100644
index 0000000000..a32cc12e18
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.cpp
@@ -0,0 +1,50 @@
+/* -*- 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"
+#include "X11UndefineNone.h"
+
+namespace mozilla {
+namespace 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 widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11.h b/widget/gtk/WindowSurfaceX11.h
new file mode 100644
index 0000000000..d297ec6b66
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11.h
@@ -0,0 +1,40 @@
+/* -*- 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>
+
+namespace mozilla {
+namespace 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 widget
+} // namespace mozilla
+
+#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..1e4d28915f
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.cpp
@@ -0,0 +1,263 @@
+/* -*- 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) {}
+
+WindowSurfaceX11Image::~WindowSurfaceX11Image() {
+ if (mTransparencyBitmap) {
+ delete[] mTransparencyBitmap;
+
+ Display* xDisplay = mWindowSurface->XDisplay();
+ Window xDrawable = mWindowSurface->XDrawable();
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, X11None,
+ ShapeSet);
+ }
+}
+
+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)) {
+#ifdef USE_SKIA
+ backend = gfx::BackendType::SKIA;
+#else
+ backend = gfx::BackendType::CAIRO;
+#endif
+ }
+ 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);
+ 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();
+ gfx::Rect rect(bounds);
+ if (rect.IsEmpty()) {
+ return;
+ }
+
+ uint32_t numRects = aInvalidRegion.GetNumRects();
+ if (numRects != 1) {
+ 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());
+ }
+
+ if (mIsShaped) {
+ ApplyTransparencyBitmap();
+ }
+
+ dt->DrawSurface(surf, rect, rect);
+
+ if (numRects != 1) {
+ dt->PopClip();
+ }
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceX11Image.h b/widget/gtk/WindowSurfaceX11Image.h
new file mode 100644
index 0000000000..b8b2a33f0e
--- /dev/null
+++ b/widget/gtk/WindowSurfaceX11Image.h
@@ -0,0 +1,48 @@
+/* -*- 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;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_X11_IMAGE_H
diff --git a/widget/gtk/WindowSurfaceXRender.cpp b/widget/gtk/WindowSurfaceXRender.cpp
new file mode 100644
index 0000000000..9f040d9ce3
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.cpp
@@ -0,0 +1,75 @@
+/* -*- 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 "WindowSurfaceXRender.h"
+
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/Types.h"
+#include "gfxPlatform.h"
+
+namespace mozilla {
+namespace widget {
+
+WindowSurfaceXRender::WindowSurfaceXRender(Display* aDisplay, Window aWindow,
+ Visual* aVisual, unsigned int aDepth)
+ : WindowSurfaceX11(aDisplay, aWindow, aVisual, aDepth),
+ mXlibSurface(nullptr),
+ mGC(X11None) {}
+
+WindowSurfaceXRender::~WindowSurfaceXRender() {
+ if (mGC != X11None) {
+ XFreeGC(mDisplay, mGC);
+ }
+}
+
+already_AddRefed<gfx::DrawTarget> WindowSurfaceXRender::Lock(
+ const LayoutDeviceIntRegion& aRegion) {
+ gfx::IntRect bounds = aRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ if (!mXlibSurface || mXlibSurface->CairoStatus() ||
+ !(size <= mXlibSurface->GetSize())) {
+ mXlibSurface = gfxXlibSurface::Create(DefaultScreenOfDisplay(mDisplay),
+ mVisual, size, mWindow);
+ }
+ if (!mXlibSurface || mXlibSurface->CairoStatus()) {
+ return nullptr;
+ }
+
+ return gfxPlatform::CreateDrawTargetForSurface(mXlibSurface, size);
+}
+
+void WindowSurfaceXRender::Commit(const LayoutDeviceIntRegion& aInvalidRegion) {
+ AutoTArray<XRectangle, 32> xrects;
+ xrects.SetCapacity(aInvalidRegion.GetNumRects());
+
+ for (auto iter = aInvalidRegion.RectIter(); !iter.Done(); iter.Next()) {
+ const LayoutDeviceIntRect& r = iter.Get();
+ XRectangle xrect = {(short)r.x, (short)r.y, (unsigned short)r.width,
+ (unsigned short)r.height};
+ xrects.AppendElement(xrect);
+ }
+
+ if (!mGC) {
+ mGC = XCreateGC(mDisplay, mWindow, 0, nullptr);
+ if (!mGC) {
+ NS_WARNING("Couldn't create X11 graphics context for window!");
+ return;
+ }
+ }
+
+ XSetClipRectangles(mDisplay, mGC, 0, 0, xrects.Elements(), xrects.Length(),
+ YXBanded);
+
+ MOZ_ASSERT(mXlibSurface && mXlibSurface->CairoStatus() == 0,
+ "Attempted to commit invalid surface!");
+ gfx::IntRect bounds = aInvalidRegion.GetBounds().ToUnknownRect();
+ gfx::IntSize size(bounds.XMost(), bounds.YMost());
+ XCopyArea(mDisplay, mXlibSurface->XDrawable(), mWindow, mGC, bounds.x,
+ bounds.y, size.width, size.height, bounds.x, bounds.y);
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/WindowSurfaceXRender.h b/widget/gtk/WindowSurfaceXRender.h
new file mode 100644
index 0000000000..8c8e2745eb
--- /dev/null
+++ b/widget/gtk/WindowSurfaceXRender.h
@@ -0,0 +1,37 @@
+/* -*- 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_XRENDER_H
+#define _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
+
+#ifdef MOZ_X11
+
+# include "WindowSurfaceX11.h"
+# include "gfxXlibSurface.h"
+
+namespace mozilla {
+namespace widget {
+
+class WindowSurfaceXRender : public WindowSurfaceX11 {
+ public:
+ WindowSurfaceXRender(Display* aDisplay, Window aWindow, Visual* aVisual,
+ unsigned int aDepth);
+ ~WindowSurfaceXRender();
+
+ already_AddRefed<gfx::DrawTarget> Lock(
+ const LayoutDeviceIntRegion& aRegion) override;
+ void Commit(const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ private:
+ RefPtr<gfxXlibSurface> mXlibSurface;
+ GC mGC;
+};
+
+} // namespace widget
+} // namespace mozilla
+
+#endif // MOZ_X11
+#endif // _MOZILLA_WIDGET_GTK_WINDOW_SURFACE_XRENDER_H
diff --git a/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
new file mode 100644
index 0000000000..4cf3b68f62
--- /dev/null
+++ b/widget/gtk/compat-gtk3/gdk/gdkversionmacros.h
@@ -0,0 +1,32 @@
+/* -*- 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 GDKVERSIONMACROS_WRAPPER_H
+#define GDKVERSIONMACROS_WRAPPER_H
+
+/**
+ * Suppress all GTK3 deprecated warnings as deprecated functions are often
+ * used for GTK2 compatibility.
+ *
+ * GDK_VERSION_MIN_REQUIRED cannot be used to suppress warnings for functions
+ * deprecated in 3.0, but still needs to be set because gdkversionmacros.h
+ * asserts that GDK_VERSION_MAX_ALLOWED >= GDK_VERSION_MIN_REQUIRED and
+ * GDK_VERSION_MIN_REQUIRED >= GDK_VERSION_3_0.
+ *
+ * Setting GDK_DISABLE_DEPRECATION_WARNINGS would also disable
+ * GDK_UNAVAILABLE() warnings, which are useful.
+ */
+
+#define GDK_VERSION_MIN_REQUIRED GDK_VERSION_3_14
+
+#include_next <gdk/gdkversionmacros.h>
+
+#undef GDK_DEPRECATED
+#define GDK_DEPRECATED GDK_AVAILABLE_IN_ALL
+#undef GDK_DEPRECATED_FOR
+#define GDK_DEPRECATED_FOR(f) GDK_AVAILABLE_IN_ALL
+
+#endif /* GDKVERSIONMACROS_WRAPPER_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..3d13c88b4d
--- /dev/null
+++ b/widget/gtk/compat/gdk/gdkx.h
@@ -0,0 +1,46 @@
+/* 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));
+}
+
+#ifndef GDK_IS_X11_DISPLAY
+# define GDK_IS_X11_DISPLAY(a) (true)
+#endif
+
+#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..a65c8e02b0
--- /dev/null
+++ b/widget/gtk/components.conf
@@ -0,0 +1,166 @@
+# -*- 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_AND_SOCKET_PROCESS,
+ },
+ {
+ 'cid': '{c401eb80-f9ea-11d3-bb6f-e732b73ebe7c}',
+ 'contract_ids': ['@mozilla.org/gfx/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': '{0f872c8c-3ee6-46bd-92a2-69652c6b474e}',
+ 'contract_ids': ['@mozilla.org/colorpicker;1'],
+ 'type': 'nsColorPicker',
+ 'headers': ['/widget/gtk/nsColorPicker.h'],
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{bd57cee8-1dd1-11b2-9fe7-95cf4709aea3}',
+ 'contract_ids': ['@mozilla.org/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': '{fc2389b8-c650-4093-9e42-b05e5f0685b7}',
+ 'contract_ids': ['@mozilla.org/widget/image-to-gdk-pixbuf;1'],
+ 'type': 'nsImageToPixbuf',
+ 'headers': ['/widget/gtk/nsImageToPixbuf.h'],
+ },
+ {
+ '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,
+ },
+]
+
+if defined('MOZ_X11'):
+ Classes += [
+ {
+ 'js_name': 'clipboard',
+ 'cid': '{8b5314ba-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/clipboard;1'],
+ 'interfaces': ['nsIClipboard'],
+ 'type': 'nsIClipboard',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ 'overridable': True,
+ },
+ {
+ 'cid': '{77221d5a-1dd2-11b2-8c69-c710f15d2ed5}',
+ 'contract_ids': ['@mozilla.org/widget/clipboardhelper;1'],
+ 'type': 'nsClipboardHelper',
+ 'headers': ['/widget/nsClipboardHelper.h'],
+ },
+ {
+ 'cid': '{8b5314bb-db01-11d2-96ce-0060b0fb9956}',
+ 'contract_ids': ['@mozilla.org/widget/dragservice;1'],
+ 'singleton': True,
+ 'type': 'nsDragService',
+ 'headers': ['/widget/gtk/nsDragService.h'],
+ 'constructor': 'nsDragService::GetInstance',
+ 'processes': ProcessSelector.MAIN_PROCESS_ONLY,
+ },
+ {
+ 'cid': '{d755a760-9f27-11df-0800-200c9a664242}',
+ 'contract_ids': ['@mozilla.org/gfx/info;1'],
+ 'type': 'mozilla::widget::GfxInfo',
+ 'headers': ['/widget/GfxInfoX11.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'],
+ },
+ {
+ 'cid': '{06beec76-a183-4d9f-85dd-085f26da565a}',
+ 'contract_ids': ['@mozilla.org/widget/printdialog-service;1'],
+ 'type': 'nsPrintDialogServiceGTK',
+ 'headers': ['/widget/gtk/nsPrintDialogGTK.h'],
+ 'init_method': 'Init',
+ },
+ {
+ 'cid': '{2f977d53-5485-11d4-87e2-0010a4e75ef2}',
+ 'contract_ids': ['@mozilla.org/gfx/printsession;1'],
+ 'type': 'nsPrintSession',
+ 'headers': ['/widget/nsPrintSession.h'],
+ 'init_method': 'Init',
+ },
+ {
+ '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'],
+ },
+ ]
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/gtk3drawing.cpp b/widget/gtk/gtk3drawing.cpp
new file mode 100644
index 0000000000..c8a39bfd5c
--- /dev/null
+++ b/widget/gtk/gtk3drawing.cpp
@@ -0,0 +1,3214 @@
+/* -*- 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 <math.h>
+#include <dlfcn.h>
+
+static gboolean checkbox_check_state;
+static gboolean notebook_has_tab_gap;
+
+static ScrollbarGTKMetrics sScrollbarMetrics[2];
+static ScrollbarGTKMetrics sActiveScrollbarMetrics[2];
+static ToggleGTKMetrics sCheckboxMetrics;
+static ToggleGTKMetrics sRadioMetrics;
+static ToggleGTKMetrics sMenuRadioMetrics;
+static ToggleGTKMetrics sMenuCheckboxMetrics;
+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-(const GtkBorder& first, const GtkBorder& second) {
+ GtkBorder result;
+ result.left = first.left - second.left;
+ result.right = first.right - second.right;
+ result.top = first.top - second.top;
+ result.bottom = first.bottom - second.bottom;
+ return result;
+}
+
+static GtkBorder operator+(const GtkBorder& first, const GtkBorder& second) {
+ GtkBorder result;
+ result.left = first.left + second.left;
+ result.right = first.right + second.right;
+ result.top = first.top + second.top;
+ result.bottom = first.bottom + second.bottom;
+ return result;
+}
+
+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 gint moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state,
+ GtkTextDirection direction);
+
+static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle);
+
+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;
+ }
+
+ sScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false;
+ sScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false;
+ sActiveScrollbarMetrics[GTK_ORIENTATION_HORIZONTAL].initialized = false;
+ sActiveScrollbarMetrics[GTK_ORIENTATION_VERTICAL].initialized = false;
+ sCheckboxMetrics.initialized = false;
+ sRadioMetrics.initialized = false;
+ sMenuCheckboxMetrics.initialized = false;
+ sMenuRadioMetrics.initialized = false;
+ sToolbarMetrics.initialized = false;
+ sToplevelWindowDecorationSize.initialized = false;
+ sPopupWindowDecorationSize.initialized = false;
+
+ /* This will destroy all of our widgets */
+ ResetWidgetCache();
+}
+
+static gint moz_gtk_get_focus_outline_size(GtkStyleContext* style,
+ gint* focus_h_width,
+ gint* focus_v_width) {
+ GtkBorder border;
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ *focus_h_width = border.left;
+ *focus_v_width = border.top;
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_ENTRY);
+ moz_gtk_get_focus_outline_size(style, focus_h_width, focus_v_width);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_style(style, "horizontal-padding", horizontal_padding,
+ nullptr);
+ return MOZ_GTK_SUCCESS;
+}
+
+gint moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_CHECKMENUITEM);
+ gtk_style_context_get_style(style, "horizontal-padding", horizontal_padding,
+ nullptr);
+ return MOZ_GTK_SUCCESS;
+}
+
+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) == nullptr) {
+ 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 equaly 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));
+
+ bool right = false;
+ 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,
+ right};
+ } else if (button.EqualsLiteral("minimize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE,
+ right};
+ } else if (button.EqualsLiteral("maximize")) {
+ aButtonLayout[activeButtons++] = {MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE,
+ right};
+ }
+ if (activeButtons == aButtonLayout.Length()) {
+ return activeButtons;
+ }
+ }
+ right = true;
+ }
+ return activeButtons;
+}
+
+static void EnsureToolbarMetrics(void) {
+ if (!sToolbarMetrics.initialized) {
+ // 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(mozilla::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_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkTextDirection direction) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BACKGROUND);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width, rect->height);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_button_paint(cairo_t* cr, const GdkRectangle* rect,
+ GtkWidgetState* state, GtkReliefStyle relief,
+ GtkWidget* widget,
+ GtkTextDirection direction) {
+ 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->scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ if (state->isDefault && relief == GTK_RELIEF_NORMAL) {
+ /* 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) {
+ WidgetNodeType buttonWidgetType =
+ (aIconWidgetType == MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE_RESTORE)
+ ? MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE
+ : aIconWidgetType;
+
+ GdkRectangle rect = *aRect;
+ // We need to inset our calculated margin because it also
+ // contains titlebar button spacing.
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(buttonWidgetType);
+ Inset(&rect, metrics->buttonMargin);
+
+ GtkWidget* buttonWidget = GetWidget(buttonWidgetType);
+ moz_gtk_button_paint(cr, &rect, state, relief, buttonWidget, direction);
+
+ GtkWidget* iconWidget =
+ gtk_bin_get_child(GTK_BIN(GetWidget(aIconWidgetType)));
+ cairo_surface_t* surface = GetWidgetIconSurface(iconWidget, state->scale);
+
+ if (surface) {
+ GtkStyleContext* style = gtk_widget_get_style_context(buttonWidget);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ gtk_style_context_save(style);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_set_state(style, state_flags);
+
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(buttonWidgetType);
+
+ /* This is available since Gtk+ 3.10 as well as GtkHeaderBar */
+ static auto sGtkRenderIconSurfacePtr =
+ (void (*)(GtkStyleContext*, cairo_t*, cairo_surface_t*, gdouble,
+ gdouble))dlsym(RTLD_DEFAULT, "gtk_render_icon_surface");
+
+ sGtkRenderIconSurfacePtr(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->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;
+}
+
+static MozGtkSize GetMinContentBox(GtkStyleContext* style) {
+ GtkStateFlags state_flags = gtk_style_context_get_state(style);
+ gint width, height;
+ gtk_style_context_get(style, state_flags, "min-width", &width, "min-height",
+ &height, nullptr);
+ return {width, height};
+}
+
+/**
+ * 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 MozGtkSize GetMinMarginBox(GtkStyleContext* style) {
+ gint width, height;
+ moz_gtk_get_widget_min_size(style, &width, &height);
+ return {width, height};
+}
+
+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 gint moz_gtk_scrollbar_button_paint(cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkScrollbarButtonFlags flags,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+ gint arrow_displacement_x, arrow_displacement_y;
+
+ GtkWidget* scrollbar = GetWidget(flags & MOZ_GTK_STEPPER_VERTICAL
+ ? MOZ_GTK_SCROLLBAR_VERTICAL
+ : MOZ_GTK_SCROLLBAR_HORIZONTAL);
+
+ gtk_widget_set_direction(scrollbar, direction);
+
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_DOWN : ARROW_UP;
+ } else {
+ arrow_angle = (flags & MOZ_GTK_STEPPER_DOWN) ? ARROW_RIGHT : ARROW_LEFT;
+ }
+
+ style = gtk_widget_get_style_context(scrollbar);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BUTTON);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_set_state(style, state_flags);
+ if (arrow_angle == ARROW_RIGHT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ } else if (arrow_angle == ARROW_DOWN) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else if (arrow_angle == ARROW_LEFT) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_TOP);
+ }
+
+ GdkRectangle rect = *aRect;
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ // The "trough-border" is not used since GTK 3.20. The stepper margin
+ // box occupies the full width of the "contents" gadget content box.
+ InsetByMargin(&rect, style);
+ } else {
+ // Scrollbar button has to be inset by trough_border because its DOM
+ // element is filling width of vertical scrollbar's track (or height
+ // in case of horizontal scrollbars).
+ GtkOrientation orientation = flags & MOZ_GTK_STEPPER_VERTICAL
+ ? GTK_ORIENTATION_VERTICAL
+ : GTK_ORIENTATION_HORIZONTAL;
+
+ const ScrollbarGTKMetrics* metrics = GetScrollbarMetrics(orientation);
+ if (flags & MOZ_GTK_STEPPER_VERTICAL) {
+ rect.x += metrics->border.track.left;
+ rect.width = metrics->size.thumb.width;
+ } else {
+ rect.y += metrics->border.track.top;
+ rect.height = metrics->size.thumb.height;
+ }
+ }
+
+ 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);
+
+ arrow_rect.width = rect.width / 2;
+ arrow_rect.height = rect.height / 2;
+
+ gfloat arrow_scaling;
+ gtk_style_context_get_style(style, "arrow-scaling", &arrow_scaling, NULL);
+
+ gdouble arrow_size = MIN(rect.width, rect.height) * arrow_scaling;
+ arrow_rect.x = rect.x + (rect.width - arrow_size) / 2;
+ arrow_rect.y = rect.y + (rect.height - arrow_size) / 2;
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ gtk_style_context_get_style(style, "arrow-displacement-x",
+ &arrow_displacement_x, "arrow-displacement-y",
+ &arrow_displacement_y, NULL);
+
+ arrow_rect.x += arrow_displacement_x;
+ arrow_rect.y += arrow_displacement_y;
+ }
+
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_size);
+
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static void moz_gtk_update_scrollbar_style(GtkStyleContext* style,
+ WidgetNodeType widget,
+ GtkTextDirection direction) {
+ if (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_BOTTOM);
+ } else {
+ if (direction == GTK_TEXT_DIR_LTR) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_RIGHT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_LEFT);
+ } else {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_LEFT);
+ gtk_style_context_remove_class(style, GTK_STYLE_CLASS_RIGHT);
+ }
+ }
+}
+
+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_scrollbar_trough_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GdkRectangle rect = *aRect;
+ GtkStyleContext* style;
+
+ if (gtk_get_minor_version() >= 20) {
+ WidgetNodeType thumb = widget == MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL
+ ? MOZ_GTK_SCROLLBAR_THUMB_VERTICAL
+ : MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ MozGtkSize thumbSize = GetMinMarginBox(GetStyleContext(thumb));
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ MozGtkSize trackSize = GetMinContentBox(style);
+ trackSize.Include(thumbSize);
+ trackSize += GetMarginBorderPadding(style);
+ // Gecko's trough |aRect| fills available breadth, but GTK's trough is
+ // centered in the contents_gadget. The centering here round left
+ // and up, like gtk_box_gadget_allocate_child().
+ if (widget == MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) {
+ rect.x += (rect.width - trackSize.width) / 2;
+ rect.width = trackSize.width;
+ } else {
+ rect.y += (rect.height - trackSize.height) / 2;
+ rect.height = trackSize.height;
+ }
+ } else {
+ style = GetStyleContext(widget, state->scale, direction, state_flags);
+ }
+
+ moz_gtk_draw_styled_frame(style, cr, &rect, state->focused);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scrollbar_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ moz_gtk_update_scrollbar_style(style, widget, direction);
+
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ style = GetStyleContext((widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ ? MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL,
+ state->scale, direction, state_flags);
+ moz_gtk_draw_styled_frame(style, cr, rect, state->focused);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_scrollbar_thumb_paint(WidgetNodeType widget, cairo_t* cr,
+ const GdkRectangle* aRect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ GtkOrientation orientation = (widget == MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL)
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+
+ GdkRectangle rect = *aRect;
+
+ const ScrollbarGTKMetrics* metrics =
+ (state->depressed || state->active || state->inHover)
+ ? GetActiveScrollbarMetrics(orientation)
+ : GetScrollbarMetrics(orientation);
+ Inset(&rect, metrics->margin.thumb);
+
+ gtk_render_slider(style, cr, rect.x, rect.y, rect.width, rect.height,
+ orientation);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_inner_spin_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_SPINBUTTON, state->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->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->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->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->scale, direction, state_flags);
+ gtk_render_slider(style, cr, x, y, thumb_width, thumb_height, flags);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_gripper_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_GRIPPER, state->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);
+ 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->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->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) {
+ // StyleAppearance::FocusOutline
+ int draw_focus_outline_only = state->depressed;
+ GdkRectangle rect = *aRect;
+
+ if (draw_focus_outline_only) {
+ // Inflate the given 'rect' with the focus outline size.
+ gint h, v;
+ moz_gtk_get_focus_outline_size(style, &h, &v);
+ rect.x -= h;
+ rect.width += 2 * h;
+ rect.y -= v;
+ rect.height += 2 * v;
+ } else {
+ 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->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->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->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->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->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->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;
+}
+
+static gint moz_gtk_tree_header_sort_arrow_paint(cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkArrowType arrow_type,
+ GtkTextDirection direction) {
+ GdkRectangle arrow_rect;
+ gdouble arrow_angle;
+ GtkStyleContext* style;
+
+ /* hard code these values */
+ arrow_rect.width = 11;
+ arrow_rect.height = 11;
+ arrow_rect.x = rect->x + (rect->width - arrow_rect.width) / 2;
+ arrow_rect.y = rect->y + (rect->height - arrow_rect.height) / 2;
+ style = GetStyleContext(MOZ_GTK_TREE_HEADER_SORTARROW, state->scale,
+ direction, GetStateFlagsFromGtkWidgetState(state));
+ 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)
+ gtk_render_arrow(style, cr, arrow_angle, arrow_rect.x, arrow_rect.y,
+ arrow_rect.width);
+ 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->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);
+
+ /* 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->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->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;
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_BUTTON_ARROW), rect, &arrow_rect,
+ direction);
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_BUTTON_ARROW, state->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_combo_box_entry_button_paint(cairo_t* cr,
+ GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean input_focus,
+ GtkTextDirection direction) {
+ gint x_displacement, y_displacement;
+ GdkRectangle arrow_rect, real_arrow_rect;
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+
+ GtkWidget* comboBoxEntry = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ moz_gtk_button_paint(cr, rect, state, GTK_RELIEF_NORMAL, comboBoxEntry,
+ direction);
+ calculate_button_inner_rect(comboBoxEntry, rect, &arrow_rect, direction);
+
+ if (state_flags & GTK_STATE_FLAG_ACTIVE) {
+ style = gtk_widget_get_style_context(comboBoxEntry);
+ StyleContextSetScale(style, state->scale);
+ gtk_style_context_get_style(style, "child-displacement-x", &x_displacement,
+ "child-displacement-y", &y_displacement, NULL);
+ arrow_rect.x += x_displacement;
+ arrow_rect.y += y_displacement;
+ }
+
+ calculate_arrow_rect(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_ARROW), &arrow_rect,
+ &real_arrow_rect, direction);
+
+ style = GetStyleContext(MOZ_GTK_COMBOBOX_ENTRY_ARROW, state->scale);
+ gtk_render_arrow(style, cr, ARROW_DOWN, real_arrow_rect.x, real_arrow_rect.y,
+ real_arrow_rect.width);
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_container_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ WidgetNodeType widget_type,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(widget_type, state->scale, direction, state_flags);
+ /* this is for drawing a prelight box */
+ if (state_flags & GTK_STATE_FLAG_PRELIGHT) {
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toggle_label_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state, gboolean isradio,
+ GtkTextDirection direction) {
+ if (!state->focused) return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style = GetStyleContext(
+ isradio ? MOZ_GTK_RADIOBUTTON_CONTAINER : MOZ_GTK_CHECKBUTTON_CONTAINER,
+ state->scale, direction, GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_focus(style, cr, rect->x, rect->y, rect->width, rect->height);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_toolbar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_TOOLBAR, state->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;
+}
+
+/* See _gtk_toolbar_paint_space_line() for reference.
+ */
+static gint moz_gtk_toolbar_separator_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint separator_width;
+ gint paint_width;
+ gboolean wide_separators;
+
+ /* Defined as constants in GTK+ 2.10.14 */
+ const double start_fraction = 0.2;
+ const double end_fraction = 0.8;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TOOLBAR, state->scale);
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-width", &separator_width, NULL);
+
+ style = GetStyleContext(MOZ_GTK_TOOLBAR_SEPARATOR, state->scale, direction);
+ if (wide_separators) {
+ if (separator_width > rect->width) separator_width = rect->width;
+
+ gtk_render_frame(style, cr, rect->x + (rect->width - separator_width) / 2,
+ rect->y + rect->height * start_fraction, separator_width,
+ rect->height * (end_fraction - start_fraction));
+ } else {
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ paint_width = padding.left;
+ if (paint_width > rect->width) paint_width = rect->width;
+
+ gtk_render_line(style, cr, rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * start_fraction,
+ rect->x + (rect->width - paint_width) / 2,
+ rect->y + rect->height * end_fraction);
+ }
+ 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->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->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->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->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->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->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->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->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->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->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->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ gtk_render_arrow(style, cr, arrow_angle, x, y, arrow_size);
+ }
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_bar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUBAR);
+ gtk_widget_set_direction(widget, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ StyleContextSetScale(style, state->scale);
+
+ 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);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_popup_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStyleContext* style;
+
+ GtkWidget* widget = GetWidget(MOZ_GTK_MENUPOPUP);
+ gtk_widget_set_direction(widget, direction);
+
+ // Draw a backing toplevel. This fixes themes that don't provide a menu
+ // background, and depend on the GtkMenu's implementation window to provide
+ // it.
+ moz_gtk_window_paint(cr, rect, direction);
+
+ style = gtk_widget_get_style_context(widget);
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENU);
+ StyleContextSetScale(style, state->scale);
+
+ 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);
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint moz_gtk_menu_separator_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkWidgetState defaultState = {0};
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUSEPARATOR, cr, rect, &defaultState,
+ direction);
+
+ if (gtk_get_minor_version() >= 20) return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style;
+ gboolean wide_separators;
+ gint separator_height;
+ gint x, y, w;
+ GtkBorder padding;
+
+ style = GetStyleContext(MOZ_GTK_MENUSEPARATOR, state->scale, direction);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-height", &separator_height, NULL);
+
+ if (wide_separators) {
+ gtk_render_frame(style, cr, x + padding.left, y + padding.top,
+ w - padding.left - padding.right, separator_height);
+ } else {
+ gtk_render_line(style, cr, x + padding.left, y + padding.top,
+ x + w - padding.right - 1, y + padding.top);
+ }
+
+ gtk_style_context_restore(style);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+// See gtk_menu_item_draw() for reference.
+static gint moz_gtk_menu_item_paint(WidgetNodeType widget, cairo_t* cr,
+ GdkRectangle* rect, GtkWidgetState* state,
+ GtkTextDirection direction) {
+ gint x, y, w, h;
+ guint minorVersion = gtk_get_minor_version();
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+
+ // GTK versions prior to 3.8 render the background and frame only when not
+ // a separator and in hover prelight.
+ if (minorVersion < 8 && (widget == MOZ_GTK_MENUSEPARATOR ||
+ !(state_flags & GTK_STATE_FLAG_PRELIGHT)))
+ return MOZ_GTK_SUCCESS;
+
+ GtkStyleContext* style =
+ GetStyleContext(widget, state->scale, direction, state_flags);
+
+ if (minorVersion < 6) {
+ // GTK+ 3.4 saves the style context and adds the menubar class to
+ // menubar children, but does each of these only when drawing, not
+ // during layout.
+ gtk_style_context_save(style);
+ if (widget == MOZ_GTK_MENUBARITEM) {
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_MENUBAR);
+ }
+ }
+
+ x = rect->x;
+ y = rect->y;
+ w = rect->width;
+ h = rect->height;
+
+ gtk_render_background(style, cr, x, y, w, h);
+ gtk_render_frame(style, cr, x, y, w, h);
+
+ if (minorVersion < 6) {
+ gtk_style_context_restore(style);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_menu_arrow_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_MENUITEM, state->scale, direction, state_flags);
+ gtk_render_arrow(style, cr,
+ (direction == GTK_TEXT_DIR_LTR) ? ARROW_RIGHT : ARROW_LEFT,
+ rect->x, rect->y, rect->width);
+ return MOZ_GTK_SUCCESS;
+}
+
+// For reference, see gtk_check_menu_item_size_allocate() in GTK versions after
+// 3.20 and gtk_real_check_menu_item_draw_indicator() in earlier versions.
+static gint moz_gtk_check_menu_item_paint(WidgetNodeType widgetType,
+ cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state,
+ gboolean checked,
+ GtkTextDirection direction) {
+ GtkStateFlags state_flags = GetStateFlagsFromGtkWidgetState(state);
+ GtkStyleContext* style;
+ gint indicator_size, horizontal_padding;
+ gint x, y;
+
+ moz_gtk_menu_item_paint(MOZ_GTK_MENUITEM, cr, rect, state, direction);
+
+ if (checked) {
+ state_flags =
+ static_cast<GtkStateFlags>(state_flags | checkbox_check_state);
+ }
+
+ bool pre_3_20 = gtk_get_minor_version() < 20;
+ gint offset;
+ style = GetStyleContext(widgetType, state->scale, direction);
+ gtk_style_context_get_style(style, "indicator-size", &indicator_size,
+ "horizontal-padding", &horizontal_padding, NULL);
+ if (pre_3_20) {
+ GtkBorder padding;
+ gtk_style_context_get_padding(style, state_flags, &padding);
+ offset = horizontal_padding + padding.left + 2;
+ } else {
+ GdkRectangle r = {0};
+ InsetByMargin(&r, style);
+ InsetByBorderPadding(&r, style);
+ offset = r.x;
+ }
+
+ bool isRadio = (widgetType == MOZ_GTK_RADIOMENUITEM);
+ WidgetNodeType indicatorType = isRadio ? MOZ_GTK_RADIOMENUITEM_INDICATOR
+ : MOZ_GTK_CHECKMENUITEM_INDICATOR;
+ const ToggleGTKMetrics* metrics = GetToggleMetrics(indicatorType);
+ style = GetStyleContext(indicatorType, state->scale, direction, state_flags);
+
+ if (direction == GTK_TEXT_DIR_RTL) {
+ x = rect->width - indicator_size - offset;
+ } else {
+ x = rect->x + offset;
+ }
+ y = rect->y + (rect->height - indicator_size) / 2;
+
+ gint indicator_width, indicator_height;
+ indicator_width = indicator_height = indicator_size;
+ if (!pre_3_20) {
+ gtk_render_background(style, cr, x, y, indicator_size, indicator_size);
+ gtk_render_frame(style, cr, x, y, indicator_size, indicator_size);
+ x = x + metrics->borderAndPadding.left;
+ y = y + metrics->borderAndPadding.top;
+ indicator_width = metrics->minSizeWithBorder.width -
+ metrics->borderAndPadding.left -
+ metrics->borderAndPadding.right;
+ indicator_height = metrics->minSizeWithBorder.height -
+ metrics->borderAndPadding.top -
+ metrics->borderAndPadding.bottom;
+ }
+
+ if (isRadio) {
+ gtk_render_option(style, cr, x, y, indicator_width, indicator_height);
+ } else {
+ gtk_render_check(style, cr, x, y, indicator_width, indicator_height);
+ }
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static gint moz_gtk_info_bar_paint(cairo_t* cr, GdkRectangle* rect,
+ GtkWidgetState* state) {
+ GtkStyleContext* style =
+ GetStyleContext(MOZ_GTK_INFO_BAR, state->scale, GTK_TEXT_DIR_LTR,
+ 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);
+
+ 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->scale, GTK_TEXT_DIR_NONE, state_flags);
+
+// Some themes (Adwaita for instance) draws bold dark line at
+// titlebar bottom. It does not fit well with Firefox tabs so
+// draw with some extent to make the titlebar bottom part invisible.
+#define TITLEBAR_EXTENT 4
+
+ // We don't need to draw window decoration for MOZ_GTK_HEADER_BAR_MAXIMIZED,
+ // i.e. when main window is maximized.
+ if (widgetType == MOZ_GTK_HEADER_BAR) {
+ GtkStyleContext* windowStyle =
+ GetStyleContext(MOZ_GTK_HEADERBAR_WINDOW, state->scale);
+ bool solidDecorations =
+ gtk_style_context_has_class(windowStyle, "solid-csd");
+ GtkStyleContext* decorationStyle =
+ GetStyleContext(solidDecorations ? MOZ_GTK_WINDOW_DECORATION_SOLID
+ : MOZ_GTK_WINDOW_DECORATION,
+ state->scale, GTK_TEXT_DIR_LTR, state_flags);
+
+ gtk_render_background(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ gtk_render_frame(decorationStyle, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ }
+
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+ gtk_render_frame(style, cr, rect->x, rect->y, rect->width,
+ rect->height + TITLEBAR_EXTENT);
+
+ return MOZ_GTK_SUCCESS;
+}
+
+static GtkBorder GetMarginBorderPadding(GtkStyleContext* aStyle) {
+ gint left = 0, top = 0, right = 0, bottom = 0;
+ moz_gtk_add_margin_border_padding(aStyle, &left, &top, &right, &bottom);
+ // narrowing conversions to gint16:
+ GtkBorder result;
+ result.left = left;
+ result.right = right;
+ result.top = top;
+ result.bottom = bottom;
+ return result;
+}
+
+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_TREE_HEADER_SORTARROW:
+ w = GetWidget(MOZ_GTK_TREE_HEADER_SORTARROW);
+ break;
+ case MOZ_GTK_DROPDOWN_ARROW:
+ w = GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON);
+ break;
+ 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_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER: {
+ w = GetWidget(widget);
+ style = gtk_widget_get_style_context(w);
+
+ *left = *top = *right = *bottom =
+ gtk_container_get_border_width(GTK_CONTAINER(w));
+ moz_gtk_add_border_padding(style, left, top, right, bottom);
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_MENUPOPUP:
+ w = GetWidget(MOZ_GTK_MENUPOPUP);
+ break;
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM: {
+ // Bug 1274143 for MOZ_GTK_MENUBARITEM
+ WidgetNodeType type =
+ widget == MOZ_GTK_MENUBARITEM ? MOZ_GTK_MENUITEM : widget;
+ style = GetStyleContext(type);
+
+ if (gtk_get_minor_version() < 20) {
+ moz_gtk_add_style_padding(style, left, top, right, bottom);
+ } else {
+ moz_gtk_add_margin_border_padding(style, left, top, right, bottom);
+ }
+ return MOZ_GTK_SUCCESS;
+ }
+ case MOZ_GTK_INFO_BAR:
+ w = GetWidget(MOZ_GTK_INFO_BAR);
+ 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_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ case MOZ_GTK_SPLITTER_HORIZONTAL:
+ case MOZ_GTK_SPLITTER_VERTICAL:
+ case MOZ_GTK_CHECKBUTTON:
+ case MOZ_GTK_RADIOBUTTON:
+ case MOZ_GTK_SCROLLBAR_BUTTON:
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ case MOZ_GTK_SCALE_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCALE_THUMB_VERTICAL:
+ case MOZ_GTK_GRIPPER:
+ 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_TOOLBAR_SEPARATOR:
+ case MOZ_GTK_MENUSEPARATOR:
+ 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:
+ case MOZ_GTK_RESIZER:
+ case MOZ_GTK_MENUARROW:
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ case MOZ_GTK_TOOLBAR:
+ case MOZ_GTK_MENUBAR:
+ 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_combo_box_entry_button_size(gint* width, gint* height) {
+ /*
+ * We get the requisition of the drop down button, which includes
+ * all padding, border and focus line widths the button uses,
+ * as well as the minimum arrow size and its padding
+ * */
+ GtkRequisition requisition;
+
+ gtk_widget_get_preferred_size(GetWidget(MOZ_GTK_COMBOBOX_ENTRY_BUTTON), NULL,
+ &requisition);
+ moz_gtk_sanity_preferred_size(&requisition);
+
+ *width = requisition.width;
+ *height = requisition.height;
+
+ 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;
+ }
+
+ GtkRequisition requisition;
+ gtk_widget_get_preferred_size(widget, NULL, &requisition);
+ moz_gtk_sanity_preferred_size(&requisition);
+
+ *width = requisition.width;
+ *height = requisition.height;
+}
+
+gint moz_gtk_get_toolbar_separator_width(gint* size) {
+ gboolean wide_separators;
+ gint separator_width;
+ GtkBorder border;
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_TOOLBAR);
+ gtk_style_context_get_style(style, "space-size", size, "wide-separators",
+ &wide_separators, "separator-width",
+ &separator_width, NULL);
+ /* Just in case... */
+ gtk_style_context_get_border(style, gtk_style_context_get_state(style),
+ &border);
+ *size = MAX(*size, (wide_separators ? separator_width : border.left));
+ return MOZ_GTK_SUCCESS;
+}
+
+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;
+}
+
+// See gtk_menu_item_draw() for reference.
+gint moz_gtk_get_menu_separator_height(gint* size) {
+ gboolean wide_separators;
+ gint separator_height;
+ GtkBorder padding;
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_MENUSEPARATOR);
+ gtk_style_context_get_padding(style, gtk_style_context_get_state(style),
+ &padding);
+
+ gtk_style_context_save(style);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_SEPARATOR);
+
+ gtk_style_context_get_style(style, "wide-separators", &wide_separators,
+ "separator-height", &separator_height, NULL);
+
+ gtk_style_context_restore(style);
+
+ *size = padding.top + padding.bottom;
+ *size += (wide_separators) ? separator_height : 1;
+
+ 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;
+}
+
+static MozGtkSize SizeFromLengthAndBreadth(GtkOrientation aOrientation,
+ gint aLength, gint aBreadth) {
+ return aOrientation == GTK_ORIENTATION_HORIZONTAL
+ ? MozGtkSize({aLength, aBreadth})
+ : MozGtkSize({aBreadth, aLength});
+}
+
+const ToggleGTKMetrics* GetToggleMetrics(WidgetNodeType aWidgetType) {
+ ToggleGTKMetrics* metrics;
+
+ switch (aWidgetType) {
+ case MOZ_GTK_RADIOBUTTON:
+ metrics = &sRadioMetrics;
+ break;
+ case MOZ_GTK_CHECKBUTTON:
+ metrics = &sCheckboxMetrics;
+ break;
+ case MOZ_GTK_RADIOMENUITEM_INDICATOR:
+ metrics = &sMenuRadioMetrics;
+ break;
+ case MOZ_GTK_CHECKMENUITEM_INDICATOR:
+ metrics = &sMenuCheckboxMetrics;
+ 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;
+ 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, indicator_spacing;
+ 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;
+}
+
+static void InitScrollbarMetrics(ScrollbarGTKMetrics* aMetrics,
+ GtkOrientation aOrientation,
+ GtkStateFlags aStateFlags) {
+ WidgetNodeType scrollbar = aOrientation == GTK_ORIENTATION_HORIZONTAL
+ ? MOZ_GTK_SCROLLBAR_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_VERTICAL;
+
+ gboolean backward, forward, secondary_backward, secondary_forward;
+ GtkStyleContext* style =
+ GetStyleContext(scrollbar, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ gtk_style_context_get_style(
+ style, "has-backward-stepper", &backward, "has-forward-stepper", &forward,
+ "has-secondary-backward-stepper", &secondary_backward,
+ "has-secondary-forward-stepper", &secondary_forward, nullptr);
+ bool hasButtons =
+ backward || forward || secondary_backward || secondary_forward;
+
+ if (gtk_get_minor_version() < 20) {
+ gint slider_width, trough_border, stepper_size, min_slider_size;
+
+ gtk_style_context_get_style(style, "slider-width", &slider_width,
+ "trough-border", &trough_border, "stepper-size",
+ &stepper_size, "min-slider-length",
+ &min_slider_size, nullptr);
+
+ aMetrics->size.thumb =
+ SizeFromLengthAndBreadth(aOrientation, min_slider_size, slider_width);
+ aMetrics->size.button =
+ SizeFromLengthAndBreadth(aOrientation, stepper_size, slider_width);
+ // overall scrollbar
+ gint breadth = slider_width + 2 * trough_border;
+ // Require room for the slider in the track if we don't have buttons.
+ gint length = hasButtons ? 0 : min_slider_size + 2 * trough_border;
+ aMetrics->size.scrollbar =
+ SizeFromLengthAndBreadth(aOrientation, length, breadth);
+
+ // Borders on the major axis are set on the outermost scrollbar
+ // element to correctly position the buttons when
+ // trough-under-steppers is true.
+ // Borders on the minor axis are set on the track element so that it
+ // receives mouse events, as in GTK.
+ // Other borders have been zero-initialized.
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ aMetrics->border.scrollbar.left = aMetrics->border.scrollbar.right =
+ aMetrics->border.track.top = aMetrics->border.track.bottom =
+ trough_border;
+ } else {
+ aMetrics->border.scrollbar.top = aMetrics->border.scrollbar.bottom =
+ aMetrics->border.track.left = aMetrics->border.track.right =
+ trough_border;
+ }
+
+ // We're done here for Gtk+ < 3.20...
+ return;
+ }
+
+ // GTK version > 3.20
+ // scrollbar
+ aMetrics->border.scrollbar = GetMarginBorderPadding(style);
+
+ WidgetNodeType contents, track, thumb;
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ contents = MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL;
+ track = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ thumb = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ } else {
+ contents = MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL;
+ track = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ thumb = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ }
+
+ /* GetStyleContext() sets GtkStateFlags to the latest widget name
+ * in css selector string. When we call:
+ *
+ * GetStyleContext(thumb, GTK_STATE_FLAG_PRELIGHT)
+ *
+ * we get:
+ *
+ * "scrollbar contents trough slider:hover"
+ *
+ * Some themes (Ubuntu Ambiance) styles trough/thumb by scrollbar,
+ * the Gtk+ css rule looks like:
+ *
+ * "scrollbar:hover contents trough slider"
+ *
+ * So we need to apply GtkStateFlags to each widgets in style path.
+ */
+
+ // thumb
+ style =
+ CreateStyleContextWithStates(thumb, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->size.thumb = GetMinMarginBox(style);
+ gtk_style_context_get_margin(style, gtk_style_context_get_state(style),
+ &aMetrics->margin.thumb);
+ g_object_unref(style);
+
+ // track
+ style =
+ CreateStyleContextWithStates(track, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->border.track = GetMarginBorderPadding(style);
+ MozGtkSize trackMinSize = GetMinContentBox(style) + aMetrics->border.track;
+ MozGtkSize trackSizeForThumb = aMetrics->size.thumb + aMetrics->border.track;
+ g_object_unref(style);
+
+ // button
+ if (hasButtons) {
+ style = CreateStyleContextWithStates(MOZ_GTK_SCROLLBAR_BUTTON, 1,
+ GTK_TEXT_DIR_NONE, aStateFlags);
+ aMetrics->size.button = GetMinMarginBox(style);
+ g_object_unref(style);
+ } else {
+ aMetrics->size.button = {0, 0};
+ }
+ if (aOrientation == GTK_ORIENTATION_HORIZONTAL) {
+ aMetrics->size.button.Rotate();
+ // If the track is wider than necessary for the thumb, including when
+ // the buttons will cause Gecko to expand the track to fill
+ // available breadth, then add to the track border to prevent Gecko
+ // from expanding the thumb to fill available breadth.
+ gint extra = std::max(trackMinSize.height, aMetrics->size.button.height) -
+ trackSizeForThumb.height;
+ if (extra > 0) {
+ // If extra is odd, then the thumb is 0.5 pixels above
+ // center as in gtk_range_compute_slider_position().
+ aMetrics->border.track.top += extra / 2;
+ aMetrics->border.track.bottom += extra - extra / 2;
+ // Update size for change in border.
+ trackSizeForThumb.height += extra;
+ }
+ } else {
+ gint extra = std::max(trackMinSize.width, aMetrics->size.button.width) -
+ trackSizeForThumb.width;
+ if (extra > 0) {
+ // If extra is odd, then the thumb is 0.5 pixels to the left
+ // of center as in gtk_range_compute_slider_position().
+ aMetrics->border.track.left += extra / 2;
+ aMetrics->border.track.right += extra - extra / 2;
+ trackSizeForThumb.width += extra;
+ }
+ }
+
+ style =
+ CreateStyleContextWithStates(contents, 1, GTK_TEXT_DIR_NONE, aStateFlags);
+ GtkBorder contentsBorder = GetMarginBorderPadding(style);
+ g_object_unref(style);
+
+ aMetrics->size.scrollbar =
+ trackSizeForThumb + contentsBorder + aMetrics->border.scrollbar;
+}
+
+const ScrollbarGTKMetrics* GetScrollbarMetrics(GtkOrientation aOrientation) {
+ auto metrics = &sScrollbarMetrics[aOrientation];
+ if (!metrics->initialized) {
+ InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_NORMAL);
+
+ // We calculate thumb margin here because it's composited from
+ // thumb class margin + difference margin between active and inactive
+ // scrollbars. It's a workaround which alows us to emulate
+ // overlay scrollbars for some Gtk+ themes (Ubuntu/Ambiance),
+ // when an inactive scrollbar thumb is smaller than the active one.
+ const ScrollbarGTKMetrics* metricsActive =
+ GetActiveScrollbarMetrics(aOrientation);
+
+ if (metrics->size.thumb < metricsActive->size.thumb) {
+ metrics->margin.thumb +=
+ (metrics->border.scrollbar + metrics->border.track) -
+ (metricsActive->border.scrollbar + metricsActive->border.track);
+ }
+
+ metrics->initialized = true;
+ }
+ return metrics;
+}
+
+const ScrollbarGTKMetrics* GetActiveScrollbarMetrics(
+ GtkOrientation aOrientation) {
+ auto metrics = &sActiveScrollbarMetrics[aOrientation];
+ if (!metrics->initialized) {
+ InitScrollbarMetrics(metrics, aOrientation, GTK_STATE_FLAG_PRELIGHT);
+ metrics->initialized = true;
+ }
+ 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_SCROLLBAR_BUTTON:
+ return moz_gtk_scrollbar_button_paint(
+ cr, rect, state, (GtkScrollbarButtonFlags)flags, direction);
+ case MOZ_GTK_SCROLLBAR_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_VERTICAL: {
+ if (flags & MOZ_GTK_TRACK_OPAQUE) {
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW, direction);
+ gtk_render_background(style, cr, rect->x, rect->y, rect->width,
+ rect->height);
+ }
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ return moz_gtk_scrollbar_paint(widget, cr, rect, state, direction);
+ }
+ WidgetNodeType trough_widget = (widget == MOZ_GTK_SCROLLBAR_HORIZONTAL)
+ ? MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL
+ : MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ return moz_gtk_scrollbar_trough_paint(trough_widget, cr, rect, state,
+ direction);
+ }
+ case MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL:
+ if (gtk_check_version(3, 20, 0) == nullptr) {
+ return moz_gtk_scrollbar_trough_paint(widget, cr, rect, state,
+ direction);
+ }
+ break;
+ case MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL:
+ case MOZ_GTK_SCROLLBAR_THUMB_VERTICAL:
+ return moz_gtk_scrollbar_thumb_paint(widget, cr, rect, state, 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->scale, direction,
+ GetStateFlagsFromGtkWidgetState(state));
+ return moz_gtk_entry_paint(cr, rect, state, style, widget);
+ }
+ case MOZ_GTK_GRIPPER:
+ return moz_gtk_gripper_paint(cr, rect, state, direction);
+ 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_TREE_HEADER_SORTARROW:
+ return moz_gtk_tree_header_sort_arrow_paint(
+ cr, rect, state, (GtkArrowType)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->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_DROPDOWN_ARROW:
+ return moz_gtk_combo_box_entry_button_paint(cr, rect, state, flags,
+ direction);
+ case MOZ_GTK_CHECKBUTTON_CONTAINER:
+ case MOZ_GTK_RADIOBUTTON_CONTAINER:
+ return moz_gtk_container_paint(cr, rect, state, widget, direction);
+ case MOZ_GTK_CHECKBUTTON_LABEL:
+ case MOZ_GTK_RADIOBUTTON_LABEL:
+ return moz_gtk_toggle_label_paint(
+ cr, rect, state, (widget == MOZ_GTK_RADIOBUTTON_LABEL), direction);
+ case MOZ_GTK_TOOLBAR:
+ return moz_gtk_toolbar_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLBAR_SEPARATOR:
+ return moz_gtk_toolbar_separator_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_MENUBAR:
+ return moz_gtk_menu_bar_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUPOPUP:
+ return moz_gtk_menu_popup_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUSEPARATOR:
+ return moz_gtk_menu_separator_paint(cr, rect, state, direction);
+ case MOZ_GTK_MENUBARITEM:
+ case MOZ_GTK_MENUITEM:
+ return moz_gtk_menu_item_paint(widget, cr, rect, state, direction);
+ case MOZ_GTK_MENUARROW:
+ return moz_gtk_menu_arrow_paint(cr, rect, state, direction);
+ case MOZ_GTK_TOOLBARBUTTON_ARROW:
+ return moz_gtk_arrow_paint(cr, rect, state, (GtkArrowType)flags,
+ direction);
+ case MOZ_GTK_CHECKMENUITEM:
+ case MOZ_GTK_RADIOMENUITEM:
+ return moz_gtk_check_menu_item_paint(widget, cr, rect, state,
+ (gboolean)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:
+ return moz_gtk_window_paint(cr, rect, direction);
+ case MOZ_GTK_INFO_BAR:
+ return moz_gtk_info_bar_paint(cr, rect, state);
+ 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..38dc1587fc
--- /dev/null
+++ b/widget/gtk/gtkdrawing.h
@@ -0,0 +1,634 @@
+/* -*- 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 scale; /* actual widget 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;
+ struct {
+ MozGtkSize scrollbar;
+ MozGtkSize thumb;
+ MozGtkSize button;
+ } size;
+ struct {
+ GtkBorder scrollbar;
+ GtkBorder track;
+ } border;
+ struct {
+ GtkBorder thumb;
+ } margin;
+} ScrollbarGTKMetrics;
+
+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;
+
+typedef enum {
+ MOZ_GTK_STEPPER_DOWN = 1 << 0,
+ MOZ_GTK_STEPPER_BOTTOM = 1 << 1,
+ MOZ_GTK_STEPPER_VERTICAL = 1 << 2
+} GtkScrollbarButtonFlags;
+
+typedef enum { MOZ_GTK_TRACK_OPAQUE = 1 << 0 } GtkScrollbarTrackFlags;
+
+/** 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 label of a GtkCheckButton (focus outline) */
+ MOZ_GTK_CHECKBUTTON_LABEL,
+
+ /* 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,
+ /* Paints the label of a GtkRadioButton (focus outline) */
+ MOZ_GTK_RADIOBUTTON_LABEL,
+ /**
+ * Paints the button of a GtkScrollbar. flags is a GtkArrowType giving
+ * the arrow direction.
+ */
+ MOZ_GTK_SCROLLBAR_BUTTON,
+
+ /* Horizontal GtkScrollbar counterparts */
+ MOZ_GTK_SCROLLBAR_HORIZONTAL,
+ MOZ_GTK_SCROLLBAR_CONTENTS_HORIZONTAL,
+ /* Paints the trough (track) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL,
+ /* Paints the slider (thumb) of a GtkScrollbar. */
+ MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL,
+
+ /* 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 the gripper of a GtkHandleBox. */
+ MOZ_GTK_GRIPPER,
+ /* 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 a dropdown arrow (a GtkButton containing a down GtkArrow). */
+ MOZ_GTK_DROPDOWN_ARROW,
+ /* Paints an entry in an editable option menu */
+ MOZ_GTK_DROPDOWN_ENTRY,
+
+ /* Paints the background of a GtkHandleBox. */
+ MOZ_GTK_TOOLBAR,
+ /* Paints a toolbar separator */
+ MOZ_GTK_TOOLBAR_SEPARATOR,
+ /* 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 sort arrows in treeheader cells */
+ MOZ_GTK_TREE_HEADER_SORTARROW,
+ /* Paints an expander for a GtkTreeView */
+ MOZ_GTK_TREEVIEW_EXPANDER,
+ /* Paints the background of the menu bar. */
+ MOZ_GTK_MENUBAR,
+ /* Paints the background of menus, context menus. */
+ MOZ_GTK_MENUPOPUP,
+ /* Paints the arrow of menuitems that contain submenus */
+ MOZ_GTK_MENUARROW,
+ /* Paints an arrow in a toolbar button. flags is a GtkArrowType. */
+ MOZ_GTK_TOOLBARBUTTON_ARROW,
+ /* Paints items of menubar. */
+ MOZ_GTK_MENUBARITEM,
+ /* Paints items of popup menus. */
+ MOZ_GTK_MENUITEM,
+ /* Paints a menuitem with check indicator, or the gets the style context for
+ a menuitem that contains a checkbox. */
+ MOZ_GTK_CHECKMENUITEM,
+ /* Gets the style context for a checkbox in a check menuitem. */
+ MOZ_GTK_CHECKMENUITEM_INDICATOR,
+ MOZ_GTK_RADIOMENUITEM,
+ MOZ_GTK_RADIOMENUITEM_INDICATOR,
+ MOZ_GTK_MENUSEPARATOR,
+ /* 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,
+ /* Window container for all widgets */
+ MOZ_GTK_WINDOW_CONTAINER,
+ /* Paints a GtkInfoBar, for notifications. */
+ MOZ_GTK_INFO_BAR,
+ /* 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;
+ bool mAtRight;
+};
+
+/*** 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);
+
+/** Returns the size of the focus ring for outline:auto.
+ * focus_h_width: [OUT] the horizontal width
+ * focus_v_width: [OUT] the vertical width
+ *
+ * returns: MOZ_GTK_SUCCESS
+ */
+gint moz_gtk_get_focus_outline_size(gint* focus_h_width, gint* focus_v_width);
+
+/** Get the horizontal padding for the menuitem widget or checkmenuitem widget.
+ * horizontal_padding: [OUT] The left and right padding of the menuitem or
+ * checkmenuitem
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_menuitem_get_horizontal_padding(gint* horizontal_padding);
+
+gint moz_gtk_checkmenuitem_get_horizontal_padding(gint* horizontal_padding);
+
+/**
+ * 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 metrics in GTK pixels for a scrollbar.
+ * aOrientation: [IN] the scrollbar orientation
+ */
+const ScrollbarGTKMetrics* GetScrollbarMetrics(GtkOrientation aOrientation);
+
+/**
+ * Get the metrics in GTK pixels for a scrollbar which is active
+ * (selected by mouse pointer).
+ * aOrientation: [IN] the scrollbar orientation
+ */
+const ScrollbarGTKMetrics* GetActiveScrollbarMetrics(
+ GtkOrientation aOrientation);
+
+/**
+ * Get the desired size of a dropdown arrow button
+ * 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_combo_box_entry_button_size(gint* width, gint* 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 height of a menu separator
+ * size: [OUT] the desired height
+ *
+ * returns: MOZ_GTK_SUCCESS if there was no error, an error code otherwise
+ */
+gint moz_gtk_get_menu_separator_height(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/maiRedundantObjectFactory.c b/widget/gtk/maiRedundantObjectFactory.c
new file mode 100644
index 0000000000..ce086e20af
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.c
@@ -0,0 +1,81 @@
+/* -*- 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 <atk/atk.h>
+#include "maiRedundantObjectFactory.h"
+
+static void mai_redundant_object_factory_class_init(
+ maiRedundantObjectFactoryClass* klass);
+
+static AtkObject* mai_redundant_object_factory_create_accessible(GObject* obj);
+static GType mai_redundant_object_factory_get_accessible_type(void);
+
+GType mai_redundant_object_factory_get_type(void) {
+ static GType type = 0;
+
+ if (!type) {
+ static const GTypeInfo tinfo = {
+ sizeof(maiRedundantObjectFactoryClass),
+ (GBaseInitFunc)NULL, /* base init */
+ (GBaseFinalizeFunc)NULL, /* base finalize */
+ (GClassInitFunc)
+ mai_redundant_object_factory_class_init, /* class init */
+ (GClassFinalizeFunc)NULL, /* class finalize */
+ NULL, /* class data */
+ sizeof(maiRedundantObjectFactory), /* instance size */
+ 0, /* nb preallocs */
+ (GInstanceInitFunc)NULL, /* instance init */
+ NULL /* value table */
+ };
+ type = g_type_register_static(ATK_TYPE_OBJECT_FACTORY,
+ "MaiRedundantObjectFactory", &tinfo, 0);
+ }
+
+ return type;
+}
+
+static void mai_redundant_object_factory_class_init(
+ maiRedundantObjectFactoryClass* klass) {
+ AtkObjectFactoryClass* class = ATK_OBJECT_FACTORY_CLASS(klass);
+
+ class->create_accessible = mai_redundant_object_factory_create_accessible;
+ class->get_accessible_type = mai_redundant_object_factory_get_accessible_type;
+}
+
+/**
+ * mai_redundant_object_factory_new:
+ *
+ * Creates an instance of an #AtkObjectFactory which generates primitive
+ * (non-functioning) #AtkObjects.
+ *
+ * Returns: an instance of an #AtkObjectFactory
+ **/
+AtkObjectFactory* mai_redundant_object_factory_new() {
+ GObject* factory;
+
+ factory = g_object_new(mai_redundant_object_factory_get_type(), NULL);
+
+ g_return_val_if_fail(factory != NULL, NULL);
+ return ATK_OBJECT_FACTORY(factory);
+}
+
+static AtkObject* mai_redundant_object_factory_create_accessible(GObject* obj) {
+ AtkObject* accessible;
+
+ g_return_val_if_fail(obj != NULL, NULL);
+
+ accessible = g_object_new(ATK_TYPE_OBJECT, NULL);
+ g_return_val_if_fail(accessible != NULL, NULL);
+
+ accessible->role = ATK_ROLE_REDUNDANT_OBJECT;
+
+ return accessible;
+}
+
+static GType mai_redundant_object_factory_get_accessible_type() {
+ return mai_redundant_object_factory_get_type();
+}
diff --git a/widget/gtk/maiRedundantObjectFactory.h b/widget/gtk/maiRedundantObjectFactory.h
new file mode 100644
index 0000000000..931adffb6b
--- /dev/null
+++ b/widget/gtk/maiRedundantObjectFactory.h
@@ -0,0 +1,30 @@
+/* -*- 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 __MAI_REDUNDANT_OBJECT_FACTORY_H__
+#define __MAI_REDUNDANT_OBJECT_FACTORY_H__
+
+G_BEGIN_DECLS
+
+typedef struct _maiRedundantObjectFactory maiRedundantObjectFactory;
+typedef struct _maiRedundantObjectFactoryClass maiRedundantObjectFactoryClass;
+
+struct _maiRedundantObjectFactory {
+ AtkObjectFactory parent;
+};
+
+struct _maiRedundantObjectFactoryClass {
+ AtkObjectFactoryClass parent_class;
+};
+
+GType mai_redundant_object_factory_get_type();
+
+AtkObjectFactory* mai_redundant_object_factory_new();
+
+G_END_DECLS
+
+#endif /* __NS_MAI_REDUNDANT_OBJECT_FACTORY_H__ */
diff --git a/widget/gtk/moz.build b/widget/gtk/moz.build
new file mode 100644
index 0000000000..cddd45e49f
--- /dev/null
+++ b/widget/gtk/moz.build
@@ -0,0 +1,178 @@
+# -*- 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["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ DIRS += ["mozgtk"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ DIRS += ["wayland", "mozwayland"]
+
+EXPORTS += [
+ "MozContainer.h",
+ "nsGTKToolkit.h",
+ "nsIImageToPixbuf.h",
+]
+
+EXPORTS.mozilla += ["WidgetUtilsGtk.h"]
+
+UNIFIED_SOURCES += [
+ "IMContextWrapper.cpp",
+ "MozContainer.cpp",
+ "MPRISServiceHandler.cpp",
+ "NativeKeyBindings.cpp",
+ "nsAppShell.cpp",
+ "nsBidiKeyboard.cpp",
+ "nsColorPicker.cpp",
+ "nsFilePicker.cpp",
+ "nsGtkKeyUtils.cpp",
+ "nsImageToPixbuf.cpp",
+ "nsLookAndFeel.cpp",
+ "nsNativeBasicThemeGTK.cpp",
+ "nsNativeThemeGTK.cpp",
+ "nsSound.cpp",
+ "nsToolkit.cpp",
+ "nsWidgetFactory.cpp",
+ "ScreenHelperGTK.cpp",
+ "TaskbarProgress.cpp",
+ "WakeLockListener.cpp",
+ "WidgetTraceEvent.cpp",
+ "WidgetUtilsGtk.cpp",
+]
+
+SOURCES += [
+ "MediaKeysEventSourceFactory.cpp",
+ "nsWindow.cpp", # conflicts with X11 headers
+ "WaylandVsyncSource.cpp", # conflicts with X11 headers
+]
+
+if CONFIG["MOZ_X11"]:
+ UNIFIED_SOURCES += [
+ "CompositorWidgetChild.cpp",
+ "CompositorWidgetParent.cpp",
+ "GtkCompositorWidget.cpp",
+ "InProcessGtkCompositorWidget.cpp",
+ "nsUserIdleServiceGTK.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "CompositorWidgetChild.h",
+ "CompositorWidgetParent.h",
+ "GtkCompositorWidget.h",
+ "InProcessGtkCompositorWidget.h",
+ ]
+
+if CONFIG["NS_PRINTING"]:
+ UNIFIED_SOURCES += [
+ "nsDeviceContextSpecG.cpp",
+ "nsPrintDialogGTK.cpp",
+ "nsPrintSettingsGTK.cpp",
+ "nsPrintSettingsServiceGTK.cpp",
+ ]
+
+if CONFIG["MOZ_X11"]:
+ UNIFIED_SOURCES += [
+ "nsClipboard.cpp",
+ "nsClipboardX11.cpp",
+ "nsDragService.cpp",
+ "WindowSurfaceProvider.cpp",
+ "WindowSurfaceX11.cpp",
+ "WindowSurfaceX11Image.cpp",
+ "WindowSurfaceXRender.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "WindowSurfaceProvider.h",
+ ]
+
+if CONFIG["MOZ_WAYLAND"]:
+ UNIFIED_SOURCES += [
+ "DMABufLibWrapper.cpp",
+ "DMABufSurface.cpp",
+ "MozContainerWayland.cpp",
+ "nsClipboardWayland.cpp",
+ "nsWaylandDisplay.cpp",
+ "WindowSurfaceWayland.cpp",
+ ]
+ EXPORTS.mozilla.widget += [
+ "DMABufLibWrapper.h",
+ "DMABufSurface.h",
+ "MozContainerWayland.h",
+ "nsWaylandDisplay.h",
+ ]
+
+if CONFIG["ACCESSIBILITY"]:
+ UNIFIED_SOURCES += [
+ "maiRedundantObjectFactory.c",
+ ]
+
+UNIFIED_SOURCES += [
+ "gtk3drawing.cpp",
+ "nsApplicationChooser.cpp",
+ "WidgetStyleCache.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",
+]
+
+if CONFIG["MOZ_X11"]:
+ LOCAL_INCLUDES += [
+ "/widget/x11",
+ ]
+
+DEFINES["CAIRO_GFX"] = True
+
+DEFINES["MOZ_APP_NAME"] = '"%s"' % CONFIG["MOZ_APP_NAME"]
+
+# When building with GTK3, the widget code always needs to use
+# system Cairo headers, regardless of whether we are also linked
+# against and using in-tree Cairo. By not using in-tree Cairo
+# headers, we avoid picking up our renamed symbols, and instead
+# use only system Cairo symbols that GTK3 uses. This allows that
+# any Cairo objects created can be freely passed back and forth
+# between the widget code and GTK3.
+if not (CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk" and CONFIG["MOZ_TREE_CAIRO"]):
+ CXXFLAGS += CONFIG["MOZ_CAIRO_CFLAGS"]
+
+CFLAGS += CONFIG["TK_CFLAGS"]
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+if CONFIG["MOZ_WAYLAND"]:
+ CFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_WAYLAND_CFLAGS"]
+
+if CONFIG["MOZ_ENABLE_DBUS"]:
+ CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/widget/gtk/mozgtk/gtk2/moz.build b/widget/gtk/mozgtk/gtk2/moz.build
new file mode 100644
index 0000000000..93e43c3957
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk2/moz.build
@@ -0,0 +1,40 @@
+# -*- 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 += [
+ "../mozgtk.c",
+]
+
+DEFINES["GTK3_SYMBOLS"] = True
+
+SharedLibrary("mozgtk2")
+
+SHARED_LIBRARY_NAME = "mozgtk"
+
+FINAL_TARGET = "dist/bin/gtk2"
+
+# 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_GTK2_LIBS"] if f.startswith("-L")]
+OS_LIBS += no_as_needed
+OS_LIBS += [
+ "gtk-x11-2.0",
+ "gdk-x11-2.0",
+]
+OS_LIBS += as_needed
diff --git a/widget/gtk/mozgtk/gtk3/moz.build b/widget/gtk/mozgtk/gtk3/moz.build
new file mode 100644
index 0000000000..b4ab68ecb1
--- /dev/null
+++ b/widget/gtk/mozgtk/gtk3/moz.build
@@ -0,0 +1,38 @@
+# -*- 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 += [
+ "../mozgtk.c",
+]
+
+DEFINES["GTK2_SYMBOLS"] = True
+
+SharedLibrary("mozgtk")
+
+SONAME = "mozgtk"
+
+# 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/moz.build b/widget/gtk/mozgtk/moz.build
new file mode 100644
index 0000000000..8288583745
--- /dev/null
+++ b/widget/gtk/mozgtk/moz.build
@@ -0,0 +1,7 @@
+# -*- 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/.
+
+DIRS += ["stub", "gtk2", "gtk3"]
diff --git a/widget/gtk/mozgtk/mozgtk.c b/widget/gtk/mozgtk/mozgtk.c
new file mode 100644
index 0000000000..0b2e3fd494
--- /dev/null
+++ b/widget/gtk/mozgtk/mozgtk.c
@@ -0,0 +1,676 @@
+/* -*- 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 "mozilla/Assertions.h"
+
+#define STUB(symbol) \
+ MOZ_EXPORT void symbol(void) { MOZ_CRASH(); }
+
+#ifdef COMMON_SYMBOLS
+STUB(gdk_atom_intern)
+STUB(gdk_atom_name)
+STUB(gdk_beep)
+STUB(gdk_cairo_create)
+STUB(gdk_color_free)
+STUB(gdk_color_parse)
+STUB(gdk_cursor_new_for_display)
+STUB(gdk_cursor_new_from_name)
+STUB(gdk_cursor_new_from_pixbuf)
+STUB(gdk_display_close)
+STUB(gdk_display_get_default)
+STUB(gdk_display_get_default_screen)
+STUB(gdk_display_get_pointer)
+STUB(gdk_display_get_window_at_pointer)
+STUB(gdk_display_manager_get)
+STUB(gdk_display_manager_set_default_display)
+STUB(gdk_display_open)
+STUB(gdk_display_sync)
+STUB(gdk_display_warp_pointer)
+STUB(gdk_drag_context_get_actions)
+STUB(gdk_drag_context_get_dest_window)
+STUB(gdk_drag_context_get_source_window)
+STUB(gdk_drag_context_list_targets)
+STUB(gdk_drag_status)
+STUB(gdk_error_trap_pop)
+STUB(gdk_error_trap_push)
+STUB(gdk_event_copy)
+STUB(gdk_event_free)
+STUB(gdk_event_get_axis)
+STUB(gdk_event_get_time)
+STUB(gdk_event_handler_set)
+STUB(gdk_event_peek)
+STUB(gdk_event_put)
+STUB(gdk_flush)
+STUB(gdk_get_default_root_window)
+STUB(gdk_get_display)
+STUB(gdk_get_display_arg_name)
+STUB(gdk_get_program_class)
+STUB(gdk_keymap_get_default)
+STUB(gdk_keymap_get_direction)
+STUB(gdk_keymap_get_entries_for_keyval)
+STUB(gdk_keymap_get_for_display)
+STUB(gdk_keymap_have_bidi_layouts)
+STUB(gdk_keymap_translate_keyboard_state)
+STUB(gdk_keyval_name)
+STUB(gdk_keyval_to_unicode)
+STUB(gdk_pango_context_get)
+STUB(gdk_pointer_grab)
+STUB(gdk_pointer_ungrab)
+STUB(gdk_property_change)
+STUB(gdk_property_get)
+STUB(gdk_property_delete)
+STUB(gdk_screen_get_default)
+STUB(gdk_screen_get_display)
+STUB(gdk_screen_get_font_options)
+STUB(gdk_screen_get_height)
+STUB(gdk_screen_get_height_mm)
+STUB(gdk_screen_get_n_monitors)
+STUB(gdk_screen_get_monitor_at_window)
+STUB(gdk_screen_get_monitor_geometry)
+STUB(gdk_screen_get_monitor_height_mm)
+STUB(gdk_screen_get_number)
+STUB(gdk_screen_get_resolution)
+STUB(gdk_screen_get_rgba_visual)
+STUB(gdk_screen_get_root_window)
+STUB(gdk_screen_get_system_visual)
+STUB(gdk_screen_get_width)
+STUB(gdk_screen_height)
+STUB(gdk_screen_is_composited)
+STUB(gdk_screen_width)
+STUB(gdk_selection_owner_get)
+STUB(gdk_set_program_class)
+STUB(gdk_unicode_to_keyval)
+STUB(gdk_visual_get_depth)
+STUB(gdk_visual_get_system)
+STUB(gdk_window_add_filter)
+STUB(gdk_window_begin_move_drag)
+STUB(gdk_window_begin_resize_drag)
+STUB(gdk_window_destroy)
+STUB(gdk_window_focus)
+STUB(gdk_window_get_children)
+STUB(gdk_window_get_display)
+STUB(gdk_window_get_events)
+STUB(gdk_window_get_geometry)
+STUB(gdk_window_get_height)
+STUB(gdk_window_get_origin)
+STUB(gdk_window_get_parent)
+STUB(gdk_window_get_position)
+STUB(gdk_window_get_root_origin)
+STUB(gdk_window_get_screen)
+STUB(gtk_window_get_size)
+STUB(gdk_window_get_state)
+STUB(gdk_window_get_toplevel)
+STUB(gdk_window_get_update_area)
+STUB(gdk_window_get_user_data)
+STUB(gdk_window_get_visual)
+STUB(gdk_window_get_width)
+STUB(gdk_window_get_window_type)
+STUB(gdk_window_hide)
+STUB(gdk_window_input_shape_combine_region)
+STUB(gdk_window_invalidate_rect)
+STUB(gdk_window_invalidate_region)
+STUB(gdk_window_is_destroyed)
+STUB(gdk_window_is_visible)
+STUB(gdk_window_lower)
+STUB(gdk_window_move)
+STUB(gdk_window_move_resize)
+STUB(gdk_window_new)
+STUB(gdk_window_peek_children)
+STUB(gdk_window_process_updates)
+STUB(gdk_window_raise)
+STUB(gdk_window_remove_filter)
+STUB(gdk_window_reparent)
+STUB(gdk_window_resize)
+STUB(gdk_window_set_cursor)
+STUB(gdk_window_set_debug_updates)
+STUB(gdk_window_set_decorations)
+STUB(gdk_window_set_events)
+STUB(gdk_window_set_role)
+STUB(gdk_window_set_urgency_hint)
+STUB(gdk_window_set_user_data)
+STUB(gdk_window_shape_combine_region)
+STUB(gdk_window_show)
+STUB(gdk_window_show_unraised)
+STUB(gdk_x11_atom_to_xatom)
+STUB(gdk_x11_display_get_user_time)
+STUB(gdk_x11_display_get_xdisplay)
+STUB(gdk_x11_get_default_root_xwindow)
+STUB(gdk_x11_get_default_xdisplay)
+STUB(gdk_x11_get_server_time)
+STUB(gdk_x11_get_xatom_by_name)
+STUB(gdk_x11_get_xatom_by_name_for_display)
+STUB(gdk_x11_lookup_xdisplay)
+STUB(gdk_x11_screen_get_xscreen)
+STUB(gdk_x11_screen_get_screen_number)
+STUB(gdk_x11_screen_lookup_visual)
+STUB(gdk_x11_screen_supports_net_wm_hint)
+STUB(gdk_x11_visual_get_xvisual)
+STUB(gdk_x11_window_foreign_new_for_display)
+STUB(gdk_x11_window_lookup_for_display)
+STUB(gdk_x11_window_set_user_time)
+STUB(gdk_x11_xatom_to_atom)
+STUB(gdk_x11_set_sm_client_id)
+STUB(gtk_accel_label_new)
+STUB(gtk_alignment_get_type)
+STUB(gtk_alignment_new)
+STUB(gtk_alignment_set_padding)
+STUB(gtk_arrow_get_type)
+STUB(gtk_arrow_new)
+STUB(gtk_bindings_activate)
+STUB(gtk_bin_get_child)
+STUB(gtk_bin_get_type)
+STUB(gtk_border_free)
+STUB(gtk_box_get_type)
+STUB(gtk_box_pack_start)
+STUB(gtk_button_new)
+STUB(gtk_button_new_with_label)
+STUB(gtk_check_button_new_with_label)
+STUB(gtk_check_button_new_with_mnemonic)
+STUB(gtk_check_menu_item_new)
+STUB(gtk_check_version)
+STUB(gtk_clipboard_clear)
+STUB(gtk_clipboard_get)
+STUB(gtk_clipboard_request_contents)
+STUB(gtk_clipboard_request_text)
+STUB(gtk_clipboard_set_can_store)
+STUB(gtk_clipboard_set_with_data)
+STUB(gtk_clipboard_store)
+STUB(gtk_color_selection_dialog_get_color_selection)
+STUB(gtk_color_selection_dialog_get_type)
+STUB(gtk_color_selection_dialog_new)
+STUB(gtk_color_selection_get_current_color)
+STUB(gtk_color_selection_get_type)
+STUB(gtk_color_selection_set_current_color)
+STUB(gtk_combo_box_get_active)
+STUB(gtk_combo_box_get_type)
+STUB(gtk_combo_box_new)
+STUB(gtk_combo_box_new_with_entry)
+STUB(gtk_combo_box_set_active)
+STUB(gtk_combo_box_text_get_type)
+STUB(gtk_combo_box_text_new)
+STUB(gtk_container_add)
+STUB(gtk_container_forall)
+STUB(gtk_container_get_border_width)
+STUB(gtk_container_get_type)
+STUB(gtk_container_set_border_width)
+STUB(gtk_container_set_resize_mode)
+STUB(gtk_dialog_get_content_area)
+STUB(gtk_dialog_get_type)
+STUB(gtk_dialog_new_with_buttons)
+STUB(gtk_dialog_run)
+STUB(gtk_dialog_set_alternative_button_order)
+STUB(gtk_dialog_set_default_response)
+STUB(gtk_drag_begin)
+STUB(gtk_drag_dest_set)
+STUB(gtk_drag_finish)
+STUB(gtk_drag_get_data)
+STUB(gtk_drag_get_source_widget)
+STUB(gtk_drag_set_icon_pixbuf)
+STUB(gtk_drag_set_icon_widget)
+STUB(gtk_editable_get_type)
+STUB(gtk_editable_select_region)
+STUB(gtk_entry_get_text)
+STUB(gtk_entry_get_type)
+STUB(gtk_entry_new)
+STUB(gtk_entry_set_activates_default)
+STUB(gtk_entry_set_text)
+STUB(gtk_enumerate_printers)
+STUB(gtk_expander_new)
+STUB(gtk_file_chooser_add_filter)
+STUB(gtk_file_chooser_dialog_new)
+STUB(gtk_file_chooser_get_filenames)
+STUB(gtk_file_chooser_get_filter)
+STUB(gtk_file_chooser_get_preview_filename)
+STUB(gtk_file_chooser_get_type)
+STUB(gtk_file_chooser_get_uri)
+STUB(gtk_file_chooser_list_filters)
+STUB(gtk_file_chooser_set_current_folder)
+STUB(gtk_file_chooser_set_current_name)
+STUB(gtk_file_chooser_set_do_overwrite_confirmation)
+STUB(gtk_file_chooser_set_filename)
+STUB(gtk_file_chooser_set_filter)
+STUB(gtk_file_chooser_set_local_only)
+STUB(gtk_file_chooser_set_preview_widget)
+STUB(gtk_file_chooser_set_preview_widget_active)
+STUB(gtk_file_chooser_set_select_multiple)
+STUB(gtk_file_chooser_widget_get_type)
+STUB(gtk_file_filter_add_pattern)
+STUB(gtk_file_filter_new)
+STUB(gtk_file_filter_set_name)
+STUB(gtk_fixed_new)
+STUB(gtk_frame_new)
+STUB(gtk_get_current_event_time)
+STUB(gtk_grab_add)
+STUB(gtk_grab_remove)
+STUB(gtk_handle_box_new)
+STUB(gtk_hbox_new)
+STUB(gtk_icon_info_free)
+STUB(gtk_icon_info_load_icon)
+STUB(gtk_icon_set_add_source)
+STUB(gtk_icon_set_new)
+STUB(gtk_icon_set_render_icon)
+STUB(gtk_icon_set_unref)
+STUB(gtk_icon_size_lookup)
+STUB(gtk_icon_source_free)
+STUB(gtk_icon_source_new)
+STUB(gtk_icon_source_set_icon_name)
+STUB(gtk_icon_theme_add_builtin_icon)
+STUB(gtk_icon_theme_get_default)
+STUB(gtk_icon_theme_get_icon_sizes)
+STUB(gtk_icon_theme_lookup_by_gicon)
+STUB(gtk_icon_theme_lookup_icon)
+STUB(gtk_image_get_icon_name)
+STUB(gtk_image_get_type)
+STUB(gtk_image_new)
+STUB(gtk_image_new_from_icon_name)
+STUB(gtk_image_new_from_stock)
+STUB(gtk_image_set_from_pixbuf)
+STUB(gtk_im_context_filter_keypress)
+STUB(gtk_im_context_focus_in)
+STUB(gtk_im_context_focus_out)
+STUB(gtk_im_context_get_preedit_string)
+STUB(gtk_im_context_reset)
+STUB(gtk_im_context_set_client_window)
+STUB(gtk_im_context_set_cursor_location)
+STUB(gtk_im_context_set_surrounding)
+STUB(gtk_im_context_simple_new)
+STUB(gtk_im_multicontext_get_context_id)
+STUB(gtk_im_multicontext_get_type)
+STUB(gtk_im_multicontext_new)
+STUB(gtk_info_bar_get_type)
+STUB(gtk_info_bar_get_content_area)
+STUB(gtk_info_bar_new)
+STUB(gtk_init)
+STUB(gtk_invisible_new)
+STUB(gtk_key_snooper_install)
+STUB(gtk_key_snooper_remove)
+STUB(gtk_label_get_type)
+STUB(gtk_label_new)
+STUB(gtk_label_set_markup)
+STUB(gtk_link_button_new)
+STUB(gtk_main_do_event)
+STUB(gtk_main_iteration)
+STUB(gtk_menu_attach_to_widget)
+STUB(gtk_menu_bar_new)
+STUB(gtk_menu_get_type)
+STUB(gtk_menu_item_get_type)
+STUB(gtk_menu_item_new)
+STUB(gtk_menu_item_set_submenu)
+STUB(gtk_menu_new)
+STUB(gtk_menu_shell_append)
+STUB(gtk_menu_shell_get_type)
+STUB(gtk_misc_get_alignment)
+STUB(gtk_misc_get_padding)
+STUB(gtk_misc_get_type)
+STUB(gtk_misc_set_alignment)
+STUB(gtk_misc_set_padding)
+STUB(gtk_notebook_new)
+STUB(gtk_page_setup_copy)
+STUB(gtk_page_setup_get_bottom_margin)
+STUB(gtk_page_setup_get_left_margin)
+STUB(gtk_page_setup_get_orientation)
+STUB(gtk_page_setup_get_paper_size)
+STUB(gtk_page_setup_get_right_margin)
+STUB(gtk_page_setup_get_top_margin)
+STUB(gtk_page_setup_new)
+STUB(gtk_page_setup_set_bottom_margin)
+STUB(gtk_page_setup_set_left_margin)
+STUB(gtk_page_setup_set_orientation)
+STUB(gtk_page_setup_set_paper_size)
+STUB(gtk_page_setup_set_paper_size_and_default_margins)
+STUB(gtk_page_setup_set_right_margin)
+STUB(gtk_page_setup_set_top_margin)
+STUB(gtk_page_setup_to_key_file)
+STUB(gtk_paper_size_free)
+STUB(gtk_paper_size_get_display_name)
+STUB(gtk_paper_size_get_height)
+STUB(gtk_paper_size_get_name)
+STUB(gtk_paper_size_get_width)
+STUB(gtk_paper_size_is_custom)
+STUB(gtk_paper_size_is_equal)
+STUB(gtk_paper_size_new)
+STUB(gtk_paper_size_new_custom)
+STUB(gtk_paper_size_set_size)
+STUB(gtk_parse_args)
+STUB(gtk_plug_get_socket_window)
+STUB(gtk_plug_get_type)
+STUB(gtk_printer_accepts_pdf)
+STUB(gtk_printer_get_name)
+STUB(gtk_printer_get_type)
+STUB(gtk_printer_is_default)
+STUB(gtk_print_job_new)
+STUB(gtk_print_job_send)
+STUB(gtk_print_job_set_source_file)
+STUB(gtk_print_run_page_setup_dialog)
+STUB(gtk_print_settings_copy)
+STUB(gtk_print_settings_foreach)
+STUB(gtk_print_settings_get)
+STUB(gtk_print_settings_get_duplex)
+STUB(gtk_print_settings_get_n_copies)
+STUB(gtk_print_settings_get_page_ranges)
+STUB(gtk_print_settings_get_paper_size)
+STUB(gtk_print_settings_get_printer)
+STUB(gtk_print_settings_get_print_pages)
+STUB(gtk_print_settings_get_resolution)
+STUB(gtk_print_settings_get_reverse)
+STUB(gtk_print_settings_get_scale)
+STUB(gtk_print_settings_get_use_color)
+STUB(gtk_print_settings_has_key)
+STUB(gtk_print_settings_new)
+STUB(gtk_print_settings_set)
+STUB(gtk_print_settings_set_duplex)
+STUB(gtk_print_settings_set_n_copies)
+STUB(gtk_print_settings_set_orientation)
+STUB(gtk_print_settings_set_page_ranges)
+STUB(gtk_print_settings_set_paper_size)
+STUB(gtk_print_settings_set_printer)
+STUB(gtk_print_settings_set_print_pages)
+STUB(gtk_print_settings_set_resolution)
+STUB(gtk_print_settings_set_reverse)
+STUB(gtk_print_settings_set_scale)
+STUB(gtk_print_settings_set_use_color)
+STUB(gtk_print_unix_dialog_add_custom_tab)
+STUB(gtk_print_unix_dialog_get_page_setup)
+STUB(gtk_print_unix_dialog_get_selected_printer)
+STUB(gtk_print_unix_dialog_get_settings)
+STUB(gtk_print_unix_dialog_get_type)
+STUB(gtk_print_unix_dialog_new)
+STUB(gtk_print_unix_dialog_set_manual_capabilities)
+STUB(gtk_print_unix_dialog_set_page_setup)
+STUB(gtk_print_unix_dialog_set_settings)
+STUB(gtk_progress_bar_new)
+STUB(gtk_propagate_event)
+STUB(gtk_radio_button_get_type)
+STUB(gtk_radio_button_new_with_label)
+STUB(gtk_radio_button_new_with_mnemonic)
+STUB(gtk_radio_button_new_with_mnemonic_from_widget)
+STUB(gtk_range_get_min_slider_size)
+STUB(gtk_range_get_type)
+STUB(gtk_recent_manager_add_item)
+STUB(gtk_recent_manager_get_default)
+STUB(gtk_scrollbar_get_type)
+STUB(gtk_scrolled_window_new)
+STUB(gtk_selection_data_copy)
+STUB(gtk_selection_data_free)
+STUB(gtk_selection_data_get_data)
+STUB(gtk_selection_data_get_length)
+STUB(gtk_selection_data_get_selection)
+STUB(gtk_selection_data_get_target)
+STUB(gtk_selection_data_get_targets)
+STUB(gtk_selection_data_set)
+STUB(gtk_selection_data_set_pixbuf)
+STUB(gtk_selection_data_set_text)
+STUB(gtk_selection_data_targets_include_text)
+STUB(gtk_separator_get_type)
+STUB(gtk_separator_menu_item_new)
+STUB(gtk_separator_tool_item_new)
+STUB(gtk_settings_get_default)
+STUB(gtk_settings_get_for_screen)
+STUB(gtk_show_uri)
+STUB(gtk_socket_add_id)
+STUB(gtk_socket_get_id)
+STUB(gtk_socket_get_type)
+STUB(gtk_socket_get_plug_window)
+STUB(gtk_socket_new)
+STUB(gtk_spin_button_new)
+STUB(gtk_statusbar_new)
+STUB(gtk_style_lookup_icon_set)
+STUB(gtk_table_attach)
+STUB(gtk_table_get_type)
+STUB(gtk_table_new)
+STUB(gtk_target_list_add)
+STUB(gtk_target_list_add_image_targets)
+STUB(gtk_target_list_add_text_targets)
+STUB(gtk_target_list_new)
+STUB(gtk_target_list_unref)
+STUB(gtk_targets_include_image)
+STUB(gtk_targets_include_text)
+STUB(gtk_target_table_free)
+STUB(gtk_target_table_new_from_list)
+STUB(gtk_text_view_new)
+STUB(gtk_toggle_button_get_active)
+STUB(gtk_toggle_button_get_type)
+STUB(gtk_toggle_button_new)
+STUB(gtk_toggle_button_set_active)
+STUB(gtk_toggle_button_set_inconsistent)
+STUB(gtk_toolbar_new)
+STUB(gtk_tooltip_get_type)
+STUB(gtk_tree_view_append_column)
+STUB(gtk_tree_view_column_new)
+STUB(gtk_tree_view_column_set_title)
+STUB(gtk_tree_view_get_type)
+STUB(gtk_tree_view_new)
+STUB(gtk_vbox_new)
+STUB(gtk_widget_add_events)
+STUB(gtk_widget_class_find_style_property)
+STUB(gtk_widget_destroy)
+STUB(gtk_widget_destroyed)
+STUB(gtk_widget_ensure_style)
+STUB(gtk_widget_event)
+STUB(gtk_widget_get_accessible)
+STUB(gtk_widget_get_allocation)
+STUB(gtk_widget_get_default_direction)
+STUB(gtk_widget_get_display)
+STUB(gtk_widget_get_events)
+STUB(gtk_widget_get_has_window)
+STUB(gtk_widget_get_mapped)
+STUB(gtk_widget_get_parent)
+STUB(gtk_widget_get_parent_window)
+STUB(gtk_widget_get_realized)
+STUB(gtk_widget_get_screen)
+STUB(gtk_widget_get_style)
+STUB(gtk_widget_get_toplevel)
+STUB(gtk_widget_get_type)
+STUB(gtk_widget_get_visible)
+STUB(gtk_widget_get_visual)
+STUB(gtk_widget_get_window)
+STUB(gtk_widget_grab_focus)
+STUB(gtk_widget_has_focus)
+STUB(gtk_widget_has_grab)
+STUB(gtk_widget_hide)
+STUB(gtk_widget_is_focus)
+STUB(gtk_widget_is_toplevel)
+STUB(gtk_widget_map)
+STUB(gtk_widget_modify_bg)
+STUB(gtk_widget_realize)
+STUB(gtk_widget_reparent)
+STUB(gtk_widget_set_allocation)
+STUB(gtk_widget_set_app_paintable)
+STUB(gtk_window_set_auto_startup_notification)
+STUB(gtk_window_set_keep_above)
+STUB(gtk_window_set_opacity)
+STUB(gtk_window_set_screen)
+STUB(gtk_widget_set_can_focus)
+STUB(gtk_widget_set_direction)
+STUB(gtk_widget_set_double_buffered)
+STUB(gtk_widget_set_has_window)
+STUB(gtk_widget_set_mapped)
+STUB(gtk_widget_set_name)
+STUB(gtk_widget_set_parent)
+STUB(gtk_widget_set_parent_window)
+STUB(gtk_widget_set_realized)
+STUB(gtk_widget_set_redraw_on_allocate)
+STUB(gtk_widget_set_sensitive)
+STUB(gtk_widget_set_window)
+STUB(gtk_widget_show)
+STUB(gtk_widget_show_all)
+STUB(gtk_widget_size_allocate)
+STUB(gtk_widget_style_get)
+STUB(gtk_widget_unparent)
+STUB(gtk_widget_unrealize)
+STUB(gtk_window_deiconify)
+STUB(gtk_window_fullscreen)
+STUB(gtk_window_get_group)
+STUB(gtk_window_get_modal)
+STUB(gtk_window_get_transient_for)
+STUB(gtk_window_get_type)
+STUB(gtk_window_get_type_hint)
+STUB(gtk_window_get_window_type)
+STUB(gtk_window_group_add_window)
+STUB(gtk_window_group_get_current_grab)
+STUB(gtk_window_group_new)
+STUB(gtk_window_iconify)
+STUB(gtk_window_is_active)
+STUB(gtk_window_maximize)
+STUB(gtk_window_move)
+STUB(gtk_window_new)
+STUB(gtk_window_present_with_time)
+STUB(gtk_window_resize)
+STUB(gtk_window_set_accept_focus)
+STUB(gtk_window_set_decorated)
+STUB(gtk_window_set_deletable)
+STUB(gtk_window_set_destroy_with_parent)
+STUB(gtk_window_set_focus_on_map)
+STUB(gtk_window_set_geometry_hints)
+STUB(gtk_window_set_icon_name)
+STUB(gtk_window_set_modal)
+STUB(gtk_window_set_skip_taskbar_hint)
+STUB(gtk_window_set_startup_id)
+STUB(gtk_window_set_title)
+STUB(gtk_window_set_transient_for)
+STUB(gtk_window_set_type_hint)
+STUB(gtk_window_set_wmclass)
+STUB(gtk_window_unfullscreen)
+STUB(gtk_window_unmaximize)
+#endif
+
+#ifdef GTK3_SYMBOLS
+STUB(gtk_css_provider_load_from_data)
+STUB(gtk_css_provider_new)
+STUB(gdk_device_get_source)
+STUB(gdk_device_manager_get_client_pointer)
+STUB(gdk_disable_multidevice)
+STUB(gdk_device_manager_list_devices)
+STUB(gdk_display_get_device_manager)
+STUB(gdk_display_manager_open_display)
+STUB(gdk_error_trap_pop_ignored)
+STUB(gdk_event_get_source_device)
+STUB(gdk_screen_get_monitor_workarea)
+STUB(gdk_window_get_type)
+STUB(gdk_window_set_opaque_region)
+STUB(gdk_x11_window_get_xid)
+STUB(gdk_x11_display_get_type)
+STUB(gdk_wayland_display_get_type)
+STUB(gdk_wayland_display_get_wl_compositor)
+STUB(gdk_wayland_display_get_wl_display)
+STUB(gdk_wayland_window_get_wl_surface)
+STUB(gtk_box_new)
+STUB(gtk_cairo_should_draw_window)
+STUB(gtk_cairo_transform_to_window)
+STUB(gtk_combo_box_text_append)
+STUB(gtk_drag_set_icon_surface)
+STUB(gtk_get_major_version)
+STUB(gtk_get_micro_version)
+STUB(gtk_get_minor_version)
+STUB(gtk_icon_info_load_symbolic_for_context)
+STUB(gtk_menu_button_new)
+STUB(gtk_offscreen_window_new)
+STUB(gtk_paned_new)
+STUB(gtk_radio_menu_item_new)
+STUB(gtk_render_activity)
+STUB(gtk_render_arrow)
+STUB(gtk_render_background)
+STUB(gtk_render_check)
+STUB(gtk_render_expander)
+STUB(gtk_render_extension)
+STUB(gtk_render_focus)
+STUB(gtk_render_frame)
+STUB(gtk_render_frame_gap)
+STUB(gtk_render_handle)
+STUB(gtk_render_icon)
+STUB(gtk_render_line)
+STUB(gtk_render_option)
+STUB(gtk_render_slider)
+STUB(gtk_scale_new)
+STUB(gtk_scrollbar_new)
+STUB(gtk_style_context_add_class)
+STUB(gtk_style_context_add_provider)
+STUB(gtk_style_context_add_region)
+STUB(gtk_style_context_get)
+STUB(gtk_style_context_get_background_color)
+STUB(gtk_style_context_get_border)
+STUB(gtk_style_context_get_border_color)
+STUB(gtk_style_context_get_color)
+STUB(gtk_style_context_get_direction)
+STUB(gtk_style_context_get_margin)
+STUB(gtk_style_context_get_padding)
+STUB(gtk_style_context_get_path)
+STUB(gtk_style_context_get_property)
+STUB(gtk_style_context_get_state)
+STUB(gtk_style_context_get_style)
+STUB(gtk_style_context_has_class)
+STUB(gtk_style_context_invalidate)
+STUB(gtk_style_context_list_classes)
+STUB(gtk_style_context_new)
+STUB(gtk_style_context_remove_class)
+STUB(gtk_style_context_remove_region)
+STUB(gtk_style_context_restore)
+STUB(gtk_style_context_save)
+STUB(gtk_style_context_set_direction)
+STUB(gtk_style_context_set_path)
+STUB(gtk_style_context_set_parent)
+STUB(gtk_style_context_set_state)
+STUB(gtk_style_properties_lookup_property)
+STUB(gtk_style_provider_get_type)
+STUB(gtk_tree_view_column_get_button)
+STUB(gtk_widget_get_preferred_size)
+STUB(gtk_widget_get_preferred_width)
+STUB(gtk_widget_get_preferred_height)
+STUB(gtk_widget_get_state_flags)
+STUB(gtk_widget_get_style_context)
+STUB(gtk_widget_path_append_type)
+STUB(gtk_widget_path_copy)
+STUB(gtk_widget_path_free)
+STUB(gtk_widget_path_iter_add_class)
+STUB(gtk_widget_path_get_object_type)
+STUB(gtk_widget_path_length)
+STUB(gtk_widget_path_new)
+STUB(gtk_widget_path_unref)
+STUB(gtk_widget_set_valign)
+STUB(gtk_widget_set_visual)
+STUB(gtk_window_set_titlebar)
+STUB(gtk_app_chooser_dialog_new_for_content_type)
+STUB(gtk_app_chooser_get_type)
+STUB(gtk_app_chooser_get_app_info)
+STUB(gtk_app_chooser_dialog_get_type)
+STUB(gtk_app_chooser_dialog_set_heading)
+STUB(gtk_color_chooser_dialog_new)
+STUB(gtk_color_chooser_dialog_get_type)
+STUB(gtk_color_chooser_get_type)
+STUB(gtk_color_chooser_set_rgba)
+STUB(gtk_color_chooser_get_rgba)
+STUB(gtk_color_chooser_set_use_alpha)
+#endif
+
+#ifdef GTK2_SYMBOLS
+STUB(gdk_drawable_get_screen)
+STUB(gdk_rgb_get_colormap)
+STUB(gdk_rgb_get_visual)
+STUB(gdk_window_lookup)
+STUB(gdk_window_set_back_pixmap)
+STUB(gdk_x11_colormap_foreign_new)
+STUB(gdk_x11_colormap_get_xcolormap)
+STUB(gdk_x11_drawable_get_xdisplay)
+STUB(gdk_x11_drawable_get_xid)
+STUB(gdk_x11_window_get_drawable_impl)
+STUB(gdkx_visual_get)
+STUB(gtk_object_get_type)
+#endif
+
+#ifndef GTK3_SYMBOLS
+// Only define the following workaround when using GTK3, which we detect
+// by checking if GTK3 stubs are not provided.
+# 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.
+MOZ_EXPORT Bool XShmQueryExtension(Display* aDisplay) { return False; }
+#endif
diff --git a/widget/gtk/mozgtk/stub/moz.build b/widget/gtk/mozgtk/stub/moz.build
new file mode 100644
index 0000000000..8af0cc1cdf
--- /dev/null
+++ b/widget/gtk/mozgtk/stub/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 += [
+ "../mozgtk.c",
+]
+
+for var in ("COMMON_SYMBOLS", "GTK2_SYMBOLS", "GTK3_SYMBOLS"):
+ DEFINES[var] = True
+
+SharedLibrary("mozgtk_stub")
+
+SONAME = "mozgtk"
diff --git a/widget/gtk/mozwayland/moz.build b/widget/gtk/mozwayland/moz.build
new file mode 100644
index 0000000000..94eca8a785
--- /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["TK_CFLAGS"]
diff --git a/widget/gtk/mozwayland/mozwayland.c b/widget/gtk/mozwayland/mozwayland.c
new file mode 100644
index 0000000000..8a79d87865
--- /dev/null
+++ b/widget/gtk/mozwayland/mozwayland.c
@@ -0,0 +1,201 @@
+/* -*- 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 strucures 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_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_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) {}
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..24bdd50833
--- /dev/null
+++ b/widget/gtk/nsAppShell.cpp
@@ -0,0 +1,253 @@
+/* -*- 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 <sys/types.h>
+#include <unistd.h>
+#include <fcntl.h>
+#include <errno.h>
+#include <gdk/gdk.h>
+#include "nsAppShell.h"
+#include "nsWindow.h"
+#include "mozilla/Logging.h"
+#include "prenv.h"
+#include "mozilla/BackgroundHangMonitor.h"
+#include "mozilla/Hal.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WidgetUtils.h"
+#include "GeckoProfiler.h"
+#include "nsIPowerManagerService.h"
+#ifdef MOZ_ENABLE_DBUS
+# include "WakeLockListener.h"
+#endif
+#include "gfxPlatform.h"
+#include "ScreenHelperGTK.h"
+#include "HeadlessScreenHelper.h"
+#include "mozilla/widget/ScreenManager.h"
+#ifdef MOZ_WAYLAND
+# include "nsWaylandDisplay.h"
+#endif
+
+using mozilla::LazyLogModule;
+using mozilla::Unused;
+using mozilla::widget::HeadlessScreenHelper;
+using mozilla::widget::ScreenHelperGTK;
+using mozilla::widget::ScreenManager;
+
+#define NOTIFY_TOKEN 0xFA
+
+LazyLogModule gWidgetLog("Widget");
+LazyLogModule gWidgetFocusLog("WidgetFocus");
+LazyLogModule gWidgetDragLog("WidgetDrag");
+LazyLogModule gWidgetDrawLog("WidgetDraw");
+LazyLogModule gWidgetWaylandLog("WidgetWayland");
+LazyLogModule gDmabufLog("Dmabuf");
+LazyLogModule gClipboardLog("WidgetClipboard");
+
+static GPollFunc sPollFunc;
+
+// Wrapper function to disable hang monitoring while waiting in poll().
+static gint PollWrapper(GPollFD* ufds, guint nfsd, gint timeout_) {
+ mozilla::BackgroundHangMonitor().NotifyWait();
+ gint result;
+ {
+ AUTO_PROFILER_LABEL("PollWrapper", IDLE);
+ AUTO_PROFILER_THREAD_SLEEP;
+ result = (*sPollFunc)(ufds, nfsd, timeout_);
+ }
+ 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);
+ NS_ASSERTION(c == (unsigned char)NOTIFY_TOKEN, "wrong token");
+
+ self->NativeEventCallback();
+ return TRUE;
+}
+
+nsAppShell::~nsAppShell() {
+ mozilla::hal::Shutdown();
+
+ if (mTag) g_source_remove(mTag);
+ if (mPipeFDs[0]) close(mPipeFDs[0]);
+ if (mPipeFDs[1]) close(mPipeFDs[1]);
+}
+
+nsresult nsAppShell::Init() {
+ mozilla::hal::Init();
+
+#ifdef MOZ_ENABLE_DBUS
+ if (XRE_IsParentProcess()) {
+ nsCOMPtr<nsIPowerManagerService> powerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+
+ if (powerManagerService) {
+ powerManagerService->AddWakeLockListener(
+ WakeLockListener::GetSingleton());
+ } else {
+ NS_WARNING(
+ "Failed to retrieve PowerManagerService, wakelocks will be broken!");
+ }
+ }
+#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. (At this point, a child process hasn't
+ // received the list of registered chrome packages, so the
+ // GetBrandShortName call would fail anyway.)
+ nsAutoString brandName;
+ mozilla::widget::WidgetUtils::GetBrandShortName(brandName);
+ if (!brandName.IsEmpty()) {
+ gdk_set_program_class(NS_ConvertUTF16toUTF8(brandName).get());
+ }
+ }
+ }
+
+ 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");
+ }
+
+ if (PR_GetEnv("MOZ_DEBUG_PAINTS")) {
+ gdk_window_set_debug_updates(TRUE);
+ }
+
+ // 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")) {
+ 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);
+
+ return nsBaseAppShell::Init();
+failed:
+ close(mPipeFDs[0]);
+ close(mPipeFDs[1]);
+ mPipeFDs[0] = mPipeFDs[1] = 0;
+ return NS_ERROR_FAILURE;
+}
+
+void nsAppShell::ScheduleNativeEventCallback() {
+ unsigned char buf[] = {NOTIFY_TOKEN};
+ Unused << write(mPipeFDs[1], buf, 1);
+}
+
+bool nsAppShell::ProcessNextNativeEvent(bool mayWait) {
+ bool ret = g_main_context_iteration(nullptr, mayWait);
+#ifdef MOZ_WAYLAND
+ mozilla::widget::WaylandDispatchDisplays();
+#endif
+ return ret;
+}
diff --git a/widget/gtk/nsAppShell.h b/widget/gtk/nsAppShell.h
new file mode 100644
index 0000000000..06543ee955
--- /dev/null
+++ b/widget/gtk/nsAppShell.h
@@ -0,0 +1,34 @@
+/* -*- 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__
+
+#include <glib.h>
+#include "nsBaseAppShell.h"
+#include "nsCOMPtr.h"
+
+class nsAppShell : public nsBaseAppShell {
+ public:
+ nsAppShell() : mTag(0) { mPipeFDs[0] = mPipeFDs[1] = 0; }
+
+ // nsBaseAppShell overrides:
+ nsresult Init();
+ virtual void ScheduleNativeEventCallback() override;
+ virtual bool ProcessNextNativeEvent(bool mayWait) override;
+
+ private:
+ virtual ~nsAppShell();
+
+ static gboolean EventProcessorCallback(GIOChannel* source,
+ GIOCondition condition, gpointer data);
+
+ int mPipeFDs[2];
+ unsigned mTag;
+};
+
+#endif /* nsAppShell_h__ */
diff --git a/widget/gtk/nsApplicationChooser.cpp b/widget/gtk/nsApplicationChooser.cpp
new file mode 100644
index 0000000000..2ebb62fd1d
--- /dev/null
+++ b/widget/gtk/nsApplicationChooser.cpp
@@ -0,0 +1,131 @@
+/* -*- 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 "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..42807845fb
--- /dev/null
+++ b/widget/gtk/nsClipboard.cpp
@@ -0,0 +1,822 @@
+/* -*- 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/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardX11.h"
+#if defined(MOZ_WAYLAND)
+# include "nsClipboardWayland.h"
+#endif
+#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 "nsIObserverService.h"
+#include "mozilla/Services.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/SchedulerGroup.h"
+#include "mozilla/TimeStamp.h"
+#include "gfxPlatformGtk.h"
+
+#include "imgIContainer.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+#include "mozilla/Encoding.h"
+
+using namespace mozilla;
+
+// Idle timeout for receiving selection and property notify events (microsec)
+const int kClipboardTimeout = 500000;
+
+// 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">)";
+
+// Callback when someone asks us for the data
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data);
+
+// Callback when someone asks us to clear a clipboard
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data);
+
+static bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength,
+ nsCString& charset, char16_t** unicodeData,
+ int32_t& outUnicodeLen);
+
+static bool GetHTMLCharset(const char* data, int32_t dataLength,
+ nsCString& str);
+
+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;
+ else
+ return -1; // THAT AIN'T NO CLIPBOARD I EVER HEARD OF
+}
+
+nsClipboard::nsClipboard() = default;
+
+nsClipboard::~nsClipboard() {
+ // We have to clear clipboard before gdk_display_close() call.
+ // See bug 531580 for details.
+ if (mGlobalTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }
+ if (mSelectionTransferable) {
+ gtk_clipboard_clear(gtk_clipboard_get(GDK_SELECTION_PRIMARY));
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsClipboard, nsIClipboard, nsIObserver)
+
+nsresult nsClipboard::Init(void) {
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ mContext = MakeUnique<nsRetrievalContextX11>();
+#if defined(MOZ_WAYLAND)
+ } else {
+ mContext = MakeUnique<nsRetrievalContextWayland>();
+#endif
+ }
+ NS_ASSERTION(mContext, "Missing nsRetrievalContext for nsClipboard!");
+
+ 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(
+ TaskCategory::Other,
+ NS_NewRunnableFunction("gtk_clipboard_store()", []() {
+ gtk_clipboard_store(gtk_clipboard_get(GDK_SELECTION_CLIPBOARD));
+ }));
+}
+
+NS_IMETHODIMP
+nsClipboard::SetData(nsITransferable* aTransferable, nsIClipboardOwner* aOwner,
+ int32_t aWhichClipboard) {
+ // See if we can short cut
+ if ((aWhichClipboard == kGlobalClipboard &&
+ aTransferable == mGlobalTransferable.get() &&
+ aOwner == mGlobalOwner.get()) ||
+ (aWhichClipboard == kSelectionClipboard &&
+ aTransferable == mSelectionTransferable.get() &&
+ aOwner == mSelectionOwner.get())) {
+ return NS_OK;
+ }
+
+ LOGCLIP(("nsClipboard::SetData (%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];
+
+ // Special case text/unicode since we can handle all of the string types.
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ LOGCLIP((" 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((" image targets\n"));
+ gtk_target_list_add_image_targets(list, 0, TRUE);
+ imagesAdded = true;
+ }
+ continue;
+ }
+
+ // Add this to our list of valid targets
+ 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;
+ GtkTargetEntry* gtkTargets =
+ gtk_target_table_new_from_list(list, &numTargets);
+
+ LOGCLIP((" gtk_target_table_new_from_list() = %p\n", (void*)gtkTargets));
+
+ // Set getcallback and request to store data after an application exit
+ if (gtkTargets &&
+ gtk_clipboard_set_with_data(gtkClipboard, gtkTargets, numTargets,
+ clipboard_get_cb, clipboard_clear_cb, this)) {
+ LOGCLIP((" gtk_clipboard_set_with_data() is ok\n"));
+ // 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) {
+ mSelectionOwner = aOwner;
+ mSelectionTransferable = aTransferable;
+ } else {
+ mGlobalOwner = aOwner;
+ mGlobalTransferable = aTransferable;
+ gtk_clipboard_set_can_store(gtkClipboard, gtkTargets, numTargets);
+ }
+
+ rv = NS_OK;
+ } else {
+ LOGCLIP((" gtk_clipboard_set_with_data() failed!\n"));
+ // Clear references to the any old data and let GTK know that it is no
+ // longer available.
+ EmptyClipboard(aWhichClipboard);
+ rv = NS_ERROR_FAILURE;
+ }
+
+ gtk_target_table_free(gtkTargets, numTargets);
+ gtk_target_list_unref(list);
+
+ return rv;
+}
+
+void nsClipboard::SetTransferableData(nsITransferable* aTransferable,
+ nsCString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength) {
+ LOGCLIP(("nsClipboard::SetTransferableData MIME %s\n", aFlavor.get()));
+
+ nsCOMPtr<nsISupports> wrapper;
+ nsPrimitiveHelpers::CreatePrimitiveForData(
+ aFlavor, aClipboardData, aClipboardDataLength, getter_AddRefs(wrapper));
+ aTransferable->SetTransferData(aFlavor.get(), wrapper);
+}
+
+NS_IMETHODIMP
+nsClipboard::GetData(nsITransferable* aTransferable, int32_t aWhichClipboard) {
+ if (!aTransferable) return NS_ERROR_FAILURE;
+
+ LOGCLIP(("nsClipboard::GetData (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ // Get a list of flavors this transferable can import
+ nsTArray<nsCString> flavors;
+ nsresult rv = aTransferable->FlavorsTransferableCanImport(flavors);
+ if (NS_FAILED(rv)) {
+ LOGCLIP((" FlavorsTransferableCanImport falied!\n"));
+ return rv;
+ }
+
+#ifdef MOZ_LOGGING
+ LOGCLIP(("Flavors which can be imported:\n"));
+ for (uint32_t i = 0; i < flavors.Length(); i++) {
+ LOGCLIP((" %s\n", flavors[i].get()));
+ }
+#endif
+
+ 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()));
+
+ uint32_t clipboardDataLength;
+ const char* clipboardData = mContext->GetClipboardData(
+ flavorStr.get(), aWhichClipboard, &clipboardDataLength);
+ if (!clipboardData) {
+ LOGCLIP((" %s type is missing\n", flavorStr.get()));
+ continue;
+ }
+
+ nsCOMPtr<nsIInputStream> byteStream;
+ NS_NewByteInputStream(getter_AddRefs(byteStream),
+ Span(clipboardData, clipboardDataLength),
+ NS_ASSIGNMENT_COPY);
+ aTransferable->SetTransferData(flavorStr.get(), byteStream);
+ LOGCLIP((" got %s MIME data\n", flavorStr.get()));
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+
+ // Special case text/unicode since we can convert any
+ // string into text/unicode
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ LOGCLIP(
+ (" Getting unicode %s MIME clipboard data\n", flavorStr.get()));
+
+ const char* clipboardData = mContext->GetClipboardText(aWhichClipboard);
+ if (!clipboardData) {
+ LOGCLIP((" failed to get unicode data\n"));
+ // If the type was text/unicode and we couldn't get
+ // text off the clipboard, run the next loop
+ // iteration.
+ continue;
+ }
+
+ // Convert utf-8 into our unicode format.
+ NS_ConvertUTF8toUTF16 ucs2string(clipboardData);
+ const char* unicodeData = (const char*)ToNewUnicode(ucs2string);
+ uint32_t unicodeDataLength = ucs2string.Length() * 2;
+ SetTransferableData(aTransferable, flavorStr, unicodeData,
+ unicodeDataLength);
+ free((void*)unicodeData);
+
+ LOGCLIP((" got unicode data, length %d\n", ucs2string.Length()));
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+
+ LOGCLIP((" Getting %s MIME clipboard data\n", flavorStr.get()));
+
+ uint32_t clipboardDataLength;
+ const char* clipboardData = mContext->GetClipboardData(
+ flavorStr.get(), aWhichClipboard, &clipboardDataLength);
+
+#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)) {
+ char16_t* htmlBody = nullptr;
+ int32_t htmlBodyLen = 0;
+ // Convert text/html into our unicode format
+ nsAutoCString charset;
+ if (!GetHTMLCharset(clipboardData, clipboardDataLength, 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");
+ }
+ if (!ConvertHTMLtoUCS2(clipboardData, clipboardDataLength, charset,
+ &htmlBody, htmlBodyLen)) {
+ LOGCLIP((" failed to convert text/html to UCS2.\n"));
+ mContext->ReleaseClipboardData(clipboardData);
+ continue;
+ }
+
+ SetTransferableData(aTransferable, flavorStr, (const char*)htmlBody,
+ htmlBodyLen * 2);
+ free(htmlBody);
+ } else {
+ SetTransferableData(aTransferable, flavorStr, clipboardData,
+ clipboardDataLength);
+ }
+
+ mContext->ReleaseClipboardData(clipboardData);
+ return NS_OK;
+ }
+ }
+
+ LOGCLIP((" failed to get clipboard content.\n"));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::EmptyClipboard(int32_t aWhichClipboard) {
+ LOGCLIP(("nsClipboard::EmptyClipboard (%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);
+ }
+ }
+
+ return NS_OK;
+}
+
+void nsClipboard::ClearTransferable(int32_t aWhichClipboard) {
+ if (aWhichClipboard == kSelectionClipboard) {
+ if (mSelectionOwner) {
+ mSelectionOwner->LosingOwnership(mSelectionTransferable);
+ mSelectionOwner = nullptr;
+ }
+ mSelectionTransferable = nullptr;
+ } else {
+ if (mGlobalOwner) {
+ mGlobalOwner->LosingOwnership(mGlobalTransferable);
+ mGlobalOwner = nullptr;
+ }
+ mGlobalTransferable = nullptr;
+ }
+}
+
+NS_IMETHODIMP
+nsClipboard::HasDataMatchingFlavors(const nsTArray<nsCString>& aFlavorList,
+ int32_t aWhichClipboard, bool* _retval) {
+ if (!_retval) {
+ return NS_ERROR_NULL_POINTER;
+ }
+
+ LOGCLIP(("nsClipboard::HasDataMatchingFlavors (%s)\n",
+ aWhichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ *_retval = false;
+
+ int targetNums;
+ GdkAtom* targets = mContext->GetTargets(aWhichClipboard, &targetNums);
+ if (!targets) {
+ LOGCLIP((" no targes at clipboard (null)\n"));
+ return NS_OK;
+ }
+
+ // Walk through the provided types and try to match it to a
+ // provided type.
+ for (auto& flavor : aFlavorList) {
+ // We special case text/unicode here.
+ if (flavor.EqualsLiteral(kUnicodeMime) &&
+ gtk_targets_include_text(targets, targetNums)) {
+ *_retval = true;
+ LOGCLIP((" has kUnicodeMime\n"));
+ break;
+ }
+
+ for (int32_t j = 0; j < targetNums; j++) {
+ gchar* atom_name = gdk_atom_name(targets[j]);
+ if (!atom_name) continue;
+
+ if (flavor.Equals(atom_name)) {
+ *_retval = true;
+ LOGCLIP((" has %s\n", atom_name));
+ }
+ // X clipboard supports image/jpeg, but we want to emulate support
+ // for image/jpg as well
+ else if (flavor.EqualsLiteral(kJPGImageMime) &&
+ !strcmp(atom_name, kJPEGImageMime)) {
+ *_retval = true;
+ LOGCLIP((" has image/jpg\n"));
+ }
+
+ g_free(atom_name);
+
+ if (*_retval) break;
+ }
+ }
+
+#ifdef MOZ_LOGGING
+ if (!(*_retval)) {
+ LOGCLIP((" no targes at clipboard (bad match)\n"));
+ }
+#endif
+
+ g_free(targets);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsSelectionClipboard(bool* _retval) {
+ *_retval = mContext->HasSelectionSupport();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsClipboard::SupportsFindClipboard(bool* _retval) {
+ *_retval = false;
+ return NS_OK;
+}
+
+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/unicode 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", gdk_atom_name(selectionTarget)));
+
+ // Check to see if the selection data is some text type.
+ if (gtk_targets_include_text(&selectionTarget, 1)) {
+ LOGCLIP((" providing text/unicode data\n"));
+ // Try to convert our internal type into a text string. Get
+ // the transferable for this clipboard and try to get the
+ // text/unicode type for it.
+ rv = trans->GetTransferData("text/unicode", getter_AddRefs(item));
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP((" GetTransferData() failed to get text/unicode!\n"));
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> wideString;
+ wideString = do_QueryInterface(item);
+ if (!wideString) return;
+
+ nsAutoString ucs2string;
+ wideString->GetData(ucs2string);
+ NS_ConvertUTF16toUTF8 utf8string(ucs2string);
+
+ LOGCLIP((" sent %d 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;
+ }
+
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(image);
+ if (!pixbuf) {
+ LOGCLIP((" nsImageToPixbuf::ImageToPixbuf() failed!\n"));
+ return;
+ }
+
+ LOGCLIP((" Setting pixbuf image data as %s\n",
+ gdk_atom_name(selectionTarget)));
+ gtk_selection_data_set_pixbuf(aSelectionData, pixbuf);
+ g_object_unref(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 %d bytest of %s data\n", html.Length(),
+ gdk_atom_name(selectionTarget)));
+ gtk_selection_data_set(aSelectionData, selectionTarget, 8,
+ (const guchar*)html.get(), html.Length());
+ return;
+ }
+
+ LOGCLIP((" Try if we have anything at GetTransferData() for %s\n",
+ gdk_atom_name(selectionTarget)));
+
+ // Try to match up the selection data target to something our
+ // transferable provides.
+ gchar* target_name = gdk_atom_name(selectionTarget);
+ if (!target_name) {
+ LOGCLIP((" Failed to get target name!\n"));
+ return;
+ }
+
+ rv = trans->GetTransferData(target_name, getter_AddRefs(item));
+ // nothing found?
+ if (NS_FAILED(rv) || !item) {
+ LOGCLIP((" Failed to get anything from GetTransferData()!\n"));
+ g_free(target_name);
+ return;
+ }
+
+ void* primitive_data = nullptr;
+ uint32_t dataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(nsDependentCString(target_name),
+ item, &primitive_data, &dataLen);
+
+ if (primitive_data) {
+ LOGCLIP((" Setting %s as a primitive data type, %d bytes\n", target_name,
+ dataLen));
+ gtk_selection_data_set(aSelectionData, selectionTarget,
+ 8, /* 8 bits in a unit */
+ (const guchar*)primitive_data, dataLen);
+ free(primitive_data);
+ } else {
+ LOGCLIP((" Failed to get primitive data!\n"));
+ }
+
+ g_free(target_name);
+}
+
+void nsClipboard::SelectionClearEvent(GtkClipboard* aGtkClipboard) {
+ int32_t whichClipboard = GetGeckoClipboardType(aGtkClipboard);
+ if (whichClipboard < 0) {
+ return;
+ }
+
+ LOGCLIP(("nsClipboard::SelectionClearEvent (%s)\n",
+ whichClipboard == kSelectionClipboard ? "primary" : "clipboard"));
+
+ ClearTransferable(whichClipboard);
+}
+
+void clipboard_get_cb(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData, guint info,
+ gpointer user_data) {
+ LOGCLIP(("clipboard_get_cb() callback\n"));
+ nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
+ aClipboard->SelectionGetEvent(aGtkClipboard, aSelectionData);
+}
+
+void clipboard_clear_cb(GtkClipboard* aGtkClipboard, gpointer user_data) {
+ LOGCLIP(("clipboard_clear_cb() callback\n"));
+ nsClipboard* aClipboard = static_cast<nsClipboard*>(user_data);
+ aClipboard->SelectionClearEvent(aGtkClipboard);
+}
+
+/*
+ * when copy-paste, mozilla wants data encoded using UCS2,
+ * other app such as StarOffice use "text/html"(RFC2854).
+ * This function convert data(got from GTK clipboard)
+ * to data mozilla wanted.
+ *
+ * data from GTK clipboard can be 3 forms:
+ * 1. From current mozilla
+ * "text/html", charset = utf-16
+ * 2. From old version mozilla or mozilla-based app
+ * content("body" only), charset = utf-16
+ * 3. From other app who use "text/html" when copy-paste
+ * "text/html", has "charset" info
+ *
+ * data : got from GTK clipboard
+ * dataLength: got from GTK clipboard
+ * body : pass to Mozilla
+ * bodyLength: pass to Mozilla
+ */
+bool ConvertHTMLtoUCS2(const char* data, int32_t dataLength, nsCString& charset,
+ char16_t** unicodeData, int32_t& outUnicodeLen) {
+ if (charset.EqualsLiteral("UTF-16")) { // current mozilla
+ outUnicodeLen = (dataLength / 2) - 1;
+ *unicodeData = reinterpret_cast<char16_t*>(
+ moz_xmalloc((outUnicodeLen + sizeof('\0')) * sizeof(char16_t)));
+ memcpy(*unicodeData, data + sizeof(char16_t),
+ outUnicodeLen * sizeof(char16_t));
+ (*unicodeData)[outUnicodeLen] = '\0';
+ return true;
+ } else if (charset.EqualsLiteral("UNKNOWN")) {
+ outUnicodeLen = 0;
+ return false;
+ } else {
+ // app which use "text/html" to copy&paste
+ // get the decoder
+ auto encoding = Encoding::ForLabelNoReplacement(charset);
+ if (!encoding) {
+ LOGCLIP(("ConvertHTMLtoUCS2: get unicode decoder error\n"));
+ outUnicodeLen = 0;
+ return false;
+ }
+
+ auto dataSpan = Span(data, dataLength);
+ // Remove kHTMLMarkupPrefix again, it won't necessarily cause any
+ // issues, but might confuse other users.
+ const size_t prefixLen = ArrayLength(kHTMLMarkupPrefix) - 1;
+ if (dataSpan.Length() >= prefixLen &&
+ Substring(data, prefixLen).EqualsLiteral(kHTMLMarkupPrefix)) {
+ dataSpan = dataSpan.From(prefixLen);
+ }
+
+ auto decoder = encoding->NewDecoder();
+ CheckedInt<size_t> needed =
+ decoder->MaxUTF16BufferLength(dataSpan.Length());
+ if (!needed.isValid() || needed.value() > INT32_MAX) {
+ outUnicodeLen = 0;
+ return false;
+ }
+
+ outUnicodeLen = 0;
+ if (needed.value()) {
+ *unicodeData = reinterpret_cast<char16_t*>(
+ moz_xmalloc((needed.value() + 1) * sizeof(char16_t)));
+ uint32_t result;
+ size_t read;
+ size_t written;
+ bool hadErrors;
+ Tie(result, read, written, hadErrors) = decoder->DecodeToUTF16(
+ AsBytes(dataSpan), Span(*unicodeData, needed.value()), true);
+ MOZ_ASSERT(result == kInputEmpty);
+ MOZ_ASSERT(read == size_t(dataSpan.Length()));
+ MOZ_ASSERT(written <= needed.value());
+ Unused << hadErrors;
+ outUnicodeLen = written;
+ // null terminate.
+ (*unicodeData)[outUnicodeLen] = '\0';
+ return true;
+ } // if valid length
+ }
+ return false;
+}
+
+/*
+ * get "charset" information from clipboard data
+ * return value can be:
+ * 1. "UTF-16": mozilla or "text/html" with "charset=utf-16"
+ * 2. "UNKNOWN": mozilla can't detect what encode it use
+ * 3. other: "text/html" with other charset than utf-16
+ */
+bool GetHTMLCharset(const char* data, int32_t dataLength, nsCString& str) {
+ // if detect "FFFE" or "FEFF", assume UTF-16
+ char16_t* beginChar = (char16_t*)data;
+ if ((beginChar[0] == 0xFFFE) || (beginChar[0] == 0xFEFF)) {
+ str.AssignLiteral("UTF-16");
+ LOGCLIP(("GetHTMLCharset: Charset of HTML is UTF-16\n"));
+ return true;
+ }
+ // no "FFFE" and "FEFF", assume ASCII first to find "charset" info
+ const nsDependentCSubstring htmlStr(data, dataLength);
+ 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) {
+ str = Substring(valueStart, valueEnd);
+ ToUpperCase(str);
+ LOGCLIP(("GetHTMLCharset: Charset of HTML = %s\n", str.get()));
+ return true;
+ }
+ str.AssignLiteral("UNKNOWN");
+ LOGCLIP(("GetHTMLCharset: Failed to get HTML Charset!\n"));
+ return false;
+}
diff --git a/widget/gtk/nsClipboard.h b/widget/gtk/nsClipboard.h
new file mode 100644
index 0000000000..95b6eb828a
--- /dev/null
+++ b/widget/gtk/nsClipboard.h
@@ -0,0 +1,90 @@
+/* -*- 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 __nsClipboard_h_
+#define __nsClipboard_h_
+
+#include "mozilla/UniquePtr.h"
+#include "nsIClipboard.h"
+#include "nsIObserver.h"
+#include <gtk/gtk.h>
+
+#ifdef MOZ_LOGGING
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+extern mozilla::LazyLogModule gClipboardLog;
+# define LOGCLIP(args) MOZ_LOG(gClipboardLog, mozilla::LogLevel::Debug, args)
+#else
+# define LOGCLIP(args)
+#endif /* MOZ_LOGGING */
+
+class nsRetrievalContext {
+ public:
+ // Get actual clipboard content (GetClipboardData/GetClipboardText)
+ // which has to be released by ReleaseClipboardData().
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) = 0;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) = 0;
+ virtual void ReleaseClipboardData(const char* aClipboardData) = 0;
+
+ // Get data mime types which can be obtained from clipboard.
+ // The returned array has to be released by g_free().
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard, int* aTargetNum) = 0;
+
+ virtual bool HasSelectionSupport(void) = 0;
+
+ virtual ~nsRetrievalContext() = default;
+};
+
+class nsClipboard : public nsIClipboard, public nsIObserver {
+ public:
+ nsClipboard();
+
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ NS_DECL_NSICLIPBOARD
+
+ // Make sure we are initialized, called from the factory
+ // constructor
+ nsresult Init(void);
+
+ // Someone requested the selection
+ void SelectionGetEvent(GtkClipboard* aGtkClipboard,
+ GtkSelectionData* aSelectionData);
+ void SelectionClearEvent(GtkClipboard* aGtkClipboard);
+
+ private:
+ virtual ~nsClipboard();
+
+ // Get our hands on the correct transferable, given a specific
+ // clipboard
+ nsITransferable* GetTransferable(int32_t aWhichClipboard);
+
+ // Send clipboard data by nsITransferable
+ void SetTransferableData(nsITransferable* aTransferable, nsCString& aFlavor,
+ const char* aClipboardData,
+ uint32_t aClipboardDataLength);
+
+ void ClearTransferable(int32_t aWhichClipboard);
+
+ // Hang on to our owners and transferables so we can transfer data
+ // when asked.
+ nsCOMPtr<nsIClipboardOwner> mSelectionOwner;
+ nsCOMPtr<nsIClipboardOwner> mGlobalOwner;
+ nsCOMPtr<nsITransferable> mSelectionTransferable;
+ nsCOMPtr<nsITransferable> mGlobalTransferable;
+ mozilla::UniquePtr<nsRetrievalContext> mContext;
+};
+
+extern const int kClipboardTimeout;
+
+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..f8e1031e9e
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.cpp
@@ -0,0 +1,915 @@
+/* -*- 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/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardWayland.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.h"
+#include "nsDragService.h"
+#include "mozwayland/mozwayland.h"
+#include "nsWaylandDisplay.h"
+
+#include <gtk/gtk.h>
+#include <poll.h>
+#include <stdlib.h>
+#include <string.h>
+#include <fcntl.h>
+#include <errno.h>
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+const char* nsRetrievalContextWayland::sTextMimeTypes[TEXT_MIME_TYPES_NUM] = {
+ "text/plain;charset=utf-8", "UTF8_STRING", "COMPOUND_TEXT"};
+
+static inline GdkDragAction wl_to_gdk_actions(uint32_t dnd_actions) {
+ GdkDragAction actions = GdkDragAction(0);
+
+ if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY)
+ actions = GdkDragAction(actions | GDK_ACTION_COPY);
+ if (dnd_actions & WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE)
+ actions = GdkDragAction(actions | GDK_ACTION_MOVE);
+
+ return actions;
+}
+
+static inline uint32_t gdk_to_wl_actions(GdkDragAction action) {
+ uint32_t dnd_actions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+ if (action & (GDK_ACTION_COPY | GDK_ACTION_LINK | GDK_ACTION_PRIVATE))
+ dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY;
+ if (action & GDK_ACTION_MOVE)
+ dnd_actions |= WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+
+ return dnd_actions;
+}
+
+static GtkWidget* get_gtk_widget_for_wl_surface(struct wl_surface* surface) {
+ GdkWindow* gdkParentWindow =
+ static_cast<GdkWindow*>(wl_surface_get_user_data(surface));
+
+ gpointer user_data = nullptr;
+ gdk_window_get_user_data(gdkParentWindow, &user_data);
+
+ return GTK_WIDGET(user_data);
+}
+
+void DataOffer::AddMIMEType(const char* aMimeType) {
+ GdkAtom atom = gdk_atom_intern(aMimeType, FALSE);
+ mTargetMIMETypes.AppendElement(atom);
+}
+
+GdkAtom* DataOffer::GetTargets(int* aTargetNum) {
+ int length = mTargetMIMETypes.Length();
+ if (!length) {
+ *aTargetNum = 0;
+ return nullptr;
+ }
+
+ GdkAtom* targetList =
+ reinterpret_cast<GdkAtom*>(g_malloc(sizeof(GdkAtom) * length));
+ for (int32_t j = 0; j < length; j++) {
+ targetList[j] = mTargetMIMETypes[j];
+ }
+
+ *aTargetNum = length;
+ return targetList;
+}
+
+bool DataOffer::HasTarget(const char* aMimeType) {
+ int length = mTargetMIMETypes.Length();
+ for (int32_t j = 0; j < length; j++) {
+ if (mTargetMIMETypes[j] == gdk_atom_intern(aMimeType, FALSE)) {
+ LOGCLIP(("DataOffer::HasTarget() we have mime %s\n", aMimeType));
+ return true;
+ }
+ }
+ LOGCLIP(("DataOffer::HasTarget() missing mime %s\n", aMimeType));
+ return false;
+}
+
+char* DataOffer::GetData(wl_display* aDisplay, const char* aMimeType,
+ uint32_t* aContentLength) {
+ LOGCLIP(("DataOffer::GetData() mime %s\n", aMimeType));
+
+ int pipe_fd[2];
+ if (pipe(pipe_fd) == -1) return nullptr;
+
+ if (!RequestDataTransfer(aMimeType, pipe_fd[1])) {
+ NS_WARNING("DataOffer::RequestDataTransfer() failed!");
+ close(pipe_fd[0]);
+ close(pipe_fd[1]);
+ return nullptr;
+ }
+
+ close(pipe_fd[1]);
+ wl_display_flush(aDisplay);
+
+ struct pollfd fds;
+ fds.fd = pipe_fd[0];
+ fds.events = POLLIN;
+ int pollReturn = -1;
+
+#define MAX_CLIPBOARD_POLL_ATTEMPTS 10
+ for (int i = 0; i < MAX_CLIPBOARD_POLL_ATTEMPTS; i++) {
+ pollReturn = poll(&fds, 1, kClipboardTimeout / 1000);
+ // ret > 0 means we have data available
+ // ret = 0 means poll timeout expired
+ // ret < 0 means poll failed with error
+ if (pollReturn >= 0) {
+ break;
+ }
+ // We should try again for EINTR/EAGAIN errors,
+ // quit for all other ones.
+ if (errno != EINTR && errno != EAGAIN) {
+ break;
+ }
+ }
+ // Quit for poll error() and timeout
+ if (pollReturn <= 0) {
+ NS_WARNING("DataOffer::RequestDataTransfer() poll timeout!");
+ close(pipe_fd[0]);
+ return nullptr;
+ }
+
+ GIOChannel* channel = g_io_channel_unix_new(pipe_fd[0]);
+ GError* error = nullptr;
+ char* clipboardData = nullptr;
+
+ g_io_channel_set_encoding(channel, nullptr, &error);
+ if (!error) {
+ gsize length = 0;
+ g_io_channel_read_to_end(channel, &clipboardData, &length, &error);
+ if (length == 0) {
+ // We don't have valid clipboard data although
+ // g_io_channel_read_to_end() allocated clipboardData for us.
+ // Release it now and return nullptr to indicate
+ // we don't have reqested data flavour.
+ g_free((void*)clipboardData);
+ clipboardData = nullptr;
+ }
+ *aContentLength = length;
+ }
+
+ if (error) {
+ NS_WARNING(
+ nsPrintfCString("Unexpected error when reading clipboard data: %s",
+ error->message)
+ .get());
+ g_error_free(error);
+ }
+
+ g_io_channel_unref(channel);
+ close(pipe_fd[0]);
+
+ LOGCLIP((" Got clipboard data length %d\n", *aContentLength));
+ return clipboardData;
+}
+
+bool WaylandDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
+ if (mWaylandDataOffer) {
+ wl_data_offer_receive(mWaylandDataOffer, aMimeType, fd);
+ return true;
+ }
+
+ return false;
+}
+
+void WaylandDataOffer::DragOfferAccept(const char* aMimeType, uint32_t aTime) {
+ wl_data_offer_accept(mWaylandDataOffer, aTime, aMimeType);
+}
+
+/* We follow logic of gdk_wayland_drag_context_commit_status()/gdkdnd-wayland.c
+ * here.
+ */
+void WaylandDataOffer::SetDragStatus(GdkDragAction aPreferredAction,
+ uint32_t aTime) {
+ uint32_t preferredAction = gdk_to_wl_actions(aPreferredAction);
+ uint32_t allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE;
+
+ /* We only don't choose a preferred action if we don't accept any.
+ * If we do accept any, it is currently alway copy and move
+ */
+ if (preferredAction != WL_DATA_DEVICE_MANAGER_DND_ACTION_NONE) {
+ allActions = WL_DATA_DEVICE_MANAGER_DND_ACTION_COPY |
+ WL_DATA_DEVICE_MANAGER_DND_ACTION_MOVE;
+ }
+
+ wl_data_offer_set_actions(mWaylandDataOffer, allActions, preferredAction);
+
+ /* Workaround Wayland D&D architecture here. To get the data_device_drop()
+ signal (which routes to nsDragService::GetData() call) we need to
+ accept at least one mime type before data_device_leave().
+
+ Real wl_data_offer_accept() for actualy requested data mime type is
+ called from nsDragService::GetData().
+ */
+ if (mTargetMIMETypes[0]) {
+ wl_data_offer_accept(mWaylandDataOffer, aTime,
+ gdk_atom_name(mTargetMIMETypes[0]));
+ }
+}
+
+void WaylandDataOffer::SetSelectedDragAction(uint32_t aWaylandAction) {
+ mSelectedDragAction = aWaylandAction;
+}
+
+GdkDragAction WaylandDataOffer::GetSelectedDragAction() {
+ return wl_to_gdk_actions(mSelectedDragAction);
+}
+
+void WaylandDataOffer::SetAvailableDragActions(uint32_t aWaylandActions) {
+ mAvailableDragActions = aWaylandActions;
+}
+
+GdkDragAction WaylandDataOffer::GetAvailableDragActions() {
+ return wl_to_gdk_actions(mAvailableDragActions);
+}
+
+void WaylandDataOffer::SetWaylandDragContext(
+ nsWaylandDragContext* aDragContext) {
+ mDragContext = aDragContext;
+}
+
+nsWaylandDragContext* WaylandDataOffer::GetWaylandDragContext() {
+ return mDragContext;
+}
+
+static void data_offer_offer(void* data, struct wl_data_offer* wl_data_offer,
+ const char* type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(type);
+}
+
+/* Advertise all available drag and drop actions from source.
+ * We don't use that but follow gdk_wayland_drag_context_commit_status()
+ * from gdkdnd-wayland.c here.
+ */
+static void data_offer_source_actions(void* data,
+ struct wl_data_offer* wl_data_offer,
+ uint32_t source_actions) {
+ auto* offer = static_cast<WaylandDataOffer*>(data);
+ offer->SetAvailableDragActions(source_actions);
+}
+
+/* Advertise recently selected drag and drop action by compositor, based
+ * on source actions and user choice (key modifiers, etc.).
+ */
+static void data_offer_action(void* data, struct wl_data_offer* wl_data_offer,
+ uint32_t dnd_action) {
+ auto* offer = static_cast<WaylandDataOffer*>(data);
+ offer->SetSelectedDragAction(dnd_action);
+
+ /* Mimic GTK which triggers the motion event callback */
+ nsWaylandDragContext* dropContext = offer->GetWaylandDragContext();
+ if (dropContext) {
+ uint32_t time;
+ nscoord x, y;
+ dropContext->GetLastDropInfo(&time, &x, &y);
+
+ WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x,
+ y, time);
+ }
+}
+
+/* wl_data_offer callback description:
+ *
+ * data_offer_offer - Is called for each MIME type available at wl_data_offer.
+ * data_offer_source_actions - This event indicates the actions offered by
+ * the data source.
+ * data_offer_action - This event indicates the action selected by
+ * the compositor after matching the source/destination
+ * side actions.
+ */
+static const moz_wl_data_offer_listener data_offer_listener = {
+ data_offer_offer, data_offer_source_actions, data_offer_action};
+
+WaylandDataOffer::WaylandDataOffer(wl_data_offer* aWaylandDataOffer)
+ : mWaylandDataOffer(aWaylandDataOffer),
+ mDragContext(nullptr),
+ mSelectedDragAction(0),
+ mAvailableDragActions(0) {
+ wl_data_offer_add_listener(
+ mWaylandDataOffer, (struct wl_data_offer_listener*)&data_offer_listener,
+ this);
+}
+
+WaylandDataOffer::~WaylandDataOffer(void) {
+ if (mWaylandDataOffer) {
+ wl_data_offer_destroy(mWaylandDataOffer);
+ }
+}
+
+bool PrimaryDataOffer::RequestDataTransfer(const char* aMimeType, int fd) {
+ if (mPrimaryDataOfferGtk) {
+ gtk_primary_selection_offer_receive(mPrimaryDataOfferGtk, aMimeType, fd);
+ return true;
+ }
+ if (mPrimaryDataOfferZwpV1) {
+ zwp_primary_selection_offer_v1_receive(mPrimaryDataOfferZwpV1, aMimeType,
+ fd);
+ return true;
+ }
+ return false;
+}
+
+static void primary_data_offer(
+ void* data, gtk_primary_selection_offer* primary_selection_offer,
+ const char* mime_type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(mime_type);
+}
+
+static void primary_data_offer(
+ void* data, zwp_primary_selection_offer_v1* primary_selection_offer,
+ const char* mime_type) {
+ auto* offer = static_cast<DataOffer*>(data);
+ offer->AddMIMEType(mime_type);
+}
+
+/* gtk_primary_selection_offer_listener callback description:
+ *
+ * primary_data_offer - Is called for each MIME type available at
+ * gtk_primary_selection_offer.
+ */
+static const struct gtk_primary_selection_offer_listener
+ primary_selection_offer_listener_gtk = {primary_data_offer};
+
+static const struct zwp_primary_selection_offer_v1_listener
+ primary_selection_offer_listener_zwp_v1 = {primary_data_offer};
+
+PrimaryDataOffer::PrimaryDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer)
+ : mPrimaryDataOfferGtk(aPrimaryDataOffer), mPrimaryDataOfferZwpV1(nullptr) {
+ gtk_primary_selection_offer_add_listener(
+ aPrimaryDataOffer, &primary_selection_offer_listener_gtk, this);
+}
+
+PrimaryDataOffer::PrimaryDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer)
+ : mPrimaryDataOfferGtk(nullptr), mPrimaryDataOfferZwpV1(aPrimaryDataOffer) {
+ zwp_primary_selection_offer_v1_add_listener(
+ aPrimaryDataOffer, &primary_selection_offer_listener_zwp_v1, this);
+}
+
+PrimaryDataOffer::~PrimaryDataOffer(void) {
+ if (mPrimaryDataOfferGtk) {
+ gtk_primary_selection_offer_destroy(mPrimaryDataOfferGtk);
+ }
+ if (mPrimaryDataOfferZwpV1) {
+ zwp_primary_selection_offer_v1_destroy(mPrimaryDataOfferZwpV1);
+ }
+}
+
+NS_IMPL_ISUPPORTS(nsWaylandDragContext, nsISupports);
+
+nsWaylandDragContext::nsWaylandDragContext(WaylandDataOffer* aDataOffer,
+ wl_display* aDisplay)
+ : mDataOffer(aDataOffer),
+ mDisplay(aDisplay),
+ mTime(0),
+ mGtkWidget(nullptr),
+ mX(0),
+ mY(0) {
+ aDataOffer->SetWaylandDragContext(this);
+}
+
+void nsWaylandDragContext::DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime,
+ nscoord aX, nscoord aY) {
+ mTime = aTime;
+ mGtkWidget = aGtkWidget;
+ mX = aX;
+ mY = aY;
+}
+
+void nsWaylandDragContext::DropMotion(uint32_t aTime, nscoord aX, nscoord aY) {
+ mTime = aTime;
+ mX = aX;
+ mY = aY;
+}
+
+void nsWaylandDragContext::GetLastDropInfo(uint32_t* aTime, nscoord* aX,
+ nscoord* aY) {
+ *aTime = mTime;
+ *aX = mX;
+ *aY = mY;
+}
+
+void nsWaylandDragContext::SetDragStatus(GdkDragAction aPreferredAction) {
+ mDataOffer->SetDragStatus(aPreferredAction, mTime);
+}
+
+GdkDragAction nsWaylandDragContext::GetAvailableDragActions() {
+ GdkDragAction gdkAction = mDataOffer->GetSelectedDragAction();
+
+ // We emulate gdk_drag_context_get_actions() here.
+ if (!gdkAction) {
+ gdkAction = mDataOffer->GetAvailableDragActions();
+ }
+
+ return gdkAction;
+}
+
+GList* nsWaylandDragContext::GetTargets() {
+ int targetNums;
+ GdkAtom* atoms = mDataOffer->GetTargets(&targetNums);
+
+ GList* targetList = nullptr;
+ for (int i = 0; i < targetNums; i++) {
+ targetList = g_list_append(targetList, GDK_ATOM_TO_POINTER(atoms[i]));
+ }
+
+ return targetList;
+}
+
+char* nsWaylandDragContext::GetData(const char* aMimeType,
+ uint32_t* aContentLength) {
+ mDataOffer->DragOfferAccept(aMimeType, mTime);
+ return mDataOffer->GetData(mDisplay, aMimeType, aContentLength);
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ wl_data_offer* aWaylandDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered WaylandDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new WaylandDataOffer(aWaylandDataOffer);
+ g_hash_table_insert(mActiveOffers, aWaylandDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered PrimaryDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
+ g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::RegisterNewDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ MOZ_ASSERT(
+ dataOffer == nullptr,
+ "Registered PrimaryDataOffer already exists. Wayland protocol error?");
+
+ if (!dataOffer) {
+ dataOffer = new PrimaryDataOffer(aPrimaryDataOffer);
+ g_hash_table_insert(mActiveOffers, aPrimaryDataOffer, dataOffer);
+ }
+}
+
+void nsRetrievalContextWayland::SetClipboardDataOffer(
+ wl_data_offer* aWaylandDataOffer) {
+ // Delete existing clipboard data offer
+ mClipboardOffer = nullptr;
+
+ // null aWaylandDataOffer indicates that our clipboard content
+ // is no longer valid and should be release.
+ if (aWaylandDataOffer != nullptr) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aWaylandDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing stored clipboard data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aWaylandDataOffer);
+ mClipboardOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::SetPrimaryDataOffer(
+ gtk_primary_selection_offer* aPrimaryDataOffer) {
+ // Release any primary offer we have.
+ mPrimaryOffer = nullptr;
+
+ // aPrimaryDataOffer can be null which means we lost
+ // the mouse selection.
+ if (aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing primary data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
+ mPrimaryOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::SetPrimaryDataOffer(
+ zwp_primary_selection_offer_v1* aPrimaryDataOffer) {
+ // Release any primary offer we have.
+ mPrimaryOffer = nullptr;
+
+ // aPrimaryDataOffer can be null which means we lost
+ // the mouse selection.
+ if (aPrimaryDataOffer) {
+ DataOffer* dataOffer = static_cast<DataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aPrimaryDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing primary data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aPrimaryDataOffer);
+ mPrimaryOffer = WrapUnique(dataOffer);
+ }
+ }
+}
+
+void nsRetrievalContextWayland::AddDragAndDropDataOffer(
+ wl_data_offer* aDropDataOffer) {
+ // Remove any existing D&D contexts.
+ mDragContext = nullptr;
+
+ WaylandDataOffer* dataOffer = static_cast<WaylandDataOffer*>(
+ g_hash_table_lookup(mActiveOffers, aDropDataOffer));
+ NS_ASSERTION(dataOffer, "We're missing drag and drop data offer!");
+ if (dataOffer) {
+ g_hash_table_remove(mActiveOffers, aDropDataOffer);
+ mDragContext = new nsWaylandDragContext(dataOffer, mDisplay->GetDisplay());
+ }
+}
+
+nsWaylandDragContext* nsRetrievalContextWayland::GetDragContext(void) {
+ return mDragContext;
+}
+
+void nsRetrievalContextWayland::ClearDragAndDropDataOffer(void) {
+ mDragContext = nullptr;
+}
+
+// We have a new fresh data content.
+// We should attach listeners to it and save for further use.
+static void data_device_data_offer(void* data,
+ struct wl_data_device* data_device,
+ struct wl_data_offer* offer) {
+ LOGCLIP(("data_device_data_offer() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(offer);
+}
+
+// The new fresh data content is clipboard.
+static void data_device_selection(void* data,
+ struct wl_data_device* wl_data_device,
+ struct wl_data_offer* offer) {
+ LOGCLIP(("data_device_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetClipboardDataOffer(offer);
+}
+
+// The new fresh wayland data content is drag and drop.
+static void data_device_enter(void* data, struct wl_data_device* data_device,
+ uint32_t time, struct wl_surface* surface,
+ int32_t x_fixed, int32_t y_fixed,
+ struct wl_data_offer* offer) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->AddDragAndDropDataOffer(offer);
+
+ nsWaylandDragContext* dragContext = context->GetDragContext();
+
+ GtkWidget* gtkWidget = get_gtk_widget_for_wl_surface(surface);
+ if (!gtkWidget) {
+ NS_WARNING("DragAndDrop: Unable to get GtkWidget for wl_surface!");
+ return;
+ }
+
+ LOGDRAG(("nsWindow data_device_enter for GtkWidget %p\n", (void*)gtkWidget));
+ dragContext->DropDataEnter(gtkWidget, time, wl_fixed_to_int(x_fixed),
+ wl_fixed_to_int(y_fixed));
+}
+
+static void data_device_leave(void* data, struct wl_data_device* data_device) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+ WindowDragLeaveHandler(dropContext->GetWidget());
+
+ LOGDRAG(("nsWindow data_device_leave for GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ context->ClearDragAndDropDataOffer();
+}
+
+static void data_device_motion(void* data, struct wl_data_device* data_device,
+ uint32_t time, int32_t x_fixed,
+ int32_t y_fixed) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+
+ nscoord x = wl_fixed_to_int(x_fixed);
+ nscoord y = wl_fixed_to_int(y_fixed);
+ dropContext->DropMotion(time, x, y);
+
+ LOGDRAG(("nsWindow data_device_motion for GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ WindowDragMotionHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
+ time);
+}
+
+static void data_device_drop(void* data, struct wl_data_device* data_device) {
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ nsWaylandDragContext* dropContext = context->GetDragContext();
+
+ uint32_t time;
+ nscoord x, y;
+ dropContext->GetLastDropInfo(&time, &x, &y);
+
+ LOGDRAG(("nsWindow data_device_drop GtkWidget %p\n",
+ (void*)dropContext->GetWidget()));
+ WindowDragDropHandler(dropContext->GetWidget(), nullptr, dropContext, x, y,
+ time);
+}
+
+/* wl_data_device callback description:
+ *
+ * data_device_data_offer - It's called when there's a new wl_data_offer
+ * available. We need to attach wl_data_offer_listener
+ * to it to get available MIME types.
+ *
+ * data_device_selection - It's called when the new wl_data_offer
+ * is a clipboard content.
+ *
+ * data_device_enter - It's called when the new wl_data_offer is a drag & drop
+ * content and it's tied to actual wl_surface.
+ * data_device_leave - It's called when the wl_data_offer (drag & dop) is not
+ * valid any more.
+ * data_device_motion - It's called when the drag and drop selection moves
+ * across wl_surface.
+ * data_device_drop - It's called when D&D operation is sucessfully finished
+ * and we can read the data from D&D.
+ * It's generated only if we call wl_data_offer_accept() and
+ * wl_data_offer_set_actions() from data_device_motion
+ * callback.
+ */
+static const struct wl_data_device_listener data_device_listener = {
+ data_device_data_offer, data_device_enter, data_device_leave,
+ data_device_motion, data_device_drop, data_device_selection};
+
+static void primary_selection_data_offer(
+ void* data, struct gtk_primary_selection_device* primary_selection_device,
+ struct gtk_primary_selection_offer* primary_offer) {
+ LOGCLIP(("primary_selection_data_offer() callback\n"));
+ // create and add listener
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(primary_offer);
+}
+
+static void primary_selection_data_offer(
+ void* data,
+ struct zwp_primary_selection_device_v1* primary_selection_device,
+ struct zwp_primary_selection_offer_v1* primary_offer) {
+ LOGCLIP(("primary_selection_data_offer() callback\n"));
+ // create and add listener
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->RegisterNewDataOffer(primary_offer);
+}
+
+static void primary_selection_selection(
+ void* data, struct gtk_primary_selection_device* primary_selection_device,
+ struct gtk_primary_selection_offer* primary_offer) {
+ LOGCLIP(("primary_selection_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetPrimaryDataOffer(primary_offer);
+}
+
+static void primary_selection_selection(
+ void* data,
+ struct zwp_primary_selection_device_v1* primary_selection_device,
+ struct zwp_primary_selection_offer_v1* primary_offer) {
+ LOGCLIP(("primary_selection_selection() callback\n"));
+ nsRetrievalContextWayland* context =
+ static_cast<nsRetrievalContextWayland*>(data);
+ context->SetPrimaryDataOffer(primary_offer);
+}
+
+/* gtk_primary_selection_device callback description:
+ *
+ * primary_selection_data_offer - It's called when there's a new
+ * gtk_primary_selection_offer available. We need to
+ * attach gtk_primary_selection_offer_listener to it
+ * to get available MIME types.
+ *
+ * primary_selection_selection - It's called when the new
+ * gtk_primary_selection_offer is a primary selection
+ * content. It can be also called with
+ * gtk_primary_selection_offer = null which means
+ * there's no primary selection.
+ */
+static const struct gtk_primary_selection_device_listener
+ primary_selection_device_listener_gtk = {
+ primary_selection_data_offer,
+ primary_selection_selection,
+};
+
+static const struct zwp_primary_selection_device_v1_listener
+ primary_selection_device_listener_zwp_v1 = {
+ primary_selection_data_offer,
+ primary_selection_selection,
+};
+
+bool nsRetrievalContextWayland::HasSelectionSupport(void) {
+ return (mDisplay->GetPrimarySelectionDeviceManagerZwpV1() != nullptr ||
+ mDisplay->GetPrimarySelectionDeviceManagerGtk() != nullptr);
+}
+
+nsRetrievalContextWayland::nsRetrievalContextWayland(void)
+ : mInitialized(false),
+ mDisplay(WaylandDisplayGet()),
+ mActiveOffers(g_hash_table_new(NULL, NULL)),
+ mClipboardOffer(nullptr),
+ mPrimaryOffer(nullptr),
+ mDragContext(nullptr),
+ mClipboardRequestNumber(0),
+ mClipboardData(nullptr),
+ mClipboardDataLength(0) {
+ wl_data_device* dataDevice = wl_data_device_manager_get_data_device(
+ mDisplay->GetDataDeviceManager(), mDisplay->GetSeat());
+ wl_data_device_add_listener(dataDevice, &data_device_listener, this);
+
+ if (mDisplay->GetPrimarySelectionDeviceManagerZwpV1()) {
+ zwp_primary_selection_device_v1* primaryDataDevice =
+ zwp_primary_selection_device_manager_v1_get_device(
+ mDisplay->GetPrimarySelectionDeviceManagerZwpV1(),
+ mDisplay->GetSeat());
+ zwp_primary_selection_device_v1_add_listener(
+ primaryDataDevice, &primary_selection_device_listener_zwp_v1, this);
+ } else if (mDisplay->GetPrimarySelectionDeviceManagerGtk()) {
+ gtk_primary_selection_device* primaryDataDevice =
+ gtk_primary_selection_device_manager_get_device(
+ mDisplay->GetPrimarySelectionDeviceManagerGtk(),
+ mDisplay->GetSeat());
+ gtk_primary_selection_device_add_listener(
+ primaryDataDevice, &primary_selection_device_listener_gtk, this);
+ }
+
+ mInitialized = true;
+}
+
+static gboolean offer_hash_remove(gpointer wl_offer, gpointer aDataOffer,
+ gpointer user_data) {
+#ifdef DEBUG
+ nsPrintfCString msg("nsRetrievalContextWayland(): leaked nsDataOffer %p\n",
+ aDataOffer);
+ NS_WARNING(msg.get());
+#endif
+ delete static_cast<DataOffer*>(aDataOffer);
+ return true;
+}
+
+nsRetrievalContextWayland::~nsRetrievalContextWayland(void) {
+ g_hash_table_foreach_remove(mActiveOffers, offer_hash_remove, nullptr);
+ g_hash_table_destroy(mActiveOffers);
+}
+
+GdkAtom* nsRetrievalContextWayland::GetTargets(int32_t aWhichClipboard,
+ int* aTargetNum) {
+ if (GetSelectionAtom(aWhichClipboard) == GDK_SELECTION_CLIPBOARD) {
+ if (mClipboardOffer) {
+ return mClipboardOffer->GetTargets(aTargetNum);
+ }
+ } else {
+ if (mPrimaryOffer) {
+ return mPrimaryOffer->GetTargets(aTargetNum);
+ }
+ }
+
+ *aTargetNum = 0;
+ return nullptr;
+}
+
+struct FastTrackClipboard {
+ FastTrackClipboard(int aClipboardRequestNumber,
+ nsRetrievalContextWayland* aRetrievalContex)
+ : mClipboardRequestNumber(aClipboardRequestNumber),
+ mRetrievalContex(aRetrievalContex) {}
+
+ int mClipboardRequestNumber;
+ nsRetrievalContextWayland* mRetrievalContex;
+};
+
+static void wayland_clipboard_contents_received(
+ GtkClipboard* clipboard, GtkSelectionData* selection_data, gpointer data) {
+ LOGCLIP(("wayland_clipboard_contents_received() callback\n"));
+ FastTrackClipboard* fastTrack = static_cast<FastTrackClipboard*>(data);
+ fastTrack->mRetrievalContex->TransferFastTrackClipboard(
+ fastTrack->mClipboardRequestNumber, selection_data);
+ delete fastTrack;
+}
+
+void nsRetrievalContextWayland::TransferFastTrackClipboard(
+ int aClipboardRequestNumber, GtkSelectionData* aSelectionData) {
+ if (mClipboardRequestNumber == aClipboardRequestNumber) {
+ int dataLength = gtk_selection_data_get_length(aSelectionData);
+ if (dataLength > 0) {
+ mClipboardDataLength = dataLength;
+ mClipboardData = reinterpret_cast<char*>(
+ g_malloc(sizeof(char) * (mClipboardDataLength + 1)));
+ memcpy(mClipboardData, gtk_selection_data_get_data(aSelectionData),
+ sizeof(char) * mClipboardDataLength);
+ mClipboardData[mClipboardDataLength] = '\0';
+ }
+ } else {
+ NS_WARNING("Received obsoleted clipboard data!");
+ }
+}
+
+const char* nsRetrievalContextWayland::GetClipboardData(
+ const char* aMimeType, int32_t aWhichClipboard, uint32_t* aContentLength) {
+ NS_ASSERTION(mClipboardData == nullptr && mClipboardDataLength == 0,
+ "Looks like we're leaking clipboard data here!");
+
+ LOGCLIP(("nsRetrievalContextWayland::GetClipboardData [%p] mime %s\n", this,
+ aMimeType));
+
+ /* If actual clipboard data is owned by us we don't need to go
+ * through Wayland but we ask Gtk+ to directly call data
+ * getter callback nsClipboard::SelectionGetEvent().
+ * see gtk_selection_convert() at gtk+/gtkselection.c.
+ */
+ GdkAtom selection = GetSelectionAtom(aWhichClipboard);
+ if (gdk_selection_owner_get(selection)) {
+ LOGCLIP((" Internal clipboard content\n"));
+ mClipboardRequestNumber++;
+ gtk_clipboard_request_contents(
+ gtk_clipboard_get(selection), gdk_atom_intern(aMimeType, FALSE),
+ wayland_clipboard_contents_received,
+ new FastTrackClipboard(mClipboardRequestNumber, this));
+ } else {
+ LOGCLIP((" Remote clipboard content\n"));
+ const auto& dataOffer =
+ (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
+ if (!dataOffer) {
+ // Something went wrong. We're requested to provide clipboard data
+ // but we haven't got any from wayland.
+ NS_WARNING("Requested data without valid DataOffer!");
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+ } else {
+ mClipboardData = dataOffer->GetData(mDisplay->GetDisplay(), aMimeType,
+ &mClipboardDataLength);
+ }
+ }
+
+ *aContentLength = mClipboardDataLength;
+ return reinterpret_cast<const char*>(mClipboardData);
+}
+
+const char* nsRetrievalContextWayland::GetClipboardText(
+ int32_t aWhichClipboard) {
+ LOGCLIP(("nsRetrievalContextWayland::GetClipboardText [%p]\n", this));
+
+ GdkAtom selection = GetSelectionAtom(aWhichClipboard);
+ const auto& dataOffer =
+ (selection == GDK_SELECTION_PRIMARY) ? mPrimaryOffer : mClipboardOffer;
+ if (!dataOffer) return nullptr;
+
+ for (unsigned int i = 0; i < TEXT_MIME_TYPES_NUM; i++) {
+ if (dataOffer->HasTarget(sTextMimeTypes[i])) {
+ uint32_t unused;
+ return GetClipboardData(sTextMimeTypes[i], aWhichClipboard, &unused);
+ }
+ }
+ return nullptr;
+}
+
+void nsRetrievalContextWayland::ReleaseClipboardData(
+ const char* aClipboardData) {
+ LOGCLIP(("nsRetrievalContextWayland::ReleaseClipboardData [%p]\n", this));
+
+ NS_ASSERTION(aClipboardData == mClipboardData,
+ "Releasing unknown clipboard data!");
+ g_free((void*)aClipboardData);
+
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+}
diff --git a/widget/gtk/nsClipboardWayland.h b/widget/gtk/nsClipboardWayland.h
new file mode 100644
index 0000000000..ddf5d40dc6
--- /dev/null
+++ b/widget/gtk/nsClipboardWayland.h
@@ -0,0 +1,162 @@
+/* -*- 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 <gtk/gtk.h>
+#include <gdk/gdkwayland.h>
+#include <nsTArray.h>
+
+#include "mozilla/UniquePtr.h"
+#include "nsClipboard.h"
+#include "nsWaylandDisplay.h"
+
+struct FastTrackClipboard;
+
+class DataOffer {
+ public:
+ void AddMIMEType(const char* aMimeType);
+
+ GdkAtom* GetTargets(int* aTargetNum);
+ bool HasTarget(const char* aMimeType);
+
+ char* GetData(wl_display* aDisplay, const char* aMimeType,
+ uint32_t* aContentLength);
+
+ virtual ~DataOffer() = default;
+
+ private:
+ virtual bool RequestDataTransfer(const char* aMimeType, int fd) = 0;
+
+ protected:
+ nsTArray<GdkAtom> mTargetMIMETypes;
+};
+
+class WaylandDataOffer : public DataOffer {
+ public:
+ explicit WaylandDataOffer(wl_data_offer* aWaylandDataOffer);
+
+ void DragOfferAccept(const char* aMimeType, uint32_t aTime);
+ void SetDragStatus(GdkDragAction aPreferredAction, uint32_t aTime);
+
+ GdkDragAction GetSelectedDragAction();
+ void SetSelectedDragAction(uint32_t aWaylandAction);
+
+ void SetAvailableDragActions(uint32_t aWaylandActions);
+ GdkDragAction GetAvailableDragActions();
+
+ void SetWaylandDragContext(nsWaylandDragContext* aDragContext);
+ nsWaylandDragContext* GetWaylandDragContext();
+
+ virtual ~WaylandDataOffer();
+
+ private:
+ bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+ wl_data_offer* mWaylandDataOffer;
+ RefPtr<nsWaylandDragContext> mDragContext;
+ uint32_t mSelectedDragAction;
+ uint32_t mAvailableDragActions;
+};
+
+class PrimaryDataOffer : public DataOffer {
+ public:
+ explicit PrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ explicit PrimaryDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+ void SetAvailableDragActions(uint32_t aWaylandActions){};
+
+ virtual ~PrimaryDataOffer();
+
+ private:
+ bool RequestDataTransfer(const char* aMimeType, int fd) override;
+
+ gtk_primary_selection_offer* mPrimaryDataOfferGtk;
+ zwp_primary_selection_offer_v1* mPrimaryDataOfferZwpV1;
+};
+
+class nsWaylandDragContext : public nsISupports {
+ NS_DECL_ISUPPORTS
+
+ public:
+ nsWaylandDragContext(WaylandDataOffer* aWaylandDataOffer,
+ wl_display* aDisplay);
+
+ void DropDataEnter(GtkWidget* aGtkWidget, uint32_t aTime, nscoord aX,
+ nscoord aY);
+ void DropMotion(uint32_t aTime, nscoord aX, nscoord aY);
+ void GetLastDropInfo(uint32_t* aTime, nscoord* aX, nscoord* aY);
+
+ void SetDragStatus(GdkDragAction aPreferredAction);
+ GdkDragAction GetAvailableDragActions();
+
+ GtkWidget* GetWidget() { return mGtkWidget; }
+ GList* GetTargets();
+ char* GetData(const char* aMimeType, uint32_t* aContentLength);
+
+ private:
+ virtual ~nsWaylandDragContext() = default;
+
+ mozilla::UniquePtr<WaylandDataOffer> mDataOffer;
+ wl_display* mDisplay;
+ uint32_t mTime;
+ GtkWidget* mGtkWidget;
+ nscoord mX, mY;
+};
+
+class nsRetrievalContextWayland : public nsRetrievalContext {
+ public:
+ nsRetrievalContextWayland();
+
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) override;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
+ virtual void ReleaseClipboardData(const char* aClipboardData) override;
+
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+ int* aTargetNum) override;
+ virtual bool HasSelectionSupport(void) override;
+
+ void RegisterNewDataOffer(wl_data_offer* aWaylandDataOffer);
+ void RegisterNewDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ void RegisterNewDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+
+ void SetClipboardDataOffer(wl_data_offer* aWaylandDataOffer);
+ void SetPrimaryDataOffer(gtk_primary_selection_offer* aPrimaryDataOffer);
+ void SetPrimaryDataOffer(zwp_primary_selection_offer_v1* aPrimaryDataOffer);
+ void AddDragAndDropDataOffer(wl_data_offer* aWaylandDataOffer);
+ nsWaylandDragContext* GetDragContext();
+
+ void ClearDragAndDropDataOffer();
+
+ void TransferFastTrackClipboard(int aClipboardRequestNumber,
+ GtkSelectionData* aSelectionData);
+
+ virtual ~nsRetrievalContextWayland() override;
+
+ private:
+ bool mInitialized;
+ RefPtr<mozilla::widget::nsWaylandDisplay> mDisplay;
+
+ // Data offers provided by Wayland data device
+ GHashTable* mActiveOffers;
+ mozilla::UniquePtr<DataOffer> mClipboardOffer;
+ mozilla::UniquePtr<DataOffer> mPrimaryOffer;
+ RefPtr<nsWaylandDragContext> mDragContext;
+
+ int mClipboardRequestNumber;
+ char* mClipboardData;
+ uint32_t mClipboardDataLength;
+
+// Mime types used for text data at Gtk+, see request_text_received_func()
+// at gtkclipboard.c.
+#define TEXT_MIME_TYPES_NUM 3
+ static const char* sTextMimeTypes[TEXT_MIME_TYPES_NUM];
+};
+
+#endif /* __nsClipboardWayland_h_ */
diff --git a/widget/gtk/nsClipboardX11.cpp b/widget/gtk/nsClipboardX11.cpp
new file mode 100644
index 0000000000..0439c2e68c
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.cpp
@@ -0,0 +1,340 @@
+/* -*- 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/ArrayUtils.h"
+
+#include "nsArrayUtils.h"
+#include "nsClipboard.h"
+#include "nsClipboardX11.h"
+#include "nsSupportsPrimitives.h"
+#include "nsString.h"
+#include "nsReadableUtils.h"
+#include "nsPrimitiveHelpers.h"
+#include "nsImageToPixbuf.h"
+#include "nsStringStream.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/TimeStamp.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;
+
+bool nsRetrievalContextX11::HasSelectionSupport(void) {
+ // yeah, unix supports the selection clipboard on X11.
+ return true;
+}
+
+nsRetrievalContextX11::nsRetrievalContextX11()
+ : mState(INITIAL),
+ mClipboardRequestNumber(0),
+ mClipboardData(nullptr),
+ mClipboardDataLength(0),
+ mTargetMIMEType(gdk_atom_intern("TARGETS", FALSE)) {}
+
+static void DispatchSelectionNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkEvent event = {};
+ event.selection.type = GDK_SELECTION_NOTIFY;
+ event.selection.window = gtk_widget_get_window(widget);
+ event.selection.selection =
+ gdk_x11_xatom_to_atom(xevent->xselection.selection);
+ event.selection.target = gdk_x11_xatom_to_atom(xevent->xselection.target);
+ event.selection.property = gdk_x11_xatom_to_atom(xevent->xselection.property);
+ event.selection.time = xevent->xselection.time;
+
+ gtk_widget_event(widget, &event);
+}
+
+static void DispatchPropertyNotifyEvent(GtkWidget* widget, XEvent* xevent) {
+ GdkWindow* window = gtk_widget_get_window(widget);
+ if ((gdk_window_get_events(window)) & GDK_PROPERTY_CHANGE_MASK) {
+ GdkEvent event = {};
+ event.property.type = GDK_PROPERTY_NOTIFY;
+ event.property.window = window;
+ event.property.atom = gdk_x11_xatom_to_atom(xevent->xproperty.atom);
+ event.property.time = xevent->xproperty.time;
+ event.property.state = xevent->xproperty.state;
+
+ gtk_widget_event(widget, &event);
+ }
+}
+
+struct checkEventContext {
+ GtkWidget* cbWidget;
+ Atom selAtom;
+};
+
+static Bool checkEventProc(Display* display, XEvent* event, XPointer arg) {
+ checkEventContext* context = (checkEventContext*)arg;
+
+ if (event->xany.type == SelectionNotify ||
+ (event->xany.type == PropertyNotify &&
+ event->xproperty.atom == context->selAtom)) {
+ GdkWindow* cbWindow = gdk_x11_window_lookup_for_display(
+ gdk_x11_lookup_xdisplay(display), event->xany.window);
+ if (cbWindow) {
+ GtkWidget* cbWidget = nullptr;
+ gdk_window_get_user_data(cbWindow, (gpointer*)&cbWidget);
+ if (cbWidget && GTK_IS_WIDGET(cbWidget)) {
+ context->cbWidget = cbWidget;
+ return X11True;
+ }
+ }
+ }
+
+ return X11False;
+}
+
+bool nsRetrievalContextX11::WaitForX11Content() {
+ if (mState == COMPLETED) { // the request completed synchronously
+ return true;
+ }
+
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ // gdk_display_get_default() returns null on headless
+ if (gdkDisplay && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* xDisplay = GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ checkEventContext context;
+ context.cbWidget = nullptr;
+ context.selAtom =
+ gdk_x11_atom_to_xatom(gdk_atom_intern("GDK_SELECTION", FALSE));
+
+ // Send X events which are relevant to the ongoing selection retrieval
+ // to the clipboard widget. Wait until either the operation completes, or
+ // we hit our timeout. All other X events remain queued.
+
+ int 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 (mState == COMPLETED) {
+ return true;
+ }
+ }
+
+ 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));
+ }
+#ifdef DEBUG_CLIPBOARD
+ printf("exceeded clipboard timeout\n");
+#endif
+ mState = TIMED_OUT;
+ return false;
+}
+
+// Call this when data has been retrieved.
+void nsRetrievalContextX11::Complete(ClipboardDataType aDataType,
+ const void* aData,
+ int aDataRequestNumber) {
+ LOGCLIP(("nsRetrievalContextX11::Complete\n"));
+
+ if (mClipboardRequestNumber != aDataRequestNumber) {
+ NS_WARNING(
+ "nsRetrievalContextX11::Complete() got obsoleted clipboard data.");
+ return;
+ }
+
+ if (mState == INITIAL) {
+ mState = COMPLETED;
+
+ MOZ_ASSERT(mClipboardData == nullptr && mClipboardDataLength == 0,
+ "We're leaking clipboard data!");
+
+ switch (aDataType) {
+ case CLIPBOARD_TEXT: {
+ const char* text = static_cast<const char*>(aData);
+ if (text) {
+ mClipboardDataLength = sizeof(char) * (strlen(text) + 1);
+ mClipboardData = moz_xmalloc(mClipboardDataLength);
+ memcpy(mClipboardData, text, mClipboardDataLength);
+ }
+ } break;
+ case CLIPBOARD_TARGETS: {
+ const GtkSelectionData* selection =
+ static_cast<const GtkSelectionData*>(aData);
+
+ gint n_targets = 0;
+ GdkAtom* targets = nullptr;
+
+ if (!gtk_selection_data_get_targets(selection, &targets, &n_targets) ||
+ !n_targets) {
+ return;
+ }
+
+ mClipboardData = targets;
+ mClipboardDataLength = n_targets;
+ } break;
+ case CLIPBOARD_DATA: {
+ const GtkSelectionData* selection =
+ static_cast<const GtkSelectionData*>(aData);
+
+ gint dataLength = gtk_selection_data_get_length(selection);
+ if (dataLength > 0) {
+ mClipboardDataLength = dataLength;
+ mClipboardData = moz_xmalloc(dataLength);
+ memcpy(mClipboardData, gtk_selection_data_get_data(selection),
+ dataLength);
+ }
+ } break;
+ }
+ } else {
+ // Already timed out
+ MOZ_ASSERT(mState == TIMED_OUT);
+ }
+}
+
+static void clipboard_contents_received(GtkClipboard* clipboard,
+ GtkSelectionData* selection_data,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP(("clipboard_contents_received (%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ ClipboardRequestHandler* handler =
+ static_cast<ClipboardRequestHandler*>(data);
+ handler->Complete(selection_data);
+ delete handler;
+}
+
+static void clipboard_text_received(GtkClipboard* clipboard, const gchar* text,
+ gpointer data) {
+ int whichClipboard = GetGeckoClipboardType(clipboard);
+ LOGCLIP(("clipboard_text_received (%s) callback\n",
+ whichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ ClipboardRequestHandler* handler =
+ static_cast<ClipboardRequestHandler*>(data);
+ handler->Complete(text);
+ delete handler;
+}
+
+bool nsRetrievalContextX11::WaitForClipboardData(ClipboardDataType aDataType,
+ GtkClipboard* clipboard,
+ const char* aMimeType) {
+ LOGCLIP(("nsRetrievalContextX11::WaitForClipboardData\n"));
+
+ mState = INITIAL;
+ NS_ASSERTION(!mClipboardData, "Leaking clipboard content!");
+
+ // Call ClipboardRequestHandler() with unique clipboard request number.
+ // The request number pairs gtk_clipboard_request_contents() data request
+ // with clipboard_contents_received() callback where the data
+ // is provided by Gtk.
+ mClipboardRequestNumber++;
+ ClipboardRequestHandler* handler =
+ new ClipboardRequestHandler(this, aDataType, mClipboardRequestNumber);
+
+ switch (aDataType) {
+ case CLIPBOARD_DATA:
+ gtk_clipboard_request_contents(clipboard,
+ gdk_atom_intern(aMimeType, FALSE),
+ clipboard_contents_received, handler);
+ break;
+ case CLIPBOARD_TEXT:
+ gtk_clipboard_request_text(clipboard, clipboard_text_received, handler);
+ break;
+ case CLIPBOARD_TARGETS:
+ gtk_clipboard_request_contents(clipboard, mTargetMIMEType,
+ clipboard_contents_received, handler);
+ break;
+ }
+
+ return WaitForX11Content();
+}
+
+GdkAtom* nsRetrievalContextX11::GetTargets(int32_t aWhichClipboard,
+ int* aTargetNums) {
+ LOGCLIP(("nsRetrievalContextX11::GetTargets(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard =
+ gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_TARGETS, clipboard)) {
+ LOGCLIP((" WaitForClipboardData() failed!\n"));
+ return nullptr;
+ }
+
+ *aTargetNums = mClipboardDataLength;
+ GdkAtom* targets = static_cast<GdkAtom*>(mClipboardData);
+
+ // We don't hold the target list internally but we transfer the ownership.
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+
+ LOGCLIP((" returned %d targets\n", *aTargetNums));
+ return targets;
+}
+
+const char* nsRetrievalContextX11::GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) {
+ LOGCLIP(("nsRetrievalContextX11::GetClipboardData(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_DATA, clipboard, aMimeType))
+ return nullptr;
+
+ *aContentLength = mClipboardDataLength;
+ return static_cast<const char*>(mClipboardData);
+}
+
+const char* nsRetrievalContextX11::GetClipboardText(int32_t aWhichClipboard) {
+ LOGCLIP(("nsRetrievalContextX11::GetClipboardText(%s)\n",
+ aWhichClipboard == nsClipboard::kSelectionClipboard ? "primary"
+ : "clipboard"));
+
+ GtkClipboard* clipboard;
+ clipboard = gtk_clipboard_get(GetSelectionAtom(aWhichClipboard));
+
+ if (!WaitForClipboardData(CLIPBOARD_TEXT, clipboard)) return nullptr;
+
+ return static_cast<const char*>(mClipboardData);
+}
+
+void nsRetrievalContextX11::ReleaseClipboardData(const char* aClipboardData) {
+ LOGCLIP(("nsRetrievalContextX11::ReleaseClipboardData\n"));
+ NS_ASSERTION(aClipboardData == mClipboardData,
+ "Releasing unknown clipboard data!");
+ free((void*)aClipboardData);
+
+ mClipboardData = nullptr;
+ mClipboardDataLength = 0;
+}
diff --git a/widget/gtk/nsClipboardX11.h b/widget/gtk/nsClipboardX11.h
new file mode 100644
index 0000000000..2363111f74
--- /dev/null
+++ b/widget/gtk/nsClipboardX11.h
@@ -0,0 +1,73 @@
+/* -*- 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>
+
+enum ClipboardDataType { CLIPBOARD_DATA, CLIPBOARD_TEXT, CLIPBOARD_TARGETS };
+
+class nsRetrievalContextX11 : public nsRetrievalContext {
+ public:
+ enum State { INITIAL, COMPLETED, TIMED_OUT };
+
+ virtual const char* GetClipboardData(const char* aMimeType,
+ int32_t aWhichClipboard,
+ uint32_t* aContentLength) override;
+ virtual const char* GetClipboardText(int32_t aWhichClipboard) override;
+ virtual void ReleaseClipboardData(const char* aClipboardData) override;
+
+ virtual GdkAtom* GetTargets(int32_t aWhichClipboard,
+ int* aTargetNums) override;
+
+ virtual bool HasSelectionSupport(void) override;
+
+ // Call this when data or text has been retrieved.
+ void Complete(ClipboardDataType aDataType, const void* aData,
+ int aDataRequestNumber);
+
+ nsRetrievalContextX11();
+
+ private:
+ bool WaitForClipboardData(ClipboardDataType aDataType,
+ GtkClipboard* clipboard,
+ const char* aMimeType = nullptr);
+
+ /**
+ * Spins X event loop until timing out or being completed. Returns
+ * null if we time out, otherwise returns the completed data (passing
+ * ownership to caller).
+ */
+ bool WaitForX11Content();
+
+ State mState;
+ int mClipboardRequestNumber;
+ void* mClipboardData;
+ uint32_t mClipboardDataLength;
+ GdkAtom mTargetMIMEType;
+};
+
+class ClipboardRequestHandler {
+ public:
+ ClipboardRequestHandler(nsRetrievalContextX11* aContext,
+ ClipboardDataType aDataType, int aDataRequestNumber)
+ : mContext(aContext),
+ mDataRequestNumber(aDataRequestNumber),
+ mDataType(aDataType) {}
+
+ void Complete(const void* aData) {
+ mContext->Complete(mDataType, aData, mDataRequestNumber);
+ }
+
+ private:
+ nsRetrievalContextX11* mContext;
+ int mDataRequestNumber;
+ ClipboardDataType mDataType;
+};
+
+#endif /* __nsClipboardX11_h_ */
diff --git a/widget/gtk/nsColorPicker.cpp b/widget/gtk/nsColorPicker.cpp
new file mode 100644
index 0000000000..21341e552f
--- /dev/null
+++ b/widget/gtk/nsColorPicker.cpp
@@ -0,0 +1,242 @@
+/* -*- 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 "nsColor.h"
+#include "nsColorPicker.h"
+#include "nsGtkUtils.h"
+#include "nsIWidget.h"
+#include "WidgetUtils.h"
+#include "nsPIDOMWindow.h"
+
+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) {
+ auto* parent = nsPIDOMWindowOuter::From(aParent);
+ mParentWidget = mozilla::widget::WidgetUtils::DOMWindowToWidget(parent);
+ mTitle = title;
+ mInitialColor = initialColor;
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP nsColorPicker::Open(
+ nsIColorPickerShownCallback* aColorPickerShownCallback) {
+ // Input color string should be 7 length (i.e. a string representing a valid
+ // simple color)
+ if (mInitialColor.Length() != 7) {
+ return NS_ERROR_FAILURE;
+ }
+
+ const nsAString& withoutHash = StringTail(mInitialColor, 6);
+ nscolor color;
+ if (!NS_HexToRGBA(withoutHash, nsHexColorType::NoAlpha, &color)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ 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, 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);
+ 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..c1f108f5c3
--- /dev/null
+++ b/widget/gtk/nsColorPicker.h
@@ -0,0 +1,72 @@
+/* -*- 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"
+
+// Don't activate the GTK3 color picker for now, because it is missing a few
+// things, mainly the ability to let the user select a color on the screen.
+// See bug 1198256.
+#undef ACTIVATE_GTK3_COLOR_PICKER
+
+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;
+};
+
+#endif // nsColorPicker_h__
diff --git a/widget/gtk/nsDeviceContextSpecG.cpp b/widget/gtk/nsDeviceContextSpecG.cpp
new file mode 100644
index 0000000000..631ce9d3d4
--- /dev/null
+++ b/widget/gtk/nsDeviceContextSpecG.cpp
@@ -0,0 +1,341 @@
+/* -*- 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/PrintTargetPDF.h"
+#include "mozilla/gfx/PrintTargetPS.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Services.h"
+
+#include "plstr.h"
+#include "prenv.h" /* for PR_GetEnv */
+
+#include "nsComponentManagerUtils.h"
+#include "nsIObserverService.h"
+#include "nsPrintfCString.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 <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.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::PrintTarget;
+using mozilla::gfx::PrintTargetPDF;
+using mozilla::gfx::PrintTargetPS;
+
+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;
+
+ nsresult rv;
+
+ // We shouldn't be attempting to get a surface if we've already got a spool
+ // file.
+ MOZ_ASSERT(!mSpoolFile);
+
+ // 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);
+
+ rv = NS_NewNativeLocalFile(nsDependentCString(buf), false,
+ getter_AddRefs(mSpoolFile));
+ if (NS_FAILED(rv)) {
+ 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");
+ rv = stream->Init(mSpoolFile, -1, -1, 0);
+ if (NS_FAILED(rv)) return nullptr;
+
+ int16_t format;
+ mPrintSettings->GetOutputFormat(&format);
+
+ // We assume PDF output if asked for native output.
+ if (format == nsIPrintSettings::kOutputFormatNative) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ }
+
+ IntSize size = IntSize::Ceil(width, height);
+ if (format == nsIPrintSettings::kOutputFormatPDF) {
+ return PrintTargetPDF::CreateOrNull(stream, size);
+ }
+
+ int32_t orientation = mPrintSettings->GetSheetOrientation();
+ return PrintTargetPS::CreateOrNull(
+ stream, size,
+ orientation == nsIPrintSettings::kPortraitOrientation
+ ? PrintTargetPS::PORTRAIT
+ : PrintTargetPS::LANDSCAPE);
+}
+
+#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
+
+/** -------------------------------------------------------
+ * Initialize the nsDeviceContextSpecGTK
+ * @update dc 2/15/98
+ * @update syd 3/2/99
+ */
+NS_IMETHODIMP nsDeviceContextSpecGTK::Init(nsIWidget* aWidget,
+ nsIPrintSettings* aPS,
+ bool aIsPrintPreview) {
+ if (gtk_major_version < 2 ||
+ (gtk_major_version == 2 && gtk_minor_version < 10))
+ return NS_ERROR_NOT_AVAILABLE; // I'm so sorry bz
+
+ mPrintSettings = do_QueryInterface(aPS);
+ if (!mPrintSettings) return NS_ERROR_NO_INTERFACE;
+
+ // This is only set by embedders
+ bool toFile;
+ aPS->GetPrintToFile(&toFile);
+
+ mToPrinter = !toFile && !aIsPrintPreview;
+
+ mGtkPrintSettings = mPrintSettings->GetGtkPrintSettings();
+ mGtkPageSetup = mPrintSettings->GetGtkPageSetup();
+
+ // 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. See bug
+ // 414314 for more info.
+ GtkPaperSize* geckosHackishPaperSize =
+ gtk_page_setup_get_paper_size(mGtkPageSetup);
+ GtkPaperSize* standardGtkPaperSize =
+ gtk_paper_size_new(gtk_paper_size_get_name(geckosHackishPaperSize));
+
+ 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;
+ if (gtk_paper_size_is_equal(geckosHackishPaperSize, standardGtkPaperSize)) {
+ properPaperSize = standardGtkPaperSize;
+ } else {
+ properPaperSize = geckosHackishPaperSize;
+ }
+ gtk_print_settings_set_paper_size(mGtkPrintSettings, properPaperSize);
+ gtk_page_setup_set_paper_size_and_default_margins(mGtkPageSetup,
+ properPaperSize);
+ gtk_paper_size_free(standardGtkPaperSize);
+
+ 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;
+
+ // 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)) {
+ 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));
+ return TRUE;
+ }
+ }
+
+ // We haven't found it yet - keep searching...
+ return FALSE;
+}
+
+void nsDeviceContextSpecGTK::StartPrintJob() {
+ GtkPrintJob* job =
+ gtk_print_job_new(mTitle.get(), 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() {
+ 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;
+}
+
+NS_IMETHODIMP nsDeviceContextSpecGTK::EndDocument() {
+ if (mToPrinter) {
+ // 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 (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));
+ }
+ } else {
+ // 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));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString destLeafName;
+ rv = destFile->GetLeafName(destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> destDir;
+ rv = destFile->GetParent(getter_AddRefs(destDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ rv = mSpoolFile->MoveTo(destDir, destLeafName);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ mSpoolFile = nullptr;
+
+ // 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));
+
+ // Notify flatpak printing portal that file is completely written
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ bool shouldUsePortal;
+ if (giovfs) {
+ giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
+ if (shouldUsePortal) {
+ // Use the name of the file for printing to match with
+ // nsFlatpakPrintPortal
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ // Pass filename to be sure that observer process the right data
+ os->NotifyObservers(nullptr, "print-to-file-finished",
+ targetPath.get());
+ }
+ }
+ }
+ return NS_OK;
+}
diff --git a/widget/gtk/nsDeviceContextSpecG.h b/widget/gtk/nsDeviceContextSpecG.h
new file mode 100644
index 0000000000..e3b0537782
--- /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 "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(nsIWidget* aWidget, nsIPrintSettings* aPS,
+ bool aIsPrintPreview) override;
+ NS_IMETHOD BeginDocument(const nsAString& aTitle,
+ const nsAString& aPrintToFileName,
+ int32_t aStartPage, int32_t aEndPage) override;
+ NS_IMETHOD EndDocument() override;
+ NS_IMETHOD BeginPage() override { return NS_OK; }
+ NS_IMETHOD EndPage() override { return NS_OK; }
+
+ protected:
+ virtual ~nsDeviceContextSpecGTK();
+ nsCOMPtr<nsPrintSettingsGTK> mPrintSettings;
+ bool mToPrinter : 1; /* If true, print to printer */
+ GtkPrintSettings* mGtkPrintSettings;
+ GtkPageSetup* mGtkPageSetup;
+
+ nsCString mSpoolName;
+ nsCOMPtr<nsIFile> mSpoolFile;
+ nsCString mTitle;
+
+ 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..7d57c35df9
--- /dev/null
+++ b/widget/gtk/nsDragService.cpp
@@ -0,0 +1,2122 @@
+/* -*- 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 <gdk/gdkx.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 "GRefPtr.h"
+
+#include "gfxXlibSurface.h"
+#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"
+#ifdef MOZ_WAYLAND
+# include "nsClipboardWayland.h"
+# include "gfxPlatformGtk.h"
+#endif
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+
+#define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
+
+// This sets how opaque the drag image is
+#define DRAG_IMAGE_ALPHA_LEVEL 0.5
+
+// These values are copied from GtkDragResult (rather than using GtkDragResult
+// directly) so that this code can be compiled against versions of GTK+ that
+// do not have GtkDragResult.
+// GtkDragResult is available from GTK+ version 2.12.
+enum {
+ MOZ_GTK_DRAG_RESULT_SUCCESS,
+ MOZ_GTK_DRAG_RESULT_NO_TARGET,
+ MOZ_GTK_DRAG_RESULT_USER_CANCELLED,
+ MOZ_GTK_DRAG_RESULT_TIMEOUT_EXPIRED,
+ MOZ_GTK_DRAG_RESULT_GRAB_BROKEN,
+ MOZ_GTK_DRAG_RESULT_ERROR
+};
+
+static LazyLogModule sDragLm("nsDragService");
+
+// 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 GtkWidget* sGrabWidget;
+
+static const char gMimeListType[] = "application/x-moz-internal-item-list";
+static const char gMozUrlType[] = "_NETSCAPE_URL";
+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 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)
+#ifdef MOZ_WAYLAND
+ ,
+ mPendingWaylandDragContext(nullptr),
+ mTargetWaylandDragContext(nullptr)
+#endif
+{
+ // 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
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService"));
+ mCanDrop = false;
+ mTargetDragDataReceived = false;
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+nsDragService::~nsDragService() {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService"));
+ if (mTaskSource) g_source_remove(mTaskSource);
+}
+
+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")) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("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;
+
+ GdkEvent* event = sMotionEvent;
+ sMotionEvent = nullptr;
+ // 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);
+ }
+ gdk_event_free(event);
+
+ // 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) {
+ if (sMotionEvent) {
+ gdk_event_free(sMotionEvent);
+ }
+ sMotionEvent = 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(getter_AddRefs(widget));
+ if (!widget) return nullptr;
+
+ GtkWidget* gtkWidget =
+ static_cast<nsWindow*>(widget.get())->GetMozContainerWidget();
+ 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("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;
+ // 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);
+
+ // 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.
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = GDK_BUTTON_PRESS;
+ event.button.window = gtk_widget_get_window(mHiddenWidget);
+ event.button.time = nsWindow::GetLastUserInputTime();
+
+ // 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));
+
+ // Get device for event source
+ GdkDisplay* display = gdk_display_get_default();
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ // start our drag.
+ GdkDragContext* context =
+ gtk_drag_begin(mHiddenWidget, sourceList, action, 1, &event);
+
+ 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() {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession"));
+ return nsBaseDragService::StartDragSession();
+}
+
+NS_IMETHODIMP
+nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::EndDragSession %d", 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) {
+ gdk_event_free(sMotionEvent);
+ sMotionEvent = nullptr;
+ }
+ }
+
+ // unset our drag action
+ SetDragAction(DRAGDROP_ACTION_NONE);
+
+ // We're done with the drag context.
+ mTargetDragContextForRemote = nullptr;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContextForRemote = nullptr;
+#endif
+
+ return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
+}
+
+// nsIDragSession
+NS_IMETHODIMP
+nsDragService::SetCanDrop(bool aCanDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d", aCanDrop));
+ mCanDrop = aCanDrop;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetCanDrop(bool* aCanDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("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);
+}
+
+// count the number of URIs in some text/uri-list format data.
+static uint32_t CountTextUriListItems(const char* data, uint32_t datalen) {
+ const char* p = data;
+ const char* endPtr = p + datalen;
+ uint32_t count = 0;
+
+ 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 ...
+ if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') count++;
+ // skip to the end of the line
+ while (p < endPtr && *p != '\0' && *p != '\n') p++;
+ p++; // skip the actual newline as well.
+ }
+ return count;
+}
+
+// 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);
+ }
+}
+
+NS_IMETHODIMP
+nsDragService::GetNumDropItems(uint32_t* aNumItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems"));
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: GetNumDropItems \
+ called without a valid target widget!\n"));
+ *aNumItems = 0;
+ return NS_OK;
+ }
+
+ bool isList = IsTargetContextList();
+ if (isList)
+ mSourceDataItems->GetLength(aNumItems);
+ else {
+ GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ const char* data = reinterpret_cast<char*>(mTargetDragData);
+ *aNumItems = CountTextUriListItems(data, mTargetDragDataLen);
+ } else
+ *aNumItems = 1;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems));
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex));
+
+ // make sure that we have a transferable
+ if (!aTransferable) return NS_ERROR_INVALID_ARG;
+
+ if (!mTargetWidget) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** warning: 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)) return rv;
+
+ // check to see if this is an internal list
+ bool isList = IsTargetContextList();
+
+ if (isList) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list..."));
+ // find a matching flavor
+ for (uint32_t i = 0; i < flavors.Length(); ++i) {
+ nsCString& flavorStr = flavors[i];
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("flavor is %s\n", flavorStr.get()));
+ // get the item with the right index
+ nsCOMPtr<nsITransferable> item =
+ do_QueryElementAt(mSourceDataItems, aItemIndex);
+ if (!item) continue;
+
+ nsCOMPtr<nsISupports> data;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("trying to get transfer data for %s\n", flavorStr.get()));
+ rv = item->GetTransferData(flavorStr.get(), getter_AddRefs(data));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n"));
+ continue;
+ }
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n"));
+ rv = aTransferable->SetTransferData(flavorStr.get(), data);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("fail to set transfer data into transferable!\n"));
+ continue;
+ }
+ // ok, we got the data
+ return NS_OK;
+ }
+ // if we got this far, we failed
+ return NS_ERROR_FAILURE;
+ }
+
+ // 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 = gdk_atom_intern(flavorStr.get(), FALSE);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("looking for data in type %s, gdk flavor %p\n", flavorStr.get(),
+ gdkFlavor));
+ bool dataFound = false;
+ if (gdkFlavor) {
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n"));
+ dataFound = true;
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n"));
+
+ // Dragging and dropping from the file manager would cause us
+ // to parse the source text as a nsIFile URL.
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (!mTargetDragData) {
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ }
+ if (mTargetDragData) {
+ const char* text = static_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(text, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv);
+ nsCOMPtr<nsIURI> fileURI;
+ rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText),
+ nullptr, nullptr, getter_AddRefs(fileURI));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv);
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsIFile> file;
+ rv = fileURL->GetFile(getter_AddRefs(file));
+ if (NS_SUCCEEDED(rv)) {
+ // The common wrapping code at the end of
+ // this function assumes the data is text
+ // and calls text-specific operations.
+ // Make a secret hideout here for nsIFile
+ // objects and return early.
+ aTransferable->SetTransferData(flavorStr.get(), file);
+ g_free(convertedText);
+ return NS_OK;
+ }
+ }
+ }
+ g_free(convertedText);
+ }
+ continue;
+ }
+ }
+
+ // if we are looking for text/unicode and we fail to find it
+ // on the clipboard first, try again with text/plain. If that
+ // is present, convert it to unicode.
+ if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying with text/plain;charset=utf-8\n"));
+ gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ NS_ConvertUTF8toUTF16 ucs2string(castedText, mTargetDragDataLen);
+ convertedText = ToNewUnicode(ucs2string, mozilla::fallible);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = ucs2string.Length() * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/unicode... \
+ trying again with text/plain\n"));
+ gdkFlavor = gdk_atom_intern(kTextMime, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
+ &convertedTextLen);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted plain text \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ } // if plain text data on clipboard
+ } // if plain text flavor present
+ } // if plain text charset=utf-8 flavor present
+ } // if looking for text/unicode
+
+ // 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)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with text/uri-list\n"));
+ gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got text/uri-list data\n"));
+ const char* data = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+
+ GetTextUriListItem(data, mTargetDragDataLen, aItemIndex,
+ &convertedText, &convertedTextLen);
+
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("successfully converted \
+ _NETSCAPE_URL to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get text/uri-list data\n"));
+ }
+ if (!dataFound) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("we were looking for text/x-moz-url...\
+ trying again with _NETSCAP_URL\n"));
+ gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE);
+ GetTargetDragData(gdkFlavor);
+ if (mTargetDragData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Got _NETSCAPE_URL data\n"));
+ const char* castedText = reinterpret_cast<char*>(mTargetDragData);
+ char16_t* convertedText = nullptr;
+ uint32_t convertedTextLen = 0;
+ UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText,
+ &convertedTextLen);
+ if (convertedText) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("successfully converted _NETSCAPE_URL \
+ to unicode.\n"));
+ // out with the old, in with the new
+ g_free(mTargetDragData);
+ mTargetDragData = convertedText;
+ mTargetDragDataLen = convertedTextLen * 2;
+ dataFound = true;
+ }
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("failed to get _NETSCAPE_URL data\n"));
+ }
+ }
+ }
+
+ } // else we try one last ditch effort to find our data
+
+ if (dataFound) {
+ if (!flavorStr.EqualsLiteral(kCustomTypesMime)) {
+ // the DOM only wants LF, so convert from MacOS line endings
+ // to DOM line endings.
+ nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
+ flavorStr, &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!
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n"));
+ break;
+ }
+ }
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::IsDataFlavorSupported %s", 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("*** 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("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) return NS_OK;
+ mSourceDataItems->GetLength(&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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", flavors[i].get(), aDataFlavor));
+ if (flavors[i].Equals(aDataFlavor)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("boioioioiooioioioing!\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);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ tmp = mTargetWaylandDragContext->GetTargets();
+ }
+ GList* tmp_head = tmp;
+#endif
+
+ for (; tmp; tmp = tmp->next) {
+ /* Bug 331198 */
+ GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data);
+ gchar* name = nullptr;
+ name = gdk_atom_name(atom);
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("checking %s against %s\n", name, aDataFlavor));
+ if (name && (strcmp(name, aDataFlavor) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n"));
+ *_retval = true;
+ }
+ // check for automatic text/uri-list -> text/x-moz-url mapping
+ if (!*_retval && name && (strcmp(name, gTextUriListType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0 ||
+ strcmp(aDataFlavor, kFileMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's text/uri-list and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
+ if (!*_retval && name && (strcmp(name, gMozUrlType) == 0) &&
+ (strcmp(aDataFlavor, kURLMime) == 0)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("good! ( it's _NETSCAPE_URL and \
+ we're checking against text/x-moz-url )\n"));
+ *_retval = true;
+ }
+ // check for auto text/plain -> text/unicode mapping
+ if (!*_retval && name && (strcmp(name, kTextMime) == 0) &&
+ ((strcmp(aDataFlavor, kUnicodeMime) == 0) ||
+ (strcmp(aDataFlavor, kFileMime) == 0))) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("good! ( it's text plain and we're checking \
+ against text/unicode or application/x-moz-file)\n"));
+ *_retval = true;
+ }
+ g_free(name);
+ }
+
+#ifdef MOZ_WAYLAND
+ // mTargetWaylandDragContext->GetTargets allocates the list
+ // so we need to free it here.
+ if (!mTargetDragContext && tmp_head) {
+ g_list_free(tmp_head);
+ }
+#endif
+
+ return NS_OK;
+}
+
+void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::ReplyToDragMotion %d", mCanDrop));
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ action = (GdkDragAction)0;
+ break;
+ default:
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ }
+
+ gdk_drag_status(aDragContext, action, mTargetTime);
+}
+
+#ifdef MOZ_WAYLAND
+void nsDragService::ReplyToDragMotion(nsWaylandDragContext* aDragContext) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService::ReplyToDragMotion %d", mCanDrop));
+
+ GdkDragAction action = (GdkDragAction)0;
+ if (mCanDrop) {
+ // notify the dragger if we can drop
+ switch (mDragAction) {
+ case DRAGDROP_ACTION_COPY:
+ action = GDK_ACTION_COPY;
+ break;
+ case DRAGDROP_ACTION_LINK:
+ action = GDK_ACTION_LINK;
+ break;
+ case DRAGDROP_ACTION_NONE:
+ action = (GdkDragAction)0;
+ break;
+ default:
+ action = GDK_ACTION_MOVE;
+ break;
+ }
+ }
+
+ aDragContext->SetDragStatus(action);
+}
+#endif
+
+void nsDragService::TargetDataReceived(GtkWidget* aWidget,
+ GdkDragContext* aContext, gint aX,
+ gint aY,
+ GtkSelectionData* aSelectionData,
+ guint aInfo, guint32 aTime) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived"));
+ TargetResetData();
+
+ mTargetDragDataReceived = true;
+ gint len = gtk_selection_data_get_length(aSelectionData);
+ const guchar* data = gtk_selection_data_get_data(aSelectionData);
+
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ char* name = gdk_atom_name(target);
+ nsCString flavor(name);
+ g_free(name);
+
+ if (len > 0 && data) {
+ mTargetDragDataLen = len;
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, data, mTargetDragDataLen);
+
+ nsTArray<uint8_t> copy;
+ if (!copy.SetLength(len, fallible)) {
+ return;
+ }
+ memcpy(copy.Elements(), data, len);
+
+ mCachedData.Put(flavor, std::move(copy));
+ } else {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Failed to get data. selection data len was %d\n",
+ mTargetDragDataLen));
+
+ mCachedData.Put(flavor, nsTArray<uint8_t>());
+ }
+}
+
+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);
+ }
+#ifdef MOZ_WAYLAND
+ GList* tmp_head = nullptr;
+ if (mTargetWaylandDragContext) {
+ tmp_head = tmp = mTargetWaylandDragContext->GetTargets();
+ }
+#endif
+
+ // 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);
+ gchar* name = nullptr;
+ name = gdk_atom_name(atom);
+ if (name && strcmp(name, gMimeListType) == 0) retval = true;
+ g_free(name);
+ if (retval) break;
+ }
+
+#ifdef MOZ_WAYLAND
+ // mTargetWaylandDragContext->GetTargets allocates the list
+ // so we need to free it here.
+ if (mTargetWaylandDragContext && tmp_head) {
+ g_list_free(tmp_head);
+ }
+#endif
+
+ return retval;
+}
+
+// Maximum time to wait for a "drag_received" arrived, in microseconds
+#define NS_DND_TIMEOUT 500000
+
+void nsDragService::GetTargetDragData(GdkAtom aFlavor) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %p\n", aFlavor));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("mLastWidget is %p and mLastContext is %p\n", mTargetWidget.get(),
+ mTargetDragContext.get()));
+ // reset our target data areas
+ TargetResetData();
+
+ if (mTargetDragContext) {
+ char* name = gdk_atom_name(aFlavor);
+ nsCString flavor(name);
+ g_free(name);
+
+ // 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.
+ if (nsTArray<uint8_t>* cached = mCachedData.GetValue(flavor)) {
+ mTargetDragDataLen = cached->Length();
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("Using cached data for %s, length is %d", flavor.get(),
+ mTargetDragDataLen));
+
+ if (mTargetDragDataLen) {
+ mTargetDragData = g_malloc(mTargetDragDataLen);
+ memcpy(mTargetDragData, cached->Elements(), mTargetDragDataLen);
+ }
+
+ mTargetDragDataReceived = true;
+ return;
+ }
+
+ gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime);
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration."));
+ PRTime entryTime = PR_Now();
+ while (!mTargetDragDataReceived && mDoingDrag) {
+ // check the number of iterations
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n"));
+ PR_Sleep(20 * PR_TicksPerSecond() / 1000); /* sleep for 20 ms/iteration */
+ if (PR_Now() - entryTime > NS_DND_TIMEOUT) break;
+ gtk_main_iteration();
+ }
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ mTargetDragData = mTargetWaylandDragContext->GetData(gdk_atom_name(aFlavor),
+ &mTargetDragDataLen);
+ mTargetDragDataReceived = true;
+ }
+#endif
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n"));
+}
+
+void nsDragService::TargetResetData(void) {
+ mTargetDragDataReceived = false;
+ // make sure to free old data if we have to
+ g_free(mTargetDragData);
+ mTargetDragData = 0;
+ mTargetDragDataLen = 0;
+}
+
+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);
+
+ // 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.
+ GtkTargetEntry* listTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gMimeListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+
+ // 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)) {
+ listTarget = (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ listTarget->target = g_strdup(gTextUriListType);
+ listTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", listTarget->target));
+ targetArray.AppendElement(listTarget);
+ }
+ }
+ } // 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];
+
+ GtkTargetEntry* target =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ target->target = g_strdup(flavorStr.get());
+ target->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("adding target %s\n", target->target));
+ targetArray.AppendElement(target);
+
+ // If there is a file, add the text/uri-list type.
+ if (flavorStr.EqualsLiteral(kFileMime)) {
+ GtkTargetEntry* urilistTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ urilistTarget->target = g_strdup(gTextUriListType);
+ urilistTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", urilistTarget->target));
+ targetArray.AppendElement(urilistTarget);
+ }
+ // Check to see if this is text/unicode.
+ // If it is, add text/plain
+ // since we automatically support text/plain
+ // if we support text/unicode.
+ else if (flavorStr.EqualsLiteral(kUnicodeMime)) {
+ GtkTargetEntry* plainUTF8Target =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ plainUTF8Target->target = g_strdup(gTextPlainUTF8Type);
+ plainUTF8Target->flags = 0;
+ MOZ_LOG(
+ sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", plainUTF8Target->target));
+ targetArray.AppendElement(plainUTF8Target);
+
+ GtkTargetEntry* plainTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ plainTarget->target = g_strdup(kTextMime);
+ plainTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", plainTarget->target));
+ targetArray.AppendElement(plainTarget);
+ }
+ // 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)) {
+ GtkTargetEntry* urlTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ urlTarget->target = g_strdup(gMozUrlType);
+ urlTarget->flags = 0;
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", urlTarget->target));
+ targetArray.AppendElement(urlTarget);
+ }
+ // XdndDirectSave
+ else if (flavorStr.EqualsLiteral(kFilePromiseMime)) {
+ GtkTargetEntry* directsaveTarget =
+ (GtkTargetEntry*)g_malloc(sizeof(GtkTargetEntry));
+ directsaveTarget->target = g_strdup(gXdndDirectSaveType);
+ directsaveTarget->flags = 0;
+ MOZ_LOG(
+ sDragLm, LogLevel::Debug,
+ ("automatically adding target %s\n", directsaveTarget->target));
+ targetArray.AppendElement(directsaveTarget);
+ }
+ }
+ }
+ }
+
+ // 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("SourceEndDragSession result %d\n", 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();
+ if (display) {
+ gint scale = mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ gdk_display_get_pointer(display, nullptr, &x, &y, nullptr);
+ SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale));
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("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 == MOZ_GTK_DRAG_RESULT_SUCCESS) {
+ // 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)
+ dropEffect = DRAGDROP_ACTION_NONE;
+ else if (action & GDK_ACTION_COPY)
+ dropEffect = DRAGDROP_ACTION_COPY;
+ else if (action & GDK_ACTION_LINK)
+ dropEffect = DRAGDROP_ACTION_LINK;
+ else if (action & GDK_ACTION_MOVE)
+ dropEffect = DRAGDROP_ACTION_MOVE;
+ else
+ dropEffect = DRAGDROP_ACTION_COPY;
+
+ } else {
+ dropEffect = DRAGDROP_ACTION_NONE;
+
+ bool isWaylandTabDrop = false;
+#ifdef MOZ_WAYLAND
+ // Bug 1527976. Wayland protocol does not have any way how to handle
+ // MOZ_GTK_DRAG_RESULT_NO_TARGET drop result so consider all tab
+ // drops as not cancelled on wayland.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay() &&
+ aResult == MOZ_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);
+ gchar* name = gdk_atom_name(atom);
+ if (name && (strcmp(name, gTabDropType) == 0)) {
+ isWaylandTabDrop = true;
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("is wayland tab drop\n"));
+ break;
+ }
+ }
+ }
+#endif
+ if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET && !isWaylandTabDrop) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("drop is user chancelled\n"));
+ mUserCancelled = true;
+ }
+ }
+
+ if (mDataTransfer) {
+ mDataTransfer->SetDropEffectInt(dropEffect);
+ }
+
+ // Schedule the appropriate drag end dom events.
+ Schedule(eDragTaskSourceEnd, nullptr, nullptr, nullptr,
+ LayoutDeviceIntPoint(), 0);
+}
+
+static void CreateURIList(nsIArray* aItems, nsACString& aURIList) {
+ uint32_t length = 0;
+ aItems->GetLength(&length);
+
+ for (uint32_t i = 0; i < length; ++i) {
+ nsCOMPtr<nsITransferable> item = do_QueryElementAt(aItems, i);
+ if (!item) {
+ continue;
+ }
+
+ nsCOMPtr<nsISupports> data;
+ nsresult rv = item->GetTransferData(kURLMime, getter_AddRefs(data));
+ if (NS_SUCCEEDED(rv)) {
+ nsCOMPtr<nsISupportsString> string = do_QueryInterface(data);
+
+ nsAutoString text;
+ if (string) {
+ string->GetData(text);
+ }
+
+ // text/x-moz-url is of form url + "\n" + title.
+ // We just want the url.
+ int32_t separatorPos = text.FindChar(u'\n');
+ if (separatorPos >= 0) {
+ text.Truncate(separatorPos);
+ }
+
+ AppendUTF16toUTF8(text, aURIList);
+ aURIList.AppendLiteral("\r\n");
+ continue;
+ }
+
+ // There is no URI available. If there is a file available, create
+ // a URI from the file.
+ rv = item->GetTransferData(kFileMime, getter_AddRefs(data));
+ if (NS_SUCCEEDED(rv)) {
+ if (nsCOMPtr<nsIFile> file = do_QueryInterface(data)) {
+ nsCOMPtr<nsIURI> fileURI;
+ NS_NewFileURI(getter_AddRefs(fileURI), file);
+ if (fileURI) {
+ nsAutoCString spec;
+ fileURI->GetSpec(spec);
+
+ aURIList.Append(spec);
+ aURIList.AppendLiteral("\r\n");
+ }
+ }
+ }
+ }
+}
+
+void nsDragService::SourceDataGet(GtkWidget* aWidget, GdkDragContext* aContext,
+ GtkSelectionData* aSelectionData,
+ guint32 aTime) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet"));
+ GdkAtom target = gtk_selection_data_get_target(aSelectionData);
+ gchar* typeName = gdk_atom_name(target);
+ if (!typeName) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n"));
+ return;
+ }
+
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName));
+ auto freeTypeName = mozilla::MakeScopeExit([&] { g_free(typeName); });
+ // check to make sure that we have data items to return.
+ if (!mSourceDataItems) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n"));
+ return;
+ }
+
+ nsDependentCSubstring mimeFlavor(typeName, strlen(typeName));
+ nsCOMPtr<nsITransferable> item;
+ item = do_QueryElementAt(mSourceDataItems, 0);
+ if (item) {
+ // if someone was asking for text/plain, lookup unicode instead so
+ // we can convert it.
+ bool needToDoConversionToPlainText = false;
+ const char* actualFlavor;
+ if (mimeFlavor.EqualsLiteral(kTextMime) ||
+ mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) {
+ actualFlavor = kUnicodeMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for _NETSCAPE_URL we need to convert to
+ // plain text but we also need to look for x-moz-url
+ else if (mimeFlavor.EqualsLiteral(gMozUrlType)) {
+ actualFlavor = kURLMime;
+ needToDoConversionToPlainText = true;
+ }
+ // if someone was asking for text/uri-list we need to convert to
+ // plain text.
+ else if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
+ actualFlavor = gTextUriListType;
+ needToDoConversionToPlainText = true;
+ }
+ // Someone is asking for the special Direct Save Protocol type.
+ else if (mimeFlavor.EqualsLiteral(gXdndDirectSaveType)) {
+ // Indicate failure by default.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"E", 1);
+
+ GdkAtom property = gdk_atom_intern(gXdndDirectSaveType, FALSE);
+ GdkAtom type = gdk_atom_intern(kTextMime, FALSE);
+
+ guchar* data;
+ gint length;
+ if (!gdk_property_get(gdk_drag_context_get_source_window(aContext),
+ property, type, 0, INT32_MAX, FALSE, nullptr,
+ nullptr, &length, &data)) {
+ return;
+ }
+
+ // Zero-terminate the string.
+ data = (guchar*)g_realloc(data, length + 1);
+ if (!data) return;
+ data[length] = '\0';
+
+ gchar* hostname;
+ char* gfullpath =
+ g_filename_from_uri((const gchar*)data, &hostname, nullptr);
+ g_free(data);
+ if (!gfullpath) return;
+
+ nsCString fullpath(gfullpath);
+ g_free(gfullpath);
+
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("XdndDirectSave filepath is %s\n", fullpath.get()));
+
+ // 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)) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("ignored drag because of different host.\n"));
+
+ // Special error code "F" for this case.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"F", 1);
+ g_free(hostname);
+ return;
+ }
+ }
+
+ g_free(hostname);
+ }
+
+ nsCOMPtr<nsIFile> file;
+ if (NS_FAILED(
+ NS_NewNativeLocalFile(fullpath, false, getter_AddRefs(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));
+
+ item->SetTransferData(kFilePromiseDirectoryMime, directory);
+
+ nsCOMPtr<nsISupportsString> filenamePrimitive =
+ do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID);
+ if (!filenamePrimitive) return;
+
+ nsAutoString leafName;
+ file->GetLeafName(leafName);
+ filenamePrimitive->SetData(leafName);
+
+ item->SetTransferData(kFilePromiseDestFilename, filenamePrimitive);
+
+ // Request a different type in GetTransferData.
+ actualFlavor = kFilePromiseMime;
+ } else {
+ actualFlavor = typeName;
+ }
+ nsresult rv;
+ nsCOMPtr<nsISupports> data;
+ rv = item->GetTransferData(actualFlavor, getter_AddRefs(data));
+
+ if (strcmp(actualFlavor, kFilePromiseMime) == 0) {
+ if (NS_SUCCEEDED(rv)) {
+ // Indicate success.
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)"S", 1);
+ }
+ return;
+ }
+
+ if (NS_SUCCEEDED(rv)) {
+ void* tmpData = nullptr;
+ uint32_t tmpDataLen = 0;
+ nsPrimitiveHelpers::CreateDataFromPrimitive(
+ nsDependentCString(actualFlavor), 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 (needToDoConversionToPlainText) {
+ 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
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)tmpData,
+ tmpDataLen);
+ // this wasn't allocated with glib
+ free(tmpData);
+ }
+ } else {
+ if (mimeFlavor.EqualsLiteral(gTextUriListType)) {
+ // fall back for text/uri-list
+ nsAutoCString list;
+ CreateURIList(mSourceDataItems, list);
+ gtk_selection_data_set(aSelectionData, target, 8, (guchar*)list.get(),
+ list.Length());
+ return;
+ }
+ }
+ }
+}
+
+void nsDragService::SourceBeginDrag(GdkDragContext* 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)) {
+ return;
+ }
+
+ nsCOMPtr<nsISupportsString> fileName = do_QueryInterface(data);
+ if (!fileName) {
+ 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());
+ }
+ }
+}
+
+void nsDragService::SetDragIcon(GdkDragContext* aContext) {
+ if (!mHasImage && !mSelection) return;
+
+ LayoutDeviceIntRect dragRect;
+ nsPresContext* pc;
+ RefPtr<SourceSurface> surface;
+ DrawDrag(mSourceNode, mRegion, mScreenPosition, &dragRect, &surface, &pc);
+ if (!pc) return;
+
+ LayoutDeviceIntPoint screenPoint =
+ ConvertToUnscaledDevPixels(pc, mScreenPosition);
+ 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.
+ if (mDragPopup && gtk_check_version(3, 19, 4)) {
+ 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) {
+ OpenDragPopup();
+ gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+ }
+ }
+ }
+ } else if (surface) {
+ if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
+ GdkPixbuf* dragPixbuf = nsImageToPixbuf::SourceSurfaceToPixbuf(
+ surface, dragRect.width, dragRect.height);
+ if (dragPixbuf) {
+ gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
+ g_object_unref(dragPixbuf);
+ }
+ }
+ }
+}
+
+static void invisibleSourceDragBegin(GtkWidget* aWidget,
+ GdkDragContext* aContext, gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin"));
+ 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet"));
+ nsDragService* dragService = (nsDragService*)aData;
+ dragService->SourceDataGet(aWidget, aContext, aSelectionData, aTime);
+}
+
+static gboolean invisibleSourceDragFailed(GtkWidget* aWidget,
+ GdkDragContext* aContext,
+ gint aResult, gpointer aData) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", 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) {
+ MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd"));
+ nsDragService* dragService = (nsDragService*)aData;
+
+ // The drag has ended. Release the hostages!
+ dragService->SourceEndDragSession(aContext, MOZ_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,
+ nsWaylandDragContext* aWaylandDragContext,
+ 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, aWaylandDragContext,
+ 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, nullptr,
+ LayoutDeviceIntPoint(), 0)) {
+ NS_WARNING("Drag leave after drop");
+ }
+}
+
+gboolean nsDragService::ScheduleDropEvent(
+ nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ LayoutDeviceIntPoint aWindowPoint, guint aTime) {
+ if (!Schedule(eDragTaskDrop, aWindow, aDragContext, aWaylandDragContext,
+ aWindowPoint, aTime)) {
+ NS_WARNING("Additional drag drop ignored");
+ return FALSE;
+ }
+
+ SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset());
+
+ // We'll reply with gtk_drag_finish().
+ return TRUE;
+}
+
+gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ 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.
+ if (mScheduledTask == eDragTaskSourceEnd ||
+ (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd))
+ return FALSE;
+
+ mScheduledTask = aTask;
+ mPendingWindow = aWindow;
+ mPendingDragContext = aDragContext;
+#ifdef MOZ_WAYLAND
+ mPendingWaylandDragContext = aWaylandDragContext;
+#endif
+ mPendingWindowPoint = aWindowPoint;
+ mPendingTime = aTime;
+
+ if (!mTaskSource) {
+ // High priority is used here because the native events involved have
+ // already waited at default priority. Perhaps a lower than default
+ // priority could be used for motion tasks because there is a chance
+ // that a leave or drop is waiting, but managing different priorities
+ // may not be worth the effort. Motion tasks shouldn't queue up as
+ // they should be throttled based on replies.
+ mTaskSource =
+ g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, this, nullptr);
+ }
+ return TRUE;
+}
+
+gboolean nsDragService::TaskDispatchCallback(gpointer data) {
+ RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data);
+ return dragService->RunScheduledTask();
+}
+
+gboolean nsDragService::RunScheduledTask() {
+ if (mTargetWindow && mTargetWindow != mPendingWindow) {
+ MOZ_LOG(sDragLm, LogLevel::Debug,
+ ("nsDragService: dispatch drag leave (%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) {
+ 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->GetMozContainerWidget();
+ mTargetDragContext = std::move(mPendingDragContext);
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContext = std::move(mPendingWaylandDragContext);
+#endif
+ mTargetTime = mPendingTime;
+
+ mCachedData.Clear();
+
+ // 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) {
+ UpdateDragAction();
+ TakeDragEventDispatchedToChildProcess(); // Clear the old value.
+ DispatchMotionEvents();
+ if (task == eDragTaskMotion) {
+ if (TakeDragEventDispatchedToChildProcess()) {
+ mTargetDragContextForRemote = mTargetDragContext;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContextForRemote = mTargetWaylandDragContext;
+#endif
+ } else {
+ // Reply to tell the source whether we can drop and what
+ // action would be taken.
+ if (mTargetDragContext) {
+ ReplyToDragMotion(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ ReplyToDragMotion(mTargetWaylandDragContext);
+ }
+#endif
+ }
+ }
+ }
+
+ if (task == eDragTaskDrop) {
+ 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) {
+ gtk_drag_finish(mTargetDragContext, success,
+ /* del = */ FALSE, mTargetTime);
+ }
+
+ // This drag is over, so clear out our reference to the previous
+ // window.
+ mTargetWindow = nullptr;
+ // 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.
+ mTargetWidget = nullptr;
+ mTargetDragContext = nullptr;
+#ifdef MOZ_WAYLAND
+ mTargetWaylandDragContext = nullptr;
+#endif
+
+ mCachedData.Clear();
+
+ // 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.
+ 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() {
+ // 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.
+
+ // default is to do nothing
+ int action = nsIDragService::DRAGDROP_ACTION_NONE;
+ GdkDragAction gdkAction = GDK_ACTION_DEFAULT;
+ if (mTargetDragContext) {
+ gdkAction = gdk_drag_context_get_actions(mTargetDragContext);
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContext) {
+ gdkAction = mTargetWaylandDragContext->GetAvailableDragActions();
+ }
+#endif
+
+ // set the default just in case nothing matches below
+ if (gdkAction & GDK_ACTION_DEFAULT)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // first check to see if move is set
+ if (gdkAction & GDK_ACTION_MOVE)
+ action = nsIDragService::DRAGDROP_ACTION_MOVE;
+
+ // then fall to the others
+ else if (gdkAction & GDK_ACTION_LINK)
+ action = nsIDragService::DRAGDROP_ACTION_LINK;
+
+ // copy is ctrl
+ else if (gdkAction & GDK_ACTION_COPY)
+ action = nsIDragService::DRAGDROP_ACTION_COPY;
+
+ // update the drag information
+ SetDragAction(action);
+}
+
+NS_IMETHODIMP
+nsDragService::UpdateDragEffect() {
+ if (mTargetDragContextForRemote) {
+ ReplyToDragMotion(mTargetDragContextForRemote);
+ mTargetDragContextForRemote = nullptr;
+ }
+#ifdef MOZ_WAYLAND
+ else if (mTargetWaylandDragContextForRemote) {
+ ReplyToDragMotion(mTargetWaylandDragContextForRemote);
+ mTargetWaylandDragContextForRemote = nullptr;
+ }
+#endif
+ return NS_OK;
+}
+
+void nsDragService::DispatchMotionEvents() {
+ mCanDrop = false;
+
+ FireDragEventAtSource(eDrag, GetCurrentModifiers());
+
+ 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->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();
+}
diff --git a/widget/gtk/nsDragService.h b/widget/gtk/nsDragService.h
new file mode 100644
index 0000000000..6072824f49
--- /dev/null
+++ b/widget/gtk/nsDragService.h
@@ -0,0 +1,210 @@
+/* -*- 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 "nsIObserver.h"
+#include <gtk/gtk.h>
+
+class nsICookieJarSettings;
+class nsWindow;
+class nsWaylandDragContext;
+
+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;
+
+ 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,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ mozilla::LayoutDeviceIntPoint aWindowPoint,
+ guint aTime);
+ void ScheduleLeaveEvent();
+ gboolean ScheduleDropEvent(nsWindow* aWindow, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ 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);
+
+ void SourceBeginDrag(GdkDragContext* aContext);
+
+ // set the drag icon during drag-begin
+ void SetDragIcon(GdkDragContext* aContext);
+
+ 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;
+
+ // 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.
+ nsDataHashtable<nsCStringHashKey, nsTArray<uint8_t>> mCachedData;
+
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mPendingWaylandDragContext;
+#endif
+ 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;
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mTargetWaylandDragContext;
+#endif
+ // mTargetDragContextForRemote is set while waiting for a reply from
+ // a child process.
+ RefPtr<GdkDragContext> mTargetDragContextForRemote;
+#ifdef MOZ_WAYLAND
+ RefPtr<nsWaylandDragContext> mTargetWaylandDragContextForRemote;
+#endif
+ 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;
+ // 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);
+ // this will reset all of the target vars
+ void TargetResetData(void);
+
+ // 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,
+ nsWaylandDragContext* aPendingWaylandDragContext,
+ 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();
+ void UpdateDragAction();
+ MOZ_CAN_RUN_SCRIPT void DispatchMotionEvents();
+ void ReplyToDragMotion(GdkDragContext* aDragContext);
+#ifdef MOZ_WAYLAND
+ void ReplyToDragMotion(nsWaylandDragContext* aDragContext);
+#endif
+ gboolean DispatchDropEvent();
+ static uint32_t GetCurrentModifiers();
+};
+
+#endif // nsDragService_h__
diff --git a/widget/gtk/nsFilePicker.cpp b/widget/gtk/nsFilePicker.cpp
new file mode 100644
index 0000000000..b1f9ed3961
--- /dev/null
+++ b/widget/gtk/nsFilePicker.cpp
@@ -0,0 +1,646 @@
+/* -*- 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 "nsGtkUtils.h"
+#include "nsIFileURL.h"
+#include "nsIGIOService.h"
+#include "nsIURI.h"
+#include "nsIWidget.h"
+#include "nsIFile.h"
+#include "mozilla/Preferences.h"
+
+#include "nsArrayEnumerator.h"
+#include "nsMemory.h"
+#include "nsEnumeratorUtils.h"
+#include "nsNetUtil.h"
+#include "nsReadableUtils.h"
+#include "MozContainer.h"
+#include "gfxPlatformGtk.h"
+
+#include "nsFilePicker.h"
+
+using namespace mozilla;
+
+#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(int16_t 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),
+ mRunning(false),
+ mAllowURLs(false),
+ mFileChooserDelegate(nullptr) {
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ // Due to Bug 1635718 always use portal for file dialog on Wayland.
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ mUseNativeFileChooser =
+ Preferences::GetBool("widget.use-xdg-desktop-portal", true);
+ } else {
+ giovfs->ShouldUseFlatpakPortal(&mUseNativeFileChooser);
+ }
+}
+
+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::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(int16_t* aReturn) {
+ NS_ENSURE_ARG_POINTER(aReturn);
+
+ nsresult rv = Open(nullptr);
+ if (NS_FAILED(rv)) return rv;
+
+ while (mRunning) {
+ 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 (mRunning) return NS_ERROR_NOT_AVAILABLE;
+
+ 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;
+ }
+
+ 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);
+
+ mRunning = true;
+ mCallback = aCallback;
+ NS_ADDREF_THIS();
+ g_signal_connect(file_chooser, "response", G_CALLBACK(OnResponse), this);
+ GtkFileChooserShow(file_chooser);
+
+ 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);
+}
+
+void nsFilePicker::Done(void* file_chooser, gint response) {
+ mRunning = false;
+
+ int16_t 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;
+ }
+ }
+ 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 && atoi(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);
+ }
+ }
+}
diff --git a/widget/gtk/nsFilePicker.h b/widget/gtk/nsFilePicker.h
new file mode 100644
index 0000000000..9b3110aa00
--- /dev/null
+++ b/widget/gtk/nsFilePicker.h
@@ -0,0 +1,87 @@
+/* -*- 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 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(int16_t* aReturn) override;
+ void ReadValuesFromFileChooser(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;
+ int16_t mResult;
+ bool mRunning;
+ 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;
+};
+
+#endif
diff --git a/widget/gtk/nsGTKToolkit.h b/widget/gtk/nsGTKToolkit.h
new file mode 100644
index 0000000000..a4708d5466
--- /dev/null
+++ b/widget/gtk/nsGTKToolkit.h
@@ -0,0 +1,53 @@
+/* -*- 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 {
+ public:
+ nsGTKToolkit();
+
+ static nsGTKToolkit* GetToolkit();
+
+ static void Shutdown() {
+ delete gToolkit;
+ gToolkit = nullptr;
+ }
+
+ /**
+ * Get/set our value of DESKTOP_STARTUP_ID. When non-empty, this is applied
+ * to the next toplevel window to be shown or focused (and then immediately
+ * cleared).
+ */
+ void SetDesktopStartupID(const nsACString& aID) { mDesktopStartupID = aID; }
+ void GetDesktopStartupID(nsACString* aID) { *aID = mDesktopStartupID; }
+
+ /**
+ * 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() { return mFocusTimestamp; }
+
+ private:
+ static nsGTKToolkit* gToolkit;
+
+ nsCString mDesktopStartupID;
+ uint32_t mFocusTimestamp;
+};
+
+#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..41a679eab7
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.cpp
@@ -0,0 +1,2377 @@
+/* -*- 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 <gdk/gdkx.h>
+#include <dlfcn.h>
+#include <gdk/gdkkeysyms-compat.h>
+#include <X11/XKBlib.h>
+#include "X11UndefineNone.h"
+#include "IMContextWrapper.h"
+#include "WidgetUtils.h"
+#include "keysym2ucs.h"
+#include "nsContentUtils.h"
+#include "nsGtkUtils.h"
+#include "nsIBidiKeyboard.h"
+#include "nsPrintfCString.h"
+#include "nsReadableUtils.h"
+#include "nsServiceManagerUtils.h"
+#include "nsWindow.h"
+#include "gfxPlatformGtk.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
+
+namespace mozilla {
+namespace widget {
+
+LazyLogModule gKeymapWrapperLog("KeymapWrapperWidgets");
+
+#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;
+Time KeymapWrapper::sLastRepeatableKeyTime = 0;
+KeymapWrapper::RepeatState KeymapWrapper::sRepeatState =
+ KeymapWrapper::NOT_PRESSED;
+
+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(Modifier 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::Modifier 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::GetModifierMask(Modifier 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 mModifierMasks[INDEX_SUPER];
+ 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->Init();
+ return sInstance;
+ }
+
+ sInstance = new KeymapWrapper();
+ return sInstance;
+}
+
+/* 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(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Constructor, mGdkKeymap=%p", this, mGdkKeymap));
+
+ g_object_ref(mGdkKeymap);
+
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ InitXKBExtension();
+ }
+
+ Init();
+}
+
+void KeymapWrapper::Init() {
+ if (mInitialized) {
+ return;
+ }
+ mInitialized = true;
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p Init, mGdkKeymap=%p", this, mGdkKeymap));
+
+ mModifierKeys.Clear();
+ memset(mModifierMasks, 0, sizeof(mModifierMasks));
+
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ InitBySystemSettingsX11();
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ InitBySystemSettingsWayland();
+ }
+#endif
+
+ gdk_window_add_filter(nullptr, FilterEvents, this);
+
+ MOZ_LOG(gKeymapWrapperLog, 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, GetModifierMask(CAPS_LOCK), GetModifierMask(NUM_LOCK),
+ GetModifierMask(SCROLL_LOCK), GetModifierMask(LEVEL3),
+ GetModifierMask(LEVEL5), GetModifierMask(SHIFT),
+ GetModifierMask(CTRL), GetModifierMask(ALT), GetModifierMask(META),
+ GetModifierMask(SUPER), GetModifierMask(HYPER)));
+}
+
+void KeymapWrapper::InitXKBExtension() {
+ PodZero(&mKeyboardState);
+
+ int xkbMajorVer = XkbMajorVersion;
+ int xkbMinorVer = XkbMinorVersion;
+ if (!XkbLibraryVersion(&xkbMajorVer, &xkbMinorVer)) {
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XkbSelectEventDetails() for XkbControlsNotify, display=0x%p",
+ this, display));
+ return;
+ }
+
+ if (!XGetKeyboardControl(display, &mKeyboardState)) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension failed due to failure of "
+ "XGetKeyboardControl(), display=0x%p",
+ this, display));
+ return;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitXKBExtension, Succeeded", this));
+}
+
+void KeymapWrapper::InitBySystemSettingsX11() {
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xkeymap",
+ this));
+ return;
+ }
+
+ XModifierKeymap* xmodmap = XGetModifierMapping(display);
+ if (!xmodmap) {
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ ("%p InitBySystemSettings, "
+ "Failed due to null xmodmap",
+ this));
+ XFree(xkeymap);
+ return;
+ }
+ MOZ_LOG(gKeymapWrapperLog, 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.
+ Modifier 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(gKeymapWrapperLog, 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++) {
+ Modifier modifier = GetModifierForGDKKeyval(syms[j]);
+ MOZ_LOG(gKeymapWrapperLog, 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:
+ // 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++) {
+ Modifier 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_SUPER:
+ modifier = SUPER;
+ 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);
+}
+
+#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_SUPER, "Super");
+ 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(gKeymapWrapperLog, 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->GetModifierMask(CAPS_LOCK),
+ keymapWrapper->GetModifierMask(NUM_LOCK),
+ keymapWrapper->GetModifierMask(SCROLL_LOCK),
+ keymapWrapper->GetModifierMask(LEVEL3),
+ keymapWrapper->GetModifierMask(LEVEL5),
+ keymapWrapper->GetModifierMask(SHIFT),
+ keymapWrapper->GetModifierMask(CTRL),
+ keymapWrapper->GetModifierMask(ALT),
+ keymapWrapper->GetModifierMask(META),
+ keymapWrapper->GetModifierMask(SUPER),
+ keymapWrapper->GetModifierMask(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) {}
+static void keyboard_handle_leave(void* data, struct wl_keyboard* keyboard,
+ uint32_t serial, struct wl_surface* 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 const struct wl_keyboard_listener keyboard_listener = {
+ keyboard_handle_keymap, keyboard_handle_enter, keyboard_handle_leave,
+ keyboard_handle_key, keyboard_handle_modifiers,
+};
+
+static void seat_handle_capabilities(void* data, struct wl_seat* seat,
+ unsigned int caps) {
+ static wl_keyboard* keyboard = nullptr;
+
+ if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !keyboard) {
+ keyboard = wl_seat_get_keyboard(seat);
+ wl_keyboard_add_listener(keyboard, &keyboard_listener, nullptr);
+ } else if (!(caps & WL_SEAT_CAPABILITY_KEYBOARD) && keyboard) {
+ wl_keyboard_destroy(keyboard);
+ keyboard = nullptr;
+ }
+}
+
+static const struct wl_seat_listener seat_listener = {
+ seat_handle_capabilities,
+};
+
+static void gdk_registry_handle_global(void* data, struct wl_registry* registry,
+ uint32_t id, const char* interface,
+ uint32_t version) {
+ if (strcmp(interface, "wl_seat") == 0) {
+ auto* seat =
+ WaylandRegistryBind<wl_seat>(registry, id, &wl_seat_interface, 1);
+ wl_seat_add_listener(seat, &seat_listener, data);
+ }
+}
+
+static void gdk_registry_handle_global_remove(void* data,
+ struct wl_registry* registry,
+ uint32_t id) {}
+
+static const struct wl_registry_listener keyboard_registry_listener = {
+ gdk_registry_handle_global, gdk_registry_handle_global_remove};
+
+void KeymapWrapper::InitBySystemSettingsWayland() {
+ wl_display* display = WaylandDisplayGetWLDisplay();
+ wl_registry_add_listener(wl_display_get_registry(display),
+ &keyboard_registry_listener, this);
+}
+#endif
+
+KeymapWrapper::~KeymapWrapper() {
+ gdk_window_remove_filter(nullptr, FilterEvents, this);
+ if (mOnKeysChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnKeysChangedSignalHandle);
+ }
+ if (mOnDirectionChangedSignalHandle) {
+ g_signal_handler_disconnect(mGdkKeymap, mOnDirectionChangedSignalHandle);
+ }
+ g_object_unref(mGdkKeymap);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info, ("%p Destructor", this));
+}
+
+/* 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ ("%p FilterEvents failed due to failure "
+ "of XGetKeyboardControl(), display=0x%p",
+ self, xkbEvent->any.display));
+ }
+ break;
+ }
+ }
+
+ return GDK_FILTER_CONTINUE;
+}
+
+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() {
+ sInstance->mInitialized = false;
+ ResetBidiKeyboard();
+}
+
+/* static */
+void KeymapWrapper::OnKeysChanged(GdkKeymap* aGdkKeymap,
+ KeymapWrapper* aKeymapWrapper) {
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ ("OnDirectionChanged, aGdkKeymap=%p, aKeymapWrapper=%p", aGdkKeymap,
+ aKeymapWrapper));
+
+ ResetBidiKeyboard();
+}
+
+/* static */
+guint KeymapWrapper::GetCurrentModifierState() {
+ GdkModifierType modifiers;
+ gdk_display_get_pointer(gdk_display_get_default(), nullptr, nullptr, nullptr,
+ &modifiers);
+ return static_cast<guint>(modifiers);
+}
+
+/* static */
+bool KeymapWrapper::AreModifiersCurrentlyActive(Modifiers aModifiers) {
+ guint modifierState = GetCurrentModifierState();
+ return AreModifiersActive(aModifiers, modifierState);
+}
+
+/* static */
+bool KeymapWrapper::AreModifiersActive(Modifiers aModifiers,
+ guint aModifierState) {
+ NS_ENSURE_TRUE(aModifiers, false);
+
+ KeymapWrapper* keymapWrapper = GetInstance();
+ for (uint32_t i = 0; i < sizeof(Modifier) * 8 && aModifiers; i++) {
+ Modifier modifier = static_cast<Modifier>(1 << i);
+ if (!(aModifiers & modifier)) {
+ continue;
+ }
+ if (!(aModifierState & keymapWrapper->GetModifierMask(modifier))) {
+ return false;
+ }
+ aModifiers &= ~modifier;
+ }
+ return true;
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeCurrentKeyModifiers() {
+ return ComputeKeyModifiers(GetCurrentModifierState());
+}
+
+/* static */
+uint32_t KeymapWrapper::ComputeKeyModifiers(guint aModifierState) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ uint32_t keyModifiers = 0;
+ // DOM Meta key should be TRUE only on Mac. We need to discuss this
+ // issue later.
+ if (keymapWrapper->AreModifiersActive(SHIFT, aModifierState)) {
+ keyModifiers |= MODIFIER_SHIFT;
+ }
+ if (keymapWrapper->AreModifiersActive(CTRL, aModifierState)) {
+ keyModifiers |= MODIFIER_CONTROL;
+ }
+ if (keymapWrapper->AreModifiersActive(ALT, aModifierState)) {
+ keyModifiers |= MODIFIER_ALT;
+ }
+ if (keymapWrapper->AreModifiersActive(META, aModifierState)) {
+ keyModifiers |= MODIFIER_META;
+ }
+ if (keymapWrapper->AreModifiersActive(SUPER, aModifierState) ||
+ keymapWrapper->AreModifiersActive(HYPER, aModifierState)) {
+ keyModifiers |= MODIFIER_OS;
+ }
+ if (keymapWrapper->AreModifiersActive(LEVEL3, aModifierState) ||
+ keymapWrapper->AreModifiersActive(LEVEL5, aModifierState)) {
+ keyModifiers |= MODIFIER_ALTGRAPH;
+ }
+ if (keymapWrapper->AreModifiersActive(CAPS_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_CAPSLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(NUM_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_NUMLOCK;
+ }
+ if (keymapWrapper->AreModifiersActive(SCROLL_LOCK, aModifierState)) {
+ keyModifiers |= MODIFIER_SCROLLLOCK;
+ }
+ return keyModifiers;
+}
+
+/* static */
+void KeymapWrapper::InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState) {
+ KeymapWrapper* keymapWrapper = GetInstance();
+
+ aInputEvent.mModifiers = ComputeKeyModifiers(aModifierState);
+
+ // 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(gKeymapWrapperLog, LogLevel::Debug,
+ ("%p InitInputEvent, aModifierState=0x%08X, "
+ "aInputEvent={ mMessage=%s, mModifiers=0x%04X (Shift: %s, "
+ "Control: %s, Alt: %s, "
+ "Meta: %s, OS: %s, AltGr: %s, "
+ "CapsLock: %s, NumLock: %s, ScrollLock: %s })",
+ keymapWrapper, aModifierState, 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_OS),
+ 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 (aModifierState & GDK_BUTTON1_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::ePrimaryFlag;
+ }
+ if (aModifierState & GDK_BUTTON3_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eSecondaryFlag;
+ }
+ if (aModifierState & GDK_BUTTON2_MASK) {
+ mouseEvent.mButtons |= MouseButtonsFlag::eMiddleFlag;
+ }
+
+ if (doLog) {
+ MOZ_LOG(
+ gKeymapWrapperLog, 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->GetModifierMask(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->GetModifierMask(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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Back\" command event"));
+ return;
+ case GDK_Forward:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Forward);
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Stop\" command event"));
+ return;
+ case GDK_Search:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Search);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Search\" command event"));
+ return;
+ case GDK_Favorites:
+ aWindow->DispatchCommandEvent(nsGkAtoms::Bookmarks);
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Copy\" content command "
+ "event"));
+ return;
+ case GDK_Cut:
+ case GDK_F20:
+ aWindow->DispatchContentCommandEvent(eContentCommandCut);
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched \"Cut\" content command "
+ "event"));
+ return;
+ case GDK_Paste:
+ case GDK_F18:
+ aWindow->DispatchContentCommandEvent(eContentCommandPaste);
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched eKeyPress event "
+ "(status=%s)",
+ GetStatusName(status)));
+ } else {
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyPressEvent(), dispatched a set of composition "
+ "events"));
+ }
+}
+
+/* static */
+bool KeymapWrapper::HandleKeyReleaseEvent(nsWindow* aWindow,
+ GdkEventKey* aGdkKeyEvent) {
+ MOZ_LOG(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Error,
+ (" HandleKeyReleaseEvent(), didn't dispatch eKeyUp event"));
+ return false;
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, LogLevel::Info,
+ (" HandleKeyReleaseEvent(), dispatched eKeyUp event "
+ "(isCancelled=%s)",
+ GetBoolName(isCancelled)));
+ return true;
+}
+
+/* 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;
+ }
+
+ // 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.
+ guint modifierState = aGdkKeyEvent->state;
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (aGdkKeyEvent->is_modifier && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ Display* display = gdk_x11_display_get_xdisplay(gdkDisplay);
+ if (XEventsQueued(display, QueuedAfterReading)) {
+ XEvent nextEvent;
+ XPeekEvent(display, &nextEvent);
+ if (nextEvent.type == keymapWrapper->mXKBBaseEventCode) {
+ XkbEvent* XKBEvent = (XkbEvent*)&nextEvent;
+ if (XKBEvent->any.xkb_type == XkbStateNotify) {
+ XkbStateNotifyEvent* stateNotifyEvent =
+ (XkbStateNotifyEvent*)XKBEvent;
+ modifierState &= ~0xFF;
+ modifierState |= stateNotifyEvent->lookup_mods;
+ }
+ }
+ }
+ }
+ 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.mPluginEvent.Copy(*aGdkKeyEvent);
+ aKeyEvent.mTime = aGdkKeyEvent->time;
+ aKeyEvent.mNativeKeyEvent = static_cast<void*>(aGdkKeyEvent);
+ aKeyEvent.mIsRepeat =
+ sRepeatState == REPEATING &&
+ aGdkKeyEvent->hardware_keycode == sLastRepeatableHardwareKeyCode;
+
+ MOZ_LOG(
+ gKeymapWrapperLog, 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 aModifierState, gint aGroup) {
+ guint keyval;
+ if (!gdk_keymap_translate_keyboard_state(
+ mGdkKeymap, aGdkKeyEvent->hardware_keycode,
+ GdkModifierType(aModifierState), aGroup, &keyval, nullptr, nullptr,
+ nullptr)) {
+ return 0;
+ }
+ GdkEventKey tmpEvent = *aGdkKeyEvent;
+ tmpEvent.state = aModifierState;
+ tmpEvent.keyval = keyval;
+ tmpEvent.group = aGroup;
+ return GetCharCodeFor(&tmpEvent);
+}
+
+uint32_t KeymapWrapper::GetUnmodifiedCharCodeFor(
+ const GdkEventKey* aGdkKeyEvent) {
+ guint state = aGdkKeyEvent->state &
+ (GetModifierMask(SHIFT) | GetModifierMask(CAPS_LOCK) |
+ GetModifierMask(NUM_LOCK) | GetModifierMask(SCROLL_LOCK) |
+ GetModifierMask(LEVEL3) | GetModifierMask(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 & ~(GetModifierMask(LEVEL3) | GetModifierMask(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) {
+ 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;
+}
+
+/* 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->GetModifierMask(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(gKeymapWrapperLog, 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(gKeymapWrapperLog, LogLevel::Info,
+ ("%p WillDispatchKeyboardEventInternal, "
+ "mKeyCode=0x%02X, mCharCode=0x%08X, level=%d",
+ this, aKeyEvent.mKeyCode, aKeyEvent.mCharCode, level));
+ return;
+ }
+
+ guint baseState =
+ aGdkKeyEvent->state & ~(GetModifierMask(SHIFT) | GetModifierMask(CTRL) |
+ GetModifierMask(ALT) | GetModifierMask(META) |
+ GetModifierMask(SUPER) | GetModifierMask(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 | GetModifierMask(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(gKeymapWrapperLog, 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(gKeymapWrapperLog, 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 | GetModifierMask(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 and Meta 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() || aKeyEvent.IsMeta()) &&
+ charCode == unmodifiedCh) {
+ aKeyEvent.SetCharCode(ch);
+ }
+
+ MOZ_LOG(gKeymapWrapperLog, 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));
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsGtkKeyUtils.h b/widget/gtk/nsGtkKeyUtils.h
new file mode 100644
index 0000000000..3354cf2fee
--- /dev/null
+++ b/widget/gtk/nsGtkKeyUtils.h
@@ -0,0 +1,467 @@
+/* -*- 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 "nsTArray.h"
+#include "mozilla/EventForwards.h"
+
+#include <gdk/gdk.h>
+#include <X11/XKBlib.h>
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include <xkbcommon/xkbcommon.h>
+#endif
+
+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);
+
+ /**
+ * Modifier is list of modifiers which we support in widget level.
+ */
+ enum Modifier {
+ 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
+ };
+
+ /**
+ * Modifiers is used for combination of Modifier.
+ * E.g., |Modifiers modifiers = (SHIFT | CTRL);| means Shift and Ctrl.
+ */
+ typedef uint32_t Modifiers;
+
+ /**
+ * 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();
+
+ /**
+ * AreModifiersCurrentlyActive() checks the "current" modifier state
+ * on aGdkWindow with the keymap of the singleton instance.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @return TRUE if all of modifieres in aModifiers are
+ * active. Otherwise, FALSE.
+ */
+ static bool AreModifiersCurrentlyActive(Modifiers aModifiers);
+
+ /**
+ * 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 aModifierState);
+
+ /**
+ * InitInputEvent() initializes the aInputEvent with aModifierState.
+ */
+ static void InitInputEvent(WidgetInputEvent& aInputEvent,
+ guint aModifierState);
+
+ /**
+ * 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);
+#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();
+
+ protected:
+ /**
+ * GetInstance() returns a KeymapWrapper instance.
+ *
+ * @return A singleton instance of KeymapWrapper.
+ */
+ static KeymapWrapper* GetInstance();
+
+ KeymapWrapper();
+ ~KeymapWrapper();
+
+ bool mInitialized;
+
+ /**
+ * Initializing methods.
+ */
+ void Init();
+ void InitXKBExtension();
+ void InitBySystemSettingsX11();
+#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_SUPER,
+ INDEX_HYPER,
+ INDEX_LEVEL3,
+ INDEX_LEVEL5,
+ COUNT_OF_MODIFIER_INDEX
+ };
+ guint mModifierMasks[COUNT_OF_MODIFIER_INDEX];
+
+ guint GetModifierMask(Modifier aModifier) const;
+
+ /**
+ * @param aGdkKeyval A GDK defined modifier key value such as
+ * GDK_Shift_L.
+ * @return Returns Modifier values for aGdkKeyval.
+ * If the given key code isn't a modifier key,
+ * returns NOT_MODIFIER.
+ */
+ static Modifier GetModifierForGDKKeyval(guint aGdkKeyval);
+
+ static const char* GetModifierName(Modifier aModifier);
+
+ /**
+ * AreModifiersActive() just checks whether aModifierState indicates
+ * all modifiers in aModifiers are active or not.
+ *
+ * @param aModifiers One or more of Modifier values except
+ * NOT_MODIFIER.
+ * @param aModifierState GDK's modifier states.
+ * @return TRUE if aGdkModifierType indecates all of
+ * modifiers in aModifier are active.
+ * Otherwise, FALSE.
+ */
+ static bool AreModifiersActive(Modifiers aModifiers, guint aModifierState);
+
+ /**
+ * mGdkKeymap is a wrapped instance by this class.
+ */
+ GdkKeymap* mGdkKeymap;
+
+ /**
+ * The base event code of XKB extension.
+ */
+ int mXKBBaseEventCode;
+
+ /**
+ * 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;
+
+ /**
+ * Pointer of the singleton instance.
+ */
+ static KeymapWrapper* sInstance;
+
+ /**
+ * Auto key repeat management.
+ */
+ static guint sLastRepeatableHardwareKeyCode;
+ static Time sLastRepeatableKeyTime;
+ 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 aModifierState,
+ 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);
+
+ /**
+ * 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);
+
+ /**
+ * 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);
+
+#ifdef MOZ_WAYLAND
+ /**
+ * Utility function to set Xkb modifier key mask.
+ */
+ void SetModifierMask(xkb_keymap* aKeymap, ModifierIndex aModifierIndex,
+ const char* aModifierName);
+#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..58cf2eed16
--- /dev/null
+++ b/widget/gtk/nsGtkUtils.h
@@ -0,0 +1,23 @@
+/* -*- 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)));
+}
+
+#endif // nsGtkUtils_h__
diff --git a/widget/gtk/nsIImageToPixbuf.h b/widget/gtk/nsIImageToPixbuf.h
new file mode 100644
index 0000000000..396d1b98b5
--- /dev/null
+++ b/widget/gtk/nsIImageToPixbuf.h
@@ -0,0 +1,38 @@
+/* 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 NSIIMAGETOPIXBUF_H_
+#define NSIIMAGETOPIXBUF_H_
+
+#include "nsISupports.h"
+
+// dfa4ac93-83f2-4ab8-9b2a-0ff7022aebe2
+#define NSIIMAGETOPIXBUF_IID \
+ { \
+ 0xdfa4ac93, 0x83f2, 0x4ab8, { \
+ 0x9b, 0x2a, 0x0f, 0xf7, 0x02, 0x2a, 0xeb, 0xe2 \
+ } \
+ }
+
+class imgIContainer;
+typedef struct _GdkPixbuf GdkPixbuf;
+
+/**
+ * An interface that allows converting the current frame of an imgIContainer to
+ * a GdkPixbuf*.
+ */
+class nsIImageToPixbuf : public nsISupports {
+ public:
+ NS_DECLARE_STATIC_IID_ACCESSOR(NSIIMAGETOPIXBUF_IID)
+
+ /**
+ * The return value, if not null, should be released as needed
+ * by the caller using g_object_unref.
+ */
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) = 0;
+};
+
+NS_DEFINE_STATIC_IID_ACCESSOR(nsIImageToPixbuf, NSIIMAGETOPIXBUF_IID)
+
+#endif
diff --git a/widget/gtk/nsImageToPixbuf.cpp b/widget/gtk/nsImageToPixbuf.cpp
new file mode 100644
index 0000000000..c70e589576
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.cpp
@@ -0,0 +1,108 @@
+/* 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"
+
+using mozilla::gfx::DataSourceSurface;
+using mozilla::gfx::SurfaceFormat;
+
+NS_IMPL_ISUPPORTS(nsImageToPixbuf, nsIImageToPixbuf)
+
+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;
+}
+
+NS_IMETHODIMP_(GdkPixbuf*)
+nsImageToPixbuf::ConvertImageToPixbuf(imgIContainer* aImage) {
+ return ImageToPixbuf(aImage);
+}
+
+GdkPixbuf* nsImageToPixbuf::ImageToPixbuf(imgIContainer* aImage) {
+ RefPtr<SourceSurface> surface = aImage->GetFrame(
+ imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_SYNC_DECODE | imgIContainer::FLAG_ASYNC_NOTIFY);
+
+ // 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)
+ surface = aImage->GetFrame(imgIContainer::FRAME_CURRENT,
+ imgIContainer::FLAG_NONE);
+
+ NS_ENSURE_TRUE(surface, nullptr);
+
+ return SourceSurfaceToPixbuf(surface, surface->GetSize().width,
+ surface->GetSize().height);
+}
+
+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");
+
+ GdkPixbuf* pixbuf =
+ 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;
+}
diff --git a/widget/gtk/nsImageToPixbuf.h b/widget/gtk/nsImageToPixbuf.h
new file mode 100644
index 0000000000..3f30201ac5
--- /dev/null
+++ b/widget/gtk/nsImageToPixbuf.h
@@ -0,0 +1,47 @@
+/* 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 "nsIImageToPixbuf.h"
+#include "mozilla/Attributes.h"
+
+namespace mozilla {
+namespace gfx {
+class SourceSurface;
+}
+} // namespace mozilla
+
+class nsImageToPixbuf final : public nsIImageToPixbuf {
+ typedef mozilla::gfx::SourceSurface SourceSurface;
+
+ public:
+ NS_DECL_ISUPPORTS
+ NS_IMETHOD_(GdkPixbuf*) ConvertImageToPixbuf(imgIContainer* aImage) override;
+
+ // Friendlier version of ConvertImageToPixbuf for callers inside of
+ // widget
+ /**
+ * The return value of all these, if not null, should be
+ * released as needed by the caller using g_object_unref.
+ */
+ static GdkPixbuf* ImageToPixbuf(imgIContainer* aImage);
+ static GdkPixbuf* SourceSurfaceToPixbuf(SourceSurface* aSurface,
+ int32_t aWidth, int32_t aHeight);
+
+ private:
+ ~nsImageToPixbuf() = default;
+};
+
+// fc2389b8-c650-4093-9e42-b05e5f0685b7
+#define NS_IMAGE_TO_PIXBUF_CID \
+ { \
+ 0xfc2389b8, 0xc650, 0x4093, { \
+ 0x9e, 0x42, 0xb0, 0x5e, 0x5f, 0x06, 0x85, 0xb7 \
+ } \
+ }
+
+#endif
diff --git a/widget/gtk/nsLookAndFeel.cpp b/widget/gtk/nsLookAndFeel.cpp
new file mode 100644
index 0000000000..0fdc7748ce
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.cpp
@@ -0,0 +1,1536 @@
+/* -*- 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 "nsLookAndFeel.h"
+
+#include <gtk/gtk.h>
+#include <gdk/gdk.h>
+
+#include <pango/pango.h>
+#include <pango/pango-fontmap.h>
+
+#include <fontconfig/fontconfig.h>
+#include "gfxPlatformGtk.h"
+#include "mozilla/FontPropertyTypes.h"
+#include "mozilla/RelativeLuminanceUtils.h"
+#include "mozilla/StaticPrefs_layout.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Telemetry.h"
+#include "ScreenHelperGTK.h"
+#include "nsNativeBasicThemeGTK.h"
+
+#include "gtkdrawing.h"
+#include "nsStyleConsts.h"
+#include "gfxFontConstants.h"
+#include "WidgetUtils.h"
+#include "nsWindow.h"
+
+#include "mozilla/gfx/2D.h"
+
+#include <cairo-gobject.h>
+#include "WidgetStyleCache.h"
+#include "prenv.h"
+#include "nsCSSColorUtils.h"
+
+using namespace mozilla;
+using mozilla::LookAndFeel;
+
+#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 */
+
+#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)))
+
+nsLookAndFeel::nsLookAndFeel(const LookAndFeelCache* aCache) {
+ if (aCache) {
+ DoSetCache(*aCache);
+ }
+}
+
+nsLookAndFeel::~nsLookAndFeel() = default;
+
+// 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 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
+nsresult nsLookAndFeel::InitCellHighlightColors() {
+ // NS_SUFFICIENT_LUMINOSITY_DIFFERENCE is the a11y standard for text
+ // on a background. Use 20% of that standard since we have a background
+ // on top of another background
+ int32_t minLuminosityDifference = NS_SUFFICIENT_LUMINOSITY_DIFFERENCE / 5;
+ int32_t backLuminosityDifference =
+ NS_LUMINOSITY_DIFFERENCE(mMozWindowBackground, mFieldBackground);
+ if (backLuminosityDifference >= minLuminosityDifference) {
+ mMozCellHighlightBackground = mMozWindowBackground;
+ mMozCellHighlightText = mMozWindowText;
+ return NS_OK;
+ }
+
+ uint16_t hue, sat, luminance;
+ uint8_t alpha;
+ mMozCellHighlightBackground = mFieldBackground;
+ mMozCellHighlightText = mFieldText;
+
+ NS_RGB2HSV(mMozCellHighlightBackground, 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(mMozCellHighlightText, 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(mMozCellHighlightBackground, hue, sat, luminance, alpha);
+ return NS_OK;
+}
+
+void nsLookAndFeel::NativeInit() { EnsureInit(); }
+
+void nsLookAndFeel::RefreshImpl() {
+ nsXPLookAndFeel::RefreshImpl();
+ moz_gtk_refresh();
+
+ mInitialized = false;
+}
+
+widget::LookAndFeelCache nsLookAndFeel::GetCacheImpl() {
+ LookAndFeelCache cache = nsXPLookAndFeel::GetCacheImpl();
+
+ constexpr IntID kIntIdsToCache[] = {IntID::SystemUsesDarkTheme,
+ IntID::PrefersReducedMotion,
+ IntID::UseAccessibilityTheme};
+
+ constexpr ColorID kColorIdsToCache[] = {
+ ColorID::ThemedScrollbar,
+ ColorID::ThemedScrollbarInactive,
+ ColorID::ThemedScrollbarThumb,
+ ColorID::ThemedScrollbarThumbHover,
+ ColorID::ThemedScrollbarThumbActive,
+ ColorID::ThemedScrollbarThumbInactive};
+
+ for (IntID id : kIntIdsToCache) {
+ cache.mInts().AppendElement(LookAndFeelInt(id, GetInt(id)));
+ }
+
+ for (ColorID id : kColorIdsToCache) {
+ cache.mColors().AppendElement(LookAndFeelColor(id, GetColor(id)));
+ }
+
+ return cache;
+}
+
+void nsLookAndFeel::SetCacheImpl(const LookAndFeelCache& aCache) {
+ DoSetCache(aCache);
+}
+
+void nsLookAndFeel::DoSetCache(const LookAndFeelCache& aCache) {
+ for (const auto& entry : aCache.mInts()) {
+ switch (entry.id()) {
+ case IntID::SystemUsesDarkTheme:
+ mSystemUsesDarkTheme = entry.value();
+ break;
+ case IntID::PrefersReducedMotion:
+ mPrefersReducedMotion = entry.value();
+ break;
+ case IntID::UseAccessibilityTheme:
+ mHighContrast = entry.value();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Int ID in cache");
+ break;
+ }
+ }
+ for (const auto& entry : aCache.mColors()) {
+ switch (entry.id()) {
+ case ColorID::ThemedScrollbar:
+ mThemedScrollbar = entry.color();
+ break;
+ case ColorID::ThemedScrollbarInactive:
+ mThemedScrollbarInactive = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumb:
+ mThemedScrollbarThumb = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbHover:
+ mThemedScrollbarThumbHover = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbActive:
+ mThemedScrollbarThumbActive = entry.color();
+ break;
+ case ColorID::ThemedScrollbarThumbInactive:
+ mThemedScrollbarThumbInactive = entry.color();
+ break;
+ default:
+ MOZ_ASSERT_UNREACHABLE("Bogus Color ID in cache");
+ break;
+ }
+ }
+}
+
+nsresult nsLookAndFeel::NativeGetColor(ColorID aID, nscolor& aColor) {
+ EnsureInit();
+
+ nsresult res = NS_OK;
+
+ switch (aID) {
+ // These colors don't seem to be used for anything anymore in Mozilla
+ // (except here at least TextSelectBackground and TextSelectForeground)
+ // The CSS2 colors below are used.
+ case ColorID::WindowBackground:
+ case ColorID::WidgetBackground:
+ case ColorID::TextBackground:
+ case ColorID::Activecaption: // active window caption background
+ case ColorID::Appworkspace: // MDI background color
+ case ColorID::Background: // desktop background
+ case ColorID::Window:
+ case ColorID::Windowframe:
+ case ColorID::MozDialog:
+ case ColorID::MozCombobox:
+ aColor = mMozWindowBackground;
+ break;
+ case ColorID::WindowForeground:
+ case ColorID::WidgetForeground:
+ case ColorID::TextForeground:
+ case ColorID::Captiontext: // text in active window caption, size box, and
+ // scrollbar arrow box (!)
+ case ColorID::Windowtext:
+ case ColorID::MozDialogtext:
+ aColor = mMozWindowText;
+ break;
+ case ColorID::WidgetSelectBackground:
+ case ColorID::TextSelectBackground:
+ case ColorID::IMESelectedRawTextBackground:
+ case ColorID::IMESelectedConvertedTextBackground:
+ case ColorID::MozDragtargetzone:
+ case ColorID::MozHtmlCellhighlight:
+ case ColorID::Highlight: // preference selected item,
+ aColor = mTextSelectedBackground;
+ break;
+ case ColorID::WidgetSelectForeground:
+ case ColorID::TextSelectForeground:
+ case ColorID::IMESelectedRawTextForeground:
+ case ColorID::IMESelectedConvertedTextForeground:
+ case ColorID::Highlighttext:
+ case ColorID::MozHtmlCellhighlighttext:
+ aColor = mTextSelectedText;
+ break;
+ case ColorID::MozCellhighlight:
+ aColor = mMozCellHighlightBackground;
+ break;
+ case ColorID::MozCellhighlighttext:
+ aColor = mMozCellHighlightText;
+ break;
+ case ColorID::Widget3DHighlight:
+ aColor = NS_RGB(0xa0, 0xa0, 0xa0);
+ break;
+ case ColorID::Widget3DShadow:
+ aColor = NS_RGB(0x40, 0x40, 0x40);
+ 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::SpellCheckerUnderline:
+ aColor = NS_RGB(0xff, 0, 0);
+ break;
+ case ColorID::ThemedScrollbar:
+ aColor = mThemedScrollbar;
+ break;
+ case ColorID::ThemedScrollbarInactive:
+ aColor = mThemedScrollbarInactive;
+ break;
+ case ColorID::ThemedScrollbarThumb:
+ aColor = mThemedScrollbarThumb;
+ break;
+ case ColorID::ThemedScrollbarThumbHover:
+ aColor = mThemedScrollbarThumbHover;
+ break;
+ case ColorID::ThemedScrollbarThumbActive:
+ aColor = mThemedScrollbarThumbActive;
+ break;
+ case ColorID::ThemedScrollbarThumbInactive:
+ aColor = mThemedScrollbarThumbInactive;
+ 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.
+ case ColorID::Inactivecaptiontext: // text in inactive window caption
+ aColor = mMenuTextInactive;
+ break;
+ case ColorID::Inactivecaption:
+ // inactive window caption
+ aColor = mMozWindowInactiveCaption;
+ break;
+ case ColorID::Infobackground:
+ // tooltip background color
+ aColor = mInfoBackground;
+ break;
+ case ColorID::Infotext:
+ // tooltip text color
+ aColor = mInfoText;
+ break;
+ case ColorID::Menu:
+ // menu background
+ aColor = mMenuBackground;
+ break;
+ case ColorID::Menutext:
+ // menu text
+ aColor = mMenuText;
+ break;
+ case ColorID::Scrollbar:
+ // scrollbar gray area
+ aColor = mMozScrollbar;
+ break;
+
+ case ColorID::Threedface:
+ case ColorID::Buttonface:
+ // 3-D face color
+ aColor = mMozWindowBackground;
+ break;
+
+ case ColorID::Buttontext:
+ // text on push buttons
+ aColor = mButtonText;
+ break;
+
+ case ColorID::Buttonhighlight:
+ // 3-D highlighted edge color
+ case ColorID::Threedhighlight:
+ // 3-D highlighted outer edge color
+ aColor = mFrameOuterLightBorder;
+ break;
+
+ case ColorID::Buttonshadow:
+ // 3-D shadow edge color
+ case ColorID::Threedshadow:
+ // 3-D shadow inner edge color
+ aColor = mFrameInnerDarkBorder;
+ break;
+
+ case ColorID::Threedlightshadow:
+ aColor = NS_RGB(0xE0, 0xE0, 0xE0);
+ break;
+ case ColorID::Threeddarkshadow:
+ aColor = NS_RGB(0xDC, 0xDC, 0xDC);
+ break;
+
+ case ColorID::MozEventreerow:
+ case ColorID::Field:
+ aColor = mFieldBackground;
+ break;
+ case ColorID::Fieldtext:
+ aColor = mFieldText;
+ break;
+ case ColorID::MozButtondefault:
+ // default button border color
+ aColor = mButtonDefault;
+ break;
+ case ColorID::MozButtonhoverface:
+ aColor = mButtonHoverFace;
+ break;
+ case ColorID::MozButtonhovertext:
+ aColor = mButtonHoverText;
+ break;
+ case ColorID::MozGtkButtonactivetext:
+ aColor = mButtonActiveText;
+ break;
+ case ColorID::MozMenuhover:
+ aColor = mMenuHover;
+ break;
+ case ColorID::MozMenuhovertext:
+ aColor = mMenuHoverText;
+ break;
+ case ColorID::MozOddtreerow:
+ aColor = mOddCellBackground;
+ break;
+ case ColorID::MozNativehyperlinktext:
+ aColor = mNativeHyperLinkText;
+ break;
+ case ColorID::MozComboboxtext:
+ aColor = mComboBoxText;
+ break;
+ case ColorID::MozMenubartext:
+ aColor = mMenuBarText;
+ break;
+ case ColorID::MozMenubarhovertext:
+ aColor = mMenuBarHoverText;
+ break;
+ case ColorID::MozGtkInfoBarText:
+ aColor = mInfoBarText;
+ break;
+ case ColorID::MozColheadertext:
+ aColor = mMozColHeaderText;
+ break;
+ case ColorID::MozColheaderhovertext:
+ aColor = mMozColHeaderHoverText;
+ 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::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_HORIZONTAL);
+ aResult = ConvertGTKStepperStyleToMozillaScrollArrowStyle(scrollbar);
+ break;
+ }
+ case IntID::ScrollSliderStyle:
+ aResult = eScrollThumbStyle_Proportional;
+ 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::DWMCompositor:
+ case IntID::WindowsClassic:
+ case IntID::WindowsDefaultTheme:
+ case IntID::WindowsThemeIdentifier:
+ case IntID::OperatingSystemVersionIdentifier:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::TouchEnabled:
+ aResult = mozilla::widget::WidgetUtils::IsTouchDeviceSupportPresent();
+ break;
+ case IntID::MacGraphiteTheme:
+ aResult = 0;
+ res = NS_ERROR_NOT_IMPLEMENTED;
+ break;
+ case IntID::AlertNotificationOrigin:
+ aResult = NS_ALERT_TOP;
+ break;
+ case IntID::IMERawInputUnderlineStyle:
+ case IntID::IMEConvertedTextUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_SOLID;
+ break;
+ case IntID::IMESelectedRawTextUnderlineStyle:
+ case IntID::IMESelectedConvertedTextUnderline:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_NONE;
+ break;
+ case IntID::SpellCheckerUnderlineStyle:
+ aResult = NS_STYLE_TEXT_DECORATION_STYLE_WAVY;
+ break;
+ case IntID::MenuBarDrag:
+ EnsureInit();
+ aResult = mMenuSupportsDrag;
+ break;
+ case IntID::ScrollbarButtonAutoRepeatBehavior:
+ aResult = 1;
+ break;
+ case IntID::SwipeAnimationEnabled:
+ aResult = 0;
+ break;
+ case IntID::ContextMenuOffsetVertical:
+ case IntID::ContextMenuOffsetHorizontal:
+ aResult = 2;
+ break;
+ case IntID::GTKCSDAvailable:
+ EnsureInit();
+ aResult = mCSDAvailable;
+ break;
+ case IntID::GTKCSDHideTitlebarByDefault:
+ EnsureInit();
+ aResult = mCSDHideTitlebarByDefault;
+ break;
+ case IntID::GTKCSDMaximizeButton:
+ EnsureInit();
+ aResult = mCSDMaximizeButton;
+ break;
+ case IntID::GTKCSDMinimizeButton:
+ EnsureInit();
+ aResult = mCSDMinimizeButton;
+ break;
+ case IntID::GTKCSDCloseButton:
+ EnsureInit();
+ aResult = mCSDCloseButton;
+ break;
+ case IntID::GTKCSDTransparentBackground: {
+ // Enable transparent titlebar corners for titlebar mode.
+ GdkScreen* screen = gdk_screen_get_default();
+ aResult = gdk_screen_is_composited(screen)
+ ? (nsWindow::GetSystemCSDSupportLevel() !=
+ nsWindow::CSD_SUPPORT_NONE)
+ : false;
+ break;
+ }
+ case IntID::GTKCSDReversedPlacement:
+ EnsureInit();
+ aResult = mCSDReversedPlacement;
+ break;
+ case IntID::PrefersReducedMotion: {
+ aResult = mPrefersReducedMotion;
+ break;
+ }
+ case IntID::SystemUsesDarkTheme: {
+ EnsureInit();
+ aResult = mSystemUsesDarkTheme;
+ break;
+ }
+ case IntID::GTKCSDMaximizeButtonPosition:
+ aResult = mCSDMaximizeButtonPosition;
+ break;
+ case IntID::GTKCSDMinimizeButtonPosition:
+ aResult = mCSDMinimizeButtonPosition;
+ break;
+ case IntID::GTKCSDCloseButtonPosition:
+ aResult = mCSDCloseButtonPosition;
+ break;
+ case IntID::UseAccessibilityTheme: {
+ EnsureInit();
+ aResult = mHighContrast;
+ 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 = mCaretRatio;
+ 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(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) {
+ switch (aID) {
+ case FontID::Menu: // css2
+ case FontID::PullDownMenu: // css3
+ aFontName = mMenuFontName;
+ aFontStyle = mMenuFontStyle;
+ break;
+
+ case FontID::Field: // css3
+ case FontID::List: // css3
+ aFontName = mFieldFontName;
+ aFontStyle = mFieldFontStyle;
+ break;
+
+ case FontID::Button: // 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
+ case FontID::Window: // css3
+ case FontID::Document: // css3
+ case FontID::Workspace: // css3
+ case FontID::Desktop: // css3
+ case FontID::Info: // css3
+ case FontID::Dialog: // css3
+ case FontID::Tooltips: // moz
+ case FontID::Widget: // moz
+ default:
+ aFontName = mDefaultFontName;
+ aFontStyle = mDefaultFontStyle;
+ break;
+ }
+
+ // Scale the font for the current monitor
+ double scaleFactor = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scaleFactor > 0) {
+ aFontStyle.size *=
+ widget::ScreenHelperGTK::GetGTKMonitorScaleFactor() / scaleFactor;
+ } else {
+ // Convert gdk pixels to CSS pixels.
+ aFontStyle.size /= gfxPlatformGtk::GetFontScaleFactor();
+ }
+
+ return true;
+}
+
+// Check color contrast according to
+// https://www.w3.org/TR/AERT/#color-contrast
+static bool HasGoodContrastVisibility(GdkRGBA& aColor1, GdkRGBA& aColor2) {
+ int32_t luminosityDifference = NS_LUMINOSITY_DIFFERENCE(
+ GDK_RGBA_TO_NS_RGBA(aColor1), GDK_RGBA_TO_NS_RGBA(aColor2));
+ if (luminosityDifference < NS_SUFFICIENT_LUMINOSITY_DIFFERENCE) {
+ return false;
+ }
+
+ double colorDifference = std::abs(aColor1.red - aColor2.red) +
+ std::abs(aColor1.green - aColor2.green) +
+ std::abs(aColor1.blue - aColor2.blue);
+ return (colorDifference * 255.0 > 500.0);
+}
+
+// Check if the foreground/background colors match with default white/black
+// html page colors.
+static bool IsGtkThemeCompatibleWithHTMLColors() {
+ GdkRGBA white = {1.0, 1.0, 1.0};
+ GdkRGBA black = {0.0, 0.0, 0.0};
+
+ GtkStyleContext* style = GetStyleContext(MOZ_GTK_WINDOW);
+
+ GdkRGBA textColor;
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &textColor);
+
+ // Theme text color and default white html page background
+ if (!HasGoodContrastVisibility(textColor, white)) {
+ return false;
+ }
+
+ GdkRGBA backgroundColor;
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL,
+ &backgroundColor);
+
+ // Theme background color and default white html page background
+ if (HasGoodContrastVisibility(backgroundColor, white)) {
+ return false;
+ }
+
+ // Theme background color and default black text color
+ return HasGoodContrastVisibility(backgroundColor, black);
+}
+
+static nsCString GetGtkTheme() {
+ MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
+ nsCString ret;
+ GtkSettings* settings = gtk_settings_get_default();
+ char* themeName = nullptr;
+ g_object_get(settings, "gtk-theme-name", &themeName, nullptr);
+ if (themeName) {
+ ret.Assign(themeName);
+ g_free(themeName);
+ }
+ return ret;
+}
+
+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;
+}
+
+void nsLookAndFeel::ConfigureTheme(const LookAndFeelTheme& aTheme) {
+ MOZ_ASSERT(XRE_IsContentProcess());
+ GtkSettings* settings = gtk_settings_get_default();
+ g_object_set(settings, "gtk-theme-name", aTheme.themeName().get(),
+ "gtk-application-prefer-dark-theme",
+ aTheme.preferDarkTheme() ? TRUE : FALSE, nullptr);
+}
+
+void nsLookAndFeel::WithThemeConfiguredForContent(
+ const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) {
+ nsWindow::WithSettingsChangesIgnored([&]() {
+ // Available on Gtk 3.20+.
+ static auto sGtkSettingsResetProperty =
+ (void (*)(GtkSettings*, const gchar*))dlsym(
+ RTLD_DEFAULT, "gtk_settings_reset_property");
+
+ nsCString themeName;
+ bool preferDarkTheme = false;
+
+ if (!sGtkSettingsResetProperty) {
+ // When gtk_settings_reset_property is not available, we instead
+ // record the current theme name and variant and explicitly restore
+ // them afterwards. This means we won't respond to any subsequent
+ // theme settings changes, which is unfortunate. (It's possible we
+ // could listen to xsettings changes and update the GtkSettings object
+ // ourselves in response, if we wanted to fix this.)
+ themeName = GetGtkTheme();
+ preferDarkTheme = GetPreferDarkTheme();
+ }
+
+ bool changed = ConfigureContentGtkTheme();
+ if (changed) {
+ RefreshImpl();
+ }
+
+ LookAndFeelTheme theme;
+ theme.themeName() = GetGtkTheme();
+ theme.preferDarkTheme() = GetPreferDarkTheme();
+
+ aFn(theme);
+
+ if (changed) {
+ 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", themeName.get(),
+ "gtk-application-prefer-dark-theme",
+ preferDarkTheme ? TRUE : FALSE, nullptr);
+ }
+ RefreshImpl();
+ }
+ });
+}
+
+bool nsLookAndFeel::ConfigureContentGtkTheme() {
+ bool changed = false;
+
+ GtkSettings* settings = gtk_settings_get_default();
+
+ nsAutoCString themeOverride;
+ mozilla::Preferences::GetCString("widget.content.gtk-theme-override",
+ themeOverride);
+ if (!themeOverride.IsEmpty()) {
+ g_object_set(settings, "gtk-theme-name", themeOverride.get(), nullptr);
+ changed = true;
+ LOG(("ConfigureContentGtkTheme(%s)\n", themeOverride.get()));
+ } else {
+ LOG(("ConfigureContentGtkTheme(%s)\n", GetGtkTheme().get()));
+ }
+
+ // Dark theme is active but user explicitly enables it, or we're on
+ // high-contrast (in which case we prevent content to mess up with the colors
+ // of the page), so we're done now.
+ if (!themeOverride.IsEmpty() || mHighContrast ||
+ StaticPrefs::widget_content_allow_gtk_dark_theme()) {
+ return changed;
+ }
+
+ // Try to select the light variant of the current theme first...
+ if (GetPreferDarkTheme()) {
+ LOG((" disabling gtk-application-prefer-dark-theme\n"));
+ g_object_set(settings, "gtk-application-prefer-dark-theme", FALSE, nullptr);
+ changed = true;
+ }
+
+ // ...and use a default Gtk theme as a fallback.
+ if (!IsGtkThemeCompatibleWithHTMLColors()) {
+ LOG((" Non-compatible dark theme, default to Adwaita\n"));
+ g_object_set(settings, "gtk-theme-name", "Adwaita", nullptr);
+ changed = true;
+ }
+
+ return changed;
+}
+
+void nsLookAndFeel::EnsureInit() {
+ if (mInitialized) {
+ return;
+ }
+
+ // Gtk manages a screen's CSS in the settings object so we
+ // ask Gtk to create it explicitly. Otherwise we may end up
+ // with wrong color theme, see Bug 972382
+ GtkSettings* settings = gtk_settings_get_default();
+ if (MOZ_UNLIKELY(!settings)) {
+ NS_WARNING("EnsureInit: No settings");
+ return;
+ }
+
+ mInitialized = true;
+
+ // gtk does non threadsafe refcounting
+ MOZ_ASSERT(NS_IsMainThread());
+
+ GtkStyleContext* style;
+ GdkRGBA color;
+
+ if (XRE_IsContentProcess()) {
+ LOG(("nsLookAndFeel::EnsureInit() [%p] Content process\n", (void*)this));
+ // Dark themes interacts poorly with widget styling (see bug 1216658).
+ // We disable dark themes by default for web content
+ // but allow user to overide it by prefs.
+ ConfigureContentGtkTheme();
+ } else {
+ // 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.
+ GdkRGBA bg, fg;
+ 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);
+ LOG(("nsLookAndFeel::EnsureInit() [%p] Chrome process\n", (void*)this));
+ // Update mSystemUsesDarkTheme only in the parent process since in the child
+ // processes we forcibly set gtk-theme-name so that we can't get correct
+ // results. Instead mSystemUsesDarkTheme in the child processes is updated
+ // via our caching machinery.
+ mSystemUsesDarkTheme =
+ (RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(bg)) <
+ RelativeLuminanceUtils::Compute(GDK_RGBA_TO_NS_RGBA(fg)));
+
+ mHighContrast = StaticPrefs::widget_content_gtk_high_contrast_enabled() &&
+ GetGtkTheme().Find("HighContrast"_ns) >= 0;
+
+ gboolean enableAnimations = false;
+ g_object_get(settings, "gtk-enable-animations", &enableAnimations, nullptr);
+ mPrefersReducedMotion = !enableAnimations;
+
+ // Colors that we pass to content processes through the LookAndFeelCache.
+ if (ShouldHonorThemeScrollbarColors()) {
+ // 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));
+
+ mMozScrollbar = mThemedScrollbar;
+
+ 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);
+ } else {
+ mMozScrollbar = mThemedScrollbar = widget::sScrollbarColor.ToABGR();
+ mThemedScrollbarInactive = widget::sScrollbarColor.ToABGR();
+ mThemedScrollbarThumb = widget::sScrollbarThumbColor.ToABGR();
+ mThemedScrollbarThumbHover = widget::sScrollbarThumbColorHover.ToABGR();
+ mThemedScrollbarThumbActive = widget::sScrollbarThumbColorActive.ToABGR();
+ mThemedScrollbarThumbInactive = widget::sScrollbarThumbColor.ToABGR();
+ }
+ }
+
+ // 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);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozWindowText = GDK_RGBA_TO_NS_RGBA(color);
+ 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);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_INSENSITIVE,
+ &color);
+ mMozWindowInactiveCaption = 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);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_TOOLTIP_BOX_LABEL);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoText = GDK_RGBA_TO_NS_RGBA(color);
+
+ 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);
+ mMenuText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(accelStyle, GTK_STATE_FLAG_INSENSITIVE, &color);
+ mMenuTextInactive = GDK_RGBA_TO_NS_RGBA(color);
+ g_object_unref(accelStyle);
+ }
+
+ style = GetStyleContext(MOZ_GTK_MENUPOPUP);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMenuBackground = GDK_RGBA_TO_NS_RGBA(color);
+
+ style = GetStyleContext(MOZ_GTK_MENUITEM);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mMenuHover = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMenuHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ 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);
+ mFieldBackground = GDK_RGBA_TO_NS_RGBA(bgColor);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mFieldText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // 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);
+ mTextSelectedBackground = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(
+ style,
+ static_cast<GtkStateFlags>(GTK_STATE_FLAG_FOCUSED |
+ GTK_STATE_FLAG_SELECTED),
+ &color);
+ mTextSelectedText = GDK_RGBA_TO_NS_RGBA(color);
+ };
+ GrabSelectionColors(selectionStyle);
+ if (mTextSelectedBackground == mTextSelectedText) {
+ // Some old distros/themes don't properly use the .selection style, so
+ // fall back to the regular text view style.
+ GrabSelectionColors(style);
+ }
+ }
+
+ // 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);
+ mButtonDefault = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mButtonText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mButtonHoverText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_ACTIVE, &color);
+ mButtonActiveText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_background_color(style, GTK_STATE_FLAG_PRELIGHT,
+ &color);
+ mButtonHoverFace = GDK_RGBA_TO_NS_RGBA(color);
+
+ // 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);
+
+ // Menubar text and hover text colors
+ style = GetStyleContext(MOZ_GTK_MENUBARITEM);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMenuBarText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMenuBarHoverText = 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);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mMozColHeaderText = GDK_RGBA_TO_NS_RGBA(color);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_PRELIGHT, &color);
+ mMozColHeaderHoverText = GDK_RGBA_TO_NS_RGBA(color);
+
+ // 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, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
+ if (!themeUsesColors) {
+ style = GetStyleContext(MOZ_GTK_FRAME);
+ GetBorderColors(style, &mFrameOuterLightBorder, &mFrameInnerDarkBorder);
+ }
+
+ // GtkInfoBar
+ // TODO - Use WidgetCache for it?
+ GtkWidget* infoBar = gtk_info_bar_new();
+ GtkWidget* infoBarContent =
+ gtk_info_bar_get_content_area(GTK_INFO_BAR(infoBar));
+ GtkWidget* infoBarLabel = gtk_label_new(nullptr);
+ gtk_container_add(GTK_CONTAINER(parent), infoBar);
+ gtk_container_add(GTK_CONTAINER(infoBarContent), infoBarLabel);
+ style = gtk_widget_get_style_context(infoBarLabel);
+ gtk_style_context_add_class(style, GTK_STYLE_CLASS_INFO);
+ gtk_style_context_get_color(style, GTK_STATE_FLAG_NORMAL, &color);
+ mInfoBarText = GDK_RGBA_TO_NS_RGBA(color);
+ // 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);
+
+ // 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);
+
+ gint blink_time;
+ gboolean blink;
+ g_object_get(settings, "gtk-cursor-blink-time", &blink_time,
+ "gtk-cursor-blink", &blink, nullptr);
+ mCaretBlinkTime = blink ? (int32_t)blink_time : 0;
+
+ GetSystemFontInfo(gtk_widget_get_style_context(entry), &mFieldFontName,
+ &mFieldFontStyle);
+
+ gtk_widget_destroy(window);
+ g_object_unref(labelWidget);
+
+ mCSDAvailable =
+ nsWindow::GetSystemCSDSupportLevel() != nsWindow::CSD_SUPPORT_NONE;
+ mCSDHideTitlebarByDefault = nsWindow::HideTitlebarByDefault();
+
+ 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;
+ if (layout.mAtRight) {
+ *pos += TOOLBAR_BUTTONS;
+ }
+ }
+ }
+
+ RecordTelemetry();
+}
+
+// virtual
+char16_t nsLookAndFeel::GetPasswordCharacterImpl() {
+ EnsureInit();
+ return mInvisibleCharacter;
+}
+
+bool nsLookAndFeel::GetEchoPasswordImpl() { return false; }
+
+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 =
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_CONTENTS_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL) ||
+ WidgetUsesImage(MOZ_GTK_SCROLLBAR_THUMB_VERTICAL);
+ 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);
+}
diff --git a/widget/gtk/nsLookAndFeel.h b/widget/gtk/nsLookAndFeel.h
new file mode 100644
index 0000000000..c8e4ec83b1
--- /dev/null
+++ b/widget/gtk/nsLookAndFeel.h
@@ -0,0 +1,131 @@
+/* -*- 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 __nsLookAndFeel
+#define __nsLookAndFeel
+
+#include "X11UndefineNone.h"
+#include "nsXPLookAndFeel.h"
+#include "nsCOMPtr.h"
+#include "gfxFont.h"
+
+enum WidgetNodeType : int;
+struct _GtkStyle;
+
+class nsLookAndFeel final : public nsXPLookAndFeel {
+ public:
+ explicit nsLookAndFeel(const LookAndFeelCache* aCache);
+ 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 aID, nscolor& aResult) override;
+ bool NativeGetFont(FontID aID, nsString& aFontName,
+ gfxFontStyle& aFontStyle) override;
+
+ char16_t GetPasswordCharacterImpl() override;
+ bool GetEchoPasswordImpl() override;
+
+ LookAndFeelCache GetCacheImpl() override;
+ void SetCacheImpl(const LookAndFeelCache& aCache) override;
+
+ void WithThemeConfiguredForContent(
+ const std::function<void(const LookAndFeelTheme& aTheme)>& aFn) override;
+ static void ConfigureTheme(const LookAndFeelTheme& aTheme);
+
+ bool IsCSDAvailable() const { return mCSDAvailable; }
+
+ static const nscolor kBlack = NS_RGB(0, 0, 0);
+ static const nscolor kWhite = NS_RGB(255, 255, 255);
+
+ protected:
+ void DoSetCache(const LookAndFeelCache& aCache);
+ bool WidgetUsesImage(WidgetNodeType aNodeType);
+ void RecordLookAndFeelSpecificTelemetry() override;
+ bool ShouldHonorThemeScrollbarColors();
+
+ // Cached fonts
+ nsString mDefaultFontName;
+ nsString mButtonFontName;
+ nsString mFieldFontName;
+ nsString mMenuFontName;
+ gfxFontStyle mDefaultFontStyle;
+ gfxFontStyle mButtonFontStyle;
+ gfxFontStyle mFieldFontStyle;
+ gfxFontStyle mMenuFontStyle;
+
+ // Cached colors
+ nscolor mInfoBackground = kWhite;
+ nscolor mInfoText = kBlack;
+ nscolor mMenuBackground = kWhite;
+ nscolor mMenuBarText = kBlack;
+ nscolor mMenuBarHoverText = kBlack;
+ nscolor mMenuText = kBlack;
+ nscolor mMenuTextInactive = kWhite;
+ nscolor mMenuHover = kWhite;
+ nscolor mMenuHoverText = kBlack;
+ nscolor mButtonDefault = kWhite;
+ nscolor mButtonText = kBlack;
+ nscolor mButtonHoverText = kBlack;
+ nscolor mButtonHoverFace = kWhite;
+ nscolor mButtonActiveText = kBlack;
+ nscolor mFrameOuterLightBorder = kBlack;
+ nscolor mFrameInnerDarkBorder = kBlack;
+ nscolor mOddCellBackground = kWhite;
+ nscolor mNativeHyperLinkText = kBlack;
+ nscolor mComboBoxText = kBlack;
+ nscolor mComboBoxBackground = kWhite;
+ nscolor mFieldText = kBlack;
+ nscolor mFieldBackground = kWhite;
+ nscolor mMozWindowText = kBlack;
+ nscolor mMozWindowBackground = kWhite;
+ nscolor mMozWindowActiveBorder = kBlack;
+ nscolor mMozWindowInactiveBorder = kBlack;
+ nscolor mMozWindowInactiveCaption = kWhite;
+ nscolor mMozCellHighlightBackground = kWhite;
+ nscolor mMozCellHighlightText = kBlack;
+ nscolor mTextSelectedText = kBlack;
+ nscolor mTextSelectedBackground = kWhite;
+ nscolor mMozScrollbar = kWhite;
+ nscolor mInfoBarText = kBlack;
+ nscolor mMozColHeaderText = kBlack;
+ nscolor mMozColHeaderHoverText = kBlack;
+ nscolor mThemedScrollbar = kWhite;
+ nscolor mThemedScrollbarInactive = kWhite;
+ nscolor mThemedScrollbarThumb = kBlack;
+ nscolor mThemedScrollbarThumbHover = kBlack;
+ nscolor mThemedScrollbarThumbActive = kBlack;
+ nscolor mThemedScrollbarThumbInactive = kBlack;
+ char16_t mInvisibleCharacter = 0;
+ float mCaretRatio = 0.0f;
+ int32_t mCaretBlinkTime = 0;
+ bool mMenuSupportsDrag = false;
+ bool mCSDAvailable = false;
+ bool mCSDHideTitlebarByDefault = false;
+ bool mCSDMaximizeButton = false;
+ bool mCSDMinimizeButton = false;
+ bool mCSDCloseButton = false;
+ bool mCSDReversedPlacement = false;
+ bool mSystemUsesDarkTheme = false;
+ bool mPrefersReducedMotion = false;
+ bool mHighContrast = false;
+ bool mInitialized = false;
+ int32_t mCSDMaximizeButtonPosition = 0;
+ int32_t mCSDMinimizeButtonPosition = 0;
+ int32_t mCSDCloseButtonPosition = 0;
+
+ void EnsureInit();
+ // Returns whether the current theme or theme variant was changed.
+ bool ConfigureContentGtkTheme();
+
+ private:
+ nsresult InitCellHighlightColors();
+};
+
+#endif
diff --git a/widget/gtk/nsNativeBasicThemeGTK.cpp b/widget/gtk/nsNativeBasicThemeGTK.cpp
new file mode 100644
index 0000000000..cf32161c40
--- /dev/null
+++ b/widget/gtk/nsNativeBasicThemeGTK.cpp
@@ -0,0 +1,125 @@
+/* -*- Mode: C++; tab-width: 40; 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 "nsNativeBasicThemeGTK.h"
+
+#include "nsLayoutUtils.h"
+
+using namespace mozilla;
+
+static constexpr CSSCoord kGtkMinimumScrollbarSize = 12;
+static constexpr CSSCoord kGtkMinimumThinScrollbarSize = 6;
+static constexpr CSSCoord kGtkMinimumScrollbarThumbSize = 40;
+
+already_AddRefed<nsITheme> do_GetBasicNativeThemeDoNotUseDirectly() {
+ static mozilla::StaticRefPtr<nsITheme> gInstance;
+ if (MOZ_UNLIKELY(!gInstance)) {
+ gInstance = new nsNativeBasicThemeGTK();
+ ClearOnShutdown(&gInstance);
+ }
+ return do_AddRef(gInstance);
+}
+
+nsITheme::Transparency nsNativeBasicThemeGTK::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ return IsRootScrollbar(aFrame) ? eOpaque : eTransparent;
+ default:
+ return nsNativeBasicTheme::GetWidgetTransparency(aFrame, aAppearance);
+ }
+}
+
+NS_IMETHODIMP
+nsNativeBasicThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ DPIRatio dpiRatio = GetDPIRatio(aFrame);
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::Scrollcorner: {
+ ComputedStyle* style = nsLayoutUtils::StyleForScrollbar(aFrame);
+ if (style->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ aResult->SizeTo(kGtkMinimumThinScrollbarSize * dpiRatio,
+ kGtkMinimumThinScrollbarSize * dpiRatio);
+ } else {
+ aResult->SizeTo(kGtkMinimumScrollbarSize * dpiRatio,
+ kGtkMinimumScrollbarSize * dpiRatio);
+ }
+ break;
+ }
+ default:
+ return nsNativeBasicTheme::GetMinimumWidgetSize(
+ aPresContext, aFrame, aAppearance, aResult, aIsOverridable);
+ }
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aResult->width = kGtkMinimumScrollbarThumbSize * dpiRatio;
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ aResult->height = kGtkMinimumScrollbarThumbSize * dpiRatio;
+ break;
+ default:
+ break;
+ }
+
+ *aIsOverridable = true;
+ return NS_OK;
+}
+
+void nsNativeBasicThemeGTK::PaintScrollbarThumb(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) {
+ sRGBColor thumbColor =
+ ComputeScrollbarThumbColor(aFrame, aStyle, aElementState, aDocumentState);
+ LayoutDeviceRect thumbRect(aRect);
+ thumbRect.Deflate(floorf((aHorizontal ? aRect.height : aRect.width) / 4.0f));
+ LayoutDeviceCoord radius =
+ (aHorizontal ? thumbRect.height : thumbRect.width) / 2.0f;
+ PaintRoundedRectWithRadius(aDrawTarget, thumbRect, thumbColor, sRGBColor(), 0,
+ radius / aDpiRatio, aDpiRatio);
+}
+
+void nsNativeBasicThemeGTK::PaintScrollbar(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [trackColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ Unused << borderColor;
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ gfx::ColorPattern(ToDeviceColor(trackColor)));
+}
+
+void nsNativeBasicThemeGTK::PaintScrollCorner(
+ DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect, nsIFrame* aFrame,
+ const ComputedStyle& aStyle, const EventStates& aDocumentState,
+ DPIRatio aDpiRatio, bool aIsRoot) {
+ auto [trackColor, borderColor] =
+ ComputeScrollbarColors(aFrame, aStyle, aDocumentState, aIsRoot);
+ Unused << borderColor;
+ aDrawTarget->FillRect(aRect.ToUnknownRect(),
+ gfx::ColorPattern(ToDeviceColor(trackColor)));
+}
diff --git a/widget/gtk/nsNativeBasicThemeGTK.h b/widget/gtk/nsNativeBasicThemeGTK.h
new file mode 100644
index 0000000000..9cad0cbb08
--- /dev/null
+++ b/widget/gtk/nsNativeBasicThemeGTK.h
@@ -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/. */
+
+#ifndef nsNativeBasicThemeGTK_h
+#define nsNativeBasicThemeGTK_h
+
+#include "nsNativeBasicTheme.h"
+
+class nsNativeBasicThemeGTK : public nsNativeBasicTheme {
+ public:
+ nsNativeBasicThemeGTK() = default;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) override;
+
+ nsITheme::Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+ void PaintScrollbarThumb(DrawTarget* aDrawTarget,
+ const LayoutDeviceRect& aRect, bool aHorizontal,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aElementState,
+ const EventStates& aDocumentState,
+ DPIRatio aDpiRatio) override;
+ void PaintScrollbar(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ bool aHorizontal, nsIFrame* aFrame,
+ const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+ void PaintScrollCorner(DrawTarget* aDrawTarget, const LayoutDeviceRect& aRect,
+ nsIFrame* aFrame, const ComputedStyle& aStyle,
+ const EventStates& aDocumentState, DPIRatio aDpiRatio,
+ bool aIsRoot) override;
+ bool ThemeSupportsScrollbarButtons() override { return false; }
+
+ protected:
+ virtual ~nsNativeBasicThemeGTK() = default;
+};
+
+#endif
diff --git a/widget/gtk/nsNativeThemeGTK.cpp b/widget/gtk/nsNativeThemeGTK.cpp
new file mode 100644
index 0000000000..f392db8013
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.cpp
@@ -0,0 +1,2019 @@
+/* -*- 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 "HeadlessThemeGTK.h"
+#include "nsStyleConsts.h"
+#include "gtkdrawing.h"
+#include "ScreenHelperGTK.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 "nsMenuFrame.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/EventStates.h"
+#include "mozilla/Services.h"
+
+#include <gdk/gdkprivate.h>
+#include <gtk/gtk.h>
+
+#include "gfxContext.h"
+#include "gfxPlatformGtk.h"
+#include "gfxGdkNativeRenderer.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 "nsNativeBasicTheme.h"
+
+#ifdef MOZ_X11
+# ifdef CAIRO_HAS_XLIB_SURFACE
+# include "cairo-xlib.h"
+# endif
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+# include "cairo-xlib-xrender.h"
+# endif
+#endif
+
+#include <algorithm>
+#include <dlfcn.h>
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using mozilla::dom::HTMLInputElement;
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNativeThemeGTK, nsNativeTheme, nsITheme,
+ nsIObserver)
+
+static int gLastGdkError;
+
+// Return scale factor of the monitor where the window is located
+// by the most part or layout.css.devPixelsPerPx pref if set to > 0.
+static inline gint GetMonitorScaleFactor(nsIFrame* aFrame) {
+ // When the layout.css.devPixelsPerPx is set the scale can be < 1,
+ // the real monitor scale cannot go under 1.
+ double scale = StaticPrefs::layout_css_devPixelsPerPx();
+ if (scale <= 0) {
+ nsIWidget* rootWidget = aFrame->PresContext()->GetRootWidget();
+ if (rootWidget) {
+ // We need to use GetDefaultScale() despite it returns monitor scale
+ // factor multiplied by font scale factor because it is the only scale
+ // updated in nsPuppetWidget.
+ // Since we don't want to apply font scale factor for UI elements
+ // (because GTK does not do so) we need to remove that from returned
+ // value. The computed monitor scale factor needs to be rounded before
+ // casting to integer to avoid rounding errors which would lead to
+ // returning 0.
+ int monitorScale = int(round(rootWidget->GetDefaultScale().scale /
+ gfxPlatformGtk::GetFontScaleFactor()));
+ // Monitor scale can be negative if it has not been initialized in the
+ // puppet widget yet. We also make sure that we return positive value.
+ if (monitorScale < 1) {
+ return 1;
+ }
+ return monitorScale;
+ }
+ }
+ // Use monitor scaling factor where devPixelsPerPx is set
+ return ScreenHelperGTK::GetGTKMonitorScaleFactor();
+}
+
+nsNativeThemeGTK::nsNativeThemeGTK() {
+ if (moz_gtk_init() != MOZ_GTK_SUCCESS) {
+ memset(mDisabledWidgetTypes, 0xff, sizeof(mDisabledWidgetTypes));
+ return;
+ }
+
+ // We have to call moz_gtk_shutdown before the event loop stops running.
+ nsCOMPtr<nsIObserverService> obsServ =
+ mozilla::services::GetObserverService();
+ obsServ->AddObserver(this, "xpcom-shutdown", false);
+
+ ThemeChanged();
+}
+
+nsNativeThemeGTK::~nsNativeThemeGTK() = default;
+
+NS_IMETHODIMP
+nsNativeThemeGTK::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* aData) {
+ if (!nsCRT::strcmp(aTopic, "xpcom-shutdown")) {
+ moz_gtk_shutdown();
+ } else {
+ MOZ_ASSERT_UNREACHABLE("unexpected topic");
+ return NS_ERROR_UNEXPECTED;
+ }
+
+ return NS_OK;
+}
+
+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>(mozilla::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)));
+}
+
+static bool ShouldScrollbarButtonBeDisabled(int32_t aCurpos, int32_t aMaxpos,
+ StyleAppearance aAppearance) {
+ return (
+ (aCurpos == 0 && (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft)) ||
+ (aCurpos == aMaxpos &&
+ (aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight)));
+}
+
+bool nsNativeThemeGTK::GetGtkWidgetAndState(StyleAppearance aAppearance,
+ nsIFrame* aFrame,
+ WidgetNodeType& aGtkWidgetType,
+ GtkWidgetState* aState,
+ gint* aWidgetFlags) {
+ if (aWidgetFlags) {
+ *aWidgetFlags = 0;
+ }
+ if (aState) {
+ memset(aState, 0, sizeof(GtkWidgetState));
+
+ // For XUL checkboxes and radio buttons, the state of the parent
+ // determines our state.
+ nsIFrame* stateFrame = aFrame;
+ if (aFrame && ((aWidgetFlags && (aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::Radio)) ||
+ aAppearance == StyleAppearance::CheckboxLabel ||
+ aAppearance == StyleAppearance::RadioLabel)) {
+ nsAtom* atom = nullptr;
+ if (IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) {
+ if (aAppearance == StyleAppearance::CheckboxLabel ||
+ aAppearance == StyleAppearance::RadioLabel) {
+ // Adjust stateFrame so GetContentState finds the correct state.
+ stateFrame = aFrame = aFrame->GetParent()->GetParent();
+ } else {
+ // GetContentState knows to look one frame up for radio/checkbox
+ // widgets, so don't adjust stateFrame here.
+ aFrame = aFrame->GetParent();
+ }
+ if (aWidgetFlags) {
+ if (!atom) {
+ atom = (aAppearance == StyleAppearance::Checkbox ||
+ aAppearance == StyleAppearance::CheckboxLabel)
+ ? nsGkAtoms::checked
+ : nsGkAtoms::selected;
+ }
+ *aWidgetFlags = CheckBooleanAttr(aFrame, atom);
+ }
+ } else {
+ if (aWidgetFlags) {
+ *aWidgetFlags = 0;
+ HTMLInputElement* inputElt =
+ HTMLInputElement::FromNode(aFrame->GetContent());
+ if (inputElt && inputElt->Checked())
+ *aWidgetFlags |= MOZ_GTK_WIDGET_CHECKED;
+
+ if (GetIndeterminate(aFrame))
+ *aWidgetFlags |= MOZ_GTK_WIDGET_INCONSISTENT;
+ }
+ }
+ } else if (aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Treeheadersortarrow ||
+ aAppearance == StyleAppearance::ButtonArrowPrevious ||
+ aAppearance == StyleAppearance::ButtonArrowNext ||
+ aAppearance == StyleAppearance::ButtonArrowUp ||
+ aAppearance == StyleAppearance::ButtonArrowDown) {
+ // The state of an arrow comes from its parent.
+ stateFrame = aFrame = aFrame->GetParent();
+ }
+
+ EventStates eventState = GetContentState(stateFrame, aAppearance);
+
+ aState->disabled = IsDisabled(aFrame, eventState) || IsReadOnly(aFrame);
+ aState->active = eventState.HasState(NS_EVENT_STATE_ACTIVE);
+ aState->focused = eventState.HasState(NS_EVENT_STATE_FOCUS);
+ aState->inHover = eventState.HasState(NS_EVENT_STATE_HOVER);
+ aState->isDefault = IsDefaultButton(aFrame);
+ aState->canDefault = FALSE; // XXX fix me
+
+ if (aAppearance == StyleAppearance::FocusOutline) {
+ aState->disabled = FALSE;
+ aState->active = FALSE;
+ aState->inHover = FALSE;
+ aState->isDefault = FALSE;
+ aState->canDefault = FALSE;
+
+ aState->focused = TRUE;
+ aState->depressed = TRUE; // see moz_gtk_entry_paint()
+ } else if (aAppearance == StyleAppearance::Button ||
+ aAppearance == StyleAppearance::Toolbarbutton ||
+ aAppearance == StyleAppearance::Dualbutton ||
+ aAppearance == StyleAppearance::ToolbarbuttonDropdown ||
+ aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ aState->active &= aState->inHover;
+ } else if (aAppearance == StyleAppearance::Treetwisty ||
+ aAppearance == StyleAppearance::Treetwistyopen) {
+ nsTreeBodyFrame* treeBodyFrame = do_QueryFrame(aFrame);
+ if (treeBodyFrame) {
+ const mozilla::AtomArray& atoms =
+ treeBodyFrame->GetPropertyArrayForCurrentDrawingItem();
+ aState->selected = atoms.Contains((nsStaticAtom*)nsGkAtoms::selected);
+ aState->inHover = atoms.Contains((nsStaticAtom*)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.
+ if (aAppearance == StyleAppearance::NumberInput ||
+ aAppearance == StyleAppearance::Textfield ||
+ aAppearance == StyleAppearance::Textarea ||
+ aAppearance == StyleAppearance::SpinnerTextfield ||
+ aAppearance == StyleAppearance::RadioContainer ||
+ aAppearance == StyleAppearance::RadioLabel) {
+ aState->focused = IsFocused(aFrame);
+ } else if (aAppearance == StyleAppearance::Radio ||
+ aAppearance == StyleAppearance::Checkbox) {
+ // In XUL, checkboxes and radios shouldn't have focus rings, their
+ // labels do
+ aState->focused = FALSE;
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) {
+ // for scrollbars we need to go up two to go from the thumb to
+ // the slider to the actual scrollbar object
+ nsIFrame* tmpFrame = aFrame->GetParent()->GetParent();
+
+ aState->curpos = CheckIntAttr(tmpFrame, nsGkAtoms::curpos, 0);
+ aState->maxpos = CheckIntAttr(tmpFrame, nsGkAtoms::maxpos, 100);
+
+ if (CheckBooleanAttr(aFrame, nsGkAtoms::active)) {
+ aState->active = TRUE;
+ // Set hover state to emulate Gtk style of active scrollbar thumb
+ aState->inHover = TRUE;
+ }
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight) {
+ // set the state to disabled when the scrollbar is scrolled to
+ // the beginning or the end, depending on the button type.
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 100);
+ if (ShouldScrollbarButtonBeDisabled(curpos, maxpos, aAppearance)) {
+ aState->disabled = true;
+ }
+
+ // In order to simulate native GTK scrollbar click behavior,
+ // we set the active attribute on the element to true if it's
+ // pressed with any mouse button.
+ // This allows us to show that it's active without setting :active
+ else if (CheckBooleanAttr(aFrame, nsGkAtoms::active))
+ aState->active = true;
+
+ if (aWidgetFlags) {
+ *aWidgetFlags = GetScrollbarButtonType(aFrame);
+ if (static_cast<uint8_t>(aAppearance) -
+ static_cast<uint8_t>(StyleAppearance::ScrollbarbuttonUp) <
+ 2)
+ *aWidgetFlags |= MOZ_GTK_STEPPER_VERTICAL;
+ }
+ }
+
+ // menu item state is determined by the attribute "_moz-menuactive",
+ // and not by the mouse hovering (accessibility). as a special case,
+ // menus which are children of a menu bar are only marked as prelight
+ // if they are open, not on normal hover.
+
+ if (aAppearance == StyleAppearance::Menuitem ||
+ aAppearance == StyleAppearance::Checkmenuitem ||
+ aAppearance == StyleAppearance::Radiomenuitem ||
+ aAppearance == StyleAppearance::Menuseparator ||
+ aAppearance == StyleAppearance::Menuarrow) {
+ bool isTopLevel = false;
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame) {
+ isTopLevel = menuFrame->IsOnMenuBar();
+ }
+
+ if (isTopLevel) {
+ aState->inHover = menuFrame->IsOpen();
+ } else {
+ aState->inHover = CheckBooleanAttr(aFrame, nsGkAtoms::menuactive);
+ }
+
+ aState->active = FALSE;
+
+ if (aAppearance == StyleAppearance::Checkmenuitem ||
+ aAppearance == StyleAppearance::Radiomenuitem) {
+ *aWidgetFlags = 0;
+ if (aFrame && aFrame->GetContent() &&
+ aFrame->GetContent()->IsElement()) {
+ *aWidgetFlags = aFrame->GetContent()->AsElement()->AttrValueIs(
+ kNameSpaceID_None, nsGkAtoms::checked, nsGkAtoms::_true,
+ eIgnoreCase);
+ }
+ }
+ }
+
+ // 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 ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) {
+ 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;
+ }
+
+ // When the input field of the drop down button has focus, some themes
+ // should draw focus for the drop down button as well.
+ if ((aAppearance == StyleAppearance::MenulistButton ||
+ aAppearance == StyleAppearance::MozMenulistArrowButton) &&
+ aWidgetFlags) {
+ *aWidgetFlags = CheckBooleanAttr(aFrame, nsGkAtoms::parentfocused);
+ }
+ }
+
+ if (aAppearance == StyleAppearance::MozWindowTitlebar ||
+ aAppearance == StyleAppearance::MozWindowTitlebarMaximized ||
+ aAppearance == StyleAppearance::MozWindowButtonClose ||
+ aAppearance == StyleAppearance::MozWindowButtonMinimize ||
+ aAppearance == StyleAppearance::MozWindowButtonMaximize ||
+ aAppearance == StyleAppearance::MozWindowButtonRestore) {
+ aState->backdrop = !nsWindow::GetTopLevelWindowActiveState(aFrame);
+ }
+
+ if (aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight ||
+ aAppearance == StyleAppearance::ScrollbarVertical ||
+ aAppearance == StyleAppearance::ScrollbarHorizontal ||
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal ||
+ aAppearance == StyleAppearance::ScrollbartrackVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) {
+ EventStates docState =
+ aFrame->GetContent()->OwnerDoc()->GetDocumentState();
+ aState->backdrop = docState.HasState(NS_DOCUMENT_STATE_WINDOW_INACTIVE);
+ }
+ }
+
+ 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::FocusOutline:
+ aGtkWidgetType = MOZ_GTK_ENTRY;
+ break;
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio:
+ aGtkWidgetType = (aAppearance == StyleAppearance::Radio)
+ ? MOZ_GTK_RADIOBUTTON
+ : MOZ_GTK_CHECKBUTTON;
+ break;
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_BUTTON;
+ break;
+ case StyleAppearance::ScrollbarVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_VERTICAL;
+ if (GetWidgetTransparency(aFrame, aAppearance) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case StyleAppearance::ScrollbarHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_HORIZONTAL;
+ if (GetWidgetTransparency(aFrame, aAppearance) == eOpaque)
+ *aWidgetFlags = MOZ_GTK_TRACK_OPAQUE;
+ else
+ *aWidgetFlags = 0;
+ break;
+ case StyleAppearance::ScrollbartrackHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_HORIZONTAL;
+ break;
+ case StyleAppearance::ScrollbartrackVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_TROUGH_VERTICAL;
+ break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_VERTICAL;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aGtkWidgetType = MOZ_GTK_SCROLLBAR_THUMB_HORIZONTAL;
+ 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::Separator:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR_SEPARATOR;
+ break;
+ case StyleAppearance::Toolbargripper:
+ aGtkWidgetType = MOZ_GTK_GRIPPER;
+ break;
+ case StyleAppearance::Resizer:
+ aGtkWidgetType = MOZ_GTK_RESIZER;
+ 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::Treeheadercell:
+ if (aWidgetFlags) {
+ // In this case, the flag denotes whether the header is the sorted one
+ // or not
+ if (GetTreeSortDirection(aFrame) == eTreeSortDirection_Natural)
+ *aWidgetFlags = false;
+ else
+ *aWidgetFlags = true;
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_CELL;
+ break;
+ case StyleAppearance::Treeheadersortarrow:
+ if (aWidgetFlags) {
+ switch (GetTreeSortDirection(aFrame)) {
+ case eTreeSortDirection_Ascending:
+ *aWidgetFlags = GTK_ARROW_DOWN;
+ break;
+ case eTreeSortDirection_Descending:
+ *aWidgetFlags = GTK_ARROW_UP;
+ break;
+ case eTreeSortDirection_Natural:
+ default:
+ /* This prevents the treecolums from getting smaller
+ * and wider when switching sort direction off and on
+ * */
+ *aWidgetFlags = GTK_ARROW_NONE;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_TREE_HEADER_SORTARROW;
+ 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::MenulistText:
+ return false; // nothing to do, but prevents the bg from being drawn
+ case StyleAppearance::MozMenulistArrowButton:
+ aGtkWidgetType = MOZ_GTK_DROPDOWN_ARROW;
+ 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::CheckboxContainer:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_CONTAINER;
+ break;
+ case StyleAppearance::RadioContainer:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_CONTAINER;
+ break;
+ case StyleAppearance::CheckboxLabel:
+ aGtkWidgetType = MOZ_GTK_CHECKBUTTON_LABEL;
+ break;
+ case StyleAppearance::RadioLabel:
+ aGtkWidgetType = MOZ_GTK_RADIOBUTTON_LABEL;
+ break;
+ case StyleAppearance::Toolbar:
+ aGtkWidgetType = MOZ_GTK_TOOLBAR;
+ break;
+ case StyleAppearance::Tooltip:
+ aGtkWidgetType = MOZ_GTK_TOOLTIP;
+ break;
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ aGtkWidgetType = MOZ_GTK_FRAME;
+ break;
+ case StyleAppearance::ProgressBar:
+ aGtkWidgetType = MOZ_GTK_PROGRESSBAR;
+ break;
+ case StyleAppearance::Progresschunk: {
+ nsIFrame* stateFrame = aFrame->GetParent();
+ EventStates eventStates = GetContentState(stateFrame, aAppearance);
+
+ aGtkWidgetType = IsIndeterminateProgress(stateFrame, eventStates)
+ ? 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::Menubar:
+ aGtkWidgetType = MOZ_GTK_MENUBAR;
+ break;
+ case StyleAppearance::Menupopup:
+ aGtkWidgetType = MOZ_GTK_MENUPOPUP;
+ break;
+ case StyleAppearance::Menuitem: {
+ nsMenuFrame* menuFrame = do_QueryFrame(aFrame);
+ if (menuFrame && menuFrame->IsOnMenuBar()) {
+ aGtkWidgetType = MOZ_GTK_MENUBARITEM;
+ break;
+ }
+ }
+ aGtkWidgetType = MOZ_GTK_MENUITEM;
+ break;
+ case StyleAppearance::Menuseparator:
+ aGtkWidgetType = MOZ_GTK_MENUSEPARATOR;
+ break;
+ case StyleAppearance::Menuarrow:
+ aGtkWidgetType = MOZ_GTK_MENUARROW;
+ break;
+ case StyleAppearance::Checkmenuitem:
+ aGtkWidgetType = MOZ_GTK_CHECKMENUITEM;
+ break;
+ case StyleAppearance::Radiomenuitem:
+ aGtkWidgetType = MOZ_GTK_RADIOMENUITEM;
+ break;
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ aGtkWidgetType = MOZ_GTK_WINDOW;
+ break;
+ case StyleAppearance::MozGtkInfoBar:
+ aGtkWidgetType = MOZ_GTK_INFO_BAR;
+ break;
+ case StyleAppearance::MozWindowTitlebar:
+ aGtkWidgetType = MOZ_GTK_HEADER_BAR;
+ 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, gint aScaleFactor,
+ bool aSnapped, const Point& aDrawOrigin,
+ const nsIntSize& aDrawSize,
+ GdkRectangle& aGDKRect,
+ nsITheme::Transparency aTransparency) {
+ bool isX11Display = gfxPlatformGtk::GetPlatform()->IsX11Display();
+ static auto sCairoSurfaceSetDeviceScalePtr =
+ (void (*)(cairo_surface_t*, double, double))dlsym(
+ RTLD_DEFAULT, "cairo_surface_set_device_scale");
+ bool useHiDPIWidgets =
+ (aScaleFactor != 1) && (sCairoSurfaceSetDeviceScalePtr != nullptr);
+
+ 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);
+
+ nsIntSize 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 (isX11Display) {
+ // If using a Cairo xlib surface, then try to reuse it.
+ BorrowedXlibDrawable borrow(aDrawTarget);
+ if (borrow.GetDrawable()) {
+ nsIntSize size = borrow.GetSize();
+ cairo_surface_t* surf = nullptr;
+ // Check if the surface is using XRender.
+# ifdef CAIRO_HAS_XLIB_XRENDER_SURFACE
+ if (borrow.GetXRenderFormat()) {
+ surf = cairo_xlib_surface_create_with_xrender_format(
+ borrow.GetDisplay(), borrow.GetDrawable(), borrow.GetScreen(),
+ borrow.GetXRenderFormat(), size.width, size.height);
+ } else {
+# else
+ if (!borrow.GetXRenderFormat()) {
+# endif
+ surf = cairo_xlib_surface_create(
+ borrow.GetDisplay(), borrow.GetDrawable(), 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);
+ }
+ }
+ }
+}
+
+bool nsNativeThemeGTK::GetExtraSizeForWidget(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsIntMargin* aExtra) {
+ *aExtra = nsIntMargin(0, 0, 0, 0);
+ // 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::ScrollbarthumbVertical:
+ aExtra->top = aExtra->bottom = 1;
+ break;
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ aExtra->left = aExtra->right = 1;
+ break;
+
+ case StyleAppearance::Button: {
+ if (IsDefaultButton(aFrame)) {
+ // Some themes draw a default indicator outside the widget,
+ // include that in overflow
+ gint top, left, bottom, right;
+ moz_gtk_button_get_default_overflow(&top, &left, &bottom, &right);
+ aExtra->top = top;
+ aExtra->right = right;
+ aExtra->bottom = bottom;
+ aExtra->left = left;
+ break;
+ }
+ return false;
+ }
+ case StyleAppearance::FocusOutline: {
+ moz_gtk_get_focus_outline_size(&aExtra->left, &aExtra->top);
+ aExtra->right = aExtra->left;
+ aExtra->bottom = aExtra->top;
+ break;
+ }
+ case StyleAppearance::Tab: {
+ if (!IsSelectedTab(aFrame)) return false;
+
+ gint gap_height = moz_gtk_get_tab_thickness(
+ IsBottomTab(aFrame) ? MOZ_GTK_TAB_BOTTOM : MOZ_GTK_TAB_TOP);
+ if (!gap_height) return false;
+
+ int32_t extra = gap_height - GetTabMarginPixels(aFrame);
+ if (extra <= 0) return false;
+
+ if (IsBottomTab(aFrame)) {
+ aExtra->top = extra;
+ } else {
+ aExtra->bottom = extra;
+ }
+ return false;
+ }
+ default:
+ return false;
+ }
+ gint scale = GetMonitorScaleFactor(aFrame);
+ aExtra->top *= scale;
+ aExtra->right *= scale;
+ aExtra->bottom *= scale;
+ aExtra->left *= scale;
+ return true;
+}
+
+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) {
+ 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);
+ gint scaleFactor = GetMonitorScaleFactor(aFrame);
+
+ // 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.
+ bool snapped = ctx->UserToDevicePixelSnapped(rect);
+ 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).
+ nsIntRect widgetRect(0, 0, NS_lround(rect.Width()), NS_lround(rect.Height()));
+ nsIntRect overflowRect(widgetRect);
+ nsIntMargin extraSize;
+ if (GetExtraSizeForWidget(aFrame, aAppearance, &extraSize)) {
+ overflowRect.Inflate(extraSize);
+ }
+
+ // This is the rectangle that will actually be drawn, in gdk pixels
+ nsIntRect drawingRect(int32_t(dirtyRect.X()), int32_t(dirtyRect.Y()),
+ int32_t(dirtyRect.Width()),
+ int32_t(dirtyRect.Height()));
+ if (widgetRect.IsEmpty() ||
+ !drawingRect.IntersectRect(overflowRect, 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.
+ GdkRectangle gdk_rect = {
+ -drawingRect.x / scaleFactor, -drawingRect.y / scaleFactor,
+ widgetRect.width / scaleFactor, widgetRect.height / scaleFactor};
+
+ // Save actual widget scale to GtkWidgetState as we don't provide
+ // nsFrame to gtk3drawing routines.
+ state.scale = scaleFactor;
+
+ // translate everything so (0,0) is the top left of the drawingRect
+ gfxPoint origin = rect.TopLeft() + drawingRect.TopLeft();
+
+ DrawThemeWithCairo(ctx, aContext->GetDrawTarget(), state, gtkWidgetType,
+ flags, direction, scaleFactor, snapped, ToPoint(origin),
+ drawingRect.Size(), gdk_rect, transparency);
+
+ if (!safeState) {
+ // gdk_flush() call from expose event crashes Gtk+ on Wayland
+ // (Gnome BZ #773307)
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ 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) {
+ nsPresContext* presContext = aFrame->PresContext();
+ wr::LayoutRect bounds = wr::ToLayoutRect(LayoutDeviceRect::FromAppUnits(
+ aRect, presContext->AppUnitsPerDevPixel()));
+
+ switch (aAppearance) {
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ aBuilder.PushRect(
+ bounds, bounds, true,
+ wr::ToColorF(ToDeviceColor(LookAndFeel::GetColor(
+ LookAndFeel::ColorID::WindowBackground, NS_RGBA(0, 0, 0, 0)))));
+ return true;
+
+ default:
+ 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,
+ LayoutDeviceIntMargin* 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;
+ }
+}
+
+void nsNativeThemeGTK::GetCachedWidgetBorder(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ GtkTextDirection aDirection,
+ LayoutDeviceIntMargin* aResult) {
+ aResult->SizeTo(0, 0, 0, 0);
+
+ 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) {
+ *aResult = mBorderCache[gtkWidgetType];
+ } else {
+ moz_gtk_get_widget_border(gtkWidgetType, &aResult->left, &aResult->top,
+ &aResult->right, &aResult->bottom, aDirection);
+ if (gtkWidgetType != MOZ_GTK_DROPDOWN) { // depends on aDirection
+ mBorderCacheValid[cacheIndex] |= cacheBit;
+ mBorderCache[gtkWidgetType] = *aResult;
+ }
+ }
+ }
+ FixupForVerticalWritingMode(aFrame->GetWritingMode(), aResult);
+}
+
+LayoutDeviceIntMargin nsNativeThemeGTK::GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame, StyleAppearance aAppearance) {
+ LayoutDeviceIntMargin result;
+ GtkTextDirection direction = GetTextDirection(aFrame);
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ const GtkBorder& border = metrics->border.scrollbar;
+ result.top = border.top;
+ result.right = border.right;
+ result.bottom = border.bottom;
+ result.left = border.left;
+ } break;
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbartrackHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ const GtkBorder& border = metrics->border.track;
+ result.top = border.top;
+ result.right = border.right;
+ result.bottom = border.bottom;
+ result.left = border.left;
+ } break;
+ 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 result;
+ }
+ moz_gtk_get_tab_border(&result.left, &result.top, &result.right,
+ &result.bottom, direction, (GtkTabFlags)flags,
+ gtkWidgetType);
+ } break;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ // For regular menuitems, we will be using GetWidgetPadding instead of
+ // GetWidgetBorder to pad up the widget's internals; other menuitems
+ // will need to fall through and use the default case as before.
+ if (IsRegularMenuItem(aFrame)) break;
+ [[fallthrough]];
+ default: {
+ GetCachedWidgetBorder(aFrame, aAppearance, direction, &result);
+ }
+ }
+
+ gint scale = GetMonitorScaleFactor(aFrame);
+ result.top *= scale;
+ result.right *= scale;
+ result.bottom *= scale;
+ result.left *= scale;
+ return result;
+}
+
+bool nsNativeThemeGTK::GetWidgetPadding(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) {
+ switch (aAppearance) {
+ case StyleAppearance::ButtonFocus:
+ case StyleAppearance::Toolbarbutton:
+ 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::MozMenulistArrowButton:
+ 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;
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem: {
+ // Menubar and menulist have their padding specified in CSS.
+ if (!IsRegularMenuItem(aFrame)) return false;
+
+ GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame),
+ aResult);
+
+ gint horizontal_padding;
+ if (aAppearance == StyleAppearance::Menuitem)
+ moz_gtk_menuitem_get_horizontal_padding(&horizontal_padding);
+ else
+ moz_gtk_checkmenuitem_get_horizontal_padding(&horizontal_padding);
+
+ aResult->left += horizontal_padding;
+ aResult->right += horizontal_padding;
+
+ gint scale = GetMonitorScaleFactor(aFrame);
+ aResult->top *= scale;
+ aResult->right *= scale;
+ aResult->bottom *= scale;
+ aResult->left *= scale;
+
+ return true;
+ }
+ default:
+ break;
+ }
+
+ return false;
+}
+
+bool nsNativeThemeGTK::GetWidgetOverflow(nsDeviceContext* aContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) {
+ nsIntMargin extraSize;
+ if (!GetExtraSizeForWidget(aFrame, aAppearance, &extraSize)) return false;
+
+ int32_t p2a = aContext->AppUnitsPerDevPixel();
+ nsMargin m(NSIntPixelsToAppUnits(extraSize.top, p2a),
+ NSIntPixelsToAppUnits(extraSize.right, p2a),
+ NSIntPixelsToAppUnits(extraSize.bottom, p2a),
+ NSIntPixelsToAppUnits(extraSize.left, p2a));
+
+ aOverflowRect->Inflate(m);
+ return true;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::GetMinimumWidgetSize(nsPresContext* aPresContext,
+ nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) {
+ aResult->width = aResult->height = 0;
+ *aIsOverridable = true;
+
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown: {
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL);
+
+ aResult->width = metrics->size.button.width;
+ aResult->height = metrics->size.button.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight: {
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL);
+
+ aResult->width = metrics->size.button.width;
+ aResult->height = metrics->size.button.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Splitter: {
+ gint metrics;
+ if (IsHorizontal(aFrame)) {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_HORIZONTAL, &metrics);
+ aResult->width = metrics;
+ aResult->height = 0;
+ } else {
+ moz_gtk_splitter_get_metrics(GTK_ORIENTATION_VERTICAL, &metrics);
+ aResult->width = 0;
+ aResult->height = metrics;
+ }
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::ScrollbarNonDisappearing: {
+ const ScrollbarGTKMetrics* verticalMetrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_VERTICAL);
+ const ScrollbarGTKMetrics* horizontalMetrics =
+ GetActiveScrollbarMetrics(GTK_ORIENTATION_HORIZONTAL);
+ aResult->width = verticalMetrics->size.scrollbar.width;
+ aResult->height = horizontalMetrics->size.scrollbar.height;
+ } break;
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical: {
+ /* While we enforce a minimum size for the thumb, this is ignored
+ * for the some scrollbars if buttons are hidden (bug 513006) because
+ * the thumb isn't a direct child of the scrollbar, unlike the buttons
+ * or track. So add a minimum size to the track as well to prevent a
+ * 0-width scrollbar. */
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ aResult->width = metrics->size.scrollbar.width;
+ aResult->height = metrics->size.scrollbar.height;
+ } break;
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal: {
+ GtkOrientation orientation =
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal
+ ? GTK_ORIENTATION_HORIZONTAL
+ : GTK_ORIENTATION_VERTICAL;
+ const ScrollbarGTKMetrics* metrics =
+ GetActiveScrollbarMetrics(orientation);
+
+ aResult->width = metrics->size.thumb.width;
+ aResult->height = metrics->size.thumb.height;
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::RangeThumb: {
+ gint thumb_length, thumb_height;
+
+ if (IsRangeHorizontal(aFrame)) {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_HORIZONTAL,
+ &thumb_length, &thumb_height);
+ } else {
+ moz_gtk_get_scalethumb_metrics(GTK_ORIENTATION_VERTICAL, &thumb_height,
+ &thumb_length);
+ }
+ aResult->width = thumb_length;
+ aResult->height = thumb_height;
+
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::TabScrollArrowBack:
+ case StyleAppearance::TabScrollArrowForward: {
+ moz_gtk_get_tab_scroll_arrow_size(&aResult->width, &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::MozMenulistArrowButton: {
+ moz_gtk_get_combo_box_entry_button_size(&aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Menuseparator: {
+ gint separator_height;
+
+ moz_gtk_get_menu_separator_height(&separator_height);
+ aResult->height = separator_height;
+
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Radio: {
+ const ToggleGTKMetrics* metrics = GetToggleMetrics(
+ aAppearance == StyleAppearance::Radio ? MOZ_GTK_RADIOBUTTON
+ : MOZ_GTK_CHECKBUTTON);
+ aResult->width = metrics->minSizeWithBorder.width;
+ aResult->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, &aResult->width,
+ &aResult->height);
+ *aIsOverridable = false;
+ } break;
+ case StyleAppearance::MozWindowButtonClose: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_CLOSE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMinimize: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MINIMIZE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore: {
+ const ToolbarButtonGTKMetrics* metrics =
+ GetToolbarButtonMetrics(MOZ_GTK_HEADER_BAR_BUTTON_MAXIMIZE);
+ aResult->width = metrics->minSizeWithBorderMargin.width;
+ aResult->height = metrics->minSizeWithBorderMargin.height;
+ break;
+ }
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::CheckboxLabel:
+ case StyleAppearance::RadioLabel:
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Toolbarbutton:
+ case StyleAppearance::Treeheadercell: {
+ if (aAppearance == StyleAppearance::Menulist ||
+ aAppearance == StyleAppearance::MenulistButton) {
+ // Include the arrow size.
+ moz_gtk_get_arrow_size(MOZ_GTK_DROPDOWN, &aResult->width,
+ &aResult->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.
+
+ LayoutDeviceIntMargin border;
+ GetCachedWidgetBorder(aFrame, aAppearance, GetTextDirection(aFrame),
+ &border);
+ aResult->width += border.left + border.right;
+ aResult->height += border.top + border.bottom;
+ } 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::Field, 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()) {
+ aResult->width = height;
+ } else {
+ aResult->height = height;
+ }
+ } break;
+ case StyleAppearance::Separator: {
+ gint separator_width;
+
+ moz_gtk_get_toolbar_separator_width(&separator_width);
+
+ aResult->width = separator_width;
+ } break;
+ case StyleAppearance::Spinner:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 26;
+ break;
+ case StyleAppearance::Treeheadersortarrow:
+ case StyleAppearance::SpinnerUpbutton:
+ case StyleAppearance::SpinnerDownbutton:
+ // hard code these sizes
+ aResult->width = 14;
+ aResult->height = 13;
+ break;
+ case StyleAppearance::Resizer:
+ // same as Windows to make our lives easier
+ aResult->width = aResult->height = 15;
+ *aIsOverridable = false;
+ break;
+ case StyleAppearance::Treetwisty:
+ case StyleAppearance::Treetwistyopen: {
+ gint expander_size;
+
+ moz_gtk_get_treeview_expander_size(&expander_size);
+ aResult->width = aResult->height = expander_size;
+ *aIsOverridable = false;
+ } break;
+ default:
+ break;
+ }
+
+ *aResult = *aResult * GetMonitorScaleFactor(aFrame);
+
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNativeThemeGTK::WidgetStateChanged(nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsAtom* aAttribute, bool* aShouldRepaint,
+ const nsAttrValue* aOldValue) {
+ // Some widget types just never change state.
+ if (aAppearance == StyleAppearance::Toolbox ||
+ aAppearance == StyleAppearance::Toolbar ||
+ aAppearance == StyleAppearance::Statusbar ||
+ aAppearance == StyleAppearance::Statusbarpanel ||
+ aAppearance == StyleAppearance::Resizerpanel ||
+ aAppearance == StyleAppearance::Progresschunk ||
+ aAppearance == StyleAppearance::ProgressBar ||
+ aAppearance == StyleAppearance::Menubar ||
+ aAppearance == StyleAppearance::Menupopup ||
+ aAppearance == StyleAppearance::Tooltip ||
+ aAppearance == StyleAppearance::Menuseparator ||
+ aAppearance == StyleAppearance::Window ||
+ aAppearance == StyleAppearance::Dialog) {
+ *aShouldRepaint = false;
+ 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;
+ }
+
+ if ((aAppearance == StyleAppearance::ScrollbarthumbVertical ||
+ aAppearance == StyleAppearance::ScrollbarthumbHorizontal) &&
+ aAttribute == nsGkAtoms::active) {
+ *aShouldRepaint = true;
+ return NS_OK;
+ }
+
+ if ((aAppearance == StyleAppearance::ScrollbarbuttonUp ||
+ aAppearance == StyleAppearance::ScrollbarbuttonDown ||
+ aAppearance == StyleAppearance::ScrollbarbuttonLeft ||
+ aAppearance == StyleAppearance::ScrollbarbuttonRight) &&
+ (aAttribute == nsGkAtoms::curpos || aAttribute == nsGkAtoms::maxpos)) {
+ // If 'curpos' has changed and we are passed its old value, we can
+ // determine whether the button's enablement actually needs to change.
+ if (aAttribute == nsGkAtoms::curpos && aOldValue) {
+ int32_t curpos = CheckIntAttr(aFrame, nsGkAtoms::curpos, 0);
+ int32_t maxpos = CheckIntAttr(aFrame, nsGkAtoms::maxpos, 0);
+ nsAutoString str;
+ aOldValue->ToString(str);
+ nsresult err;
+ int32_t oldCurpos = str.ToInteger(&err);
+ if (str.IsEmpty() || NS_FAILED(err)) {
+ *aShouldRepaint = true;
+ } else {
+ bool disabledBefore =
+ ShouldScrollbarButtonBeDisabled(oldCurpos, maxpos, aAppearance);
+ bool disabledNow =
+ ShouldScrollbarButtonBeDisabled(curpos, maxpos, aAppearance);
+ *aShouldRepaint = (disabledBefore != disabledNow);
+ }
+ } else {
+ *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;
+ } else {
+ // 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 ||
+ aAttribute == nsGkAtoms::parentfocused)
+ *aShouldRepaint = true;
+ }
+
+ 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 (IsWidgetScrollbarPart(aAppearance)) {
+ ComputedStyle* cs = nsLayoutUtils::StyleForScrollbar(aFrame);
+ if (cs->StyleUI()->HasCustomScrollbars() ||
+ // We cannot handle thin scrollbar on GTK+ widget directly as well.
+ cs->StyleUIReset()->mScrollbarWidth == StyleScrollbarWidth::Thin) {
+ return false;
+ }
+ }
+
+ switch (aAppearance) {
+ // Combobox dropdowns don't support native theming in vertical mode.
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::MenulistText:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ [[fallthrough]];
+
+ case StyleAppearance::Button:
+ case StyleAppearance::ButtonFocus:
+ case StyleAppearance::Radio:
+ case StyleAppearance::Checkbox:
+ case StyleAppearance::Toolbox: // N/A
+ case StyleAppearance::Toolbar:
+ 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::Separator:
+ case StyleAppearance::Toolbargripper:
+ case StyleAppearance::Statusbar:
+ case StyleAppearance::Statusbarpanel:
+ case StyleAppearance::Resizerpanel:
+ case StyleAppearance::Resizer:
+ case StyleAppearance::Listbox:
+ case StyleAppearance::Treeview:
+ // case StyleAppearance::Treeitem:
+ case StyleAppearance::Treetwisty:
+ // case StyleAppearance::Treeline:
+ // case StyleAppearance::Treeheader:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::Treeheadersortarrow:
+ 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::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarNonDisappearing:
+ case StyleAppearance::NumberInput:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Range:
+ case StyleAppearance::RangeThumb:
+ case StyleAppearance::CheckboxContainer:
+ case StyleAppearance::RadioContainer:
+ case StyleAppearance::CheckboxLabel:
+ case StyleAppearance::RadioLabel:
+ case StyleAppearance::Menubar:
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Menuitem:
+ case StyleAppearance::Menuarrow:
+ case StyleAppearance::Menuseparator:
+ case StyleAppearance::Checkmenuitem:
+ case StyleAppearance::Radiomenuitem:
+ case StyleAppearance::Splitter:
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ case StyleAppearance::MozGtkInfoBar:
+ case StyleAppearance::MozWindowButtonBox:
+ case StyleAppearance::MozWindowButtonClose:
+ case StyleAppearance::MozWindowButtonMinimize:
+ case StyleAppearance::MozWindowButtonMaximize:
+ case StyleAppearance::MozWindowButtonRestore:
+ case StyleAppearance::MozWindowTitlebar:
+ case StyleAppearance::MozWindowTitlebarMaximized:
+ return !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ case StyleAppearance::MozMenulistArrowButton:
+ if (aFrame && aFrame->GetWritingMode().IsVertical()) {
+ return false;
+ }
+ // "Native" dropdown buttons cause padding and margin problems, but only
+ // in HTML so allow them in XUL.
+ return (!aFrame ||
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL)) &&
+ !IsWidgetStyled(aPresContext, aFrame, aAppearance);
+
+ case StyleAppearance::FocusOutline:
+ return true;
+ default:
+ break;
+ }
+
+ return false;
+}
+
+NS_IMETHODIMP_(bool)
+nsNativeThemeGTK::WidgetIsContainer(StyleAppearance aAppearance) {
+ // XXXdwh At some point flesh all of this out.
+ if (aAppearance == StyleAppearance::MozMenulistArrowButton ||
+ 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(StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::Button:
+ case StyleAppearance::Menulist:
+ case StyleAppearance::MenulistButton:
+ case StyleAppearance::Textarea:
+ case StyleAppearance::Textfield:
+ case StyleAppearance::Treeheadercell:
+ case StyleAppearance::NumberInput:
+ return true;
+ default:
+ return false;
+ }
+}
+
+bool nsNativeThemeGTK::ThemeNeedsComboboxDropmarker() { return false; }
+
+nsITheme::Transparency nsNativeThemeGTK::GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ // These widgets always draw a default background.
+ case StyleAppearance::Menupopup:
+ case StyleAppearance::Window:
+ case StyleAppearance::Dialog:
+ return eOpaque;
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ // Make scrollbar tracks opaque on the window's scroll frame to prevent
+ // leaf layers from overlapping. See bug 1179780.
+ if (!(CheckBooleanAttr(aFrame, nsGkAtoms::root_) &&
+ aFrame->PresContext()->IsRootContentDocument() &&
+ IsFrameContentNodeInNamespace(aFrame, kNameSpaceID_XUL))) {
+ return eTransparent;
+ }
+ return eOpaque;
+ // Tooltips use gtk_paint_flat_box() on Gtk2
+ // but are shaped on Gtk3
+ case StyleAppearance::Tooltip:
+ return eTransparent;
+ default:
+ return eUnknownTransparency;
+ }
+}
+
+bool nsNativeThemeGTK::WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) {
+ switch (aAppearance) {
+ case StyleAppearance::ScrollbarbuttonUp:
+ case StyleAppearance::ScrollbarbuttonDown:
+ case StyleAppearance::ScrollbarbuttonLeft:
+ case StyleAppearance::ScrollbarbuttonRight:
+ case StyleAppearance::ScrollbarVertical:
+ case StyleAppearance::ScrollbarHorizontal:
+ case StyleAppearance::ScrollbartrackHorizontal:
+ case StyleAppearance::ScrollbartrackVertical:
+ case StyleAppearance::ScrollbarthumbVertical:
+ case StyleAppearance::ScrollbarthumbHorizontal:
+ return true;
+ default:
+ return false;
+ }
+}
+
+already_AddRefed<nsITheme> do_GetNativeThemeDoNotUseDirectly() {
+ static nsCOMPtr<nsITheme> inst;
+
+ if (!inst) {
+ if (gfxPlatform::IsHeadless()) {
+ inst = new HeadlessThemeGTK();
+ } else {
+ inst = new nsNativeThemeGTK();
+ }
+ ClearOnShutdown(&inst);
+ }
+
+ return do_AddRef(inst);
+}
diff --git a/widget/gtk/nsNativeThemeGTK.h b/widget/gtk/nsNativeThemeGTK.h
new file mode 100644
index 0000000000..8b86625bda
--- /dev/null
+++ b/widget/gtk/nsNativeThemeGTK.h
@@ -0,0 +1,116 @@
+/* -*- 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 "nsIObserver.h"
+#include "nsNativeTheme.h"
+#include "nsStyleConsts.h"
+
+#include <gtk/gtk.h>
+#include "gtkdrawing.h"
+
+class nsNativeThemeGTK final : private nsNativeTheme,
+ public nsITheme,
+ public nsIObserver {
+ public:
+ NS_DECL_ISUPPORTS_INHERITED
+
+ NS_DECL_NSIOBSERVER
+
+ // The nsITheme interface.
+ NS_IMETHOD DrawWidgetBackground(gfxContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ const nsRect& aRect,
+ const nsRect& aDirtyRect) override;
+
+ bool 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) override;
+
+ [[nodiscard]] LayoutDeviceIntMargin GetWidgetBorder(
+ nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance) override;
+
+ bool GetWidgetPadding(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ LayoutDeviceIntMargin* aResult) override;
+
+ virtual bool GetWidgetOverflow(nsDeviceContext* aContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ nsRect* aOverflowRect) override;
+
+ NS_IMETHOD GetMinimumWidgetSize(nsPresContext* aPresContext, nsIFrame* aFrame,
+ StyleAppearance aAppearance,
+ mozilla::LayoutDeviceIntSize* aResult,
+ bool* aIsOverridable) 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;
+
+ NS_IMETHOD_(bool)
+ ThemeDrawsFocusForWidget(StyleAppearance aAppearance) override;
+
+ virtual bool ThemeNeedsComboboxDropmarker() override;
+
+ virtual Transparency GetWidgetTransparency(
+ nsIFrame* aFrame, StyleAppearance aAppearance) override;
+
+ virtual bool WidgetAppearanceDependsOnWindowFocus(
+ StyleAppearance aAppearance) 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);
+ bool GetExtraSizeForWidget(nsIFrame* aFrame, StyleAppearance aAppearance,
+ nsIntMargin* aExtra);
+ 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.
+ void GetCachedWidgetBorder(nsIFrame* aFrame, StyleAppearance aAppearance,
+ GtkTextDirection aDirection,
+ LayoutDeviceIntMargin* aResult);
+ uint8_t mBorderCacheValid[(MOZ_GTK_WIDGET_NODE_COUNT + 7) / 8];
+ LayoutDeviceIntMargin 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..9c1a72a6e3
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.cpp
@@ -0,0 +1,1054 @@
+/* -*- 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 "nsIGIOService.h"
+#include "WidgetUtils.h"
+#include "nsIObserverService.h"
+
+// for gdk_x11_window_get_xid
+#include <gdk/gdkx.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <gio/gunixfdlist.h>
+#include "gfxPlatformGtk.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,
+ 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,
+ 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.
+ bool canSelectText = aSettings->GetIsPrintSelectionRBEnabled();
+ if (gtk_major_version > 2 ||
+ (gtk_major_version == 2 && gtk_minor_version >= 18)) {
+ useNativeSelection = true;
+ g_object_set(dialog, "support-selection", TRUE, "has-selection",
+ canSelectText, "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, canSelectText);
+ 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");
+ else
+ 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();
+
+ 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());
+
+ 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->SetPrintToFile(false);
+
+ 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)));
+
+ // 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; }
+
+// Used to obtain window handle. The portal use this handle
+// to ensure that print dialog is modal.
+typedef void (*WindowHandleExported)(GtkWindow* window, const char* handle,
+ gpointer user_data);
+
+typedef void (*GtkWindowHandleExported)(GtkWindow* window, const char* handle,
+ gpointer user_data);
+#ifdef MOZ_WAYLAND
+# if !GTK_CHECK_VERSION(3, 22, 0)
+typedef void (*GdkWaylandWindowExported)(GdkWindow* window, const char* handle,
+ gpointer user_data);
+# endif
+
+typedef struct {
+ GtkWindow* window;
+ WindowHandleExported callback;
+ gpointer user_data;
+} WaylandWindowHandleExportedData;
+
+static void wayland_window_handle_exported(GdkWindow* window,
+ const char* wayland_handle_str,
+ gpointer user_data) {
+ WaylandWindowHandleExportedData* data =
+ static_cast<WaylandWindowHandleExportedData*>(user_data);
+ char* handle_str;
+
+ handle_str = g_strdup_printf("wayland:%s", wayland_handle_str);
+ data->callback(data->window, handle_str, data->user_data);
+ g_free(handle_str);
+}
+#endif
+
+// Get window handle for the portal, taken from gtk/gtkwindow.c
+// (currently not exported)
+static gboolean window_export_handle(GtkWindow* window,
+ GtkWindowHandleExported callback,
+ gpointer user_data) {
+ if (gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ char* handle_str;
+ guint32 xid = (guint32)gdk_x11_window_get_xid(gdk_window);
+
+ handle_str = g_strdup_printf("x11:%x", xid);
+ callback(window, handle_str, user_data);
+ g_free(handle_str);
+ return true;
+ }
+#ifdef MOZ_WAYLAND
+ else {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(window));
+ WaylandWindowHandleExportedData* data;
+
+ data = g_new0(WaylandWindowHandleExportedData, 1);
+ data->window = window;
+ data->callback = callback;
+ data->user_data = user_data;
+
+ static auto s_gdk_wayland_window_export_handle =
+ reinterpret_cast<gboolean (*)(GdkWindow*, GdkWaylandWindowExported,
+ gpointer, GDestroyNotify)>(
+ dlsym(RTLD_DEFAULT, "gdk_wayland_window_export_handle"));
+ if (!s_gdk_wayland_window_export_handle ||
+ !s_gdk_wayland_window_export_handle(
+ gdk_window, wayland_window_handle_exported, data, g_free)) {
+ g_free(data);
+ return false;
+ } else {
+ return true;
+ }
+ }
+#endif
+
+ g_warning("Couldn't export handle, unsupported windowing system");
+
+ return false;
+}
+/**
+ * Communication class with the GTK print portal handler
+ *
+ * To print document from flatpak we need to use print portal because
+ * printers are not directly accessible in the sandboxed environment.
+ *
+ * At first we request portal to show the print dialog to let user choose
+ * printer settings. We use DBUS interface for that (PreparePrint method).
+ *
+ * Next we force application to print to temporary file and after the writing
+ * to the file is finished we pass its file descriptor to the portal.
+ * Portal will pass duplicate of the file descriptor to the printer which
+ * user selected before (by DBUS Print method).
+ *
+ * Since DBUS communication is done async while nsPrintDialogServiceGTK::Show
+ * is expecting sync execution, we need to create a new GMainLoop during the
+ * print portal dialog is running. The loop is stopped after the dialog
+ * is closed.
+ */
+class nsFlatpakPrintPortal : public nsIObserver {
+ NS_DECL_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ public:
+ explicit nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings);
+ nsresult PreparePrintRequest(GtkWindow* aWindow);
+ static void OnWindowExportHandleDone(GtkWindow* aWindow,
+ const char* aWindowHandleStr,
+ gpointer aUserData);
+ void PreparePrint(GtkWindow* aWindow, const char* aWindowHandleStr);
+ static void OnPreparePrintResponse(GDBusConnection* connection,
+ const char* sender_name,
+ const char* object_path,
+ const char* interface_name,
+ const char* signal_name,
+ GVariant* parameters, gpointer data);
+ GtkPrintOperationResult GetResult();
+
+ private:
+ virtual ~nsFlatpakPrintPortal();
+ void FinishPrintDialog(GVariant* parameters);
+ nsCOMPtr<nsPrintSettingsGTK> mPrintAndPageSettings;
+ GDBusProxy* mProxy;
+ guint32 mToken;
+ GMainLoop* mLoop;
+ GtkPrintOperationResult mResult;
+ guint mResponseSignalId;
+ GtkWindow* mParentWindow;
+};
+
+NS_IMPL_ISUPPORTS(nsFlatpakPrintPortal, nsIObserver)
+
+nsFlatpakPrintPortal::nsFlatpakPrintPortal(nsPrintSettingsGTK* aPrintSettings)
+ : mPrintAndPageSettings(aPrintSettings),
+ mProxy(nullptr),
+ mLoop(nullptr),
+ mResponseSignalId(0),
+ mParentWindow(nullptr) {}
+
+/**
+ * Creates GDBusProxy, query for window handle and create a new GMainLoop.
+ *
+ * The GMainLoop is to be run from GetResult() and be quitted during
+ * FinishPrintDialog.
+ *
+ * @param aWindow toplevel application window which is used as parent of print
+ * dialog
+ */
+nsresult nsFlatpakPrintPortal::PreparePrintRequest(GtkWindow* aWindow) {
+ MOZ_ASSERT(aWindow, "aWindow must not be null");
+ MOZ_ASSERT(mPrintAndPageSettings, "mPrintAndPageSettings must not be null");
+
+ GError* error = nullptr;
+ mProxy = 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.Print", nullptr, &error);
+ if (mProxy == nullptr) {
+ NS_WARNING(
+ nsPrintfCString("Unable to create dbus proxy: %s", error->message)
+ .get());
+ g_error_free(error);
+ return NS_ERROR_FAILURE;
+ }
+
+ // The window handler is returned async, we will continue by PreparePrint
+ // method when it is returned.
+ if (!window_export_handle(
+ aWindow, &nsFlatpakPrintPortal::OnWindowExportHandleDone, this)) {
+ NS_WARNING("Unable to get window handle for creating modal print dialog.");
+ return NS_ERROR_FAILURE;
+ }
+
+ mLoop = g_main_loop_new(NULL, FALSE);
+ return NS_OK;
+}
+
+void nsFlatpakPrintPortal::OnWindowExportHandleDone(
+ GtkWindow* aWindow, const char* aWindowHandleStr, gpointer aUserData) {
+ nsFlatpakPrintPortal* printPortal =
+ static_cast<nsFlatpakPrintPortal*>(aUserData);
+ printPortal->PreparePrint(aWindow, aWindowHandleStr);
+}
+
+/**
+ * Ask print portal to show the print dialog.
+ *
+ * Print and page settings and window handle are passed to the portal to prefill
+ * last used settings.
+ */
+void nsFlatpakPrintPortal::PreparePrint(GtkWindow* aWindow,
+ const char* aWindowHandleStr) {
+ GtkPrintSettings* gtkSettings = mPrintAndPageSettings->GetGtkPrintSettings();
+ GtkPageSetup* pageSetup = mPrintAndPageSettings->GetGtkPageSetup();
+
+ // We need to remember GtkWindow to unexport window handle after it is
+ // no longer needed by the portal dialog (apply only on non-X11 sessions).
+ if (gfxPlatformGtk::GetPlatform()->IsWaylandDisplay()) {
+ mParentWindow = aWindow;
+ }
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ char* token = g_strdup_printf("mozilla%d", g_random_int_range(0, G_MAXINT));
+ g_variant_builder_add(&opt_builder, "{sv}", "handle_token",
+ g_variant_new_string(token));
+ g_free(token);
+ GVariant* options = g_variant_builder_end(&opt_builder);
+ static auto s_gtk_print_settings_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPrintSettings*)>(
+ dlsym(RTLD_DEFAULT, "gtk_print_settings_to_gvariant"));
+ static auto s_gtk_page_setup_to_gvariant =
+ reinterpret_cast<GVariant* (*)(GtkPageSetup*)>(
+ dlsym(RTLD_DEFAULT, "gtk_page_setup_to_gvariant"));
+ if (!s_gtk_print_settings_to_gvariant || !s_gtk_page_setup_to_gvariant) {
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ // Get translated window title
+ nsCOMPtr<nsIStringBundleService> bundleSvc =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+ nsCOMPtr<nsIStringBundle> printBundle;
+ bundleSvc->CreateBundle("chrome://global/locale/printdialog.properties",
+ getter_AddRefs(printBundle));
+ nsAutoString intlPrintTitle;
+ printBundle->GetStringFromName("printTitleGTK", intlPrintTitle);
+
+ GError* error = nullptr;
+ GVariant* ret = g_dbus_proxy_call_sync(
+ mProxy, "PreparePrint",
+ g_variant_new(
+ "(ss@a{sv}@a{sv}@a{sv})", aWindowHandleStr,
+ NS_ConvertUTF16toUTF8(intlPrintTitle).get(), // Title of the window
+ s_gtk_print_settings_to_gvariant(gtkSettings),
+ s_gtk_page_setup_to_gvariant(pageSetup), options),
+ G_DBUS_CALL_FLAGS_NONE, -1, nullptr, &error);
+ if (ret == nullptr) {
+ NS_WARNING(
+ nsPrintfCString("Unable to call dbus proxy: %s", error->message).get());
+ g_error_free(error);
+ mResult = GTK_PRINT_OPERATION_RESULT_ERROR;
+ FinishPrintDialog(nullptr);
+ return;
+ }
+
+ const char* handle = nullptr;
+ g_variant_get(ret, "(&o)", &handle);
+ if (strcmp(aWindowHandleStr, handle) != 0) {
+ aWindowHandleStr = g_strdup(handle);
+ if (mResponseSignalId) {
+ g_dbus_connection_signal_unsubscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
+ }
+ }
+ mResponseSignalId = g_dbus_connection_signal_subscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)),
+ "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request",
+ "Response", aWindowHandleStr, NULL, G_DBUS_SIGNAL_FLAGS_NO_MATCH_RULE,
+ &nsFlatpakPrintPortal::OnPreparePrintResponse, this, NULL);
+}
+
+void nsFlatpakPrintPortal::OnPreparePrintResponse(
+ GDBusConnection* connection, const char* sender_name,
+ const char* object_path, const char* interface_name,
+ const char* signal_name, GVariant* parameters, gpointer data) {
+ nsFlatpakPrintPortal* printPortal = static_cast<nsFlatpakPrintPortal*>(data);
+ printPortal->FinishPrintDialog(parameters);
+}
+
+/**
+ * When the dialog is accepted, read print and page settings and token.
+ *
+ * Token is later used for printing portal as print operation identifier.
+ * Print and page settings are modified in-place and stored to
+ * mPrintAndPageSettings.
+ */
+void nsFlatpakPrintPortal::FinishPrintDialog(GVariant* parameters) {
+ // This ends GetResult() method
+ if (mLoop) {
+ g_main_loop_quit(mLoop);
+ mLoop = nullptr;
+ }
+
+ if (!parameters) {
+ // mResult should be already defined
+ return;
+ }
+
+ guint32 response;
+ GVariant* options;
+
+ g_variant_get(parameters, "(u@a{sv})", &response, &options);
+ mResult = GTK_PRINT_OPERATION_RESULT_CANCEL;
+ if (response == 0) {
+ GVariant* v =
+ g_variant_lookup_value(options, "settings", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_print_settings_new_from_gvariant =
+ reinterpret_cast<GtkPrintSettings* (*)(GVariant*)>(
+ dlsym(RTLD_DEFAULT, "gtk_print_settings_new_from_gvariant"));
+
+ GtkPrintSettings* printSettings = s_gtk_print_settings_new_from_gvariant(v);
+ g_variant_unref(v);
+
+ v = g_variant_lookup_value(options, "page-setup", G_VARIANT_TYPE_VARDICT);
+ static auto s_gtk_page_setup_new_from_gvariant =
+ reinterpret_cast<GtkPageSetup* (*)(GVariant*)>(
+ dlsym(RTLD_DEFAULT, "gtk_page_setup_new_from_gvariant"));
+ GtkPageSetup* pageSetup = s_gtk_page_setup_new_from_gvariant(v);
+ g_variant_unref(v);
+
+ g_variant_lookup(options, "token", "u", &mToken);
+
+ // Save native settings in the session object
+ mPrintAndPageSettings->SetGtkPrintSettings(printSettings);
+ mPrintAndPageSettings->SetGtkPageSetup(pageSetup);
+
+ // Portal consumes PDF file
+ mPrintAndPageSettings->SetOutputFormat(nsIPrintSettings::kOutputFormatPDF);
+
+ // We need to set to print to file
+ mPrintAndPageSettings->SetPrintToFile(true);
+
+ mResult = GTK_PRINT_OPERATION_RESULT_APPLY;
+ }
+}
+
+/**
+ * Get result of the print dialog.
+ *
+ * This call blocks until FinishPrintDialog is called.
+ *
+ */
+GtkPrintOperationResult nsFlatpakPrintPortal::GetResult() {
+ // If the mLoop has not been initialized we haven't go thru PreparePrint
+ // method
+ if (!NS_IsMainThread() || !mLoop) {
+ return GTK_PRINT_OPERATION_RESULT_ERROR;
+ }
+ // Calling g_main_loop_run stops current code until g_main_loop_quit is called
+ g_main_loop_run(mLoop);
+
+ // Free resources we've allocated in order to show print dialog.
+#ifdef MOZ_WAYLAND
+ if (mParentWindow) {
+ GdkWindow* gdk_window = gtk_widget_get_window(GTK_WIDGET(mParentWindow));
+ static auto s_gdk_wayland_window_unexport_handle =
+ reinterpret_cast<void (*)(GdkWindow*)>(
+ dlsym(RTLD_DEFAULT, "gdk_wayland_window_unexport_handle"));
+ if (s_gdk_wayland_window_unexport_handle) {
+ s_gdk_wayland_window_unexport_handle(gdk_window);
+ }
+ }
+#endif
+ return mResult;
+}
+
+/**
+ * Send file descriptor of the file which contains document to the portal to
+ * finish the print operation.
+ */
+NS_IMETHODIMP
+nsFlatpakPrintPortal::Observe(nsISupports* aObject, const char* aTopic,
+ const char16_t* aData) {
+ // Check that written file match to the stored filename in case multiple
+ // print operations are in progress.
+ nsAutoString filenameStr;
+ mPrintAndPageSettings->GetToFileName(filenameStr);
+ if (!nsDependentString(aData).Equals(filenameStr)) {
+ // Different file is finished, not for this instance
+ return NS_OK;
+ }
+ int fd, idx;
+ fd = open(NS_ConvertUTF16toUTF8(filenameStr).get(), O_RDONLY | O_CLOEXEC);
+ static auto s_g_unix_fd_list_new = reinterpret_cast<GUnixFDList* (*)(void)>(
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_new"));
+ NS_ASSERTION(s_g_unix_fd_list_new,
+ "Cannot find g_unix_fd_list_new function.");
+
+ GUnixFDList* fd_list = s_g_unix_fd_list_new();
+ static auto s_g_unix_fd_list_append =
+ reinterpret_cast<gint (*)(GUnixFDList*, gint, GError**)>(
+ dlsym(RTLD_DEFAULT, "g_unix_fd_list_append"));
+ idx = s_g_unix_fd_list_append(fd_list, fd, NULL);
+ close(fd);
+
+ GVariantBuilder opt_builder;
+ g_variant_builder_init(&opt_builder, G_VARIANT_TYPE_VARDICT);
+ g_variant_builder_add(&opt_builder, "{sv}", "token",
+ g_variant_new_uint32(mToken));
+ g_dbus_proxy_call_with_unix_fd_list(
+ mProxy, "Print",
+ g_variant_new("(ssh@a{sv})", "", /* window */
+ "Print", /* title */
+ idx, g_variant_builder_end(&opt_builder)),
+ G_DBUS_CALL_FLAGS_NONE, -1, fd_list, NULL,
+ NULL, // TODO portal result cb function
+ nullptr); // data
+ g_object_unref(fd_list);
+
+ nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
+ // Let the nsFlatpakPrintPortal instance die
+ os->RemoveObserver(this, "print-to-file-finished");
+ return NS_OK;
+}
+
+nsFlatpakPrintPortal::~nsFlatpakPrintPortal() {
+ if (mProxy) {
+ if (mResponseSignalId) {
+ g_dbus_connection_signal_unsubscribe(
+ g_dbus_proxy_get_connection(G_DBUS_PROXY(mProxy)), mResponseSignalId);
+ }
+ g_object_unref(mProxy);
+ }
+ if (mLoop) g_main_loop_quit(mLoop);
+}
+
+NS_IMETHODIMP
+nsPrintDialogServiceGTK::Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) {
+ MOZ_ASSERT(aParent, "aParent must not be null");
+ MOZ_ASSERT(aSettings, "aSettings must not be null");
+
+ // Check for the flatpak portal first
+ nsCOMPtr<nsIGIOService> giovfs = do_GetService(NS_GIOSERVICE_CONTRACTID);
+ bool shouldUsePortal;
+ giovfs->ShouldUseFlatpakPortal(&shouldUsePortal);
+ if (shouldUsePortal && gtk_check_version(3, 22, 0) == nullptr) {
+ 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<nsPrintSettingsGTK> printSettingsGTK(do_QueryInterface(aSettings));
+ RefPtr<nsFlatpakPrintPortal> fpPrintPortal =
+ new nsFlatpakPrintPortal(printSettingsGTK);
+
+ nsresult rv = fpPrintPortal->PreparePrintRequest(gtkParent);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // This blocks until nsFlatpakPrintPortal::FinishPrintDialog is called
+ GtkPrintOperationResult printDialogResult = fpPrintPortal->GetResult();
+
+ switch (printDialogResult) {
+ case GTK_PRINT_OPERATION_RESULT_APPLY: {
+ nsCOMPtr<nsIObserverService> os =
+ mozilla::services::GetObserverService();
+ NS_ENSURE_STATE(os);
+ // Observer waits until notified that the file with the content
+ // to print has been written.
+ rv = os->AddObserver(fpPrintPortal, "print-to-file-finished", false);
+ NS_ENSURE_SUCCESS(rv, rv);
+ break;
+ }
+ case GTK_PRINT_OPERATION_RESULT_CANCEL:
+ rv = NS_ERROR_ABORT;
+ break;
+ default:
+ NS_WARNING("Unexpected response");
+ rv = NS_ERROR_ABORT;
+ }
+ return rv;
+ }
+
+ nsPrintDialogWidgetGTK printDialog(aParent, 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::ShowPageSetup(nsPIDOMWindowOuter* 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(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->SavePrintSettingsToPrefs(
+ aNSSettings, true,
+ 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..83cd357992
--- /dev/null
+++ b/widget/gtk/nsPrintDialogGTK.h
@@ -0,0 +1,37 @@
+/* -*- 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 : public nsIPrintDialogService {
+ virtual ~nsPrintDialogServiceGTK();
+
+ public:
+ nsPrintDialogServiceGTK();
+
+ NS_DECL_ISUPPORTS
+
+ NS_IMETHOD Init() override;
+ NS_IMETHOD Show(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+ NS_IMETHOD ShowPageSetup(nsPIDOMWindowOuter* aParent,
+ nsIPrintSettings* aSettings) override;
+};
+
+#endif
diff --git a/widget/gtk/nsPrintSettingsGTK.cpp b/widget/gtk/nsPrintSettingsGTK.cpp
new file mode 100644
index 0000000000..986e90ee4b
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.cpp
@@ -0,0 +1,685 @@
+/* -*- 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>
+
+static gboolean ref_printer(GtkPrinter* aPrinter, gpointer aData) {
+ ((nsPrintSettingsGTK*)aData)->SetGtkPrinter(aPrinter);
+ return TRUE;
+}
+
+static gboolean printer_enumerator(GtkPrinter* aPrinter, gpointer aData) {
+ if (gtk_printer_is_default(aPrinter)) return ref_printer(aPrinter, aData);
+
+ return FALSE; // Keep 'em coming...
+}
+
+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);
+ mGTKPrinter = (GtkPrinter*)g_object_ref(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)) {
+ if (gtk_printer_accepts_pdf(mGTKPrinter)) {
+ format = nsIPrintSettings::kOutputFormatPDF;
+ } else {
+ format = nsIPrintSettings::kOutputFormatPS;
+ }
+ }
+
+ *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::SetupSilentPrinting() {
+ // We have to get a printer here, rather than when the print settings are
+ // constructed. This is because when we request sync, GTK makes us wait in the
+ // *event loop* while waiting for the enumeration to finish. We must do this
+ // when event loop runs are expected.
+ gtk_enumerate_printers(printer_enumerator, this, nullptr, TRUE);
+
+ // XXX If no default printer set, get the first one.
+ if (!GTK_IS_PRINTER(mGTKPrinter))
+ gtk_enumerate_printers(ref_printer, this, nullptr, TRUE);
+
+ 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) {
+ if (!gtk_print_settings_has_key(mPrintSettings,
+ GTK_PRINT_SETTINGS_RESOLUTION))
+ return NS_ERROR_FAILURE;
+ *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) {
+ if (!gtk_print_settings_has_key(mPrintSettings, GTK_PRINT_SETTINGS_DUPLEX)) {
+ *aDuplex = GTK_PRINT_DUPLEX_SIMPLEX;
+ } else {
+ *aDuplex = gtk_print_settings_get_duplex(mPrintSettings);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsPrintSettingsGTK::SetDuplex(int32_t aDuplex) {
+ MOZ_ASSERT(aDuplex >= GTK_PRINT_DUPLEX_SIMPLEX &&
+ aDuplex <= GTK_PRINT_DUPLEX_VERTICAL,
+ "value is out of bounds for GtkPrintDuplex enum");
+ gtk_print_settings_set_duplex(mPrintSettings,
+ static_cast<GtkPrintDuplex>(aDuplex));
+
+ // 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.
+ constexpr char kCupsDuplex[] = "cups-Duplex";
+ switch (aDuplex) {
+ case GTK_PRINT_DUPLEX_SIMPLEX:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "None");
+ break;
+ case GTK_PRINT_DUPLEX_HORIZONTAL:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "DuplexNoTumble");
+ break;
+ case GTK_PRINT_DUPLEX_VERTICAL:
+ gtk_print_settings_set(mPrintSettings, kCupsDuplex, "DuplexTumble");
+ break;
+ }
+
+ return NS_OK;
+}
diff --git a/widget/gtk/nsPrintSettingsGTK.h b/widget/gtk/nsPrintSettingsGTK.h
new file mode 100644
index 0000000000..c32c594587
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsGTK.h
@@ -0,0 +1,145 @@
+/* -*- 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);
+
+ // 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 SetupSilentPrinting() 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..7894101831
--- /dev/null
+++ b/widget/gtk/nsPrintSettingsServiceGTK.cpp
@@ -0,0 +1,78 @@
+/* -*- 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 "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/nsSound.cpp b/widget/gtk/nsSound.cpp
new file mode 100644
index 0000000000..4d685d8566
--- /dev/null
+++ b/widget/gtk/nsSound.cpp
@@ -0,0 +1,398 @@
+/* -*- 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 "plstr.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,
+ &fd.rwget());
+ 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, 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..9b4be6fb93
--- /dev/null
+++ b/widget/gtk/nsToolkit.cpp
@@ -0,0 +1,31 @@
+/* -*- 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;
+
+//-------------------------------------------------------------------------
+//
+// constructor
+//
+//-------------------------------------------------------------------------
+nsGTKToolkit::nsGTKToolkit() : mFocusTimestamp(0) {}
+
+//-------------------------------------------------------------------------------
+// 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..882ded8cad
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.cpp
@@ -0,0 +1,115 @@
+/* -*- 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"
+
+using mozilla::LogLevel;
+
+static mozilla::LazyLogModule sIdleLog("nsIUserIdleService");
+
+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);
+
+static bool sInitialized = false;
+static _XScreenSaverQueryExtension_fn _XSSQueryExtension = nullptr;
+static _XScreenSaverAllocInfo_fn _XSSAllocInfo = nullptr;
+static _XScreenSaverQueryInfo_fn _XSSQueryInfo = nullptr;
+
+static void Initialize() {
+ if (!gdk_display_get_default() ||
+ !GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ return;
+ }
+
+ // This will leak - See comments in ~nsUserIdleServiceGTK().
+ PRLibrary* xsslib = PR_LoadLibrary("libXss.so.1");
+ if (!xsslib) // ouch.
+ {
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to find libXss.so!\n"));
+ return;
+ }
+
+ _XSSQueryExtension = (_XScreenSaverQueryExtension_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryExtension");
+ _XSSAllocInfo = (_XScreenSaverAllocInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverAllocInfo");
+ _XSSQueryInfo = (_XScreenSaverQueryInfo_fn)PR_FindFunctionSymbol(
+ xsslib, "XScreenSaverQueryInfo");
+
+ if (!_XSSQueryExtension)
+ MOZ_LOG(sIdleLog, LogLevel::Warning,
+ ("Failed to get XSSQueryExtension!\n"));
+ if (!_XSSAllocInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSAllocInfo!\n"));
+ if (!_XSSQueryInfo)
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("Failed to get XSSQueryInfo!\n"));
+
+ sInitialized = true;
+}
+
+nsUserIdleServiceGTK::nsUserIdleServiceGTK() : mXssInfo(nullptr) {
+ Initialize();
+}
+
+nsUserIdleServiceGTK::~nsUserIdleServiceGTK() {
+ if (mXssInfo) XFree(mXssInfo);
+
+// 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
+}
+
+bool nsUserIdleServiceGTK::PollIdleTime(uint32_t* aIdleTime) {
+ if (!sInitialized) {
+ // For some reason, we could not find xscreensaver.
+ return false;
+ }
+
+ // 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;
+ }
+
+ if (!_XSSQueryExtension || !_XSSAllocInfo || !_XSSQueryInfo) {
+ return false;
+ }
+
+ int event_base, error_base;
+ if (_XSSQueryExtension(dplay, &event_base, &error_base)) {
+ if (!mXssInfo) mXssInfo = _XSSAllocInfo();
+ if (!mXssInfo) return false;
+ _XSSQueryInfo(dplay, GDK_ROOT_WINDOW(), mXssInfo);
+ *aIdleTime = mXssInfo->idle;
+ return true;
+ }
+ // If we get here, we couldn't get to XScreenSaver:
+ MOZ_LOG(sIdleLog, LogLevel::Warning, ("XSSQueryExtension returned false!\n"));
+ return false;
+}
+
+bool nsUserIdleServiceGTK::UsePollMode() { return sInitialized; }
diff --git a/widget/gtk/nsUserIdleServiceGTK.h b/widget/gtk/nsUserIdleServiceGTK.h
new file mode 100644
index 0000000000..9b9ba31846
--- /dev/null
+++ b/widget/gtk/nsUserIdleServiceGTK.h
@@ -0,0 +1,50 @@
+/* -*- 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 <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <gdk/gdkx.h>
+
+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;
+
+class nsUserIdleServiceGTK : public nsUserIdleService {
+ public:
+ NS_INLINE_DECL_REFCOUNTING_INHERITED(nsUserIdleServiceGTK, nsUserIdleService)
+
+ virtual bool PollIdleTime(uint32_t* aIdleTime) override;
+
+ static already_AddRefed<nsUserIdleServiceGTK> GetInstance() {
+ RefPtr<nsUserIdleServiceGTK> idleService =
+ nsUserIdleService::GetInstance().downcast<nsUserIdleServiceGTK>();
+ if (!idleService) {
+ idleService = new nsUserIdleServiceGTK();
+ }
+
+ return idleService.forget();
+ }
+
+ private:
+ ~nsUserIdleServiceGTK();
+ XScreenSaverInfo* mXssInfo;
+
+ protected:
+ nsUserIdleServiceGTK();
+ virtual bool UsePollMode() override;
+};
+
+#endif // nsUserIdleServiceGTK_h__
diff --git a/widget/gtk/nsWaylandDisplay.cpp b/widget/gtk/nsWaylandDisplay.cpp
new file mode 100644
index 0000000000..310917a27b
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.cpp
@@ -0,0 +1,309 @@
+/* -*- 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 "nsWaylandDisplay.h"
+
+#include "base/message_loop.h" // for MessageLoop
+#include "base/task.h" // for NewRunnableMethod, etc
+#include "mozilla/StaticMutex.h"
+#include "mozilla/StaticPrefs_widget.h"
+
+namespace mozilla {
+namespace widget {
+
+// nsWaylandDisplay needs to be created for each calling thread(main thread,
+// compositor thread and render thread)
+#define MAX_DISPLAY_CONNECTIONS 10
+
+// An array of active wayland displays. We need a display for every thread
+// where is wayland interface used as we need to dispatch waylands events
+// there.
+static RefPtr<nsWaylandDisplay> gWaylandDisplays[MAX_DISPLAY_CONNECTIONS];
+static StaticMutex gWaylandDisplayArrayWriteMutex;
+
+// Dispatch events to Compositor/Render queues
+void WaylandDispatchDisplays() {
+ MOZ_ASSERT(NS_IsMainThread(),
+ "WaylandDispatchDisplays() is supposed to run in main thread");
+ for (auto& display : gWaylandDisplays) {
+ if (display) {
+ display->DispatchEventQueue();
+ }
+ }
+}
+
+void WaylandDisplayRelease() {
+ StaticMutexAutoLock lock(gWaylandDisplayArrayWriteMutex);
+ for (auto& display : gWaylandDisplays) {
+ if (display) {
+ display = nullptr;
+ }
+ }
+}
+
+// Get WaylandDisplay for given wl_display and actual calling thread.
+RefPtr<nsWaylandDisplay> WaylandDisplayGet(GdkDisplay* aGdkDisplay) {
+ wl_display* waylandDisplay = WaylandDisplayGetWLDisplay(aGdkDisplay);
+ if (!waylandDisplay) {
+ return nullptr;
+ }
+
+ // Search existing display connections for wl_display:thread combination.
+ for (auto& display : gWaylandDisplays) {
+ if (display && display->Matches(waylandDisplay)) {
+ return display;
+ }
+ }
+
+ StaticMutexAutoLock arrayLock(gWaylandDisplayArrayWriteMutex);
+ for (auto& display : gWaylandDisplays) {
+ if (display == nullptr) {
+ display = new nsWaylandDisplay(waylandDisplay);
+ return display;
+ }
+ }
+
+ MOZ_CRASH("There's too many wayland display conections!");
+ return nullptr;
+}
+
+wl_display* WaylandDisplayGetWLDisplay(GdkDisplay* aGdkDisplay) {
+ if (!aGdkDisplay) {
+ aGdkDisplay = gdk_display_get_default();
+ if (!aGdkDisplay || GDK_IS_X11_DISPLAY(aGdkDisplay)) {
+ return nullptr;
+ }
+ }
+
+ return gdk_wayland_display_get_wl_display(aGdkDisplay);
+}
+
+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::SetDataDeviceManager(
+ wl_data_device_manager* aDataDeviceManager) {
+ mDataDeviceManager = aDataDeviceManager;
+}
+
+void nsWaylandDisplay::SetSeat(wl_seat* aSeat) { mSeat = aSeat; }
+
+void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
+ gtk_primary_selection_device_manager* aPrimarySelectionDeviceManager) {
+ mPrimarySelectionDeviceManagerGtk = aPrimarySelectionDeviceManager;
+}
+
+void nsWaylandDisplay::SetPrimarySelectionDeviceManager(
+ zwp_primary_selection_device_manager_v1* aPrimarySelectionDeviceManager) {
+ mPrimarySelectionDeviceManagerZwpV1 = aPrimarySelectionDeviceManager;
+}
+
+void nsWaylandDisplay::SetIdleInhibitManager(
+ zwp_idle_inhibit_manager_v1* aIdleInhibitManager) {
+ mIdleInhibitManager = aIdleInhibitManager;
+}
+
+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;
+ }
+
+ if (strcmp(interface, "wl_shm") == 0) {
+ auto* shm = WaylandRegistryBind<wl_shm>(registry, id, &wl_shm_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)shm, display->GetEventQueue());
+ display->SetShm(shm);
+ } else if (strcmp(interface, "wl_data_device_manager") == 0) {
+ int data_device_manager_version = MIN(version, 3);
+ auto* data_device_manager = WaylandRegistryBind<wl_data_device_manager>(
+ registry, id, &wl_data_device_manager_interface,
+ data_device_manager_version);
+ wl_proxy_set_queue((struct wl_proxy*)data_device_manager,
+ display->GetEventQueue());
+ display->SetDataDeviceManager(data_device_manager);
+ } else if (strcmp(interface, "wl_seat") == 0) {
+ auto* seat =
+ WaylandRegistryBind<wl_seat>(registry, id, &wl_seat_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)seat, display->GetEventQueue());
+ display->SetSeat(seat);
+ } else if (strcmp(interface, "gtk_primary_selection_device_manager") == 0) {
+ auto* primary_selection_device_manager =
+ WaylandRegistryBind<gtk_primary_selection_device_manager>(
+ registry, id, &gtk_primary_selection_device_manager_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)primary_selection_device_manager,
+ display->GetEventQueue());
+ display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
+ } else if (strcmp(interface, "zwp_primary_selection_device_manager_v1") ==
+ 0) {
+ auto* primary_selection_device_manager =
+ WaylandRegistryBind<gtk_primary_selection_device_manager>(
+ registry, id, &zwp_primary_selection_device_manager_v1_interface,
+ 1);
+ wl_proxy_set_queue((struct wl_proxy*)primary_selection_device_manager,
+ display->GetEventQueue());
+ display->SetPrimarySelectionDeviceManager(primary_selection_device_manager);
+ } else if (strcmp(interface, "zwp_idle_inhibit_manager_v1") == 0) {
+ auto* idle_inhibit_manager =
+ WaylandRegistryBind<zwp_idle_inhibit_manager_v1>(
+ registry, id, &zwp_idle_inhibit_manager_v1_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)idle_inhibit_manager,
+ display->GetEventQueue());
+ display->SetIdleInhibitManager(idle_inhibit_manager);
+ } else if (strcmp(interface, "wl_compositor") == 0) {
+ // Requested wl_compositor version 4 as we need wl_surface_damage_buffer().
+ auto* compositor = WaylandRegistryBind<wl_compositor>(
+ registry, id, &wl_compositor_interface, 4);
+ wl_proxy_set_queue((struct wl_proxy*)compositor, display->GetEventQueue());
+ display->SetCompositor(compositor);
+ } else if (strcmp(interface, "wl_subcompositor") == 0) {
+ auto* subcompositor = WaylandRegistryBind<wl_subcompositor>(
+ registry, id, &wl_subcompositor_interface, 1);
+ wl_proxy_set_queue((struct wl_proxy*)subcompositor,
+ display->GetEventQueue());
+ display->SetSubcompositor(subcompositor);
+ }
+}
+
+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};
+
+bool nsWaylandDisplay::DispatchEventQueue() {
+ if (mEventQueue) {
+ wl_display_dispatch_queue_pending(mDisplay, mEventQueue);
+ }
+ return true;
+}
+
+void nsWaylandDisplay::SyncEnd() {
+ wl_callback_destroy(mSyncCallback);
+ mSyncCallback = nullptr;
+}
+
+static void wayland_sync_callback(void* data, struct wl_callback* callback,
+ uint32_t time) {
+ auto display = static_cast<nsWaylandDisplay*>(data);
+ display->SyncEnd();
+}
+
+static const struct wl_callback_listener sync_callback_listener = {
+ .done = wayland_sync_callback};
+
+void nsWaylandDisplay::SyncBegin() {
+ WaitForSyncEnd();
+
+ // Use wl_display_sync() to synchronize wayland events.
+ // See dri2_wl_swap_buffers_with_damage() from MESA
+ // or wl_display_roundtrip_queue() from wayland-client.
+ struct wl_display* displayWrapper =
+ static_cast<wl_display*>(wl_proxy_create_wrapper((void*)mDisplay));
+ if (!displayWrapper) {
+ NS_WARNING("Failed to create wl_proxy wrapper!");
+ return;
+ }
+
+ wl_proxy_set_queue((struct wl_proxy*)displayWrapper, mEventQueue);
+ mSyncCallback = wl_display_sync(displayWrapper);
+ wl_proxy_wrapper_destroy((void*)displayWrapper);
+
+ if (!mSyncCallback) {
+ NS_WARNING("Failed to create wl_display_sync callback!");
+ return;
+ }
+
+ wl_callback_add_listener(mSyncCallback, &sync_callback_listener, this);
+ wl_display_flush(mDisplay);
+}
+
+void nsWaylandDisplay::QueueSyncBegin() {
+ RefPtr<nsWaylandDisplay> self(this);
+ NS_DispatchToMainThread(
+ NS_NewRunnableFunction("nsWaylandDisplay::QueueSyncBegin",
+ [self]() -> void { self->SyncBegin(); }));
+}
+
+void nsWaylandDisplay::WaitForSyncEnd() {
+ // We're done here
+ if (!mSyncCallback) {
+ return;
+ }
+
+ while (mSyncCallback != nullptr) {
+ // TODO: wl_display_dispatch_queue() should not be called while
+ // glib main loop is iterated at nsAppShell::ProcessNextNativeEvent().
+ if (wl_display_dispatch_queue(mDisplay, mEventQueue) == -1) {
+ NS_WARNING("wl_display_dispatch_queue failed!");
+ SyncEnd();
+ return;
+ }
+ }
+}
+
+bool nsWaylandDisplay::Matches(wl_display* aDisplay) {
+ return mThreadId == PR_GetCurrentThread() && aDisplay == mDisplay;
+}
+
+nsWaylandDisplay::nsWaylandDisplay(wl_display* aDisplay, bool aLighWrapper)
+ : mThreadId(PR_GetCurrentThread()),
+ mDisplay(aDisplay),
+ mEventQueue(nullptr),
+ mDataDeviceManager(nullptr),
+ mCompositor(nullptr),
+ mSubcompositor(nullptr),
+ mSeat(nullptr),
+ mShm(nullptr),
+ mSyncCallback(nullptr),
+ mPrimarySelectionDeviceManagerGtk(nullptr),
+ mPrimarySelectionDeviceManagerZwpV1(nullptr),
+ mIdleInhibitManager(nullptr),
+ mRegistry(nullptr),
+ mExplicitSync(false) {
+ if (!aLighWrapper) {
+ mRegistry = wl_display_get_registry(mDisplay);
+ wl_registry_add_listener(mRegistry, &registry_listener, this);
+ }
+
+ if (!NS_IsMainThread()) {
+ mEventQueue = wl_display_create_queue(mDisplay);
+ wl_proxy_set_queue((struct wl_proxy*)mRegistry, mEventQueue);
+ }
+
+ if (!aLighWrapper) {
+ if (mEventQueue) {
+ wl_display_roundtrip_queue(mDisplay, mEventQueue);
+ wl_display_roundtrip_queue(mDisplay, mEventQueue);
+ } else {
+ wl_display_roundtrip(mDisplay);
+ wl_display_roundtrip(mDisplay);
+ }
+ }
+}
+
+nsWaylandDisplay::~nsWaylandDisplay() {
+ wl_registry_destroy(mRegistry);
+ mRegistry = nullptr;
+
+ if (mEventQueue) {
+ wl_event_queue_destroy(mEventQueue);
+ mEventQueue = nullptr;
+ }
+ mDisplay = nullptr;
+}
+
+} // namespace widget
+} // namespace mozilla
diff --git a/widget/gtk/nsWaylandDisplay.h b/widget/gtk/nsWaylandDisplay.h
new file mode 100644
index 0000000000..6beb7ef684
--- /dev/null
+++ b/widget/gtk/nsWaylandDisplay.h
@@ -0,0 +1,130 @@
+/* -*- 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 __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/gtk-primary-selection-client-protocol.h"
+#include "mozilla/widget/idle-inhibit-unstable-v1-client-protocol.h"
+#include "mozilla/widget/linux-dmabuf-unstable-v1-client-protocol.h"
+#include "mozilla/widget/primary-selection-unstable-v1-client-protocol.h"
+
+namespace mozilla {
+namespace 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:
+ NS_INLINE_DECL_THREADSAFE_REFCOUNTING(nsWaylandDisplay)
+
+ // Create nsWaylandDisplay object on top of native Wayland wl_display
+ // connection. When aLighWrapper is set we don't get wayland registry
+ // objects and only event loop is provided.
+ explicit nsWaylandDisplay(wl_display* aDisplay, bool aLighWrapper = false);
+
+ bool DispatchEventQueue();
+
+ void SyncBegin();
+ void QueueSyncBegin();
+ void SyncEnd();
+ void WaitForSyncEnd();
+
+ bool Matches(wl_display* aDisplay);
+
+ wl_display* GetDisplay() { return mDisplay; };
+ wl_event_queue* GetEventQueue() { return mEventQueue; };
+ wl_compositor* GetCompositor(void) { return mCompositor; };
+ wl_subcompositor* GetSubcompositor(void) { return mSubcompositor; };
+ wl_data_device_manager* GetDataDeviceManager(void) {
+ return mDataDeviceManager;
+ };
+ wl_seat* GetSeat(void) { return mSeat; };
+ wl_shm* GetShm(void) { return mShm; };
+ gtk_primary_selection_device_manager* GetPrimarySelectionDeviceManagerGtk(
+ void) {
+ return mPrimarySelectionDeviceManagerGtk;
+ };
+ zwp_primary_selection_device_manager_v1*
+ GetPrimarySelectionDeviceManagerZwpV1(void) {
+ return mPrimarySelectionDeviceManagerZwpV1;
+ };
+ zwp_idle_inhibit_manager_v1* GetIdleInhibitManager(void) {
+ return mIdleInhibitManager;
+ }
+
+ bool IsMainThreadDisplay() { return mEventQueue == nullptr; }
+
+ void SetShm(wl_shm* aShm);
+ void SetCompositor(wl_compositor* aCompositor);
+ void SetSubcompositor(wl_subcompositor* aSubcompositor);
+ void SetDataDeviceManager(wl_data_device_manager* aDataDeviceManager);
+ void SetSeat(wl_seat* aSeat);
+ void SetPrimarySelectionDeviceManager(
+ gtk_primary_selection_device_manager* aPrimarySelectionDeviceManager);
+ void SetPrimarySelectionDeviceManager(
+ zwp_primary_selection_device_manager_v1* aPrimarySelectionDeviceManager);
+ void SetIdleInhibitManager(zwp_idle_inhibit_manager_v1* aIdleInhibitManager);
+
+ bool IsExplicitSyncEnabled() { return mExplicitSync; }
+
+ private:
+ ~nsWaylandDisplay();
+
+ PRThread* mThreadId;
+ wl_display* mDisplay;
+ wl_event_queue* mEventQueue;
+ wl_data_device_manager* mDataDeviceManager;
+ wl_compositor* mCompositor;
+ wl_subcompositor* mSubcompositor;
+ wl_seat* mSeat;
+ wl_shm* mShm;
+ wl_callback* mSyncCallback;
+ gtk_primary_selection_device_manager* mPrimarySelectionDeviceManagerGtk;
+ zwp_primary_selection_device_manager_v1* mPrimarySelectionDeviceManagerZwpV1;
+ zwp_idle_inhibit_manager_v1* mIdleInhibitManager;
+ wl_registry* mRegistry;
+ bool mExplicitSync;
+};
+
+void WaylandDispatchDisplays();
+void WaylandDisplayRelease();
+
+RefPtr<nsWaylandDisplay> WaylandDisplayGet(GdkDisplay* aGdkDisplay = nullptr);
+wl_display* WaylandDisplayGetWLDisplay(GdkDisplay* aGdkDisplay = nullptr);
+
+} // namespace widget
+} // namespace mozilla
+
+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..d649c7e0bf
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.cpp
@@ -0,0 +1,74 @@
+/* -*- 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"
+#ifdef MOZ_X11
+# include "nsClipboard.h"
+#endif
+#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;
+
+#ifdef MOZ_X11
+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>();
+}
+#endif
+
+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();
+#ifdef MOZ_ENABLE_DBUS
+ WakeLockListener::Shutdown();
+#endif
+}
diff --git a/widget/gtk/nsWidgetFactory.h b/widget/gtk/nsWidgetFactory.h
new file mode 100644
index 0000000000..21ea0da667
--- /dev/null
+++ b/widget/gtk/nsWidgetFactory.h
@@ -0,0 +1,22 @@
+/* -*- 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(nsISupports* outer, 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..f435464f7a
--- /dev/null
+++ b/widget/gtk/nsWindow.cpp
@@ -0,0 +1,8420 @@
+/* -*- 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 "mozilla/ArrayUtils.h"
+#include "mozilla/EventForwards.h"
+#include "mozilla/MiscEvents.h"
+#include "mozilla/MouseEvents.h"
+#include "mozilla/PresShell.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_apz.h"
+#include "mozilla/StaticPrefs_ui.h"
+#include "mozilla/TextEvents.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/TouchEvents.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/WidgetUtils.h"
+#include "mozilla/X11Util.h"
+#include "mozilla/XREAppData.h"
+#include "mozilla/dom/Document.h"
+#include "mozilla/dom/WheelEventBinding.h"
+#include "InputData.h"
+#include "nsAppRunner.h"
+#include <algorithm>
+
+#include "GeckoProfiler.h"
+
+#include "prlink.h"
+#include "nsGTKToolkit.h"
+#include "nsIRollupListener.h"
+#include "nsINode.h"
+
+#include "nsWidgetsCID.h"
+#include "nsDragService.h"
+#include "nsIWidgetListener.h"
+#include "nsIScreenManager.h"
+#include "SystemTimeConverter.h"
+#include "nsViewManager.h"
+#include "nsMenuPopupFrame.h"
+#include "nsXPLookAndFeel.h"
+
+#include "nsGtkKeyUtils.h"
+#include "nsGtkCursors.h"
+#include "ScreenHelperGTK.h"
+
+#include <gtk/gtk.h>
+#include <gtk/gtkx.h>
+
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+#endif /* MOZ_WAYLAND */
+
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include <X11/Xatom.h>
+# include <X11/extensions/XShm.h>
+# include <X11/extensions/shape.h>
+# include <gdk/gdkkeysyms-compat.h>
+#endif /* MOZ_X11 */
+
+#include <gdk/gdkkeysyms.h>
+
+#if defined(MOZ_WAYLAND)
+# include <gdk/gdkwayland.h>
+# include "nsView.h"
+#endif
+
+#include "nsGkAtoms.h"
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Likely.h"
+#include "mozilla/Preferences.h"
+#include "nsGfxCIID.h"
+#include "nsGtkUtils.h"
+#include "nsLayoutUtils.h"
+#include "mozilla/layers/LayersTypes.h"
+#include "nsIUserIdleServiceInternal.h"
+#include "GLContext.h"
+#include "gfx2DGlue.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Accessible.h"
+# include "mozilla/a11y/Platform.h"
+# include "nsAccessibilityService.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+#endif
+
+/* For SetIcon */
+#include "nsAppDirectoryServiceDefs.h"
+#include "nsString.h"
+#include "nsIFile.h"
+
+/* SetCursor(imgIContainer*) */
+#include <gdk/gdk.h>
+#include <wchar.h>
+#include "imgIContainer.h"
+#include "nsGfxCIID.h"
+#include "nsImageToPixbuf.h"
+#include "nsIInterfaceRequestorUtils.h"
+#include "ClientLayerManager.h"
+#include "nsIGSettingsService.h"
+
+#include "gfxPlatformGtk.h"
+#include "gfxContext.h"
+#include "gfxImageSurface.h"
+#include "gfxUtils.h"
+#include "Layers.h"
+#include "GLContextProvider.h"
+#include "mozilla/gfx/2D.h"
+#include "mozilla/gfx/HelpersCairo.h"
+#include "mozilla/gfx/GPUProcessManager.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"
+
+#ifdef MOZ_X11
+# include "mozilla/gfx/gfxVars.h"
+# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
+# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
+# include "GtkCompositorWidget.h"
+# include "gfxXlibSurface.h"
+# include "WindowSurfaceX11Image.h"
+# include "WindowSurfaceX11SHM.h"
+# include "WindowSurfaceXRender.h"
+#endif // MOZ_X11
+#ifdef MOZ_WAYLAND
+# include "nsIClipboard.h"
+#endif
+
+#include "nsShmImage.h"
+#include "gtkdrawing.h"
+
+#include "NativeKeyBindings.h"
+
+#include <dlfcn.h>
+#include "nsPresContext.h"
+
+using namespace mozilla;
+using namespace mozilla::gfx;
+using namespace mozilla::widget;
+using namespace mozilla::layers;
+using mozilla::gl::GLContextEGL;
+using mozilla::gl::GLContextGLX;
+
+// 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;
+
+GdkEventMask GDK_TOUCHPAD_GESTURE_MASK = static_cast<GdkEventMask>(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;
+
+#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
+
+/* 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);
+
+static int is_parent_ungrab_enter(GdkEventCrossing* aEvent);
+static int is_parent_grab_leave(GdkEventCrossing* aEvent);
+
+/* callbacks from widgets */
+static gboolean expose_event_cb(GtkWidget* widget, cairo_t* rect);
+static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
+static void container_unrealize_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);
+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 void hierarchy_changed_cb(GtkWidget* widget,
+ GtkWidget* previous_toplevel);
+static gboolean window_state_event_cb(GtkWidget* widget,
+ GdkEventWindowState* event);
+static void settings_changed_cb(GtkSettings* settings, GParamSpec* pspec,
+ nsWindow* data);
+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 guint32 sRetryGrabTime;
+
+static SystemTimeConverter<guint32>& TimeConverter() {
+ static SystemTimeConverter<guint32> sTimeConverterSingleton;
+ return sTimeConverterSingleton;
+}
+
+nsWindow::CSDSupportLevel nsWindow::sCSDSupportLevel = CSD_SUPPORT_UNKNOWN;
+bool nsWindow::sTransparentMainWindow = false;
+static bool sIgnoreChangedSettings = false;
+
+void nsWindow::WithSettingsChangesIgnored(const std::function<void()>& aFn) {
+ AutoRestore ar(sIgnoreChangedSettings);
+ sIgnoreChangedSettings = true;
+ aFn();
+}
+
+namespace mozilla {
+
+class CurrentX11TimeGetter {
+ public:
+ explicit CurrentX11TimeGetter(GdkWindow* aWindow)
+ : mWindow(aWindow), mAsyncUpdateStart() {}
+
+ 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;
+};
+
+} // namespace mozilla
+
+static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
+
+// 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 gRaiseWindows = true;
+static bool gUseWaylandVsync = true;
+static bool gUseWaylandUseOpaqueRegion = true;
+static bool gUseAspectRatio = true;
+static GList* gVisibleWaylandPopupWindows = nullptr;
+static uint32_t gLastTouchID = 0;
+
+#define NS_WINDOW_TITLE_MAX_LENGTH 4095
+
+// If after selecting profile window, the startup fail, please refer to
+// http://bugzilla.gnome.org/show_bug.cgi?id=88940
+
+// needed for imgIContainer cursors
+// GdkDisplay* was added in 2.2
+typedef struct _GdkDisplay GdkDisplay;
+
+#define kWindowPositionSlop 20
+
+// cursor cache
+static GdkCursor* gCursorCache[eCursorCount];
+
+static GtkWidget* gInvisibleContainer = nullptr;
+
+// 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;
+}
+
+void GetWindowOrigin(GdkWindow* aWindow, int* aX, int* aY) {
+ if (aWindow) {
+ gdk_window_get_origin(aWindow, aX, aY);
+ }
+
+ // 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.
+#if 0
+ *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;
+ }
+#endif
+}
+
+nsWindow::nsWindow() {
+ mIsTopLevel = false;
+ mIsDestroyed = false;
+ mListenForResizes = false;
+ mNeedsDispatchResized = false;
+ mIsShown = false;
+ mNeedsShow = false;
+ mEnabled = true;
+ mCreated = false;
+ mHandleTouchEvent = false;
+ mIsDragPopup = false;
+ mIsX11Display = gfxPlatformGtk::GetPlatform()->IsX11Display();
+
+ mContainer = nullptr;
+ mGdkWindow = nullptr;
+ mShell = nullptr;
+ mCompositorWidgetDelegate = nullptr;
+ mHasMappedToplevel = false;
+ mRetryPointerGrab = false;
+ mWindowType = eWindowType_child;
+ mSizeState = nsSizeMode_Normal;
+ mBoundsAreValid = true;
+ mAspectRatio = 0.0f;
+ mAspectRatioSaved = 0.0f;
+ mLastSizeMode = nsSizeMode_Normal;
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
+
+#ifdef MOZ_X11
+ mOldFocusWindow = 0;
+
+ mXDisplay = nullptr;
+ mXWindow = X11None;
+ mXVisual = nullptr;
+ mXDepth = 0;
+#endif /* MOZ_X11 */
+
+#ifdef MOZ_WAYLAND
+ mNeedsCompositorResume = false;
+ mCompositorInitiallyPaused = false;
+#endif
+ mWaitingForMoveToRectCB = false;
+ mPendingSizeRect = LayoutDeviceIntRect(0, 0, 0, 0);
+
+ 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 (!mIsX11Display) {
+ nsCOMPtr<nsIClipboard> clipboard =
+ do_GetService("@mozilla.org/widget/clipboard;1");
+ NS_ASSERTION(clipboard, "Failed to init clipboard!");
+ }
+#endif
+ }
+
+ mLastMotionPressure = 0;
+
+#ifdef ACCESSIBILITY
+ mRootAccessible = nullptr;
+#endif
+
+ mIsTransparent = false;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapForTitlebar = false;
+
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ mLastScrollEventTime = GDK_CURRENT_TIME;
+
+ mPendingConfigures = 0;
+ mCSDSupportLevel = CSD_SUPPORT_NONE;
+ mDrawToContainer = false;
+ mDrawInTitlebar = false;
+ mTitlebarBackdropState = false;
+
+ mHasAlphaVisual = false;
+ mIsPIPWindow = false;
+ mAlwaysOnTop = false;
+
+ mWindowScaleFactorChanged = true;
+ mWindowScaleFactor = 1;
+
+ mCompositedScreen = gdk_screen_is_composited(gdk_screen_get_default());
+}
+
+nsWindow::~nsWindow() {
+ LOG(("nsWindow::~nsWindow() [%p]\n", (void*)this));
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+
+ Destroy();
+}
+
+/* static */
+void nsWindow::ReleaseGlobals() {
+ for (auto& cursor : gCursorCache) {
+ if (cursor) {
+ g_object_unref(cursor);
+ cursor = nullptr;
+ }
+ }
+}
+
+void nsWindow::CommonCreate(nsIWidget* aParent, bool aListenForResizes) {
+ mParent = aParent;
+ mListenForResizes = aListenForResizes;
+ mCreated = true;
+}
+
+void nsWindow::DispatchActivateEvent(void) {
+ NS_ASSERTION(mContainer || mIsDestroyed,
+ "DispatchActivateEvent only intended for container windows");
+
+#ifdef ACCESSIBILITY
+ DispatchActivateEventAccessible();
+#endif // ACCESSIBILITY
+
+ if (mWidgetListener) mWidgetListener->WindowActivated();
+}
+
+void nsWindow::DispatchDeactivateEvent(void) {
+ if (mWidgetListener) mWidgetListener->WindowDeactivated();
+
+#ifdef ACCESSIBILITY
+ DispatchDeactivateEventAccessible();
+#endif // ACCESSIBILITY
+}
+
+void nsWindow::DispatchResized() {
+ mNeedsDispatchResized = false;
+ if (mWidgetListener) {
+ mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+ if (mAttachedWidgetListener) {
+ mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
+ }
+}
+
+void nsWindow::MaybeDispatchResized() {
+ if (mNeedsDispatchResized && !mIsDestroyed) {
+ 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() {
+ return mBounds.width > 0 && mBounds.height > 0;
+}
+
+static GtkWidget* EnsureInvisibleContainer() {
+ if (!gInvisibleContainer) {
+ // GtkWidgets need to be anchored to a GtkWindow to be realized (to
+ // have a window). Using GTK_WINDOW_POPUP rather than
+ // GTK_WINDOW_TOPLEVEL in the hope that POPUP results in less
+ // initialization and window manager interaction.
+ GtkWidget* window = gtk_window_new(GTK_WINDOW_POPUP);
+ gInvisibleContainer = moz_container_new();
+ gtk_container_add(GTK_CONTAINER(window), gInvisibleContainer);
+ gtk_widget_realize(gInvisibleContainer);
+ }
+ return gInvisibleContainer;
+}
+
+static void CheckDestroyInvisibleContainer() {
+ MOZ_ASSERT(gInvisibleContainer, "oh, no");
+
+ if (!gdk_window_peek_children(gtk_widget_get_window(gInvisibleContainer))) {
+ // No children, so not in use.
+ // Make sure to destroy the GtkWindow also.
+ gtk_widget_destroy(gtk_widget_get_parent(gInvisibleContainer));
+ gInvisibleContainer = nullptr;
+ }
+}
+
+// Change the containing GtkWidget on a sub-hierarchy of GdkWindows belonging
+// to aOldWidget and rooted at aWindow, and reparent any child GtkWidgets of
+// the GdkWindow hierarchy to aNewWidget.
+static void SetWidgetForHierarchy(GdkWindow* aWindow, GtkWidget* aOldWidget,
+ GtkWidget* aNewWidget) {
+ gpointer data;
+ gdk_window_get_user_data(aWindow, &data);
+
+ if (data != aOldWidget) {
+ if (!GTK_IS_WIDGET(data)) return;
+
+ auto* widget = static_cast<GtkWidget*>(data);
+ if (gtk_widget_get_parent(widget) != aOldWidget) return;
+
+ // This window belongs to a child widget, which will no longer be a
+ // child of aOldWidget.
+ gtk_widget_reparent(widget, aNewWidget);
+
+ return;
+ }
+
+ GList* children = gdk_window_get_children(aWindow);
+ for (GList* list = children; list; list = list->next) {
+ SetWidgetForHierarchy(GDK_WINDOW(list->data), aOldWidget, aNewWidget);
+ }
+ g_list_free(children);
+
+ gdk_window_set_user_data(aWindow, aNewWidget);
+}
+
+// Walk the list of child windows and call destroy on them.
+void 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();
+ } else {
+ // This child is not an nsWindow.
+ // Destroy the child GtkWidget.
+ gpointer data;
+ gdk_window_get_user_data(child, &data);
+ if (GTK_IS_WIDGET(data)) {
+ gtk_widget_destroy(static_cast<GtkWidget*>(data));
+ }
+ }
+ }
+}
+
+void nsWindow::Destroy() {
+ if (mIsDestroyed || !mCreated) return;
+
+ LOG(("nsWindow::Destroy [%p]\n", (void*)this));
+ mIsDestroyed = true;
+ mCreated = false;
+
+ /** Need to clean our LayerManager up while still alive */
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ }
+ mLayerManager = nullptr;
+
+#ifdef MOZ_WAYLAND
+ // Shut down our local vsync source
+ if (mWaylandVsyncSource) {
+ mWaylandVsyncSource->Shutdown();
+ mWaylandVsyncSource = nullptr;
+ }
+#endif
+
+ // It is safe to call DestroyeCompositor several times (here and
+ // in the parent class) since it will take effect only once.
+ // The reason we call it here is because on gtk platforms we need
+ // to destroy the compositor before we destroy the gdk window (which
+ // destroys the the gl context attached to it).
+ DestroyCompositor();
+
+#ifdef MOZ_X11
+ // Ensure any resources assigned to the window get cleaned up first
+ // to avoid double-freeing.
+ mSurfaceProvider.CleanupResources();
+#endif
+
+ ClearCachedResources();
+
+ g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
+
+ nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
+ if (rollupListener) {
+ nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
+ if (static_cast<nsIWidget*>(this) == rollupWidget) {
+ rollupListener->Rollup(0, false, nullptr, nullptr);
+ }
+ }
+
+ // dragService will be null after shutdown of the service manager.
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ if (dragService && this == dragService->GetMostRecentDestWindow()) {
+ dragService->ScheduleLeaveEvent();
+ }
+
+ NativeShow(false);
+
+ if (mIMContext) {
+ mIMContext->OnDestroyWindow(this);
+ }
+
+ // make sure that we remove ourself as the focus window
+ if (gFocusWindow == this) {
+ LOGFOCUS(("automatically losing focus...\n"));
+ gFocusWindow = nullptr;
+ }
+
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ if (mShell) {
+ gtk_widget_destroy(mShell);
+ mShell = nullptr;
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ } else if (mContainer) {
+ gtk_widget_destroy(GTK_WIDGET(mContainer));
+ mContainer = nullptr;
+ MOZ_ASSERT(!mGdkWindow,
+ "mGdkWindow should be NULL when mContainer is destroyed");
+ } else if (mGdkWindow) {
+ // Destroy child windows to ensure that their mThebesSurfaces are
+ // released and to remove references from GdkWindows back to their
+ // container widget. (OnContainerUnrealize() does this when the
+ // MozContainer widget is destroyed.)
+ DestroyChildWindows();
+
+ gdk_window_set_user_data(mGdkWindow, nullptr);
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ gdk_window_destroy(mGdkWindow);
+ mGdkWindow = nullptr;
+ }
+
+ if (gInvisibleContainer && owningWidget == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+
+#ifdef ACCESSIBILITY
+ if (mRootAccessible) {
+ mRootAccessible = nullptr;
+ }
+#endif
+
+ // Save until last because OnDestroy() may cause us to be deleted.
+ OnDestroy();
+}
+
+nsIWidget* nsWindow::GetParent(void) { return mParent; }
+
+float nsWindow::GetDPI() {
+ float dpi = 96.0f;
+ nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
+ if (screen) {
+ screen->GetDpi(&dpi);
+ }
+ return dpi;
+}
+
+double nsWindow::GetDefaultScaleInternal() {
+ return GdkScaleFactor() * gfxPlatformGtk::GetFontScaleFactor();
+}
+
+DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display) {
+ return DesktopToLayoutDeviceScale(GdkScaleFactor());
+ }
+#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 (!mIsX11Display) {
+ 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();
+}
+
+void nsWindow::SetParent(nsIWidget* aNewParent) {
+ if (!mGdkWindow) {
+ MOZ_ASSERT_UNREACHABLE("The native window has already been destroyed");
+ return;
+ }
+
+ if (mContainer) {
+ // FIXME bug 1469183
+ NS_ERROR("nsWindow should not have a container here");
+ return;
+ }
+
+ nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
+ if (mParent) {
+ mParent->RemoveChild(this);
+ }
+ mParent = aNewParent;
+
+ GtkWidget* oldContainer = GetMozContainerWidget();
+ if (!oldContainer) {
+ // The GdkWindows have been destroyed so there is nothing else to
+ // reparent.
+ MOZ_ASSERT(gdk_window_is_destroyed(mGdkWindow),
+ "live GdkWindow with no widget");
+ return;
+ }
+
+ nsWindow* newParent = static_cast<nsWindow*>(aNewParent);
+ GdkWindow* newParentWindow = nullptr;
+ GtkWidget* newContainer = nullptr;
+ if (aNewParent) {
+ aNewParent->AddChild(this);
+ newParentWindow = newParent->mGdkWindow;
+ newContainer = newParent->GetMozContainerWidget();
+ } else {
+ // aNewParent is nullptr, but reparent to a hidden window to avoid
+ // destroying the GdkWindow and its descendants.
+ // An invisible container widget is needed to hold descendant
+ // GtkWidgets.
+ newContainer = EnsureInvisibleContainer();
+ newParentWindow = gtk_widget_get_window(newContainer);
+ }
+
+ if (!newContainer) {
+ // The new parent GdkWindow has been destroyed.
+ MOZ_ASSERT(!newParentWindow || gdk_window_is_destroyed(newParentWindow),
+ "live GdkWindow with no widget");
+ Destroy();
+ } else {
+ if (newContainer != oldContainer) {
+ MOZ_ASSERT(!gdk_window_is_destroyed(newParentWindow),
+ "destroyed GdkWindow with widget");
+ SetWidgetForHierarchy(mGdkWindow, oldContainer, newContainer);
+
+ if (oldContainer == gInvisibleContainer) {
+ CheckDestroyInvisibleContainer();
+ }
+ }
+
+ gdk_window_reparent(mGdkWindow, newParentWindow,
+ DevicePixelsToGdkCoordRoundDown(mBounds.x),
+ DevicePixelsToGdkCoordRoundDown(mBounds.y));
+ }
+
+ bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel;
+ if (mHasMappedToplevel != parentHasMappedToplevel) {
+ SetHasMappedToplevel(parentHasMappedToplevel);
+ }
+}
+
+bool nsWindow::WidgetTypeSupportsAcceleration() {
+ 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 == eWindowType_popup) {
+ return 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(!gdk_window_is_destroyed(mGdkWindow),
+ "destroyed GdkWindow with widget");
+
+ MOZ_ASSERT(
+ !mParent,
+ "nsWindow::ReparentNativeWidget() works on toplevel windows only.");
+
+ auto* newParent = static_cast<nsWindow*>(aNewParent);
+ GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
+ GtkWindow* shell = GTK_WINDOW(mShell);
+
+ if (shell && gtk_window_get_transient_for(shell)) {
+ gtk_window_set_transient_for(shell, newParentWidget);
+ }
+}
+
+void nsWindow::SetModal(bool aModal) {
+ LOG(("nsWindow::SetModal [%p] %d\n", (void*)this, aModal));
+ if (mIsDestroyed) return;
+ if (!mIsTopLevel || !mShell) return;
+ gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
+}
+
+// nsIWidget method, which means IsShown.
+bool nsWindow::IsVisible() const { return mIsShown; }
+
+void nsWindow::RegisterTouchWindow() {
+ mHandleTouchEvent = true;
+ mTouches.Clear();
+}
+
+void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
+ if (!mIsTopLevel || !mShell) 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<nsIScreen> screen;
+ nsCOMPtr<nsIScreenManager> screenmgr =
+ do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (screenmgr) {
+ screenmgr->ScreenForRect(*aX, *aY, logWidth, logHeight,
+ getter_AddRefs(screen));
+ }
+
+ // We don't have any screen so leave the coordinates as is
+ if (!screen) return;
+
+ nsIntRect screenRect;
+ if (mSizeMode != nsSizeMode_Fullscreen) {
+ // For normalized windows, use the desktop work area.
+ screen->GetAvailRectDisplayPix(&screenRect.x, &screenRect.y,
+ &screenRect.width, &screenRect.height);
+ } else {
+ // For full screen windows, use the desktop.
+ screen->GetRectDisplayPix(&screenRect.x, &screenRect.y, &screenRect.width,
+ &screenRect.height);
+ }
+
+ if (aAllowSlop) {
+ if (*aX < screenRect.x - logWidth + kWindowPositionSlop)
+ *aX = screenRect.x - logWidth + kWindowPositionSlop;
+ else if (*aX >= screenRect.XMost() - kWindowPositionSlop)
+ *aX = screenRect.XMost() - kWindowPositionSlop;
+
+ if (*aY < screenRect.y - logHeight + kWindowPositionSlop)
+ *aY = screenRect.y - logHeight + kWindowPositionSlop;
+ else if (*aY >= screenRect.YMost() - kWindowPositionSlop)
+ *aY = screenRect.YMost() - kWindowPositionSlop;
+ } else {
+ if (*aX < screenRect.x)
+ *aX = screenRect.x;
+ else if (*aX >= screenRect.XMost() - logWidth)
+ *aX = screenRect.XMost() - logWidth;
+
+ if (*aY < screenRect.y)
+ *aY = screenRect.y;
+ else if (*aY >= screenRect.YMost() - logHeight)
+ *aY = screenRect.YMost() - logHeight;
+ }
+}
+
+void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
+ mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
+ mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
+
+ ApplySizeConstraints();
+}
+
+void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) {
+ GtkBorder decorationSize = GetCSDDecorationSize(!mIsTopLevel);
+ *aWidth += decorationSize.left + decorationSize.right;
+ *aHeight += decorationSize.top + decorationSize.bottom;
+ }
+}
+
+void nsWindow::ApplySizeConstraints(void) {
+ 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(0, 0)) {
+ AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
+ hints |= GDK_HINT_MIN_SIZE;
+ LOG(("nsWindow::ApplySizeConstraints [%p] min size %d %d\n", (void*)this,
+ geometry.min_width, geometry.min_height));
+ }
+ if (mSizeConstraints.mMaxSize !=
+ LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
+ AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
+ hints |= GDK_HINT_MAX_SIZE;
+ LOG(("nsWindow::ApplySizeConstraints [%p] max size %d %d\n", (void*)this,
+ geometry.max_width, geometry.max_height));
+ }
+
+ if (mAspectRatio != 0.0f) {
+ 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;
+
+ // Clear our cached resources when the window is hidden.
+ if (mIsShown && !aState) {
+ ClearCachedResources();
+ }
+
+ mIsShown = aState;
+
+ LOG(("nsWindow::Show [%p] state %d\n", (void*)this, aState));
+
+ if (aState) {
+ // Now that this window is shown, mHasMappedToplevel needs to be
+ // tracked on viewable descendants.
+ SetHasMappedToplevel(mHasMappedToplevel);
+ }
+
+ // 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);
+}
+
+void nsWindow::ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove,
+ bool aRepaint) {
+ LOG(("nsWindow::ResizeInt [%p] x:%d y:%d -> w:%d h:%d repaint %d aMove %d\n",
+ (void*)this, aX, aY, aWidth, aHeight, aRepaint, aMove));
+
+ ConstrainSize(&aWidth, &aHeight);
+
+ LOG((" ConstrainSize: w:%d h;%d\n", aWidth, aHeight));
+
+ // If we used to have insane bounds, we may have skipped actually positioning
+ // the widget in NativeMoveResizeWaylandPopup, in which case we need to
+ // actually position it now as well.
+ const bool hadInsaneWaylandPopupDimensions =
+ !AreBoundsSane() && IsWaylandPopup();
+
+ if (aMove) {
+ mBounds.x = aX;
+ mBounds.y = aY;
+ }
+
+ // For top-level windows, aWidth and aHeight should possibly be
+ // interpreted as frame bounds, but NativeResize treats these as window
+ // bounds (Bug 581866).
+ mBounds.SizeTo(aWidth, aHeight);
+
+ // We set correct mBounds in advance here. This can be invalided by state
+ // event.
+ mBoundsAreValid = true;
+
+ // Recalculate aspect ratio when resized from DOM
+ if (mAspectRatio != 0.0) {
+ LockAspectRatio(true);
+ }
+
+ if (!mCreated) return;
+
+ if (aMove || mPreferredPopupRectFlushed || hadInsaneWaylandPopupDimensions) {
+ LOG((" Need also to move, flushed? %d, bounds were insane: %d\n",
+ mPreferredPopupRectFlushed, hadInsaneWaylandPopupDimensions));
+ NativeMoveResize();
+ } else {
+ NativeResize();
+ }
+
+ NotifyRollupGeometryChange();
+
+ // send a resize notification if this is a toplevel
+ if (mIsTopLevel || mListenForResizes) {
+ DispatchResized();
+ }
+}
+
+void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
+ LOG(("nsWindow::Resize [%p] %f %f\n", (void*)this, aWidth, aHeight));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+
+ ResizeInt(0, 0, width, height, /* aMove */ false, aRepaint);
+}
+
+void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) {
+ LOG(("nsWindow::Resize [%p] %f %f repaint %d\n", (void*)this, aWidth, aHeight,
+ aRepaint));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t width = NSToIntRound(scale * aWidth);
+ int32_t height = NSToIntRound(scale * aHeight);
+
+ int32_t x = NSToIntRound(scale * aX);
+ int32_t y = NSToIntRound(scale * aY);
+
+ ResizeInt(x, y, width, height, /* aMove */ true, aRepaint);
+}
+
+void nsWindow::Enable(bool aState) { mEnabled = aState; }
+
+bool nsWindow::IsEnabled() const { return mEnabled; }
+
+void nsWindow::Move(double aX, double aY) {
+ LOG(("nsWindow::Move [%p] %f %f\n", (void*)this, aX, aY));
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t x = NSToIntRound(aX * scale);
+ int32_t y = NSToIntRound(aY * scale);
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ SetSizeMode(nsSizeMode_Normal);
+ }
+
+ // 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.
+ if (x == mBounds.x && y == mBounds.y && mWindowType != eWindowType_popup)
+ return;
+
+ // XXX Should we do some AreBoundsSane check here?
+
+ mBounds.x = x;
+ mBounds.y = y;
+
+ if (!mCreated) return;
+
+ if (IsWaylandPopup()) {
+ int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ if (mPreferredPopupRect.x != mBounds.x * p2a &&
+ mPreferredPopupRect.y != mBounds.y * p2a) {
+ NativeMove();
+ NotifyRollupGeometryChange();
+ } else {
+ LOG((" mBounds same as mPreferredPopupRect, no need to move"));
+ }
+ } else {
+ NativeMove();
+ NotifyRollupGeometryChange();
+ }
+}
+
+bool nsWindow::IsPopup() {
+ return mIsTopLevel && mWindowType == eWindowType_popup;
+}
+
+bool nsWindow::IsWaylandPopup() { return !mIsX11Display && IsPopup(); }
+
+void nsWindow::HideWaylandTooltips() {
+ while (gVisibleWaylandPopupWindows) {
+ nsWindow* window =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ if (window->mPopupType != ePopupTypeTooltip) break;
+ LOG(("nsWindow::HideWaylandTooltips [%p] hidding tooltip [%p].\n",
+ (void*)this, window));
+ window->HideWaylandWindow();
+ }
+}
+
+void nsWindow::HideWaylandOpenedPopups() {
+ while (gVisibleWaylandPopupWindows) {
+ nsWindow* window =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ window->HideWaylandWindow();
+ }
+}
+
+// Hide popup nsWindows which are no longer in the nsXULPopupManager widget
+// chain list.
+void nsWindow::CleanupWaylandPopups() {
+ LOG(("nsWindow::CleanupWaylandPopups...\n"));
+ nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+ AutoTArray<nsIWidget*, 5> widgetChain;
+ pm->GetSubmenuWidgetChain(&widgetChain);
+ GList* popupList = gVisibleWaylandPopupWindows;
+ while (popupList) {
+ LOG((" Looking for %p [nsWindow]\n", popupList->data));
+ nsWindow* waylandWnd = static_cast<nsWindow*>(popupList->data);
+ // Remove only menu popups or empty frames - they are most likely
+ // already rolledup popups
+ if (waylandWnd->IsMainMenuWindow() || !waylandWnd->GetFrame()) {
+ bool popupFound = false;
+ for (unsigned long i = 0; i < widgetChain.Length(); i++) {
+ if (waylandWnd == widgetChain[i]) {
+ popupFound = true;
+ break;
+ }
+ }
+ if (!popupFound) {
+ LOG((" nsWindow [%p] not found in PopupManager, hiding it.\n",
+ waylandWnd));
+ waylandWnd->HideWaylandWindow();
+ popupList = gVisibleWaylandPopupWindows;
+ } else {
+ LOG((" nsWindow [%p] is still open.\n", waylandWnd));
+ popupList = popupList->next;
+ }
+ } else {
+ popupList = popupList->next;
+ }
+ }
+}
+
+static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
+ if (aFrame) {
+ nsMenuPopupFrame* menuPopupFrame = do_QueryFrame(aFrame);
+ return menuPopupFrame;
+ }
+ return nullptr;
+}
+
+// The MenuList popups are used as dropdown menus for example in WebRTC
+// microphone/camera chooser or autocomplete widgets.
+bool nsWindow::IsMainMenuWindow() {
+ nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
+ if (menuPopupFrame) {
+ LOG((" nsMenuPopupFrame [%p] type: %d IsMenu: %d, IsMenuList: %d\n",
+ menuPopupFrame, menuPopupFrame->PopupType(), menuPopupFrame->IsMenu(),
+ menuPopupFrame->IsMenuList()));
+ return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList();
+ }
+ return false;
+}
+
+GtkWindow* nsWindow::GetTopmostWindow() {
+ nsView* view = nsView::GetViewFor(this);
+ if (view) {
+ nsView* parentView = view->GetParent();
+ if (parentView) {
+ nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
+ if (parentWidget) {
+ nsWindow* parentnsWindow = static_cast<nsWindow*>(parentWidget);
+ LOG((" Topmost window: %p [nsWindow]\n", parentnsWindow));
+ return GTK_WINDOW(parentnsWindow->mShell);
+ }
+ }
+ }
+ return nullptr;
+}
+
+GtkWindow* nsWindow::GetCurrentWindow() {
+ GtkWindow* parentGtkWindow = nullptr;
+ // get the last opened window from gVisibleWaylandPopupWindows
+ if (gVisibleWaylandPopupWindows) {
+ nsWindow* parentnsWindow =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ if (parentnsWindow) {
+ LOG((" Setting parent to last opened window: %p [nsWindow]\n",
+ parentnsWindow));
+ parentGtkWindow = GTK_WINDOW(parentnsWindow->GetGtkWidget());
+ }
+ }
+ // get the topmost window if the last opened windows are empty
+ if (!parentGtkWindow) {
+ parentGtkWindow = GetTopmostWindow();
+ }
+ if (parentGtkWindow && GTK_IS_WINDOW(parentGtkWindow)) {
+ return GTK_WINDOW(parentGtkWindow);
+ } else {
+ LOG((" Failed to get current window for %p: %p\n", this, parentGtkWindow));
+ }
+ return nullptr;
+}
+
+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;
+}
+
+// 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.
+GtkWidget* nsWindow::ConfigureWaylandPopupWindows() {
+ MOZ_ASSERT(this->mWindowType == eWindowType_popup);
+ LOG(
+ ("nsWindow::ConfigureWaylandPopupWindows [%p], frame %p hasRemoteContent "
+ "%d\n",
+ (void*)this, this->GetFrame(), this->HasRemoteContent()));
+#if DEBUG
+ if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
+ nsCString nodeId;
+ this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
+ LOG((" [%p] popup node id=%s\n", this, nodeId.get()));
+ }
+#endif
+
+ if (!GetFrame()) {
+ LOG((" Window without frame cannot be configured.\n"));
+ return nullptr;
+ }
+
+ // Check if we're already configured.
+ if (gVisibleWaylandPopupWindows &&
+ g_list_find(gVisibleWaylandPopupWindows, this)) {
+ LOG((" [%p] is already configured.\n", (void*)this));
+ return GTK_WIDGET(gtk_window_get_transient_for(GTK_WINDOW(mShell)));
+ }
+
+ // If we're opening a new window we don't want to attach it to a tooltip
+ // as it's short lived temporary window.
+ HideWaylandTooltips();
+
+ // Cleanup already closed menus
+ CleanupWaylandPopups();
+
+ if (gVisibleWaylandPopupWindows &&
+ (HasRemoteContent() || IsWidgetOverflowWindow())) {
+ nsWindow* openedWindow =
+ static_cast<nsWindow*>(gVisibleWaylandPopupWindows->data);
+ LOG((" this [%p], lastOpenedWindow [%p]", this, openedWindow));
+ if (openedWindow != this) {
+ LOG(
+ (" Hiding all opened popups because the window is remote content or "
+ "overflow-widget"));
+ HideWaylandOpenedPopups();
+ }
+ }
+
+ GtkWindow* parentGtkWindow = GetCurrentWindow();
+ if (parentGtkWindow) {
+ MOZ_ASSERT(parentGtkWindow != GTK_WINDOW(this->GetGtkWidget()),
+ "Cannot set self as parent");
+ gtk_window_set_transient_for(GTK_WINDOW(mShell),
+ GTK_WINDOW(parentGtkWindow));
+ // Add current window to the visible popup list
+ gVisibleWaylandPopupWindows =
+ g_list_prepend(gVisibleWaylandPopupWindows, this);
+ LOG((" Parent window for %p: %p [GtkWindow]", this, parentGtkWindow));
+ }
+
+ MOZ_ASSERT(parentGtkWindow, "NO parent window for %p: expect popup glitches");
+ return GTK_WIDGET(parentGtkWindow);
+}
+
+static void NativeMoveResizeWaylandPopupCallback(
+ GdkWindow* window, const GdkRectangle* flipped_rect,
+ const GdkRectangle* final_rect, gboolean flipped_x, gboolean flipped_y,
+ void* aWindow) {
+ LOG(("NativeMoveResizeWaylandPopupCallback [%p] flipped_x %d flipped_y %d\n",
+ aWindow, flipped_x, flipped_y));
+
+ LOG((" flipped_rect x=%d y=%d width=%d height=%d\n", flipped_rect->x,
+ flipped_rect->y, flipped_rect->width, flipped_rect->height));
+ LOG((" final_rect x=%d y=%d width=%d height=%d\n", final_rect->x,
+ final_rect->y, final_rect->width, final_rect->height));
+ nsWindow* wnd = get_window_for_gdk_window(window);
+
+ wnd->NativeMoveResizeWaylandPopupCB(final_rect, flipped_x, flipped_y);
+}
+
+void nsWindow::NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
+ bool aFlippedX, bool aFlippedY) {
+ LOG((" orig mBounds x=%d y=%d width=%d height=%d\n", mBounds.x, mBounds.y,
+ mBounds.width, mBounds.height));
+
+ // Remove signal handler because it can also be called from
+ // xdg_popup_configure
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+ if (g_signal_handler_find(
+ gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) {
+ LOG((" Disconnecting NativeMoveResizeWaylandPopupCallback"));
+ g_signal_handlers_disconnect_by_func(
+ gdkWindow, FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this);
+ }
+ mWaitingForMoveToRectCB = false;
+
+ // We ignore the callback position data because the another resize has been
+ // called before the callback have been triggered.
+ if (mPendingSizeRect.height > 0 || mPendingSizeRect.width > 0) {
+ LOG(
+ (" Another resize called during waiting for callback, calling "
+ "Resize(%d, %d)\n",
+ mPendingSizeRect.width, mPendingSizeRect.height));
+ // Set the preferred size to zero to avoid wrong size of popup because the
+ // mPreferredPopupRect is used in nsMenuPopupFrame to set dimensions
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+
+ // We need to schedule another resize because the window has been resized
+ // again before callback was called.
+ Resize(mPendingSizeRect.width, mPendingSizeRect.height, true);
+ DispatchResized();
+ mPendingSizeRect.width = mPendingSizeRect.height = 0;
+ return;
+ }
+
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (!parentGtkWindow || !GTK_IS_WIDGET(parentGtkWindow)) {
+ NS_WARNING("Popup has no parent!");
+ return;
+ }
+
+ // The position of the menu in GTK is relative to it's parent window while
+ // in mBounds we have position relative to toplevel window. We need to check
+ // and update mBounds in the toplevel coordinates.
+ int x_parent, y_parent;
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)), &x_parent,
+ &y_parent);
+
+ LayoutDeviceIntRect newBounds(aFinalSize->x, aFinalSize->y, aFinalSize->width,
+ aFinalSize->height);
+
+ newBounds.x = GdkCoordToDevicePixels(newBounds.x);
+ newBounds.y = GdkCoordToDevicePixels(newBounds.y);
+
+ double scale =
+ BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
+ int32_t newWidth = NSToIntRound(scale * newBounds.width);
+ int32_t newHeight = NSToIntRound(scale * newBounds.height);
+
+ LOG((" new mBounds x=%d y=%d width=%d height=%d\n", newBounds.x,
+ newBounds.y, newWidth, newHeight));
+
+ bool needsPositionUpdate =
+ (newBounds.x != mBounds.x || newBounds.y != mBounds.y);
+ bool needsSizeUpdate =
+ (newWidth != mBounds.width || newHeight != mBounds.height);
+ // Update view
+
+ if (needsSizeUpdate) {
+ LOG((" needSizeUpdate\n"));
+ int32_t p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ mPreferredPopupRect = nsRect(NSIntPixelsToAppUnits(newBounds.x, p2a),
+ NSIntPixelsToAppUnits(newBounds.y, p2a),
+ NSIntPixelsToAppUnits(newBounds.width, p2a),
+ NSIntPixelsToAppUnits(newBounds.height, p2a));
+ mPreferredPopupRectFlushed = false;
+ Resize(newBounds.width, newBounds.height, true);
+ DispatchResized();
+
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+ if (popupFrame) {
+ RefPtr<PresShell> presShell = popupFrame->PresShell();
+ presShell->FrameNeedsReflow(popupFrame, IntrinsicDirty::Resize,
+ NS_FRAME_IS_DIRTY);
+ }
+ }
+
+ if (needsPositionUpdate) {
+ LOG((" needPositionUpdate\n"));
+ // The newBounds are in coordinates relative to the parent window/popup.
+ // The NotifyWindowMoved requires the coordinates relative to the toplevel.
+ // We use the gdk_window_get_origin to get correct coordinates.
+ gint x = 0, y = 0;
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(mShell)), &x, &y);
+ NotifyWindowMoved(GdkCoordToDevicePixels(x), GdkCoordToDevicePixels(y));
+ }
+}
+
+#ifdef MOZ_WAYLAND
+static GdkGravity PopupAlignmentToGdkGravity(int8_t aAlignment) {
+ switch (aAlignment) {
+ case POPUPALIGNMENT_NONE:
+ return GDK_GRAVITY_NORTH_WEST;
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ return GDK_GRAVITY_NORTH_WEST;
+ break;
+ case POPUPALIGNMENT_TOPRIGHT:
+ return GDK_GRAVITY_NORTH_EAST;
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ return GDK_GRAVITY_SOUTH_WEST;
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ return GDK_GRAVITY_SOUTH_EAST;
+ break;
+ case POPUPALIGNMENT_LEFTCENTER:
+ return GDK_GRAVITY_WEST;
+ break;
+ case POPUPALIGNMENT_RIGHTCENTER:
+ return GDK_GRAVITY_EAST;
+ break;
+ case POPUPALIGNMENT_TOPCENTER:
+ return GDK_GRAVITY_NORTH;
+ break;
+ case POPUPALIGNMENT_BOTTOMCENTER:
+ return GDK_GRAVITY_SOUTH;
+ break;
+ }
+ return GDK_GRAVITY_STATIC;
+}
+#endif
+
+void nsWindow::NativeMoveResizeWaylandPopup(GdkPoint* aPosition,
+ GdkRectangle* aSize) {
+ // 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");
+ LOG(("nsWindow::NativeMoveResizeWaylandPopup [%p]\n", (void*)this));
+
+ // 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", mBounds.width,
+ mBounds.height));
+ return;
+ }
+
+ if (aSize) {
+ gtk_window_resize(GTK_WINDOW(mShell), aSize->width, aSize->height);
+ }
+
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(mShell));
+
+ // Use standard gtk_window_move() instead of gdk_window_move_to_rect() when:
+ // - gdk_window_move_to_rect() is not available
+ // - the widget doesn't have a valid GdkWindow
+ if (!sGdkWindowMoveToRect || !gdkWindow) {
+ LOG((" use gtk_window_move(%d, %d)\n", aPosition->x, aPosition->y));
+ gtk_window_move(GTK_WINDOW(mShell), aPosition->x, aPosition->y);
+ return;
+ }
+
+ GtkWidget* parentWindow = ConfigureWaylandPopupWindows();
+ LOG(("nsWindow::NativeMoveResizeWaylandPopup: Set popup parent %p\n",
+ parentWindow));
+
+ // Get anchor rectangle
+ LayoutDeviceIntRect anchorRect(0, 0, 0, 0);
+ nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
+
+ int32_t p2a;
+ double devPixelsPerCSSPixel = StaticPrefs::layout_css_devPixelsPerPx();
+ if (devPixelsPerCSSPixel > 0.0) {
+ p2a = AppUnitsPerCSSPixel() / devPixelsPerCSSPixel * GdkScaleFactor();
+ } else {
+ p2a = AppUnitsPerCSSPixel() / gfxPlatformGtk::GetFontScaleFactor();
+ }
+ if (popupFrame) {
+#ifdef MOZ_WAYLAND
+ anchorRect = LayoutDeviceIntRect::FromAppUnitsToOutside(
+ popupFrame->GetAnchorRect(), p2a);
+#endif
+ }
+
+#ifdef MOZ_WAYLAND
+ bool hasAnchorRect = true;
+#endif
+ if (anchorRect.width == 0) {
+ LOG((" No anchor rect given, use aPosition for anchor"));
+ anchorRect.SetRect(aPosition->x, aPosition->y, 1, 1);
+#ifdef MOZ_WAYLAND
+ hasAnchorRect = false;
+#endif
+ }
+ LOG((" anchor x %d y %d width %d height %d (absolute coords)\n",
+ anchorRect.x, anchorRect.y, anchorRect.width, anchorRect.height));
+
+ // Anchor rect is in the toplevel coordinates but we need to transfer it to
+ // the coordinates relative to the popup parent for the
+ // gdk_window_move_to_rect
+ int x_parent = 0, y_parent = 0;
+ GtkWindow* parentGtkWindow = gtk_window_get_transient_for(GTK_WINDOW(mShell));
+ if (parentGtkWindow) {
+ GetWindowOrigin(gtk_widget_get_window(GTK_WIDGET(parentGtkWindow)),
+ &x_parent, &y_parent);
+ }
+ LOG((" x_parent %d y_parent %d\n", x_parent, y_parent));
+ anchorRect.MoveBy(-x_parent, -y_parent);
+ GdkRectangle rect = {anchorRect.x, anchorRect.y, anchorRect.width,
+ anchorRect.height};
+
+ // Get gravity and flip type
+ GdkGravity rectAnchor = GDK_GRAVITY_NORTH_WEST;
+ GdkGravity menuAnchor = GDK_GRAVITY_NORTH_WEST;
+ FlipType flipType = FlipType_Default;
+ int8_t position = -1;
+ if (popupFrame) {
+#ifdef MOZ_WAYLAND
+ rectAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAnchor());
+ menuAnchor = PopupAlignmentToGdkGravity(popupFrame->GetPopupAlignment());
+ flipType = popupFrame->GetFlipType();
+ position = popupFrame->GetAlignmentPosition();
+#endif
+ } else {
+ LOG((" NO ANCHOR INFO"));
+ if (GetTextDirection() == GTK_TEXT_DIR_RTL) {
+ rectAnchor = GDK_GRAVITY_NORTH_EAST;
+ menuAnchor = GDK_GRAVITY_NORTH_EAST;
+ }
+ }
+ LOG((" parentRect gravity: %d anchor gravity: %d\n", rectAnchor, menuAnchor));
+
+ GdkAnchorHints hints = GdkAnchorHints(GDK_ANCHOR_RESIZE);
+
+ // slideHorizontal from nsMenuPopupFrame::SetPopupPosition
+ 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);
+ }
+
+ if (popupFrame && 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 (!IsMainMenuWindow()) {
+ // we don't want to slide menus to fit the screen rather resize them
+ hints = GdkAnchorHints(hints | GDK_ANCHOR_SLIDE);
+ }
+
+ // A workaround for https://gitlab.gnome.org/GNOME/gtk/issues/1986
+ // gdk_window_move_to_rect() does not reposition visible windows.
+ static auto sGtkWidgetIsVisible =
+ (gboolean(*)(GtkWidget*))dlsym(RTLD_DEFAULT, "gtk_widget_is_visible");
+
+ bool isWidgetVisible =
+ (sGtkWidgetIsVisible != nullptr) && sGtkWidgetIsVisible(mShell);
+ if (isWidgetVisible) {
+ PauseRemoteRenderer();
+ gtk_widget_hide(mShell);
+ }
+
+ LOG((" requested rect: x: %d y: %d width: %d height: %d\n", rect.x, rect.y,
+ rect.width, rect.height));
+ if (aSize) {
+ LOG((" aSize: x%d y%d w%d h%d\n", aSize->x, aSize->y, aSize->width,
+ aSize->height));
+ } else {
+ LOG((" No aSize given"));
+ }
+
+ // Inspired by nsMenuPopupFrame::AdjustPositionForAnchorAlign
+ nsPoint cursorOffset(0, 0);
+#ifdef MOZ_WAYLAND
+ // Offset is already computed to the tooltips
+ if (hasAnchorRect && popupFrame && mPopupType != ePopupTypeTooltip) {
+ nsMargin margin(0, 0, 0, 0);
+ popupFrame->StyleMargin()->GetMargin(margin);
+ switch (popupFrame->GetPopupAlignment()) {
+ case POPUPALIGNMENT_TOPRIGHT:
+ cursorOffset.MoveBy(-margin.right, margin.top);
+ break;
+ case POPUPALIGNMENT_BOTTOMLEFT:
+ cursorOffset.MoveBy(margin.left, -margin.bottom);
+ break;
+ case POPUPALIGNMENT_BOTTOMRIGHT:
+ cursorOffset.MoveBy(-margin.right, -margin.bottom);
+ break;
+ case POPUPALIGNMENT_TOPLEFT:
+ default:
+ cursorOffset.MoveBy(margin.left, margin.top);
+ break;
+ }
+ }
+#endif
+
+ if (!g_signal_handler_find(
+ gdkWindow, G_SIGNAL_MATCH_FUNC, 0, 0, nullptr,
+ FuncToGpointer(NativeMoveResizeWaylandPopupCallback), this)) {
+ g_signal_connect(gdkWindow, "moved-to-rect",
+ G_CALLBACK(NativeMoveResizeWaylandPopupCallback), this);
+ }
+
+ LOG((" popup window cursor offset x: %d y: %d\n", cursorOffset.x / p2a,
+ cursorOffset.y / p2a));
+ mWaitingForMoveToRectCB = true;
+ sGdkWindowMoveToRect(gdkWindow, &rect, rectAnchor, menuAnchor, hints,
+ cursorOffset.x / p2a, cursorOffset.y / p2a);
+
+ if (isWidgetVisible) {
+ // We show the popup with the same configuration so no need to call
+ // ConfigureWaylandPopupWindows() before gtk_widget_show().
+ gtk_widget_show(mShell);
+ }
+}
+
+void nsWindow::NativeMove() {
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ LOG(("nsWindow::NativeMove [%p] %d %d\n", (void*)this, point.x, point.y));
+
+ if (IsWaylandPopup()) {
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ NativeMoveResizeWaylandPopup(&point, &size);
+ } else if (mIsTopLevel) {
+ gtk_window_move(GTK_WINDOW(mShell), point.x, point.y);
+ } else if (mGdkWindow) {
+ gdk_window_move(mGdkWindow, point.x, point.y);
+ }
+}
+
+void nsWindow::SetZIndex(int32_t aZIndex) {
+ nsIWidget* oldPrev = GetPrevSibling();
+
+ nsBaseWidget::SetZIndex(aZIndex);
+
+ if (GetPrevSibling() == oldPrev) {
+ return;
+ }
+
+ NS_ASSERTION(!mContainer, "Expected Mozilla child widget");
+
+ // We skip the nsWindows that don't have mGdkWindows.
+ // These are probably in the process of being destroyed.
+
+ 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 [%p] %d\n", (void*)this, aMode));
+
+ // Save the requested state.
+ nsBaseWidget::SetSizeMode(aMode);
+
+ // return if there's no shell or our current state is the same as
+ // the mode we were just set to.
+ if (!mShell || mSizeState == mSizeMode) {
+ LOG((" already set"));
+ return;
+ }
+
+ 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:
+ LOG((" set normal"));
+ // nsSizeMode_Normal, really.
+ if (mSizeState == nsSizeMode_Minimized)
+ gtk_window_deiconify(GTK_WINDOW(mShell));
+ else if (mSizeState == nsSizeMode_Maximized)
+ gtk_window_unmaximize(GTK_WINDOW(mShell));
+ break;
+ }
+
+ // Request mBounds update from configure event as we may not get
+ // OnSizeAllocate for size state changes (Bug 1489463).
+ mBoundsAreValid = false;
+
+ mSizeState = mSizeMode;
+}
+
+static bool GetWindowManagerName(GdkWindow* gdk_window, nsACString& wmName) {
+ if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ return false;
+ }
+
+ 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));
+
+ 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 false;
+ }
+ 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 false;
+ }
+
+ Window wmWindow = reinterpret_cast<Window*>(prop_return)[0];
+ if (!wmWindow) {
+ return false;
+ }
+
+ 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 false;
+ }
+ {
+ // Suppress fatal errors for a missing window.
+ ScopedXErrorHandler handler;
+ 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 false;
+ }
+
+ wmName = reinterpret_cast<const char*>(prop_return);
+ return true;
+}
+
+#define kDesktopMutterSchema "org.gnome.mutter"
+#define kDesktopDynamicWorkspacesKey "dynamic-workspaces"
+
+static bool WorkspaceManagementDisabled(GdkWindow* gdk_window) {
+ if (Preferences::GetBool("widget.disable-workspace-management", false)) {
+ return true;
+ }
+ if (Preferences::HasUserValue("widget.workspace-management")) {
+ return Preferences::GetBool("widget.workspace-management");
+ }
+
+ static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop && strstr(currentDesktop, "GNOME")) {
+ // 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(nsLiteralCString(kDesktopMutterSchema),
+ getter_AddRefs(mutterSettings));
+ if (mutterSettings) {
+ if (NS_SUCCEEDED(mutterSettings->GetBoolean(
+ nsLiteralCString(kDesktopDynamicWorkspacesKey),
+ &usesDynamicWorkspaces))) {
+ }
+ }
+ }
+ return usesDynamicWorkspaces;
+ }
+
+ // When XDG_CURRENT_DESKTOP is missing, try to get window manager name.
+ if (!currentDesktop) {
+ nsAutoCString wmName;
+ if (GetWindowManagerName(gdk_window, wmName)) {
+ if (wmName.EqualsLiteral("bspwm")) {
+ return true;
+ }
+ if (wmName.EqualsLiteral("i3")) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+void nsWindow::GetWorkspaceID(nsAString& workspaceID) {
+ workspaceID.Truncate();
+
+ if (!mIsX11Display || !mShell) {
+ return;
+ }
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ return;
+ }
+
+ if (WorkspaceManagementDisabled(gdk_window)) {
+ 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)) {
+ return;
+ }
+
+ workspaceID.AppendInt((int32_t)wm_desktop[0]);
+ g_free(wm_desktop);
+}
+
+void nsWindow::MoveToWorkspace(const nsAString& workspaceIDStr) {
+ nsresult rv = NS_OK;
+ int32_t workspaceID = workspaceIDStr.ToInteger(&rv);
+ if (NS_FAILED(rv) || !workspaceID || !mIsX11Display || !mShell) {
+ return;
+ }
+
+ // Get the gdk window for this widget.
+ GdkWindow* gdk_window = gtk_widget_get_window(mShell);
+ if (!gdk_window) {
+ 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);
+}
+
+typedef void (*SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp);
+
+static void SetUserTimeAndStartupIDForActivatedWindow(GtkWidget* aWindow) {
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (!GTKToolkit) return;
+
+ nsAutoCString desktopStartupID;
+ GTKToolkit->GetDesktopStartupID(&desktopStartupID);
+ if (desktopStartupID.IsEmpty()) {
+ // 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.
+ uint32_t timestamp = GTKToolkit->GetFocusTimestamp();
+ if (timestamp) {
+ gdk_window_focus(gtk_widget_get_window(aWindow), timestamp);
+ GTKToolkit->SetFocusTimestamp(0);
+ }
+ return;
+ }
+
+ gtk_window_set_startup_id(GTK_WINDOW(aWindow), desktopStartupID.get());
+
+ // 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
+ GTKToolkit->SetFocusTimestamp(0);
+ GTKToolkit->SetDesktopStartupID(""_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.
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ guint32 timestamp = GDK_IS_X11_DISPLAY(gdkDisplay)
+ ? gdk_x11_display_get_user_time(gdkDisplay)
+ : gtk_get_current_event_time();
+
+ if (sLastUserInputTime != GDK_CURRENT_TIME &&
+ TimestampIsNewerThan(sLastUserInputTime, timestamp)) {
+ return sLastUserInputTime;
+ }
+
+ return timestamp;
+}
+
+void nsWindow::SetFocus(Raise aRaise, mozilla::dom::CallerType aCallerType) {
+ // 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.
+
+ LOGFOCUS((" SetFocus %d [%p]\n", aRaise == Raise::Yes, (void*)this));
+
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ if (!owningWidget) return;
+
+ // Raise the window if someone passed in true and the prefs are
+ // set properly.
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(owningWidget);
+
+ if (gRaiseWindows && aRaise == Raise::Yes && toplevelWidget &&
+ !gtk_widget_has_focus(owningWidget) &&
+ !gtk_widget_has_focus(toplevelWidget)) {
+ GtkWidget* top_window = GetToplevelWidget();
+ if (top_window && (gtk_widget_get_visible(top_window))) {
+ gdk_window_show_unraised(gtk_widget_get_window(top_window));
+ // Unset the urgency hint if possible.
+ SetUrgencyHint(top_window, false);
+ }
+ }
+
+ RefPtr<nsWindow> owningWindow = get_window_for_gtk_widget(owningWidget);
+ if (!owningWindow) 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 (gRaiseWindows && owningWindow->mIsShown && owningWindow->mShell &&
+ !gtk_window_is_active(GTK_WINDOW(owningWindow->mShell))) {
+ if (!mIsX11Display &&
+ Preferences::GetBool("testing.browserTestHarness.running", false)) {
+ // Wayland does not support focus changes so we need to workaround it
+ // by window hide/show sequence but only when it's running in testsuite.
+ owningWindow->NativeShow(false);
+ owningWindow->NativeShow(true);
+ return;
+ }
+
+ uint32_t timestamp = GDK_CURRENT_TIME;
+
+ nsGTKToolkit* GTKToolkit = nsGTKToolkit::GetToolkit();
+ if (GTKToolkit) timestamp = GTKToolkit->GetFocusTimestamp();
+
+ LOGFOCUS((" requesting toplevel activation [%p]\n", (void*)this));
+ NS_ASSERTION(owningWindow->mWindowType != eWindowType_popup || mParent,
+ "Presenting an override-redirect window");
+ gtk_window_present_with_time(GTK_WINDOW(owningWindow->mShell), timestamp);
+
+ if (GTKToolkit) GTKToolkit->SetFocusTimestamp(0);
+ }
+ return;
+ }
+
+ // aRaise == No means that keyboard events should be dispatched from this
+ // widget.
+
+ // Ensure owningWidget is the focused GtkWidget within its toplevel window.
+ //
+ // For eWindowType_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(owningWidget)) {
+ // 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(owningWidget);
+ gBlockActivateEvent = false;
+ }
+
+ // If this is the widget that already has focus, return.
+ if (gFocusWindow == this) {
+ LOGFOCUS((" already have focus [%p]\n", (void*)this));
+ return;
+ }
+
+ // Set this window to be the focused child window
+ gFocusWindow = this;
+
+ if (mIMContext) {
+ mIMContext->OnFocusWindow(this);
+ }
+
+ LOGFOCUS((" widget now has focus in SetFocus() [%p]\n", (void*)this));
+}
+
+LayoutDeviceIntRect nsWindow::GetScreenBounds() {
+ LayoutDeviceIntRect rect;
+ if (mIsTopLevel && mContainer) {
+ // use the point including window decorations
+ gint x, y;
+ gdk_window_get_root_origin(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ &x, &y);
+ rect.MoveTo(GdkPointToDevicePixels({x, y}));
+ } else {
+ rect.MoveTo(WidgetToScreenOffset());
+ }
+ // 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.
+ rect.SizeTo(mBounds.Size());
+#if MOZ_LOGGING
+ gint scale = GdkScaleFactor();
+ LOG(("GetScreenBounds [%p] %d,%d -> %d x %d, unscaled %d,%d -> %d x %d\n",
+ this, 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::UpdateClientOffsetFromFrameExtents() {
+ AUTO_PROFILER_LABEL("nsWindow::UpdateClientOffsetFromFrameExtents", OTHER);
+
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT && mDrawInTitlebar) {
+ return;
+ }
+
+ if (!mIsTopLevel || !mShell ||
+ gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
+ mClientOffset = nsIntPoint(0, 0);
+ return;
+ }
+
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+
+ GdkAtom type_returned;
+ int format_returned;
+ int length_returned;
+ long* frame_extents;
+
+ if (!gdk_property_get(gtk_widget_get_window(mShell),
+ gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE),
+ cardinal_atom,
+ 0, // offset
+ 4 * 4, // length
+ FALSE, // delete
+ &type_returned, &format_returned, &length_returned,
+ (guchar**)&frame_extents) ||
+ length_returned / sizeof(glong) != 4) {
+ mClientOffset = nsIntPoint(0, 0);
+ } else {
+ // data returned is in the order left, right, top, bottom
+ auto left = int32_t(frame_extents[0]);
+ auto top = int32_t(frame_extents[2]);
+ g_free(frame_extents);
+
+ mClientOffset = nsIntPoint(left, top);
+ }
+
+ // 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);
+
+ LOG(("nsWindow::UpdateClientOffsetFromFrameExtents [%p] %d,%d\n", (void*)this,
+ mClientOffset.x, mClientOffset.y));
+}
+
+LayoutDeviceIntPoint nsWindow::GetClientOffset() {
+ return mIsX11Display ? LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset)
+ : LayoutDeviceIntPoint(0, 0);
+}
+
+gboolean nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget,
+ GdkEventProperty* aEvent) {
+ if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) {
+ UpdateClientOffsetFromFrameExtents();
+ return FALSE;
+ }
+
+ if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) {
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+static GdkCursor* GetCursorForImage(imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ if (!aCursorImage) {
+ return nullptr;
+ }
+ GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursorImage);
+ if (!pixbuf) {
+ return nullptr;
+ }
+
+ int width = gdk_pixbuf_get_width(pixbuf);
+ int height = gdk_pixbuf_get_height(pixbuf);
+
+ auto CleanupPixBuf =
+ mozilla::MakeScopeExit([&]() { g_object_unref(pixbuf); });
+
+ // 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 (width > 128 || height > 128) {
+ 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)) {
+ GdkPixbuf* alphaBuf = gdk_pixbuf_add_alpha(pixbuf, FALSE, 0, 0, 0);
+ g_object_unref(pixbuf);
+ pixbuf = alphaBuf;
+ if (!alphaBuf) {
+ return nullptr;
+ }
+ }
+
+ return gdk_cursor_new_from_pixbuf(gdk_display_get_default(), pixbuf,
+ aHotspotX, aHotspotY);
+}
+
+void nsWindow::SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursorImage,
+ uint32_t aHotspotX, uint32_t aHotspotY) {
+ // if we're not the toplevel window pass up the cursor request to
+ // the toplevel window to handle it.
+ if (!mContainer && mGdkWindow) {
+ nsWindow* window = GetContainerWindow();
+ if (!window) return;
+
+ window->SetCursor(aDefaultCursor, aCursorImage, aHotspotX, aHotspotY);
+ return;
+ }
+
+ // Only change cursor if it's actually been changed
+ if (!aCursorImage && aDefaultCursor == mCursor && !mUpdateCursor) {
+ return;
+ }
+
+ mUpdateCursor = false;
+ mCursor = eCursorInvalid;
+
+ // Try to set the cursor image first, and fall back to the numeric cursor.
+ GdkCursor* newCursor = GetCursorForImage(aCursorImage, aHotspotX, aHotspotY);
+ if (!newCursor) {
+ newCursor = get_gtk_cursor(aDefaultCursor);
+ if (newCursor) {
+ mCursor = aDefaultCursor;
+ }
+ }
+
+ auto CleanupCursor = mozilla::MakeScopeExit([&]() {
+ // get_gtk_cursor returns a weak reference, which we shouldn't unref.
+ if (newCursor && mCursor == eCursorInvalid) {
+ g_object_unref(newCursor);
+ }
+ });
+
+ if (!newCursor || !mContainer) {
+ return;
+ }
+
+ gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)),
+ newCursor);
+}
+
+void nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) {
+ if (!mGdkWindow) return;
+
+ GdkRectangle rect = DevicePixelsToGdkRectRoundOut(aRect);
+ gdk_window_invalidate_rect(mGdkWindow, &rect, FALSE);
+
+ LOGDRAW(("Invalidate (rect) [%p]: %d %d %d %d\n", (void*)this, rect.x, rect.y,
+ rect.width, rect.height));
+}
+
+void* nsWindow::GetNativeData(uint32_t aDataType) {
+ switch (aDataType) {
+ case NS_NATIVE_WINDOW:
+ case NS_NATIVE_WIDGET: {
+ if (!mGdkWindow) return nullptr;
+
+ return mGdkWindow;
+ }
+
+ case NS_NATIVE_DISPLAY: {
+#ifdef MOZ_X11
+ GdkDisplay* gdkDisplay = gdk_display_get_default();
+ if (gdkDisplay && GDK_IS_X11_DISPLAY(gdkDisplay)) {
+ return GDK_DISPLAY_XDISPLAY(gdkDisplay);
+ }
+#endif /* MOZ_X11 */
+ // Don't bother to return native display on Wayland as it's for
+ // X11 only NPAPI plugins.
+ return nullptr;
+ }
+ case NS_NATIVE_SHELLWIDGET:
+ return GetToplevelWidget();
+
+ case NS_NATIVE_WINDOW_WEBRTC_DEVICE_ID:
+ case NS_NATIVE_SHAREABLE_WINDOW:
+ if (mIsX11Display) {
+ return (void*)GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow));
+ }
+ NS_WARNING(
+ "nsWindow::GetNativeData(): "
+ "NS_NATIVE_SHAREABLE_WINDOW / 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: {
+ if (mIsX11Display) {
+ return mGdkWindow ? (void*)GDK_WINDOW_XID(mGdkWindow) : nullptr;
+ }
+#ifdef MOZ_WAYLAND
+ if (mContainer) {
+ return moz_container_wayland_get_egl_window(mContainer,
+ GdkScaleFactor());
+ }
+#endif
+ return nullptr;
+ }
+ 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());
+ }
+}
+
+LayoutDeviceIntPoint nsWindow::WidgetToScreenOffset() {
+ nsIntPoint origin(0, 0);
+ GetWindowOrigin(mGdkWindow, &origin.x, &origin.y);
+
+ return GdkPointToDevicePixels({origin.x, origin.y});
+}
+
+void nsWindow::CaptureMouse(bool aCapture) {
+ LOG(("CaptureMouse %p\n", (void*)this));
+
+ if (!mGdkWindow) return;
+
+ if (!mContainer) return;
+
+ if (aCapture) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ } else {
+ ReleaseGrabs();
+ gtk_grab_remove(GTK_WIDGET(mContainer));
+ }
+}
+
+void nsWindow::CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) {
+ if (!mGdkWindow) return;
+
+ if (!mContainer) return;
+
+ LOG(("CaptureRollupEvents %p %i\n", this, int(aDoCapture)));
+
+ if (aDoCapture) {
+ gRollupListener = aListener;
+ // Don't add a grab if a drag is in progress, or if the widget is a drag
+ // feedback popup. (panels with type="drag").
+ if (!mIsDragPopup && !nsWindow::DragInProgress()) {
+ gtk_grab_add(GTK_WIDGET(mContainer));
+ GrabPointer(GetLastUserInputTime());
+ }
+ } else {
+ if (!nsWindow::DragInProgress()) {
+ ReleaseGrabs();
+ }
+ // 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));
+ gRollupListener = nullptr;
+ }
+}
+
+nsresult nsWindow::GetAttention(int32_t aCycleCount) {
+ LOG(("nsWindow::GetAttention [%p]\n", (void*)this));
+
+ 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 (mIsX11Display) {
+ 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;
+}
+
+#if 0
+# ifdef DEBUG
+// Paint flashing code (disabled for cairo - see below)
+
+# define CAPS_LOCK_IS_ON \
+ (KeymapWrapper::AreModifiersCurrentlyActive(KeymapWrapper::CAPS_LOCK))
+
+# define WANT_PAINT_FLASHING (debug_WantPaintFlashing() && CAPS_LOCK_IS_ON)
+
+# ifdef MOZ_X11
+static void
+gdk_window_flash(GdkWindow * aGdkWindow,
+ unsigned int aTimes,
+ unsigned int aInterval, // Milliseconds
+ GdkRegion * aRegion)
+{
+ gint x;
+ gint y;
+ gint width;
+ gint height;
+ guint i;
+ GdkGC * gc = 0;
+ GdkColor white;
+
+ gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height);
+
+ gdk_window_get_origin (aGdkWindow,
+ &x,
+ &y);
+
+ gc = gdk_gc_new(gdk_get_default_root_window());
+
+ white.pixel = WhitePixel(gdk_display,DefaultScreen(gdk_display));
+
+ gdk_gc_set_foreground(gc,&white);
+ gdk_gc_set_function(gc,GDK_XOR);
+ gdk_gc_set_subwindow(gc,GDK_INCLUDE_INFERIORS);
+
+ gdk_region_offset(aRegion, x, y);
+ gdk_gc_set_clip_region(gc, aRegion);
+
+ /*
+ * Need to do this twice so that the XOR effect can replace
+ * the original window contents.
+ */
+ for (i = 0; i < aTimes * 2; i++)
+ {
+ gdk_draw_rectangle(gdk_get_default_root_window(),
+ gc,
+ TRUE,
+ x,
+ y,
+ width,
+ height);
+
+ gdk_flush();
+
+ PR_Sleep(PR_MillisecondsToInterval(aInterval));
+ }
+
+ gdk_gc_destroy(gc);
+
+ gdk_region_offset(aRegion, -x, -y);
+}
+# endif /* MOZ_X11 */
+# endif // DEBUG
+#endif
+
+#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(r.x, r.y, r.width, r.height));
+ LOGDRAW(("\t%f %f %f %f\n", r.x, r.y, r.width, r.height));
+ }
+
+ cairo_rectangle_list_destroy(rects);
+ return true;
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::MaybeResumeCompositor() {
+ MOZ_RELEASE_ASSERT(NS_IsMainThread());
+
+ if (mIsDestroyed || !mNeedsCompositorResume) {
+ return;
+ }
+
+ if (CompositorBridgeChild* remoteRenderer = GetRemoteRenderer()) {
+ MOZ_ASSERT(mCompositorWidgetDelegate);
+ if (mCompositorWidgetDelegate) {
+ mCompositorInitiallyPaused = false;
+ mNeedsCompositorResume = false;
+ remoteRenderer->SendResumeAsync();
+ }
+ remoteRenderer->SendForcePresent();
+ }
+}
+
+void nsWindow::CreateCompositorVsyncDispatcher() {
+ if (!mWaylandVsyncSource) {
+ nsBaseWidget::CreateCompositorVsyncDispatcher();
+ return;
+ }
+
+ if (XRE_IsParentProcess()) {
+ if (!mCompositorVsyncDispatcherLock) {
+ mCompositorVsyncDispatcherLock =
+ MakeUnique<Mutex>("mCompositorVsyncDispatcherLock");
+ }
+ MutexAutoLock lock(*mCompositorVsyncDispatcherLock);
+ if (!mCompositorVsyncDispatcher) {
+ mCompositorVsyncDispatcher =
+ new CompositorVsyncDispatcher(mWaylandVsyncSource);
+ }
+ }
+}
+#endif
+
+gboolean nsWindow::OnExposeEvent(cairo_t* cr) {
+ // Send any pending resize events so that layout can update.
+ // May run event loop.
+ 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 (!mIsX11Display && !moz_container_wayland_can_draw(mContainer)) {
+ return FALSE;
+ }
+#endif
+
+ nsIWidgetListener* listener = GetListener();
+ if (!listener) return FALSE;
+
+ LOGDRAW(("received expose event [%p] %p 0x%lx (rects follow):\n", this,
+ mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0));
+ LayoutDeviceIntRegion exposeRegion;
+ if (!ExtractExposeRegion(exposeRegion, cr)) {
+ return FALSE;
+ }
+
+ gint scale = GdkScaleFactor();
+ LayoutDeviceIntRegion region = exposeRegion;
+ region.ScaleRoundOut(scale, scale);
+
+ if (GetLayerManager()->AsKnowsCompositor() && mCompositorSession) {
+ // 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.
+ GetLayerManager()->SetNeedsComposite(true);
+ GetLayerManager()->SendInvalidRegion(region.ToUnknownRegion());
+ }
+
+ RefPtr<nsWindow> strongThis(this);
+
+ // Dispatch WillPaintWindow notification to allow scripts etc. to run
+ // before we paint
+ {
+ listener->WillPaintWindow(this);
+
+ // If the window has been destroyed during the will paint notification,
+ // there is nothing left to do.
+ if (!mGdkWindow) return TRUE;
+
+ // Re-get the listener since the will paint notification might have
+ // killed it.
+ listener = GetListener();
+ if (!listener) return FALSE;
+ }
+
+ if (GetLayerManager()->AsKnowsCompositor() &&
+ GetLayerManager()->NeedsComposite()) {
+ GetLayerManager()->ScheduleComposite();
+ GetLayerManager()->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 (eTransparencyTransparent == GetTransparencyMode()) {
+ auto window = static_cast<nsWindow*>(GetTopLevelWidget());
+ if (mTransparencyBitmapForTitlebar) {
+ if (mSizeState == 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 (!shaped) {
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+ nsWindow* kid = get_window_for_gdk_window(gdkWin);
+ if (kid && gdk_window_is_visible(gdkWin)) {
+ AutoTArray<LayoutDeviceIntRect, 1> clipRects;
+ kid->GetWindowClipRegion(&clipRects);
+ LayoutDeviceIntRect bounds = kid->GetBounds();
+ for (uint32_t i = 0; i < clipRects.Length(); ++i) {
+ LayoutDeviceIntRect r = clipRects[i] + bounds.TopLeft();
+ region.Sub(region, r);
+ }
+ }
+ children = children->next;
+ }
+ }
+
+ if (region.IsEmpty()) {
+ return TRUE;
+ }
+
+ // If this widget uses OMTC...
+ if (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_CLIENT ||
+ GetLayerManager()->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;
+ }
+ RefPtr<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 = gfxContext::CreatePreservingTransformOrNull(destDT);
+ } else {
+ gfxUtils::ClipToRegion(dt, region.ToUnknownRegion());
+ ctx = gfxContext::CreatePreservingTransformOrNull(dt);
+ }
+ MOZ_ASSERT(ctx); // checked both dt and destDT valid draw target above
+
+# 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 (GetLayerManager()->GetBackendType() == LayersBackend::LAYERS_BASIC) {
+ if (GetTransparencyMode() == eTransparencyTransparent &&
+ 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, 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 = nullptr;
+ 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.
+
+ LOG(("configure event [%p] %d %d %d %d\n", (void*)this, aEvent->x, aEvent->y,
+ aEvent->width, aEvent->height));
+
+ if (mPendingConfigures > 0) {
+ mPendingConfigures--;
+ }
+
+ LayoutDeviceIntRect screenBounds = GetScreenBounds();
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ // This check avoids unwanted rollup on spurious configure events from
+ // Cygwin/X (bug 672103).
+ if (mBounds.x != screenBounds.x || mBounds.y != screenBounds.y) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+ NS_ASSERTION(GTK_IS_WINDOW(aWidget),
+ "Configure event on widget that is not a GtkWindow");
+ if (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.
+ GetLayerManager()->FlushRendering();
+ return FALSE;
+ }
+
+ mBounds.MoveTo(screenBounds.TopLeft());
+
+ // XXX mozilla will invalidate the entire window after this move
+ // complete. wtf?
+ NotifyWindowMoved(mBounds.x, mBounds.y);
+
+ // A GTK app would usually update its client area size in response to
+ // a "size-allocate" signal.
+ // However, we need to set mBounds in advance at Resize()
+ // as JS code expects immediate window size change.
+ // If Gecko requests a resize from GTK, but subsequently,
+ // before a corresponding "size-allocate" signal is emitted, the window is
+ // resized to its former size via other means, such as maximizing,
+ // then there is no "size-allocate" signal from which to update
+ // the value of mBounds. Similarly, if Gecko's resize request is refused
+ // by the window manager, then there will be no "size-allocate" signal.
+ // In the refused request case, the window manager is required to dispatch
+ // a ConfigureNotify event. mBounds can then be updated here.
+ // This seems to also be sufficient to update mBounds when Gecko resizes
+ // the window from maximized size and then immediately maximizes again.
+ if (!mBoundsAreValid) {
+ GtkAllocation allocation = {-1, -1, 0, 0};
+ gtk_window_get_size(GTK_WINDOW(mShell), &allocation.width,
+ &allocation.height);
+ OnSizeAllocate(&allocation);
+ }
+
+ return FALSE;
+}
+
+void nsWindow::OnContainerUnrealize() {
+ // The GdkWindows are about to be destroyed (but not deleted), so remove
+ // their references back to their container widget while the GdkWindow
+ // hierarchy is still available.
+
+ if (mGdkWindow) {
+ DestroyChildWindows();
+
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", nullptr);
+ mGdkWindow = nullptr;
+ }
+}
+
+void nsWindow::OnSizeAllocate(GtkAllocation* aAllocation) {
+ LOG(("nsWindow::OnSizeAllocate [%p] %d,%d -> %d x %d\n", (void*)this,
+ 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.
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) {
+ UpdateClientOffsetFromCSDWindow();
+ }
+ }
+
+ mBoundsAreValid = true;
+
+ LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size();
+ if (mBounds.Size() == size) {
+ LOG((" Already the same size"));
+ // We were already resized at nsWindow::OnConfigureEvent() so skip it.
+ 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 (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);
+ }
+
+ mBounds.SizeTo(size);
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // 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.
+ mNeedsDispatchResized = true;
+ NS_DispatchToCurrentThread(NewRunnableMethod(
+ "nsWindow::MaybeDispatchResized", this, &nsWindow::MaybeDispatchResized));
+}
+
+void nsWindow::OnDeleteEvent() {
+ if (mWidgetListener) mWidgetListener->RequestWindowClose(this);
+}
+
+void nsWindow::OnEnterNotifyEvent(GdkEventCrossing* aEvent) {
+ // 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 != nullptr) return;
+
+ // Check before is_parent_ungrab_enter() as the button state may have
+ // changed while a non-Gecko ancestor window had a pointer grab.
+ DispatchMissedButtonReleases(aEvent);
+
+ if (is_parent_ungrab_enter(aEvent)) return;
+
+ WidgetMouseEvent event(true, eMouseEnterIntoWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ LOG(("OnEnterNotify: %p\n", (void*)this));
+
+ DispatchInputEvent(&event);
+}
+
+// XXX Is this the right test for embedding cases?
+static bool is_top_level_mouse_exit(GdkWindow* aWindow,
+ GdkEventCrossing* aEvent) {
+ auto x = gint(aEvent->x_root);
+ auto y = gint(aEvent->y_root);
+ GdkDisplay* display = gdk_window_get_display(aWindow);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (!winAtPt) return true;
+ GdkWindow* topLevelAtPt = gdk_window_get_toplevel(winAtPt);
+ GdkWindow* topLevelWidget = gdk_window_get_toplevel(aWindow);
+ return topLevelAtPt != topLevelWidget;
+}
+
+void nsWindow::OnLeaveNotifyEvent(GdkEventCrossing* aEvent) {
+ // 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 != nullptr) return;
+
+ WidgetMouseEvent event(true, eMouseExitFromWidget, this,
+ WidgetMouseEvent::eReal);
+
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ event.mExitFrom = Some(is_top_level_mouse_exit(mGdkWindow, aEvent)
+ ? WidgetMouseEvent::ePlatformTopLevel
+ : WidgetMouseEvent::ePlatformChild);
+
+ LOG(("OnLeaveNotify: %p\n", (void*)this));
+
+ DispatchInputEvent(&event);
+}
+
+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::OnMotionNotifyEvent(GdkEventMotion* aEvent) {
+ if (mWindowShouldStartDragging) {
+ mWindowShouldStartDragging = false;
+ // find the top-level window
+ GdkWindow* gdk_window = gdk_window_get_toplevel(mGdkWindow);
+ MOZ_ASSERT(gdk_window, "gdk_window_get_toplevel should not return null");
+
+ bool canDrag = true;
+ if (mIsX11Display) {
+ // 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(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ canDrag = false;
+ }
+ }
+
+ if (canDrag) {
+ gdk_window_begin_move_drag(gdk_window, 1, aEvent->x_root, aEvent->y_root,
+ aEvent->time);
+ return;
+ }
+ }
+
+ // see if we can compress this event
+ // XXXldb Why skip every other motion event when we have multiple,
+ // but not more than that?
+ bool synthEvent = false;
+#ifdef MOZ_X11
+ XEvent xevent;
+
+ if (mIsX11Display) {
+ while (XPending(GDK_WINDOW_XDISPLAY(aEvent->window))) {
+ XEvent peeked;
+ XPeekEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &peeked);
+ if (peeked.xany.window != gdk_x11_window_get_xid(aEvent->window) ||
+ peeked.type != MotionNotify)
+ break;
+
+ synthEvent = true;
+ XNextEvent(GDK_WINDOW_XDISPLAY(aEvent->window), &xevent);
+ }
+ }
+#endif /* MOZ_X11 */
+
+ 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;
+
+ guint modifierState;
+ if (synthEvent) {
+#ifdef MOZ_X11
+ event.mRefPoint.x = nscoord(xevent.xmotion.x);
+ event.mRefPoint.y = nscoord(xevent.xmotion.y);
+
+ modifierState = xevent.xmotion.state;
+
+ event.AssignEventTime(GetWidgetEventTime(xevent.xmotion.time));
+#else
+ event.mRefPoint = GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+#endif /* MOZ_X11 */
+ } else {
+ event.mRefPoint = GetRefPoint(this, aEvent);
+
+ modifierState = aEvent->state;
+
+ event.AssignEventTime(GetWidgetEventTime(aEvent->time));
+ }
+
+ KeymapWrapper::InitInputEvent(event, modifierState);
+
+ 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 on %p\n", guint(buttonType + 1),
+ (void*)this));
+
+ // 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) {
+ aEvent.mRefPoint = GetRefPoint(this, aGdkEvent);
+
+ 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) {
+ if (domButton == MouseButton::eSecondary && MOZ_LIKELY(!mIsDestroyed)) {
+ WidgetMouseEvent contextMenuEvent(true, eContextMenu, this,
+ WidgetMouseEvent::eReal);
+ InitButtonEvent(contextMenuEvent, aEvent);
+ contextMenuEvent.mPressure = mLastMotionPressure;
+ DispatchInputEvent(&contextMenuEvent);
+ }
+}
+
+void nsWindow::OnButtonPressEvent(GdkEventButton* aEvent) {
+ LOG(("Button %u press on %p\n", aEvent->button, (void*)this));
+
+ // 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.
+ GdkEvent* peekedEvent = gdk_event_peek();
+ if (peekedEvent) {
+ GdkEventType type = peekedEvent->any.type;
+ gdk_event_free(peekedEvent);
+ if (type == GDK_2BUTTON_PRESS || type == GDK_3BUTTON_PRESS) return;
+ }
+
+ nsWindow* containerWindow = GetContainerWindow();
+ if (!gFocusWindow && containerWindow) {
+ containerWindow->DispatchActivateEvent();
+ }
+
+ // check to see if we should rollup
+ if (CheckForRollup(aEvent->x_root, aEvent->y_root, false, false)) 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 to back/forward
+ case 8:
+ if (!Preferences::GetBool("mousebutton.4th.enabled", true)) {
+ return;
+ }
+ DispatchCommandEvent(nsGkAtoms::Back);
+ return;
+ case 9:
+ 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);
+ event.mPressure = mLastMotionPressure;
+
+ nsEventStatus eventStatus = DispatchInputEvent(&event);
+
+ LayoutDeviceIntPoint refPoint =
+ GdkEventCoordsToDevicePixels(aEvent->x, aEvent->y);
+ if (mDraggableRegion.Contains(refPoint.x, refPoint.y) &&
+ domButton == MouseButton::ePrimary &&
+ eventStatus != nsEventStatus_eConsumeNoDefault) {
+ mWindowShouldStartDragging = true;
+ }
+
+ // right menu click on linux should also pop up a context menu
+ if (!StaticPrefs::ui_context_menus_after_mouseup()) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
+ }
+}
+
+void nsWindow::OnButtonReleaseEvent(GdkEventButton* aEvent) {
+ LOG(("Button %u release on %p\n", aEvent->button, (void*)this));
+
+ 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);
+
+ WidgetMouseEvent event(true, eMouseUp, this, WidgetMouseEvent::eReal);
+ event.mButton = domButton;
+ InitButtonEvent(event, aEvent);
+ gdouble pressure = 0;
+ gdk_event_get_axis((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure);
+ event.mPressure = pressure ? pressure : mLastMotionPressure;
+
+ // The mRefPoint is manipulated in DispatchInputEvent, we're saving it
+ // to use it for the doubleclick position check.
+ LayoutDeviceIntPoint pos = event.mRefPoint;
+
+ nsEventStatus eventStatus = DispatchInputEvent(&event);
+
+ bool defaultPrevented = (eventStatus == nsEventStatus_eConsumeNoDefault);
+ // Check if mouse position in titlebar and doubleclick happened to
+ // trigger restore/maximize.
+ if (!defaultPrevented && mDrawInTitlebar &&
+ event.mButton == MouseButton::ePrimary && event.mClickCount == 2 &&
+ mDraggableRegion.Contains(pos.x, pos.y)) {
+ if (mSizeState == nsSizeMode_Maximized) {
+ SetSizeMode(nsSizeMode_Normal);
+ } else {
+ SetSizeMode(nsSizeMode_Maximized);
+ }
+ }
+ mLastMotionPressure = pressure;
+
+ // right menu click on linux should also pop up a context menu
+ if (StaticPrefs::ui_context_menus_after_mouseup()) {
+ DispatchContextMenuEventFromMouseEvent(domButton, aEvent);
+ }
+
+ // Open window manager menu on PIP window to allow user
+ // to place it on top / all workspaces.
+ if (mIsPIPWindow && aEvent->button == 3) {
+ static auto sGdkWindowShowWindowMenu =
+ (gboolean(*)(GdkWindow * window, GdkEvent*))
+ dlsym(RTLD_DEFAULT, "gdk_window_show_window_menu");
+ if (sGdkWindowShowWindowMenu) {
+ sGdkWindowShowWindowMenu(mGdkWindow, (GdkEvent*)aEvent);
+ }
+ }
+}
+
+void nsWindow::OnContainerFocusInEvent(GdkEventFocus* aEvent) {
+ LOGFOCUS(("OnContainerFocusInEvent [%p]\n", (void*)this));
+
+ // 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) {
+ LOGFOCUS(("activated notification is blocked [%p]\n", (void*)this));
+ 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;
+ }
+
+ LOGFOCUS(("Events sent from focus in event [%p]\n", (void*)this));
+}
+
+void nsWindow::OnContainerFocusOutEvent(GdkEventFocus* aEvent) {
+ LOGFOCUS(("OnContainerFocusOutEvent [%p]\n", (void*)this));
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+ nsCOMPtr<nsIDragSession> dragSession;
+ dragService->GetCurrentSession(getter_AddRefs(dragSession));
+
+ // Rollup popups 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.
+ bool shouldRollup = !dragSession;
+ if (!shouldRollup) {
+ // we also roll up when a drag is from a different application
+ nsCOMPtr<nsINode> sourceNode;
+ dragSession->GetSourceNode(getter_AddRefs(sourceNode));
+ shouldRollup = (sourceNode == nullptr);
+ }
+
+ if (shouldRollup) {
+ CheckForRollup(0, 0, false, true);
+ }
+ }
+
+ 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();
+ }
+
+ LOGFOCUS(("Done with container focus out [%p]\n", (void*)this));
+}
+
+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(aEventTime, 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 (!mIsX11Display) {
+ // 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 {
+ CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter();
+ MOZ_ASSERT(getCurrentTime,
+ "Null current time getter despite having a window");
+ eventTimeStamp =
+ TimeConverter().GetTimeStampFromSystemTime(aEventTime, *getCurrentTime);
+ }
+ return eventTimeStamp;
+}
+
+mozilla::CurrentX11TimeGetter* nsWindow::GetCurrentTimeGetter() {
+ MOZ_ASSERT(mGdkWindow, "Expected mGdkWindow to be set");
+ if (MOZ_UNLIKELY(!mCurrentTimeGetter)) {
+ mCurrentTimeGetter = MakeUnique<CurrentX11TimeGetter>(mGdkWindow);
+ }
+ return mCurrentTimeGetter.get();
+}
+
+gboolean nsWindow::OnKeyPressEvent(GdkEventKey* aEvent) {
+ LOGFOCUS(("OnKeyPressEvent [%p]\n", (void*)this));
+
+ RefPtr<nsWindow> self(this);
+ KeymapWrapper::HandleKeyPressEvent(self, aEvent);
+ return TRUE;
+}
+
+gboolean nsWindow::OnKeyReleaseEvent(GdkEventKey* aEvent) {
+ LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void*)this));
+
+ RefPtr<nsWindow> self(this);
+ if (NS_WARN_IF(!KeymapWrapper::HandleKeyReleaseEvent(self, aEvent))) {
+ return FALSE;
+ }
+ return TRUE;
+}
+
+void nsWindow::OnScrollEvent(GdkEventScroll* aEvent) {
+ // 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) {
+ if (StaticPrefs::apz_gtk_kinetic_scroll_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));
+ PanGestureInput::PanGestureType eventType =
+ PanGestureInput::PANGESTURE_PAN;
+ if (sGdkEventIsScrollStopEvent((GdkEvent*)aEvent)) {
+ eventType = PanGestureInput::PANGESTURE_END;
+ mPanInProgress = false;
+ } else if (!mPanInProgress) {
+ eventType = PanGestureInput::PANGESTURE_START;
+ mPanInProgress = true;
+ }
+
+ LayoutDeviceIntPoint touchPoint = GetRefPoint(this, aEvent);
+ PanGestureInput panEvent(
+ eventType, aEvent->time, GetEventTimeStamp(aEvent->time),
+ ScreenPoint(touchPoint.x, touchPoint.y),
+ ScreenPoint(aEvent->delta_x, aEvent->delta_y),
+ KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+ panEvent.mDeltaType = PanGestureInput::PANDELTA_PAGE;
+ panEvent.mSimulateMomentum = true;
+
+ DispatchPanGestureInput(panEvent);
+
+ return;
+ }
+
+ // Older GTK doesn't support stop events, so we can't support fling
+ wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY;
+ }
+
+ // 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.mIsNoLineOrPageDelta = true;
+
+ break;
+ }
+ case GDK_SCROLL_UP:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = -3;
+ break;
+ case GDK_SCROLL_DOWN:
+ wheelEvent.mDeltaY = wheelEvent.mLineOrPageDeltaY = 3;
+ break;
+ case GDK_SCROLL_LEFT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = -1;
+ break;
+ case GDK_SCROLL_RIGHT:
+ wheelEvent.mDeltaX = wheelEvent.mLineOrPageDeltaX = 1;
+ break;
+ }
+
+ wheelEvent.mRefPoint = GetRefPoint(this, aEvent);
+
+ KeymapWrapper::InitInputEvent(wheelEvent, aEvent->state);
+
+ wheelEvent.AssignEventTime(GetWidgetEventTime(aEvent->time));
+
+ DispatchInputEvent(&wheelEvent);
+}
+
+void nsWindow::OnWindowStateEvent(GtkWidget* aWidget,
+ GdkEventWindowState* aEvent) {
+ LOG(
+ ("nsWindow::OnWindowStateEvent [%p] for %p changed 0x%x new_window_state "
+ "0x%x\n",
+ (void*)this, 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));
+ if (mHasMappedToplevel != mapped) {
+ 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 some DE still have this issue
+ // (Bug 1624199) so let's remove it for Wayland only.
+ if (mIsX11Display) {
+ 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
+ // mSizeState and obtaining a focus.
+ bool waylandWasIconified =
+ (!mIsX11Display && aEvent->changed_mask & GDK_WINDOW_STATE_FOCUSED &&
+ aEvent->new_window_state & GDK_WINDOW_STATE_FOCUSED &&
+ mSizeState == 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;
+ }
+
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_ICONIFIED) {
+ LOG(("\tIconified\n"));
+ mSizeState = nsSizeMode_Minimized;
+#ifdef ACCESSIBILITY
+ DispatchMinimizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN) {
+ LOG(("\tFullscreen\n"));
+ mSizeState = nsSizeMode_Fullscreen;
+ } else if (aEvent->new_window_state & GDK_WINDOW_STATE_MAXIMIZED) {
+ LOG(("\tMaximized\n"));
+ mSizeState = nsSizeMode_Maximized;
+#ifdef ACCESSIBILITY
+ DispatchMaximizeEventAccessible();
+#endif // ACCESSIBILITY
+ } else {
+ LOG(("\tNormal\n"));
+ mSizeState = nsSizeMode_Normal;
+#ifdef ACCESSIBILITY
+ DispatchRestoreEventAccessible();
+#endif // ACCESSIBILITY
+ }
+
+ if (aEvent->new_window_state & GDK_WINDOW_STATE_TILED) {
+ LOG(("\tTiled\n"));
+ mIsTiled = true;
+ } else {
+ LOG(("\tNot tiled\n"));
+ mIsTiled = false;
+ }
+
+ if (mWidgetListener) {
+ mWidgetListener->SizeModeChanged(mSizeState);
+ if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) {
+ mWidgetListener->FullscreenChanged(aEvent->new_window_state &
+ GDK_WINDOW_STATE_FULLSCREEN);
+ }
+ }
+
+ if (mDrawInTitlebar && mTransparencyBitmapForTitlebar) {
+ if (mSizeState == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+void nsWindow::ThemeChanged() {
+ // Everything could've changed.
+ NotifyThemeChanged(ThemeChangeKind::StyleAndLayout);
+
+ if (!mGdkWindow || MOZ_UNLIKELY(mIsDestroyed)) return;
+
+ // Dispatch theme change notification to all child windows
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ while (children) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+
+ auto* win = (nsWindow*)g_object_get_data(G_OBJECT(gdkWin), "nsWindow");
+
+ if (win && win != this) { // guard against infinite recursion
+ RefPtr<nsWindow> kungFuDeathGrip = win;
+ win->ThemeChanged();
+ }
+
+ children = children->next;
+ }
+
+ IMContextWrapper::OnThemeChanged();
+}
+
+void nsWindow::OnDPIChanged() {
+ if (mWidgetListener) {
+ if (PresShell* presShell = mWidgetListener->GetPresShell()) {
+ presShell->BackingScaleFactorChanged();
+ // Update menu's font size etc.
+ // This affects style / layout because it affects system font sizes.
+ presShell->ThemeChanged(ThemeChangeKind::StyleAndLayout);
+ }
+ 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(GtkAllocation* aAllocation) {
+ LOG(("nsWindow::OnScaleChanged [%p] %d,%d -> %d x %d\n", (void*)this,
+ aAllocation->x, aAllocation->y, aAllocation->width,
+ aAllocation->height));
+
+ // Force scale factor recalculation
+ mWindowScaleFactorChanged = true;
+
+ // This eventually propagate new scale to the PuppetWidgets
+ OnDPIChanged();
+
+ // configure_event is already fired before scale-factor signal,
+ // but size-allocate isn't fired by changing scale
+ OnSizeAllocate(aAllocation);
+
+ // Client offset are updated by _NET_FRAME_EXTENTS on X11 when system titlebar
+ // is enabled. In ither 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.
+ if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ if (!mIsX11Display || (mIsX11Display && mDrawInTitlebar)) {
+ UpdateClientOffsetFromCSDWindow();
+ }
+ }
+
+#ifdef MOZ_WAYLAND
+ // We need to update scale when scale of egl window is changed.
+ if (mContainer && moz_container_wayland_has_egl_window(mContainer)) {
+ moz_container_wayland_set_scale_factor(mContainer);
+ }
+#endif
+}
+
+void nsWindow::DispatchDragEvent(EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint,
+ guint aTime) {
+ 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(%p)\n", (void*)this));
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ dragService->TargetDataReceived(aWidget, aDragContext, aX, aY, aSelectionData,
+ aInfo, aTime);
+}
+
+nsWindow* nsWindow::GetTransientForWindowIfPopup() {
+ if (mWindowType != eWindowType_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()) {
+ PinchGestureInput::PinchGestureType pinchGestureType =
+ PinchGestureInput::PINCHGESTURE_SCALE;
+ ScreenCoord CurrentSpan;
+ ScreenCoord PreviousSpan;
+
+ switch (aEvent->phase) {
+ case GDK_TOUCHPAD_GESTURE_PHASE_BEGIN:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_START;
+ CurrentSpan = aEvent->scale;
+
+ // 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;
+ mLastPinchEventSpan = aEvent->scale;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_UPDATE:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_SCALE;
+ if (aEvent->scale == mLastPinchEventSpan) {
+ return FALSE;
+ }
+ CurrentSpan = aEvent->scale;
+ PreviousSpan = mLastPinchEventSpan;
+ mLastPinchEventSpan = aEvent->scale;
+ break;
+
+ case GDK_TOUCHPAD_GESTURE_PHASE_END:
+ pinchGestureType = PinchGestureInput::PINCHGESTURE_END;
+ CurrentSpan = aEvent->scale;
+ PreviousSpan = mLastPinchEventSpan;
+ break;
+
+ default:
+ return FALSE;
+ }
+
+ LayoutDeviceIntPoint touchpadPoint = GetRefPoint(this, aEvent);
+ PinchGestureInput event(
+ pinchGestureType, PinchGestureInput::TRACKPAD, aEvent->time,
+ GetEventTimeStamp(aEvent->time), ExternalPoint(0, 0),
+ ScreenPoint(touchpadPoint.x, touchpadPoint.y), CurrentSpan,
+ PreviousSpan, KeymapWrapper::ComputeKeyModifiers(aEvent->state));
+
+ DispatchPinchGestureInput(event);
+ }
+ return TRUE;
+}
+
+gboolean nsWindow::OnTouchEvent(GdkEventTouch* aEvent) {
+ 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:
+ msg = eTouchStart;
+ break;
+ case GDK_TOUCH_UPDATE:
+ msg = eTouchMove;
+ break;
+ case GDK_TOUCH_END:
+ msg = eTouchEnd;
+ break;
+ case GDK_TOUCH_CANCEL:
+ msg = eTouchCancel;
+ break;
+ default:
+ return FALSE;
+ }
+
+ 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);
+ event.mTime = aEvent->time;
+
+ if (aEvent->type == GDK_TOUCH_BEGIN || aEvent->type == GDK_TOUCH_UPDATE) {
+ mTouches.Put(aEvent->sequence, std::move(touch));
+ // add all touch points to event object
+ for (auto iter = mTouches.Iter(); !iter.Done(); iter.Next()) {
+ event.mTouches.AppendElement(new dom::Touch(*iter.UserData()));
+ }
+ } else if (aEvent->type == GDK_TOUCH_END ||
+ aEvent->type == GDK_TOUCH_CANCEL) {
+ *event.mTouches.AppendElement() = std::move(touch);
+ }
+
+ DispatchInputEvent(&event);
+ 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 = GetSystemCSDSupportLevel() != CSD_SUPPORT_NONE;
+ }
+ }
+ transparencyConfigured = true;
+ }
+
+ return sTransparentMainWindow;
+}
+
+static GdkWindow* CreateGdkWindow(GdkWindow* parent, GtkWidget* widget) {
+ GdkWindowAttr attributes;
+ gint attributes_mask = GDK_WA_VISUAL;
+
+ attributes.event_mask = kEvents;
+
+ attributes.width = 1;
+ attributes.height = 1;
+ attributes.wclass = GDK_INPUT_OUTPUT;
+ attributes.visual = gtk_widget_get_visual(widget);
+ attributes.window_type = GDK_WINDOW_CHILD;
+
+ GdkWindow* window = gdk_window_new(parent, &attributes, attributes_mask);
+ gdk_window_set_user_data(window, widget);
+
+ return window;
+}
+
+// Configure GL visual on X11. We add alpha silently
+// if we use WebRender to workaround NVIDIA specific Bug 1663273.
+bool nsWindow::ConfigureX11GLVisual(bool aUseAlpha) {
+ if (!mIsX11Display) {
+ return false;
+ }
+
+ // If using WebRender on X11, we need to select a visual with a depth
+ // buffer, as well as an alpha channel if transparency is requested. This
+ // must be done before the widget is realized.
+ bool useWebRender = gfx::gfxVars::UseWebRender();
+ auto* screen = gtk_widget_get_screen(mShell);
+ int visualId = 0;
+ bool haveVisual;
+
+ // See https://bugzilla.mozilla.org/show_bug.cgi?id=1663003
+ // We need to use GLX to get visual even on EGL until
+ // EGL can provide compositable visual:
+ // https://gitlab.freedesktop.org/mesa/mesa/-/issues/149
+ if ((true /* !gfx::gfxVars::UseEGL() */)) {
+ auto* display = GDK_DISPLAY_XDISPLAY(gtk_widget_get_display(mShell));
+ int screenNumber = GDK_SCREEN_XNUMBER(screen);
+ haveVisual = GLContextGLX::FindVisual(display, screenNumber, useWebRender,
+ aUseAlpha || useWebRender, &visualId);
+ } else {
+ haveVisual = GLContextEGL::FindVisual(useWebRender,
+ aUseAlpha || useWebRender, &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!");
+ if (aUseAlpha || useWebRender) {
+ // We try to use a fallback alpha visual
+ GdkScreen* screen = gtk_widget_get_screen(mShell);
+ gdkVisual = gdk_screen_get_rgba_visual(screen);
+ }
+ }
+ if (gdkVisual) {
+ // TODO: We use alpha visual even on non-compositing screens (Bug 1479135).
+ gtk_widget_set_visual(mShell, gdkVisual);
+ mHasAlphaVisual = aUseAlpha;
+ }
+
+ return true;
+}
+
+nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) {
+#ifdef MOZ_LOGGING
+ if (this->GetFrame() && this->GetFrame()->GetContent()) {
+ nsCString nodeName =
+ NS_ConvertUTF16toUTF8(this->GetFrame()->GetContent()->NodeName());
+ LOG(("nsWindow::Create: creating [%p]: nodename %s\n", this,
+ nodeName.get()));
+ }
+#endif
+ // only set the base parent if we're going to be a dialog or a
+ // toplevel
+ nsIWidget* baseParent =
+ aInitData && (aInitData->mWindowType == eWindowType_dialog ||
+ aInitData->mWindowType == eWindowType_toplevel ||
+ aInitData->mWindowType == eWindowType_invisible)
+ ? nullptr
+ : aParent;
+
+#ifdef ACCESSIBILITY
+ // Send a DBus message to check whether a11y is enabled
+ a11y::PreInit();
+#endif
+
+ // Ensure that the toolkit is created.
+ nsGTKToolkit::GetToolkit();
+
+ // initialize all the common bits of this class
+ BaseCreate(baseParent, aInitData);
+
+ // Do we need to listen for resizes?
+ bool listenForResizes = false;
+ ;
+ if (aNativeParent || (aInitData && aInitData->mListenForResizes))
+ listenForResizes = true;
+
+ // and do our common creation
+ CommonCreate(aParent, listenForResizes);
+
+ // save our bounds
+ mBounds = aRect;
+ LOG((" mBounds: x:%d y:%d w:%d h:%d\n", mBounds.x, mBounds.y, mBounds.width,
+ mBounds.height));
+
+ mPreferredPopupRectFlushed = false;
+
+ ConstrainSize(&mBounds.width, &mBounds.height);
+
+ GtkWidget* eventWidget = nullptr;
+ bool popupNeedsAlphaVisual = (mWindowType == eWindowType_popup &&
+ (aInitData && aInitData->mSupportTranslucency));
+
+ // Figure out our parent window - only used for eWindowType_child
+ GtkWidget* parentMozContainer = nullptr;
+ GtkContainer* parentGtkContainer = nullptr;
+ GdkWindow* parentGdkWindow = nullptr;
+ nsWindow* parentnsWindow = nullptr;
+
+ if (aParent) {
+ parentnsWindow = static_cast<nsWindow*>(aParent);
+ parentGdkWindow = parentnsWindow->mGdkWindow;
+ } else if (aNativeParent && GDK_IS_WINDOW(aNativeParent)) {
+ parentGdkWindow = GDK_WINDOW(aNativeParent);
+ parentnsWindow = get_window_for_gdk_window(parentGdkWindow);
+ if (!parentnsWindow) return NS_ERROR_FAILURE;
+
+ } else if (aNativeParent && GTK_IS_CONTAINER(aNativeParent)) {
+ parentGtkContainer = GTK_CONTAINER(aNativeParent);
+ }
+
+ if (parentGdkWindow) {
+ // get the widget for the window - it should be a moz container
+ parentMozContainer = parentnsWindow->GetMozContainerWidget();
+ if (!parentMozContainer) return NS_ERROR_FAILURE;
+ }
+ // ^^ only used for eWindowType_child
+
+ if (!mIsX11Display) {
+ if (mWindowType == eWindowType_child) {
+ // eWindowType_child is not supported on Wayland. Just switch to toplevel
+ // as a workaround.
+ mWindowType = eWindowType_toplevel;
+ } else if (mWindowType == eWindowType_popup && !aNativeParent && !aParent) {
+ // Workaround for Wayland where the popup windows always need to have
+ // parent window. For example webrtc ui is a popup window without parent.
+ mWindowType = eWindowType_toplevel;
+ }
+ }
+
+ mAlwaysOnTop = aInitData && aInitData->mAlwaysOnTop;
+ mIsPIPWindow = aInitData && aInitData->mPIPWindow;
+
+ // ok, create our windows
+ switch (mWindowType) {
+ case eWindowType_dialog:
+ case eWindowType_popup:
+ case eWindowType_toplevel:
+ case eWindowType_invisible: {
+ mIsTopLevel = true;
+
+ // 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 == eWindowType_popup) {
+ type = (mIsX11Display && aInitData->mNoAutoHide) ? GTK_WINDOW_TOPLEVEL
+ : GTK_WINDOW_POPUP;
+ }
+ mShell = gtk_window_new(type);
+
+ // Ensure gfxPlatform is initialized, since that is what initializes
+ // gfxVars, used below.
+ Unused << gfxPlatform::GetPlatform();
+
+ if (mWindowType == eWindowType_toplevel ||
+ mWindowType == eWindowType_dialog) {
+ bool isPopup = mIsPIPWindow || mWindowType == eWindowType_dialog;
+ mCSDSupportLevel = GetSystemCSDSupportLevel(isPopup);
+ }
+
+ // Don't use transparency for PictureInPicture windows.
+ bool toplevelNeedsAlphaVisual = false;
+ if (mWindowType == eWindowType_toplevel && !mIsPIPWindow) {
+ toplevelNeedsAlphaVisual = IsToplevelWindowTransparent();
+ }
+
+ bool isGLVisualSet = false;
+ bool isAccelerated = ComputeShouldAccelerate();
+#ifdef MOZ_X11
+ if (isAccelerated) {
+ isGLVisualSet = ConfigureX11GLVisual(popupNeedsAlphaVisual ||
+ toplevelNeedsAlphaVisual);
+ }
+#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 eTransparencyTransparent
+ // from layout, so set mIsTransparent here.
+ if (mWindowType == eWindowType_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.
+ NativeResize();
+
+ if (mWindowType == eWindowType_dialog) {
+ mGtkWindowRoleName = "Dialog";
+
+ SetDefaultIcon();
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_DIALOG);
+
+ if (parentnsWindow) {
+ gtk_window_set_transient_for(
+ GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ LOG((
+ "nsWindow::Create(): dialog [%p], parent window %p [GdkWindow]\n",
+ this, aNativeParent));
+ }
+
+ } else if (mWindowType == eWindowType_popup) {
+ mGtkWindowRoleName = "Popup";
+
+ if (aInitData->mNoAutoHide) {
+ // ... but the window manager does not decorate this window,
+ // nor provide a separate taskbar icon.
+ if (mBorderStyle == eBorderStyle_default) {
+ gtk_window_set_decorated(GTK_WINDOW(mShell), FALSE);
+ } else {
+ bool decorate = mBorderStyle & eBorderStyle_title;
+ gtk_window_set_decorated(GTK_WINDOW(mShell), decorate);
+ if (decorate) {
+ gtk_window_set_deletable(GTK_WINDOW(mShell),
+ mBorderStyle & eBorderStyle_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 (mIsX11Display) {
+ gtk_widget_realize(mShell);
+ gdk_window_add_filter(gtk_widget_get_window(mShell),
+ popup_take_focus_filter, nullptr);
+ }
+#endif
+ }
+
+ GdkWindowTypeHint gtkTypeHint;
+ if (aInitData->mIsDragPopup) {
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
+ mIsDragPopup = true;
+ } else {
+ switch (aInitData->mPopupHint) {
+ case ePopupTypeMenu:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+ break;
+ case ePopupTypeTooltip:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+ break;
+ default:
+ gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+ break;
+ }
+ }
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
+ if (parentnsWindow) {
+ LOG(("nsWindow::Create() [%p]: parent window for popup: %p\n", this,
+ parentnsWindow));
+ gtk_window_set_transient_for(
+ GTK_WINDOW(mShell), GTK_WINDOW(parentnsWindow->GetGtkWidget()));
+ }
+
+ // We need realized mShell at NativeMove().
+ gtk_widget_realize(mShell);
+
+ // With popup windows, we want to control their position, so don't
+ // wait for the window manager to place them (which wouldn't
+ // happen with override-redirect windows anyway).
+ NativeMove();
+ } else { // must be eWindowType_toplevel
+ mGtkWindowRoleName = "Toplevel";
+ SetDefaultIcon();
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ }
+
+ // 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);
+ }
+
+ // Create a container to hold child windows and child GtkWidgets.
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+#ifdef MOZ_WAYLAND
+ if (!mIsX11Display && isAccelerated) {
+ mCompositorInitiallyPaused = true;
+ RefPtr<nsWindow> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void {
+ self->mNeedsCompositorResume = true;
+ self->MaybeResumeCompositor();
+ });
+ }
+#endif
+
+ // "csd" style is set when widget is realized so we need to call
+ // it explicitly now.
+ gtk_widget_realize(mShell);
+
+ /* There are several cases here:
+ *
+ * 1) We're running on Gtk+ without client side decorations.
+ * Content is rendered to mShell window and we listen
+ * to the Gtk+ events on mShell
+ * 2) We're running on Gtk+ and client side decorations
+ * are drawn by Gtk+ to mShell. Content is rendered to mContainer
+ * and we listen to the Gtk+ events on mContainer.
+ * 3) We're running on Wayland. All gecko content is rendered
+ * to mContainer and we listen to the Gtk+ events on mContainer.
+ */
+ GtkStyleContext* style = gtk_widget_get_style_context(mShell);
+ mDrawToContainer = !mIsX11Display ||
+ (mCSDSupportLevel == CSD_SUPPORT_CLIENT) ||
+ gtk_style_context_has_class(style, "csd");
+ eventWidget = (mDrawToContainer) ? container : mShell;
+
+ // Prevent GtkWindow from painting a background to avoid flickering.
+ gtk_widget_set_app_paintable(eventWidget, TRUE);
+
+ gtk_widget_add_events(eventWidget, kEvents);
+ if (mDrawToContainer) {
+ gtk_widget_add_events(mShell, GDK_PROPERTY_CHANGE_MASK);
+ gtk_widget_set_app_paintable(mShell, TRUE);
+ }
+ 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, mDrawToContainer);
+
+ 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);
+ }
+
+ // the drawing window
+ mGdkWindow = gtk_widget_get_window(eventWidget);
+
+ if (mWindowType == eWindowType_popup) {
+ // gdk does not automatically set the cursor for "temporary"
+ // windows, which are what gtk uses for popups.
+
+ mCursor = eCursor_wait; // force SetCursor to actually set the
+ // cursor, even though our internal state
+ // indicates that we already have the
+ // standard cursor.
+ SetCursor(eCursor_standard, nullptr, 0, 0);
+
+ if (aInitData->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.
+ SetWindowMouseTransparent(aInitData->mMouseTransparent);
+ }
+ } break;
+
+ case eWindowType_plugin:
+ case eWindowType_plugin_ipc_chrome:
+ case eWindowType_plugin_ipc_content:
+ MOZ_ASSERT_UNREACHABLE("Unexpected eWindowType_plugin*");
+ return NS_ERROR_FAILURE;
+
+ case eWindowType_child: {
+ if (parentMozContainer) {
+ mGdkWindow = CreateGdkWindow(parentGdkWindow, parentMozContainer);
+ mHasMappedToplevel = parentnsWindow->mHasMappedToplevel;
+ } else if (parentGtkContainer) {
+ // This MozContainer has its own window for drawing and receives
+ // events because there is no mShell widget (corresponding to this
+ // nsWindow).
+ GtkWidget* container = moz_container_new();
+ mContainer = MOZ_CONTAINER(container);
+ eventWidget = container;
+ gtk_widget_add_events(eventWidget, kEvents);
+ gtk_container_add(parentGtkContainer, container);
+ gtk_widget_realize(container);
+
+ mGdkWindow = gtk_widget_get_window(container);
+ } else {
+ NS_WARNING(
+ "Warning: tried to create a new child widget with no parent!");
+ return NS_ERROR_FAILURE;
+ }
+ } break;
+ default:
+ break;
+ }
+
+ // label the drawing window with this object so we can find our way home
+ g_object_set_data(G_OBJECT(mGdkWindow), "nsWindow", this);
+ if (mDrawToContainer) {
+ // Also label mShell toplevel window,
+ // property_notify_event_cb callback also needs to find its way home
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+ }
+
+ if (mContainer) g_object_set_data(G_OBJECT(mContainer), "nsWindow", this);
+
+ if (mShell) g_object_set_data(G_OBJECT(mShell), "nsWindow", this);
+
+ // attach listeners for events
+ if (mShell) {
+ 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, "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 == eWindowType_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),
+ 0)) {
+ g_signal_connect(screen, "composited-changed",
+ G_CALLBACK(screen_composited_changed_cb), nullptr);
+ }
+
+ GtkSettings* default_settings = gtk_settings_get_default();
+ g_signal_connect_after(default_settings, "notify::gtk-theme-name",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-font-name",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-enable-animations",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-decoration-layout",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-xft-dpi",
+ G_CALLBACK(settings_xft_dpi_changed_cb), this);
+ // For remote LookAndFeel, to refresh the content processes' copies:
+ g_signal_connect_after(default_settings, "notify::gtk-cursor-blink-time",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-cursor-blink",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-entry-select-on-focus",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings,
+ "notify::gtk-primary-button-warps-slider",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-menu-popup-delay",
+ G_CALLBACK(settings_changed_cb), this);
+ g_signal_connect_after(default_settings, "notify::gtk-dnd-drag-threshold",
+ G_CALLBACK(settings_changed_cb), this);
+ }
+
+ if (mContainer) {
+ // Widget signals
+ g_signal_connect(mContainer, "unrealize",
+ G_CALLBACK(container_unrealize_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);
+
+ gtk_drag_dest_set((GtkWidget*)mContainer, (GtkDestDefaults)0, nullptr, 0,
+ (GdkDragAction)0);
+
+ g_signal_connect(mContainer, "drag_motion",
+ G_CALLBACK(drag_motion_event_cb), nullptr);
+ g_signal_connect(mContainer, "drag_leave", G_CALLBACK(drag_leave_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "drag_drop", G_CALLBACK(drag_drop_event_cb),
+ nullptr);
+ g_signal_connect(mContainer, "drag_data_received",
+ G_CALLBACK(drag_data_received_event_cb), nullptr);
+
+#ifdef MOZ_X11
+ if (mIsX11Display) {
+ GtkWidget* widgets[] = {GTK_WIDGET(mContainer),
+ !mDrawToContainer ? mShell : nullptr};
+ for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) {
+ // Double buffering is controlled by the window's owning
+ // widget. Disable double buffering for painting directly to the
+ // X Window.
+ gtk_widget_set_double_buffered(widgets[i], FALSE);
+ }
+ }
+#endif
+
+ // We create input contexts for all containers, except for
+ // toplevel popup windows
+ if (mWindowType != eWindowType_popup) {
+ mIMContext = new IMContextWrapper(this);
+ }
+ } else if (!mIMContext) {
+ nsWindow* container = GetContainerWindow();
+ if (container) {
+ mIMContext = container->mIMContext;
+ }
+ }
+
+ if (eventWidget) {
+ // 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(eventWidget, "enter-notify-event",
+ G_CALLBACK(enter_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "leave-notify-event",
+ G_CALLBACK(leave_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "motion-notify-event",
+ G_CALLBACK(motion_notify_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-press-event",
+ G_CALLBACK(button_press_event_cb), nullptr);
+ g_signal_connect(eventWidget, "button-release-event",
+ G_CALLBACK(button_release_event_cb), nullptr);
+ g_signal_connect(eventWidget, "scroll-event", G_CALLBACK(scroll_event_cb),
+ nullptr);
+ if (gtk_check_version(3, 18, 0) == nullptr) {
+ g_signal_connect(eventWidget, "event", G_CALLBACK(generic_event_cb),
+ nullptr);
+ }
+ g_signal_connect(eventWidget, "touch-event", G_CALLBACK(touch_event_cb),
+ nullptr);
+ }
+
+ LOG(("nsWindow [%p] %s %s\n", (void*)this,
+ mWindowType == eWindowType_toplevel ? "Toplevel" : "Popup",
+ mIsPIPWindow ? "PIP window" : ""));
+ if (mShell) {
+ LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n", mShell, mContainer,
+ mGdkWindow, mIsX11Display ? gdk_x11_window_get_xid(mGdkWindow) : 0));
+ } else if (mContainer) {
+ LOG(("\tmContainer %p mGdkWindow %p\n", mContainer, mGdkWindow));
+ } else if (mGdkWindow) {
+ LOG(("\tmGdkWindow %p parent %p\n", mGdkWindow,
+ gdk_window_get_parent(mGdkWindow)));
+ }
+
+ // resize so that everything is set to the right dimensions
+ if (!mIsTopLevel)
+ Resize(mBounds.x, mBounds.y, mBounds.width, mBounds.height, false);
+
+#ifdef MOZ_X11
+ if (mIsX11Display && mGdkWindow) {
+ mXDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ mXWindow = gdk_x11_window_get_xid(mGdkWindow);
+
+ GdkVisual* gdkVisual = gdk_window_get_visual(mGdkWindow);
+ mXVisual = gdk_x11_visual_get_xvisual(gdkVisual);
+ mXDepth = gdk_visual_get_depth(gdkVisual);
+ bool shaped = popupNeedsAlphaVisual && !mHasAlphaVisual;
+
+ mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth, shaped);
+
+ if (mIsTopLevel) {
+ // 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);
+ }
+ }
+# ifdef MOZ_WAYLAND
+ else if (!mIsX11Display) {
+ mSurfaceProvider.Initialize(this);
+ WaylandStartVsync();
+ }
+# endif
+#endif
+
+ // Set default application name when it's empty.
+ if (mGtkWindowAppName.IsEmpty()) {
+ mGtkWindowAppName = gAppData->name;
+ }
+ RefreshWindowClass();
+
+ return NS_OK;
+}
+
+void nsWindow::RefreshWindowClass(void) {
+ GdkWindow* gdkWindow = gtk_widget_get_window(mShell);
+ if (!gdkWindow) {
+ return;
+ }
+
+ if (!mGtkWindowRoleName.IsEmpty()) {
+ gdk_window_set_role(gdkWindow, mGtkWindowRoleName.get());
+ }
+
+#ifdef MOZ_X11
+ if (!mGtkWindowAppName.IsEmpty() && mIsX11Display) {
+ XClassHint* class_hint = XAllocClassHint();
+ if (!class_hint) {
+ return;
+ }
+ const char* res_class = gdk_get_program_class();
+ if (!res_class) return;
+
+ class_hint->res_name = const_cast<char*>(mGtkWindowAppName.get());
+ 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 */
+}
+
+void nsWindow::SetWindowClass(const nsAString& xulWinType) {
+ if (!mShell) return;
+
+ char* res_name = ToNewCString(xulWinType, mozilla::fallible);
+ if (!res_name) return;
+
+ const char* role = nullptr;
+
+ // 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] = toupper(res_name[0]);
+ if (!role) role = res_name;
+
+ mGtkWindowAppName = res_name;
+ mGtkWindowRoleName = role;
+ free(res_name);
+
+ RefreshWindowClass();
+}
+
+void nsWindow::NativeResize() {
+ if (!AreBoundsSane()) {
+ // 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);
+ }
+ return;
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+
+ LOG(("nsWindow::NativeResize [%p] %d %d\n", (void*)this, size.width,
+ size.height));
+
+ if (mIsTopLevel) {
+ MOZ_ASSERT(size.width > 0 && size.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ if (mWaitingForMoveToRectCB) {
+ LOG(("Waiting for move to rect, schedulling "));
+ mPendingSizeRect = mBounds;
+ }
+ } else if (mContainer) {
+ GtkWidget* widget = GTK_WIDGET(mContainer);
+ GtkAllocation allocation, prev_allocation;
+ gtk_widget_get_allocation(widget, &prev_allocation);
+ allocation.x = prev_allocation.x;
+ allocation.y = prev_allocation.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(widget, &allocation);
+ } else if (mGdkWindow) {
+ gdk_window_resize(mGdkWindow, size.width, size.height);
+ }
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void nsWindow::NativeMoveResize() {
+ if (!AreBoundsSane()) {
+ // 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);
+ }
+ NativeMove();
+
+ return;
+ }
+
+ GdkRectangle size = DevicePixelsToGdkSizeRoundUp(mBounds.Size());
+ GdkPoint topLeft = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft());
+
+ LOG(("nsWindow::NativeMoveResize [%p] %d %d %d %d\n", (void*)this, topLeft.x,
+ topLeft.y, size.width, size.height));
+
+ if (IsWaylandPopup()) {
+ NativeMoveResizeWaylandPopup(&topLeft, &size);
+ } else {
+ if (mIsTopLevel) {
+ // x and y give the position of the window manager frame top-left.
+ gtk_window_move(GTK_WINDOW(mShell), topLeft.x, topLeft.y);
+ // This sets the client window size.
+ MOZ_ASSERT(size.width > 0 && size.height > 0,
+ "Can't resize window smaller than 1x1.");
+ gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height);
+ } else if (mContainer) {
+ GtkAllocation allocation;
+ allocation.x = topLeft.x;
+ allocation.y = topLeft.y;
+ allocation.width = size.width;
+ allocation.height = size.height;
+ gtk_widget_size_allocate(GTK_WIDGET(mContainer), &allocation);
+ } else if (mGdkWindow) {
+ gdk_window_move_resize(mGdkWindow, topLeft.x, topLeft.y, size.width,
+ size.height);
+ }
+ }
+
+#ifdef MOZ_X11
+ // Notify the GtkCompositorWidget of a ClientSizeChange
+ // This is different than OnSizeAllocate to catch initial sizing
+ if (mCompositorWidgetDelegate) {
+ mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
+ }
+#endif
+
+ // Does it need to be shown because bounds were previously insane?
+ if (mNeedsShow && mIsShown) {
+ NativeShow(true);
+ }
+}
+
+void nsWindow::PauseRemoteRenderer() {
+#ifdef MOZ_WAYLAND
+ if (!mIsDestroyed) {
+ if (mContainer) {
+ // Because wl_egl_window is destroyed on moz_container_unmap(),
+ // the current compositor cannot use it anymore. To avoid crash,
+ // pause the compositor and destroy EGLSurface & resume the compositor
+ // and re-create EGLSurface on next expose event.
+
+ // moz_container_wayland_has_egl_window() could not be used here, since
+ // there is a case that resume compositor is not completed yet.
+
+ CompositorBridgeChild* remoteRenderer = GetRemoteRenderer();
+ bool needsCompositorPause = !mNeedsCompositorResume && !!remoteRenderer &&
+ mCompositorWidgetDelegate;
+ if (needsCompositorPause) {
+ // XXX slow sync IPC
+ remoteRenderer->SendPause();
+ // Re-request initial draw callback
+ RefPtr<nsWindow> self(this);
+ moz_container_wayland_add_initial_draw_callback(
+ mContainer, [self]() -> void {
+ self->mNeedsCompositorResume = true;
+ self->MaybeResumeCompositor();
+ });
+ } else {
+ DestroyLayerManager();
+ }
+ }
+ }
+#endif
+}
+
+void nsWindow::HideWaylandWindow() {
+ if (mWindowType == eWindowType_popup) {
+ LOG(("nsWindow::HideWaylandWindow: popup [%p]\n", this));
+ GList* foundWindow = g_list_find(gVisibleWaylandPopupWindows, this);
+ if (foundWindow) {
+ gVisibleWaylandPopupWindows =
+ g_list_delete_link(gVisibleWaylandPopupWindows, foundWindow);
+ }
+ }
+ PauseRemoteRenderer();
+ gtk_widget_hide(mShell);
+}
+
+void nsWindow::WaylandStartVsync() {
+#ifdef MOZ_WAYLAND
+ // only use for toplevel windows for now - see bug 1619246
+ if (!gUseWaylandVsync || mWindowType != eWindowType_toplevel) {
+ return;
+ }
+
+ if (!mWaylandVsyncSource) {
+ mWaylandVsyncSource = new mozilla::WaylandVsyncSource(mContainer);
+ }
+ WaylandVsyncSource::WaylandDisplay& display =
+ static_cast<WaylandVsyncSource::WaylandDisplay&>(
+ mWaylandVsyncSource->GetGlobalDisplay());
+ display.EnableMonitor();
+#endif
+}
+
+void nsWindow::WaylandStopVsync() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncSource) {
+ // The widget is going to be hidden, so clear the surface of our
+ // vsync source.
+ WaylandVsyncSource::WaylandDisplay& display =
+ static_cast<WaylandVsyncSource::WaylandDisplay&>(
+ mWaylandVsyncSource->GetGlobalDisplay());
+ display.DisableMonitor();
+ }
+#endif
+}
+
+void nsWindow::NativeShow(bool aAction) {
+ if (aAction) {
+ // unset our flag now that our window has been shown
+ mNeedsShow = false;
+
+ if (mIsTopLevel) {
+ // Set up usertime/startupID metadata for the created window.
+ if (mWindowType != eWindowType_invisible) {
+ SetUserTimeAndStartupIDForActivatedWindow(mShell);
+ }
+ // Update popup window hierarchy run-time on Wayland.
+ if (IsWaylandPopup()) {
+ if (!ConfigureWaylandPopupWindows()) {
+ mNeedsShow = true;
+ return;
+ }
+ }
+
+ LOG((" calling gtk_widget_show(mShell)\n"));
+ gtk_widget_show(mShell);
+ if (!mIsX11Display) {
+ WaylandStartVsync();
+ }
+ } else if (mContainer) {
+ LOG((" calling gtk_widget_show(mContainer)\n"));
+ gtk_widget_show(GTK_WIDGET(mContainer));
+ } else if (mGdkWindow) {
+ LOG((" calling gdk_window_show_unraised\n"));
+ gdk_window_show_unraised(mGdkWindow);
+ }
+ } else {
+ // There's a chance that when the popup will be shown again it might be
+ // resized because parent could be moved meanwhile.
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+ mPreferredPopupRectFlushed = false;
+ if (!mIsX11Display) {
+ WaylandStopVsync();
+ if (IsWaylandPopup() && IsMainMenuWindow()) {
+ CleanupWaylandPopups();
+ }
+ HideWaylandWindow();
+ } else if (mIsTopLevel) {
+ // 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
+ } else if (mContainer) {
+ gtk_widget_hide(GTK_WIDGET(mContainer));
+ } else if (mGdkWindow) {
+ gdk_window_hide(mGdkWindow);
+ }
+ }
+}
+
+void nsWindow::SetHasMappedToplevel(bool aState) {
+ // 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.
+ bool oldState = mHasMappedToplevel;
+ mHasMappedToplevel = aState;
+
+ // mHasMappedToplevel is not updated for children of windows that are
+ // hidden; GDK knows not to send expose events for these windows. The
+ // state is recorded on the hidden window itself, but, for child trees of
+ // hidden windows, their state essentially becomes disconnected from their
+ // hidden parent. When the hidden parent gets shown, the child trees are
+ // reconnected, and the state of the window being shown can be easily
+ // propagated.
+ if (!mIsShown || !mGdkWindow) return;
+
+ if (aState && !oldState) {
+ // Check that a grab didn't fail due to the window not being
+ // viewable.
+ EnsureGrabs();
+ }
+
+ for (GList* children = gdk_window_peek_children(mGdkWindow); children;
+ children = children->next) {
+ GdkWindow* gdkWin = GDK_WINDOW(children->data);
+ nsWindow* child = get_window_for_gdk_window(gdkWin);
+
+ if (child && child->mHasMappedToplevel != aState) {
+ child->SetHasMappedToplevel(aState);
+ }
+ }
+}
+
+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 (mLayerManager && mLayerManager->AsKnowsCompositor()) {
+ maxSize = std::min(maxSize,
+ mLayerManager->AsKnowsCompositor()->GetMaxTextureSize());
+ }
+ if (result.width > maxSize) {
+ result.width = maxSize;
+ }
+ if (result.height > maxSize) {
+ result.height = maxSize;
+ }
+ return result;
+}
+
+void nsWindow::EnsureGrabs(void) {
+ if (mRetryPointerGrab) GrabPointer(sRetryGrabTime);
+}
+
+void nsWindow::CleanLayerManagerRecursive(void) {
+ if (mLayerManager) {
+ mLayerManager->Destroy();
+ mLayerManager = nullptr;
+ }
+
+ DestroyCompositor();
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->CleanLayerManagerRecursive();
+ }
+ }
+}
+
+void nsWindow::SetTransparencyMode(nsTransparencyMode aMode) {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return;
+
+ topWindow->SetTransparencyMode(aMode);
+ return;
+ }
+
+ bool isTransparent = aMode == eTransparencyTransparent;
+
+ if (mIsTransparent == isTransparent) {
+ return;
+ }
+
+ if (mWindowType != eWindowType_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.
+ CleanLayerManagerRecursive();
+ }
+}
+
+nsTransparencyMode nsWindow::GetTransparencyMode() {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) {
+ return eTransparencyOpaque;
+ }
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) {
+ return eTransparencyOpaque;
+ }
+
+ return topWindow->GetTransparencyMode();
+ }
+
+ return mIsTransparent ? eTransparencyTransparent : eTransparencyOpaque;
+}
+
+void nsWindow::SetWindowMouseTransparent(bool aIsTransparent) {
+ if (!mGdkWindow) {
+ return;
+ }
+
+ cairo_rectangle_int_t emptyRect = {0, 0, 0, 0};
+ cairo_region_t* region =
+ aIsTransparent ? cairo_region_create_rectangle(&emptyRect) : nullptr;
+
+ gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0);
+ if (region) {
+ cairo_region_destroy(region);
+ }
+}
+
+// For setting the draggable titlebar region from CSS
+// with -moz-window-dragging: drag.
+void nsWindow::UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) {
+ if (mDraggableRegion != aRegion) {
+ mDraggableRegion = aRegion;
+ }
+}
+
+// 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) {
+ cairo_rectangle_int_t rect = {aX, aY, TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT};
+ cairo_region_subtract_rectangle(aRegion, &rect);
+ rect = {
+ aX + aWindowWidth - TITLEBAR_SHAPE_MASK_HEIGHT,
+ aY,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ };
+ cairo_region_subtract_rectangle(aRegion, &rect);
+}
+
+void nsWindow::UpdateTopLevelOpaqueRegion(void) {
+ if (!mCompositedScreen) {
+ return;
+ }
+
+ GdkWindow* window =
+ (mDrawToContainer) ? gtk_widget_get_window(mShell) : mGdkWindow;
+ if (!window) {
+ return;
+ }
+ MOZ_ASSERT(gdk_window_get_window_type(window) == GDK_WINDOW_TOPLEVEL);
+
+ int x = 0;
+ int y = 0;
+
+ if (mDrawToContainer) {
+ 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);
+
+ bool subtractCorners = DoDrawTilebarCorners();
+ if (subtractCorners) {
+ SubtractTitlebarCorners(region, x, y, width);
+ }
+
+ gdk_window_set_opaque_region(window, region);
+
+ cairo_region_destroy(region);
+
+#ifdef MOZ_WAYLAND
+ // We don't set opaque region to mozContainer by default due to Bug 1615098.
+ if (!mIsX11Display && gUseWaylandUseOpaqueRegion) {
+ moz_container_wayland_update_opaque_region(mContainer, subtractCorners);
+ }
+#endif
+}
+
+bool nsWindow::IsChromeWindowTitlebar() {
+ return mDrawInTitlebar && !mIsPIPWindow &&
+ mWindowType == eWindowType_toplevel;
+}
+
+bool nsWindow::DoDrawTilebarCorners() {
+ return IsChromeWindowTitlebar() && mSizeState == nsSizeMode_Normal &&
+ !mIsTiled;
+}
+
+nsresult nsWindow::ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) {
+ // If this is a remotely updated widget we receive clipping, position, and
+ // size information from a source other than our owner. Don't let our parent
+ // update this information.
+ if (mWindowType == eWindowType_plugin_ipc_chrome) {
+ return NS_OK;
+ }
+
+ for (uint32_t i = 0; i < aConfigurations.Length(); ++i) {
+ const Configuration& configuration = aConfigurations[i];
+ auto* w = static_cast<nsWindow*>(configuration.mChild.get());
+ NS_ASSERTION(w->GetParent() == this, "Configured widget is not a child");
+ w->SetWindowClipRegion(configuration.mClipRegion, true);
+ if (w->mBounds.Size() != configuration.mBounds.Size()) {
+ w->Resize(configuration.mBounds.x, configuration.mBounds.y,
+ configuration.mBounds.width, configuration.mBounds.height,
+ true);
+ } else if (w->mBounds.TopLeft() != configuration.mBounds.TopLeft()) {
+ w->Move(configuration.mBounds.x, configuration.mBounds.y);
+ }
+ w->SetWindowClipRegion(configuration.mClipRegion, false);
+ }
+ return NS_OK;
+}
+
+nsresult nsWindow::SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects, bool aIntersectWithExisting) {
+ const nsTArray<LayoutDeviceIntRect>* newRects = &aRects;
+
+ AutoTArray<LayoutDeviceIntRect, 1> intersectRects;
+ if (aIntersectWithExisting) {
+ AutoTArray<LayoutDeviceIntRect, 1> existingRects;
+ GetWindowClipRegion(&existingRects);
+
+ LayoutDeviceIntRegion existingRegion = RegionFromArray(existingRects);
+ LayoutDeviceIntRegion newRegion = RegionFromArray(aRects);
+ LayoutDeviceIntRegion intersectRegion;
+ intersectRegion.And(newRegion, existingRegion);
+
+ // If mClipRects is null we haven't set a clip rect yet, so we
+ // need to set the clip even if it is equal.
+ if (mClipRects && intersectRegion.IsEqual(existingRegion)) {
+ return NS_OK;
+ }
+
+ if (!intersectRegion.IsEqual(newRegion)) {
+ ArrayFromRegion(intersectRegion, intersectRects);
+ newRects = &intersectRects;
+ }
+ }
+
+ if (IsWindowClipRegionEqual(*newRects)) return NS_OK;
+
+ StoreWindowClipRegion(*newRects);
+
+ if (!mGdkWindow) return NS_OK;
+
+ cairo_region_t* region = cairo_region_create();
+ for (uint32_t i = 0; i < newRects->Length(); ++i) {
+ const LayoutDeviceIntRect& r = newRects->ElementAt(i);
+ cairo_rectangle_int_t rect = {r.x, r.y, r.width, r.height};
+ cairo_region_union_rectangle(region, &rect);
+ }
+
+ gdk_window_shape_combine_region(mGdkWindow, region, 0, 0);
+ cairo_region_destroy(region);
+
+ return NS_OK;
+}
+
+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);
+#else
+ cairo_surface_t* maskBitmap;
+ maskBitmap = cairo_image_surface_create_for_data(
+ (unsigned char*)mTransparencyBitmap, CAIRO_FORMAT_A1,
+ mTransparencyBitmapWidth, mTransparencyBitmapHeight,
+ GetBitmapStride(mTransparencyBitmapWidth));
+ if (!maskBitmap) return;
+
+ cairo_region_t* maskRegion = gdk_cairo_region_create_from_surface(maskBitmap);
+ gtk_widget_shape_combine_region(mShell, maskRegion);
+ cairo_region_destroy(maskRegion);
+ cairo_surface_destroy(maskBitmap);
+#endif // MOZ_X11
+}
+
+void nsWindow::ClearTransparencyBitmap() {
+ if (!mTransparencyBitmap) return;
+
+ delete[] mTransparencyBitmap;
+ mTransparencyBitmap = nullptr;
+ mTransparencyBitmapWidth = 0;
+ mTransparencyBitmapHeight = 0;
+
+ if (!mShell) return;
+
+#ifdef MOZ_X11
+ if (!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) {
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return NS_ERROR_FAILURE;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return NS_ERROR_FAILURE;
+
+ return topWindow->UpdateTranslucentWindowAlphaInternal(aRect, aAlphas,
+ 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;
+}
+
+bool nsWindow::GetTitlebarRect(mozilla::gfx::Rect& aRect) {
+ if (!mGdkWindow || !mDrawInTitlebar) {
+ return false;
+ }
+
+ aRect = gfx::Rect(0, 0, mBounds.width, TITLEBAR_SHAPE_MASK_HEIGHT);
+ return true;
+}
+
+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;
+
+ if (maskCreate) {
+ if (mTransparencyBitmap) {
+ delete[] mTransparencyBitmap;
+ }
+ int32_t size = GetBitmapStride(mBounds.width) * TITLEBAR_SHAPE_MASK_HEIGHT;
+ 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, TITLEBAR_SHAPE_MASK_HEIGHT);
+ if (!surface) return;
+
+ cairo_t* cr = cairo_create(surface);
+
+ GtkWidgetState state;
+ memset((void*)&state, 0, sizeof(state));
+ GdkRectangle rect = {0, 0, mTransparencyBitmapWidth,
+ TITLEBAR_SHAPE_MASK_HEIGHT};
+
+ 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,
+ TITLEBAR_SHAPE_MASK_HEIGHT,
+ nsIntRect(0, 0, mTransparencyBitmapWidth, TITLEBAR_SHAPE_MASK_HEIGHT),
+ cairo_image_surface_get_data(surface),
+ cairo_format_stride_for_width(CAIRO_FORMAT_A8,
+ mTransparencyBitmapWidth));
+
+ cairo_surface_destroy(surface);
+ }
+
+ if (!mNeedsShow) {
+ Display* xDisplay = GDK_WINDOW_XDISPLAY(mGdkWindow);
+ Window xDrawable = GDK_WINDOW_XID(mGdkWindow);
+
+ Pixmap maskPixmap = XCreateBitmapFromData(
+ xDisplay, xDrawable, mTransparencyBitmap, mTransparencyBitmapWidth,
+ TITLEBAR_SHAPE_MASK_HEIGHT);
+
+ XShapeCombineMask(xDisplay, xDrawable, ShapeBounding, 0, 0, maskPixmap,
+ ShapeSet);
+
+ if (mTransparencyBitmapHeight > TITLEBAR_SHAPE_MASK_HEIGHT) {
+ XRectangle rect = {0, 0, (unsigned short)mTransparencyBitmapWidth,
+ (unsigned short)(mTransparencyBitmapHeight -
+ TITLEBAR_SHAPE_MASK_HEIGHT)};
+ XShapeCombineRectangles(xDisplay, xDrawable, ShapeBounding, 0,
+ TITLEBAR_SHAPE_MASK_HEIGHT, &rect, 1, ShapeUnion,
+ 0);
+ }
+
+ XFreePixmap(xDisplay, maskPixmap);
+ }
+}
+
+void nsWindow::GrabPointer(guint32 aTime) {
+ LOG(("GrabPointer time=0x%08x retry=%d\n", (unsigned int)aTime,
+ mRetryPointerGrab));
+
+ mRetryPointerGrab = false;
+ sRetryGrabTime = aTime;
+
+ // If the window isn't visible, just set the flag to retry the
+ // grab. When this window becomes visible, the grab will be
+ // retried.
+ if (!mHasMappedToplevel) {
+ LOG(("GrabPointer: window not visible\n"));
+ mRetryPointerGrab = true;
+ return;
+ }
+
+ if (!mGdkWindow) return;
+
+ if (!mIsX11Display) {
+ // Don't to the grab on Wayland as it causes a regression
+ // from Bug 1377084.
+ return;
+ }
+
+ gint retval;
+ // Note that we need GDK_TOUCH_MASK below to work around a GDK/X11 bug that
+ // causes touch events that would normally be received by this client on
+ // other windows to be discarded during the grab.
+ retval = gdk_pointer_grab(
+ mGdkWindow, TRUE,
+ (GdkEventMask)(GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
+ GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
+ GDK_POINTER_MOTION_MASK | GDK_TOUCH_MASK),
+ (GdkWindow*)nullptr, nullptr, aTime);
+
+ if (retval == GDK_GRAB_NOT_VIEWABLE) {
+ LOG(("GrabPointer: window not viewable; will retry\n"));
+ mRetryPointerGrab = true;
+ } else if (retval != GDK_GRAB_SUCCESS) {
+ LOG(("GrabPointer: pointer grab failed: %i\n", retval));
+ // A failed grab indicates that another app has grabbed the pointer.
+ // Check for rollup now, because, without the grab, we likely won't
+ // get subsequent button press events. Do this with an event so that
+ // popups don't rollup while potentially adjusting the grab for
+ // this popup.
+ nsCOMPtr<nsIRunnable> event =
+ NewRunnableMethod("nsWindow::CheckForRollupDuringGrab", this,
+ &nsWindow::CheckForRollupDuringGrab);
+ NS_DispatchToCurrentThread(event.forget());
+ }
+}
+
+void nsWindow::ReleaseGrabs(void) {
+ LOG(("ReleaseGrabs\n"));
+
+ mRetryPointerGrab = false;
+
+ if (!mIsX11Display) {
+ // Don't to the ungrab on Wayland as it causes a regression
+ // from Bug 1377084.
+ return;
+ }
+
+ gdk_pointer_ungrab(GDK_CURRENT_TIME);
+}
+
+GtkWidget* nsWindow::GetToplevelWidget() {
+ if (mShell) {
+ return mShell;
+ }
+
+ GtkWidget* widget = GetMozContainerWidget();
+ if (!widget) return nullptr;
+
+ return gtk_widget_get_toplevel(widget);
+}
+
+GtkWidget* nsWindow::GetMozContainerWidget() {
+ if (!mGdkWindow) return nullptr;
+
+ if (mContainer) return GTK_WIDGET(mContainer);
+
+ GtkWidget* owningWidget = get_gtk_widget_for_gdk_window(mGdkWindow);
+ return owningWidget;
+}
+
+nsWindow* nsWindow::GetContainerWindow() {
+ GtkWidget* owningWidget = GetMozContainerWidget();
+ 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) {
+ if (!top_window) return;
+
+ gdk_window_set_urgency_hint(gtk_widget_get_window(top_window), state);
+}
+
+void nsWindow::SetDefaultIcon(void) { SetIcon(u"default"_ns); }
+
+gint nsWindow::ConvertBorderStyles(nsBorderStyle aStyle) {
+ gint w = 0;
+
+ if (aStyle == eBorderStyle_default) return -1;
+
+ // note that we don't handle eBorderStyle_close yet
+ if (aStyle & eBorderStyle_all) w |= GDK_DECOR_ALL;
+ if (aStyle & eBorderStyle_border) w |= GDK_DECOR_BORDER;
+ if (aStyle & eBorderStyle_resizeh) w |= GDK_DECOR_RESIZEH;
+ if (aStyle & eBorderStyle_title) w |= GDK_DECOR_TITLE;
+ if (aStyle & eBorderStyle_menu) w |= GDK_DECOR_MENU;
+ if (aStyle & eBorderStyle_minimize) w |= GDK_DECOR_MINIMIZE;
+ if (aStyle & eBorderStyle_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);
+ gtk_window_set_transient_for(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);
+
+ GdkColor bgColor;
+ bgColor.red = bgColor.green = bgColor.blue = 0;
+ gtk_widget_modify_bg(mWindow, GTK_STATE_NORMAL, &bgColor);
+
+ gtk_window_set_opacity(gtkWin, 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_window_set_opacity(GTK_WINDOW(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<nsIScreen> nsWindow::GetWidgetScreen() {
+ nsCOMPtr<nsIScreenManager> screenManager;
+ screenManager = do_GetService("@mozilla.org/gfx/screenmanager;1");
+ if (!screenManager) {
+ return nullptr;
+ }
+
+ // GetScreenBounds() is slow for the GTK port so we override and use
+ // mBounds directly.
+ LayoutDeviceIntRect bounds = mBounds;
+ if (!mIsTopLevel) {
+ bounds.MoveTo(WidgetToScreenOffset());
+ }
+
+ DesktopIntRect deskBounds = RoundedToInt(bounds / GetDesktopToDeviceScale());
+ nsCOMPtr<nsIScreen> screen;
+ screenManager->ScreenForRect(deskBounds.x, deskBounds.y, deskBounds.width,
+ deskBounds.height, getter_AddRefs(screen));
+ return screen.forget();
+}
+
+RefPtr<VsyncSource> nsWindow::GetVsyncSource() {
+#ifdef MOZ_WAYLAND
+ if (mWaylandVsyncSource) {
+ return mWaylandVsyncSource;
+ }
+#endif
+
+ return nullptr;
+}
+
+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);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ return false;
+ }
+#endif
+ return true;
+}
+
+nsresult nsWindow::MakeFullScreen(bool aFullScreen, nsIScreen* aTargetScreen) {
+ LOG(("nsWindow::MakeFullScreen [%p] aFullScreen %d\n", (void*)this,
+ aFullScreen));
+
+ if (mIsX11Display && !IsFullscreenSupported(mShell)) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ bool wasFullscreen = mSizeState == nsSizeMode_Fullscreen;
+ if (aFullScreen != wasFullscreen && mWidgetListener) {
+ mWidgetListener->FullscreenWillChange(aFullScreen);
+ }
+
+ if (aFullScreen) {
+ if (mSizeMode != nsSizeMode_Fullscreen) mLastSizeMode = mSizeMode;
+
+ mSizeMode = nsSizeMode_Fullscreen;
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell), GDK_WINDOW_TYPE_HINT_NORMAL);
+ if (gUseAspectRatio) {
+ mAspectRatioSaved = mAspectRatio;
+ mAspectRatio = 0.0f;
+ ApplySizeConstraints();
+ }
+ }
+
+ gtk_window_fullscreen(GTK_WINDOW(mShell));
+ } else {
+ mSizeMode = mLastSizeMode;
+ gtk_window_unfullscreen(GTK_WINDOW(mShell));
+
+ if (mIsPIPWindow) {
+ gtk_window_set_type_hint(GTK_WINDOW(mShell),
+ GDK_WINDOW_TYPE_HINT_UTILITY);
+ if (gUseAspectRatio) {
+ mAspectRatio = mAspectRatioSaved;
+ // ApplySizeConstraints();
+ }
+ }
+ }
+
+ NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen,
+ "mLastSizeMode should never be fullscreen");
+ return NS_OK;
+}
+
+void nsWindow::SetWindowDecoration(nsBorderStyle aStyle) {
+ LOG(("nsWindow::SetWindowDecoration() [%p] Border style %x\n", (void*)this,
+ aStyle));
+
+ if (!mShell) {
+ // Pass the request to the toplevel window
+ GtkWidget* topWidget = GetToplevelWidget();
+ if (!topWidget) return;
+
+ nsWindow* topWindow = get_window_for_gtk_widget(topWidget);
+ if (!topWindow) return;
+
+ topWindow->SetWindowDecoration(aStyle);
+ return;
+ }
+
+ // We can't use mGdkWindow directly here as it can be
+ // derived from mContainer which is not a top-level GdkWindow.
+ GdkWindow* window = gtk_widget_get_window(mShell);
+
+ // 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 (mIsX11Display) {
+ XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11False);
+ } else
+#endif /* MOZ_X11 */
+ {
+ gdk_flush();
+ }
+}
+
+void nsWindow::HideWindowChrome(bool aShouldHide) {
+ SetWindowDecoration(aShouldHide ? eBorderStyle_none : mBorderStyle);
+}
+
+bool nsWindow::CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup) {
+ nsIRollupListener* rollupListener = GetActiveRollupListener();
+ nsCOMPtr<nsIWidget> rollupWidget;
+ if (rollupListener) {
+ rollupWidget = rollupListener->GetRollupWidget();
+ }
+ if (!rollupWidget) {
+ nsBaseWidget::gRollupListener = nullptr;
+ return false;
+ }
+
+ bool retVal = false;
+ auto* currentPopup =
+ (GdkWindow*)rollupWidget->GetNativeData(NS_NATIVE_WINDOW);
+ if (aAlwaysRollup || !is_mouse_in_window(currentPopup, aMouseX, aMouseY)) {
+ bool rollup = true;
+ if (aIsWheel) {
+ rollup = rollupListener->ShouldRollupOnMouseWheelEvent();
+ retVal = rollupListener->ShouldConsumeOnMouseWheelEvent();
+ }
+ // 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
+ uint32_t popupsToRollup = UINT32_MAX;
+ 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) {
+ rollup = false;
+ } else {
+ popupsToRollup = sameTypeCount;
+ }
+ break;
+ }
+ } // foreach parent menu widget
+ } // if rollup listener knows about menus
+
+ // if we've determined that we should still rollup, do it.
+ bool usePoint = !aIsWheel && !aAlwaysRollup;
+ IntPoint point;
+ if (usePoint) {
+ LayoutDeviceIntPoint p = GdkEventCoordsToDevicePixels(aMouseX, aMouseY);
+ point = p.ToUnknownPoint();
+ }
+ if (rollup &&
+ rollupListener->Rollup(popupsToRollup, true,
+ usePoint ? &point : nullptr, nullptr)) {
+ retVal = true;
+ }
+ }
+ return retVal;
+}
+
+/* static */
+bool nsWindow::DragInProgress(void) {
+ nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+
+ if (!dragService) return false;
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ dragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ return currentDragSession != nullptr;
+}
+
+static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
+ gdouble aMouseY) {
+ gint x = 0;
+ gint y = 0;
+ gint w, h;
+
+ gint offsetX = 0;
+ gint offsetY = 0;
+
+ GdkWindow* window = aWindow;
+
+ 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);
+ }
+
+ w = gdk_window_get_width(aWindow);
+ h = gdk_window_get_height(aWindow);
+
+ if (aMouseX > x && aMouseX < x + w && aMouseY > y && aMouseY < y + h)
+ return true;
+
+ return false;
+}
+
+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 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();
+
+ // The strategy here is to use standard GDK cursors, and, if not available,
+ // load by standard name with gdk_cursor_new_from_name.
+ // Spec is here: http://www.freedesktop.org/wiki/Specifications/cursor-spec/
+ switch (aCursor) {
+ case eCursor_standard:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ break;
+ case eCursor_wait:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_WATCH);
+ break;
+ case eCursor_select:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_XTERM);
+ break;
+ case eCursor_hyperlink:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_HAND2);
+ break;
+ case eCursor_n_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_SIDE);
+ break;
+ case eCursor_s_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_SIDE);
+ break;
+ case eCursor_w_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_SIDE);
+ break;
+ case eCursor_e_resize:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_RIGHT_SIDE);
+ break;
+ case eCursor_nw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_LEFT_CORNER);
+ break;
+ case eCursor_se_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_RIGHT_CORNER);
+ break;
+ case eCursor_ne_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_TOP_RIGHT_CORNER);
+ break;
+ case eCursor_sw_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_BOTTOM_LEFT_CORNER);
+ break;
+ case eCursor_crosshair:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_CROSSHAIR);
+ break;
+ case eCursor_move:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_help:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_QUESTION_ARROW);
+ break;
+ case eCursor_copy: // CSS3
+ 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_for_display(defaultDisplay, GDK_PLUS);
+ break;
+ // Those two aren’t standardized. Trying both KDE’s and GNOME’s names
+ case eCursor_grab:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "openhand");
+ if (!gdkcursor) newType = MOZ_CURSOR_HAND_GRAB;
+ break;
+ case eCursor_grabbing:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "closedhand");
+ if (!gdkcursor)
+ 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) // nonstandard, yet common
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "crossed_circle");
+ 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:
+ newType = MOZ_CURSOR_VERTICAL_TEXT;
+ break;
+ case eCursor_all_scroll:
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_FLEUR);
+ break;
+ case eCursor_nesw_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_bdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NESW_RESIZE;
+ break;
+ case eCursor_nwse_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "size_fdiag");
+ if (!gdkcursor) newType = MOZ_CURSOR_NWSE_RESIZE;
+ break;
+ case eCursor_ns_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_ew_resize:
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ break;
+ // Here, two better fitting cursors exist in some cursor themes. Try those
+ // first
+ case eCursor_row_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_v");
+ if (!gdkcursor)
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_V_DOUBLE_ARROW);
+ break;
+ case eCursor_col_resize:
+ gdkcursor = gdk_cursor_new_from_name(defaultDisplay, "split_h");
+ if (!gdkcursor)
+ gdkcursor =
+ gdk_cursor_new_for_display(defaultDisplay, GDK_SB_H_DOUBLE_ARROW);
+ break;
+ case eCursor_none:
+ newType = MOZ_CURSOR_NONE;
+ break;
+ default:
+ NS_ASSERTION(aCursor, "Invalid cursor type");
+ gdkcursor = gdk_cursor_new_for_display(defaultDisplay, GDK_LEFT_PTR);
+ 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 = *bits++;
+ char mask = *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_gdk_window(aWindow);
+ 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);
+ }
+ }
+
+ GList* children = gdk_window_get_children(aWindow);
+ GList* child = children;
+ while (child) {
+ GdkWindow* window = GDK_WINDOW(child->data);
+ gpointer windowWidget;
+ gdk_window_get_user_data(window, &windowWidget);
+ if (windowWidget == widget) {
+ draw_window_of_widget(widget, window, cr);
+ }
+ child = g_list_next(child);
+ }
+ g_list_free(children);
+}
+
+/* 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);
+}
+
+static void container_unrealize_cb(GtkWidget* widget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(widget);
+ if (!window) {
+ return;
+ }
+
+ window->OnContainerUnrealize();
+}
+
+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;
+ }
+
+ window->OnEnterNotifyEvent(event);
+
+ return TRUE;
+}
+
+static gboolean leave_notify_event_cb(GtkWidget* widget,
+ GdkEventCrossing* event) {
+ if (is_parent_grab_leave(event)) {
+ return TRUE;
+ }
+
+ // bug 369599: Suppress LeaveNotify events caused by pointer grabs to
+ // avoid generating spurious mouse exit events.
+ auto x = gint(event->x_root);
+ auto y = gint(event->y_root);
+ GdkDisplay* display = gtk_widget_get_display(widget);
+ GdkWindow* winAtPt = gdk_display_get_window_at_pointer(display, &x, &y);
+ if (winAtPt == event->window) {
+ return TRUE;
+ }
+
+ RefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
+ if (!window) return TRUE;
+
+ 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);
+
+ 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);
+
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnButtonPressEvent(event);
+
+ return TRUE;
+}
+
+static gboolean button_release_event_cb(GtkWidget* widget,
+ GdkEventButton* event) {
+ UpdateLastInputEventTime(event);
+
+ 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 deconified.
+ 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) {
+ LOG(("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 (GDK_IS_X11_DISPLAY(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) {
+ LOG(("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) {
+ nsWindow* window = GetFirstNSWindowForGDKWindow(event->window);
+ if (!window) return FALSE;
+
+ window->OnScrollEvent(event);
+
+ 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_changed_cb(GtkSettings* settings, GParamSpec* pspec,
+ nsWindow* data) {
+ if (sIgnoreChangedSettings) {
+ return;
+ }
+ RefPtr<nsWindow> window = data;
+ window->ThemeChanged();
+}
+
+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;
+ }
+
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(widget, &allocation);
+ window->OnScaleChanged(&allocation);
+}
+
+static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) {
+ UpdateLastInputEventTime(aEvent);
+
+ 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);
+
+ 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);
+}
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ 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(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety});
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ return dragService->ScheduleMotionEvent(innerMostWindow, aDragContext,
+ aWaylandDragContext, point, aTime);
+}
+
+static gboolean drag_motion_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragMotionHandler(aWidget, aDragContext, nullptr, aX, aY, aTime);
+}
+
+void WindowDragLeaveHandler(GtkWidget* aWidget) {
+ RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
+ if (!window) return;
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+
+ 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.
+ return;
+ }
+
+ GtkWidget* mozContainer = mostRecentDragWindow->GetMozContainerWidget();
+ if (aWidget != mozContainer) {
+ // 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.
+ return;
+ }
+
+ LOGDRAG(("nsWindow drag-leave signal for %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,
+ nsWaylandDragContext* aWaylandDragContext,
+ 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(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow));
+
+ LayoutDeviceIntPoint point = window->GdkPointToDevicePixels({retx, rety});
+
+ RefPtr<nsDragService> dragService = nsDragService::GetInstance();
+ return dragService->ScheduleDropEvent(innerMostWindow, aDragContext,
+ aWaylandDragContext, point, aTime);
+}
+
+static gboolean drag_drop_event_cb(GtkWidget* aWidget,
+ GdkDragContext* aDragContext, gint aX,
+ gint aY, guint aTime, gpointer aData) {
+ return WindowDragDropHandler(aWidget, aDragContext, nullptr, 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) {
+ gRaiseWindows =
+ Preferences::GetBool("mozilla.widget.raise-on-setfocus", true);
+ gUseWaylandVsync =
+ Preferences::GetBool("widget.wayland_vsync.enabled", false);
+ gUseWaylandUseOpaqueRegion =
+ Preferences::GetBool("widget.wayland.use-opaque-region", false);
+
+ if (Preferences::HasUserValue("widget.use-aspect-ratio")) {
+ gUseAspectRatio = Preferences::GetBool("widget.use-aspect-ratio", true);
+ } else {
+ static const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ gUseAspectRatio =
+ currentDesktop ? (strstr(currentDesktop, "GNOME") != nullptr) : false;
+ }
+
+ return NS_OK;
+}
+
+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;
+}
+
+static int is_parent_ungrab_enter(GdkEventCrossing* aEvent) {
+ return (GDK_CROSSING_UNGRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+}
+
+static int is_parent_grab_leave(GdkEventCrossing* aEvent) {
+ return (GDK_CROSSING_GRAB == aEvent->mode) &&
+ ((GDK_NOTIFY_ANCESTOR == aEvent->detail) ||
+ (GDK_NOTIFY_VIRTUAL == aEvent->detail));
+}
+
+#ifdef ACCESSIBILITY
+void nsWindow::CreateRootAccessible() {
+ if (mIsTopLevel && !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::Accessible* 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;
+}
+
+void nsWindow::GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const WidgetKeyboardEvent& aEvent,
+ nsTArray<CommandInt>& aCommands,
+ uint32_t aGeckoKeyCode,
+ uint32_t aNativeKeyCode) {
+ // If aEvent.mNativeKeyEvent is nullptr, the event was created by chrome
+ // script. In such case, we shouldn't expose the OS settings to it.
+ // So, just ignore such events here.
+ if (!aEvent.mNativeKeyEvent) {
+ return;
+ }
+ WidgetKeyboardEvent modifiedEvent(aEvent);
+ modifiedEvent.mKeyCode = aGeckoKeyCode;
+ static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval =
+ aNativeKeyCode;
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(modifiedEvent, aCommands);
+}
+
+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;
+ }
+
+ if (aEvent.mKeyCode >= NS_VK_LEFT && aEvent.mKeyCode <= NS_VK_DOWN) {
+ // Check if we're targeting content with vertical writing mode,
+ // and if so remap the arrow keys.
+ // XXX This may be expensive.
+ WidgetQueryContentEvent querySelectedTextEvent(true, eQuerySelectedText,
+ this);
+ nsEventStatus status;
+ DispatchEvent(&querySelectedTextEvent, status);
+
+ if (querySelectedTextEvent.FoundSelection() &&
+ querySelectedTextEvent.mReply->mWritingMode.IsVertical()) {
+ uint32_t geckoCode = 0;
+ uint32_t gdkCode = 0;
+ switch (aEvent.mKeyCode) {
+ case NS_VK_LEFT:
+ if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ } else {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ }
+ break;
+
+ case NS_VK_RIGHT:
+ if (querySelectedTextEvent.mReply->mWritingMode.IsVerticalLR()) {
+ geckoCode = NS_VK_DOWN;
+ gdkCode = GDK_Down;
+ } else {
+ geckoCode = NS_VK_UP;
+ gdkCode = GDK_Up;
+ }
+ break;
+
+ case NS_VK_UP:
+ geckoCode = NS_VK_LEFT;
+ gdkCode = GDK_Left;
+ break;
+
+ case NS_VK_DOWN:
+ geckoCode = NS_VK_RIGHT;
+ gdkCode = GDK_Right;
+ break;
+ }
+
+ GetEditCommandsRemapped(aType, aEvent, aCommands, geckoCode, gdkCode);
+ return true;
+ }
+ }
+
+ NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType);
+ keyBindings->GetEditCommands(aEvent, aCommands);
+ return true;
+}
+
+already_AddRefed<DrawTarget> nsWindow::StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) {
+ return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion,
+ aBufferMode);
+}
+
+void nsWindow::EndRemoteDrawingInRegion(
+ DrawTarget* aDrawTarget, const LayoutDeviceIntRegion& aInvalidRegion) {
+ mSurfaceProvider.EndRemoteDrawingInRegion(aDrawTarget, aInvalidRegion);
+}
+
+// Code shared begin BeginMoveDrag and BeginResizeDrag
+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;
+ }
+
+ if (mIsX11Display) {
+ // 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(gdk_window);
+ GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
+ if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
+ static unsigned int lastTimeStamp = 0;
+ if (lastTimeStamp != aMouseEvent->mTime) {
+ lastTimeStamp = aMouseEvent->mTime;
+ } else {
+ return false;
+ }
+ }
+ }
+
+ // 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;
+}
+
+nsresult nsWindow::BeginResizeDrag(WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) {
+ NS_ENSURE_ARG_POINTER(aEvent);
+
+ if (aEvent->mClass != eMouseEventClass) {
+ // you can only begin a resize drag with a mouse event
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ GdkWindow* gdk_window;
+ gint button, screenX, screenY;
+ if (!GetDragInfo(aEvent->AsMouseEvent(), &gdk_window, &button, &screenX,
+ &screenY)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // work out what GdkWindowEdge we're talking about
+ GdkWindowEdge window_edge;
+ if (aVertical < 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_NORTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_NORTH_EAST;
+ }
+ } else if (aVertical == 0) {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_WEST;
+ } else if (aHorizontal == 0) {
+ return NS_ERROR_INVALID_ARG;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_EAST;
+ }
+ } else {
+ if (aHorizontal < 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_WEST;
+ } else if (aHorizontal == 0) {
+ window_edge = GDK_WINDOW_EDGE_SOUTH;
+ } else {
+ window_edge = GDK_WINDOW_EDGE_SOUTH_EAST;
+ }
+ }
+
+ // tell the window manager to start the resize
+ gdk_window_begin_resize_drag(gdk_window, window_edge, button, screenX,
+ screenY, aEvent->mTime);
+
+ return NS_OK;
+}
+
+nsIWidget::LayerManager* nsWindow::GetLayerManager(
+ PLayerTransactionChild* aShadowManager, LayersBackend aBackendHint,
+ LayerManagerPersistence aPersistence) {
+ 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 mLayerManager;
+ }
+
+ return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint,
+ aPersistence);
+}
+
+void nsWindow::SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) {
+ if (delegate) {
+ mCompositorWidgetDelegate = delegate->AsPlatformSpecificDelegate();
+ MOZ_ASSERT(mCompositorWidgetDelegate,
+ "nsWindow::SetCompositorWidgetDelegate called with a "
+ "non-PlatformCompositorWidgetDelegate");
+#ifdef MOZ_WAYLAND
+ MaybeResumeCompositor();
+#endif
+ } else {
+ mCompositorWidgetDelegate = nullptr;
+ }
+}
+
+void nsWindow::ClearCachedResources() {
+ if (mLayerManager && mLayerManager->GetBackendType() ==
+ mozilla::layers::LayersBackend::LAYERS_BASIC) {
+ mLayerManager->ClearCachedResources();
+ }
+
+ GList* children = gdk_window_peek_children(mGdkWindow);
+ for (GList* list = children; list; list = list->next) {
+ nsWindow* window = get_window_for_gdk_window(GDK_WINDOW(list->data));
+ if (window) {
+ window->ClearCachedResources();
+ }
+ }
+}
+
+/* nsWindow::UpdateClientOffsetFromCSDWindow() is designed to be called from
+ * nsWindow::OnConfigureEvent() when mContainer window is already positioned.
+ *
+ * It works only for CSD decorated GtkWindow.
+ */
+void nsWindow::UpdateClientOffsetFromCSDWindow() {
+ int x, y;
+ gdk_window_get_position(mGdkWindow, &x, &y);
+
+ x = GdkCoordToDevicePixels(x);
+ y = GdkCoordToDevicePixels(y);
+
+ if (mClientOffset.x != x || mClientOffset.y != y) {
+ mClientOffset = nsIntPoint(x, y);
+
+ LOG(("nsWindow::UpdateClientOffsetFromCSDWindow [%p] %d, %d\n", (void*)this,
+ mClientOffset.x, mClientOffset.y));
+
+ // 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);
+ }
+}
+
+nsresult nsWindow::SetNonClientMargins(LayoutDeviceIntMargin& aMargins) {
+ SetDrawsInTitlebar(aMargins.top == 0);
+ return NS_OK;
+}
+
+void nsWindow::SetDrawsInTitlebar(bool aState) {
+ LOG(("nsWindow::SetDrawsInTitlebar() [%p] State %d mCSDSupportLevel %d\n",
+ (void*)this, aState, (int)mCSDSupportLevel));
+
+ if (!mShell || mCSDSupportLevel == CSD_SUPPORT_NONE ||
+ aState == mDrawInTitlebar) {
+ return;
+ }
+
+ if (mCSDSupportLevel == CSD_SUPPORT_SYSTEM) {
+ SetWindowDecoration(aState ? eBorderStyle_border : mBorderStyle);
+ } else if (mCSDSupportLevel == CSD_SUPPORT_CLIENT) {
+ LOG((" Using CSD mode\n"));
+
+ /* 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.
+ */
+ 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));
+
+ if (aState) {
+ // 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), gtk_fixed_new());
+ } else {
+ gtk_window_set_titlebar(GTK_WINDOW(mShell), 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));
+ mNeedsShow = true;
+ NativeResize();
+
+ // Label mShell toplevel window so property_notify_event_cb callback
+ // can find its way home.
+ g_object_set_data(G_OBJECT(gtk_widget_get_window(mShell)), "nsWindow",
+ this);
+#ifdef MOZ_X11
+ SetCompositorHint(GTK_WIDGET_COMPOSIDED_ENABLED);
+#endif
+ RefreshWindowClass();
+
+ gtk_widget_destroy(tmpWindow);
+ }
+
+ mDrawInTitlebar = aState;
+
+ if (mTransparencyBitmapForTitlebar) {
+ if (mDrawInTitlebar && mSizeState == nsSizeMode_Normal && !mIsTiled) {
+ UpdateTitlebarTransparencyBitmap();
+ } else {
+ ClearTransparencyBitmap();
+ }
+ }
+}
+
+GtkWindow* nsWindow::GetCurrentTopmostWindow() {
+ GtkWindow* parentWindow = GTK_WINDOW(GetGtkWidget());
+ GtkWindow* topmostParentWindow = nullptr;
+ while (parentWindow) {
+ topmostParentWindow = parentWindow;
+ parentWindow = gtk_window_get_transient_for(parentWindow);
+ }
+ return topmostParentWindow;
+}
+
+gint nsWindow::GdkScaleFactor() {
+ // We depend on notify::scale-factor callback which is reliable for toplevel
+ // windows only, so don't use scale cache for popup windows.
+ if (mWindowType == eWindowType_toplevel && !mWindowScaleFactorChanged) {
+ return mWindowScaleFactor;
+ }
+
+ GdkWindow* scaledGdkWindow = mGdkWindow;
+ if (!mIsX11Display) {
+ // For popup windows/dialogs with parent window we need to get scale factor
+ // of the topmost window. Otherwise the scale factor of the popup is
+ // not updated during it's hidden.
+ if (mWindowType == eWindowType_popup || mWindowType == eWindowType_dialog) {
+ // Get toplevel window for scale factor:
+ GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
+ if (topmostParentWindow) {
+ scaledGdkWindow =
+ gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
+ } else {
+ NS_WARNING("Popup/Dialog has no parent.");
+ }
+ // Fallback for windows which parent has been unrealized.
+ if (!scaledGdkWindow) {
+ scaledGdkWindow = mGdkWindow;
+ }
+ }
+ }
+
+ // Available as of GTK 3.10+
+ static auto sGdkWindowGetScaleFactorPtr =
+ (gint(*)(GdkWindow*))dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor");
+ if (sGdkWindowGetScaleFactorPtr && scaledGdkWindow) {
+ mWindowScaleFactor = (*sGdkWindowGetScaleFactorPtr)(scaledGdkWindow);
+ mWindowScaleFactorChanged = false;
+ } else {
+ mWindowScaleFactor = ScreenHelperGTK::GetGTKMonitorScaleFactor();
+ }
+
+ return mWindowScaleFactor;
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundUp(int pixels) {
+ gint scale = GdkScaleFactor();
+ return (pixels + scale - 1) / scale;
+}
+
+gint nsWindow::DevicePixelsToGdkCoordRoundDown(int pixels) {
+ gint scale = GdkScaleFactor();
+ return pixels / scale;
+}
+
+GdkPoint nsWindow::DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point) {
+ gint scale = GdkScaleFactor();
+ return {point.x / scale, point.y / scale};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect rect) {
+ gint scale = GdkScaleFactor();
+ int x = rect.x / scale;
+ int y = rect.y / scale;
+ int right = (rect.x + rect.width + scale - 1) / scale;
+ int bottom = (rect.y + rect.height + scale - 1) / scale;
+ return {x, y, right - x, bottom - y};
+}
+
+GdkRectangle nsWindow::DevicePixelsToGdkSizeRoundUp(
+ LayoutDeviceIntSize pixelSize) {
+ gint scale = GdkScaleFactor();
+ gint width = (pixelSize.width + scale - 1) / scale;
+ gint height = (pixelSize.height + scale - 1) / scale;
+ return {0, 0, width, height};
+}
+
+int nsWindow::GdkCoordToDevicePixels(gint coord) {
+ return coord * GdkScaleFactor();
+}
+
+LayoutDeviceIntPoint nsWindow::GdkEventCoordsToDevicePixels(gdouble x,
+ gdouble y) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint::Round(x * scale, y * scale);
+}
+
+LayoutDeviceIntPoint nsWindow::GdkPointToDevicePixels(GdkPoint point) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntPoint(point.x * scale, point.y * scale);
+}
+
+LayoutDeviceIntRect nsWindow::GdkRectToDevicePixels(GdkRectangle rect) {
+ gint scale = GdkScaleFactor();
+ return LayoutDeviceIntRect(rect.x * scale, rect.y * scale, rect.width * scale,
+ rect.height * scale);
+}
+
+nsresult nsWindow::SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) {
+ AutoObserverNotifier notifier(aObserver, "mouseevent");
+
+ if (!mGdkWindow) {
+ return NS_OK;
+ }
+
+ GdkDisplay* display = gdk_window_get_display(mGdkWindow);
+
+ // 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.
+ if (aNativeMessage == GDK_BUTTON_PRESS ||
+ aNativeMessage == GDK_BUTTON_RELEASE) {
+ GdkEvent event;
+ memset(&event, 0, sizeof(GdkEvent));
+ event.type = (GdkEventType)aNativeMessage;
+ event.button.button = 1;
+ event.button.window = mGdkWindow;
+ event.button.time = GDK_CURRENT_TIME;
+
+ // Get device for event source
+ GdkDeviceManager* device_manager = gdk_display_get_device_manager(display);
+ event.button.device = gdk_device_manager_get_client_pointer(device_manager);
+
+ 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);
+ } else {
+ // 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().
+ GdkScreen* screen = gdk_window_get_screen(mGdkWindow);
+ GdkPoint point = DevicePixelsToGdkPointRoundDown(aPoint);
+ gdk_display_warp_pointer(display, screen, point.x, point.y);
+ }
+
+ return NS_OK;
+}
+
+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;
+ }
+
+ 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);
+ 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);
+
+ 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;
+}
+
+nsWindow::CSDSupportLevel nsWindow::GetSystemCSDSupportLevel(bool aIsPopup) {
+ if (sCSDSupportLevel != CSD_SUPPORT_UNKNOWN) {
+ return sCSDSupportLevel;
+ }
+
+ // Allow MOZ_GTK_TITLEBAR_DECORATION to override our heuristics
+ const char* decorationOverride = getenv("MOZ_GTK_TITLEBAR_DECORATION");
+ if (decorationOverride) {
+ if (strcmp(decorationOverride, "none") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ } else if (strcmp(decorationOverride, "client") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strcmp(decorationOverride, "system") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ }
+ return sCSDSupportLevel;
+ }
+
+ // nsWindow::GetSystemCSDSupportLevel can be called from various threads
+ // so we can't use gfxPlatformGtk here.
+ if (!GDK_IS_X11_DISPLAY(gdk_display_get_default())) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ return sCSDSupportLevel;
+ }
+
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop) {
+ // GNOME Flashback (fallback)
+ if (strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr) {
+ sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM;
+ // Pop Linux Bug 1629198
+ } else if (strstr(currentDesktop, "pop:GNOME") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ // gnome-shell
+ } else if (strstr(currentDesktop, "GNOME") != nullptr) {
+ sCSDSupportLevel = aIsPopup ? CSD_SUPPORT_CLIENT : CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "XFCE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "X-Cinnamon") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ // KDE Plasma
+ } else if (strstr(currentDesktop, "KDE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "Enlightenment") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "LXDE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "openbox") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else if (strstr(currentDesktop, "i3") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ } else if (strstr(currentDesktop, "MATE") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ // Ubuntu Unity
+ } else if (strstr(currentDesktop, "Unity") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ // Elementary OS
+ } else if (strstr(currentDesktop, "Pantheon") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "LXQt") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_SYSTEM;
+ } else if (strstr(currentDesktop, "Deepin") != nullptr) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ } else {
+// Release or beta builds are not supposed to be broken
+// so disable titlebar rendering on untested/unknown systems.
+#if defined(RELEASE_OR_BETA)
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+#else
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+#endif
+ }
+ } else {
+ sCSDSupportLevel = CSD_SUPPORT_NONE;
+ }
+
+ // 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 (sCSDSupportLevel == CSD_SUPPORT_SYSTEM) {
+ const char* csdOverride = getenv("GTK_CSD");
+ if (csdOverride && g_strcmp0(csdOverride, "1") == 0) {
+ sCSDSupportLevel = CSD_SUPPORT_CLIENT;
+ }
+ }
+
+ return sCSDSupportLevel;
+}
+
+bool nsWindow::TitlebarUseShapeMask() {
+ static int useShapeMask = []() {
+ // Don't use titlebar shape mask on Wayland
+ if (!gfxPlatformGtk::GetPlatform()->IsX11Display()) {
+ return false;
+ }
+
+ // We can'y use shape masks on Mutter/X.org as we can't resize Firefox
+ // window there (Bug 1530252).
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (currentDesktop) {
+ if (strstr(currentDesktop, "GNOME") != nullptr) {
+ const char* sessionType = getenv("XDG_SESSION_TYPE");
+ if (sessionType && strstr(sessionType, "x11") != nullptr) {
+ return false;
+ }
+ }
+ }
+
+ return Preferences::GetBool("widget.titlebar-x11-use-shape-mask", false);
+ }();
+ return useShapeMask;
+}
+
+bool nsWindow::HideTitlebarByDefault() {
+ static int hideTitlebar = []() {
+ // When user defined widget.default-hidden-titlebar don't do any
+ // heuristics and just follow it.
+ if (Preferences::HasUserValue("widget.default-hidden-titlebar")) {
+ return Preferences::GetBool("widget.default-hidden-titlebar", false);
+ }
+
+ // Don't hide titlebar when it's disabled on current desktop.
+ const char* currentDesktop = getenv("XDG_CURRENT_DESKTOP");
+ if (!currentDesktop || GetSystemCSDSupportLevel() == CSD_SUPPORT_NONE) {
+ return false;
+ }
+
+ // We hide system titlebar on Gnome/ElementaryOS without any restriction.
+ return ((strstr(currentDesktop, "GNOME-Flashback:GNOME") != nullptr ||
+ strstr(currentDesktop, "GNOME") != nullptr ||
+ strstr(currentDesktop, "Pantheon") != nullptr));
+ }();
+ return hideTitlebar;
+}
+
+int32_t nsWindow::RoundsWidgetCoordinatesTo() { return GdkScaleFactor(); }
+
+void nsWindow::GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) {
+ // Make sure the window XID is propagated to X server, we can fail otherwise
+ // in GPU process (Bug 1401634).
+ if (mXDisplay && mXWindow != X11None) {
+ XFlush(mXDisplay);
+ }
+
+ bool isShaped =
+ mIsTransparent && !mHasAlphaVisual && !mTransparencyBitmapForTitlebar;
+ *aInitData = mozilla::widget::GtkCompositorWidgetInitData(
+ (mXWindow != X11None) ? mXWindow : (uintptr_t) nullptr,
+ mXDisplay ? nsCString(XDisplayString(mXDisplay)) : nsCString(), isShaped,
+ mIsX11Display, GetClientSize());
+}
+
+#ifdef MOZ_WAYLAND
+bool nsWindow::WaylandSurfaceNeedsClear() {
+ if (mContainer) {
+ return moz_container_wayland_surface_needs_clear(MOZ_CONTAINER(mContainer));
+ }
+ return false;
+}
+#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 (!mIsX11Display) {
+ return;
+ }
+
+ if (!mShell) {
+ return;
+ }
+
+ progressPercent = MIN(progressPercent, 100);
+
+ set_window_hint_cardinal(GDK_WINDOW_XID(gtk_widget_get_window(mShell)),
+ PROGRESS_HINT, progressPercent);
+#endif // MOZ_X11
+}
+
+#ifdef MOZ_X11
+void nsWindow::SetCompositorHint(WindowComposeRequest aState) {
+ if (!mIsX11Display) {
+ return;
+ }
+
+ gulong value = aState;
+ GdkAtom cardinal_atom = gdk_x11_xatom_to_atom(XA_CARDINAL);
+ gdk_property_change(gtk_widget_get_window(mShell),
+ 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
+nsresult nsWindow::GetScreenRect(LayoutDeviceIntRect* aRect) {
+ typedef struct _GdkMonitor GdkMonitor;
+ static auto s_gdk_display_get_monitor_at_window =
+ (GdkMonitor * (*)(GdkDisplay*, GdkWindow*))
+ dlsym(RTLD_DEFAULT, "gdk_display_get_monitor_at_window");
+
+ static auto s_gdk_monitor_get_workarea =
+ (void (*)(GdkMonitor*, GdkRectangle*))dlsym(RTLD_DEFAULT,
+ "gdk_monitor_get_workarea");
+
+ if (!s_gdk_display_get_monitor_at_window || !s_gdk_monitor_get_workarea) {
+ return NS_ERROR_NOT_IMPLEMENTED;
+ }
+
+ GtkWindow* topmostParentWindow = GetCurrentTopmostWindow();
+ GdkWindow* gdkWindow = gtk_widget_get_window(GTK_WIDGET(topmostParentWindow));
+
+ GdkMonitor* monitor =
+ s_gdk_display_get_monitor_at_window(gdk_display_get_default(), gdkWindow);
+ if (monitor) {
+ GdkRectangle workArea;
+ s_gdk_monitor_get_workarea(monitor, &workArea);
+ // The monitor offset won't help us in Wayland, because we can't get the
+ // absolute position of our window.
+ aRect->x = aRect->y = 0;
+ aRect->width = workArea.width;
+ aRect->height = workArea.height;
+ LOG((" workarea for [%p], monitor %p: x%d y%d w%d h%d\n", this, monitor,
+ workArea.x, workArea.y, workArea.width, workArea.height));
+ return NS_OK;
+ }
+ return NS_ERROR_NOT_IMPLEMENTED;
+}
+#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;
+ }
+
+ // Get our toplevel nsWindow.
+ if (!window->mIsTopLevel) {
+ GtkWidget* widget = window->GetMozContainerWidget();
+ if (!widget) {
+ return false;
+ }
+
+ GtkWidget* toplevelWidget = gtk_widget_get_toplevel(widget);
+ window = get_window_for_gtk_widget(toplevelWidget);
+ 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(void) {
+ 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(void) {
+ 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);
+ }
+ }
+}
+
+GtkTextDirection nsWindow::GetTextDirection() {
+ nsIFrame* frame = GetFrame();
+ if (!frame) {
+ return GTK_TEXT_DIR_LTR;
+ }
+
+ WritingMode wm = frame->GetWritingMode();
+ return wm.IsPhysicalLTR() ? GTK_TEXT_DIR_LTR : GTK_TEXT_DIR_RTL;
+}
+
+void nsWindow::LockAspectRatio(bool aShouldLock) {
+ if (!gUseAspectRatio) {
+ return;
+ }
+
+ if (aShouldLock) {
+ int decWidth = 0, decHeight = 0;
+ AddCSDDecorationSize(&decWidth, &decHeight);
+
+ float width =
+ (float)DevicePixelsToGdkCoordRoundDown(mBounds.width) + decWidth;
+ float height =
+ (float)DevicePixelsToGdkCoordRoundDown(mBounds.height) + decHeight;
+
+ mAspectRatio = width / height;
+ LOG(("nsWindow::LockAspectRatio() [%p] width %f height %f aspect %f\n",
+ (void*)this, width, height, mAspectRatio));
+ } else {
+ mAspectRatio = 0.0;
+ LOG(("nsWindow::LockAspectRatio() [%p] removed aspect ratio\n",
+ (void*)this));
+ }
+
+ ApplySizeConstraints();
+}
+
+#ifdef MOZ_WAYLAND
+void nsWindow::SetEGLNativeWindowSize(
+ const LayoutDeviceIntSize& aEGLWindowSize) {
+ if (mContainer && !mIsX11Display) {
+ moz_container_wayland_egl_window_set_size(mContainer, aEGLWindowSize.width,
+ aEGLWindowSize.height);
+ }
+}
+
+nsWindow* nsWindow::GetFocusedWindow() { return gFocusWindow; }
+#endif
+
+LayoutDeviceIntRect nsWindow::GetMozContainerSize() {
+ LayoutDeviceIntRect size(0, 0, 0, 0);
+ if (mContainer) {
+ GtkAllocation allocation;
+ gtk_widget_get_allocation(GTK_WIDGET(mContainer), &allocation);
+ int scale = GdkScaleFactor();
+ size.width = allocation.width * scale;
+ size.height = allocation.height * scale;
+ }
+ return size;
+}
diff --git a/widget/gtk/nsWindow.h b/widget/gtk/nsWindow.h
new file mode 100644
index 0000000000..5bfdcd9291
--- /dev/null
+++ b/widget/gtk/nsWindow.h
@@ -0,0 +1,719 @@
+/* -*- 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 __nsWindow_h__
+#define __nsWindow_h__
+
+#include <gdk/gdk.h>
+#include <gtk/gtk.h>
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+# include "X11UndefineNone.h"
+#endif /* MOZ_X11 */
+#ifdef MOZ_WAYLAND
+# include <gdk/gdkwayland.h>
+# include "base/thread.h"
+# include "WaylandVsyncSource.h"
+#endif
+#include "MozContainer.h"
+#include "mozilla/RefPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIDragService.h"
+#include "nsGkAtoms.h"
+#include "nsRefPtrHashtable.h"
+#include "nsBaseWidget.h"
+#include "CompositorWidget.h"
+#include "mozilla/widget/WindowSurface.h"
+#include "mozilla/widget/WindowSurfaceProvider.h"
+#include "mozilla/Maybe.h"
+
+#ifdef ACCESSIBILITY
+# include "mozilla/a11y/Accessible.h"
+#endif
+#include "mozilla/EventForwards.h"
+#include "mozilla/TouchEvents.h"
+
+#include "IMContextWrapper.h"
+
+#undef LOG
+#ifdef MOZ_LOGGING
+
+# include "mozilla/Logging.h"
+# include "nsTArray.h"
+# include "Units.h"
+
+extern mozilla::LazyLogModule gWidgetLog;
+extern mozilla::LazyLogModule gWidgetFocusLog;
+extern mozilla::LazyLogModule gWidgetDragLog;
+extern mozilla::LazyLogModule gWidgetDrawLog;
+
+# define LOG(args) MOZ_LOG(gWidgetLog, mozilla::LogLevel::Debug, args)
+# define LOGFOCUS(args) \
+ MOZ_LOG(gWidgetFocusLog, mozilla::LogLevel::Debug, args)
+# define LOGDRAG(args) MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args)
+# define LOGDRAW(args) MOZ_LOG(gWidgetDrawLog, mozilla::LogLevel::Debug, args)
+
+#else
+
+# define LOG(args)
+# define LOGFOCUS(args)
+# define LOGDRAG(args)
+# define LOGDRAW(args)
+
+#endif /* MOZ_LOGGING */
+
+#ifdef MOZ_WAYLAND
+class nsWaylandDragContext;
+
+gboolean WindowDragMotionHandler(GtkWidget* aWidget,
+ GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime);
+gboolean WindowDragDropHandler(GtkWidget* aWidget, GdkDragContext* aDragContext,
+ nsWaylandDragContext* aWaylandDragContext,
+ gint aX, gint aY, guint aTime);
+void WindowDragLeaveHandler(GtkWidget* aWidget);
+#endif
+
+class gfxPattern;
+class nsIFrame;
+#if !GTK_CHECK_VERSION(3, 18, 0)
+struct _GdkEventTouchpadPinch;
+typedef struct _GdkEventTouchpadPinch GdkEventTouchpadPinch;
+
+#endif
+
+namespace mozilla {
+class TimeStamp;
+class CurrentX11TimeGetter;
+
+} // 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)
+
+ void CommonCreate(nsIWidget* aParent, bool aListenForResizes);
+
+ virtual nsresult DispatchEvent(mozilla::WidgetGUIEvent* aEvent,
+ nsEventStatus& aStatus) override;
+
+ // called when we are destroyed
+ virtual void OnDestroy(void) 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]] virtual nsresult Create(nsIWidget* aParent,
+ nsNativeWidget aNativeParent,
+ const LayoutDeviceIntRect& aRect,
+ nsWidgetInitData* aInitData) override;
+ virtual void Destroy() override;
+ virtual nsIWidget* GetParent() override;
+ virtual float GetDPI() override;
+ virtual double GetDefaultScaleInternal() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScale() override;
+ mozilla::DesktopToLayoutDeviceScale GetDesktopToDeviceScaleByScreen()
+ override;
+ virtual void SetParent(nsIWidget* aNewParent) override;
+ virtual void SetModal(bool aModal) override;
+ virtual bool IsVisible() const override;
+ virtual void ConstrainPosition(bool aAllowSlop, int32_t* aX,
+ int32_t* aY) override;
+ virtual void SetSizeConstraints(const SizeConstraints& aConstraints) override;
+ virtual void LockAspectRatio(bool aShouldLock) override;
+ virtual void Move(double aX, double aY) override;
+ virtual void Show(bool aState) override;
+ virtual void Resize(double aWidth, double aHeight, bool aRepaint) override;
+ virtual void Resize(double aX, double aY, double aWidth, double aHeight,
+ bool aRepaint) override;
+ virtual bool IsEnabled() const override;
+
+ void SetZIndex(int32_t aZIndex) override;
+ virtual void SetSizeMode(nsSizeMode aMode) override;
+ virtual void GetWorkspaceID(nsAString& workspaceID) override;
+ virtual void MoveToWorkspace(const nsAString& workspaceID) override;
+ virtual void Enable(bool aState) override;
+ virtual void SetFocus(Raise, mozilla::dom::CallerType aCallerType) override;
+ virtual LayoutDeviceIntRect GetScreenBounds() override;
+ virtual LayoutDeviceIntRect GetClientBounds() override;
+ virtual LayoutDeviceIntSize GetClientSize() override;
+ virtual LayoutDeviceIntPoint GetClientOffset() override;
+ virtual void SetCursor(nsCursor aDefaultCursor, imgIContainer* aCursor,
+ uint32_t aHotspotX, uint32_t aHotspotY) override;
+ virtual void Invalidate(const LayoutDeviceIntRect& aRect) override;
+ virtual void* GetNativeData(uint32_t aDataType) override;
+ virtual nsresult SetTitle(const nsAString& aTitle) override;
+ virtual void SetIcon(const nsAString& aIconSpec) override;
+ virtual void SetWindowClass(const nsAString& xulWinType) override;
+ virtual LayoutDeviceIntPoint WidgetToScreenOffset() override;
+ virtual void CaptureMouse(bool aCapture) override;
+ virtual void CaptureRollupEvents(nsIRollupListener* aListener,
+ bool aDoCapture) override;
+ [[nodiscard]] virtual nsresult GetAttention(int32_t aCycleCount) override;
+ virtual nsresult SetWindowClipRegion(
+ const nsTArray<LayoutDeviceIntRect>& aRects,
+ bool aIntersectWithExisting) override;
+ virtual bool HasPendingInputEvent() override;
+
+ virtual bool PrepareForFullscreenTransition(nsISupports** aData) override;
+ virtual void PerformFullscreenTransition(FullscreenTransitionStage aStage,
+ uint16_t aDuration,
+ nsISupports* aData,
+ nsIRunnable* aCallback) override;
+ virtual already_AddRefed<nsIScreen> GetWidgetScreen() override;
+ virtual nsresult MakeFullScreen(bool aFullScreen,
+ nsIScreen* aTargetScreen = nullptr) override;
+ virtual 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(nsBorderStyle aStyle);
+
+ GdkRectangle DevicePixelsToGdkRectRoundOut(LayoutDeviceIntRect aRect);
+
+ 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 OnContainerUnrealize();
+ 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 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);
+
+ void UpdateTopLevelOpaqueRegion();
+
+ virtual already_AddRefed<mozilla::gfx::DrawTarget> StartRemoteDrawingInRegion(
+ LayoutDeviceIntRegion& aInvalidRegion,
+ mozilla::layers::BufferMode* aBufferMode) override;
+ virtual void EndRemoteDrawingInRegion(
+ mozilla::gfx::DrawTarget* aDrawTarget,
+ const LayoutDeviceIntRegion& aInvalidRegion) override;
+
+ void SetProgress(unsigned long progressPercent);
+
+#ifdef MOZ_WAYLAND
+ void SetEGLNativeWindowSize(const LayoutDeviceIntSize& aEGLWindowSize);
+ static nsWindow* GetFocusedWindow();
+#endif
+
+ RefPtr<mozilla::gfx::VsyncSource> GetVsyncSource() override;
+
+ static void WithSettingsChangesIgnored(const std::function<void()>& aFn);
+
+ private:
+ void UpdateAlpha(mozilla::gfx::SourceSurface* aSourceSurface,
+ nsIntRect aBoundsRect);
+
+ void NativeMove();
+ void NativeResize();
+ void NativeMoveResize();
+
+ void NativeShow(bool aAction);
+ void SetHasMappedToplevel(bool aState);
+ LayoutDeviceIntSize GetSafeWindowSize(LayoutDeviceIntSize aSize);
+
+ void EnsureGrabs(void);
+ void GrabPointer(guint32 aTime);
+ void ReleaseGrabs(void);
+
+ void UpdateClientOffsetFromFrameExtents();
+ void UpdateClientOffsetFromCSDWindow();
+
+ void DispatchContextMenuEventFromMouseEvent(uint16_t domButton,
+ GdkEventButton* aEvent);
+#ifdef MOZ_WAYLAND
+ void MaybeResumeCompositor();
+#endif
+
+ void WaylandStartVsync();
+ void WaylandStopVsync();
+
+ public:
+ void ThemeChanged(void);
+ void OnDPIChanged(void);
+ void OnCheckResize(void);
+ void OnCompositedChanged(void);
+ void OnScaleChanged(GtkAllocation* aAllocation);
+ void DispatchResized();
+
+#ifdef MOZ_X11
+ Window mOldFocusWindow;
+#endif /* MOZ_X11 */
+
+ static guint32 sLastButtonPressTime;
+
+ [[nodiscard]] virtual nsresult BeginResizeDrag(
+ mozilla::WidgetGUIEvent* aEvent, int32_t aHorizontal,
+ int32_t aVertical) override;
+
+ MozContainer* GetMozContainer() { return mContainer; }
+ LayoutDeviceIntRect GetMozContainerSize();
+ // GetMozContainerWidget returns the MozContainer even for undestroyed
+ // descendant windows
+ GtkWidget* GetMozContainerWidget();
+ GdkWindow* GetGdkWindow() { return mGdkWindow; }
+ GtkWidget* GetGtkWidget() { return mShell; }
+ nsIFrame* GetFrame();
+ bool IsDestroyed() { return mIsDestroyed; }
+ bool IsPopup();
+ bool IsWaylandPopup();
+ bool IsPIPWindow() { return mIsPIPWindow; };
+
+ void DispatchDragEvent(mozilla::EventMessage aMsg,
+ const LayoutDeviceIntPoint& aRefPoint, guint aTime);
+ static void UpdateDragStatus(GdkDragContext* aDragContext,
+ nsIDragService* aDragService);
+
+ WidgetEventTime GetWidgetEventTime(guint32 aEventTime);
+ mozilla::TimeStamp GetEventTimeStamp(guint32 aEventTime);
+ mozilla::CurrentX11TimeGetter* GetCurrentTimeGetter();
+
+ virtual void SetInputContext(const InputContext& aContext,
+ const InputContextAction& aAction) override;
+ virtual InputContext GetInputContext() override;
+ virtual TextEventDispatcherListener* GetNativeTextEventDispatcherListener()
+ override;
+ void GetEditCommandsRemapped(NativeKeyBindingsType aType,
+ const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands,
+ uint32_t aGeckoKeyCode, uint32_t aNativeKeyCode);
+ virtual bool GetEditCommands(
+ NativeKeyBindingsType aType, const mozilla::WidgetKeyboardEvent& aEvent,
+ nsTArray<mozilla::CommandInt>& aCommands) override;
+
+ // These methods are for toplevel windows only.
+ void ResizeTransparencyBitmap();
+ void ApplyTransparencyBitmap();
+ void ClearTransparencyBitmap();
+
+ virtual void SetTransparencyMode(nsTransparencyMode aMode) override;
+ virtual nsTransparencyMode GetTransparencyMode() override;
+ virtual void SetWindowMouseTransparent(bool aIsTransparent) override;
+ virtual nsresult ConfigureChildren(
+ const nsTArray<Configuration>& aConfigurations) override;
+ nsresult UpdateTranslucentWindowAlphaInternal(const nsIntRect& aRect,
+ uint8_t* aAlphas,
+ int32_t aStride);
+ void UpdateTitlebarTransparencyBitmap();
+
+ virtual void ReparentNativeWidget(nsIWidget* aNewParent) override;
+
+ virtual nsresult SynthesizeNativeMouseEvent(LayoutDeviceIntPoint aPoint,
+ uint32_t aNativeMessage,
+ uint32_t aModifierFlags,
+ nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeMouseMove(LayoutDeviceIntPoint aPoint,
+ nsIObserver* aObserver) override {
+ return SynthesizeNativeMouseEvent(aPoint, GDK_MOTION_NOTIFY, 0, aObserver);
+ }
+
+ virtual nsresult SynthesizeNativeMouseScrollEvent(
+ LayoutDeviceIntPoint aPoint, uint32_t aNativeMessage, double aDeltaX,
+ double aDeltaY, double aDeltaZ, uint32_t aModifierFlags,
+ uint32_t aAdditionalFlags, nsIObserver* aObserver) override;
+
+ virtual nsresult SynthesizeNativeTouchPoint(uint32_t aPointerId,
+ TouchPointerState aPointerState,
+ LayoutDeviceIntPoint aPoint,
+ double aPointerPressure,
+ uint32_t aPointerOrientation,
+ nsIObserver* aObserver) override;
+
+#ifdef MOZ_X11
+ Display* XDisplay() { return mXDisplay; }
+#endif
+#ifdef MOZ_WAYLAND
+ wl_display* GetWaylandDisplay();
+ bool WaylandSurfaceNeedsClear();
+ virtual void CreateCompositorVsyncDispatcher() override;
+#endif
+ virtual void GetCompositorWidgetInitData(
+ mozilla::widget::CompositorWidgetInitData* aInitData) override;
+
+ virtual nsresult SetNonClientMargins(
+ LayoutDeviceIntMargin& aMargins) override;
+ void SetDrawsInTitlebar(bool aState) override;
+ bool GetTitlebarRect(mozilla::gfx::Rect& aRect);
+ virtual void UpdateWindowDraggingRegion(
+ const LayoutDeviceIntRegion& aRegion) override;
+
+ // HiDPI scale conversion
+ gint GdkScaleFactor();
+
+ // To GDK
+ gint DevicePixelsToGdkCoordRoundUp(int pixels);
+ gint DevicePixelsToGdkCoordRoundDown(int pixels);
+ GdkPoint DevicePixelsToGdkPointRoundDown(LayoutDeviceIntPoint point);
+ GdkRectangle DevicePixelsToGdkSizeRoundUp(LayoutDeviceIntSize pixelSize);
+
+ // From GDK
+ int GdkCoordToDevicePixels(gint coord);
+ LayoutDeviceIntPoint GdkPointToDevicePixels(GdkPoint point);
+ LayoutDeviceIntPoint GdkEventCoordsToDevicePixels(gdouble x, gdouble y);
+ LayoutDeviceIntRect GdkRectToDevicePixels(GdkRectangle rect);
+
+ virtual bool WidgetTypeSupportsAcceleration() override;
+
+ nsresult SetSystemFont(const nsCString& aFontName) override;
+ nsresult GetSystemFont(nsCString& aFontName) override;
+
+ typedef enum {
+ CSD_SUPPORT_SYSTEM, // CSD including shadows
+ CSD_SUPPORT_CLIENT, // CSD without shadows
+ CSD_SUPPORT_NONE, // WM does not support CSD at all
+ CSD_SUPPORT_UNKNOWN
+ } CSDSupportLevel;
+ /**
+ * Get the support of Client Side Decoration by checking
+ * the XDG_CURRENT_DESKTOP environment variable.
+ */
+ static CSDSupportLevel GetSystemCSDSupportLevel(bool aIsPopup = false);
+
+ static bool HideTitlebarByDefault();
+ static bool GetTopLevelWindowActiveState(nsIFrame* aFrame);
+ static bool TitlebarUseShapeMask();
+#ifdef MOZ_WAYLAND
+ virtual nsresult GetScreenRect(LayoutDeviceIntRect* aRect) override;
+ virtual nsRect GetPreferredPopupRect() override {
+ return mPreferredPopupRect;
+ };
+ virtual void FlushPreferredPopupRect() override {
+ mPreferredPopupRect = nsRect(0, 0, 0, 0);
+ mPreferredPopupRectFlushed = true;
+ };
+#endif
+ bool IsRemoteContent() { return HasRemoteContent(); }
+ static void HideWaylandOpenedPopups();
+ void NativeMoveResizeWaylandPopupCB(const GdkRectangle* aFinalSize,
+ bool aFlippedX, bool aFlippedY);
+ static bool IsToplevelWindowTransparent();
+
+ protected:
+ virtual ~nsWindow();
+
+ // event handling code
+ void DispatchActivateEvent(void);
+ void DispatchDeactivateEvent(void);
+ void MaybeDispatchResized();
+
+ virtual void RegisterTouchWindow() override;
+ virtual bool CompositorInitiallyPaused() override {
+#ifdef MOZ_WAYLAND
+ return mCompositorInitiallyPaused;
+#else
+ return false;
+#endif
+ }
+ nsCOMPtr<nsIWidget> mParent;
+ // Is this a toplevel window?
+ bool mIsTopLevel;
+ // Has this widget been destroyed yet?
+ bool mIsDestroyed;
+
+ // Should we send resize events on all resizes?
+ bool mListenForResizes;
+ // Does WindowResized need to be called on listeners?
+ bool mNeedsDispatchResized;
+ // This flag tracks if we're hidden or shown.
+ bool mIsShown;
+ bool mNeedsShow;
+ // is this widget enabled?
+ bool mEnabled;
+ // has the native window for this been created yet?
+ bool mCreated;
+ // whether we handle touch event
+ bool mHandleTouchEvent;
+ // true if this is a drag and drop feedback popup
+ bool mIsDragPopup;
+ // Can we access X?
+ bool mIsX11Display;
+#ifdef MOZ_WAYLAND
+ bool mNeedsCompositorResume;
+ bool mCompositorInitiallyPaused;
+#endif
+ bool mWindowScaleFactorChanged;
+ int mWindowScaleFactor;
+ bool mCompositedScreen;
+
+ private:
+ void DestroyChildWindows();
+ GtkWidget* GetToplevelWidget();
+ nsWindow* GetContainerWindow();
+ void SetUrgencyHint(GtkWidget* top_window, bool state);
+ void SetDefaultIcon(void);
+ void SetWindowDecoration(nsBorderStyle aStyle);
+ void InitButtonEvent(mozilla::WidgetMouseEvent& aEvent,
+ GdkEventButton* aGdkEvent);
+ bool CheckForRollup(gdouble aMouseX, gdouble aMouseY, bool aIsWheel,
+ bool aAlwaysRollup);
+ void CheckForRollupDuringGrab() { CheckForRollup(0, 0, false, true); }
+
+ bool GetDragInfo(mozilla::WidgetMouseEvent* aMouseEvent, GdkWindow** aWindow,
+ gint* aButton, gint* aRootX, gint* aRootY);
+ void ClearCachedResources();
+ nsIWidgetListener* GetListener();
+
+ nsWindow* GetTransientForWindowIfPopup();
+ bool IsHandlingTouchSequence(GdkEventSequence* aSequence);
+
+ void ResizeInt(int aX, int aY, int aWidth, int aHeight, bool aMove,
+ bool aRepaint);
+ void NativeMoveResizeWaylandPopup(GdkPoint* aPosition, GdkRectangle* aSize);
+
+ GtkTextDirection GetTextDirection();
+
+ void AddCSDDecorationSize(int* aWidth, int* aHeight);
+
+#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);
+#endif
+ nsCString mGtkWindowAppName;
+ nsCString mGtkWindowRoleName;
+ void RefreshWindowClass();
+
+ GtkWidget* mShell;
+ MozContainer* mContainer;
+ GdkWindow* mGdkWindow;
+ bool mWindowShouldStartDragging = false;
+ PlatformCompositorWidgetDelegate* mCompositorWidgetDelegate;
+
+ uint32_t mHasMappedToplevel : 1, mRetryPointerGrab : 1;
+ nsSizeMode mSizeState;
+ float mAspectRatio;
+ float mAspectRatioSaved;
+ nsIntPoint mClientOffset;
+
+ // This field omits duplicate scroll events caused by GNOME bug 726878.
+ guint32 mLastScrollEventTime;
+ mozilla::ScreenCoord mLastPinchEventSpan;
+ bool mPanInProgress = false;
+
+ // for touch event handling
+ nsRefPtrHashtable<nsPtrHashKey<GdkEventSequence>, mozilla::dom::Touch>
+ mTouches;
+
+#ifdef MOZ_X11
+ Display* mXDisplay;
+ Window mXWindow;
+ Visual* mXVisual;
+ int mXDepth;
+ mozilla::widget::WindowSurfaceProvider mSurfaceProvider;
+
+ bool ConfigureX11GLVisual(bool aUseAlpha);
+#endif
+#ifdef MOZ_WAYLAND
+ RefPtr<mozilla::gfx::VsyncSource> mWaylandVsyncSource;
+#endif
+
+ // Upper bound on pending ConfigureNotify events to be dispatched to the
+ // window. See bug 1225044.
+ unsigned int mPendingConfigures;
+
+ // Window titlebar rendering mode, CSD_SUPPORT_NONE if it's disabled
+ // for this window.
+ CSDSupportLevel mCSDSupportLevel;
+ // Use dedicated GdkWindow for mContainer
+ bool mDrawToContainer;
+ // If true, draw our own window titlebar.
+ bool mDrawInTitlebar;
+ // Draw titlebar with :backdrop css state (inactive/unfocused).
+ bool mTitlebarBackdropState;
+ // Draggable titlebar region maintained by UpdateWindowDraggingRegion
+ LayoutDeviceIntRegion mDraggableRegion;
+ // It's PictureInPicture window.
+ bool mIsPIPWindow;
+ bool mAlwaysOnTop;
+
+#ifdef ACCESSIBILITY
+ RefPtr<mozilla::a11y::Accessible> 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
+
+ // The cursor cache
+ static GdkCursor* gsGtkCursorCache[eCursorCount];
+
+ // Transparency
+ bool mIsTransparent;
+ // 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;
+ int32_t mTransparencyBitmapWidth;
+ int32_t mTransparencyBitmapHeight;
+ // The transparency bitmap is used instead of ARGB visual for toplevel
+ // window to draw titlebar.
+ bool mTransparencyBitmapForTitlebar;
+
+ // True when we're on compositing window manager and this
+ // window is using visual with alpha channel.
+ bool mHasAlphaVisual;
+
+ // all of our DND stuff
+ void InitDragEvent(mozilla::WidgetDragEvent& aEvent);
+
+ float mLastMotionPressure;
+
+ // Remember the last sizemode so that we can restore it when
+ // leaving fullscreen
+ nsSizeMode mLastSizeMode;
+ // We can't detect size state changes correctly so set this flag
+ // to force update mBounds after a size state change from a configure
+ // event.
+ bool mBoundsAreValid;
+
+ static bool DragInProgress(void);
+
+ void DispatchMissedButtonReleases(GdkEventCrossing* aGdkEvent);
+
+ // nsBaseWidget
+ virtual LayerManager* GetLayerManager(
+ PLayerTransactionChild* aShadowManager = nullptr,
+ LayersBackend aBackendHint = mozilla::layers::LayersBackend::LAYERS_NONE,
+ LayerManagerPersistence aPersistence = LAYER_MANAGER_CURRENT) override;
+
+ void SetCompositorWidgetDelegate(CompositorWidgetDelegate* delegate) override;
+
+ void CleanLayerManagerRecursive();
+
+ virtual int32_t RoundsWidgetCoordinatesTo() override;
+
+ void UpdateMozWindowActive();
+
+ void ForceTitlebarRedraw();
+ bool DoDrawTilebarCorners();
+ bool IsChromeWindowTitlebar();
+
+ void SetPopupWindowDecoration(bool aShowOnTaskbar);
+
+ void ApplySizeConstraints(void);
+
+ bool IsMainMenuWindow();
+ GtkWidget* ConfigureWaylandPopupWindows();
+ void PauseRemoteRenderer();
+ void HideWaylandWindow();
+ void HideWaylandTooltips();
+ void HideWaylandPopupAndAllChildren();
+ void CleanupWaylandPopups();
+ GtkWindow* GetCurrentTopmostWindow();
+ GtkWindow* GetCurrentWindow();
+ GtkWindow* GetTopmostWindow();
+ bool IsWidgetOverflowWindow();
+ nsRect mPreferredPopupRect;
+ bool mPreferredPopupRectFlushed;
+ bool mWaitingForMoveToRectCB;
+ LayoutDeviceIntRect mPendingSizeRect;
+
+ /**
+ * |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;
+
+ mozilla::UniquePtr<mozilla::CurrentX11TimeGetter> mCurrentTimeGetter;
+ static CSDSupportLevel sCSDSupportLevel;
+
+ static bool sTransparentMainWindow;
+};
+
+#endif /* __nsWindow_h__ */
diff --git a/widget/gtk/wayland/gbm.h b/widget/gtk/wayland/gbm.h
new file mode 100644
index 0000000000..bd94fa8967
--- /dev/null
+++ b/widget/gtk/wayland/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/wayland/gtk-primary-selection-client-protocol.h b/widget/gtk/wayland/gtk-primary-selection-client-protocol.h
new file mode 100644
index 0000000000..770c35e03f
--- /dev/null
+++ b/widget/gtk/wayland/gtk-primary-selection-client-protocol.h
@@ -0,0 +1,580 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef GTK_PRIMARY_SELECTION_CLIENT_PROTOCOL_H
+#define GTK_PRIMARY_SELECTION_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_gtk_primary_selection The gtk_primary_selection protocol
+ * Primary selection protocol
+ *
+ * @section page_desc_gtk_primary_selection Description
+ *
+ * This protocol provides the ability to have a primary selection device to
+ * match that of the X server. This primary selection is a shortcut to the
+ * common clipboard selection, where text just needs to be selected in order
+ * to allow copying it elsewhere. The de facto way to perform this action
+ * is the middle mouse button, although it is not limited to this one.
+ *
+ * Clients wishing to honor primary selection should create a primary
+ * selection source and set it as the selection through
+ * wp_primary_selection_device.set_selection whenever the text selection
+ * changes. In order to minimize calls in pointer-driven text selection,
+ * it should happen only once after the operation finished. Similarly,
+ * a NULL source should be set when text is unselected.
+ *
+ * wp_primary_selection_offer objects are first announced through the
+ * wp_primary_selection_device.data_offer event. Immediately after this event,
+ * the primary data offer will emit wp_primary_selection_offer.offer events
+ * to let know of the mime types being offered.
+ *
+ * When the primary selection changes, the client with the keyboard focus
+ * will receive wp_primary_selection_device.selection events. Only the client
+ * with the keyboard focus will receive such events with a non-NULL
+ * wp_primary_selection_offer. Across keyboard focus changes, previously
+ * focused clients will receive wp_primary_selection_device.events with a
+ * NULL wp_primary_selection_offer.
+ *
+ * In order to request the primary selection data, the client must pass
+ * a recent serial pertaining to the press event that is triggering the
+ * operation, if the compositor deems the serial valid and recent, the
+ * wp_primary_selection_source.send event will happen in the other end
+ * to let the transfer begin. The client owning the primary selection
+ * should write the requested data, and close the file descriptor
+ * immediately.
+ *
+ * If the primary selection owner client disappeared during the transfer,
+ * the client reading the data will receive a
+ * wp_primary_selection_device.selection event with a NULL
+ * wp_primary_selection_offer, the client should take this as a hint
+ * to finish the reads related to the no longer existing offer.
+ *
+ * The primary selection owner should be checking for errors during
+ * writes, merely cancelling the ongoing transfer if any happened.
+ *
+ * @section page_ifaces_gtk_primary_selection Interfaces
+ * - @subpage page_iface_gtk_primary_selection_device_manager - X primary selection emulation
+ * - @subpage page_iface_gtk_primary_selection_device -
+ * - @subpage page_iface_gtk_primary_selection_offer - offer to transfer primary selection contents
+ * - @subpage page_iface_gtk_primary_selection_source - offer to replace the contents of the primary selection
+ * @section page_copyright_gtk_primary_selection Copyright
+ * <pre>
+ *
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * 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 gtk_primary_selection_device;
+struct gtk_primary_selection_device_manager;
+struct gtk_primary_selection_offer;
+struct gtk_primary_selection_source;
+struct wl_seat;
+
+/**
+ * @page page_iface_gtk_primary_selection_device_manager gtk_primary_selection_device_manager
+ * @section page_iface_gtk_primary_selection_device_manager_desc Description
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ * @section page_iface_gtk_primary_selection_device_manager_api API
+ * See @ref iface_gtk_primary_selection_device_manager.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_device_manager The gtk_primary_selection_device_manager interface
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ */
+extern const struct wl_interface gtk_primary_selection_device_manager_interface;
+/**
+ * @page page_iface_gtk_primary_selection_device gtk_primary_selection_device
+ * @section page_iface_gtk_primary_selection_device_api API
+ * See @ref iface_gtk_primary_selection_device.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_device The gtk_primary_selection_device interface
+ */
+extern const struct wl_interface gtk_primary_selection_device_interface;
+/**
+ * @page page_iface_gtk_primary_selection_offer gtk_primary_selection_offer
+ * @section page_iface_gtk_primary_selection_offer_desc Description
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the source
+ * will transferthat the
+ * data can be converted to and provides the mechanisms for transferring the
+ * data directly to the client.
+ * @section page_iface_gtk_primary_selection_offer_api API
+ * See @ref iface_gtk_primary_selection_offer.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_offer The gtk_primary_selection_offer interface
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the source
+ * will transferthat the
+ * data can be converted to and provides the mechanisms for transferring the
+ * data directly to the client.
+ */
+extern const struct wl_interface gtk_primary_selection_offer_interface;
+/**
+ * @page page_iface_gtk_primary_selection_source gtk_primary_selection_source
+ * @section page_iface_gtk_primary_selection_source_desc Description
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ * @section page_iface_gtk_primary_selection_source_api API
+ * See @ref iface_gtk_primary_selection_source.
+ */
+/**
+ * @defgroup iface_gtk_primary_selection_source The gtk_primary_selection_source interface
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ */
+extern const struct wl_interface gtk_primary_selection_source_interface;
+
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE 0
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE 1
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY 2
+
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_device_manager */
+static inline void
+gtk_primary_selection_device_manager_set_user_data(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_device_manager, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_device_manager */
+static inline void *
+gtk_primary_selection_device_manager_get_user_data(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+static inline uint32_t
+gtk_primary_selection_device_manager_get_version(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Create a new primary selection source.
+ */
+static inline struct gtk_primary_selection_source *
+gtk_primary_selection_device_manager_create_source(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_CREATE_SOURCE, &gtk_primary_selection_source_interface, NULL);
+
+ return (struct gtk_primary_selection_source *) id;
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Create a new data device for a given seat.
+ */
+static inline struct gtk_primary_selection_device *
+gtk_primary_selection_device_manager_get_device(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager, struct wl_seat *seat)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_GET_DEVICE, &gtk_primary_selection_device_interface, NULL, seat);
+
+ return (struct gtk_primary_selection_device *) id;
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device_manager
+ *
+ * Destroy the primary selection device manager.
+ */
+static inline void
+gtk_primary_selection_device_manager_destroy(struct gtk_primary_selection_device_manager *gtk_primary_selection_device_manager)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device_manager,
+ GTK_PRIMARY_SELECTION_DEVICE_MANAGER_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_device_manager);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ * @struct gtk_primary_selection_device_listener
+ */
+struct gtk_primary_selection_device_listener {
+ /**
+ * introduce a new wp_primary_selection_offer
+ *
+ * Introduces a new wp_primary_selection_offer object that may be
+ * used to receive the current primary selection. Immediately
+ * following this event, the new wp_primary_selection_offer object
+ * will send wp_primary_selection_offer.offer events to describe
+ * the offered mime types.
+ */
+ void (*data_offer)(void *data,
+ struct gtk_primary_selection_device *gtk_primary_selection_device,
+ struct gtk_primary_selection_offer *offer);
+ /**
+ * advertise a new primary selection
+ *
+ * The wp_primary_selection_device.selection event is sent to
+ * notify the client of a new primary selection. This event is sent
+ * after the wp_primary_selection.data_offer event introducing this
+ * object, and after the offer has announced its mimetypes through
+ * wp_primary_selection_offer.offer.
+ *
+ * The data_offer is valid until a new offer or NULL is received or
+ * until the client loses keyboard focus. The client must destroy
+ * the previous selection data_offer, if any, upon receiving this
+ * event.
+ */
+ void (*selection)(void *data,
+ struct gtk_primary_selection_device *gtk_primary_selection_device,
+ struct gtk_primary_selection_offer *id);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+static inline int
+gtk_primary_selection_device_add_listener(struct gtk_primary_selection_device *gtk_primary_selection_device,
+ const struct gtk_primary_selection_device_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_device,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION 0
+#define GTK_PRIMARY_SELECTION_DEVICE_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_DATA_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_SELECTION_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ */
+#define GTK_PRIMARY_SELECTION_DEVICE_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_device */
+static inline void
+gtk_primary_selection_device_set_user_data(struct gtk_primary_selection_device *gtk_primary_selection_device, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_device, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_device */
+static inline void *
+gtk_primary_selection_device_get_user_data(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+static inline uint32_t
+gtk_primary_selection_device_get_version(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ *
+ * Replaces the current selection. The previous owner of the primary selection
+ * will receive a wp_primary_selection_source.cancelled event.
+ *
+ * To unset the selection, set the source to NULL.
+ */
+static inline void
+gtk_primary_selection_device_set_selection(struct gtk_primary_selection_device *gtk_primary_selection_device, struct gtk_primary_selection_source *source, uint32_t serial)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device,
+ GTK_PRIMARY_SELECTION_DEVICE_SET_SELECTION, source, serial);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_device
+ *
+ * Destroy the primary selection device.
+ */
+static inline void
+gtk_primary_selection_device_destroy(struct gtk_primary_selection_device *gtk_primary_selection_device)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_device,
+ GTK_PRIMARY_SELECTION_DEVICE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_device);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ * @struct gtk_primary_selection_offer_listener
+ */
+struct gtk_primary_selection_offer_listener {
+ /**
+ * advertise offered mime type
+ *
+ * Sent immediately after creating announcing the
+ * wp_primary_selection_offer through
+ * wp_primary_selection_device.data_offer. One event is sent per
+ * offered mime type.
+ */
+ void (*offer)(void *data,
+ struct gtk_primary_selection_offer *gtk_primary_selection_offer,
+ const char *mime_type);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+static inline int
+gtk_primary_selection_offer_add_listener(struct gtk_primary_selection_offer *gtk_primary_selection_offer,
+ const struct gtk_primary_selection_offer_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_offer,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_OFFER_RECEIVE 0
+#define GTK_PRIMARY_SELECTION_OFFER_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_OFFER_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_RECEIVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ */
+#define GTK_PRIMARY_SELECTION_OFFER_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_offer */
+static inline void
+gtk_primary_selection_offer_set_user_data(struct gtk_primary_selection_offer *gtk_primary_selection_offer, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_offer, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_offer */
+static inline void *
+gtk_primary_selection_offer_get_user_data(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+static inline uint32_t
+gtk_primary_selection_offer_get_version(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ *
+ * To transfer the contents of the primary selection clipboard, the client
+ * issues this request and indicates the mime type that it wants to
+ * receive. The transfer happens through the passed file descriptor
+ * (typically created with the pipe system call). The source client writes
+ * the data in the mime type representation requested and then closes the
+ * file descriptor.
+ *
+ * The receiving client reads from the read end of the pipe until EOF and
+ * closes its end, at which point the transfer is complete.
+ */
+static inline void
+gtk_primary_selection_offer_receive(struct gtk_primary_selection_offer *gtk_primary_selection_offer, const char *mime_type, int32_t fd)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_offer,
+ GTK_PRIMARY_SELECTION_OFFER_RECEIVE, mime_type, fd);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_offer
+ *
+ * Destroy the primary selection offer.
+ */
+static inline void
+gtk_primary_selection_offer_destroy(struct gtk_primary_selection_offer *gtk_primary_selection_offer)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_offer,
+ GTK_PRIMARY_SELECTION_OFFER_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_offer);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ * @struct gtk_primary_selection_source_listener
+ */
+struct gtk_primary_selection_source_listener {
+ /**
+ * send the primary selection contents
+ *
+ * Request for the current primary selection contents from the
+ * client. Send the specified mime type over the passed file
+ * descriptor, then close it.
+ */
+ void (*send)(void *data,
+ struct gtk_primary_selection_source *gtk_primary_selection_source,
+ const char *mime_type,
+ int32_t fd);
+ /**
+ * request for primary selection contents was canceled
+ *
+ * This primary selection source is no longer valid. The client
+ * should clean up and destroy this primary selection source.
+ */
+ void (*cancelled)(void *data,
+ struct gtk_primary_selection_source *gtk_primary_selection_source);
+};
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+static inline int
+gtk_primary_selection_source_add_listener(struct gtk_primary_selection_source *gtk_primary_selection_source,
+ const struct gtk_primary_selection_source_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) gtk_primary_selection_source,
+ (void (**)(void)) listener, data);
+}
+
+#define GTK_PRIMARY_SELECTION_SOURCE_OFFER 0
+#define GTK_PRIMARY_SELECTION_SOURCE_DESTROY 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_SEND_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_CANCELLED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ */
+#define GTK_PRIMARY_SELECTION_SOURCE_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_gtk_primary_selection_source */
+static inline void
+gtk_primary_selection_source_set_user_data(struct gtk_primary_selection_source *gtk_primary_selection_source, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) gtk_primary_selection_source, user_data);
+}
+
+/** @ingroup iface_gtk_primary_selection_source */
+static inline void *
+gtk_primary_selection_source_get_user_data(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+static inline uint32_t
+gtk_primary_selection_source_get_version(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ return wl_proxy_get_version((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ *
+ * This request adds a mime type to the set of mime types advertised to
+ * targets. Can be called several times to offer multiple types.
+ */
+static inline void
+gtk_primary_selection_source_offer(struct gtk_primary_selection_source *gtk_primary_selection_source, const char *mime_type)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_source,
+ GTK_PRIMARY_SELECTION_SOURCE_OFFER, mime_type);
+}
+
+/**
+ * @ingroup iface_gtk_primary_selection_source
+ *
+ * Destroy the primary selection source.
+ */
+static inline void
+gtk_primary_selection_source_destroy(struct gtk_primary_selection_source *gtk_primary_selection_source)
+{
+ wl_proxy_marshal((struct wl_proxy *) gtk_primary_selection_source,
+ GTK_PRIMARY_SELECTION_SOURCE_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) gtk_primary_selection_source);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/gtk-primary-selection-protocol.c b/widget/gtk/wayland/gtk-primary-selection-protocol.c
new file mode 100644
index 0000000000..8a1166d2a2
--- /dev/null
+++ b/widget/gtk/wayland/gtk-primary-selection-protocol.c
@@ -0,0 +1,115 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * 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 gtk_primary_selection_device_interface;
+extern const struct wl_interface gtk_primary_selection_offer_interface;
+extern const struct wl_interface gtk_primary_selection_source_interface;
+extern const struct wl_interface wl_seat_interface;
+
+static const struct wl_interface *gtk_primary_selection_types[] = {
+ NULL,
+ NULL,
+ &gtk_primary_selection_source_interface,
+ &gtk_primary_selection_device_interface,
+ &wl_seat_interface,
+ &gtk_primary_selection_source_interface,
+ NULL,
+ &gtk_primary_selection_offer_interface,
+ &gtk_primary_selection_offer_interface,
+};
+
+static const struct wl_message gtk_primary_selection_device_manager_requests[] = {
+ { "create_source", "n", gtk_primary_selection_types + 2 },
+ { "get_device", "no", gtk_primary_selection_types + 3 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_device_manager_interface = {
+ "gtk_primary_selection_device_manager", 1,
+ 3, gtk_primary_selection_device_manager_requests,
+ 0, NULL,
+};
+
+static const struct wl_message gtk_primary_selection_device_requests[] = {
+ { "set_selection", "?ou", gtk_primary_selection_types + 5 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_device_events[] = {
+ { "data_offer", "n", gtk_primary_selection_types + 7 },
+ { "selection", "?o", gtk_primary_selection_types + 8 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_device_interface = {
+ "gtk_primary_selection_device", 1,
+ 2, gtk_primary_selection_device_requests,
+ 2, gtk_primary_selection_device_events,
+};
+
+static const struct wl_message gtk_primary_selection_offer_requests[] = {
+ { "receive", "sh", gtk_primary_selection_types + 0 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_offer_events[] = {
+ { "offer", "s", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_offer_interface = {
+ "gtk_primary_selection_offer", 1,
+ 2, gtk_primary_selection_offer_requests,
+ 1, gtk_primary_selection_offer_events,
+};
+
+static const struct wl_message gtk_primary_selection_source_requests[] = {
+ { "offer", "s", gtk_primary_selection_types + 0 },
+ { "destroy", "", gtk_primary_selection_types + 0 },
+};
+
+static const struct wl_message gtk_primary_selection_source_events[] = {
+ { "send", "sh", gtk_primary_selection_types + 0 },
+ { "cancelled", "", gtk_primary_selection_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface gtk_primary_selection_source_interface = {
+ "gtk_primary_selection_source", 1,
+ 2, gtk_primary_selection_source_requests,
+ 2, gtk_primary_selection_source_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..a6d4adbdc3
--- /dev/null
+++ b/widget/gtk/wayland/moz.build
@@ -0,0 +1,35 @@
+# -*- 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 += [
+ "gtk-primary-selection-protocol.c",
+ "idle-inhibit-unstable-v1-protocol.c",
+ "linux-dmabuf-unstable-v1-protocol.c",
+ "primary-selection-unstable-v1-protocol.c",
+ "xdg-output-unstable-v1-protocol.c",
+]
+
+EXPORTS.mozilla.widget += [
+ "gbm.h",
+ "gtk-primary-selection-client-protocol.h",
+ "idle-inhibit-unstable-v1-client-protocol.h",
+ "linux-dmabuf-unstable-v1-client-protocol.h",
+ "primary-selection-unstable-v1-client-protocol.h",
+ "va_drmcommon.h",
+ "xdg-output-unstable-v1-client-protocol.h",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul"
+
+CFLAGS += CONFIG["TK_CFLAGS"]
+CXXFLAGS += CONFIG["TK_CFLAGS"]
+
+CXXFLAGS += ["-Wno-error=shadow"]
diff --git a/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h b/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h
new file mode 100644
index 0000000000..998266c3db
--- /dev/null
+++ b/widget/gtk/wayland/primary-selection-unstable-v1-client-protocol.h
@@ -0,0 +1,578 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+#ifndef WP_PRIMARY_SELECTION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+#define WP_PRIMARY_SELECTION_UNSTABLE_V1_CLIENT_PROTOCOL_H
+
+#include <stdint.h>
+#include <stddef.h>
+#include "wayland-client.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+/**
+ * @page page_wp_primary_selection_unstable_v1 The wp_primary_selection_unstable_v1 protocol
+ * Primary selection protocol
+ *
+ * @section page_desc_wp_primary_selection_unstable_v1 Description
+ *
+ * This protocol provides the ability to have a primary selection device to
+ * match that of the X server. This primary selection is a shortcut to the
+ * common clipboard selection, where text just needs to be selected in order
+ * to allow copying it elsewhere. The de facto way to perform this action
+ * is the middle mouse button, although it is not limited to this one.
+ *
+ * Clients wishing to honor primary selection should create a primary
+ * selection source and set it as the selection through
+ * wp_primary_selection_device.set_selection whenever the text selection
+ * changes. In order to minimize calls in pointer-driven text selection,
+ * it should happen only once after the operation finished. Similarly,
+ * a NULL source should be set when text is unselected.
+ *
+ * wp_primary_selection_offer objects are first announced through the
+ * wp_primary_selection_device.data_offer event. Immediately after this event,
+ * the primary data offer will emit wp_primary_selection_offer.offer events
+ * to let know of the mime types being offered.
+ *
+ * When the primary selection changes, the client with the keyboard focus
+ * will receive wp_primary_selection_device.selection events. Only the client
+ * with the keyboard focus will receive such events with a non-NULL
+ * wp_primary_selection_offer. Across keyboard focus changes, previously
+ * focused clients will receive wp_primary_selection_device.events with a
+ * NULL wp_primary_selection_offer.
+ *
+ * In order to request the primary selection data, the client must pass
+ * a recent serial pertaining to the press event that is triggering the
+ * operation, if the compositor deems the serial valid and recent, the
+ * wp_primary_selection_source.send event will happen in the other end
+ * to let the transfer begin. The client owning the primary selection
+ * should write the requested data, and close the file descriptor
+ * immediately.
+ *
+ * If the primary selection owner client disappeared during the transfer,
+ * the client reading the data will receive a
+ * wp_primary_selection_device.selection event with a NULL
+ * wp_primary_selection_offer, the client should take this as a hint
+ * to finish the reads related to the no longer existing offer.
+ *
+ * The primary selection owner should be checking for errors during
+ * writes, merely cancelling the ongoing transfer if any happened.
+ *
+ * @section page_ifaces_wp_primary_selection_unstable_v1 Interfaces
+ * - @subpage page_iface_zwp_primary_selection_device_manager_v1 - X primary selection emulation
+ * - @subpage page_iface_zwp_primary_selection_device_v1 -
+ * - @subpage page_iface_zwp_primary_selection_offer_v1 - offer to transfer primary selection contents
+ * - @subpage page_iface_zwp_primary_selection_source_v1 - offer to replace the contents of the primary selection
+ * @section page_copyright_wp_primary_selection_unstable_v1 Copyright
+ * <pre>
+ *
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * 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 zwp_primary_selection_device_manager_v1;
+struct zwp_primary_selection_device_v1;
+struct zwp_primary_selection_offer_v1;
+struct zwp_primary_selection_source_v1;
+
+/**
+ * @page page_iface_zwp_primary_selection_device_manager_v1 zwp_primary_selection_device_manager_v1
+ * @section page_iface_zwp_primary_selection_device_manager_v1_desc Description
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ * @section page_iface_zwp_primary_selection_device_manager_v1_api API
+ * See @ref iface_zwp_primary_selection_device_manager_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_device_manager_v1 The zwp_primary_selection_device_manager_v1 interface
+ *
+ * The primary selection device manager is a singleton global object that
+ * provides access to the primary selection. It allows to create
+ * wp_primary_selection_source objects, as well as retrieving the per-seat
+ * wp_primary_selection_device objects.
+ */
+extern const struct wl_interface zwp_primary_selection_device_manager_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_device_v1 zwp_primary_selection_device_v1
+ * @section page_iface_zwp_primary_selection_device_v1_api API
+ * See @ref iface_zwp_primary_selection_device_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_device_v1 The zwp_primary_selection_device_v1 interface
+ */
+extern const struct wl_interface zwp_primary_selection_device_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_offer_v1 zwp_primary_selection_offer_v1
+ * @section page_iface_zwp_primary_selection_offer_v1_desc Description
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the data can
+ * be converted to and provides the mechanisms for transferring the data
+ * directly to the client.
+ * @section page_iface_zwp_primary_selection_offer_v1_api API
+ * See @ref iface_zwp_primary_selection_offer_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_offer_v1 The zwp_primary_selection_offer_v1 interface
+ *
+ * A wp_primary_selection_offer represents an offer to transfer the contents
+ * of the primary selection clipboard to the client. Similar to
+ * wl_data_offer, the offer also describes the mime types that the data can
+ * be converted to and provides the mechanisms for transferring the data
+ * directly to the client.
+ */
+extern const struct wl_interface zwp_primary_selection_offer_v1_interface;
+/**
+ * @page page_iface_zwp_primary_selection_source_v1 zwp_primary_selection_source_v1
+ * @section page_iface_zwp_primary_selection_source_v1_desc Description
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ * @section page_iface_zwp_primary_selection_source_v1_api API
+ * See @ref iface_zwp_primary_selection_source_v1.
+ */
+/**
+ * @defgroup iface_zwp_primary_selection_source_v1 The zwp_primary_selection_source_v1 interface
+ *
+ * The source side of a wp_primary_selection_offer, it provides a way to
+ * describe the offered data and respond to requests to transfer the
+ * requested contents of the primary selection clipboard.
+ */
+extern const struct wl_interface zwp_primary_selection_source_v1_interface;
+
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE 0
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE 1
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY 2
+
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_device_manager_v1 */
+static inline void
+zwp_primary_selection_device_manager_v1_set_user_data(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_device_manager_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_device_manager_v1 */
+static inline void *
+zwp_primary_selection_device_manager_v1_get_user_data(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_device_manager_v1_get_version(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Create a new primary selection source.
+ */
+static inline struct zwp_primary_selection_source_v1 *
+zwp_primary_selection_device_manager_v1_create_source(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_CREATE_SOURCE, &zwp_primary_selection_source_v1_interface, NULL);
+
+ return (struct zwp_primary_selection_source_v1 *) id;
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Create a new data device for a given seat.
+ */
+static inline struct zwp_primary_selection_device_v1 *
+zwp_primary_selection_device_manager_v1_get_device(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1, struct wl_seat *seat)
+{
+ struct wl_proxy *id;
+
+ id = wl_proxy_marshal_constructor((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_GET_DEVICE, &zwp_primary_selection_device_v1_interface, NULL, seat);
+
+ return (struct zwp_primary_selection_device_v1 *) id;
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_manager_v1
+ *
+ * Destroy the primary selection device manager.
+ */
+static inline void
+zwp_primary_selection_device_manager_v1_destroy(struct zwp_primary_selection_device_manager_v1 *zwp_primary_selection_device_manager_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_manager_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_MANAGER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_device_manager_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ * @struct zwp_primary_selection_device_v1_listener
+ */
+struct zwp_primary_selection_device_v1_listener {
+ /**
+ * introduce a new wp_primary_selection_offer
+ *
+ * Introduces a new wp_primary_selection_offer object that may be
+ * used to receive the current primary selection. Immediately
+ * following this event, the new wp_primary_selection_offer object
+ * will send wp_primary_selection_offer.offer events to describe
+ * the offered mime types.
+ */
+ void (*data_offer)(void *data,
+ struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ struct zwp_primary_selection_offer_v1 *offer);
+ /**
+ * advertise a new primary selection
+ *
+ * The wp_primary_selection_device.selection event is sent to
+ * notify the client of a new primary selection. This event is sent
+ * after the wp_primary_selection.data_offer event introducing this
+ * object, and after the offer has announced its mimetypes through
+ * wp_primary_selection_offer.offer.
+ *
+ * The data_offer is valid until a new offer or NULL is received or
+ * until the client loses keyboard focus. The client must destroy
+ * the previous selection data_offer, if any, upon receiving this
+ * event.
+ */
+ void (*selection)(void *data,
+ struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ struct zwp_primary_selection_offer_v1 *id);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+static inline int
+zwp_primary_selection_device_v1_add_listener(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1,
+ const struct zwp_primary_selection_device_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_device_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION 0
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DATA_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SELECTION_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ */
+#define ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_device_v1 */
+static inline void
+zwp_primary_selection_device_v1_set_user_data(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_device_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_device_v1 */
+static inline void *
+zwp_primary_selection_device_v1_get_user_data(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_device_v1_get_version(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ *
+ * Replaces the current selection. The previous owner of the primary
+ * selection will receive a wp_primary_selection_source.cancelled event.
+ *
+ * To unset the selection, set the source to NULL.
+ */
+static inline void
+zwp_primary_selection_device_v1_set_selection(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1, struct zwp_primary_selection_source_v1 *source, uint32_t serial)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_V1_SET_SELECTION, source, serial);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_device_v1
+ *
+ * Destroy the primary selection device.
+ */
+static inline void
+zwp_primary_selection_device_v1_destroy(struct zwp_primary_selection_device_v1 *zwp_primary_selection_device_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_device_v1,
+ ZWP_PRIMARY_SELECTION_DEVICE_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_device_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ * @struct zwp_primary_selection_offer_v1_listener
+ */
+struct zwp_primary_selection_offer_v1_listener {
+ /**
+ * advertise offered mime type
+ *
+ * Sent immediately after creating announcing the
+ * wp_primary_selection_offer through
+ * wp_primary_selection_device.data_offer. One event is sent per
+ * offered mime type.
+ */
+ void (*offer)(void *data,
+ struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
+ const char *mime_type);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+static inline int
+zwp_primary_selection_offer_v1_add_listener(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1,
+ const struct zwp_primary_selection_offer_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE 0
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_OFFER_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ */
+#define ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_offer_v1 */
+static inline void
+zwp_primary_selection_offer_v1_set_user_data(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_offer_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_offer_v1 */
+static inline void *
+zwp_primary_selection_offer_v1_get_user_data(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_offer_v1_get_version(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ *
+ * To transfer the contents of the primary selection clipboard, the client
+ * issues this request and indicates the mime type that it wants to
+ * receive. The transfer happens through the passed file descriptor
+ * (typically created with the pipe system call). The source client writes
+ * the data in the mime type representation requested and then closes the
+ * file descriptor.
+ *
+ * The receiving client reads from the read end of the pipe until EOF and
+ * closes its end, at which point the transfer is complete.
+ */
+static inline void
+zwp_primary_selection_offer_v1_receive(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1, const char *mime_type, int32_t fd)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ ZWP_PRIMARY_SELECTION_OFFER_V1_RECEIVE, mime_type, fd);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_offer_v1
+ *
+ * Destroy the primary selection offer.
+ */
+static inline void
+zwp_primary_selection_offer_v1_destroy(struct zwp_primary_selection_offer_v1 *zwp_primary_selection_offer_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_offer_v1,
+ ZWP_PRIMARY_SELECTION_OFFER_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_offer_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ * @struct zwp_primary_selection_source_v1_listener
+ */
+struct zwp_primary_selection_source_v1_listener {
+ /**
+ * send the primary selection contents
+ *
+ * Request for the current primary selection contents from the
+ * client. Send the specified mime type over the passed file
+ * descriptor, then close it.
+ */
+ void (*send)(void *data,
+ struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
+ const char *mime_type,
+ int32_t fd);
+ /**
+ * request for primary selection contents was canceled
+ *
+ * This primary selection source is no longer valid. The client
+ * should clean up and destroy this primary selection source.
+ */
+ void (*cancelled)(void *data,
+ struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1);
+};
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+static inline int
+zwp_primary_selection_source_v1_add_listener(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1,
+ const struct zwp_primary_selection_source_v1_listener *listener, void *data)
+{
+ return wl_proxy_add_listener((struct wl_proxy *) zwp_primary_selection_source_v1,
+ (void (**)(void)) listener, data);
+}
+
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER 0
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_SEND_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_CANCELLED_SINCE_VERSION 1
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER_SINCE_VERSION 1
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ */
+#define ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY_SINCE_VERSION 1
+
+/** @ingroup iface_zwp_primary_selection_source_v1 */
+static inline void
+zwp_primary_selection_source_v1_set_user_data(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, void *user_data)
+{
+ wl_proxy_set_user_data((struct wl_proxy *) zwp_primary_selection_source_v1, user_data);
+}
+
+/** @ingroup iface_zwp_primary_selection_source_v1 */
+static inline void *
+zwp_primary_selection_source_v1_get_user_data(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ return wl_proxy_get_user_data((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+static inline uint32_t
+zwp_primary_selection_source_v1_get_version(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ return wl_proxy_get_version((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ *
+ * This request adds a mime type to the set of mime types advertised to
+ * targets. Can be called several times to offer multiple types.
+ */
+static inline void
+zwp_primary_selection_source_v1_offer(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1, const char *mime_type)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_source_v1,
+ ZWP_PRIMARY_SELECTION_SOURCE_V1_OFFER, mime_type);
+}
+
+/**
+ * @ingroup iface_zwp_primary_selection_source_v1
+ *
+ * Destroy the primary selection source.
+ */
+static inline void
+zwp_primary_selection_source_v1_destroy(struct zwp_primary_selection_source_v1 *zwp_primary_selection_source_v1)
+{
+ wl_proxy_marshal((struct wl_proxy *) zwp_primary_selection_source_v1,
+ ZWP_PRIMARY_SELECTION_SOURCE_V1_DESTROY);
+
+ wl_proxy_destroy((struct wl_proxy *) zwp_primary_selection_source_v1);
+}
+
+#ifdef __cplusplus
+}
+#endif
+
+#endif
diff --git a/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c b/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c
new file mode 100644
index 0000000000..9f43d4d65e
--- /dev/null
+++ b/widget/gtk/wayland/primary-selection-unstable-v1-protocol.c
@@ -0,0 +1,115 @@
+/* Generated by wayland-scanner 1.18.0 */
+
+/*
+ * Copyright © 2015, 2016 Red Hat
+ *
+ * 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_seat_interface;
+extern const struct wl_interface zwp_primary_selection_device_v1_interface;
+extern const struct wl_interface zwp_primary_selection_offer_v1_interface;
+extern const struct wl_interface zwp_primary_selection_source_v1_interface;
+
+static const struct wl_interface *wp_primary_selection_unstable_v1_types[] = {
+ NULL,
+ NULL,
+ &zwp_primary_selection_source_v1_interface,
+ &zwp_primary_selection_device_v1_interface,
+ &wl_seat_interface,
+ &zwp_primary_selection_source_v1_interface,
+ NULL,
+ &zwp_primary_selection_offer_v1_interface,
+ &zwp_primary_selection_offer_v1_interface,
+};
+
+static const struct wl_message zwp_primary_selection_device_manager_v1_requests[] = {
+ { "create_source", "n", wp_primary_selection_unstable_v1_types + 2 },
+ { "get_device", "no", wp_primary_selection_unstable_v1_types + 3 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_device_manager_v1_interface = {
+ "zwp_primary_selection_device_manager_v1", 1,
+ 3, zwp_primary_selection_device_manager_v1_requests,
+ 0, NULL,
+};
+
+static const struct wl_message zwp_primary_selection_device_v1_requests[] = {
+ { "set_selection", "?ou", wp_primary_selection_unstable_v1_types + 5 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_device_v1_events[] = {
+ { "data_offer", "n", wp_primary_selection_unstable_v1_types + 7 },
+ { "selection", "?o", wp_primary_selection_unstable_v1_types + 8 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_device_v1_interface = {
+ "zwp_primary_selection_device_v1", 1,
+ 2, zwp_primary_selection_device_v1_requests,
+ 2, zwp_primary_selection_device_v1_events,
+};
+
+static const struct wl_message zwp_primary_selection_offer_v1_requests[] = {
+ { "receive", "sh", wp_primary_selection_unstable_v1_types + 0 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_offer_v1_events[] = {
+ { "offer", "s", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_offer_v1_interface = {
+ "zwp_primary_selection_offer_v1", 1,
+ 2, zwp_primary_selection_offer_v1_requests,
+ 1, zwp_primary_selection_offer_v1_events,
+};
+
+static const struct wl_message zwp_primary_selection_source_v1_requests[] = {
+ { "offer", "s", wp_primary_selection_unstable_v1_types + 0 },
+ { "destroy", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+static const struct wl_message zwp_primary_selection_source_v1_events[] = {
+ { "send", "sh", wp_primary_selection_unstable_v1_types + 0 },
+ { "cancelled", "", wp_primary_selection_unstable_v1_types + 0 },
+};
+
+WL_PRIVATE const struct wl_interface zwp_primary_selection_source_v1_interface = {
+ "zwp_primary_selection_source_v1", 1,
+ 2, zwp_primary_selection_source_v1_requests,
+ 2, zwp_primary_selection_source_v1_events,
+};
diff --git a/widget/gtk/wayland/va_drmcommon.h b/widget/gtk/wayland/va_drmcommon.h
new file mode 100644
index 0000000000..e16f244a46
--- /dev/null
+++ b/widget/gtk/wayland/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/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,
+};