Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#include "nsWindow.h"
#include <algorithm>
#include <cstdint>
#include <dlfcn.h>
#include <gdk/gdkkeysyms.h>
#include <wchar.h>
#include "VsyncSource.h"
#include "gfx2DGlue.h"
#include "gfxContext.h"
#include "gfxImageSurface.h"
#include "gfxPlatformGtk.h"
#include "gfxUtils.h"
#include "GLContextProvider.h"
#include "GLContext.h"
#include "GtkCompositorWidget.h"
#include "gtkdrawing.h"
#include "imgIContainer.h"
#include "InputData.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Components.h"
#include "mozilla/GRefPtr.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/HelpersCairo.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/Likely.h"
#include "mozilla/Maybe.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/NativeKeyBindingsType.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_mozilla.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/WidgetUtils.h"
#include "mozilla/WritingModes.h"
#ifdef MOZ_X11
# include "mozilla/X11Util.h"
#endif
#include "mozilla/XREAppData.h"
#include "NativeKeyBindings.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsAppRunner.h"
#include "nsDragService.h"
#include "nsGTKToolkit.h"
#include "nsGtkKeyUtils.h"
#include "nsGtkCursors.h"
#include "nsGfxCIID.h"
#include "nsGtkUtils.h"
#include "nsIFile.h"
#include "nsIGSettingsService.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsImageToPixbuf.h"
#include "nsINode.h"
#include "nsIRollupListener.h"
#include "nsIScreenManager.h"
#include "nsIUserIdleServiceInternal.h"
#include "nsIWidgetListener.h"
#include "nsLayoutUtils.h"
#include "nsMenuPopupFrame.h"
#include "nsPresContext.h"
#include "nsShmImage.h"
#include "nsString.h"
#include "nsWidgetsCID.h"
#include "nsViewManager.h"
#include "nsXPLookAndFeel.h"
#include "prlink.h"
#include "Screen.h"
#include "ScreenHelperGTK.h"
#include "SystemTimeConverter.h"
#include "WidgetUtilsGtk.h"
#include "NativeMenuGtk.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/LocalAccessible.h"
# include "mozilla/a11y/Platform.h"
# include "nsAccessibilityService.h"
#endif
#ifdef MOZ_X11
# include <gdk/gdkkeysyms-compat.h>
# include <X11/Xatom.h>
# include <X11/extensions/XShm.h>
# include <X11/extensions/shape.h>
# include "gfxXlibSurface.h"
# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
# include "WindowSurfaceX11Image.h"
# include "WindowSurfaceX11SHM.h"
#endif
#ifdef MOZ_WAYLAND
# include <gdk/gdkkeysyms-compat.h>
# include "nsIClipboard.h"
# include "nsView.h"
# include "WaylandVsyncSource.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
#ifdef MOZ_X11
using mozilla::gl::GLContextEGL;
using mozilla::gl::GLContextGLX;
#endif
// Don't put more than this many rects in the dirty region, just fluff
// out to the bounding-box if there are more
#define MAX_RECTS_IN_REGION 100
#if !GTK_CHECK_VERSION(3, 22, 0)
constexpr gint GDK_WINDOW_STATE_TOP_TILED = 1 << 9;
constexpr gint GDK_WINDOW_STATE_TOP_RESIZABLE = 1 << 10;
constexpr gint GDK_WINDOW_STATE_RIGHT_TILED = 1 << 11;
constexpr gint GDK_WINDOW_STATE_RIGHT_RESIZABLE = 1 << 12;
constexpr gint GDK_WINDOW_STATE_BOTTOM_TILED = 1 << 13;
constexpr gint GDK_WINDOW_STATE_BOTTOM_RESIZABLE = 1 << 14;
constexpr gint GDK_WINDOW_STATE_LEFT_TILED = 1 << 15;
constexpr gint GDK_WINDOW_STATE_LEFT_RESIZABLE = 1 << 16;
#endif
constexpr gint kPerSideTiledStates =
GDK_WINDOW_STATE_TOP_TILED | GDK_WINDOW_STATE_RIGHT_TILED |
GDK_WINDOW_STATE_BOTTOM_TILED | GDK_WINDOW_STATE_LEFT_TILED;
constexpr gint kTiledStates = GDK_WINDOW_STATE_TILED | kPerSideTiledStates;
constexpr gint kResizableStates =
GDK_WINDOW_STATE_TOP_RESIZABLE | GDK_WINDOW_STATE_RIGHT_RESIZABLE |
GDK_WINDOW_STATE_BOTTOM_RESIZABLE | GDK_WINDOW_STATE_LEFT_RESIZABLE;
#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;
};
constexpr gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
constexpr GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
#endif
constexpr gint kEvents =
GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK | GDK_STRUCTURE_MASK |
GDK_VISIBILITY_NOTIFY_MASK | GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK | GDK_SMOOTH_SCROLL_MASK |
GDK_TOUCH_MASK | GDK_SCROLL_MASK | GDK_POINTER_MOTION_MASK |
GDK_PROPERTY_CHANGE_MASK;
/* utility functions */
static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
gdouble aMouseY);
static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
static nsWindow* get_window_for_gdk_window(GdkWindow* window);
static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
static GdkCursor* get_gtk_cursor(nsCursor aCursor);
/* callbacks from widgets */
static gboolean expose_event_cb(GtkWidget* widget, cairo_t* cr);
static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
static void size_allocate_cb(GtkWidget* widget, GtkAllocation* allocation);
static void toplevel_window_size_allocate_cb(GtkWidget* widget,
GtkAllocation* allocation);
static gboolean delete_event_cb(GtkWidget* widget, GdkEventAny* event);
static gboolean enter_notify_event_cb(GtkWidget* widget,
GdkEventCrossing* event);
static gboolean leave_notify_event_cb(GtkWidget* widget,
GdkEventCrossing* event);
static gboolean motion_notify_event_cb(GtkWidget* widget,
GdkEventMotion* event);
MOZ_CAN_RUN_SCRIPT static gboolean button_press_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean button_release_event_cb(GtkWidget* widget,
GdkEventButton* event);
static gboolean focus_in_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean focus_out_event_cb(GtkWidget* widget, GdkEventFocus* event);
static gboolean key_press_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean key_release_event_cb(GtkWidget* widget, GdkEventKey* event);
static gboolean property_notify_event_cb(GtkWidget* widget,
GdkEventProperty* event);
static gboolean scroll_event_cb(GtkWidget* widget, GdkEventScroll* event);
static gboolean visibility_notify_event_cb(GtkWidget* widget,
GdkEventVisibility* event);
static void hierarchy_changed_cb(GtkWidget* widget,
GtkWidget* previous_toplevel);
static gboolean window_state_event_cb(GtkWidget* widget,
GdkEventWindowState* event);
static void settings_xft_dpi_changed_cb(GtkSettings* settings,
GParamSpec* pspec, nsWindow* data);
static void check_resize_cb(GtkContainer* container, gpointer user_data);
static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
gpointer aPointer);
static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
static void widget_destroy_cb(GtkWidget* widget, gpointer user_data);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef MOZ_X11
static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
GdkEvent* event, gpointer data);
#endif /* MOZ_X11 */
#ifdef __cplusplus
}
#endif /* __cplusplus */
static gboolean drag_motion_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_leave_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, guint aTime,
gpointer aData);
static gboolean drag_drop_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_data_received_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY,
GtkSelectionData* aSelectionData,
guint aInfo, guint32 aTime,
gpointer aData);
/* initialization static functions */
static nsresult initialize_prefs(void);
static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
static SystemTimeConverter<guint32>& TimeConverter() {
static SystemTimeConverter<guint32> sTimeConverterSingleton;
return sTimeConverterSingleton;
}
bool nsWindow::sTransparentMainWindow = false;
// forward declare from mozgtk
extern "C" MOZ_EXPORT void mozgtk_linker_holder();
namespace mozilla {
#ifdef MOZ_X11
class CurrentX11TimeGetter {
public:
explicit CurrentX11TimeGetter(GdkWindow* aWindow) : mWindow(aWindow) {}
guint32 GetCurrentTime() const { return gdk_x11_get_server_time(mWindow); }
void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
// Check for in-flight request
if (!mAsyncUpdateStart.IsNull()) {
return;
}
mAsyncUpdateStart = aNow;
Display* xDisplay = GDK_WINDOW_XDISPLAY(mWindow);
Window xWindow = GDK_WINDOW_XID(mWindow);
unsigned char c = 'a';
Atom timeStampPropAtom = TimeStampPropAtom();
XChangeProperty(xDisplay, xWindow, timeStampPropAtom, timeStampPropAtom, 8,
PropModeReplace, &c, 1);
XFlush(xDisplay);
}
gboolean PropertyNotifyHandler(GtkWidget* aWidget, GdkEventProperty* aEvent) {
if (aEvent->atom != gdk_x11_xatom_to_atom(TimeStampPropAtom())) {
return FALSE;
}
guint32 eventTime = aEvent->time;
TimeStamp lowerBound = mAsyncUpdateStart;
TimeConverter().CompensateForBackwardsSkew(eventTime, lowerBound);
mAsyncUpdateStart = TimeStamp();
return TRUE;
}
private:
static Atom TimeStampPropAtom() {
return gdk_x11_get_xatom_by_name_for_display(gdk_display_get_default(),
"GDK_TIMESTAMP_PROP");
}
// This is safe because this class is stored as a member of mWindow and
// won't outlive it.
GdkWindow* mWindow;
TimeStamp mAsyncUpdateStart;
};
#endif
} // namespace mozilla
// The window from which the focus manager asks us to dispatch key events.
static nsWindow* gFocusWindow = nullptr;
static bool gBlockActivateEvent = false;
static bool gGlobalsInitialized = false;
static bool gUseAspectRatio = true;
static uint32_t gLastTouchID = 0;
// event is a correct one when we get it.
// Store it and issue it later from enter notify event if it's correct,
// throw it away otherwise.
MOZ_RUNINIT static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
#define NS_WINDOW_TITLE_MAX_LENGTH 4095
// cursor cache
static GdkCursor* gCursorCache[eCursorCount];
// Sometimes this actually also includes the state of the modifier keys, but
// only the button state bits are used.
static guint gButtonState;
static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
// Timestamps are just the least significant bits of a monotonically
// increasing function, and so the use of unsigned overflow arithmetic.
return a - b <= G_MAXUINT32 / 2;
}
static void UpdateLastInputEventTime(void* aGdkEvent) {
nsCOMPtr<nsIUserIdleServiceInternal> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1");
if (idleService) {
idleService->ResetIdleTimeOut(0);
}
guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
if (timestamp == GDK_CURRENT_TIME) {
return;
}
sLastUserInputTime = timestamp;
}
// Don't set parent (transient for) if nothing changes.
// gtk_window_set_transient_for() blows up wl_subsurfaces used by aWindow
// even if aParent is the same.
static void GtkWindowSetTransientFor(GtkWindow* aWindow, GtkWindow* aParent) {
GtkWindow* parent = gtk_window_get_transient_for(aWindow);
if (parent != aParent) {
gtk_window_set_transient_for(aWindow, aParent);
}
}
#define gtk_window_set_transient_for(a, b) \
{ \
MOZ_ASSERT_UNREACHABLE( \
"gtk_window_set_transient_for() can't be used directly."); \
}
nsWindow::nsWindow()
: mWindowVisibilityMutex("nsWindow::mWindowVisibilityMutex"),
mIsMapped(false),
mIsDestroyed(false),
mIsShown(false),
mNeedsShow(false),
mEnabled(true),
mCreated(false),
mHandleTouchEvent(false),
mIsDragPopup(false),
mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
mIsAccelerated(false),
mIsAlert(false),
mWindowShouldStartDragging(false),
mHasMappedToplevel(false),
mPanInProgress(false),
mTitlebarBackdropState(false),
mIsChildWindow(false),
mAlwaysOnTop(false),
mNoAutoHide(false),
mIsTransparent(false),
mHasReceivedSizeAllocate(false),
mWidgetCursorLocked(false),
mUndecorated(false),
mPopupTrackInHierarchy(false),
mPopupTrackInHierarchyConfigured(false),
mHiddenPopupPositioned(false),
mHasAlphaVisual(false),
mPopupAnchored(false),
mPopupContextMenu(false),
mPopupMatchesLayout(false),
mPopupChanged(false),
mPopupTemporaryHidden(false),
mPopupClosed(false),
mPopupUseMoveToRect(false),
mWaitingForMoveToRectCallback(false),
mMovedAfterMoveToRect(false),
mResizedAfterMoveToRect(false),
mConfiguredClearColor(false),
mGotNonBlankPaint(false),
mNeedsToRetryCapturingMouse(false) {
mWindowType = WindowType::Child;
mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize);
if (!gGlobalsInitialized) {
gGlobalsInitialized = true;
// It's OK if either of these fail, but it may not be one day.
initialize_prefs();
#ifdef MOZ_WAYLAND
// Wayland provides clipboard data to application on focus-in event
// so we need to init our clipboard hooks before we create window
// and get focus.
if (GdkIsWaylandDisplay()) {
nsCOMPtr<nsIClipboard> clipboard =
do_GetService("@mozilla.org/widget/clipboard;1");
NS_ASSERTION(clipboard, "Failed to init clipboard!");
}
#endif
}
// Dummy call to mozgtk to prevent the linker from removing
// the dependency with --as-needed.
// see toolkit/library/moz.build for details.
mozgtk_linker_holder();
}
nsWindow::~nsWindow() {
LOG("nsWindow::~nsWindow()");
MOZ_DIAGNOSTIC_ASSERT(mIsDestroyed);
Destroy();
}
/* static */
void nsWindow::ReleaseGlobals() {
for (auto& cursor : gCursorCache) {
if (cursor) {
g_object_unref(cursor);
cursor = nullptr;
}
}
}
void nsWindow::DispatchActivateEvent(void) {
#ifdef ACCESSIBILITY
DispatchActivateEventAccessible();
#endif // ACCESSIBILITY
if (mWidgetListener) mWidgetListener->WindowActivated();
}
void nsWindow::DispatchDeactivateEvent() {
if (mWidgetListener) {
mWidgetListener->WindowDeactivated();
}
#ifdef ACCESSIBILITY
DispatchDeactivateEventAccessible();
#endif // ACCESSIBILITY
}
void nsWindow::DispatchResized() {
LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
(int)(mBounds.height));
mNeedsDispatchSize = LayoutDeviceIntSize(-1, -1);
if (mWidgetListener) {
mWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
}
if (mAttachedWidgetListener) {
mAttachedWidgetListener->WindowResized(this, mBounds.width, mBounds.height);
}
}
void nsWindow::MaybeDispatchResized() {
if (mNeedsDispatchSize != LayoutDeviceIntSize(-1, -1) && !mIsDestroyed) {
mBounds.SizeTo(mNeedsDispatchSize);
// Check mBounds size
if (mCompositorSession &&
!wr::WindowSizeSanityCheck(mBounds.width, mBounds.height)) {
gfxCriticalNoteOnce << "Invalid mBounds in MaybeDispatchResized "
<< mBounds << " size state " << mSizeMode;
}
// Notify the GtkCompositorWidget of a ClientSizeChange
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->NotifyClientSizeChanged(GetClientSize());
}
DispatchResized();
}
}
nsIWidgetListener* nsWindow::GetListener() {
return mAttachedWidgetListener ? mAttachedWidgetListener : mWidgetListener;
}
nsresult nsWindow::DispatchEvent(WidgetGUIEvent* aEvent,
nsEventStatus& aStatus) {
#ifdef DEBUG
debug_DumpEvent(stdout, aEvent->mWidget, aEvent, "something", 0);
#endif
aStatus = nsEventStatus_eIgnore;
nsIWidgetListener* listener = GetListener();
if (listener) {
aStatus = listener->HandleEvent(aEvent, mUseAttachedEvents);
}
return NS_OK;
}
void nsWindow::OnDestroy(void) {
if (mOnDestroyCalled) {
return;
}
mOnDestroyCalled = true;
// Prevent deletion.
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
// release references to children, device context, toolkit + app shell
nsBaseWidget::OnDestroy();
// Remove association between this object and its parent and siblings.
nsBaseWidget::Destroy();
RemoveAllChildren();
NotifyWindowDestroyed();
}
bool nsWindow::AreBoundsSane() {
// Check requested size, as mBounds might not have been updated.
return !mLastSizeRequest.IsEmpty();
}
void nsWindow::Destroy() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
if (mIsDestroyed || !mCreated) {
return;
}
LOG("nsWindow::Destroy\n");
mIsDestroyed = true;
mCreated = false;
MozClearHandleID(mCompositorPauseTimeoutID, g_source_remove);
#ifdef MOZ_WAYLAND
// Shut down our local vsync source
if (mWaylandVsyncSource) {
mWaylandVsyncSource->Shutdown();
mWaylandVsyncSource = nullptr;
}
mWaylandVsyncDispatcher = nullptr;
UnlockNativePointer();
#endif
// Cancel (dragleave) the current drag session, if any.
RefPtr<nsDragService> dragService = nsDragService::GetInstance();
if (dragService) {
nsDragSession* dragSession =
static_cast<nsDragSession*>(dragService->GetCurrentSession(this));
if (dragSession && this == dragSession->GetMostRecentDestWindow()) {
dragSession->ScheduleLeaveEvent();
}
}
nsIRollupListener* rollupListener = nsBaseWidget::GetActiveRollupListener();
if (rollupListener) {
nsCOMPtr<nsIWidget> rollupWidget = rollupListener->GetRollupWidget();
if (static_cast<nsIWidget*>(this) == rollupWidget) {
rollupListener->Rollup({});
}
}
NativeShow(false);
MOZ_ASSERT(!gtk_widget_get_mapped(mShell));
MOZ_ASSERT(!gtk_widget_get_mapped(GTK_WIDGET(mContainer)));
DestroyLayerManager();
// mSurfaceProvider holds reference to this nsWindow so we need to explicitly
// clear it here to avoid nsWindow leak.
mSurfaceProvider.CleanupResources();
g_signal_handlers_disconnect_by_data(gtk_settings_get_default(), this);
if (mIMContext) {
mIMContext->OnDestroyWindow(this);
}
// make sure that we remove ourself as the focus window
if (gFocusWindow == this) {
LOG("automatically losing focus...\n");
gFocusWindow = nullptr;
}
if (sStoredLeaveNotifyEvent) {
nsWindow* window =
get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
if (window == this) {
sStoredLeaveNotifyEvent = nullptr;
}
}
// We need to detach accessible object here because mContainer is a custom
// widget and doesn't call gtk_widget_real_destroy() from destroy handler
// as regular widgets.
if (AtkObject* ac = gtk_widget_get_accessible(GTK_WIDGET(mContainer))) {
gtk_accessible_set_widget(GTK_ACCESSIBLE(ac), nullptr);
}
gtk_widget_destroy(mShell);
mShell = nullptr;
mContainer = nullptr;
MOZ_ASSERT(!mGdkWindow,
"mGdkWindow should be NULL when mContainer is destroyed");
#ifdef ACCESSIBILITY
if (mRootAccessible) {
mRootAccessible = nullptr;
}
#endif
// Save until last because OnDestroy() may cause us to be deleted.
OnDestroy();
}
float nsWindow::GetDPI() {
float dpi = 96.0f;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (screen) {
screen->GetDpi(&dpi);
}
return dpi;
}
double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
#ifdef MOZ_WAYLAND
if (GdkIsWaylandDisplay()) {
return DesktopToLayoutDeviceScale(FractionalScaleFactor());
}
#endif
// In Gtk/X11, we manage windows using device pixels.
return DesktopToLayoutDeviceScale(1.0);
}
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
#ifdef MOZ_WAYLAND
if (GdkIsWaylandDisplay()) {
// In wayland there's no absolute screen position, so we need to use the
// scale factor of our top level, which is what FractionalScaleFactor does,
// luckily.
return DesktopToLayoutDeviceScale(FractionalScaleFactor());
}
#endif
return nsBaseWidget::GetDesktopToDeviceScale();
}
bool nsWindow::WidgetTypeSupportsAcceleration() {
if (IsSmallPopup()) {
return false;
}
if (mWindowType == WindowType::Popup) {
return HasRemoteContent();
}
return true;
}
void nsWindow::DidChangeParent(nsIWidget* aOldParent) {
LOG("nsWindow::DidChangeParent new parent %p -> %p\n", aOldParent, mParent);
if (!mParent) {
return;
}
auto* newParent = static_cast<nsWindow*>(mParent);
if (mIsDestroyed || newParent->IsDestroyed()) {
return;
}
if (!IsTopLevelWidget()) {
GdkWindow* window = GetToplevelGdkWindow();
GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
gdk_window_reparent(window, parentWindow, 0, 0);
SetHasMappedToplevel(newParent->mHasMappedToplevel);
return;
}
GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
GtkWindowSetTransientFor(GTK_WINDOW(mShell), newParentWidget);
}
static void InitPenEvent(WidgetMouseEvent& aGeckoEvent, GdkEvent* aEvent) {
// Find the source of the event
GdkDevice* device = gdk_event_get_source_device(aEvent);
GdkInputSource eSource = gdk_device_get_source(device);
gdouble value;
// We distinguish touch screens from pens using the event type
// Eraser corresponds to the pen with the "erase" button pressed
if (eSource != GDK_SOURCE_PEN && eSource != GDK_SOURCE_ERASER) {
bool XWaylandPen = false;
#ifdef MOZ_X11
// Workaround : When using Xwayland, pens are reported as
// GDK_SOURCE_TOUCHSCREEN If eSource is GDK_SOURCE_TOUCHSCREEN and the
// GDK_AXIS_XTILT and GDK_AXIS_YTILT axes are reported then it's a pen and
// not a finger on a screen. Yes, that's a stupid heuristic but it works...
// Note, however, that the tilt values are not reliable
// Another approach could be use the device tool type, but that's only
// available in GTK > 3.22
XWaylandPen = (eSource == GDK_SOURCE_TOUCHSCREEN && GdkIsX11Display() &&
gdk_event_get_axis(aEvent, GDK_AXIS_XTILT, &value) &&
gdk_event_get_axis(aEvent, GDK_AXIS_YTILT, &value));
#endif
if (!XWaylandPen) {
return;
}
LOGW("InitPenEvent(): Is XWayland pen");
}
aGeckoEvent.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_PEN;
aGeckoEvent.pointerId = 1;
// The range of xtilt and ytilt are -1 to 1. Normalize it to -90 to 90.
if (gdk_event_get_axis(aEvent, GDK_AXIS_XTILT, &value)) {
aGeckoEvent.tiltX = int32_t(NS_round(value * 90));
}
if (gdk_event_get_axis(aEvent, GDK_AXIS_YTILT, &value)) {
aGeckoEvent.tiltY = int32_t(NS_round(value * 90));
}
if (gdk_event_get_axis(aEvent, GDK_AXIS_PRESSURE, &value)) {
aGeckoEvent.mPressure = (float)value;
// Make sure the pression is acceptable
MOZ_ASSERT(aGeckoEvent.mPressure >= 0.0 && aGeckoEvent.mPressure <= 1.0);
}
LOGW("InitPenEvent(): pressure %f\n", aGeckoEvent.mPressure);
}
void nsWindow::SetModal(bool aModal) {
LOG("nsWindow::SetModal %d\n", aModal);
if (mIsDestroyed) {
return;
}
gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
}
// nsIWidget method, which means IsShown.
bool nsWindow::IsVisible() const { return mIsShown; }
bool nsWindow::IsMapped() const { return mIsMapped; }
void nsWindow::RegisterTouchWindow() {
mHandleTouchEvent = true;
mTouches.Clear();
}
LayoutDeviceIntPoint nsWindow::GetScreenEdgeSlop() {
if (DrawsToCSDTitlebar()) {
return GetClientOffset();
}
return {};
}
void nsWindow::ConstrainPosition(DesktopIntPoint& aPoint) {
if (!mShell || GdkIsWaylandDisplay()) {
return;
}
double dpiScale = GetDefaultScale().scale;
// we need to use the window size in logical screen pixels
int32_t logWidth = std::max(NSToIntRound(mBounds.width / dpiScale), 1);
int32_t logHeight = std::max(NSToIntRound(mBounds.height / dpiScale), 1);
/* get our playing field. use the current screen, or failing that
for any reason, use device caps for the default screen. */
nsCOMPtr<nsIScreenManager> screenmgr =
do_GetService("@mozilla.org/gfx/screenmanager;1");
if (!screenmgr) {
return;
}
nsCOMPtr<nsIScreen> screen;
screenmgr->ScreenForRect(aPoint.x, aPoint.y, logWidth, logHeight,
getter_AddRefs(screen));
// We don't have any screen so leave the coordinates as is
if (!screen) {
return;
}
// For normalized windows, use the desktop work area.
// For full screen windows, use the desktop.
DesktopIntRect screenRect = mSizeMode == nsSizeMode_Fullscreen
? screen->GetRectDisplayPix()
: screen->GetAvailRectDisplayPix();
// Expand for the decoration size if needed.
auto slop =
DesktopIntPoint::Round(GetScreenEdgeSlop() / GetDesktopToDeviceScale());
screenRect.Inflate(slop.x, slop.y);
aPoint = ConstrainPositionToBounds(aPoint, {logWidth, logHeight}, screenRect);
}
void nsWindow::SetSizeConstraints(const SizeConstraints& aConstraints) {
mSizeConstraints.mMinSize = GetSafeWindowSize(aConstraints.mMinSize);
mSizeConstraints.mMaxSize = GetSafeWindowSize(aConstraints.mMaxSize);
ApplySizeConstraints();
}
bool nsWindow::DrawsToCSDTitlebar() const {
return mSizeMode == nsSizeMode_Normal &&
mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar;
}
void nsWindow::AddCSDDecorationSize(int* aWidth, int* aHeight) {
if (mSizeMode != nsSizeMode_Normal || mUndecorated ||
mGtkWindowDecoration != GTK_DECORATION_CLIENT || !GdkIsWaylandDisplay() ||
!IsGnomeDesktopEnvironment()) {
return;
}
GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
*aWidth += decorationSize.left + decorationSize.right;
*aHeight += decorationSize.top + decorationSize.bottom;
}
#ifdef MOZ_WAYLAND
bool nsWindow::GetCSDDecorationOffset(int* aDx, int* aDy) {
if (!DrawsToCSDTitlebar()) {
return false;
}
GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
*aDx = decorationSize.left;
*aDy = decorationSize.top;
return true;
}
#endif
void nsWindow::ApplySizeConstraints() {
if (mShell) {
GdkGeometry geometry;
geometry.min_width =
DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.width);
geometry.min_height =
DevicePixelsToGdkCoordRoundUp(mSizeConstraints.mMinSize.height);
geometry.max_width =
DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.width);
geometry.max_height =
DevicePixelsToGdkCoordRoundDown(mSizeConstraints.mMaxSize.height);
uint32_t hints = 0;
if (mSizeConstraints.mMinSize != LayoutDeviceIntSize()) {
gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
geometry.min_height);
AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
hints |= GDK_HINT_MIN_SIZE;
}
if (mSizeConstraints.mMaxSize !=
LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
hints |= GDK_HINT_MAX_SIZE;
}
if (mAspectRatio != 0.0f && !mAspectResizer) {
geometry.min_aspect = mAspectRatio;
geometry.max_aspect = mAspectRatio;
hints |= GDK_HINT_ASPECT;
}
gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, &geometry,
GdkWindowHints(hints));
}
}
void nsWindow::Show(bool aState) {
if (aState == mIsShown) {
return;
}
mIsShown = aState;
#ifdef MOZ_LOGGING
LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
if (!aState && mSourceDragContext && GdkIsWaylandDisplay()) {
LOG(" closing Drag&Drop source window, D&D will be canceled!");
}
#endif
// Ok, someone called show on a window that isn't sized to a sane
// value. Mark this window as needing to have Show() called on it
// and return.
if ((aState && !AreBoundsSane()) || !mCreated) {
LOG("\tbounds are insane or window hasn't been created yet\n");
mNeedsShow = true;
return;
}
// If someone is hiding this widget, clear any needing show flag.
if (!aState) mNeedsShow = false;
#ifdef ACCESSIBILITY
if (aState && a11y::ShouldA11yBeEnabled()) {
CreateRootAccessible();
}
#endif
NativeShow(aState);
RefreshWindowClass();
}
void nsWindow::ResizeInt(const Maybe<LayoutDeviceIntPoint>& aMove,
LayoutDeviceIntSize aSize) {
LOG("nsWindow::ResizeInt w:%d h:%d\n", aSize.width, aSize.height);
const bool moved = aMove && *aMove != mBounds.TopLeft();
if (moved) {
mBounds.MoveTo(*aMove);
LOG(" with move to left:%d top:%d", aMove->x.value, aMove->y.value);
}
ConstrainSize(&aSize.width, &aSize.height);
LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
#if MOZ_LOGGING
LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
resized, aSize.width, aSize.height, mLastSizeRequest.width,
mLastSizeRequest.height, mBounds.width, mBounds.height);
#endif
// For top-level windows, aSize should possibly be
// interpreted as frame bounds, but NativeMoveResize treats these as window
mLastSizeRequest = aSize;
// Check size
if (mCompositorSession &&
!wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
<< " size state " << mSizeMode;
}
// Recalculate aspect ratio when resized from DOM
if (mAspectRatio != 0.0) {
LockAspectRatio(true);
}
if (!mCreated) {
return;
}
if (!moved && !resized) {
LOG(" not moved or resized, quit");
return;
}
NativeMoveResize(moved, resized);
// We optimistically assume size changes immediately in two cases:
// 1. Override-redirect window: Size is controlled by only us.
// 2. Managed window that has not not yet received a size-allocate event:
// Resize() Callers expect initial sizes to be applied synchronously.
// If the size request is not honored, then we'll correct in
// OnSizeAllocate().
//
// When a managed window has already received a size-allocate, we cannot
// assume we'll always get a notification if our request does not get
// honored: "If the configure request has not changed, we don't ever resend
// it, because it could mean fighting the user or window manager."
// So we don't update mBounds until OnSizeAllocate() when we know the
// request is granted.
bool isOrWillBeVisible = mHasReceivedSizeAllocate || mNeedsShow || mIsShown;
if (!isOrWillBeVisible ||
gtk_window_get_window_type(GTK_WINDOW(mShell)) == GTK_WINDOW_POPUP) {
mBounds.SizeTo(aSize);
if (mCompositorWidgetDelegate) {
mCompositorWidgetDelegate->NotifyClientSizeChanged(aSize);
}
DispatchResized();
}
}
void nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) {
LOG("nsWindow::Resize %f %f\n", aWidth, aHeight);
double scale =
BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
ResizeInt(Nothing(), size);
}
void nsWindow::Resize(double aX, double aY, double aWidth, double aHeight,
bool aRepaint) {
LOG("nsWindow::Resize [%f,%f] -> [%f x %f] repaint %d\n", aX, aY, aWidth,
aHeight, aRepaint);
double scale =
BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
auto size = LayoutDeviceIntSize::Round(scale * aWidth, scale * aHeight);
auto topLeft = LayoutDeviceIntPoint::Round(scale * aX, scale * aY);
ResizeInt(Some(topLeft), size);
}
void nsWindow::Enable(bool aState) { mEnabled = aState; }
bool nsWindow::IsEnabled() const { return mEnabled; }
void nsWindow::Move(double aX, double aY) {
double scale =
BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0;
int32_t x = NSToIntRound(aX * scale);
int32_t y = NSToIntRound(aY * scale);
LOG("nsWindow::Move to %d x %d\n", x, y);
if (mSizeMode != nsSizeMode_Normal && IsTopLevelWidget()) {
LOG(" size state is not normal, bailing");
return;
}
// Since a popup window's x/y coordinates are in relation to to
// the parent, the parent might have moved so we always move a
// popup window.
LOG(" bounds %d x %d\n", mBounds.x, mBounds.y);
if (x == mBounds.x && y == mBounds.y && mWindowType != WindowType::Popup) {
LOG(" position is the same, return\n");
return;
}
// XXX Should we do some AreBoundsSane check here?
mBounds.x = x;
mBounds.y = y;
if (!mCreated) {
LOG(" is not created, return.\n");
return;
}
NativeMoveResize(/* move */ true, /* resize */ false);
}
bool nsWindow::IsPopup() const { return mWindowType == WindowType::Popup; }
bool nsWindow::IsWaylandPopup() const {
return GdkIsWaylandDisplay() && IsPopup();
}
static nsMenuPopupFrame* GetMenuPopupFrame(nsIFrame* aFrame) {
return do_QueryFrame(aFrame);
}
void nsWindow::AppendPopupToHierarchyList(nsWindow* aToplevelWindow) {
mWaylandToplevel = aToplevelWindow;
nsWindow* popup = aToplevelWindow;
while (popup && popup->mWaylandPopupNext) {
popup = popup->mWaylandPopupNext;
}
popup->mWaylandPopupNext = this;
mWaylandPopupPrev = popup;
mWaylandPopupNext = nullptr;
mPopupChanged = true;
mPopupClosed = false;
}
void nsWindow::RemovePopupFromHierarchyList() {
// We're already removed from the popup hierarchy
if (!IsInPopupHierarchy()) {
return;
}
mWaylandPopupPrev->mWaylandPopupNext = mWaylandPopupNext;
if (mWaylandPopupNext) {
mWaylandPopupNext->mWaylandPopupPrev = mWaylandPopupPrev;
mWaylandPopupNext->mPopupChanged = true;
}
mWaylandPopupNext = mWaylandPopupPrev = nullptr;
}
// Gtk refuses to map popup window with x < 0 && y < 0 relative coordinates
// as a workaround just fool around and place the popup temporary to 0,0.
bool nsWindow::WaylandPopupRemoveNegativePosition(int* aX, int* aY) {
// windows only
GdkWindow* window = GetToplevelGdkWindow();
if (!window || gdk_window_get_window_type(window) != GDK_WINDOW_TEMP) {
return false;
}
LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
int x, y;
gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
bool moveBack = (x < 0 && y < 0);
if (moveBack) {
gtk_window_move(GTK_WINDOW(mShell), 0, 0);
if (aX) {
*aX = x;
}
if (aY) {
*aY = y;
}
}
gdk_window_get_geometry(window, &x, &y, nullptr, nullptr);
if (x < 0 && y < 0) {
gdk_window_move(window, 0, 0);
}
return moveBack;
}
void nsWindow::ShowWaylandPopupWindow() {
LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
MOZ_ASSERT(IsWaylandPopup());
if (!mPopupTrackInHierarchy) {
LOG(" popup is not tracked in popup hierarchy, show it now");
gtk_widget_show(mShell);
return;
}
// Popup position was checked before gdk_window_move_to_rect() callback
// so just show it.
if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
LOG(" active move-to-rect callback, show it as is");
gtk_widget_show(mShell);
return;
}
if (gtk_widget_is_visible(mShell)) {
LOG(" is already visible, quit");
return;
}
int x, y;
bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
gtk_widget_show(mShell);
if (moved) {
LOG(" move back to (%d, %d) and show", x, y);
gtk_window_move(GTK_WINDOW(mShell), x, y);
}
}
void nsWindow::WaylandPopupMarkAsClosed() {
LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
mPopupClosed = true;
// If we have any child popup window notify it about
// parent switch.
if (mWaylandPopupNext) {
mWaylandPopupNext->mPopupChanged = true;
}
}
nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
while (aPopup && aPopup->mWaylandPopupNext) {
aPopup = aPopup->mWaylandPopupNext;
}
return aPopup;
}
// Hide and potentially removes popup from popup hierarchy.
void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
bool aRemoveFromPopupList) {
LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
aRemoveFromPopupList);
if (aRemoveFromPopupList) {
RemovePopupFromHierarchyList();
}
if (!mPopupClosed) {
mPopupClosed = !aTemporaryHide;
}
bool visible = gtk_widget_is_visible(mShell);
LOG(" gtk_widget_is_visible() = %d\n", visible);
// Restore only popups which are really visible
mPopupTemporaryHidden = aTemporaryHide && visible;
// Hide only visible popups or popups closed pernamently.
if (visible) {
gtk_widget_hide(mShell);
// If there's pending Move-To-Rect callback and we hide the popup
// the callback won't be called any more.
mWaitingForMoveToRectCallback = false;
}
if (mPopupClosed) {
LOG(" Clearing mMoveToRectPopupSize\n");
mMoveToRectPopupSize = {};
#ifdef MOZ_WAYLAND
if (moz_container_wayland_is_waiting_to_show(mContainer)) {
LOG(" popup failed to show by Wayland compositor, clear rendering "
"queue.");
moz_container_wayland_clear_waiting_to_show_flag(mContainer);
ClearRenderingQueue();
}
#endif
}
}
void nsWindow::HideWaylandToplevelWindow() {
LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
if (mWaylandPopupNext) {
nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
while (popup->mWaylandToplevel != nullptr) {
nsWindow* prev = popup->mWaylandPopupPrev;
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
popup = prev;
}
}
WaylandStopVsync();
gtk_widget_hide(mShell);
}
void nsWindow::ShowWaylandToplevelWindow() {
MOZ_ASSERT(!IsWaylandPopup());
LOG("nsWindow::ShowWaylandToplevelWindow");
gtk_widget_show(mShell);
}
void nsWindow::WaylandPopupRemoveClosedPopups() {
LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
nsWindow* popup = this;
while (popup) {
nsWindow* next = popup->mWaylandPopupNext;
if (popup->mPopupClosed) {
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
}
popup = next;
}
}
// Hide all tooltips except the latest one.
void nsWindow::WaylandPopupHideTooltips() {
LOG("nsWindow::WaylandPopupHideTooltips");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
nsWindow* popup = mWaylandPopupNext;
while (popup && popup->mWaylandPopupNext) {
if (popup->mPopupType == PopupType::Tooltip) {
LOG(" hidding tooltip [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupCloseOrphanedPopups() {
#ifdef MOZ_WAYLAND
LOG("nsWindow::WaylandPopupCloseOrphanedPopups");