diff options
Diffstat (limited to 'widget/gtk/nsWindow.cpp')
-rw-r--r-- | widget/gtk/nsWindow.cpp | 7036 |
1 files changed, 7036 insertions, 0 deletions
diff --git a/widget/gtk/nsWindow.cpp b/widget/gtk/nsWindow.cpp new file mode 100644 index 000000000..d97b35002 --- /dev/null +++ b/widget/gtk/nsWindow.cpp @@ -0,0 +1,7036 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* 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 "nsWindow.h" + +#include "mozilla/ArrayUtils.h" +#include "mozilla/EventForwards.h" +#include "mozilla/MiscEvents.h" +#include "mozilla/MouseEvents.h" +#include "mozilla/RefPtr.h" +#include "mozilla/TextEventDispatcher.h" +#include "mozilla/TextEvents.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/TouchEvents.h" +#include "mozilla/UniquePtrExtensions.h" +#include <algorithm> + +#include "GeckoProfiler.h" + +#include "prlink.h" +#include "nsGTKToolkit.h" +#include "nsIRollupListener.h" +#include "nsIDOMNode.h" + +#include "nsWidgetsCID.h" +#include "nsDragService.h" +#include "nsIWidgetListener.h" +#include "nsIScreenManager.h" +#include "SystemTimeConverter.h" + +#include "nsGtkKeyUtils.h" +#include "nsGtkCursors.h" +#include "nsScreenGtk.h" + +#include <gtk/gtk.h> +#if (MOZ_WIDGET_GTK == 3) +#include <gtk/gtkx.h> +#endif +#ifdef MOZ_X11 +#include <gdk/gdkx.h> +#include <X11/Xatom.h> +#include <X11/extensions/XShm.h> +#include <X11/extensions/shape.h> +#if (MOZ_WIDGET_GTK == 3) +#include <gdk/gdkkeysyms-compat.h> +#endif + +#if (MOZ_WIDGET_GTK == 2) +#include "gtk2xtbin.h" +#endif +#endif /* MOZ_X11 */ +#include <gdk/gdkkeysyms.h> +#if (MOZ_WIDGET_GTK == 2) +#include <gtk/gtkprivate.h> +#endif + +#include "nsGkAtoms.h" + +#ifdef MOZ_ENABLE_STARTUP_NOTIFICATION +#define SN_API_NOT_YET_FROZEN +#include <startup-notification-1.0/libsn/sn.h> +#endif + +#include "mozilla/Assertions.h" +#include "mozilla/Likely.h" +#include "mozilla/Preferences.h" +#include "nsIPrefService.h" +#include "nsIGConfService.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsGfxCIID.h" +#include "nsGtkUtils.h" +#include "nsIObserverService.h" +#include "mozilla/layers/LayersTypes.h" +#include "nsIIdleServiceInternal.h" +#include "nsIPropertyBag2.h" +#include "GLContext.h" +#include "gfx2DGlue.h" +#include "nsPluginNativeWindowGtk.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 "nsXPIDLString.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 "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/layers/CompositorBridgeParent.h" +#include "mozilla/layers/CompositorThread.h" + +#ifdef MOZ_X11 +#include "X11CompositorWidget.h" +#include "gfxXlibSurface.h" +#include "WindowSurfaceX11Image.h" +#include "WindowSurfaceX11SHM.h" +#include "WindowSurfaceXRender.h" +#endif // MOZ_X11 + +#include "nsShmImage.h" + +#include "nsIDOMWheelEvent.h" + +#include "NativeKeyBindings.h" + +#include <dlfcn.h> + +#include "mozilla/layers/APZCTreeManager.h" + +using namespace mozilla; +using namespace mozilla::gfx; +using namespace mozilla::widget; +using namespace mozilla::layers; +using mozilla::gl::GLContext; + +// 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 + +const gint kEvents = 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 | +#if GTK_CHECK_VERSION(3,4,0) + GDK_SMOOTH_SCROLL_MASK | + GDK_TOUCH_MASK | +#endif + 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 inline bool is_context_menu_key(const WidgetKeyboardEvent& inKeyEvent); + +static int is_parent_ungrab_enter(GdkEventCrossing *aEvent); +static int is_parent_grab_leave(GdkEventCrossing *aEvent); + +static void GetBrandName(nsXPIDLString& brandName); + +/* callbacks from widgets */ +#if (MOZ_WIDGET_GTK == 2) +static gboolean expose_event_cb (GtkWidget *widget, + GdkEventExpose *event); +#else +static gboolean expose_event_cb (GtkWidget *widget, + cairo_t *rect); +#endif +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 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 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 theme_changed_cb (GtkSettings *settings, + GParamSpec *pspec, + nsWindow *data); +static void check_resize_cb (GtkContainer* container, + gpointer user_data); + +#if (MOZ_WIDGET_GTK == 3) +static void scale_changed_cb (GtkWidget* widget, + GParamSpec* aPSpec, + gpointer aPointer); +#endif +#if GTK_CHECK_VERSION(3,4,0) +static gboolean touch_event_cb (GtkWidget* aWidget, + GdkEventTouch* aEvent); +#endif +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); +static GdkFilterReturn plugin_window_filter_func (GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data); +static GdkFilterReturn plugin_client_message_filter (GdkXEvent *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; +} + +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 nsWindow *gPluginFocusWindow = nullptr; + +#if GTK_CHECK_VERSION(3,4,0) +static uint32_t gLastTouchID = 0; +#endif + +#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) || (MOZ_WIDGET_GTK == 2) + 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<nsIIdleServiceInternal> idleService = + do_GetService("@mozilla.org/widget/idleservice;1"); + if (idleService) { + idleService->ResetIdleTimeOut(0); + } + + guint timestamp = gdk_event_get_time(static_cast<GdkEvent*>(aGdkEvent)); + if (timestamp == GDK_CURRENT_TIME) + return; + + sLastUserInputTime = timestamp; +} + +NS_IMPL_ISUPPORTS_INHERITED0(nsWindow, nsBaseWidget) + +nsWindow::nsWindow() +{ + mIsTopLevel = false; + mIsDestroyed = false; + mListenForResizes = false; + mNeedsDispatchResized = false; + mIsShown = false; + mNeedsShow = false; + mEnabled = true; + mCreated = false; +#if GTK_CHECK_VERSION(3,4,0) + mHandleTouchEvent = false; +#endif + mIsDragPopup = false; + mIsX11Display = GDK_IS_X11_DISPLAY(gdk_display_get_default()); + + mContainer = nullptr; + mGdkWindow = nullptr; + mShell = nullptr; + mPluginNativeWindow = nullptr; + mHasMappedToplevel = false; + mIsFullyObscured = false; + mRetryPointerGrab = false; + mWindowType = eWindowType_child; + mSizeState = nsSizeMode_Normal; + mLastSizeMode = nsSizeMode_Normal; + mSizeConstraints.mMaxSize = GetSafeWindowSize(mSizeConstraints.mMaxSize); + +#ifdef MOZ_X11 + mOldFocusWindow = 0; + + mXDisplay = nullptr; + mXWindow = X11None; + mXVisual = nullptr; + mXDepth = 0; +#endif /* MOZ_X11 */ + mPluginType = PluginType_NONE; + + if (!gGlobalsInitialized) { + gGlobalsInitialized = true; + + // It's OK if either of these fail, but it may not be one day. + initialize_prefs(); + } + + mLastMotionPressure = 0; + +#ifdef ACCESSIBILITY + mRootAccessible = nullptr; +#endif + + mIsTransparent = false; + mTransparencyBitmap = nullptr; + + mTransparencyBitmapWidth = 0; + mTransparencyBitmapHeight = 0; + +#if GTK_CHECK_VERSION(3,4,0) + mLastScrollEventTime = GDK_CURRENT_TIME; +#endif + mPendingConfigures = 0; +} + +nsWindow::~nsWindow() +{ + LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this)); + + delete[] mTransparencyBitmap; + mTransparencyBitmap = nullptr; + + Destroy(); +} + +/* static */ void +nsWindow::ReleaseGlobals() +{ + for (uint32_t i = 0; i < ArrayLength(gCursorCache); ++i) { + if (gCursorCache[i]) { +#if (MOZ_WIDGET_GTK == 3) + g_object_unref(gCursorCache[i]); +#else + gdk_cursor_unref(gCursorCache[i]); +#endif + gCursorCache[i] = 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(void) +{ + if (mBounds.width > 0 && mBounds.height > 0) + return true; + + return false; +} + +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() +{ + NS_PRECONDITION(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; + + GtkWidget* 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; + + // 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_func(gtk_settings_get_default(), + FuncToGpointer(theme_changed_cb), + 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. + 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; + } + +#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11) + // make sure that we remove ourself as the plugin focus window + if (gPluginFocusWindow == this) { + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 && defined(MOZ_X11) */ + + 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() +{ + GdkScreen *screen = gdk_display_get_default_screen(gdk_display_get_default()); + double heightInches = gdk_screen_get_height_mm(screen)/MM_PER_INCH_FLOAT; + if (heightInches < 0.25) { + // Something's broken, but we'd better not crash. + return 96.0f; + } + return float(gdk_screen_get_height(screen)/heightInches); +} + +double +nsWindow::GetDefaultScaleInternal() +{ + return GdkScaleFactor() * gfxPlatformGtk::GetDPIScale(); +} + +NS_IMETHODIMP +nsWindow::SetParent(nsIWidget *aNewParent) +{ + if (mContainer || !mGdkWindow) { + NS_NOTREACHED("nsWindow::SetParent called illegally"); + return NS_ERROR_NOT_IMPLEMENTED; + } + + 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 NS_OK; + } + + if (aNewParent) { + aNewParent->AddChild(this); + ReparentNativeWidget(aNewParent); + } 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. + GtkWidget* newContainer = EnsureInvisibleContainer(); + GdkWindow* newParentWindow = gtk_widget_get_window(newContainer); + ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow, + oldContainer); + } + return NS_OK; +} + +bool +nsWindow::WidgetTypeSupportsAcceleration() +{ + return !IsSmallPopup(); +} + +void +nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) +{ + NS_PRECONDITION(aNewParent, ""); + NS_ASSERTION(!mIsDestroyed, ""); + NS_ASSERTION(!static_cast<nsWindow*>(aNewParent)->mIsDestroyed, ""); + + 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; + } + MOZ_ASSERT(!gdk_window_is_destroyed(mGdkWindow), + "destroyed GdkWindow with widget"); + + nsWindow* newParent = static_cast<nsWindow*>(aNewParent); + GdkWindow* newParentWindow = newParent->mGdkWindow; + GtkWidget* newContainer = newParent->GetMozContainerWidget(); + GtkWindow* shell = GTK_WINDOW(mShell); + + if (shell && gtk_window_get_transient_for(shell)) { + GtkWindow* topLevelParent = + GTK_WINDOW(gtk_widget_get_toplevel(newContainer)); + gtk_window_set_transient_for(shell, topLevelParent); + } + + ReparentNativeWidgetInternal(aNewParent, newContainer, newParentWindow, + oldContainer); +} + +void +nsWindow::ReparentNativeWidgetInternal(nsIWidget* aNewParent, + GtkWidget* aNewContainer, + GdkWindow* aNewParentWindow, + GtkWidget* aOldContainer) +{ + if (!aNewContainer) { + // The new parent GdkWindow has been destroyed. + MOZ_ASSERT(!aNewParentWindow || + gdk_window_is_destroyed(aNewParentWindow), + "live GdkWindow with no widget"); + Destroy(); + } else { + if (aNewContainer != aOldContainer) { + MOZ_ASSERT(!gdk_window_is_destroyed(aNewParentWindow), + "destroyed GdkWindow with widget"); + SetWidgetForHierarchy(mGdkWindow, aOldContainer, aNewContainer); + + if (aOldContainer == gInvisibleContainer) { + CheckDestroyInvisibleContainer(); + } + } + + if (!mIsTopLevel) { + gdk_window_reparent(mGdkWindow, aNewParentWindow, + DevicePixelsToGdkCoordRoundDown(mBounds.x), + DevicePixelsToGdkCoordRoundDown(mBounds.y)); + } + } + + nsWindow* newParent = static_cast<nsWindow*>(aNewParent); + bool parentHasMappedToplevel = + newParent && newParent->mHasMappedToplevel; + if (mHasMappedToplevel != parentHasMappedToplevel) { + SetHasMappedToplevel(parentHasMappedToplevel); + } +} + +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() +{ +#if GTK_CHECK_VERSION(3,4,0) + mHandleTouchEvent = true; + mTouches.Clear(); +#endif +} + +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); + + 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 (aConstraints.mMinSize != LayoutDeviceIntSize(0, 0)) { + hints |= GDK_HINT_MIN_SIZE; + } + if (aConstraints.mMaxSize != + LayoutDeviceIntSize(NS_MAXSIZE, NS_MAXSIZE)) { + hints |= GDK_HINT_MAX_SIZE; + } + gtk_window_set_geometry_hints(GTK_WINDOW(mShell), nullptr, + &geometry, GdkWindowHints(hints)); + } +} + +NS_IMETHODIMP +nsWindow::Show(bool aState) +{ + if (aState == mIsShown) + return NS_OK; + + // 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 NS_OK; + } + + // 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); + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::Resize(double aWidth, double aHeight, bool aRepaint) +{ + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(scale * aWidth); + int32_t height = NSToIntRound(scale * aHeight); + ConstrainSize(&width, &height); + + // 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(width, height); + + if (!mCreated) + return NS_OK; + + NativeResize(); + + NotifyRollupGeometryChange(); + ResizePluginSocketWidget(); + + // send a resize notification if this is a toplevel + if (mIsTopLevel || mListenForResizes) { + DispatchResized(); + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::Resize(double aX, double aY, double aWidth, double aHeight, + bool aRepaint) +{ + double scale = BoundsUseDesktopPixels() ? GetDesktopToDeviceScale().scale : 1.0; + int32_t width = NSToIntRound(scale * aWidth); + int32_t height = NSToIntRound(scale * aHeight); + ConstrainSize(&width, &height); + + int32_t x = NSToIntRound(scale * aX); + int32_t y = NSToIntRound(scale * aY); + mBounds.x = x; + mBounds.y = y; + mBounds.SizeTo(width, height); + + if (!mCreated) + return NS_OK; + + NativeMoveResize(); + + NotifyRollupGeometryChange(); + ResizePluginSocketWidget(); + + if (mIsTopLevel || mListenForResizes) { + DispatchResized(); + } + + return NS_OK; +} + +void +nsWindow::ResizePluginSocketWidget() +{ + // e10s specific, a eWindowType_plugin_ipc_chrome holds its own + // nsPluginNativeWindowGtk wrapper. We are responsible for resizing + // the embedded socket widget. + if (mWindowType == eWindowType_plugin_ipc_chrome) { + nsPluginNativeWindowGtk* wrapper = (nsPluginNativeWindowGtk*) + GetNativeData(NS_NATIVE_PLUGIN_OBJECT_PTR); + if (wrapper) { + wrapper->width = mBounds.width; + wrapper->height = mBounds.height; + wrapper->SetAllocation(); + } + } +} + +NS_IMETHODIMP +nsWindow::Enable(bool aState) +{ + mEnabled = aState; + + return NS_OK; +} + +bool +nsWindow::IsEnabled() const +{ + return mEnabled; +} + + + +NS_IMETHODIMP +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 NS_OK; + + // XXX Should we do some AreBoundsSane check here? + + mBounds.x = x; + mBounds.y = y; + + if (!mCreated) + return NS_OK; + + NativeMove(); + + NotifyRollupGeometryChange(); + return NS_OK; +} + + +void +nsWindow::NativeMove() +{ + GdkPoint point = DevicePixelsToGdkPointRoundDown(mBounds.TopLeft()); + + 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) { + return; + } + + switch (aMode) { + case nsSizeMode_Maximized: + gtk_window_maximize(GTK_WINDOW(mShell)); + break; + case nsSizeMode_Minimized: + gtk_window_iconify(GTK_WINDOW(mShell)); + break; + case nsSizeMode_Fullscreen: + MakeFullScreen(true); + break; + + default: + // 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; + } + + mSizeState = mSizeMode; +} + +typedef void (* SetUserTimeFunc)(GdkWindow* aWindow, guint32 aTimestamp); + +// This will become obsolete when new GTK APIs are widely supported, +// as described here: http://bugzilla.gnome.org/show_bug.cgi?id=347375 +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; + } + +#if defined(MOZ_ENABLE_STARTUP_NOTIFICATION) + // TODO - Implement for non-X11 Gtk backends (Bug 726479) + if (GDK_IS_X11_DISPLAY(gdk_display_get_default())) { + GdkWindow* gdkWindow = gtk_widget_get_window(aWindow); + + GdkScreen* screen = gdk_window_get_screen(gdkWindow); + SnDisplay* snd = + sn_display_new(gdk_x11_display_get_xdisplay(gdk_window_get_display(gdkWindow)), + nullptr, nullptr); + if (!snd) + return; + SnLauncheeContext* ctx = + sn_launchee_context_new(snd, gdk_screen_get_number(screen), + desktopStartupID.get()); + if (!ctx) { + sn_display_unref(snd); + return; + } + + if (sn_launchee_context_get_id_has_timestamp(ctx)) { + gdk_x11_window_set_user_time(gdkWindow, + sn_launchee_context_get_timestamp(ctx)); + } + + sn_launchee_context_setup_window(ctx, gdk_x11_window_get_xid(gdkWindow)); + sn_launchee_context_complete(ctx); + + sn_launchee_context_unref(ctx); + sn_display_unref(snd); + } +#endif + + // 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(EmptyCString()); +} + +/* static */ guint32 +nsWindow::GetLastUserInputTime() +{ + // gdk_x11_display_get_user_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. + guint32 timestamp = + gdk_x11_display_get_user_time(gdk_display_get_default()); + if (sLastUserInputTime != GDK_CURRENT_TIME && + TimestampIsNewerThan(sLastUserInputTime, timestamp)) { + return sLastUserInputTime; + } + + return timestamp; +} + +NS_IMETHODIMP +nsWindow::SetFocus(bool aRaise) +{ + // 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, (void *)this)); + + GtkWidget *owningWidget = GetMozContainerWidget(); + if (!owningWidget) + return NS_ERROR_FAILURE; + + // Raise the window if someone passed in true and the prefs are + // set properly. + GtkWidget *toplevelWidget = gtk_widget_get_toplevel(owningWidget); + + if (gRaiseWindows && aRaise && 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 NS_ERROR_FAILURE; + + if (aRaise) { + // aRaise == true 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))) { + + 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 NS_OK; + } + + // aRaise == false 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 NS_OK; + } + + // 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)); + + return NS_OK; +} + +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()); + LOG(("GetScreenBounds %d,%d | %dx%d\n", + rect.x, rect.y, rect.width, rect.height)); + 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::UpdateClientOffset() +{ + PROFILER_LABEL("nsWindow", "UpdateClientOffset", js::ProfileEntry::Category::GRAPHICS); + + if (!mIsTopLevel || !mShell || !mGdkWindow || !mIsX11Display || + 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(mGdkWindow, + 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); + return; + } + + // data returned is in the order left, right, top, bottom + int32_t left = int32_t(frame_extents[0]); + int32_t top = int32_t(frame_extents[2]); + + g_free(frame_extents); + + mClientOffset = nsIntPoint(left, top); +} + +LayoutDeviceIntPoint +nsWindow::GetClientOffset() +{ + return LayoutDeviceIntPoint::FromUnknownPoint(mClientOffset); +} + +gboolean +nsWindow::OnPropertyNotifyEvent(GtkWidget* aWidget, GdkEventProperty* aEvent) + +{ + if (aEvent->atom == gdk_atom_intern("_NET_FRAME_EXTENTS", FALSE)) { + UpdateClientOffset(); + return FALSE; + } + + if (GetCurrentTimeGetter()->PropertyNotifyHandler(aWidget, aEvent)) { + return TRUE; + } + + return FALSE; +} + +NS_IMETHODIMP +nsWindow::SetCursor(nsCursor aCursor) +{ + // 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 NS_ERROR_FAILURE; + + return window->SetCursor(aCursor); + } + + // Only change cursor if it's actually been changed + if (aCursor != mCursor || mUpdateCursor) { + GdkCursor *newCursor = nullptr; + mUpdateCursor = false; + + newCursor = get_gtk_cursor(aCursor); + + if (nullptr != newCursor) { + mCursor = aCursor; + + if (!mContainer) + return NS_OK; + + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), newCursor); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::SetCursor(imgIContainer* aCursor, + 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 NS_ERROR_FAILURE; + + return window->SetCursor(aCursor, aHotspotX, aHotspotY); + } + + mCursor = nsCursor(-1); + + // Get the image's current frame + GdkPixbuf* pixbuf = nsImageToPixbuf::ImageToPixbuf(aCursor); + if (!pixbuf) + return NS_ERROR_NOT_AVAILABLE; + + int width = gdk_pixbuf_get_width(pixbuf); + int height = gdk_pixbuf_get_height(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. + if (width > 128 || height > 128) { + g_object_unref(pixbuf); + return NS_ERROR_NOT_AVAILABLE; + } + + // 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); + if (!alphaBuf) { + return NS_ERROR_OUT_OF_MEMORY; + } + pixbuf = alphaBuf; + } + + GdkCursor* cursor = gdk_cursor_new_from_pixbuf(gdk_display_get_default(), + pixbuf, + aHotspotX, aHotspotY); + g_object_unref(pixbuf); + nsresult rv = NS_ERROR_OUT_OF_MEMORY; + if (cursor) { + if (mContainer) { + gdk_window_set_cursor(gtk_widget_get_window(GTK_WIDGET(mContainer)), cursor); + rv = NS_OK; + } +#if (MOZ_WIDGET_GTK == 3) + g_object_unref(cursor); +#else + gdk_cursor_unref(cursor); +#endif + } + + return rv; +} + +NS_IMETHODIMP +nsWindow::Invalidate(const LayoutDeviceIntRect& aRect) +{ + if (!mGdkWindow) + return NS_OK; + + 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)); + + return NS_OK; +} + +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_PLUGIN_PORT: + return SetupPluginPort(); + + case NS_NATIVE_PLUGIN_ID: + if (!mPluginNativeWindow) { + NS_WARNING("no native plugin instance!"); + return nullptr; + } + // Return the socket widget XID + return (void*)mPluginNativeWindow->window; + + case NS_NATIVE_DISPLAY: { +#ifdef MOZ_X11 + GdkDisplay* gdkDisplay = gdk_display_get_default(); + if (GDK_IS_X11_DISPLAY(gdkDisplay)) { + return GDK_DISPLAY_XDISPLAY(gdkDisplay); + } +#endif /* MOZ_X11 */ + return nullptr; + } + case NS_NATIVE_SHELLWIDGET: + return GetToplevelWidget(); + + case NS_NATIVE_SHAREABLE_WINDOW: + return (void *) GDK_WINDOW_XID(gdk_window_get_toplevel(mGdkWindow)); + case NS_NATIVE_PLUGIN_OBJECT_PTR: + return (void *) mPluginNativeWindow; + 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; +#ifdef MOZ_X11 + case NS_NATIVE_COMPOSITOR_DISPLAY: + return gfxPlatformGtk::GetPlatform()->GetCompositorDisplay(); +#endif // MOZ_X11 + default: + NS_WARNING("nsWindow::GetNativeData called with bad value"); + return nullptr; + } +} + +void +nsWindow::SetNativeData(uint32_t aDataType, uintptr_t aVal) +{ + if (aDataType != NS_NATIVE_PLUGIN_OBJECT_PTR) { + NS_WARNING("nsWindow::SetNativeData called with bad value"); + return; + } + mPluginNativeWindow = (nsPluginNativeWindowGtk*)aVal; +} + +NS_IMETHODIMP +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; +} + +NS_IMETHODIMP +nsWindow::SetIcon(const nsAString& aIconSpec) +{ + if (!mShell) + return NS_OK; + + nsAutoCString iconName; + + if (aIconSpec.EqualsLiteral("default")) { + nsXPIDLString brandName; + GetBrandName(brandName); + 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 char extensions[6][7] = { ".png", "16.png", "32.png", "48.png", + ".xpm", "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; + + nsAutoString extension; + extension.AppendASCII(extensions[i]); + + ResolveIconName(aIconSpec, extension, 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()); + } + + return NS_OK; +} + + +LayoutDeviceIntPoint +nsWindow::WidgetToScreenOffset() +{ + gint x = 0, y = 0; + + if (mGdkWindow) { + gdk_window_get_origin(mGdkWindow, &x, &y); + } + + return GdkPointToDevicePixels({ x, 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; + } +} + +NS_IMETHODIMP +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; + +#if (MOZ_WIDGET_GTK == 2) + gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height,nullptr); +#else + gdk_window_get_geometry(aGdkWindow,nullptr,nullptr,&width,&height); +#endif + + 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 + +#if (MOZ_WIDGET_GTK == 2) +static bool +ExtractExposeRegion(LayoutDeviceIntRegion& aRegion, GdkEventExpose* aEvent) +{ + GdkRectangle* rects; + gint nrects; + gdk_region_get_rectangles(aEvent->region, &rects, &nrects); + + if (nrects > MAX_RECTS_IN_REGION) { + // Just use the bounding box + rects[0] = aEvent->area; + nrects = 1; + } + + for (GdkRectangle* r = rects; r < rects + nrects; r++) { + aRegion.Or(aRegion, LayoutDeviceIntRect(r->x, r->y, r->width, r->height)); + LOGDRAW(("\t%d %d %d %d\n", r->x, r->y, r->width, r->height)); + } + + g_free(rects); + return true; +} + +#else +# 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%d %d %d %d\n", r.x, r.y, r.width, r.height)); + } + + cairo_rectangle_list_destroy(rects); + return true; +} +#endif + +#if (MOZ_WIDGET_GTK == 2) +gboolean +nsWindow::OnExposeEvent(GdkEventExpose *aEvent) +#else +gboolean +nsWindow::OnExposeEvent(cairo_t *cr) +#endif +{ + // 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 || mIsFullyObscured || !mHasMappedToplevel) + return FALSE; + + nsIWidgetListener *listener = GetListener(); + if (!listener) + return FALSE; + + LayoutDeviceIntRegion exposeRegion; +#if (MOZ_WIDGET_GTK == 2) + if (!ExtractExposeRegion(exposeRegion, aEvent)) { +#else + if (!ExtractExposeRegion(exposeRegion, cr)) { +#endif + return FALSE; + } + + gint scale = GdkScaleFactor(); + LayoutDeviceIntRegion region = exposeRegion; + region.ScaleRoundOut(scale, scale); + + ClientLayerManager *clientLayers = GetLayerManager()->AsClientLayerManager(); + + if (clientLayers && 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. + clientLayers->SetNeedsComposite(true); + clientLayers->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 (clientLayers && clientLayers->NeedsComposite()) { + clientLayers->Composite(); + clientLayers->SetNeedsComposite(false); + } + + LOGDRAW(("sending expose event [%p] %p 0x%lx (rects follow):\n", + (void *)this, (void *)mGdkWindow, + gdk_x11_window_get_xid(mGdkWindow))); + + // 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()) { + GdkScreen *screen = gdk_window_get_screen(mGdkWindow); + if (gdk_screen_is_composited(screen) && + gdk_window_get_visual(mGdkWindow) == + gdk_screen_get_rgba_visual(screen)) { + // Remove possible shape mask from when window manger was not + // previously compositing. + static_cast<nsWindow*>(GetTopLevelWidget())-> + 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) { + 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) { + GdkScreen *screen = gdk_window_get_screen(mGdkWindow); + if (GetTransparencyMode() == eTransparencyTransparent && + layerBuffering == BufferMode::BUFFER_NONE && + gdk_screen_is_composited(screen) && + gdk_window_get_visual(mGdkWindow) == + gdk_screen_get_rgba_visual(screen)) { + // 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 +#if (MOZ_WIDGET_GTK == 2) + GdkRegion* dirtyArea = gdk_window_get_update_area(mGdkWindow); +#else + cairo_region_t* dirtyArea = gdk_window_get_update_area(mGdkWindow); +#endif + + if (dirtyArea) { + gdk_window_invalidate_region(mGdkWindow, dirtyArea, false); +#if (MOZ_WIDGET_GTK == 2) + gdk_region_destroy(dirtyArea); +#else + cairo_region_destroy(dirtyArea); +#endif + 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); + } + } + + // This event indicates that the window position may have changed. + // mBounds.Size() is updated in OnSizeAllocate(). + + 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 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. + return FALSE; + } + + mBounds.MoveTo(screenBounds.TopLeft()); + + // XXX mozilla will invalidate the entire window after this move + // complete. wtf? + NotifyWindowMoved(mBounds.x, mBounds.y); + + 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(("size_allocate [%p] %d %d %d %d\n", + (void *)this, aAllocation->x, aAllocation->y, + aAllocation->width, aAllocation->height)); + + LayoutDeviceIntSize size = GdkRectToDevicePixels(*aAllocation).Size(); + + if (mBounds.Size() == size) + 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 X11CompositorWidget 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(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) +{ + gint x = gint(aEvent->x_root); + gint 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 = is_top_level_mouse_exit(mGdkWindow, aEvent) + ? WidgetMouseEvent::eTopLevel : WidgetMouseEvent::eChild; + + 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) +{ + // 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); + } +#if (MOZ_WIDGET_GTK == 2) + // if plugins still keeps the focus, get it back + if (gPluginFocusWindow && gPluginFocusWindow != this) { + RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow; + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_WIDGET_GTK == 2 */ + } +#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.pressure = 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 = WidgetMouseEvent::eLeftButton; + break; + case GDK_BUTTON2_MASK: + buttonType = WidgetMouseEvent::eMiddleButton; + break; + default: + NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK, + "Unexpected button mask"); + buttonType = WidgetMouseEvent::eRightButton; + } + + 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.button = 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::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 = WidgetMouseEvent::eLeftButton; + break; + case 2: + domButton = WidgetMouseEvent::eMiddleButton; + break; + case 3: + domButton = WidgetMouseEvent::eRightButton; + 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: + DispatchCommandEvent(nsGkAtoms::Back); + return; + case 9: + DispatchCommandEvent(nsGkAtoms::Forward); + return; + default: + return; + } + + gButtonState |= ButtonMaskFromGDKButton(aEvent->button); + + WidgetMouseEvent event(true, eMouseDown, this, WidgetMouseEvent::eReal); + event.button = domButton; + InitButtonEvent(event, aEvent); + event.pressure = mLastMotionPressure; + + DispatchInputEvent(&event); + + // right menu click on linux should also pop up a context menu + if (domButton == WidgetMouseEvent::eRightButton && + MOZ_LIKELY(!mIsDestroyed)) { + WidgetMouseEvent contextMenuEvent(true, eContextMenu, this, + WidgetMouseEvent::eReal); + InitButtonEvent(contextMenuEvent, aEvent); + contextMenuEvent.pressure = mLastMotionPressure; + DispatchInputEvent(&contextMenuEvent); + } +} + +void +nsWindow::OnButtonReleaseEvent(GdkEventButton *aEvent) +{ + LOG(("Button %u release on %p\n", aEvent->button, (void *)this)); + + uint16_t domButton; + switch (aEvent->button) { + case 1: + domButton = WidgetMouseEvent::eLeftButton; + break; + case 2: + domButton = WidgetMouseEvent::eMiddleButton; + break; + case 3: + domButton = WidgetMouseEvent::eRightButton; + break; + default: + return; + } + + gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button); + + WidgetMouseEvent event(true, eMouseUp, this, + WidgetMouseEvent::eReal); + event.button = domButton; + InitButtonEvent(event, aEvent); + gdouble pressure = 0; + gdk_event_get_axis ((GdkEvent*)aEvent, GDK_AXIS_PRESSURE, &pressure); + event.pressure = pressure ? pressure : mLastMotionPressure; + + DispatchInputEvent(&event); + mLastMotionPressure = pressure; +} + +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<nsIDOMNode> sourceNode; + dragSession->GetSourceNode(getter_AddRefs(sourceNode)); + shouldRollup = (sourceNode == nullptr); + } + + if (shouldRollup) { + CheckForRollup(0, 0, false, true); + } + } + +#if (MOZ_WIDGET_GTK == 2) && defined(MOZ_X11) + // plugin lose focus + if (gPluginFocusWindow) { + RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow; + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } +#endif /* MOZ_X11 && MOZ_WIDGET_GTK == 2 */ + + if (gFocusWindow) { + RefPtr<nsWindow> kungFuDeathGrip = gFocusWindow; + if (gFocusWindow->mIMContext) { + gFocusWindow->mIMContext->OnBlurWindow(gFocusWindow); + } + gFocusWindow = nullptr; + } + + DispatchDeactivateEvent(); + + LOGFOCUS(("Done with container focus out [%p]\n", (void *)this)); +} + +bool +nsWindow::DispatchCommandEvent(nsIAtom* aCommand) +{ + nsEventStatus status; + WidgetCommandEvent event(true, nsGkAtoms::onAppCommand, aCommand, this); + DispatchEvent(&event, status); + return TRUE; +} + +bool +nsWindow::DispatchContentCommandEvent(EventMessage aMsg) +{ + nsEventStatus status; + WidgetContentCommandEvent event(true, aMsg, this); + DispatchEvent(&event, status); + return TRUE; +} + +static bool +IsCtrlAltTab(GdkEventKey *aEvent) +{ + return aEvent->keyval == GDK_Tab && + KeymapWrapper::AreModifiersActive( + KeymapWrapper::CTRL | KeymapWrapper::ALT, aEvent->state); +} + +bool +nsWindow::DispatchKeyDownEvent(GdkEventKey *aEvent, bool *aCancelled) +{ + NS_PRECONDITION(aCancelled, "aCancelled must not be null"); + + *aCancelled = false; + + if (IsCtrlAltTab(aEvent)) { + return false; + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return FALSE; + } + + WidgetKeyboardEvent keydownEvent(true, eKeyDown, this); + KeymapWrapper::InitKeyEvent(keydownEvent, aEvent); + nsEventStatus status = nsEventStatus_eIgnore; + bool dispatched = + dispatcher->DispatchKeyboardEvent(eKeyDown, keydownEvent, + status, aEvent); + *aCancelled = (status == nsEventStatus_eConsumeNoDefault); + return dispatched ? TRUE : FALSE; +} + +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(); + } + CurrentX11TimeGetter* getCurrentTime = GetCurrentTimeGetter(); + MOZ_ASSERT(getCurrentTime, + "Null current time getter despite having a window"); + return TimeConverter().GetTimeStampFromSystemTime(aEventTime, + *getCurrentTime); +} + +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)); + + // 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; + if (mIMContext) { + IMEWasEnabled = mIMContext->IsEnabled(); + if (mIMContext->OnKeyEvent(this, aEvent)) { + return TRUE; + } + } + + // work around for annoying things. + if (IsCtrlAltTab(aEvent)) { + return TRUE; + } + + nsCOMPtr<nsIWidget> kungFuDeathGrip = this; + + // 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 (DispatchKeyDownEvent(aEvent, &isKeyDownCancelled) && + (MOZ_UNLIKELY(mIsDestroyed) || isKeyDownCancelled)) { + return TRUE; + } + + // 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. + if (!IMEWasEnabled && mIMContext && mIMContext->IsEnabled()) { + // Notice our keydown event was already dispatched. This prevents + // unnecessary DOM keydown event in the editor. + if (mIMContext->OnKeyEvent(this, aEvent, true)) { + return TRUE; + } + } + + // Look for specialized app-command keys + switch (aEvent->keyval) { + case GDK_Back: + return DispatchCommandEvent(nsGkAtoms::Back); + case GDK_Forward: + return DispatchCommandEvent(nsGkAtoms::Forward); + case GDK_Refresh: + return DispatchCommandEvent(nsGkAtoms::Reload); + case GDK_Stop: + return DispatchCommandEvent(nsGkAtoms::Stop); + case GDK_Search: + return DispatchCommandEvent(nsGkAtoms::Search); + case GDK_Favorites: + return DispatchCommandEvent(nsGkAtoms::Bookmarks); + case GDK_HomePage: + return DispatchCommandEvent(nsGkAtoms::Home); + case GDK_Copy: + case GDK_F16: // F16, F20, F18, F14 are old keysyms for Copy Cut Paste Undo + return DispatchContentCommandEvent(eContentCommandCopy); + case GDK_Cut: + case GDK_F20: + return DispatchContentCommandEvent(eContentCommandCut); + case GDK_Paste: + case GDK_F18: + return DispatchContentCommandEvent(eContentCommandPaste); + case GDK_Redo: + return DispatchContentCommandEvent(eContentCommandRedo); + case GDK_Undo: + case GDK_F14: + return DispatchContentCommandEvent(eContentCommandUndo); + } + + WidgetKeyboardEvent keypressEvent(true, eKeyPress, this); + KeymapWrapper::InitKeyEvent(keypressEvent, aEvent); + + // before we dispatch a key, check if it's the context menu key. + // If so, send a context menu key event instead. + if (is_context_menu_key(keypressEvent)) { + WidgetMouseEvent contextMenuEvent(true, eContextMenu, this, + WidgetMouseEvent::eReal, + WidgetMouseEvent::eContextMenuKey); + + contextMenuEvent.mRefPoint = LayoutDeviceIntPoint(0, 0); + contextMenuEvent.AssignEventTime(GetWidgetEventTime(aEvent->time)); + contextMenuEvent.mClickCount = 1; + KeymapWrapper::InitInputEvent(contextMenuEvent, aEvent->state); + DispatchInputEvent(&contextMenuEvent); + } else { + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return TRUE; + } + + // 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. + nsEventStatus status = nsEventStatus_eIgnore; + if (keypressEvent.mKeyNameIndex != KEY_NAME_INDEX_USE_STRING || + keypressEvent.mKeyValue.Length() == 1) { + dispatcher->MaybeDispatchKeypressEvents(keypressEvent, + status, aEvent); + } else { + WidgetEventTime eventTime = GetWidgetEventTime(aEvent->time); + dispatcher->CommitComposition(status, &keypressEvent.mKeyValue, + &eventTime); + } + } + + return TRUE; +} + +gboolean +nsWindow::OnKeyReleaseEvent(GdkEventKey *aEvent) +{ + LOGFOCUS(("OnKeyReleaseEvent [%p]\n", (void *)this)); + + if (mIMContext && mIMContext->OnKeyEvent(this, aEvent)) { + return TRUE; + } + + RefPtr<TextEventDispatcher> dispatcher = GetTextEventDispatcher(); + nsresult rv = dispatcher->BeginNativeInputTransaction(); + if (NS_WARN_IF(NS_FAILED(rv))) { + return false; + } + + WidgetKeyboardEvent keyupEvent(true, eKeyUp, this); + KeymapWrapper::InitKeyEvent(keyupEvent, aEvent); + nsEventStatus status = nsEventStatus_eIgnore; + dispatcher->DispatchKeyboardEvent(eKeyUp, keyupEvent, status, aEvent); + + 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; +#if GTK_CHECK_VERSION(3,4,0) + // check for duplicate legacy scroll event, see GNOME bug 726878 + if (aEvent->direction != GDK_SCROLL_SMOOTH && + mLastScrollEventTime == aEvent->time) + return; +#endif + WidgetWheelEvent wheelEvent(true, eWheel, this); + wheelEvent.mDeltaMode = nsIDOMWheelEvent::DOM_DELTA_LINE; + switch (aEvent->direction) { +#if GTK_CHECK_VERSION(3,4,0) + case GDK_SCROLL_SMOOTH: + { + // As of GTK 3.4, all directional scroll events are provided by + // the GDK_SCROLL_SMOOTH direction on XInput2 devices. + mLastScrollEventTime = aEvent->time; + // 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; + // This next step manually unsets smooth scrolling for touch devices + // that trigger GDK_SCROLL_SMOOTH. We use the slave device, which + // represents the actual input. + 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) { + wheelEvent.mScrollType = WidgetWheelEvent::SCROLL_ASYNCHRONOUSELY; + } + break; + } +#endif + 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::OnVisibilityNotifyEvent(GdkEventVisibility *aEvent) +{ + LOGDRAW(("Visibility event %i on [%p] %p\n", + aEvent->state, this, aEvent->window)); + + if (!mGdkWindow) + return; + + switch (aEvent->state) { + case GDK_VISIBILITY_UNOBSCURED: + case GDK_VISIBILITY_PARTIAL: + if (mIsFullyObscured && mHasMappedToplevel) { + // GDK_EXPOSE events have been ignored, so make sure GDK + // doesn't think that the window has already been painted. + gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE); + } + + mIsFullyObscured = false; + + // if we have to retry the grab, retry it. + EnsureGrabs(); + break; + default: // includes GDK_VISIBILITY_FULLY_OBSCURED + mIsFullyObscured = true; + break; + } +} + +void +nsWindow::OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent) +{ + LOG(("nsWindow::OnWindowStateEvent [%p] changed %d new_window_state %d\n", + (void *)this, 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); + } + return; + } + // else the widget is a shell widget. + + // We don't care about anything but changes in the maximized/icon/fullscreen + // states + if ((aEvent->changed_mask + & (GDK_WINDOW_STATE_ICONIFIED | + GDK_WINDOW_STATE_MAXIMIZED | + GDK_WINDOW_STATE_FULLSCREEN)) == 0) { + 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 (mWidgetListener) { + mWidgetListener->SizeModeChanged(mSizeState); + if (aEvent->changed_mask & GDK_WINDOW_STATE_FULLSCREEN) { + mWidgetListener->FullscreenChanged( + aEvent->new_window_state & GDK_WINDOW_STATE_FULLSCREEN); + } + } +} + +void +nsWindow::ThemeChanged() +{ + NotifyThemeChanged(); + + 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); + + nsWindow *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; + } +} + +void +nsWindow::OnDPIChanged() +{ + if (mWidgetListener) { + nsIPresShell* presShell = mWidgetListener->GetPresShell(); + if (presShell) { + presShell->BackingScaleFactorChanged(); + // Update menu's font size etc + presShell->ThemeChanged(); + } + } +} + +void +nsWindow::OnCheckResize() +{ + mPendingConfigures++; +} + +void +nsWindow::DispatchDragEvent(EventMessage aMsg, const LayoutDeviceIntPoint& aRefPoint, + guint aTime) +{ + WidgetDragEvent event(true, aMsg, this); + + if (aMsg == eDragOver) { + 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)); + + nsDragService::GetInstance()-> + TargetDataReceived(aWidget, aDragContext, aX, aY, + aSelectionData, aInfo, aTime); +} + +#if GTK_CHECK_VERSION(3,4,0) +gboolean +nsWindow::OnTouchEvent(GdkEventTouch* aEvent) +{ + if (!mHandleTouchEvent) { + 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, touch.forget()); + // 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() = touch.forget(); + } + + DispatchInputEvent(&event); + return TRUE; +} +#endif + +static void +GetBrandName(nsXPIDLString& brandName) +{ + nsCOMPtr<nsIStringBundleService> bundleService = + do_GetService(NS_STRINGBUNDLE_CONTRACTID); + + nsCOMPtr<nsIStringBundle> bundle; + if (bundleService) + bundleService->CreateBundle( + "chrome://branding/locale/brand.properties", + getter_AddRefs(bundle)); + + if (bundle) + bundle->GetStringFromName( + u"brandShortName", + getter_Copies(brandName)); + + if (brandName.IsEmpty()) + brandName.AssignLiteral(u"Mozilla"); +} + +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; + +#if (MOZ_WIDGET_GTK == 2) + attributes_mask |= GDK_WA_COLORMAP; + attributes.colormap = gtk_widget_get_colormap(widget); +#endif + + GdkWindow *window = gdk_window_new(parent, &attributes, attributes_mask); + gdk_window_set_user_data(window, widget); + +// GTK3 TODO? +#if (MOZ_WIDGET_GTK == 2) + /* set the default pixmap to None so that you don't end up with the + gtk default which is BlackPixel. */ + gdk_window_set_back_pixmap(window, nullptr, FALSE); +#endif + + return window; +} + +nsresult +nsWindow::Create(nsIWidget* aParent, + nsNativeWidget aNativeParent, + const LayoutDeviceIntRect& aRect, + nsWidgetInitData* aInitData) +{ + // 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; + ConstrainSize(&mBounds.width, &mBounds.height); + + // figure out our parent window + GtkWidget *parentMozContainer = nullptr; + GtkContainer *parentGtkContainer = nullptr; + GdkWindow *parentGdkWindow = nullptr; + GtkWindow *topLevelParent = nullptr; + nsWindow *parentnsWindow = nullptr; + GtkWidget *eventWidget = nullptr; + bool shellHasCSD = false; + + 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; + + // get the toplevel window just in case someone needs to use it + // for setting transients or whatever. + topLevelParent = + GTK_WINDOW(gtk_widget_get_toplevel(parentMozContainer)); + } + + // 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 ... + GtkWindowType type = + mWindowType != eWindowType_popup || aInitData->mNoAutoHide ? + GTK_WINDOW_TOPLEVEL : GTK_WINDOW_POPUP; + mShell = gtk_window_new(type); + + // 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) { + SetDefaultIcon(); + gtk_window_set_wmclass(GTK_WINDOW(mShell), "Dialog", + gdk_get_program_class()); + gtk_window_set_type_hint(GTK_WINDOW(mShell), + GDK_WINDOW_TYPE_HINT_DIALOG); + gtk_window_set_transient_for(GTK_WINDOW(mShell), + topLevelParent); + } + else if (mWindowType == eWindowType_popup) { + // 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(); + + gtk_window_set_wmclass(GTK_WINDOW(mShell), "Popup", + gdk_get_program_class()); + + if (aInitData->mSupportTranslucency) { + // We need to select an ARGB visual here instead of in + // SetTransparencyMode() because it has to be done before the + // widget is realized. An ARGB visual is only useful if we + // are on a compositing window manager. + GdkScreen *screen = gtk_widget_get_screen(mShell); + if (gdk_screen_is_composited(screen)) { +#if (MOZ_WIDGET_GTK == 2) + GdkColormap *colormap = + gdk_screen_get_rgba_colormap(screen); + gtk_widget_set_colormap(mShell, colormap); +#else + GdkVisual *visual = gdk_screen_get_rgba_visual(screen); + gtk_widget_set_visual(mShell, visual); +#endif + } + } + 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. + 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 (topLevelParent) { + gtk_window_set_transient_for(GTK_WINDOW(mShell), + topLevelParent); + } + } + else { // must be eWindowType_toplevel + SetDefaultIcon(); + gtk_window_set_wmclass(GTK_WINDOW(mShell), "Toplevel", + gdk_get_program_class()); + + // 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); + } + + // Create a container to hold child windows and child GtkWidgets. + GtkWidget *container = moz_container_new(); + mContainer = MOZ_CONTAINER(container); + +#if (MOZ_WIDGET_GTK == 3) + // "csd" style is set when widget is realized so we need to call + // it explicitly now. + gtk_widget_realize(mShell); + + // We can't draw directly to top-level window when client side + // decorations are enabled. We use container with GdkWindow instead. + GtkStyleContext* style = gtk_widget_get_style_context(mShell); + shellHasCSD = gtk_style_context_has_class(style, "csd"); +#endif + if (!shellHasCSD) { + // Use mShell's window for drawing and events. + gtk_widget_set_has_window(container, FALSE); + // Prevent GtkWindow from painting a background to flicker. + gtk_widget_set_app_paintable(mShell, TRUE); + } + // Set up event widget + eventWidget = shellHasCSD ? container : mShell; + gtk_widget_add_events(eventWidget, kEvents); + + gtk_container_add(GTK_CONTAINER(mShell), container); + gtk_widget_realize(container); + + // make sure this is the focus widget in the container + gtk_widget_show(container); + 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); + + 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. + if (aInitData->mMouseTransparent) { +#if (MOZ_WIDGET_GTK == 2) + GdkRectangle rect = { 0, 0, 0, 0 }; + GdkRegion *region = gdk_region_rectangle(&rect); + + gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0); + gdk_region_destroy(region); +#else + cairo_rectangle_int_t rect = { 0, 0, 0, 0 }; + cairo_region_t *region = cairo_region_create_rectangle(&rect); + + gdk_window_input_shape_combine_region(mGdkWindow, region, 0, 0); + cairo_region_destroy(region); +#endif + } + } + } + break; + case eWindowType_plugin: + case eWindowType_plugin_ipc_chrome: + case eWindowType_plugin_ipc_content: + 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 (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); + + GtkSettings* default_settings = gtk_settings_get_default(); + g_signal_connect_after(default_settings, + "notify::gtk-theme-name", + G_CALLBACK(theme_changed_cb), this); + g_signal_connect_after(default_settings, + "notify::gtk-font-name", + G_CALLBACK(theme_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); +#if (MOZ_WIDGET_GTK == 3) + g_signal_connect(mContainer, "notify::scale-factor", + G_CALLBACK(scale_changed_cb), nullptr); +#endif + // Initialize mHasMappedToplevel. + hierarchy_changed_cb(GTK_WIDGET(mContainer), nullptr); + // Expose, focus, key, and drag events are sent even to GTK_NO_WINDOW + // widgets. +#if (MOZ_WIDGET_GTK == 2) + g_signal_connect(mContainer, "expose_event", + G_CALLBACK(expose_event_cb), nullptr); +#else + g_signal_connect(G_OBJECT(mContainer), "draw", + G_CALLBACK(expose_event_cb), nullptr); +#endif + 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); + + GtkWidget *widgets[] = { GTK_WIDGET(mContainer), + !shellHasCSD ? mShell : nullptr }; + for (size_t i = 0; i < ArrayLength(widgets) && widgets[i]; ++i) { + // Visibility events are sent to the owning widget of the relevant + // window but do not propagate to parent widgets so connect on + // mShell (if it exists) as well as mContainer. + g_signal_connect(widgets[i], "visibility-notify-event", + G_CALLBACK(visibility_notify_event_cb), nullptr); + // Similarly 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); + } + + // 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) { +#if (MOZ_WIDGET_GTK == 2) + // Don't let GTK mess with the shapes of our GdkWindows + GTK_PRIVATE_SET_FLAG(eventWidget, GTK_HAS_SHAPE_MASK); +#endif + + // 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, "property-notify-event", + G_CALLBACK(property_notify_event_cb), nullptr); + g_signal_connect(eventWidget, "scroll-event", + G_CALLBACK(scroll_event_cb), nullptr); +#if GTK_CHECK_VERSION(3,4,0) + g_signal_connect(eventWidget, "touch-event", + G_CALLBACK(touch_event_cb), nullptr); +#endif + } + + LOG(("nsWindow [%p]\n", (void *)this)); + if (mShell) { + LOG(("\tmShell %p mContainer %p mGdkWindow %p 0x%lx\n", + mShell, mContainer, mGdkWindow, + gdk_x11_window_get_xid(mGdkWindow))); + } 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); + + mSurfaceProvider.Initialize(mXDisplay, mXWindow, mXVisual, mXDepth); + } +#endif + + return NS_OK; +} + +void +nsWindow::SetWindowClass(const nsAString &xulWinType) +{ + if (!mShell) + return; + + const char *res_class = gdk_get_program_class(); + if (!res_class) + return; + + char *res_name = ToNewCString(xulWinType); + 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; + + gdk_window_set_role(mGdkWindow, role); + +#ifdef MOZ_X11 + if (mIsX11Display) { + XClassHint *class_hint = XAllocClassHint(); + if (!class_hint) { + free(res_name); + return; + } + class_hint->res_name = res_name; + class_hint->res_class = const_cast<char*>(res_class); + + // Can't use gtk_window_set_wmclass() for this; it prints + // a warning & refuses to make the change. + GdkDisplay *display = gdk_display_get_default(); + XSetClassHint(GDK_DISPLAY_XDISPLAY(display), + gdk_x11_window_get_xid(mGdkWindow), + class_hint); + XFree(class_hint); + } +#endif /* MOZ_X11 */ + + free(res_name); +} + +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) { + gtk_window_resize(GTK_WINDOW(mShell), size.width, size.height); + } + 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 X11CompositorWidget 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(); + } + + 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 (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. + 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 X11CompositorWidget 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::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); + } + + gtk_widget_show(mShell); + } + else if (mContainer) { + gtk_widget_show(GTK_WIDGET(mContainer)); + } + else if (mGdkWindow) { + gdk_window_show_unraised(mGdkWindow); + } + } + 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 && !mIsFullyObscured) { + // GDK_EXPOSE events have been ignored but the window is now visible, + // so make sure GDK doesn't think that the window has already been + // painted. + gdk_window_invalidate_rect(mGdkWindow, nullptr, FALSE); + + // 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. + LayoutDeviceIntSize result = aSize; + const int32_t kInt16Max = 32767; + if (result.width > kInt16Max) { + result.width = kInt16Max; + } + if (result.height > kInt16Max) { + result.height = kInt16Max; + } + 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 (!isTransparent) { + ClearTransparencyBitmap(); + } // else the new default alpha values are "all 1", so we don't + // need to change anything yet + + mIsTransparent = isTransparent; + + // Need to clean our LayerManager up while still alive because + // we don't want to use layers acceleration on shaped windows + 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; +} + +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]; + nsWindow* 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; + +#if (MOZ_WIDGET_GTK == 2) + GdkRegion *region = gdk_region_new(); // aborts on OOM + for (uint32_t i = 0; i < newRects->Length(); ++i) { + const LayoutDeviceIntRect& r = newRects->ElementAt(i); + GdkRectangle rect = { r.x, r.y, r.width, r.height }; + gdk_region_union_with_rect(region, &rect); + } + + gdk_window_shape_combine_region(mGdkWindow, region, 0, 0); + gdk_region_destroy(region); +#else + 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); +#endif + + 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; + gchar* 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 +#if (MOZ_WIDGET_GTK == 2) + gtk_widget_reset_shapes(mShell); + GdkBitmap* maskBitmap = gdk_bitmap_create_from_data(mGdkWindow, + mTransparencyBitmap, + mTransparencyBitmapWidth, mTransparencyBitmapHeight); + if (!maskBitmap) + return; + + gtk_widget_shape_combine_mask(mShell, maskBitmap, 0, 0); + g_object_unref(maskBitmap); +#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_WIDGET_GTK == 2 +#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"); + + 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; +} + +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 || mIsFullyObscured) { + LOG(("GrabPointer: window not visible\n")); + mRetryPointerGrab = true; + return; + } + + if (!mGdkWindow) + return; + + gint retval; + 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), + (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(this, &nsWindow::CheckForRollupDuringGrab); + NS_DispatchToCurrentThread(event.forget()); + } +} + +void +nsWindow::ReleaseGrabs(void) +{ + LOG(("ReleaseGrabs\n")); + + mRetryPointerGrab = false; + 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::SetupPluginPort(void) +{ + if (!mGdkWindow) + return nullptr; + + if (gdk_window_is_destroyed(mGdkWindow) == TRUE) + return nullptr; + + Window window = gdk_x11_window_get_xid(mGdkWindow); + + // we have to flush the X queue here so that any plugins that + // might be running on separate X connections will be able to use + // this window in case it was just created +#ifdef MOZ_X11 + XWindowAttributes xattrs; + Display *display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + XGetWindowAttributes(display, window, &xattrs); + XSelectInput (display, window, + xattrs.your_event_mask | + SubstructureNotifyMask); + + gdk_window_add_filter(mGdkWindow, plugin_window_filter_func, this); + + XSync(display, False); +#endif /* MOZ_X11 */ + + return (void *)window; +} + +void +nsWindow::SetDefaultIcon(void) +{ + SetIcon(NS_LITERAL_STRING("default")); +} + +void +nsWindow::SetPluginType(PluginType aPluginType) +{ + mPluginType = aPluginType; +} + +#ifdef MOZ_X11 +void +nsWindow::SetNonXEmbedPluginFocus() +{ + if (gPluginFocusWindow == this || mPluginType!=PluginType_NONXEMBED) { + return; + } + + if (gPluginFocusWindow) { + RefPtr<nsWindow> kungFuDeathGrip = gPluginFocusWindow; + gPluginFocusWindow->LoseNonXEmbedPluginFocus(); + } + + LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus\n")); + + Window curFocusWindow; + int focusState; + + GdkDisplay *gdkDisplay = gdk_window_get_display(mGdkWindow); + XGetInputFocus(gdk_x11_display_get_xdisplay(gdkDisplay), + &curFocusWindow, + &focusState); + + LOGFOCUS(("\t curFocusWindow=%p\n", curFocusWindow)); + + GdkWindow* toplevel = gdk_window_get_toplevel(mGdkWindow); +#if (MOZ_WIDGET_GTK == 2) + GdkWindow *gdkfocuswin = gdk_window_lookup(curFocusWindow); +#else + GdkWindow *gdkfocuswin = gdk_x11_window_lookup_for_display(gdkDisplay, + curFocusWindow); +#endif + + // lookup with the focus proxy window is supposed to get the + // same GdkWindow as toplevel. If the current focused window + // is not the focus proxy, we return without any change. + if (gdkfocuswin != toplevel) { + return; + } + + // switch the focus from the focus proxy to the plugin window + mOldFocusWindow = curFocusWindow; + XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow), + gdk_x11_window_get_xid(mGdkWindow)); + gdk_error_trap_push(); + XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow), + gdk_x11_window_get_xid(mGdkWindow), + RevertToNone, + CurrentTime); + gdk_flush(); +#if (MOZ_WIDGET_GTK == 3) + gdk_error_trap_pop_ignored(); +#else + gdk_error_trap_pop(); +#endif + gPluginFocusWindow = this; + gdk_window_add_filter(nullptr, plugin_client_message_filter, this); + + LOGFOCUS(("nsWindow::SetNonXEmbedPluginFocus oldfocus=%p new=%p\n", + mOldFocusWindow, gdk_x11_window_get_xid(mGdkWindow))); +} + +void +nsWindow::LoseNonXEmbedPluginFocus() +{ + LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus\n")); + + // This method is only for the nsWindow which contains a + // Non-XEmbed plugin, for example, JAVA plugin. + if (gPluginFocusWindow != this || mPluginType!=PluginType_NONXEMBED) { + return; + } + + Window curFocusWindow; + int focusState; + + XGetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow), + &curFocusWindow, + &focusState); + + // we only switch focus between plugin window and focus proxy. If the + // current focused window is not the plugin window, just removing the + // event filter that blocks the WM_TAKE_FOCUS is enough. WM and gtk2 + // will take care of the focus later. + if (!curFocusWindow || + curFocusWindow == gdk_x11_window_get_xid(mGdkWindow)) { + + gdk_error_trap_push(); + XRaiseWindow(GDK_WINDOW_XDISPLAY(mGdkWindow), + mOldFocusWindow); + XSetInputFocus(GDK_WINDOW_XDISPLAY(mGdkWindow), + mOldFocusWindow, + RevertToParent, + CurrentTime); + gdk_flush(); +#if (MOZ_WIDGET_GTK == 3) + gdk_error_trap_pop_ignored(); +#else + gdk_error_trap_pop(); +#endif + } + gPluginFocusWindow = nullptr; + mOldFocusWindow = 0; + gdk_window_remove_filter(nullptr, plugin_client_message_filter, this); + + LOGFOCUS(("nsWindow::LoseNonXEmbedPluginFocus end\n")); +} +#endif /* MOZ_X11 */ + +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); + 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) +{ + GdkScreen* screen = gtk_widget_get_screen(mShell); + if (!gdk_screen_is_composited(screen)) { + 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); +} + +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 (!IsFullscreenSupported(mShell)) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (aFullScreen) { + if (mSizeMode != nsSizeMode_Fullscreen) + mLastSizeMode = mSizeMode; + + mSizeMode = nsSizeMode_Fullscreen; + gtk_window_fullscreen(GTK_WINDOW(mShell)); + } + else { + mSizeMode = mLastSizeMode; + gtk_window_unfullscreen(GTK_WINDOW(mShell)); + } + + NS_ASSERTION(mLastSizeMode != nsSizeMode_Fullscreen, + "mLastSizeMode should never be fullscreen"); + return NS_OK; +} + +NS_IMETHODIMP +nsWindow::HideWindowChrome(bool aShouldHide) +{ + 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->HideWindowChrome(aShouldHide); + } + + // 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(mGdkWindow)) { + gdk_window_hide(mGdkWindow); + wasVisible = true; + } + + gint wmd; + if (aShouldHide) + wmd = 0; + else + wmd = ConvertBorderStyles(mBorderStyle); + + if (wmd != -1) + gdk_window_set_decorations(mGdkWindow, (GdkWMDecoration) wmd); + + if (wasVisible) + gdk_window_show(mGdkWindow); + + // 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 + XSync(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()) , False); +#else + gdk_flush (); +#endif /* MOZ_X11 */ + + return NS_OK; +} + +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; + GdkWindow *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 (uint32_t i=0; i<widgetChain.Length(); ++i) { + nsIWidget* widget = widgetChain[i]; + GdkWindow* 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 = IntPoint::Truncate(aMouseX, aMouseY); + 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); + } + +#if (MOZ_WIDGET_GTK == 2) + gdk_drawable_get_size(aWindow, &w, &h); +#else + w = gdk_window_get_width(aWindow); + h = gdk_window_get_height(aWindow); +#endif + + 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: + newType = MOZ_CURSOR_ZOOM_IN; + break; + case eCursor_zoom_out: + 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 + +#if (MOZ_WIDGET_GTK == 2) +static gboolean +expose_event_cb(GtkWidget *widget, GdkEventExpose *event) +{ + RefPtr<nsWindow> window = get_window_for_gdk_window(event->window); + if (!window) + return FALSE; + + window->OnExposeEvent(event); + return FALSE; +} +#else +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; +} +#endif //MOZ_WIDGET_GTK == 2 + +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 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. + gint x = gint(event->x_root); + gint 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) +{ + XEvent* 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; +} + +static GdkFilterReturn +plugin_window_filter_func(GdkXEvent *gdk_xevent, GdkEvent *event, gpointer data) +{ + GdkWindow *plugin_window; + XEvent *xevent; + Window xeventWindow; + + RefPtr<nsWindow> nswindow = (nsWindow*)data; + GdkFilterReturn return_val; + + xevent = (XEvent *)gdk_xevent; + return_val = GDK_FILTER_CONTINUE; + + switch (xevent->type) + { + case CreateNotify: + case ReparentNotify: + if (xevent->type==CreateNotify) { + xeventWindow = xevent->xcreatewindow.window; + } + else { + if (xevent->xreparent.event != xevent->xreparent.parent) + break; + xeventWindow = xevent->xreparent.window; + } +#if (MOZ_WIDGET_GTK == 2) + plugin_window = gdk_window_lookup(xeventWindow); +#else + plugin_window = gdk_x11_window_lookup_for_display( + gdk_x11_lookup_xdisplay(xevent->xcreatewindow.display), xeventWindow); +#endif + if (plugin_window) { + GtkWidget *widget = + get_gtk_widget_for_gdk_window(plugin_window); + +// TODO GTK3 +#if (MOZ_WIDGET_GTK == 2) + if (GTK_IS_XTBIN(widget)) { + nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED); + break; + } + else +#endif + if(GTK_IS_SOCKET(widget)) { + if (!g_object_get_data(G_OBJECT(widget), "enable-xt-focus")) { + nswindow->SetPluginType(nsWindow::PluginType_XEMBED); + break; + } + } + } + nswindow->SetPluginType(nsWindow::PluginType_NONXEMBED); + return_val = GDK_FILTER_REMOVE; + break; + case EnterNotify: + nswindow->SetNonXEmbedPluginFocus(); + break; + case DestroyNotify: + gdk_window_remove_filter + ((GdkWindow*)(nswindow->GetNativeData(NS_NATIVE_WINDOW)), + plugin_window_filter_func, + nswindow); + // Currently we consider all plugins are non-xembed and calls + // LoseNonXEmbedPluginFocus without any checking. + nswindow->LoseNonXEmbedPluginFocus(); + break; + default: + break; + } + return return_val; +} + +static GdkFilterReturn +plugin_client_message_filter(GdkXEvent *gdk_xevent, + GdkEvent *event, + gpointer data) +{ + XEvent *xevent; + xevent = (XEvent *)gdk_xevent; + + GdkFilterReturn return_val; + return_val = GDK_FILTER_CONTINUE; + + if (!gPluginFocusWindow || xevent->type!=ClientMessage) { + return return_val; + } + + // When WM sends out WM_TAKE_FOCUS, gtk2 will use XSetInputFocus + // to set the focus to the focus proxy. To prevent this happen + // while the focus is on the plugin, we filter the WM_TAKE_FOCUS + // out. + if (gdk_x11_get_xatom_by_name("WM_PROTOCOLS") + != xevent->xclient.message_type) { + return return_val; + } + + if ((Atom) xevent->xclient.data.l[0] == + gdk_x11_get_xatom_by_name("WM_TAKE_FOCUS")) { + // block it from gtk2.0 focus proxy + return_val = GDK_FILTER_REMOVE; + } + + return return_val; +} +#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 */ + 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 gboolean +visibility_notify_event_cb (GtkWidget *widget, GdkEventVisibility *event) +{ + RefPtr<nsWindow> window = get_window_for_gdk_window(event->window); + if (!window) + return FALSE; + + window->OnVisibilityNotifyEvent(event); + + 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 +theme_changed_cb (GtkSettings *settings, GParamSpec *pspec, nsWindow *data) +{ + RefPtr<nsWindow> window = data; + window->ThemeChanged(); +} + +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(); +} + +#if (MOZ_WIDGET_GTK == 3) +static void +scale_changed_cb (GtkWidget* widget, GParamSpec* aPSpec, gpointer aPointer) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(widget); + if (!window) { + return; + } + window->OnDPIChanged(); + + // configure_event is already fired before scale-factor signal, + // but size-allocate isn't fired by changing scale + GtkAllocation allocation; + gtk_widget_get_allocation(widget, &allocation); + window->OnSizeAllocate(&allocation); +} +#endif + +#if GTK_CHECK_VERSION(3,4,0) +static gboolean +touch_event_cb(GtkWidget* aWidget, GdkEventTouch* aEvent) +{ + UpdateLastInputEventTime(aEvent); + + nsWindow* window = GetFirstNSWindowForGDKWindow(aEvent->window); + if (!window) { + return FALSE; + } + + return window->OnTouchEvent(aEvent); +} +#endif + +////////////////////////////////////////////////////////////////////// +// These are all of our drag and drop operations + +void +nsWindow::InitDragEvent(WidgetDragEvent &aEvent) +{ + // set the keyboard modifiers + guint modifierState = KeymapWrapper::GetCurrentModifierState(); + KeymapWrapper::InitInputEvent(aEvent, modifierState); +} + +static gboolean +drag_motion_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + guint aTime, + gpointer aData) +{ + 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 }); + + return nsDragService::GetInstance()-> + ScheduleMotionEvent(innerMostWindow, aDragContext, + point, aTime); +} + +static void +drag_leave_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + guint aTime, + gpointer aData) +{ + RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget); + if (!window) + return; + + 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 gboolean +drag_drop_event_cb(GtkWidget *aWidget, + GdkDragContext *aDragContext, + gint aX, + gint aY, + guint aTime, + gpointer aData) +{ + 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 }); + + return nsDragService::GetInstance()-> + ScheduleDropEvent(innerMostWindow, aDragContext, + point, 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); + + 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)) { + GdkWindow *childWindow = (GdkWindow *) child->data; + if (get_window_for_gdk_window(childWindow)) { +#if (MOZ_WIDGET_GTK == 2) + gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch, nullptr); +#else + gdk_window_get_geometry(childWindow, &cx, &cy, &cw, &ch); +#endif + 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 inline bool +is_context_menu_key(const WidgetKeyboardEvent& aKeyEvent) +{ + return ((aKeyEvent.mKeyCode == NS_VK_F10 && aKeyEvent.IsShift() && + !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && + !aKeyEvent.IsAlt()) || + (aKeyEvent.mKeyCode == NS_VK_CONTEXT_MENU && !aKeyEvent.IsShift() && + !aKeyEvent.IsControl() && !aKeyEvent.IsMeta() && + !aKeyEvent.IsAlt())); +} + +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 */ + +// nsChildWindow class + +nsChildWindow::nsChildWindow() +{ +} + +nsChildWindow::~nsChildWindow() +{ +} + +NS_IMETHODIMP_(void) +nsWindow::SetInputContext(const InputContext& aContext, + const InputContextAction& aAction) +{ + if (!mIMContext) { + return; + } + mIMContext->SetInputContext(this, &aContext, &aAction); +} + +NS_IMETHODIMP_(InputContext) +nsWindow::GetInputContext() +{ + InputContext context; + if (!mIMContext) { + context.mIMEState.mEnabled = IMEState::DISABLED; + context.mIMEState.mOpen = IMEState::OPEN_STATE_NOT_SUPPORTED; + } else { + context = mIMContext->GetInputContext(); + } + return context; +} + +nsIMEUpdatePreference +nsWindow::GetIMEUpdatePreference() +{ + if (!mIMContext) { + return nsIMEUpdatePreference(); + } + return mIMContext->GetIMEUpdatePreference(); +} + +NS_IMETHODIMP_(TextEventDispatcherListener*) +nsWindow::GetNativeTextEventDispatcherListener() +{ + if (NS_WARN_IF(!mIMContext)) { + return nullptr; + } + return mIMContext; +} + +bool +nsWindow::ExecuteNativeKeyBindingRemapped(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData, + uint32_t aGeckoKeyCode, + uint32_t aNativeKeyCode) +{ + WidgetKeyboardEvent modifiedEvent(aEvent); + modifiedEvent.mKeyCode = aGeckoKeyCode; + static_cast<GdkEventKey*>(modifiedEvent.mNativeKeyEvent)->keyval = + aNativeKeyCode; + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(modifiedEvent, aCallback, aCallbackData); +} + +NS_IMETHODIMP_(bool) +nsWindow::ExecuteNativeKeyBinding(NativeKeyBindingsType aType, + const WidgetKeyboardEvent& aEvent, + DoCommandCallback aCallback, + void* aCallbackData) +{ + 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. + WidgetQueryContentEvent query(true, eQuerySelectedText, this); + nsEventStatus status; + DispatchEvent(&query, status); + + if (query.mSucceeded && query.mReply.mWritingMode.IsVertical()) { + uint32_t geckoCode = 0; + uint32_t gdkCode = 0; + switch (aEvent.mKeyCode) { + case NS_VK_LEFT: + if (query.mReply.mWritingMode.IsVerticalLR()) { + geckoCode = NS_VK_UP; + gdkCode = GDK_Up; + } else { + geckoCode = NS_VK_DOWN; + gdkCode = GDK_Down; + } + break; + + case NS_VK_RIGHT: + if (query.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; + } + + return ExecuteNativeKeyBindingRemapped(aType, aEvent, aCallback, + aCallbackData, + geckoCode, gdkCode); + } + } + + NativeKeyBindings* keyBindings = NativeKeyBindings::GetInstance(aType); + return keyBindings->Execute(aEvent, aCallback, aCallbackData); +} + +#if defined(MOZ_X11) && (MOZ_WIDGET_GTK == 2) +/* static */ already_AddRefed<DrawTarget> +nsWindow::GetDrawTargetForGdkDrawable(GdkDrawable* aDrawable, + const IntSize& aSize) +{ + GdkVisual* visual = gdk_drawable_get_visual(aDrawable); + Screen* xScreen = + gdk_x11_screen_get_xscreen(gdk_drawable_get_screen(aDrawable)); + Display* xDisplay = DisplayOfScreen(xScreen); + Drawable xDrawable = gdk_x11_drawable_get_xid(aDrawable); + + RefPtr<gfxASurface> surface; + + if (visual) { + Visual* xVisual = gdk_x11_visual_get_xvisual(visual); + + surface = new gfxXlibSurface(xDisplay, xDrawable, xVisual, aSize); + } else { + // no visual? we must be using an xrender format. Find a format + // for this depth. + XRenderPictFormat *pf = nullptr; + switch (gdk_drawable_get_depth(aDrawable)) { + case 32: + pf = XRenderFindStandardFormat(xDisplay, PictStandardARGB32); + break; + case 24: + pf = XRenderFindStandardFormat(xDisplay, PictStandardRGB24); + break; + default: + NS_ERROR("Don't know how to handle the given depth!"); + break; + } + + surface = new gfxXlibSurface(xScreen, xDrawable, pf, aSize); + } + + RefPtr<DrawTarget> dt = + gfxPlatform::GetPlatform()->CreateDrawTargetForSurface(surface, aSize); + + if (!dt || !dt->IsValid()) { + return nullptr; + } + + return dt.forget(); +} +#endif + +already_AddRefed<DrawTarget> +nsWindow::StartRemoteDrawingInRegion(LayoutDeviceIntRegion& aInvalidRegion, BufferMode* aBufferMode) +{ + return mSurfaceProvider.StartRemoteDrawingInRegion(aInvalidRegion, aBufferMode); +} + +void +nsWindow::EndRemoteDrawingInRegion(DrawTarget* aDrawTarget, + 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->button != WidgetMouseEvent::eLeftButton) { + // 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; + } + + // 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; +} + +NS_IMETHODIMP +nsWindow::BeginMoveDrag(WidgetMouseEvent* aEvent) +{ + MOZ_ASSERT(aEvent, "must have event"); + MOZ_ASSERT(aEvent->mClass == eMouseEventClass, + "event must have correct struct type"); + + GdkWindow *gdk_window; + gint button, screenX, screenY; + if (!GetDragInfo(aEvent, &gdk_window, &button, &screenX, &screenY)) { + return NS_ERROR_FAILURE; + } + + // tell the window manager to start the move + screenX = DevicePixelsToGdkCoordRoundDown(screenX); + screenY = DevicePixelsToGdkCoordRoundDown(screenY); + gdk_window_begin_move_drag(gdk_window, button, screenX, screenY, + aEvent->mTime); + + return NS_OK; +} + +NS_IMETHODIMP +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; + } + if (!mLayerManager && eTransparencyTransparent == GetTransparencyMode()) { + mLayerManager = CreateBasicLayerManager(); + } + + return nsBaseWidget::GetLayerManager(aShadowManager, aBackendHint, aPersistence); +} + +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(); + } + } +} + +gint +nsWindow::GdkScaleFactor() +{ +#if (MOZ_WIDGET_GTK >= 3) + // Available as of GTK 3.10+ + static auto sGdkWindowGetScaleFactorPtr = (gint (*)(GdkWindow*)) + dlsym(RTLD_DEFAULT, "gdk_window_get_scale_factor"); + if (sGdkWindowGetScaleFactorPtr && mGdkWindow) + return (*sGdkWindowGetScaleFactorPtr)(mGdkWindow); +#endif + return nsScreenGtk::GetGtkMonitorScaleFactor(); +} + + +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; + +#if (MOZ_WIDGET_GTK == 3) + // 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); +#endif + + 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; +#if (MOZ_WIDGET_GTK == 3) + // 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); +#endif + 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. +#if GTK_CHECK_VERSION(3,4,0) + // TODO: is this correct? I don't have GTK 3.4+ so I can't check + event.scroll.direction = GDK_SCROLL_SMOOTH; + event.scroll.delta_x = -aDeltaX; + event.scroll.delta_y = -aDeltaY; +#else + if (aDeltaX < 0) { + event.scroll.direction = GDK_SCROLL_RIGHT; + } else if (aDeltaX > 0) { + event.scroll.direction = GDK_SCROLL_LEFT; + } else if (aDeltaY < 0) { + event.scroll.direction = GDK_SCROLL_DOWN; + } else if (aDeltaY > 0) { + event.scroll.direction = GDK_SCROLL_UP; + } else { + return NS_OK; + } +#endif + + gdk_event_put(&event); + + return NS_OK; +} + +#if GTK_CHECK_VERSION(3,4,0) +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; +} +#endif + +int32_t +nsWindow::RoundsWidgetCoordinatesTo() +{ + return GdkScaleFactor(); +} + +void nsWindow::GetCompositorWidgetInitData(mozilla::widget::CompositorWidgetInitData* aInitData) +{ + #ifdef MOZ_X11 + *aInitData = mozilla::widget::CompositorWidgetInitData( + mXWindow, + nsCString(XDisplayString(mXDisplay)), + GetClientSize()); + #endif +} |