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
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
#include "nsWindow.h"
#include <algorithm>
#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 "Layers.h"
#include "mozilla/ArrayUtils.h"
#include "mozilla/Assertions.h"
#include "mozilla/Components.h"
#include "mozilla/GRefPtr.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/WheelEventBinding.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/gfxVars.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/gfx/HelpersCairo.h"
#include "mozilla/layers/APZThreadUtils.h"
#include "mozilla/layers/LayersTypes.h"
#include "mozilla/layers/CompositorBridgeChild.h"
#include "mozilla/layers/CompositorBridgeParent.h"
#include "mozilla/layers/CompositorThread.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "mozilla/layers/WebRenderBridgeChild.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "mozilla/Likely.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/NativeKeyBindingsType.h"
#include "mozilla/Preferences.h"
#include "mozilla/PresShell.h"
#include "mozilla/ProfilerLabels.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_mozilla.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TextEventDispatcher.h"
#include "mozilla/TextEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/WidgetUtils.h"
#include "mozilla/WritingModes.h"
#ifdef MOZ_X11
# include "mozilla/X11Util.h"
#endif
#include "mozilla/XREAppData.h"
#include "NativeKeyBindings.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsAppRunner.h"
#include "nsDragService.h"
#include "nsGTKToolkit.h"
#include "nsGtkKeyUtils.h"
#include "nsGtkCursors.h"
#include "nsGfxCIID.h"
#include "nsGtkUtils.h"
#include "nsIFile.h"
#include "nsIGSettingsService.h"
#include "nsIInterfaceRequestorUtils.h"
#include "nsImageToPixbuf.h"
#include "nsINode.h"
#include "nsIRollupListener.h"
#include "nsIScreenManager.h"
#include "nsIUserIdleServiceInternal.h"
#include "nsIWidgetListener.h"
#include "nsLayoutUtils.h"
#include "nsMenuPopupFrame.h"
#include "nsPresContext.h"
#include "nsShmImage.h"
#include "nsString.h"
#include "nsWidgetsCID.h"
#include "nsViewManager.h"
#include "nsXPLookAndFeel.h"
#include "prlink.h"
#include "ScreenHelperGTK.h"
#include "SystemTimeConverter.h"
#include "WidgetUtilsGtk.h"
#ifdef ACCESSIBILITY
# include "mozilla/a11y/LocalAccessible.h"
# include "mozilla/a11y/Platform.h"
# include "nsAccessibilityService.h"
#endif
#ifdef MOZ_X11
# include <gdk/gdkkeysyms-compat.h>
# include <X11/Xatom.h>
# include <X11/extensions/XShm.h>
# include <X11/extensions/shape.h>
# include "gfxXlibSurface.h"
# include "GLContextGLX.h" // for GLContextGLX::FindVisual()
# include "GLContextEGL.h" // for GLContextEGL::FindVisual()
# include "WindowSurfaceX11Image.h"
# include "WindowSurfaceX11SHM.h"
#endif
#ifdef MOZ_WAYLAND
# include <gdk/gdkkeysyms-compat.h>
# include "nsIClipboard.h"
# include "nsView.h"
#endif
using namespace mozilla;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
#ifdef MOZ_X11
using mozilla::gl::GLContextEGL;
using mozilla::gl::GLContextGLX;
#endif
// Don't put more than this many rects in the dirty region, just fluff
// out to the bounding-box if there are more
#define MAX_RECTS_IN_REGION 100
#if !GTK_CHECK_VERSION(3, 18, 0)
struct _GdkEventTouchpadPinch {
GdkEventType type;
GdkWindow* window;
gint8 send_event;
gint8 phase;
gint8 n_fingers;
guint32 time;
gdouble x;
gdouble y;
gdouble dx;
gdouble dy;
gdouble angle_delta;
gdouble scale;
gdouble x_root, y_root;
guint state;
};
typedef enum {
GDK_TOUCHPAD_GESTURE_PHASE_BEGIN,
GDK_TOUCHPAD_GESTURE_PHASE_UPDATE,
GDK_TOUCHPAD_GESTURE_PHASE_END,
GDK_TOUCHPAD_GESTURE_PHASE_CANCEL
} GdkTouchpadGesturePhase;
gint GDK_TOUCHPAD_GESTURE_MASK = 1 << 24;
GdkEventType GDK_TOUCHPAD_PINCH = static_cast<GdkEventType>(42);
#endif
const gint kEvents = GDK_TOUCHPAD_GESTURE_MASK | GDK_EXPOSURE_MASK |
GDK_STRUCTURE_MASK | GDK_VISIBILITY_NOTIFY_MASK |
GDK_ENTER_NOTIFY_MASK | GDK_LEAVE_NOTIFY_MASK |
GDK_BUTTON_PRESS_MASK | GDK_BUTTON_RELEASE_MASK |
GDK_SMOOTH_SCROLL_MASK | GDK_TOUCH_MASK | GDK_SCROLL_MASK |
GDK_POINTER_MOTION_MASK | GDK_PROPERTY_CHANGE_MASK;
/* utility functions */
static bool is_mouse_in_window(GdkWindow* aWindow, gdouble aMouseX,
gdouble aMouseY);
static nsWindow* get_window_for_gtk_widget(GtkWidget* widget);
static nsWindow* get_window_for_gdk_window(GdkWindow* window);
static GtkWidget* get_gtk_widget_for_gdk_window(GdkWindow* window);
static GdkCursor* get_gtk_cursor(nsCursor aCursor);
static GdkWindow* get_inner_gdk_window(GdkWindow* aWindow, gint x, gint y,
gint* retx, gint* rety);
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* cr);
static gboolean configure_event_cb(GtkWidget* widget, GdkEventConfigure* event);
static void widget_map_cb(GtkWidget* widget);
static void widget_unmap_cb(GtkWidget* widget);
static void widget_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);
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 void hierarchy_changed_cb(GtkWidget* widget,
GtkWidget* previous_toplevel);
static gboolean window_state_event_cb(GtkWidget* widget,
GdkEventWindowState* event);
static void settings_xft_dpi_changed_cb(GtkSettings* settings,
GParamSpec* pspec, nsWindow* data);
static void check_resize_cb(GtkContainer* container, gpointer user_data);
static void screen_composited_changed_cb(GdkScreen* screen, gpointer user_data);
static void widget_composited_changed_cb(GtkWidget* widget, gpointer user_data);
static void scale_changed_cb(GtkWidget* widget, GParamSpec* aPSpec,
gpointer aPointer);
static gboolean touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent);
static gboolean generic_event_cb(GtkWidget* widget, GdkEvent* aEvent);
static nsWindow* GetFirstNSWindowForGDKWindow(GdkWindow* aGdkWindow);
#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */
#ifdef MOZ_X11
static GdkFilterReturn popup_take_focus_filter(GdkXEvent* gdk_xevent,
GdkEvent* event, gpointer data);
#endif /* MOZ_X11 */
#ifdef __cplusplus
}
#endif /* __cplusplus */
static gboolean drag_motion_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_leave_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, guint aTime,
gpointer aData);
static gboolean drag_drop_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY, guint aTime, gpointer aData);
static void drag_data_received_event_cb(GtkWidget* aWidget,
GdkDragContext* aDragContext, gint aX,
gint aY,
GtkSelectionData* aSelectionData,
guint aInfo, guint32 aTime,
gpointer aData);
/* initialization static functions */
static nsresult initialize_prefs(void);
static guint32 sLastUserInputTime = GDK_CURRENT_TIME;
static guint32 sRetryGrabTime;
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), 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;
};
#endif
} // namespace mozilla
// The window from which the focus manager asks us to dispatch key events.
static nsWindow* gFocusWindow = nullptr;
static bool gBlockActivateEvent = false;
static bool gGlobalsInitialized = false;
static bool gUseAspectRatio = true;
static uint32_t gLastTouchID = 0;
// See Bug 1777269 for details. We don't know if the suspected leave notify
// event is a correct one when we get it.
// Store it and issue it later from enter notify event if it's correct,
// throw it away otherwise.
static GUniquePtr<GdkEventCrossing> sStoredLeaveNotifyEvent;
#define NS_WINDOW_TITLE_MAX_LENGTH 4095
#define kWindowPositionSlop 20
// cursor cache
static GdkCursor* gCursorCache[eCursorCount];
// Sometimes this actually also includes the state of the modifier keys, but
// only the button state bits are used.
static guint gButtonState;
static inline int32_t GetBitmapStride(int32_t width) {
#if defined(MOZ_X11)
return (width + 7) / 8;
#else
return cairo_format_stride_for_width(CAIRO_FORMAT_A1, width);
#endif
}
static inline bool TimestampIsNewerThan(guint32 a, guint32 b) {
// Timestamps are just the least significant bits of a monotonically
// increasing function, and so the use of unsigned overflow arithmetic.
return a - b <= G_MAXUINT32 / 2;
}
static void UpdateLastInputEventTime(void* aGdkEvent) {
nsCOMPtr<nsIUserIdleServiceInternal> idleService =
do_GetService("@mozilla.org/widget/useridleservice;1");
if (idleService) {
idleService->ResetIdleTimeOut(0);
}
guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent));
if (timestamp == GDK_CURRENT_TIME) return;
sLastUserInputTime = timestamp;
}
nsWindow::nsWindow()
: mIsDestroyed(false),
mNeedsDispatchResized(false),
mIsShown(false),
mNeedsShow(false),
mIsMapped(false),
mEnabled(true),
mCreated(false),
mHandleTouchEvent(false),
mIsDragPopup(false),
mWindowScaleFactorChanged(true),
mCompositedScreen(gdk_screen_is_composited(gdk_screen_get_default())),
mIsAccelerated(false),
mWindowShouldStartDragging(false),
mHasMappedToplevel(false),
mRetryPointerGrab(false),
mPanInProgress(false),
mDrawToContainer(false),
mTitlebarBackdropState(false),
mIsPIPWindow(false),
mIsWaylandPanelWindow(false),
mIsChildWindow(false),
mAlwaysOnTop(false),
mNoAutoHide(false),
mIsTransparent(false),
mHasReceivedSizeAllocate(false),
mPopupTrackInHierarchy(false),
mPopupTrackInHierarchyConfigured(false),
mHiddenPopupPositioned(false),
mTransparencyBitmapForTitlebar(false),
mHasAlphaVisual(false),
mPopupAnchored(false),
mPopupContextMenu(false),
mPopupMatchesLayout(false),
mPopupChanged(false),
mPopupTemporaryHidden(false),
mPopupClosed(false),
mPopupUseMoveToRect(false),
mWaitingForMoveToRectCallback(false),
mMovedAfterMoveToRect(false),
mResizedAfterMoveToRect(false),
mConfiguredClearColor(false),
mGotNonBlankPaint(false) {
mWindowType = eWindowType_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()");
delete[] mTransparencyBitmap;
mTransparencyBitmap = nullptr;
Destroy();
}
/* static */
void nsWindow::ReleaseGlobals() {
for (auto& cursor : gCursorCache) {
if (cursor) {
g_object_unref(cursor);
cursor = nullptr;
}
}
}
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() {
LOG("nsWindow::DispatchResized() size [%d, %d]", (int)(mBounds.width),
(int)(mBounds.height));
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() {
// Check requested size, as mBounds might not have been updated.
return !mLastSizeRequest.IsEmpty();
}
// Walk the list of child windows and call destroy on them.
void nsWindow::DestroyChildWindows() {
LOG("nsWindow::DestroyChildWindows()");
if (!mGdkWindow) {
return;
}
while (GList* children = gdk_window_peek_children(mGdkWindow)) {
GdkWindow* child = GDK_WINDOW(children->data);
nsWindow* kid = get_window_for_gdk_window(child);
if (kid) {
kid->Destroy();
}
}
}
void nsWindow::Destroy() {
MOZ_DIAGNOSTIC_ASSERT(NS_IsMainThread());
if (mIsDestroyed || !mCreated) return;
LOG("nsWindow::Destroy\n");
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;
MozClearPointer(mXdgToken, xdg_activation_token_v1_destroy);
#endif
/** Need to clean our LayerManager up while still alive */
DestroyLayerManager();
// Ensure any resources assigned to the window get cleaned up first
// to avoid double-freeing.
mSurfaceProvider.CleanupResources();
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) {
LOG("automatically losing focus...\n");
gFocusWindow = nullptr;
}
if (sStoredLeaveNotifyEvent) {
nsWindow* window =
get_window_for_gdk_window(sStoredLeaveNotifyEvent->window);
if (window == this) {
sStoredLeaveNotifyEvent = nullptr;
}
}
gtk_widget_destroy(mShell);
mShell = nullptr;
mContainer = nullptr;
MOZ_ASSERT(!mGdkWindow,
"mGdkWindow should be NULL when mContainer is destroyed");
#ifdef ACCESSIBILITY
if (mRootAccessible) {
mRootAccessible = nullptr;
}
#endif
// Save until last because OnDestroy() may cause us to be deleted.
OnDestroy();
}
nsIWidget* nsWindow::GetParent() { return mParent; }
float nsWindow::GetDPI() {
float dpi = 96.0f;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (screen) {
screen->GetDpi(&dpi);
}
return dpi;
}
double nsWindow::GetDefaultScaleInternal() { return FractionalScaleFactor(); }
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScale() {
#ifdef MOZ_WAYLAND
if (GdkIsWaylandDisplay()) {
return DesktopToLayoutDeviceScale(GdkCeiledScaleFactor());
}
#endif
// In Gtk/X11, we manage windows using device pixels.
return DesktopToLayoutDeviceScale(1.0);
}
DesktopToLayoutDeviceScale nsWindow::GetDesktopToDeviceScaleByScreen() {
#ifdef MOZ_WAYLAND
// In Wayland there's no way to get absolute position of the window and use it
// to determine the screen factor of the monitor on which the window is
// placed. The window is notified of the current scale factor but not at this
// point, so the GdkScaleFactor can return wrong value which can lead to wrong
// popup placement. We need to use parent's window scale factor for the new
// one.
if (GdkIsWaylandDisplay()) {
nsView* view = nsView::GetViewFor(this);
if (view) {
nsView* parentView = view->GetParent();
if (parentView) {
nsIWidget* parentWidget = parentView->GetNearestWidget(nullptr);
if (parentWidget) {
return DesktopToLayoutDeviceScale(
parentWidget->RoundsWidgetCoordinatesTo());
}
NS_WARNING("Widget has no parent");
}
} else {
NS_WARNING("Cannot find widget view");
}
}
#endif
return nsBaseWidget::GetDesktopToDeviceScale();
}
// Reparent a child window to a new parent.
void nsWindow::SetParent(nsIWidget* aNewParent) {
LOG("nsWindow::SetParent() new parent %p", aNewParent);
if (!mIsChildWindow) {
NS_WARNING("Used by child widgets only");
return;
}
nsCOMPtr<nsIWidget> kungFuDeathGrip = this;
if (mParent) {
mParent->RemoveChild(this);
}
mParent = aNewParent;
// We're already deleted, quit.
if (!mGdkWindow || mIsDestroyed || !aNewParent) {
return;
}
aNewParent->AddChild(this);
auto* newParent = static_cast<nsWindow*>(aNewParent);
// New parent is deleted, quit.
if (newParent->mIsDestroyed) {
Destroy();
return;
}
GdkWindow* window = GetToplevelGdkWindow();
GdkWindow* parentWindow = newParent->GetToplevelGdkWindow();
LOG(" child GdkWindow %p set parent GdkWindow %p", window, parentWindow);
gdk_window_reparent(window, parentWindow, 0, 0);
bool parentHasMappedToplevel = newParent && newParent->mHasMappedToplevel;
if (mHasMappedToplevel != parentHasMappedToplevel) {
SetHasMappedToplevel(parentHasMappedToplevel);
}
}
bool nsWindow::WidgetTypeSupportsAcceleration() {
if (mWindowType == eWindowType_invisible) {
return false;
}
if (IsSmallPopup()) {
return false;
}
// Workaround for Bug 1479135
// We draw transparent popups on non-compositing screens by SW as we don't
// implement X shape masks in WebRender.
if (mWindowType == eWindowType_popup) {
return HasRemoteContent() && mCompositedScreen;
}
return true;
}
void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
MOZ_ASSERT(aNewParent, "null widget");
MOZ_ASSERT(!mIsDestroyed, "");
MOZ_ASSERT(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, "");
MOZ_ASSERT(
!mParent,
"nsWindow::ReparentNativeWidget() works on toplevel windows only.");
auto* newParent = static_cast<nsWindow*>(aNewParent);
GtkWindow* newParentWidget = GTK_WINDOW(newParent->GetGtkWidget());
LOG("nsWindow::ReparentNativeWidget new parent %p\n", newParent);
gtk_window_set_transient_for(GTK_WINDOW(mShell), newParentWidget);
}
void nsWindow::SetModal(bool aModal) {
LOG("nsWindow::SetModal %d\n", aModal);
if (mIsDestroyed) return;
gtk_window_set_modal(GTK_WINDOW(mShell), aModal ? TRUE : FALSE);
}
// nsIWidget method, which means IsShown.
bool nsWindow::IsVisible() const { return mIsShown; }
void nsWindow::RegisterTouchWindow() {
mHandleTouchEvent = true;
mTouches.Clear();
}
void nsWindow::ConstrainPosition(bool aAllowSlop, int32_t* aX, int32_t* aY) {
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<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 (mSizeMode == nsSizeMode_Normal &&
mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
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 (mSizeMode == nsSizeMode_Normal &&
mGtkWindowDecoration == GTK_DECORATION_CLIENT && mDrawInTitlebar) {
GtkBorder decorationSize = GetCSDDecorationSize(IsPopup());
*aDx = decorationSize.left;
*aDy = decorationSize.top;
return true;
}
return false;
}
#endif
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)) {
if (GdkIsWaylandDisplay()) {
gtk_widget_set_size_request(GTK_WIDGET(mContainer), geometry.min_width,
geometry.min_height);
}
AddCSDDecorationSize(&geometry.min_width, &geometry.min_height);
hints |= GDK_HINT_MIN_SIZE;
}
if (mSizeConstraints.mMaxSize !=
LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) {
AddCSDDecorationSize(&geometry.max_width, &geometry.max_height);
hints |= GDK_HINT_MAX_SIZE;
}
if (mAspectRatio != 0.0f) {
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;
LOG("nsWindow::Show state %d frame %s\n", aState, GetFrameTag().get());
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(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, aMove->y);
}
ConstrainSize(&aSize.width, &aSize.height);
LOG(" ConstrainSize: w:%d h;%d\n", aSize.width, aSize.height);
const bool resized = aSize != mLastSizeRequest || mBounds.Size() != aSize;
#if MOZ_LOGGING
LOG(" resized %d aSize [%d, %d] mLastSizeRequest [%d, %d] mBounds [%d, %d]",
resized, aSize.width, aSize.height, mLastSizeRequest.width,
mLastSizeRequest.height, mBounds.width, mBounds.height);
#endif
// For top-level windows, aSize should possibly be
// interpreted as frame bounds, but NativeMoveResize treats these as window
// bounds (Bug 581866).
mLastSizeRequest = aSize;
// Check size
if (mCompositorSession &&
!wr::WindowSizeSanityCheck(aSize.width, aSize.height)) {
gfxCriticalNoteOnce << "Invalid aSize in ResizeInt " << aSize
<< " size state " << mSizeMode;
}
// Recalculate aspect ratio when resized from DOM
if (mAspectRatio != 0.0) {
LockAspectRatio(true);
}
if (!mCreated) {
return;
}
if (!moved && !resized) {
LOG(" not moved or resized, quit");
return;
}
NativeMoveResize(moved, resized);
// We optimistically assume size changes immediately in two cases:
// 1. Override-redirect window: Size is controlled by only us.
// 2. Managed window that has not not yet received a size-allocate event:
// Resize() Callers expect initial sizes to be applied synchronously.
// If the size request is not honored, then we'll correct in
// OnSizeAllocate().
//
// When a managed window has already received a size-allocate, we cannot
// assume we'll always get a notification if our request does not get
// honored: "If the configure request has not changed, we don't ever resend
// it, because it could mean fighting the user or window manager."
// So we don't update mBounds until OnSizeAllocate() when we know the
// request is granted.
if (!mHasReceivedSizeAllocate ||
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 && (mWindowType == eWindowType_toplevel ||
mWindowType == eWindowType_dialog)) {
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 != eWindowType_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 == eWindowType_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, i.e. tooltips & DND windows.
if (mPopupType != ePopupTypeTooltip) {
return false;
}
LOG("nsWindow::WaylandPopupRemoveNegativePosition()");
int x, y;
gtk_window_get_position(GTK_WINDOW(mShell), &x, &y);
if (x >= 0 || y >= 0) {
LOG(" coordinates are correct (%d, %d)", x, y);
return false;
}
// We need to reset coordinates of both GtkWindow and GtkWindow
LOG(" wrong coord (%d, %d) move to 0,0", x, y);
GdkWindow* window = gtk_widget_get_window(mShell);
gdk_window_move(window, 0, 0);
gtk_window_move(GTK_WINDOW(mShell), 0, 0);
if (aX) {
*aX = x;
}
if (aY) {
*aY = y;
}
return true;
}
void nsWindow::ShowWaylandPopupWindow() {
LOG("nsWindow::ShowWaylandPopupWindow. Expected to see visible.");
MOZ_ASSERT(IsWaylandPopup());
if (!mPopupTrackInHierarchy) {
LOG(" popup is not tracked in popup hierarchy, show it now");
gtk_widget_show(mShell);
return;
}
// Popup position was checked before gdk_window_move_to_rect() callback
// so just show it.
if (mPopupUseMoveToRect && mWaitingForMoveToRectCallback) {
LOG(" active move-to-rect callback, show it as is");
gtk_widget_show(mShell);
return;
}
if (gtk_widget_is_visible(mShell)) {
LOG(" is already visible, quit");
return;
}
int x, y;
bool moved = WaylandPopupRemoveNegativePosition(&x, &y);
gtk_widget_show(mShell);
if (moved) {
LOG(" move back to (%d, %d) and show", x, y);
gtk_window_move(GTK_WINDOW(mShell), x, y);
}
}
void nsWindow::WaylandPopupMarkAsClosed() {
LOG("nsWindow::WaylandPopupMarkAsClosed: [%p]\n", this);
mPopupClosed = true;
// If we have any child popup window notify it about
// parent switch.
if (mWaylandPopupNext) {
mWaylandPopupNext->mPopupChanged = true;
}
}
nsWindow* nsWindow::WaylandPopupFindLast(nsWindow* aPopup) {
while (aPopup && aPopup->mWaylandPopupNext) {
aPopup = aPopup->mWaylandPopupNext;
}
return aPopup;
}
// Hide and potentially removes popup from popup hierarchy.
void nsWindow::HideWaylandPopupWindow(bool aTemporaryHide,
bool aRemoveFromPopupList) {
LOG("nsWindow::HideWaylandPopupWindow: remove from list %d\n",
aRemoveFromPopupList);
if (aRemoveFromPopupList) {
RemovePopupFromHierarchyList();
}
if (!mPopupClosed) {
mPopupClosed = !aTemporaryHide;
}
bool visible = gtk_widget_is_visible(mShell);
LOG(" gtk_widget_is_visible() = %d\n", visible);
// Restore only popups which are really visible
mPopupTemporaryHidden = aTemporaryHide && visible;
// Hide only visible popups or popups closed pernamently.
if (visible) {
gtk_widget_hide(mShell);
// If there's pending Move-To-Rect callback and we hide the popup
// the callback won't be called any more.
mWaitingForMoveToRectCallback = false;
}
if (mPopupClosed) {
LOG(" Clearing mMoveToRectPopupSize\n");
mMoveToRectPopupSize = {};
#ifdef MOZ_WAYLAND
if (moz_container_wayland_is_waiting_to_show(mContainer)) {
// We need to clear rendering queue, see Bug 1782948.
LOG(" popup failed to show by Wayland compositor, clear rendering "
"queue.");
moz_container_wayland_clear_waiting_to_show_flag(mContainer);
ClearRenderingQueue();
}
#endif
}
}
void nsWindow::HideWaylandToplevelWindow() {
LOG("nsWindow::HideWaylandToplevelWindow: [%p]\n", this);
if (mWaylandPopupNext) {
nsWindow* popup = WaylandPopupFindLast(mWaylandPopupNext);
while (popup->mWaylandToplevel != nullptr) {
nsWindow* prev = popup->mWaylandPopupPrev;
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
popup = prev;
}
}
WaylandStopVsync();
gtk_widget_hide(mShell);
}
void nsWindow::ShowWaylandToplevelWindow() {
MOZ_ASSERT(!IsWaylandPopup());
LOG("nsWindow::ShowWaylandToplevelWindow");
gtk_widget_show(mShell);
}
void nsWindow::WaylandPopupRemoveClosedPopups() {
LOG("nsWindow::WaylandPopupRemoveClosedPopups()");
nsWindow* popup = this;
while (popup) {
nsWindow* next = popup->mWaylandPopupNext;
if (popup->mPopupClosed) {
popup->HideWaylandPopupWindow(/* aTemporaryHide */ false,
/* aRemoveFromPopupList */ true);
}
popup = next;
}
}
// Hide all tooltips except the latest one.
void nsWindow::WaylandPopupHideTooltips() {
LOG("nsWindow::WaylandPopupHideTooltips");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
nsWindow* popup = mWaylandPopupNext;
while (popup && popup->mWaylandPopupNext) {
if (popup->mPopupType == ePopupTypeTooltip) {
LOG(" hidding tooltip [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupCloseOrphanedPopups() {
#ifdef MOZ_WAYLAND
LOG("nsWindow::WaylandPopupCloseOrphanedPopups");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
nsWindow* popup = mWaylandPopupNext;
bool dangling = false;
while (popup) {
if (!dangling &&
moz_container_wayland_is_waiting_to_show(popup->GetMozContainer())) {
LOG(" popup [%p] is waiting to show, close all child popups", popup);
dangling = true;
} else if (dangling) {
popup->WaylandPopupMarkAsClosed();
}
popup = popup->mWaylandPopupNext;
}
#endif
}
// We can't show popups with remote content or overflow popups
// on top of regular ones.
// If there's any remote popup opened, close all parent popups of it.
void nsWindow::CloseAllPopupsBeforeRemotePopup() {
LOG("nsWindow::CloseAllPopupsBeforeRemotePopup");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Don't waste time when there's only one popup opened.
if (!mWaylandPopupNext || mWaylandPopupNext->mWaylandPopupNext == nullptr) {
return;
}
// Find the first opened remote content popup
nsWindow* remotePopup = mWaylandPopupNext;
while (remotePopup) {
if (remotePopup->HasRemoteContent() ||
remotePopup->IsWidgetOverflowWindow()) {
LOG(" remote popup [%p]", remotePopup);
break;
}
remotePopup = remotePopup->mWaylandPopupNext;
}
if (!remotePopup) {
return;
}
// ...hide opened popups before the remote one.
nsWindow* popup = mWaylandPopupNext;
while (popup && popup != remotePopup) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
popup = popup->mWaylandPopupNext;
}
}
static void GetLayoutPopupWidgetChain(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
pm->GetSubmenuWidgetChain(aLayoutWidgetHierarchy);
aLayoutWidgetHierarchy->Reverse();
}
// Compare 'this' popup position in Wayland widget hierarchy
// (mWaylandPopupPrev/mWaylandPopupNext) with
// 'this' popup position in layout hierarchy.
//
// When aMustMatchParent is true we also request
// 'this' parents match, i.e. 'this' has the same parent in
// both layout and widget hierarchy.
bool nsWindow::IsPopupInLayoutPopupChain(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy, bool aMustMatchParent) {
int len = (int)aLayoutWidgetHierarchy->Length();
for (int i = 0; i < len; i++) {
if (this == (*aLayoutWidgetHierarchy)[i]) {
if (!aMustMatchParent) {
return true;
}
// Find correct parent popup for 'this' according to widget
// hierarchy. That means we need to skip closed popups.
nsWindow* parentPopup = nullptr;
if (mWaylandPopupPrev != mWaylandToplevel) {
parentPopup = mWaylandPopupPrev;
while (parentPopup != mWaylandToplevel && parentPopup->mPopupClosed) {
parentPopup = parentPopup->mWaylandPopupPrev;
}
}
if (i == 0) {
// We found 'this' popups as a first popup in layout hierarchy.
// It matches layout hierarchy if it's first widget also in
// wayland widget hierarchy (i.e. parent is null).
return parentPopup == nullptr;
}
return parentPopup == (*aLayoutWidgetHierarchy)[i - 1];
}
}
return false;
}
// Hide popups which are not in popup chain.
void nsWindow::WaylandPopupHierarchyHideByLayout(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
LOG("nsWindow::WaylandPopupHierarchyHideByLayout");
MOZ_ASSERT(mWaylandToplevel == nullptr, "Should be called on toplevel only!");
// Hide all popups which are not in layout popup chain
nsWindow* popup = mWaylandPopupNext;
while (popup) {
// Tooltips are not tracked in layout chain
if (!popup->mPopupClosed && popup->mPopupType != ePopupTypeTooltip) {
if (!popup->IsPopupInLayoutPopupChain(aLayoutWidgetHierarchy,
/* aMustMatchParent */ false)) {
LOG(" hidding popup [%p]", popup);
popup->WaylandPopupMarkAsClosed();
}
}
popup = popup->mWaylandPopupNext;
}
}
// Mark popups outside of layout hierarchy
void nsWindow::WaylandPopupHierarchyValidateByLayout(
nsTArray<nsIWidget*>* aLayoutWidgetHierarchy) {
LOG("nsWindow::WaylandPopupHierarchyValidateByLayout");
nsWindow* popup = mWaylandPopupNext;
while (popup) {
if (popup->mPopupType == ePopupTypeTooltip) {
popup->mPopupMatchesLayout = true;
} else if (!popup->mPopupClosed) {
popup->mPopupMatchesLayout = popup->IsPopupInLayoutPopupChain(
aLayoutWidgetHierarchy, /* aMustMatchParent */ true);
LOG(" popup [%p] parent window [%p] matches layout %d\n", (void*)popup,
(void*)popup->mWaylandPopupPrev, popup->mPopupMatchesLayout);
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupHierarchyHideTemporary() {
LOG("nsWindow::WaylandPopupHierarchyHideTemporary()");
nsWindow* popup = WaylandPopupFindLast(this);
while (popup && popup != this) {
LOG(" temporary hidding popup [%p]", popup);
nsWindow* prev = popup->mWaylandPopupPrev;
popup->HideWaylandPopupWindow(/* aTemporaryHide */ true,
/* aRemoveFromPopupList */ false);
popup = prev;
}
}
void nsWindow::WaylandPopupHierarchyShowTemporaryHidden() {
LOG("nsWindow::WaylandPopupHierarchyShowTemporaryHidden()");
nsWindow* popup = this;
while (popup) {
if (popup->mPopupTemporaryHidden) {
popup->mPopupTemporaryHidden = false;
LOG(" showing temporary hidden popup [%p]", popup);
popup->ShowWaylandPopupWindow();
}
popup = popup->mWaylandPopupNext;
}
}
void nsWindow::WaylandPopupHierarchyCalculatePositions() {
LOG("nsWindow::WaylandPopupHierarchyCalculatePositions()");
// Set widget hierarchy in Gtk
nsWindow* popup = mWaylandToplevel->mWaylandPopupNext;
while (popup) {
LOG(" popup [%p] set parent window [%p]", (void*)popup,
(void*)popup->mWaylandPopupPrev);
gtk_window_set_transient_for(GTK_WINDOW(popup->mShell),
GTK_WINDOW(popup->mWaylandPopupPrev->mShell));
popup = popup->mWaylandPopupNext;
}
popup = this;
while (popup) {
// Anchored window has mPopupPosition already calculated against
// its parent, no need to recalculate.
LOG(" popup [%p] bounds [%d, %d] -> [%d x %d]", popup,
(int)(popup->mBounds.x / FractionalScaleFactor()),
(int)(popup->mBounds.y / FractionalScaleFactor()),
(int)(popup->mBounds.width / FractionalScaleFactor()),
(int)(popup->mBounds.height / FractionalScaleFactor()));
#ifdef MOZ_LOGGING
if (LOG_ENABLED()) {
if (nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame())) {
auto r = LayoutDeviceRect::FromAppUnitsRounded(
popupFrame->GetRect(),
popupFrame->PresContext()->AppUnitsPerDevPixel());
LOG(" popup [%p] layout [%d, %d] -> [%d x %d]", popup, r.x, r.y,
r.width, r.height);
}
}
#endif
if (popup->WaylandPopupIsFirst()) {
LOG(" popup [%p] has toplevel as parent", popup);
popup->mRelativePopupPosition = popup->mPopupPosition;
} else {
if (popup->mPopupAnchored) {
LOG(" popup [%p] is anchored", popup);
if (!popup->mPopupMatchesLayout) {
NS_WARNING("Anchored popup does not match layout!");
}
}
GdkPoint parent = WaylandGetParentPosition();
LOG(" popup [%p] uses transformed coordinates\n", popup);
LOG(" parent position [%d, %d]\n", parent.x, parent.y);
LOG(" popup position [%d, %d]\n", popup->mPopupPosition.x,
popup->mPopupPosition.y);
popup->mRelativePopupPosition.x = popup->mPopupPosition.x - parent.x;
popup->mRelativePopupPosition.y = popup->mPopupPosition.y - parent.y;
}
LOG(" popup [%p] transformed popup coordinates from [%d, %d] to [%d, %d]",
popup, popup->mPopupPosition.x, popup->mPopupPosition.y,
popup->mRelativePopupPosition.x, popup->mRelativePopupPosition.y);
popup = popup->mWaylandPopupNext;
}
}
// The MenuList popups are used as dropdown menus for example in WebRTC
// microphone/camera chooser or autocomplete widgets.
bool nsWindow::WaylandPopupIsMenu() {
nsMenuPopupFrame* menuPopupFrame = GetMenuPopupFrame(GetFrame());
if (menuPopupFrame) {
return mPopupType == ePopupTypeMenu && !menuPopupFrame->IsMenuList();
}
return false;
}
bool nsWindow::WaylandPopupIsContextMenu() {
nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
if (!popupFrame) {
return false;
}
return popupFrame->IsContextMenu();
}
bool nsWindow::WaylandPopupIsPermanent() {
nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
if (!popupFrame) {
// We can always hide popups without frames.
return false;
}
return popupFrame->IsNoAutoHide();
}
bool nsWindow::WaylandPopupIsAnchored() {
nsMenuPopupFrame* popupFrame = GetMenuPopupFrame(GetFrame());
if (!popupFrame) {
// We can always hide popups without frames.
return false;
}
return !!popupFrame->GetAnchor();
}
bool nsWindow::IsWidgetOverflowWindow() {
if (this->GetFrame() && this->GetFrame()->GetContent()->GetID()) {
nsCString nodeId;
this->GetFrame()->GetContent()->GetID()->ToUTF8String(nodeId);
return nodeId.Equals("widget-overflow");
}
return false;
}
bool nsWindow::WaylandPopupIsFirst() {
return !mWaylandPopupPrev || !mWaylandPopupPrev->mWaylandToplevel;
}