From c42f5a38872e348884c3076989438b074b1c8f8d Mon Sep 17 00:00:00 2001 From: Karl Tomlinson Date: Thu, 19 Apr 2012 18:18:31 +1200 Subject: [PATCH] b=497498 schedule event dispatch in response to GTK drag target signals to avoid running the event loop at unexpected times r=roc --HG-- extra : rebase_source : 881ad7c0efa85174347059a9f53b3a5bd4c76696 --- widget/gtk2/nsDragService.cpp | 238 ++++++++++++++++++++++++++- widget/gtk2/nsDragService.h | 91 ++++++++++- widget/gtk2/nsWindow.cpp | 299 +++++++--------------------------- widget/gtk2/nsWindow.h | 35 +--- 4 files changed, 389 insertions(+), 274 deletions(-) diff --git a/widget/gtk2/nsDragService.cpp b/widget/gtk2/nsDragService.cpp index a67ff82018f4..93e97c14688c 100644 --- a/widget/gtk2/nsDragService.cpp +++ b/widget/gtk2/nsDragService.cpp @@ -127,6 +127,7 @@ invisibleSourceDragDataGet(GtkWidget *aWidget, gpointer aData); nsDragService::nsDragService() + : mTaskSource(0) { // We have to destroy the hidden widget before the event loop stops // running. @@ -162,9 +163,6 @@ nsDragService::nsDragService() sDragLm = PR_NewLogModule("nsDragService"); PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::nsDragService")); mGrabWidget = 0; - mTargetWidget = 0; - mTargetDragContext = 0; - mTargetTime = 0; mCanDrop = false; mTargetDragDataReceived = false; mTargetDragData = 0; @@ -174,6 +172,9 @@ nsDragService::nsDragService() nsDragService::~nsDragService() { PR_LOG(sDragLm, PR_LOG_DEBUG, ("nsDragService::~nsDragService")); + if (mTaskSource) + g_source_remove(mTaskSource); + } NS_IMPL_ISUPPORTS_INHERITED2(nsDragService, nsBaseDragService, @@ -1131,7 +1132,8 @@ nsDragService::GetTargetDragData(GdkAtom aFlavor) { PR_LOG(sDragLm, PR_LOG_DEBUG, ("getting data flavor %d\n", aFlavor)); PR_LOG(sDragLm, PR_LOG_DEBUG, ("mLastWidget is %p and mLastContext is %p\n", - mTargetWidget, mTargetDragContext)); + mTargetWidget.get(), + mTargetDragContext.get())); // reset our target data areas TargetResetData(); gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); @@ -1702,3 +1704,231 @@ invisibleSourceDragEnd(GtkWidget *aWidget, dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS); } +// The following methods handle responding to GTK drag destination signals and +// tracking state between these signals. +// +// In general, GTK does not expect us to run the event loop while handling its +// drag destination signals, however our drag event handlers may run the +// event loop, most often to fetch information about the drag data. +// +// GTK, for example, uses the return value from drag-motion signals to +// determine whether drag-leave signals should be sent. If an event loop is +// run during drag-motion the XdndLeave message can get processed but when GTK +// receives the message it does not yet know that it needs to send the +// drag-leave signal to our widget. +// +// After a drag-drop signal, we need to reply with gtk_drag_finish(). +// However, gtk_drag_finish should happen after the drag-drop signal handler +// returns so that when the Motif drag protocol is used, the +// XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START +// reply sent on return from the drag-drop signal handler. +// +// Therefore we reply to the signals immediately and schedule a task to +// dispatch the Gecko events, which may run the event loop. +// +// Action in response to drag-leave signals is also delayed until the event +// loop runs again so that we find out whether a drag-drop signal follows. +// +// A single task is scheduled to manage responses to all three GTK signals. +// If further signals are received while the task is scheduled, the scheduled +// response is updated, sometimes effectively compressing successive signals. +// +// No Gecko drag events are dispatched (during nested event loops) while other +// Gecko drag events are in flight. This helps event handlers that may not +// expect nested events, while accessing an event's dataTransfer for example. + +gboolean +nsDragService::ScheduleMotionEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, guint aTime) +{ + if (mScheduledTask == eDragTaskMotion) { + // The drag source has sent another motion message before we've + // replied to the previous. That shouldn't happen with Xdnd. The + // spec for Motif drags is less clear, but we'll just update the + // scheduled task with the new position reply only to the most + // recent message. + NS_WARNING("Drag Motion message received before previous reply was sent"); + } + + // Returning TRUE means we'll reply with a status message, unless we first + // get a leave. + return Schedule(eDragTaskMotion, aWindow, aDragContext, + aWindowPoint, aTime); +} + +void +nsDragService::ScheduleLeaveEvent() +{ + // We don't know at this stage whether a drop signal will immediately + // follow. If the drop signal gets sent it will happen before we return + // to the main loop and the scheduled leave task will be replaced. + if (!Schedule(eDragTaskLeave, nsnull, NULL, nsIntPoint(), 0)) { + NS_WARNING("Drag leave after drop"); + } +} + +gboolean +nsDragService::ScheduleDropEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, guint aTime) +{ + if (!Schedule(eDragTaskDrop, aWindow, + aDragContext, aWindowPoint, aTime)) { + NS_WARNING("Additional drag drop ignored"); + return FALSE; + } + + SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset()); + + // We'll reply with gtk_drag_finish(). + return TRUE; +} + +gboolean +nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, guint aTime) +{ + // If we haven't yet run a scheduled drop task, just say that + // we are not ready to receive another drop. + if (mScheduledTask == eDragTaskDrop) + return FALSE; + + // If there is an existing leave or motion task scheduled, then that + // will be replaced. When the new task is run, it will dispatch + // any necessary leave or motion events. + + mScheduledTask = aTask; + mPendingWindow = aWindow; + mPendingDragContext = aDragContext; + mPendingWindowPoint = aWindowPoint; + mPendingTime = aTime; + + if (!mTaskSource) { + // High priority is used here because the native events involved have + // already waited at default priority. Perhaps a lower than default + // priority could be used for motion tasks because there is a chance + // that a leave or drop is waiting, but managing different priorities + // may not be worth the effort. Motion tasks shouldn't queue up as + // they should be throttled based on replies. + mTaskSource = + g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, this, NULL); + } + return TRUE; +} + +gboolean +nsDragService::TaskDispatchCallback(gpointer data) +{ + nsRefPtr dragService = static_cast(data); + return dragService->RunScheduledTask(); +} + +gboolean +nsDragService::RunScheduledTask() +{ + if (mTargetWindow && mTargetWindow != mPendingWindow) { + mTargetWindow->OnDragLeave(); + } + + // It is possible that the pending state has been updated during dispatch + // of the leave event. That's fine. + + // Now we collect the pending state because, from this point on, we want + // to use the same state for all events dispatched. All state is updated + // so that when other tasks are scheduled during dispatch here, this + // task is considered to have already been run. + bool positionHasChanged = + mPendingWindow != mTargetWindow || + mPendingWindowPoint != mTargetWindowPoint; + DragTask task = mScheduledTask; + mScheduledTask = eDragTaskNone; + mTargetWindow = mPendingWindow.forget(); + mTargetWindowPoint = mPendingWindowPoint; + + if (task == eDragTaskLeave) { + // Nothing more to do + // Returning false removes the task source from the event loop. + mTaskSource = 0; + return FALSE; + } + + // This may be the start of a destination drag session. + StartDragSession(); + + // mTargetWidget may be NULL if the window has been destroyed. + // (The leave event is not scheduled if a drop task is still scheduled.) + // We still reply appropriately to indicate that the drop will or didn't + // succeeed. + mTargetWidget = mTargetWindow->GetMozContainerWidget(); + mTargetDragContext.steal(mPendingDragContext); + mTargetTime = mPendingTime; + + // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model + // (as at 27 December 2010) indicates that a "drop" event should only be + // fired (at the current target element) if the current drag operation is + // not none. The current drag operation will only be set to a non-none + // value during a "dragover" event. + // + // If the user has ended the drag before any dragover events have been + // sent, then the spec recommends skipping the drop (because the current + // drag operation is none). However, here we assume that, by releasing + // the mouse button, the user has indicated that they want to drop, so we + // proceed with the drop where possible. + // + // In order to make the events appear to content in the same way as if the + // spec is being followed we make sure to dispatch a "dragover" event with + // appropriate coordinates and check canDrop before the "drop" event. + // + // When the Xdnd protocol is used for source/destination communication (as + // should be the case with GTK source applications) a dragover event + // should have already been sent during the drag-motion signal, which + // would have already been received because XdndDrop messages do not + // contain a position. However, we can't assume the same when the Motif + // protocol is used. + if (task == eDragTaskMotion || positionHasChanged) { + nsWindow::UpdateDragStatus(mTargetDragContext, this); + mTargetWindow-> + DispatchDragMotionEvents(this, mTargetWindowPoint, mTargetTime); + + if (task == eDragTaskMotion) { + // Reply to tell the source whether we can drop and what + // action would be taken. + TargetEndDragMotion(mTargetWidget, mTargetDragContext, mTargetTime); + } + } + + if (task == eDragTaskDrop) { + gboolean success = mTargetWindow-> + DispatchDragDropEvent(this, mTargetWindowPoint, mTargetTime); + + // Perhaps we should set the del parameter to TRUE when the drag + // action is move, but we don't know whether the data was successfully + // transferred. + gtk_drag_finish(mTargetDragContext, success, + /* del = */ FALSE, mTargetTime); + + // This drag is over, so clear out our reference to the previous + // window. + mTargetWindow = nsnull; + // Make sure to end the drag session. If this drag started in a + // different app, we won't get a drag_end signal to end it from. + EndDragSession(true); + } + + // We're done with the drag context. + mTargetWidget = NULL; + mTargetDragContext = NULL; + + // If we got another drag signal while running the sheduled task, that + // must have happened while running a nested event loop. Leave the task + // source on the event loop. + if (mScheduledTask != eDragTaskNone) + return TRUE; + + // We have no task scheduled. + // Returning false removes the task source from the event loop. + mTaskSource = 0; + return FALSE; +} diff --git a/widget/gtk2/nsDragService.h b/widget/gtk2/nsDragService.h index 0f9ab6bbdcbf..a883667b9eb5 100644 --- a/widget/gtk2/nsDragService.h +++ b/widget/gtk2/nsDragService.h @@ -46,6 +46,30 @@ #include "nsIObserver.h" #include +class nsWindow; + +#ifndef HAVE_NSGOBJECTREFTRAITS +#define HAVE_NSGOBJECTREFTRAITS +template +class nsGObjectRefTraits : public nsPointerRefTraits { +public: + static void Release(T *aPtr) { g_object_unref(aPtr); } + static void AddRef(T *aPtr) { g_object_ref(aPtr); } +}; +#endif + +#ifndef HAVE_NSAUTOREFTRAITS_GTKWIDGET +#define HAVE_NSAUTOREFTRAITS_GTKWIDGET +template <> +class nsAutoRefTraits : public nsGObjectRefTraits { }; +#endif + +#ifndef HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT +#define HAVE_NSAUTOREFTRAITS_GDKDRAGCONTEXT +template <> +class nsAutoRefTraits : + public nsGObjectRefTraits { }; +#endif /** * Native GTK DragService wrapper @@ -100,6 +124,25 @@ public: static nsDragService* GetInstance(); + // Methods called from nsWindow to handle responding to GTK drag + // destination signals + + gboolean ScheduleMotionEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, + guint aTime); + void ScheduleLeaveEvent(); + gboolean ScheduleDropEvent(nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, + guint aTime); + + nsWindow* GetMostRecentDestWindow() + { + return mScheduledTask == eDragTaskNone ? mTargetWindow + : mPendingWindow; + } + // END PUBLIC API // These methods are public only so that they can be called from functions @@ -118,14 +161,47 @@ public: private: - // target side vars + // mScheduledTask indicates what signal has been received from GTK and + // so what needs to be dispatched when the scheduled task is run. It is + // eDragTaskNone when there is no task scheduled (but the + // previous task may still not have finished running). + enum DragTask { + eDragTaskNone, + eDragTaskMotion, + eDragTaskLeave, + eDragTaskDrop + }; + DragTask mScheduledTask; + // mTaskSource is the GSource id for the task that is either scheduled + // or currently running. It is 0 if no task is scheduled or running. + guint mTaskSource; - // the last widget that was the target of a drag - GtkWidget *mTargetWidget; - GdkDragContext *mTargetDragContext; + // target/destination side vars + // These variables keep track of the state of the current drag. + + // mPendingWindow, mPendingWindowPoint, mPendingDragContext, and + // mPendingTime, carry information from the GTK signal that will be used + // when the scheduled task is run. mPendingWindow and mPendingDragContext + // will be NULL if the scheduled task is eDragTaskLeave. + nsRefPtr mPendingWindow; + nsIntPoint mPendingWindowPoint; + nsCountedRef mPendingDragContext; + guint mPendingTime; + + // mTargetWindow and mTargetWindowPoint record the position of the last + // eDragTaskMotion or eDragTaskDrop task that was run or is still running. + // mTargetWindow is cleared once the drag has completed or left. + nsRefPtr mTargetWindow; + nsIntPoint mTargetWindowPoint; + // mTargetWidget and mTargetDragContext are set only while dispatching + // motion or drop events. mTime records the corresponding timestamp. + nsCountedRef mTargetWidget; + nsCountedRef mTargetDragContext; guint mTargetTime; + // is it OK to drop on us? bool mCanDrop; + // have we received our drag data? bool mTargetDragDataReceived; // last data received and its length @@ -161,6 +237,13 @@ private: PRInt32 aYOffset, const nsIntRect &dragRect); + gboolean Schedule(DragTask aTask, nsWindow *aWindow, + GdkDragContext *aDragContext, + nsIntPoint aWindowPoint, guint aTime); + + // Callback for g_idle_add_full() to run mScheduledTask. + static gboolean TaskDispatchCallback(gpointer data); + gboolean RunScheduledTask(); }; #endif // nsDragService_h__ diff --git a/widget/gtk2/nsWindow.cpp b/widget/gtk2/nsWindow.cpp index c88fe0435f54..f5d9406a2f18 100644 --- a/widget/gtk2/nsWindow.cpp +++ b/widget/gtk2/nsWindow.cpp @@ -272,9 +272,6 @@ static void drag_data_received_event_cb(GtkWidget *aWidget, /* initialization static functions */ static nsresult initialize_prefs (void); -// this is the last window that had a drag event happen on it. -nsWindow *nsWindow::sLastDragMotionWindow = NULL; - // Time of the last button release event. We use it to detect when the // drag ended before we could properly setup drag and drop. static guint32 sLastButtonReleaseTime = 0; @@ -433,9 +430,6 @@ nsWindow::nsWindow() nsWindow::~nsWindow() { LOG(("nsWindow::~nsWindow() [%p]\n", (void *)this)); - if (sLastDragMotionWindow == this) { - sLastDragMotionWindow = NULL; - } delete[] mTransparencyBitmap; mTransparencyBitmap = nsnull; @@ -687,6 +681,11 @@ nsWindow::Destroy(void) gRollupListener = nsnull; } + nsDragService *dragService = nsDragService::GetInstance(); + if (this == dragService->GetMostRecentDestWindow()) { + dragService->ScheduleLeaveEvent(); + } + NativeShow(false); if (mIMModule) { @@ -715,11 +714,6 @@ nsWindow::Destroy(void) // the surface after its X Window. mThebesSurface = nsnull; - if (mDragLeaveTimer) { - mDragLeaveTimer->Cancel(); - mDragLeaveTimer = nsnull; - } - GtkWidget *owningWidget = GetMozContainerWidget(); if (mShell) { gtk_widget_destroy(mShell); @@ -3309,35 +3303,6 @@ nsWindow::ThemeChanged() } } -void -nsWindow::CheckNeedDragLeave(nsWindow* aInnerMostWidget, - nsIDragService* aDragService, - GdkDragContext *aDragContext, - nscoord aX, nscoord aY) -{ - // check to see if there was a drag motion window already in place - if (sLastDragMotionWindow) { - // same as the last window so no need for dragleave event - if (sLastDragMotionWindow == aInnerMostWidget) { - UpdateDragStatus(aDragContext, aDragService); - return; - } - - // send a dragleave event to the last window that got a motion event - nsRefPtr kungFuDeathGrip = sLastDragMotionWindow; - sLastDragMotionWindow->OnDragLeave(); - } - - // Make sure that the drag service knows we're now dragging - aDragService->StartDragSession(); - - // update our drag status - UpdateDragStatus(aDragContext, aDragService); - - // set the last window to the innerMostWidget - sLastDragMotionWindow = aInnerMostWidget; -} - void nsWindow::DispatchDragMotionEvents(nsDragService *aDragService, const nsIntPoint& aWindowPoint, guint aTime) @@ -3386,170 +3351,6 @@ nsWindow::DispatchDragEvent(PRUint32 aMsg, const nsIntPoint& aRefPoint, DispatchEvent(&event, status); } -gboolean -nsWindow::OnDragMotionEvent(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData) -{ - LOGDRAG(("nsWindow::OnDragMotionSignal\n")); - - // get our drag context - nsCOMPtr dragService = do_GetService(kCDragServiceCID); - nsDragService *dragServiceGTK = - static_cast(dragService.get()); - - // first, 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); - nsRefPtr innerMostWidget = get_window_for_gdk_window(innerWindow); - - if (!innerMostWidget) - innerMostWidget = this; - - // clear any drag leave timer that might be pending so that it - // doesn't get processed when we actually go out to get data. - if (mDragLeaveTimer) { - mDragLeaveTimer->Cancel(); - mDragLeaveTimer = nsnull; - } - - CheckNeedDragLeave(innerMostWidget, dragService, aDragContext, retx, rety); - - // update the drag context - dragServiceGTK->TargetSetLastContext(aWidget, aDragContext, aTime); - - innerMostWidget-> - DispatchDragMotionEvents(dragServiceGTK, nsIntPoint(retx, rety), aTime); - - // Reply to tell the source whether we can drop and what action would be - // taken. - dragServiceGTK->TargetEndDragMotion(aWidget, aDragContext, aTime); - - // and unset our context - dragServiceGTK->TargetSetLastContext(0, 0, 0); - - return TRUE; -} - -void -nsWindow::OnDragLeaveEvent(GtkWidget *aWidget, - GdkDragContext *aDragContext, - guint aTime, - gpointer aData) -{ - // XXX Do we want to pass this on only if the event's subwindow is null? - - LOGDRAG(("nsWindow::OnDragLeaveSignal(%p)\n", (void*)this)); - - if (mDragLeaveTimer) { - return; - } - - // create a fast timer - we're delaying the drag leave until the - // next mainloop in hopes that we might be able to get a drag drop - // signal - mDragLeaveTimer = do_CreateInstance("@mozilla.org/timer;1"); - NS_ASSERTION(mDragLeaveTimer, "Failed to create drag leave timer!"); - // fire this baby asafp, but not too quickly... see bug 216800 ;-) - mDragLeaveTimer->InitWithFuncCallback(DragLeaveTimerCallback, - (void *)this, - 20, nsITimer::TYPE_ONE_SHOT); -} - -gboolean -nsWindow::OnDragDropEvent(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData) - -{ - LOGDRAG(("nsWindow::OnDragDropSignal\n")); - - // get our drag context - nsCOMPtr dragService = do_GetService(kCDragServiceCID); - nsDragService *dragServiceGTK = static_cast(dragService.get()); - - dragServiceGTK->SetDragEndPoint(nsIntPoint(aX, aY) + WidgetToScreenOffset()); - - nscoord retx = 0; - nscoord rety = 0; - - GdkWindow *innerWindow = get_inner_gdk_window(gtk_widget_get_window(aWidget), aX, aY, - &retx, &rety); - nsRefPtr innerMostWidget = get_window_for_gdk_window(innerWindow); - - if (!innerMostWidget) - innerMostWidget = this; - - // clear any drag leave timer that might be pending so that it - // doesn't get processed when we actually go out to get data. - if (mDragLeaveTimer) { - mDragLeaveTimer->Cancel(); - mDragLeaveTimer = nsnull; - } - - CheckNeedDragLeave(innerMostWidget, dragService, aDragContext, retx, rety); - - // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model - // (as at 27 December 2010) indicates that a "drop" event should only be - // fired (at the current target element) if the current drag operation is - // not none. The current drag operation will only be set to a non-none - // value during a "dragover" event. - // - // If the user has ended the drag before any dragover events have been - // sent, then the spec recommends skipping the drop (because the current - // drag operation is none). However, here we assume that, by releasing - // the mouse button, the user has indicated that they want to drop, so we - // proceed with the drop where possible. - // - // In order to make the events appear to content in the same way as if the - // spec is being followed we make sure to dispatch a "dragover" event with - // appropriate coordinates and check canDrop before the "drop" event. - // - // When the Xdnd protocol is used for source/destination communication (as - // should be the case with GTK source applications) a dragover event - // should have already been sent during the drag-motion signal, which - // would have already been received because XdndDrop messages do not - // contain a position. However, we can't assume the same when the Motif - // protocol is used. - - dragServiceGTK->TargetSetLastContext(aWidget, aDragContext, aTime); - - innerMostWidget-> - DispatchDragMotionEvents(dragServiceGTK, nsIntPoint(retx, rety), aTime); - - gboolean success = innerMostWidget-> - DispatchDragDropEvent(dragServiceGTK, nsIntPoint(retx, rety), aTime); - - // before we unset the context we need to do a drop_finish - - gdk_drop_finish(aDragContext, success, aTime); - - // after a drop takes place we need to make sure that the drag - // service doesn't think that it still has a context. if the other - // way ( besides the drop ) to end a drag event is during the leave - // event and and that case is handled in that handler. - dragServiceGTK->TargetSetLastContext(0, 0, 0); - - // clear the sLastDragMotion window - sLastDragMotionWindow = 0; - - // Make sure to end the drag session. If this drag started in a - // different app, we won't get a drag_end signal to end it from. - dragService->EndDragSession(true); - - return TRUE; -} - void nsWindow::OnDragDataReceivedEvent(GtkWidget *aWidget, GdkDragContext *aDragContext, @@ -5910,7 +5711,7 @@ nsWindow::InitDragEvent(nsDragEvent &aEvent) // drag context. Gtk gets this from a combination of the key settings // and what the source is offering. -void +/* static */ void nsWindow::UpdateDragStatus(GdkDragContext *aDragContext, nsIDragService *aDragService) { @@ -5973,9 +5774,24 @@ drag_motion_event_cb(GtkWidget *aWidget, } } - return window->OnDragMotionEvent(aWidget, - aDragContext, - aX, aY, aTime, aData); + // 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); + nsRefPtr innerMostWindow = get_window_for_gdk_window(innerWindow); + + if (!innerMostWindow) { + innerMostWindow = window; + } + + LOGDRAG(("nsWindow drag-motion signal for %p\n", (void*)innerMostWindow)); + + return nsDragService::GetInstance()-> + ScheduleMotionEvent(innerMostWindow, aDragContext, + nsIntPoint(aX, aY), aTime); } static void @@ -5988,7 +5804,27 @@ drag_leave_event_cb(GtkWidget *aWidget, if (!window) return; - window->OnDragLeaveEvent(aWidget, aDragContext, aTime, aData); + nsDragService *dragService = nsDragService::GetInstance(); + + nsWindow *mostRecentDragWindow = dragService->GetMostRecentDestWindow(); + if (!mostRecentDragWindow) { + NS_WARNING("Spurious drag leave signal"); + 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(); } @@ -6004,9 +5840,24 @@ drag_drop_event_cb(GtkWidget *aWidget, if (!window) return FALSE; - return window->OnDragDropEvent(aWidget, - aDragContext, - aX, aY, aTime, aData); + // 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); + nsRefPtr innerMostWindow = get_window_for_gdk_window(innerWindow); + + if (!innerMostWindow) { + innerMostWindow = window; + } + + LOGDRAG(("nsWindow drag-drop signal for %p\n", (void*)innerMostWindow)); + + return nsDragService::GetInstance()-> + ScheduleDropEvent(innerMostWindow, aDragContext, + nsIntPoint(aX, aY), aTime); } static void @@ -6041,30 +5892,6 @@ initialize_prefs(void) return NS_OK; } -void -nsWindow::FireDragLeaveTimer(void) -{ - LOGDRAG(("nsWindow::FireDragLeaveTimer(%p)\n", (void*)this)); - - mDragLeaveTimer = nsnull; - - // clean up any pending drag motion window info - if (sLastDragMotionWindow) { - nsRefPtr kungFuDeathGrip = sLastDragMotionWindow; - // send our leave signal - sLastDragMotionWindow->OnDragLeave(); - sLastDragMotionWindow = 0; - } -} - -/* static */ -void -nsWindow::DragLeaveTimerCallback(nsITimer *aTimer, void *aClosure) -{ - nsRefPtr window = static_cast(aClosure); - window->FireDragLeaveTimer(); -} - static GdkWindow * get_inner_gdk_window (GdkWindow *aWindow, gint x, gint y, diff --git a/widget/gtk2/nsWindow.h b/widget/gtk2/nsWindow.h index 11b1c963505b..d8e35e1bfd94 100644 --- a/widget/gtk2/nsWindow.h +++ b/widget/gtk2/nsWindow.h @@ -237,22 +237,6 @@ public: GdkEventVisibility *aEvent); void OnWindowStateEvent(GtkWidget *aWidget, GdkEventWindowState *aEvent); - gboolean OnDragMotionEvent(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData); - void OnDragLeaveEvent(GtkWidget * aWidget, - GdkDragContext *aDragContext, - guint aTime, - gpointer aData); - gboolean OnDragDropEvent(GtkWidget *aWidget, - GdkDragContext *aDragContext, - gint aX, - gint aY, - guint aTime, - gpointer aData); void OnDragDataReceivedEvent(GtkWidget *aWidget, GdkDragContext *aDragContext, gint aX, @@ -297,11 +281,6 @@ public: void ThemeChanged(void); - void CheckNeedDragLeave(nsWindow* aInnerMostWidget, - nsIDragService* aDragService, - GdkDragContext *aDragContext, - nscoord aX, nscoord aY); - #ifdef MOZ_X11 Window mOldFocusWindow; #endif /* MOZ_X11 */ @@ -312,6 +291,9 @@ public: NS_IMETHOD BeginMoveDrag(nsMouseEvent* aEvent); MozContainer* GetMozContainer() { return mContainer; } + // GetMozContainerWidget returns the MozContainer even for undestroyed + // descendant windows + GtkWidget* GetMozContainerWidget(); GdkWindow* GetGdkWindow() { return mGdkWindow; } bool IsDestroyed() { return mIsDestroyed; } @@ -324,6 +306,8 @@ public: gboolean DispatchDragDropEvent(nsDragService *aDragService, const nsIntPoint& aWindowPoint, guint aTime); + static void UpdateDragStatus (GdkDragContext *aDragContext, + nsIDragService *aDragService); // If this dispatched the keydown event actually, this returns TRUE, // otherwise, FALSE. bool DispatchKeyDownEvent(GdkEventKey *aEvent, @@ -393,7 +377,6 @@ protected: private: void DestroyChildWindows(); void GetToplevelWidget(GtkWidget **aWidget); - GtkWidget *GetMozContainerWidget(); nsWindow *GetContainerWindow(); void SetUrgencyHint(GtkWidget *top_window, bool state); void *SetupPluginPort(void); @@ -493,13 +476,8 @@ private: gchar* mTransparencyBitmap; // all of our DND stuff - // this is the last window that had a drag event happen on it. - static nsWindow *sLastDragMotionWindow; void InitDragEvent (nsDragEvent &aEvent); - void UpdateDragStatus (GdkDragContext *aDragContext, - nsIDragService *aDragService); - nsCOMPtr mDragLeaveTimer; float mLastMotionPressure; // Remember the last sizemode so that we can restore it when @@ -508,9 +486,6 @@ private: static bool DragInProgress(void); - void FireDragLeaveTimer (void); - static void DragLeaveTimerCallback (nsITimer *aTimer, void *aClosure); - void DispatchMissedButtonReleases(GdkEventCrossing *aGdkEvent); /**