/* -*- 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/Move.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;
}

void
nsWindow::SetMenuBar(UniquePtr<nsMenuBar> aMenuBar) {
    mMenuBar = mozilla::Move(aMenuBar);
}

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;
    }

    if (mIsX11Display) {
      // Workaround for https://bugzilla.gnome.org/show_bug.cgi?id=789054
      // To avoid crashes disable double-click on WM without _NET_WM_MOVERESIZE.
      // See _should_perform_ewmh_drag() at gdkwindow-x11.c
      GdkScreen* screen = gdk_window_get_screen(gdk_window);
      GdkAtom atom = gdk_atom_intern("_NET_WM_MOVERESIZE", FALSE);
      if (!gdk_x11_screen_supports_net_wm_hint(screen, atom)) {
          static unsigned int lastTimeStamp = 0;
          if (lastTimeStamp != aMouseEvent->mTime) {
              lastTimeStamp = aMouseEvent->mTime;
          } else {
              return false;
          }
      }
    }

    // FIXME: It would be nice to have the widget position at the time
    // of the event, but it's relatively unlikely that the widget has
    // moved since the mousedown.  (On the other hand, it's quite likely
    // that the mouse has moved, which is why we use the mouse position
    // from the event.)
    LayoutDeviceIntPoint offset = aMouseEvent->mWidget->WidgetToScreenOffset();
    *aRootX = aMouseEvent->mRefPoint.x + offset.x;
    *aRootY = aMouseEvent->mRefPoint.y + offset.y;

    return true;
}

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
}
