Bug 1731737 [Linux] Call nsDragService::RunScheduledTask() event handler faster and disable its re-entrance r=emilio

We sometimes miss reply to D&D motion event due to delayed DnsDragService::RunScheduledTask() call.
We can't call D&D handler directly from drag-motion event as it causes nested recursions and re-entrance
in glib loop so use higher priority for the delayed handler call at least.

Disable re-entrance of nsDragService::RunScheduledTask() which may happen when
we get D&D data from drag_drop event.

Provide more loggig to D&D code.

Differential Revision: https://phabricator.services.mozilla.com/D126182
This commit is contained in:
stransky
2021-09-21 13:30:44 +00:00
parent 4f58131e06
commit dcadf2b1b3
3 changed files with 73 additions and 16 deletions

View File

@@ -30,6 +30,7 @@
#include "mozilla/ClearOnShutdown.h" #include "mozilla/ClearOnShutdown.h"
#include "mozilla/PresShell.h" #include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h" #include "mozilla/ScopeExit.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/WidgetUtilsGtk.h" #include "mozilla/WidgetUtilsGtk.h"
#include "GRefPtr.h" #include "GRefPtr.h"
@@ -112,7 +113,8 @@ static void invisibleSourceDragDataGet(GtkWidget* aWidget,
nsDragService::nsDragService() nsDragService::nsDragService()
: mScheduledTask(eDragTaskNone), : mScheduledTask(eDragTaskNone),
mTaskSource(0) mTaskSource(0),
mScheduledTaskIsRunning(false)
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
, ,
mPendingWaylandDataOffer(nullptr), mPendingWaylandDataOffer(nullptr),
@@ -481,6 +483,8 @@ nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
mTargetWaylandDataOfferForRemote = nullptr; mTargetWaylandDataOfferForRemote = nullptr;
#endif #endif
mTargetWindow = nullptr;
mPendingWindow = nullptr;
return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers); return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
} }
@@ -978,6 +982,7 @@ void nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) {
} }
} }
LOGDRAGSERVICE((" gdk_drag_status() action %d", action));
gdk_drag_status(aDragContext, action, mTargetTime); gdk_drag_status(aDragContext, action, mTargetTime);
} }
@@ -1142,7 +1147,7 @@ void nsDragService::GetTargetDragData(GdkAtom aFlavor) {
} }
} }
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
else { else if (mTargetWaylandDataOffer) {
mTargetDragData = mTargetWaylandDataOffer->GetDragData( mTargetDragData = mTargetWaylandDataOffer->GetDragData(
gdk_atom_name(aFlavor), &mTargetDragDataLen); gdk_atom_name(aFlavor), &mTargetDragDataLen);
mTargetDragDataReceived = true; mTargetDragDataReceived = true;
@@ -1847,6 +1852,16 @@ gboolean nsDragService::ScheduleDropEvent(nsWindow* aWindow,
return TRUE; return TRUE;
} }
#ifdef MOZ_LOGGING
const char* nsDragService::GetDragServiceTaskName(DragTask aTask) {
static const char* taskNames[] = {"eDragTaskNone", "eDragTaskMotion",
"eDragTaskLeave", "eDragTaskDrop",
"eDragTaskSourceEnd"};
MOZ_ASSERT(size_t(aTask) < ArrayLength(taskNames));
return taskNames[aTask];
}
#endif
gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow, gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
GdkDragContext* aDragContext, GdkDragContext* aDragContext,
RefPtr<DataOffer> aWaylandDataOffer, RefPtr<DataOffer> aWaylandDataOffer,
@@ -1861,9 +1876,15 @@ gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
// within the allowed time). Otherwise, if we haven't yet run a scheduled // within the allowed time). Otherwise, if we haven't yet run a scheduled
// drop or end task, just say that we are not ready to receive another // drop or end task, just say that we are not ready to receive another
// drop. // drop.
LOGDRAGSERVICE(("nsDragService::Schedule() task %s window %p\n",
GetDragServiceTaskName(aTask), aWindow));
if (mScheduledTask == eDragTaskSourceEnd || if (mScheduledTask == eDragTaskSourceEnd ||
(mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) {
LOGDRAGSERVICE((" task does not fit recent task %s, quit!\n",
GetDragServiceTaskName(mScheduledTask)));
return FALSE; return FALSE;
}
mScheduledTask = aTask; mScheduledTask = aTask;
mPendingWindow = aWindow; mPendingWindow = aWindow;
@@ -1875,14 +1896,15 @@ gboolean nsDragService::Schedule(DragTask aTask, nsWindow* aWindow,
mPendingTime = aTime; mPendingTime = aTime;
if (!mTaskSource) { if (!mTaskSource) {
// High priority is used here because the native events involved have // High priority is used here because we want to process motion events
// already waited at default priority. Perhaps a lower than default // right after drag_motion event handler which is called by Gtk.
// priority could be used for motion tasks because there is a chance // An ideal scenario is to call TaskDispatchCallback() directly here
// that a leave or drop is waiting, but managing different priorities // but we can't do that. TaskDispatchCallback() spins gtk event loop
// may not be worth the effort. Motion tasks shouldn't queue up as // while nsDragService::Schedule() is already called from event loop
// they should be throttled based on replies. // (by drag_motion* gtk_widget events) so that direct call will cause
mTaskSource = // nested recursion.
g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, this, nullptr); mTaskSource = g_timeout_add_full(G_PRIORITY_HIGH, 0, TaskDispatchCallback,
this, nullptr);
} }
return TRUE; return TRUE;
} }
@@ -1893,9 +1915,22 @@ gboolean nsDragService::TaskDispatchCallback(gpointer data) {
} }
gboolean nsDragService::RunScheduledTask() { gboolean nsDragService::RunScheduledTask() {
LOGDRAGSERVICE(
("nsDragService::RunScheduledTask() task %s mTargetWindow %p "
"mPendingWindow %p\n",
GetDragServiceTaskName(mScheduledTask), mTargetWindow.get(),
mPendingWindow.get()));
// Don't run RunScheduledTask() twice. As we use it in main thread only
// we don't need to be thread safe here.
if (mScheduledTaskIsRunning) {
return FALSE;
}
AutoRestore<bool> guard(mScheduledTaskIsRunning);
mScheduledTaskIsRunning = true;
if (mTargetWindow && mTargetWindow != mPendingWindow) { if (mTargetWindow && mTargetWindow != mPendingWindow) {
LOGDRAGSERVICE( LOGDRAGSERVICE((" dispatch eDragExit (%p)\n", mTargetWindow.get()));
("nsDragService: dispatch drag leave (%p)\n", mTargetWindow.get()));
mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0); mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0);
if (!mSourceNode) { if (!mSourceNode) {
@@ -1921,6 +1956,7 @@ gboolean nsDragService::RunScheduledTask() {
mTargetWindowPoint = mPendingWindowPoint; mTargetWindowPoint = mPendingWindowPoint;
if (task == eDragTaskLeave || task == eDragTaskSourceEnd) { if (task == eDragTaskLeave || task == eDragTaskSourceEnd) {
LOGDRAGSERVICE((" quit, task %s\n", GetDragServiceTaskName(task)));
if (task == eDragTaskSourceEnd) { if (task == eDragTaskSourceEnd) {
// Dispatch drag end events. // Dispatch drag end events.
EndDragSession(true, GetCurrentModifiers()); EndDragSession(true, GetCurrentModifiers());
@@ -1939,7 +1975,11 @@ gboolean nsDragService::RunScheduledTask() {
// (The leave event is not scheduled if a drop task is still scheduled.) // (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 // We still reply appropriately to indicate that the drop will or didn't
// succeeed. // succeeed.
mTargetWidget = mTargetWindow->GetMozContainerWidget(); mTargetWidget =
mTargetWindow ? mTargetWindow->GetMozContainerWidget() : nullptr;
LOGDRAGSERVICE((" start drag session mTargetWindow %p mTargetWidget %p\n",
mTargetWindow.get(), mTargetWidget.get()));
mTargetDragContext = std::move(mPendingDragContext); mTargetDragContext = std::move(mPendingDragContext);
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
mTargetWaylandDataOffer = std::move(mPendingWaylandDataOffer); mTargetWaylandDataOffer = std::move(mPendingWaylandDataOffer);
@@ -1971,6 +2011,7 @@ gboolean nsDragService::RunScheduledTask() {
// contain a position. However, we can't assume the same when the Motif // contain a position. However, we can't assume the same when the Motif
// protocol is used. // protocol is used.
if (task == eDragTaskMotion || positionHasChanged) { if (task == eDragTaskMotion || positionHasChanged) {
LOGDRAGSERVICE((" process motion event\n"));
UpdateDragAction(); UpdateDragAction();
TakeDragEventDispatchedToChildProcess(); // Clear the old value. TakeDragEventDispatchedToChildProcess(); // Clear the old value.
DispatchMotionEvents(); DispatchMotionEvents();
@@ -1996,12 +2037,14 @@ gboolean nsDragService::RunScheduledTask() {
} }
if (task == eDragTaskDrop) { if (task == eDragTaskDrop) {
LOGDRAGSERVICE((" process drop task\n"));
gboolean success = DispatchDropEvent(); gboolean success = DispatchDropEvent();
// Perhaps we should set the del parameter to TRUE when the drag // 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 // action is move, but we don't know whether the data was successfully
// transferred. // transferred.
if (mTargetDragContext) { if (mTargetDragContext) {
LOGDRAGSERVICE((" drag finished\n"));
gtk_drag_finish(mTargetDragContext, success, gtk_drag_finish(mTargetDragContext, success,
/* del = */ FALSE, mTargetTime); /* del = */ FALSE, mTargetTime);
} }
@@ -2015,6 +2058,7 @@ gboolean nsDragService::RunScheduledTask() {
} }
// We're done with the drag context. // We're done with the drag context.
LOGDRAGSERVICE((" clear mTargetWindow mTargetWidget and other data\n"));
mTargetWidget = nullptr; mTargetWidget = nullptr;
mTargetDragContext = nullptr; mTargetDragContext = nullptr;
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
@@ -2030,6 +2074,7 @@ gboolean nsDragService::RunScheduledTask() {
// We have no task scheduled. // We have no task scheduled.
// Returning false removes the task source from the event loop. // Returning false removes the task source from the event loop.
LOGDRAGSERVICE((" remove task source\n"));
mTaskSource = 0; mTaskSource = 0;
return FALSE; return FALSE;
} }
@@ -2045,6 +2090,7 @@ void nsDragService::UpdateDragAction() {
// more appropriate. GdkDragContext::actions should be used to set // more appropriate. GdkDragContext::actions should be used to set
// dataTransfer.effectAllowed, which doesn't currently happen with // dataTransfer.effectAllowed, which doesn't currently happen with
// external sources. // external sources.
LOGDRAGSERVICE(("nsDragService::UpdateDragAction()\n"));
// default is to do nothing // default is to do nothing
int action = nsIDragService::DRAGDROP_ACTION_NONE; int action = nsIDragService::DRAGDROP_ACTION_NONE;
@@ -2080,6 +2126,8 @@ void nsDragService::UpdateDragAction() {
NS_IMETHODIMP NS_IMETHODIMP
nsDragService::UpdateDragEffect() { nsDragService::UpdateDragEffect() {
LOGDRAGSERVICE(
("nsDragService::UpdateDragEffect() from e10s child process\n"));
if (mTargetDragContextForRemote) { if (mTargetDragContextForRemote) {
ReplyToDragMotion(mTargetDragContextForRemote); ReplyToDragMotion(mTargetDragContextForRemote);
mTargetDragContextForRemote = nullptr; mTargetDragContextForRemote = nullptr;

View File

@@ -101,6 +101,7 @@ class nsDragService final : public nsBaseDragService, public nsIObserver {
// set the drag icon during drag-begin // set the drag icon during drag-begin
void SetDragIcon(GdkDragContext* aContext); void SetDragIcon(GdkDragContext* aContext);
gboolean IsDragActive() { return mScheduledTask != eDragTaskNone; }
protected: protected:
virtual ~nsDragService(); virtual ~nsDragService();
@@ -121,6 +122,7 @@ class nsDragService final : public nsBaseDragService, public nsIObserver {
// mTaskSource is the GSource id for the task that is either scheduled // 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. // or currently running. It is 0 if no task is scheduled or running.
guint mTaskSource; guint mTaskSource;
bool mScheduledTaskIsRunning;
// target/destination side vars // target/destination side vars
// These variables keep track of the state of the current drag. // These variables keep track of the state of the current drag.
@@ -207,6 +209,9 @@ class nsDragService final : public nsBaseDragService, public nsIObserver {
void ReplyToDragMotion(GdkDragContext* aDragContext); void ReplyToDragMotion(GdkDragContext* aDragContext);
#ifdef MOZ_WAYLAND #ifdef MOZ_WAYLAND
void ReplyToDragMotion(RefPtr<DataOffer> aDragContext); void ReplyToDragMotion(RefPtr<DataOffer> aDragContext);
#endif
#ifdef MOZ_LOGGING
const char* GetDragServiceTaskName(nsDragService::DragTask aTask);
#endif #endif
gboolean DispatchDropEvent(); gboolean DispatchDropEvent();
static uint32_t GetCurrentModifiers(); static uint32_t GetCurrentModifiers();

View File

@@ -8154,14 +8154,18 @@ static gboolean drag_motion_event_cb(GtkWidget* aWidget,
void WindowDragLeaveHandler(GtkWidget* aWidget) { void WindowDragLeaveHandler(GtkWidget* aWidget) {
LOGDRAG(("WindowDragLeaveHandler()\n")); LOGDRAG(("WindowDragLeaveHandler()\n"));
RefPtr<nsDragService> dragService = nsDragService::GetInstance();
if (!dragService->IsDragActive()) {
LOGDRAG((" Already finished.\n"));
return;
}
RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget); RefPtr<nsWindow> window = get_window_for_gtk_widget(aWidget);
if (!window) { if (!window) {
LOGDRAG((" Failed - can't find nsWindow!\n")); LOGDRAG((" Failed - can't find nsWindow!\n"));
return; return;
} }
RefPtr<nsDragService> dragService = nsDragService::GetInstance();
nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow(); nsWindow* mostRecentDragWindow = dragService->GetMostRecentDestWindow();
if (!mostRecentDragWindow) { if (!mostRecentDragWindow) {
// This can happen when the target will not accept a drop. A GTK drag // This can happen when the target will not accept a drop. A GTK drag