b=544211 provide synthesized button-up events when native button-up events are missed r=roc

This commit is contained in:
Karl Tomlinson 2010-03-24 16:37:06 +13:00
parent 110f627e7b
commit 89dfe33336
3 changed files with 109 additions and 20 deletions

View File

@ -227,7 +227,7 @@ nsDragService::InvokeDragSession(nsIDOMNode *aDOMNode,
memset(&event, 0, sizeof(GdkEvent));
event.type = GDK_BUTTON_PRESS;
event.button.window = mHiddenWidget->window;
event.button.time = nsWindow::mLastButtonPressTime;
event.button.time = nsWindow::sLastButtonPressTime;
// start our drag.
GdkDragContext *context = gtk_drag_begin(mHiddenWidget,

View File

@ -276,10 +276,10 @@ PRBool nsWindow::sIsDraggingOutOf = PR_FALSE;
// This is the time of the last button press event. The drag service
// uses it as the time to start drags.
guint32 nsWindow::mLastButtonPressTime = 0;
guint32 nsWindow::sLastButtonPressTime = 0;
// Time of the last button release event. We use it to detect when the
// drag ended before we could properly setup drag and drop.
guint32 nsWindow::mLastButtonReleaseTime = 0;
guint32 nsWindow::sLastButtonReleaseTime = 0;
static NS_DEFINE_IID(kCDragServiceCID, NS_DRAGSERVICE_CID);
@ -339,9 +339,13 @@ static PRBool gForce24bpp = PR_FALSE;
static GtkWidget *gInvisibleContainer = NULL;
// Sometimes this actually also includes the state of the modifier keys, but
// only the button state bits are used.
static guint gButtonState;
// Some gobject functions expect functions for gpointer arguments.
// gpointer is void* but C++ doesn't like casting functions to void*.
template<class T> gpointer
template<class T> static inline gpointer
FuncToGpointer(T aFunction)
{
return reinterpret_cast<gpointer>
@ -2627,10 +2631,21 @@ nsWindow::OnDeleteEvent(GtkWidget *aWidget, GdkEventAny *aEvent)
void
nsWindow::OnEnterNotifyEvent(GtkWidget *aWidget, GdkEventCrossing *aEvent)
{
// XXXldb Is this the right test for embedding cases?
// 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 != NULL)
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;
nsMouseEvent event(PR_TRUE, NS_MOUSE_ENTER, this, nsMouseEvent::eReal);
event.refPoint.x = nscoord(aEvent->x);
@ -2644,6 +2659,7 @@ nsWindow::OnEnterNotifyEvent(GtkWidget *aWidget, GdkEventCrossing *aEvent)
DispatchEvent(&event, status);
}
// XXX Is this the right test for embedding cases?
static PRBool
is_top_level_mouse_exit(GdkWindow* aWindow, GdkEventCrossing *aEvent)
{
@ -2661,7 +2677,14 @@ is_top_level_mouse_exit(GdkWindow* aWindow, GdkEventCrossing *aEvent)
void
nsWindow::OnLeaveNotifyEvent(GtkWidget *aWidget, GdkEventCrossing *aEvent)
{
// XXXldb Is this the right test for embedding cases?
// 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 != NULL)
return;
@ -2837,6 +2860,61 @@ nsWindow::OnMotionNotifyEvent(GtkWidget *aWidget, GdkEventMotion *aEvent)
}
#endif
// 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) {
PRInt16 buttonType;
switch (buttonMask) {
case GDK_BUTTON1_MASK:
buttonType = nsMouseEvent::eLeftButton;
break;
case GDK_BUTTON2_MASK:
buttonType = nsMouseEvent::eMiddleButton;
break;
default:
NS_ASSERTION(buttonMask == GDK_BUTTON3_MASK,
"Unexpected button mask");
buttonType = nsMouseEvent::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.
nsMouseEvent synthEvent(PR_TRUE, NS_MOUSE_BUTTON_UP, this,
nsMouseEvent::eSynthesized);
synthEvent.button = buttonType;
nsEventStatus status;
DispatchEvent(&synthEvent, status);
sLastButtonReleaseTime = aGdkEvent->time;
}
}
}
void
nsWindow::InitButtonEvent(nsMouseEvent &aEvent,
GdkEventButton *aGdkEvent)
@ -2870,13 +2948,20 @@ nsWindow::InitButtonEvent(nsMouseEvent &aEvent,
}
}
static guint ButtonMaskFromGDKButton(guint button)
{
return GDK_BUTTON1_MASK << (button - 1);
}
void
nsWindow::OnButtonPressEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
{
LOG(("Button %u press on %p\n", aEvent->button, (void *)this));
nsEventStatus status;
// If you double click in GDK, it will actually generate a single
// click event before sending the double click event, and this is
// 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
@ -2890,8 +2975,8 @@ nsWindow::OnButtonPressEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
}
// Always save the time of this event
mLastButtonPressTime = aEvent->time;
mLastButtonReleaseTime = 0;
sLastButtonPressTime = aEvent->time;
sLastButtonReleaseTime = 0;
nsWindow *containerWindow = GetContainerWindow();
if (!gFocusWindow && containerWindow) {
@ -2953,6 +3038,8 @@ nsWindow::OnButtonPressEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
return;
}
gButtonState |= ButtonMaskFromGDKButton(aEvent->button);
nsMouseEvent event(PR_TRUE, NS_MOUSE_BUTTON_DOWN, this, nsMouseEvent::eReal);
event.button = domButton;
InitButtonEvent(event, aEvent);
@ -2974,8 +3061,10 @@ nsWindow::OnButtonPressEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
void
nsWindow::OnButtonReleaseEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
{
LOG(("Button %u release on %p\n", aEvent->button, (void *)this));
PRUint16 domButton;
mLastButtonReleaseTime = aEvent->time;
sLastButtonReleaseTime = aEvent->time;
switch (aEvent->button) {
case 1:
@ -2991,6 +3080,8 @@ nsWindow::OnButtonReleaseEvent(GtkWidget *aWidget, GdkEventButton *aEvent)
return;
}
gButtonState &= ~ButtonMaskFromGDKButton(aEvent->button);
nsMouseEvent event(PR_TRUE, NS_MOUSE_BUTTON_UP, this, nsMouseEvent::eReal);
event.button = domButton;
InitButtonEvent(event, aEvent);
@ -3584,7 +3675,7 @@ nsWindow::OnDragMotionEvent(GtkWidget *aWidget,
{
LOGDRAG(("nsWindow::OnDragMotionSignal\n"));
if (mLastButtonReleaseTime) {
if (sLastButtonReleaseTime) {
// The drag ended before it was even setup to handle the end of the drag
// So, we fake the button getting released again to release the drag
GtkWidget *widget = gtk_grab_get_current();
@ -3592,9 +3683,9 @@ nsWindow::OnDragMotionEvent(GtkWidget *aWidget,
gboolean retval;
memset(&event, 0, sizeof(event));
event.type = GDK_BUTTON_RELEASE;
event.button.time = mLastButtonReleaseTime;
event.button.time = sLastButtonReleaseTime;
event.button.button = 1;
mLastButtonReleaseTime = 0;
sLastButtonReleaseTime = 0;
if (widget) {
g_signal_emit_by_name(widget, "button_release_event", &event, &retval);
return TRUE;
@ -5636,10 +5727,6 @@ gboolean
enter_notify_event_cb(GtkWidget *widget,
GdkEventCrossing *event)
{
if (is_parent_ungrab_enter(event)) {
return TRUE;
}
nsRefPtr<nsWindow> window = get_window_for_gdk_window(event->window);
if (!window)
return TRUE;

View File

@ -300,8 +300,8 @@ public:
Window mOldFocusWindow;
#endif /* MOZ_X11 */
static guint32 mLastButtonPressTime;
static guint32 mLastButtonReleaseTime;
static guint32 sLastButtonPressTime;
static guint32 sLastButtonReleaseTime;
NS_IMETHOD BeginResizeDrag (nsGUIEvent* aEvent, PRInt32 aHorizontal, PRInt32 aVertical);
@ -494,6 +494,8 @@ private:
*flag &= ~mask;
}
void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent);
/**
* |mIMModule| takes all IME related stuff.
*