1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=4 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsDragService.h"
8 #include "nsArrayUtils.h"
9 #include "nsIObserverService.h"
10 #include "nsWidgetsCID.h"
12 #include "nsSystemInfo.h"
14 #include "nsICookieJarSettings.h"
15 #include "nsISupportsPrimitives.h"
16 #include "nsIIOService.h"
17 #include "nsIFileURL.h"
18 #include "nsNetUtil.h"
19 #include "mozilla/Logging.h"
21 #include "nsPrimitiveHelpers.h"
28 #include "mozilla/BasicEvents.h"
29 #include "mozilla/Services.h"
30 #include "mozilla/ClearOnShutdown.h"
31 #include "mozilla/PresShell.h"
32 #include "mozilla/ScopeExit.h"
33 #include "mozilla/WidgetUtilsGtk.h"
36 #include "gfxXlibSurface.h"
37 #include "gfxContext.h"
38 #include "nsImageToPixbuf.h"
39 #include "nsPresContext.h"
40 #include "nsIContent.h"
41 #include "mozilla/dom/Document.h"
42 #include "nsViewManager.h"
44 #include "nsGtkUtils.h"
45 #include "nsGtkKeyUtils.h"
46 #include "mozilla/gfx/2D.h"
47 #include "gfxPlatform.h"
48 #include "ScreenHelperGTK.h"
49 #include "nsArrayUtils.h"
51 # include "nsClipboardWayland.h"
52 # include "gfxPlatformGtk.h"
55 using namespace mozilla
;
56 using namespace mozilla::gfx
;
58 #define NS_SYSTEMINFO_CONTRACTID "@mozilla.org/system-info;1"
60 // This sets how opaque the drag image is
61 #define DRAG_IMAGE_ALPHA_LEVEL 0.5
63 // These values are copied from GtkDragResult (rather than using GtkDragResult
64 // directly) so that this code can be compiled against versions of GTK+ that
65 // do not have GtkDragResult.
66 // GtkDragResult is available from GTK+ version 2.12.
68 MOZ_GTK_DRAG_RESULT_SUCCESS
,
69 MOZ_GTK_DRAG_RESULT_NO_TARGET
,
70 MOZ_GTK_DRAG_RESULT_USER_CANCELLED
,
71 MOZ_GTK_DRAG_RESULT_TIMEOUT_EXPIRED
,
72 MOZ_GTK_DRAG_RESULT_GRAB_BROKEN
,
73 MOZ_GTK_DRAG_RESULT_ERROR
78 extern mozilla::LazyLogModule gWidgetDragLog
;
79 # define LOG(args) MOZ_LOG(gWidgetDragLog, mozilla::LogLevel::Debug, args)
84 // data used for synthetic periodic motion events sent to the source widget
85 // grabbing real events for the drag.
86 static guint sMotionEventTimerID
;
87 static GdkEvent
* sMotionEvent
;
88 static GtkWidget
* sGrabWidget
;
90 static const char gMimeListType
[] = "application/x-moz-internal-item-list";
91 static const char gMozUrlType
[] = "_NETSCAPE_URL";
92 static const char gTextUriListType
[] = "text/uri-list";
93 static const char gTextPlainUTF8Type
[] = "text/plain;charset=utf-8";
94 static const char gXdndDirectSaveType
[] = "XdndDirectSave0";
95 static const char gTabDropType
[] = "application/x-moz-tabbrowser-tab";
97 static void invisibleSourceDragBegin(GtkWidget
* aWidget
,
98 GdkDragContext
* aContext
, gpointer aData
);
100 static void invisibleSourceDragEnd(GtkWidget
* aWidget
, GdkDragContext
* aContext
,
103 static gboolean
invisibleSourceDragFailed(GtkWidget
* aWidget
,
104 GdkDragContext
* aContext
,
105 gint aResult
, gpointer aData
);
107 static void invisibleSourceDragDataGet(GtkWidget
* aWidget
,
108 GdkDragContext
* aContext
,
109 GtkSelectionData
* aSelectionData
,
110 guint aInfo
, guint32 aTime
,
113 nsDragService::nsDragService()
114 : mScheduledTask(eDragTaskNone
),
118 mPendingWaylandDragContext(nullptr),
119 mTargetWaylandDragContext(nullptr)
122 // We have to destroy the hidden widget before the event loop stops
124 nsCOMPtr
<nsIObserverService
> obsServ
=
125 mozilla::services::GetObserverService();
126 obsServ
->AddObserver(this, "quit-application", false);
128 // our hidden source widget
129 // Using an offscreen window works around bug 983843.
130 mHiddenWidget
= gtk_offscreen_window_new();
131 // make sure that the widget is realized so that
132 // we can use it as a drag source.
133 gtk_widget_realize(mHiddenWidget
);
134 // hook up our internal signals so that we can get some feedback
135 // from our drag source
136 g_signal_connect(mHiddenWidget
, "drag_begin",
137 G_CALLBACK(invisibleSourceDragBegin
), this);
138 g_signal_connect(mHiddenWidget
, "drag_data_get",
139 G_CALLBACK(invisibleSourceDragDataGet
), this);
140 g_signal_connect(mHiddenWidget
, "drag_end",
141 G_CALLBACK(invisibleSourceDragEnd
), this);
142 // drag-failed is available from GTK+ version 2.12
144 g_signal_lookup("drag-failed", G_TYPE_FROM_INSTANCE(mHiddenWidget
));
146 g_signal_connect_closure_by_id(
147 mHiddenWidget
, dragFailedID
, 0,
148 g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed
), this, nullptr),
152 // set up our logging module
153 LOG(("nsDragService::nsDragService"));
155 mTargetDragDataReceived
= false;
157 mTargetDragDataLen
= 0;
160 nsDragService::~nsDragService() {
161 LOG(("nsDragService::~nsDragService"));
162 if (mTaskSource
) g_source_remove(mTaskSource
);
165 NS_IMPL_ISUPPORTS_INHERITED(nsDragService
, nsBaseDragService
, nsIObserver
)
167 mozilla::StaticRefPtr
<nsDragService
> sDragServiceInstance
;
169 already_AddRefed
<nsDragService
> nsDragService::GetInstance() {
170 if (gfxPlatform::IsHeadless()) {
173 if (!sDragServiceInstance
) {
174 sDragServiceInstance
= new nsDragService();
175 ClearOnShutdown(&sDragServiceInstance
);
178 RefPtr
<nsDragService
> service
= sDragServiceInstance
.get();
179 return service
.forget();
185 nsDragService::Observe(nsISupports
* aSubject
, const char* aTopic
,
186 const char16_t
* aData
) {
187 if (!nsCRT::strcmp(aTopic
, "quit-application")) {
188 LOG(("nsDragService::Observe(\"quit-application\")"));
190 gtk_widget_destroy(mHiddenWidget
);
195 MOZ_ASSERT_UNREACHABLE("unexpected topic");
196 return NS_ERROR_UNEXPECTED
;
202 // Support for periodic drag events
204 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
205 // and the Xdnd protocol both recommend that drag events are sent periodically,
206 // but GTK does not normally provide this.
208 // Here GTK is periodically stimulated by copies of the most recent mouse
209 // motion events so as to send drag position messages to the destination when
210 // appropriate (after it has received a status event from the previous
213 // (If events were sent only on the destination side then the destination
214 // would have no message to which it could reply with a drag status. Without
215 // sending a drag status to the source, the destination would not be able to
216 // change its feedback re whether it could accept the drop, and so the
217 // source's behavior on drop will not be consistent.)
219 static gboolean
DispatchMotionEventCopy(gpointer aData
) {
220 // Clear the timer id before OnSourceGrabEventAfter is called during event
222 sMotionEventTimerID
= 0;
224 GdkEvent
* event
= sMotionEvent
;
225 sMotionEvent
= nullptr;
226 // If there is no longer a grab on the widget, then the drag is over and
227 // there is no need to continue drag motion.
228 if (gtk_widget_has_grab(sGrabWidget
)) {
229 gtk_propagate_event(sGrabWidget
, event
);
231 gdk_event_free(event
);
233 // Cancel this timer;
234 // We've already started another if the motion event was dispatched.
238 static void OnSourceGrabEventAfter(GtkWidget
* widget
, GdkEvent
* event
,
239 gpointer user_data
) {
240 // If there is no longer a grab on the widget, then the drag motion is
241 // over (though the data may not be fetched yet).
242 if (!gtk_widget_has_grab(sGrabWidget
)) return;
244 if (event
->type
== GDK_MOTION_NOTIFY
) {
246 gdk_event_free(sMotionEvent
);
248 sMotionEvent
= gdk_event_copy(event
);
250 // Update the cursor position. The last of these recorded gets used for
251 // the eDragEnd event.
252 nsDragService
* dragService
= static_cast<nsDragService
*>(user_data
);
253 gint scale
= mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
254 auto p
= LayoutDeviceIntPoint::Round(event
->motion
.x_root
* scale
,
255 event
->motion
.y_root
* scale
);
256 dragService
->SetDragEndPoint(p
);
257 } else if (sMotionEvent
&&
258 (event
->type
== GDK_KEY_PRESS
|| event
->type
== GDK_KEY_RELEASE
)) {
259 // Update modifier state from key events.
260 sMotionEvent
->motion
.state
= event
->key
.state
;
265 if (sMotionEventTimerID
) {
266 g_source_remove(sMotionEventTimerID
);
269 // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source
270 // and lower than GTK's idle source that sends drag position messages after
271 // motion-notify signals.
273 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
274 // recommends an interval of 350ms +/- 200ms.
275 sMotionEventTimerID
= g_timeout_add_full(
276 G_PRIORITY_DEFAULT_IDLE
, 350, DispatchMotionEventCopy
, nullptr, nullptr);
279 static GtkWindow
* GetGtkWindow(dom::Document
* aDocument
) {
280 if (!aDocument
) return nullptr;
282 PresShell
* presShell
= aDocument
->GetPresShell();
287 RefPtr
<nsViewManager
> vm
= presShell
->GetViewManager();
288 if (!vm
) return nullptr;
290 nsCOMPtr
<nsIWidget
> widget
;
291 vm
->GetRootWidget(getter_AddRefs(widget
));
292 if (!widget
) return nullptr;
294 GtkWidget
* gtkWidget
=
295 static_cast<nsWindow
*>(widget
.get())->GetMozContainerWidget();
296 if (!gtkWidget
) return nullptr;
298 GtkWidget
* toplevel
= nullptr;
299 toplevel
= gtk_widget_get_toplevel(gtkWidget
);
300 if (!GTK_IS_WINDOW(toplevel
)) return nullptr;
302 return GTK_WINDOW(toplevel
);
308 nsDragService::InvokeDragSession(
309 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
310 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aArrayTransferables
,
311 uint32_t aActionType
,
312 nsContentPolicyType aContentPolicyType
= nsIContentPolicy::TYPE_OTHER
) {
313 LOG(("nsDragService::InvokeDragSession"));
315 // If the previous source drag has not yet completed, signal handlers need
316 // to be removed from sGrabWidget and dragend needs to be dispatched to
317 // the source node, but we can't call EndDragSession yet because we don't
318 // know whether or not the drag succeeded.
319 if (mSourceNode
) return NS_ERROR_NOT_AVAILABLE
;
321 return nsBaseDragService::InvokeDragSession(
322 aDOMNode
, aPrincipal
, aCsp
, aCookieJarSettings
, aArrayTransferables
,
323 aActionType
, aContentPolicyType
);
327 nsresult
nsDragService::InvokeDragSessionImpl(
328 nsIArray
* aArrayTransferables
, const Maybe
<CSSIntRegion
>& aRegion
,
329 uint32_t aActionType
) {
330 // make sure that we have an array of transferables to use
331 if (!aArrayTransferables
) return NS_ERROR_INVALID_ARG
;
332 // set our reference to the transferables. this will also addref
333 // the transferables since we're going to hang onto this beyond the
334 // length of this call
335 mSourceDataItems
= aArrayTransferables
;
336 // get the list of items we offer for drags
337 GtkTargetList
* sourceList
= GetSourceList();
339 if (!sourceList
) return NS_OK
;
341 // save our action type
342 GdkDragAction action
= GDK_ACTION_DEFAULT
;
344 if (aActionType
& DRAGDROP_ACTION_COPY
)
345 action
= (GdkDragAction
)(action
| GDK_ACTION_COPY
);
346 if (aActionType
& DRAGDROP_ACTION_MOVE
)
347 action
= (GdkDragAction
)(action
| GDK_ACTION_MOVE
);
348 if (aActionType
& DRAGDROP_ACTION_LINK
)
349 action
= (GdkDragAction
)(action
| GDK_ACTION_LINK
);
351 // Create a fake event for the drag so we can pass the time (so to speak).
352 // If we don't do this, then, when the timestamp for the pending button
353 // release event is used for the ungrab, the ungrab can fail due to the
354 // timestamp being _earlier_ than CurrentTime.
356 memset(&event
, 0, sizeof(GdkEvent
));
357 event
.type
= GDK_BUTTON_PRESS
;
358 event
.button
.window
= gtk_widget_get_window(mHiddenWidget
);
359 event
.button
.time
= nsWindow::GetLastUserInputTime();
361 // Put the drag widget in the window group of the source node so that the
362 // gtk_grab_add during gtk_drag_begin is effective.
363 // gtk_window_get_group(nullptr) returns the default window group.
364 GtkWindowGroup
* window_group
=
365 gtk_window_get_group(GetGtkWindow(mSourceDocument
));
366 gtk_window_group_add_window(window_group
, GTK_WINDOW(mHiddenWidget
));
368 // Get device for event source
369 GdkDisplay
* display
= gdk_display_get_default();
370 GdkDeviceManager
* device_manager
= gdk_display_get_device_manager(display
);
371 event
.button
.device
= gdk_device_manager_get_client_pointer(device_manager
);
374 GdkDragContext
* context
=
375 gtk_drag_begin(mHiddenWidget
, sourceList
, action
, 1, &event
);
381 // GTK uses another hidden window for receiving mouse events.
382 sGrabWidget
= gtk_window_group_get_current_grab(window_group
);
384 g_object_ref(sGrabWidget
);
385 // Only motion and key events are required but connect to
386 // "event-after" as this is never blocked by other handlers.
387 g_signal_connect(sGrabWidget
, "event-after",
388 G_CALLBACK(OnSourceGrabEventAfter
), this);
390 // We don't have a drag end point yet.
391 mEndDragPoint
= LayoutDeviceIntPoint(-1, -1);
394 rv
= NS_ERROR_FAILURE
;
397 gtk_target_list_unref(sourceList
);
402 bool nsDragService::SetAlphaPixmap(SourceSurface
* aSurface
,
403 GdkDragContext
* aContext
, int32_t aXOffset
,
405 const LayoutDeviceIntRect
& dragRect
) {
406 GdkScreen
* screen
= gtk_widget_get_screen(mHiddenWidget
);
408 // Transparent drag icons need, like a lot of transparency-related things,
409 // a compositing X window manager
410 if (!gdk_screen_is_composited(screen
)) return false;
412 #ifdef cairo_image_surface_create
413 # error "Looks like we're including Mozilla's cairo instead of system cairo"
416 // TODO: grab X11 pixmap or image data instead of expensive readback.
417 cairo_surface_t
* surf
= cairo_image_surface_create(
418 CAIRO_FORMAT_ARGB32
, dragRect
.width
, dragRect
.height
);
419 if (!surf
) return false;
421 RefPtr
<DrawTarget
> dt
= gfxPlatform::CreateDrawTargetForData(
422 cairo_image_surface_get_data(surf
),
423 nsIntSize(dragRect
.width
, dragRect
.height
),
424 cairo_image_surface_get_stride(surf
), SurfaceFormat::B8G8R8A8
);
425 if (!dt
) return false;
427 dt
->ClearRect(Rect(0, 0, dragRect
.width
, dragRect
.height
));
429 aSurface
, Rect(0, 0, dragRect
.width
, dragRect
.height
),
430 Rect(0, 0, dragRect
.width
, dragRect
.height
), DrawSurfaceOptions(),
431 DrawOptions(DRAG_IMAGE_ALPHA_LEVEL
, CompositionOp::OP_SOURCE
));
433 cairo_surface_mark_dirty(surf
);
434 cairo_surface_set_device_offset(surf
, -aXOffset
, -aYOffset
);
436 // Ensure that the surface is drawn at the correct scale on HiDPI displays.
437 static auto sCairoSurfaceSetDeviceScalePtr
=
438 (void (*)(cairo_surface_t
*, double, double))dlsym(
439 RTLD_DEFAULT
, "cairo_surface_set_device_scale");
440 if (sCairoSurfaceSetDeviceScalePtr
) {
441 gint scale
= mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
442 sCairoSurfaceSetDeviceScalePtr(surf
, scale
, scale
);
445 gtk_drag_set_icon_surface(aContext
, surf
);
446 cairo_surface_destroy(surf
);
451 nsDragService::StartDragSession() {
452 LOG(("nsDragService::StartDragSession"));
453 return nsBaseDragService::StartDragSession();
457 nsDragService::EndDragSession(bool aDoneDrag
, uint32_t aKeyModifiers
) {
458 LOG(("nsDragService::EndDragSession %d", aDoneDrag
));
461 g_signal_handlers_disconnect_by_func(
462 sGrabWidget
, FuncToGpointer(OnSourceGrabEventAfter
), this);
463 g_object_unref(sGrabWidget
);
464 sGrabWidget
= nullptr;
466 if (sMotionEventTimerID
) {
467 g_source_remove(sMotionEventTimerID
);
468 sMotionEventTimerID
= 0;
471 gdk_event_free(sMotionEvent
);
472 sMotionEvent
= nullptr;
476 // unset our drag action
477 SetDragAction(DRAGDROP_ACTION_NONE
);
479 // We're done with the drag context.
480 mTargetDragContextForRemote
= nullptr;
482 mTargetWaylandDragContextForRemote
= nullptr;
485 return nsBaseDragService::EndDragSession(aDoneDrag
, aKeyModifiers
);
490 nsDragService::SetCanDrop(bool aCanDrop
) {
491 LOG(("nsDragService::SetCanDrop %d", aCanDrop
));
497 nsDragService::GetCanDrop(bool* aCanDrop
) {
498 LOG(("nsDragService::GetCanDrop"));
499 *aCanDrop
= mCanDrop
;
503 static void UTF16ToNewUTF8(const char16_t
* aUTF16
, uint32_t aUTF16Len
,
504 char** aUTF8
, uint32_t* aUTF8Len
) {
505 nsDependentSubstring
utf16(aUTF16
, aUTF16Len
);
506 *aUTF8
= ToNewUTF8String(utf16
, aUTF8Len
);
509 static void UTF8ToNewUTF16(const char* aUTF8
, uint32_t aUTF8Len
,
510 char16_t
** aUTF16
, uint32_t* aUTF16Len
) {
511 nsDependentCSubstring
utf8(aUTF8
, aUTF8Len
);
512 *aUTF16
= UTF8ToNewUnicode(utf8
, aUTF16Len
);
515 // count the number of URIs in some text/uri-list format data.
516 static uint32_t CountTextUriListItems(const char* data
, uint32_t datalen
) {
517 const char* p
= data
;
518 const char* endPtr
= p
+ datalen
;
522 // skip whitespace (if any)
523 while (p
< endPtr
&& *p
!= '\0' && isspace(*p
)) p
++;
524 // if we aren't at the end of the line ...
525 if (p
!= endPtr
&& *p
!= '\0' && *p
!= '\n' && *p
!= '\r') count
++;
526 // skip to the end of the line
527 while (p
< endPtr
&& *p
!= '\0' && *p
!= '\n') p
++;
528 p
++; // skip the actual newline as well.
533 // extract an item from text/uri-list formatted data and convert it to
535 static void GetTextUriListItem(const char* data
, uint32_t datalen
,
536 uint32_t aItemIndex
, char16_t
** convertedText
,
537 uint32_t* convertedTextLen
) {
538 const char* p
= data
;
539 const char* endPtr
= p
+ datalen
;
540 unsigned int count
= 0;
542 *convertedText
= nullptr;
544 // skip whitespace (if any)
545 while (p
< endPtr
&& *p
!= '\0' && isspace(*p
)) p
++;
546 // if we aren't at the end of the line, we have a url
547 if (p
!= endPtr
&& *p
!= '\0' && *p
!= '\n' && *p
!= '\r') count
++;
548 // this is the item we are after ...
549 if (aItemIndex
+ 1 == count
) {
551 while (q
< endPtr
&& *q
!= '\0' && *q
!= '\n' && *q
!= '\r') q
++;
552 UTF8ToNewUTF16(p
, q
- p
, convertedText
, convertedTextLen
);
555 // skip to the end of the line
556 while (p
< endPtr
&& *p
!= '\0' && *p
!= '\n') p
++;
557 p
++; // skip the actual newline as well.
560 // didn't find the desired item, so just pass the whole lot
561 if (!*convertedText
) {
562 UTF8ToNewUTF16(data
, datalen
, convertedText
, convertedTextLen
);
567 nsDragService::GetNumDropItems(uint32_t* aNumItems
) {
568 LOG(("nsDragService::GetNumDropItems"));
570 if (!mTargetWidget
) {
572 ("*** warning: GetNumDropItems \
573 called without a valid target widget!\n"));
578 bool isList
= IsTargetContextList();
580 mSourceDataItems
->GetLength(aNumItems
);
582 GdkAtom gdkFlavor
= gdk_atom_intern(gTextUriListType
, FALSE
);
583 GetTargetDragData(gdkFlavor
);
584 if (mTargetDragData
) {
585 const char* data
= reinterpret_cast<char*>(mTargetDragData
);
586 *aNumItems
= CountTextUriListItems(data
, mTargetDragDataLen
);
590 LOG(("%d items", *aNumItems
));
595 nsDragService::GetData(nsITransferable
* aTransferable
, uint32_t aItemIndex
) {
596 LOG(("nsDragService::GetData %d", aItemIndex
));
598 // make sure that we have a transferable
599 if (!aTransferable
) return NS_ERROR_INVALID_ARG
;
601 if (!mTargetWidget
) {
603 ("*** warning: GetData \
604 called without a valid target widget!\n"));
605 return NS_ERROR_FAILURE
;
608 // get flavor list that includes all acceptable flavors (including
609 // ones obtained through conversion).
610 nsTArray
<nsCString
> flavors
;
611 nsresult rv
= aTransferable
->FlavorsTransferableCanImport(flavors
);
612 if (NS_FAILED(rv
)) return rv
;
614 // check to see if this is an internal list
615 bool isList
= IsTargetContextList();
618 LOG(("it's a list..."));
619 // find a matching flavor
620 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
621 nsCString
& flavorStr
= flavors
[i
];
622 LOG(("flavor is %s\n", flavorStr
.get()));
623 // get the item with the right index
624 nsCOMPtr
<nsITransferable
> item
=
625 do_QueryElementAt(mSourceDataItems
, aItemIndex
);
628 nsCOMPtr
<nsISupports
> data
;
629 LOG(("trying to get transfer data for %s\n", flavorStr
.get()));
630 rv
= item
->GetTransferData(flavorStr
.get(), getter_AddRefs(data
));
635 LOG(("succeeded.\n"));
636 rv
= aTransferable
->SetTransferData(flavorStr
.get(), data
);
638 LOG(("fail to set transfer data into transferable!\n"));
641 // ok, we got the data
644 // if we got this far, we failed
645 return NS_ERROR_FAILURE
;
648 // Now walk down the list of flavors. When we find one that is
649 // actually present, copy out the data into the transferable in that
650 // format. SetTransferData() implicitly handles conversions.
651 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
652 nsCString
& flavorStr
= flavors
[i
];
653 GdkAtom gdkFlavor
= gdk_atom_intern(flavorStr
.get(), FALSE
);
654 LOG(("looking for data in type %s, gdk flavor %p\n", flavorStr
.get(),
656 bool dataFound
= false;
658 GetTargetDragData(gdkFlavor
);
660 if (mTargetDragData
) {
661 LOG(("dataFound = true\n"));
664 LOG(("dataFound = false\n"));
666 // Dragging and dropping from the file manager would cause us
667 // to parse the source text as a nsIFile URL.
668 if (flavorStr
.EqualsLiteral(kFileMime
)) {
669 gdkFlavor
= gdk_atom_intern(kTextMime
, FALSE
);
670 GetTargetDragData(gdkFlavor
);
671 if (!mTargetDragData
) {
672 gdkFlavor
= gdk_atom_intern(gTextUriListType
, FALSE
);
673 GetTargetDragData(gdkFlavor
);
675 if (mTargetDragData
) {
676 const char* text
= static_cast<char*>(mTargetDragData
);
677 char16_t
* convertedText
= nullptr;
678 uint32_t convertedTextLen
= 0;
680 GetTextUriListItem(text
, mTargetDragDataLen
, aItemIndex
,
681 &convertedText
, &convertedTextLen
);
684 nsCOMPtr
<nsIIOService
> ioService
= do_GetIOService(&rv
);
685 nsCOMPtr
<nsIURI
> fileURI
;
686 rv
= ioService
->NewURI(NS_ConvertUTF16toUTF8(convertedText
),
687 nullptr, nullptr, getter_AddRefs(fileURI
));
688 if (NS_SUCCEEDED(rv
)) {
689 nsCOMPtr
<nsIFileURL
> fileURL
= do_QueryInterface(fileURI
, &rv
);
690 if (NS_SUCCEEDED(rv
)) {
691 nsCOMPtr
<nsIFile
> file
;
692 rv
= fileURL
->GetFile(getter_AddRefs(file
));
693 if (NS_SUCCEEDED(rv
)) {
694 // The common wrapping code at the end of
695 // this function assumes the data is text
696 // and calls text-specific operations.
697 // Make a secret hideout here for nsIFile
698 // objects and return early.
699 aTransferable
->SetTransferData(flavorStr
.get(), file
);
700 g_free(convertedText
);
705 g_free(convertedText
);
711 // if we are looking for text/unicode and we fail to find it
712 // on the clipboard first, try again with text/plain. If that
713 // is present, convert it to unicode.
714 if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
716 ("we were looking for text/unicode... \
717 trying with text/plain;charset=utf-8\n"));
718 gdkFlavor
= gdk_atom_intern(gTextPlainUTF8Type
, FALSE
);
719 GetTargetDragData(gdkFlavor
);
720 if (mTargetDragData
) {
721 LOG(("Got textplain data\n"));
722 const char* castedText
= reinterpret_cast<char*>(mTargetDragData
);
723 char16_t
* convertedText
= nullptr;
724 NS_ConvertUTF8toUTF16
ucs2string(castedText
, mTargetDragDataLen
);
725 convertedText
= ToNewUnicode(ucs2string
, mozilla::fallible
);
727 LOG(("successfully converted plain text to unicode.\n"));
728 // out with the old, in with the new
729 g_free(mTargetDragData
);
730 mTargetDragData
= convertedText
;
731 mTargetDragDataLen
= ucs2string
.Length() * 2;
733 } // if plain text data on clipboard
736 ("we were looking for text/unicode... \
737 trying again with text/plain\n"));
738 gdkFlavor
= gdk_atom_intern(kTextMime
, FALSE
);
739 GetTargetDragData(gdkFlavor
);
740 if (mTargetDragData
) {
741 LOG(("Got textplain data\n"));
742 const char* castedText
= reinterpret_cast<char*>(mTargetDragData
);
743 char16_t
* convertedText
= nullptr;
744 uint32_t convertedTextLen
= 0;
745 UTF8ToNewUTF16(castedText
, mTargetDragDataLen
, &convertedText
,
748 LOG(("successfully converted plain text to unicode.\n"));
749 // out with the old, in with the new
750 g_free(mTargetDragData
);
751 mTargetDragData
= convertedText
;
752 mTargetDragDataLen
= convertedTextLen
* 2;
754 } // if plain text data on clipboard
755 } // if plain text flavor present
756 } // if plain text charset=utf-8 flavor present
757 } // if looking for text/unicode
759 // if we are looking for text/x-moz-url and we failed to find
760 // it on the clipboard, try again with text/uri-list, and then
762 if (flavorStr
.EqualsLiteral(kURLMime
)) {
764 ("we were looking for text/x-moz-url...\
765 trying again with text/uri-list\n"));
766 gdkFlavor
= gdk_atom_intern(gTextUriListType
, FALSE
);
767 GetTargetDragData(gdkFlavor
);
768 if (mTargetDragData
) {
769 LOG(("Got text/uri-list data\n"));
770 const char* data
= reinterpret_cast<char*>(mTargetDragData
);
771 char16_t
* convertedText
= nullptr;
772 uint32_t convertedTextLen
= 0;
774 GetTextUriListItem(data
, mTargetDragDataLen
, aItemIndex
,
775 &convertedText
, &convertedTextLen
);
778 LOG(("successfully converted _NETSCAPE_URL to unicode.\n"));
779 // out with the old, in with the new
780 g_free(mTargetDragData
);
781 mTargetDragData
= convertedText
;
782 mTargetDragDataLen
= convertedTextLen
* 2;
786 LOG(("failed to get text/uri-list data\n"));
790 ("we were looking for text/x-moz-url...\
791 trying again with _NETSCAP_URL\n"));
792 gdkFlavor
= gdk_atom_intern(gMozUrlType
, FALSE
);
793 GetTargetDragData(gdkFlavor
);
794 if (mTargetDragData
) {
795 LOG(("Got _NETSCAPE_URL data\n"));
796 const char* castedText
= reinterpret_cast<char*>(mTargetDragData
);
797 char16_t
* convertedText
= nullptr;
798 uint32_t convertedTextLen
= 0;
799 UTF8ToNewUTF16(castedText
, mTargetDragDataLen
, &convertedText
,
803 ("successfully converted _NETSCAPE_URL \
805 // out with the old, in with the new
806 g_free(mTargetDragData
);
807 mTargetDragData
= convertedText
;
808 mTargetDragDataLen
= convertedTextLen
* 2;
812 LOG(("failed to get _NETSCAPE_URL data\n"));
817 } // else we try one last ditch effort to find our data
820 if (!flavorStr
.EqualsLiteral(kCustomTypesMime
)) {
821 // the DOM only wants LF, so convert from MacOS line endings
822 // to DOM line endings.
823 nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks(
824 flavorStr
, &mTargetDragData
,
825 reinterpret_cast<int*>(&mTargetDragDataLen
));
828 // put it into the transferable.
829 nsCOMPtr
<nsISupports
> genericDataWrapper
;
830 nsPrimitiveHelpers::CreatePrimitiveForData(
831 flavorStr
, mTargetDragData
, mTargetDragDataLen
,
832 getter_AddRefs(genericDataWrapper
));
833 aTransferable
->SetTransferData(flavorStr
.get(), genericDataWrapper
);
834 // we found one, get out of this loop!
835 LOG(("dataFound and converted!\n"));
844 nsDragService::IsDataFlavorSupported(const char* aDataFlavor
, bool* _retval
) {
845 LOG(("nsDragService::IsDataFlavorSupported %s", aDataFlavor
));
847 return NS_ERROR_INVALID_ARG
;
850 // set this to no by default
853 // check to make sure that we have a drag object set, here
854 if (!mTargetWidget
) {
856 ("*** warning: IsDataFlavorSupported \
857 called without a valid target widget!\n"));
861 // check to see if the target context is a list.
862 bool isList
= IsTargetContextList();
863 // if it is, just look in the internal data since we are the source
866 LOG(("It's a list.."));
867 uint32_t numDragItems
= 0;
868 // if we don't have mDataItems we didn't start this drag so it's
869 // an external client trying to fool us.
870 if (!mSourceDataItems
) return NS_OK
;
871 mSourceDataItems
->GetLength(&numDragItems
);
872 for (uint32_t itemIndex
= 0; itemIndex
< numDragItems
; ++itemIndex
) {
873 nsCOMPtr
<nsITransferable
> currItem
=
874 do_QueryElementAt(mSourceDataItems
, itemIndex
);
876 nsTArray
<nsCString
> flavors
;
877 currItem
->FlavorsTransferableCanExport(flavors
);
878 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
879 LOG(("checking %s against %s\n", flavors
[i
].get(), aDataFlavor
));
880 if (flavors
[i
].Equals(aDataFlavor
)) {
881 LOG(("boioioioiooioioioing!\n"));
890 // check the target context vs. this flavor, one at a time
891 GList
* tmp
= nullptr;
892 if (mTargetDragContext
) {
893 tmp
= gdk_drag_context_list_targets(mTargetDragContext
);
896 else if (mTargetWaylandDragContext
) {
897 tmp
= mTargetWaylandDragContext
->GetTargets();
899 GList
* tmp_head
= tmp
;
902 for (; tmp
; tmp
= tmp
->next
) {
904 GdkAtom atom
= GDK_POINTER_TO_ATOM(tmp
->data
);
905 gchar
* name
= nullptr;
906 name
= gdk_atom_name(atom
);
907 LOG(("checking %s against %s\n", name
, aDataFlavor
));
908 if (name
&& (strcmp(name
, aDataFlavor
) == 0)) {
912 // check for automatic text/uri-list -> text/x-moz-url mapping
913 if (!*_retval
&& name
&& (strcmp(name
, gTextUriListType
) == 0) &&
914 (strcmp(aDataFlavor
, kURLMime
) == 0 ||
915 strcmp(aDataFlavor
, kFileMime
) == 0)) {
917 ("good! ( it's text/uri-list and \
918 we're checking against text/x-moz-url )\n"));
921 // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping
922 if (!*_retval
&& name
&& (strcmp(name
, gMozUrlType
) == 0) &&
923 (strcmp(aDataFlavor
, kURLMime
) == 0)) {
925 ("good! ( it's _NETSCAPE_URL and \
926 we're checking against text/x-moz-url )\n"));
929 // check for auto text/plain -> text/unicode mapping
930 if (!*_retval
&& name
&& (strcmp(name
, kTextMime
) == 0) &&
931 ((strcmp(aDataFlavor
, kUnicodeMime
) == 0) ||
932 (strcmp(aDataFlavor
, kFileMime
) == 0))) {
934 ("good! ( it's text plain and we're checking \
935 against text/unicode or application/x-moz-file)\n"));
942 // mTargetWaylandDragContext->GetTargets allocates the list
943 // so we need to free it here.
944 if (!mTargetDragContext
&& tmp_head
) {
945 g_list_free(tmp_head
);
952 void nsDragService::ReplyToDragMotion(GdkDragContext
* aDragContext
) {
953 LOG(("nsDragService::ReplyToDragMotion %d", mCanDrop
));
955 GdkDragAction action
= (GdkDragAction
)0;
957 // notify the dragger if we can drop
958 switch (mDragAction
) {
959 case DRAGDROP_ACTION_COPY
:
960 action
= GDK_ACTION_COPY
;
962 case DRAGDROP_ACTION_LINK
:
963 action
= GDK_ACTION_LINK
;
965 case DRAGDROP_ACTION_NONE
:
966 action
= (GdkDragAction
)0;
969 action
= GDK_ACTION_MOVE
;
974 gdk_drag_status(aDragContext
, action
, mTargetTime
);
978 void nsDragService::ReplyToDragMotion(nsWaylandDragContext
* aDragContext
) {
979 LOG(("nsDragService::ReplyToDragMotion %d", mCanDrop
));
981 GdkDragAction action
= (GdkDragAction
)0;
983 // notify the dragger if we can drop
984 switch (mDragAction
) {
985 case DRAGDROP_ACTION_COPY
:
986 action
= GDK_ACTION_COPY
;
988 case DRAGDROP_ACTION_LINK
:
989 action
= GDK_ACTION_LINK
;
991 case DRAGDROP_ACTION_NONE
:
992 action
= (GdkDragAction
)0;
995 action
= GDK_ACTION_MOVE
;
1000 aDragContext
->SetDragStatus(action
);
1004 void nsDragService::TargetDataReceived(GtkWidget
* aWidget
,
1005 GdkDragContext
* aContext
, gint aX
,
1007 GtkSelectionData
* aSelectionData
,
1008 guint aInfo
, guint32 aTime
) {
1009 LOG(("nsDragService::TargetDataReceived"));
1012 mTargetDragDataReceived
= true;
1013 gint len
= gtk_selection_data_get_length(aSelectionData
);
1014 const guchar
* data
= gtk_selection_data_get_data(aSelectionData
);
1016 GdkAtom target
= gtk_selection_data_get_target(aSelectionData
);
1017 char* name
= gdk_atom_name(target
);
1018 nsCString
flavor(name
);
1021 if (len
> 0 && data
) {
1022 mTargetDragDataLen
= len
;
1023 mTargetDragData
= g_malloc(mTargetDragDataLen
);
1024 memcpy(mTargetDragData
, data
, mTargetDragDataLen
);
1026 nsTArray
<uint8_t> copy
;
1027 if (!copy
.SetLength(len
, fallible
)) {
1030 memcpy(copy
.Elements(), data
, len
);
1032 mCachedData
.InsertOrUpdate(flavor
, std::move(copy
));
1034 LOG(("Failed to get data. selection data len was %d\n",
1035 mTargetDragDataLen
));
1037 mCachedData
.InsertOrUpdate(flavor
, nsTArray
<uint8_t>());
1041 bool nsDragService::IsTargetContextList(void) {
1042 bool retval
= false;
1044 // gMimeListType drags only work for drags within a single process. The
1045 // gtk_drag_get_source_widget() function will return nullptr if the source
1046 // of the drag is another app, so we use it to check if a gMimeListType
1047 // drop will work or not.
1048 if (mTargetDragContext
&&
1049 gtk_drag_get_source_widget(mTargetDragContext
) == nullptr) {
1053 GList
* tmp
= nullptr;
1054 if (mTargetDragContext
) {
1055 tmp
= gdk_drag_context_list_targets(mTargetDragContext
);
1058 GList
* tmp_head
= nullptr;
1059 if (mTargetWaylandDragContext
) {
1060 tmp_head
= tmp
= mTargetWaylandDragContext
->GetTargets();
1064 // walk the list of context targets and see if one of them is a list
1066 for (; tmp
; tmp
= tmp
->next
) {
1068 GdkAtom atom
= GDK_POINTER_TO_ATOM(tmp
->data
);
1069 gchar
* name
= nullptr;
1070 name
= gdk_atom_name(atom
);
1071 if (name
&& strcmp(name
, gMimeListType
) == 0) retval
= true;
1077 // mTargetWaylandDragContext->GetTargets allocates the list
1078 // so we need to free it here.
1079 if (mTargetWaylandDragContext
&& tmp_head
) {
1080 g_list_free(tmp_head
);
1087 // Maximum time to wait for a "drag_received" arrived, in microseconds
1088 #define NS_DND_TIMEOUT 500000
1090 void nsDragService::GetTargetDragData(GdkAtom aFlavor
) {
1091 LOG(("getting data flavor %p\n", aFlavor
));
1092 LOG(("mLastWidget is %p and mLastContext is %p\n", mTargetWidget
.get(),
1093 mTargetDragContext
.get()));
1094 // reset our target data areas
1097 if (mTargetDragContext
) {
1098 char* name
= gdk_atom_name(aFlavor
);
1099 nsCString
flavor(name
);
1102 // We keep a copy of the requested data with the same life-time
1103 // as mTargetDragContext.
1104 // Especially with multiple items the same data is requested
1106 if (auto cached
= mCachedData
.Lookup(flavor
)) {
1107 mTargetDragDataLen
= cached
->Length();
1108 LOG(("Using cached data for %s, length is %d", flavor
.get(),
1109 mTargetDragDataLen
));
1111 if (mTargetDragDataLen
) {
1112 mTargetDragData
= g_malloc(mTargetDragDataLen
);
1113 memcpy(mTargetDragData
, cached
->Elements(), mTargetDragDataLen
);
1116 mTargetDragDataReceived
= true;
1120 gtk_drag_get_data(mTargetWidget
, mTargetDragContext
, aFlavor
, mTargetTime
);
1122 LOG(("about to start inner iteration."));
1123 PRTime entryTime
= PR_Now();
1124 while (!mTargetDragDataReceived
&& mDoingDrag
) {
1125 // check the number of iterations
1126 LOG(("doing iteration...\n"));
1127 PR_Sleep(20 * PR_TicksPerSecond() / 1000); /* sleep for 20 ms/iteration */
1128 if (PR_Now() - entryTime
> NS_DND_TIMEOUT
) break;
1129 gtk_main_iteration();
1134 mTargetDragData
= mTargetWaylandDragContext
->GetData(gdk_atom_name(aFlavor
),
1135 &mTargetDragDataLen
);
1136 mTargetDragDataReceived
= true;
1139 LOG(("finished inner iteration\n"));
1142 void nsDragService::TargetResetData(void) {
1143 mTargetDragDataReceived
= false;
1144 // make sure to free old data if we have to
1145 g_free(mTargetDragData
);
1146 mTargetDragData
= 0;
1147 mTargetDragDataLen
= 0;
1150 GtkTargetList
* nsDragService::GetSourceList(void) {
1151 if (!mSourceDataItems
) return nullptr;
1152 nsTArray
<GtkTargetEntry
*> targetArray
;
1153 GtkTargetEntry
* targets
;
1154 GtkTargetList
* targetList
= 0;
1155 uint32_t targetCount
= 0;
1156 unsigned int numDragItems
= 0;
1158 mSourceDataItems
->GetLength(&numDragItems
);
1160 // Check to see if we're dragging > 1 item.
1161 if (numDragItems
> 1) {
1162 // as the Xdnd protocol only supports a single item (or is it just
1163 // gtk's implementation?), we don't advertise all flavours listed
1164 // in the nsITransferable.
1166 // the application/x-moz-internal-item-list format, which preserves
1167 // all information for drags within the same mozilla instance.
1168 GtkTargetEntry
* listTarget
=
1169 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1170 listTarget
->target
= g_strdup(gMimeListType
);
1171 listTarget
->flags
= 0;
1172 LOG(("automatically adding target %s\n", listTarget
->target
));
1173 targetArray
.AppendElement(listTarget
);
1175 // check what flavours are supported so we can decide what other
1176 // targets to advertise.
1177 nsCOMPtr
<nsITransferable
> currItem
= do_QueryElementAt(mSourceDataItems
, 0);
1180 nsTArray
<nsCString
> flavors
;
1181 currItem
->FlavorsTransferableCanExport(flavors
);
1182 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
1183 // check if text/x-moz-url is supported.
1186 if (flavors
[i
].EqualsLiteral(kURLMime
)) {
1187 listTarget
= (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1188 listTarget
->target
= g_strdup(gTextUriListType
);
1189 listTarget
->flags
= 0;
1190 LOG(("automatically adding target %s\n", listTarget
->target
));
1191 targetArray
.AppendElement(listTarget
);
1194 } // if item is a transferable
1195 } else if (numDragItems
== 1) {
1196 nsCOMPtr
<nsITransferable
> currItem
= do_QueryElementAt(mSourceDataItems
, 0);
1198 nsTArray
<nsCString
> flavors
;
1199 currItem
->FlavorsTransferableCanExport(flavors
);
1200 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
1201 nsCString
& flavorStr
= flavors
[i
];
1203 GtkTargetEntry
* target
=
1204 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1205 target
->target
= g_strdup(flavorStr
.get());
1207 LOG(("adding target %s\n", target
->target
));
1208 targetArray
.AppendElement(target
);
1210 // If there is a file, add the text/uri-list type.
1211 if (flavorStr
.EqualsLiteral(kFileMime
)) {
1212 GtkTargetEntry
* urilistTarget
=
1213 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1214 urilistTarget
->target
= g_strdup(gTextUriListType
);
1215 urilistTarget
->flags
= 0;
1216 LOG(("automatically adding target %s\n", urilistTarget
->target
));
1217 targetArray
.AppendElement(urilistTarget
);
1219 // Check to see if this is text/unicode.
1220 // If it is, add text/plain
1221 // since we automatically support text/plain
1222 // if we support text/unicode.
1223 else if (flavorStr
.EqualsLiteral(kUnicodeMime
)) {
1224 GtkTargetEntry
* plainUTF8Target
=
1225 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1226 plainUTF8Target
->target
= g_strdup(gTextPlainUTF8Type
);
1227 plainUTF8Target
->flags
= 0;
1228 LOG(("automatically adding target %s\n", plainUTF8Target
->target
));
1229 targetArray
.AppendElement(plainUTF8Target
);
1231 GtkTargetEntry
* plainTarget
=
1232 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1233 plainTarget
->target
= g_strdup(kTextMime
);
1234 plainTarget
->flags
= 0;
1235 LOG(("automatically adding target %s\n", plainTarget
->target
));
1236 targetArray
.AppendElement(plainTarget
);
1238 // Check to see if this is the x-moz-url type.
1239 // If it is, add _NETSCAPE_URL
1240 // this is a type used by everybody.
1241 else if (flavorStr
.EqualsLiteral(kURLMime
)) {
1242 GtkTargetEntry
* urlTarget
=
1243 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1244 urlTarget
->target
= g_strdup(gMozUrlType
);
1245 urlTarget
->flags
= 0;
1246 LOG(("automatically adding target %s\n", urlTarget
->target
));
1247 targetArray
.AppendElement(urlTarget
);
1250 else if (flavorStr
.EqualsLiteral(kFilePromiseMime
)) {
1251 GtkTargetEntry
* directsaveTarget
=
1252 (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
));
1253 directsaveTarget
->target
= g_strdup(gXdndDirectSaveType
);
1254 directsaveTarget
->flags
= 0;
1255 LOG(("automatically adding target %s\n", directsaveTarget
->target
));
1256 targetArray
.AppendElement(directsaveTarget
);
1262 // get all the elements that we created.
1263 targetCount
= targetArray
.Length();
1265 // allocate space to create the list of valid targets
1266 targets
= (GtkTargetEntry
*)g_malloc(sizeof(GtkTargetEntry
) * targetCount
);
1267 uint32_t targetIndex
;
1268 for (targetIndex
= 0; targetIndex
< targetCount
; ++targetIndex
) {
1269 GtkTargetEntry
* disEntry
= targetArray
.ElementAt(targetIndex
);
1270 // this is a string reference but it will be freed later.
1271 targets
[targetIndex
].target
= disEntry
->target
;
1272 targets
[targetIndex
].flags
= disEntry
->flags
;
1273 targets
[targetIndex
].info
= 0;
1275 targetList
= gtk_target_list_new(targets
, targetCount
);
1276 // clean up the target list
1277 for (uint32_t cleanIndex
= 0; cleanIndex
< targetCount
; ++cleanIndex
) {
1278 GtkTargetEntry
* thisTarget
= targetArray
.ElementAt(cleanIndex
);
1279 g_free(thisTarget
->target
);
1284 // We need to create a dummy target list to be able initialize dnd.
1285 targetList
= gtk_target_list_new(nullptr, 0);
1290 void nsDragService::SourceEndDragSession(GdkDragContext
* aContext
,
1292 LOG(("SourceEndDragSession result %d\n", aResult
));
1294 // this just releases the list of data items that we provide
1295 mSourceDataItems
= nullptr;
1297 // Remove this property, if it exists, to satisfy the Direct Save Protocol.
1298 GdkAtom property
= gdk_atom_intern(gXdndDirectSaveType
, FALSE
);
1299 gdk_property_delete(gdk_drag_context_get_source_window(aContext
), property
);
1301 if (!mDoingDrag
|| mScheduledTask
== eDragTaskSourceEnd
)
1302 // EndDragSession() was already called on drop
1303 // or SourceEndDragSession on drag-failed
1306 if (mEndDragPoint
.x
< 0) {
1307 // We don't have a drag end point, so guess
1309 GdkDisplay
* display
= gdk_display_get_default();
1311 gint scale
= mozilla::widget::ScreenHelperGTK::GetGTKMonitorScaleFactor();
1312 gdk_display_get_pointer(display
, nullptr, &x
, &y
, nullptr);
1313 SetDragEndPoint(LayoutDeviceIntPoint(x
* scale
, y
* scale
));
1314 LOG(("guess drag end point %d %d\n", x
* scale
, y
* scale
));
1318 // Either the drag was aborted or the drop occurred outside the app.
1319 // The dropEffect of mDataTransfer is not updated for motion outside the
1320 // app, but is needed for the dragend event, so set it now.
1322 uint32_t dropEffect
;
1324 if (aResult
== MOZ_GTK_DRAG_RESULT_SUCCESS
) {
1325 // With GTK+ versions 2.10.x and prior the drag may have been
1326 // cancelled (but no drag-failed signal would have been sent).
1327 // aContext->dest_window will be non-nullptr only if the drop was
1329 GdkDragAction action
= gdk_drag_context_get_dest_window(aContext
)
1330 ? gdk_drag_context_get_actions(aContext
)
1333 // Only one bit of action should be set, but, just in case someone
1334 // does something funny, erring away from MOVE, and not recording
1335 // unusual action combinations as NONE.
1337 dropEffect
= DRAGDROP_ACTION_NONE
;
1338 else if (action
& GDK_ACTION_COPY
)
1339 dropEffect
= DRAGDROP_ACTION_COPY
;
1340 else if (action
& GDK_ACTION_LINK
)
1341 dropEffect
= DRAGDROP_ACTION_LINK
;
1342 else if (action
& GDK_ACTION_MOVE
)
1343 dropEffect
= DRAGDROP_ACTION_MOVE
;
1345 dropEffect
= DRAGDROP_ACTION_COPY
;
1348 dropEffect
= DRAGDROP_ACTION_NONE
;
1349 if (aResult
!= MOZ_GTK_DRAG_RESULT_NO_TARGET
) {
1350 LOG(("drop is user chancelled\n"));
1351 mUserCancelled
= true;
1355 if (mDataTransfer
) {
1356 mDataTransfer
->SetDropEffectInt(dropEffect
);
1359 // Schedule the appropriate drag end dom events.
1360 Schedule(eDragTaskSourceEnd
, nullptr, nullptr, nullptr,
1361 LayoutDeviceIntPoint(), 0);
1364 static void CreateURIList(nsIArray
* aItems
, nsACString
& aURIList
) {
1365 uint32_t length
= 0;
1366 aItems
->GetLength(&length
);
1368 for (uint32_t i
= 0; i
< length
; ++i
) {
1369 nsCOMPtr
<nsITransferable
> item
= do_QueryElementAt(aItems
, i
);
1374 nsCOMPtr
<nsISupports
> data
;
1375 nsresult rv
= item
->GetTransferData(kURLMime
, getter_AddRefs(data
));
1376 if (NS_SUCCEEDED(rv
)) {
1377 nsCOMPtr
<nsISupportsString
> string
= do_QueryInterface(data
);
1381 string
->GetData(text
);
1384 // text/x-moz-url is of form url + "\n" + title.
1385 // We just want the url.
1386 int32_t separatorPos
= text
.FindChar(u
'\n');
1387 if (separatorPos
>= 0) {
1388 text
.Truncate(separatorPos
);
1391 AppendUTF16toUTF8(text
, aURIList
);
1392 aURIList
.AppendLiteral("\r\n");
1396 // There is no URI available. If there is a file available, create
1397 // a URI from the file.
1398 rv
= item
->GetTransferData(kFileMime
, getter_AddRefs(data
));
1399 if (NS_SUCCEEDED(rv
)) {
1400 if (nsCOMPtr
<nsIFile
> file
= do_QueryInterface(data
)) {
1401 nsCOMPtr
<nsIURI
> fileURI
;
1402 NS_NewFileURI(getter_AddRefs(fileURI
), file
);
1405 fileURI
->GetSpec(spec
);
1407 aURIList
.Append(spec
);
1408 aURIList
.AppendLiteral("\r\n");
1415 void nsDragService::SourceDataGet(GtkWidget
* aWidget
, GdkDragContext
* aContext
,
1416 GtkSelectionData
* aSelectionData
,
1418 LOG(("nsDragService::SourceDataGet"));
1419 GdkAtom target
= gtk_selection_data_get_target(aSelectionData
);
1420 gchar
* typeName
= gdk_atom_name(target
);
1422 LOG(("failed to get atom name.\n"));
1426 LOG(("Type is %s\n", typeName
));
1427 auto freeTypeName
= mozilla::MakeScopeExit([&] { g_free(typeName
); });
1428 // check to make sure that we have data items to return.
1429 if (!mSourceDataItems
) {
1430 LOG(("Failed to get our data items\n"));
1434 nsDependentCSubstring
mimeFlavor(typeName
, strlen(typeName
));
1435 nsCOMPtr
<nsITransferable
> item
;
1436 item
= do_QueryElementAt(mSourceDataItems
, 0);
1438 // if someone was asking for text/plain, lookup unicode instead so
1439 // we can convert it.
1440 bool needToDoConversionToPlainText
= false;
1441 const char* actualFlavor
;
1442 if (mimeFlavor
.EqualsLiteral(kTextMime
) ||
1443 mimeFlavor
.EqualsLiteral(gTextPlainUTF8Type
)) {
1444 actualFlavor
= kUnicodeMime
;
1445 needToDoConversionToPlainText
= true;
1447 // if someone was asking for _NETSCAPE_URL we need to convert to
1448 // plain text but we also need to look for x-moz-url
1449 else if (mimeFlavor
.EqualsLiteral(gMozUrlType
)) {
1450 actualFlavor
= kURLMime
;
1451 needToDoConversionToPlainText
= true;
1453 // if someone was asking for text/uri-list we need to convert to
1455 else if (mimeFlavor
.EqualsLiteral(gTextUriListType
)) {
1456 actualFlavor
= gTextUriListType
;
1457 needToDoConversionToPlainText
= true;
1459 // Someone is asking for the special Direct Save Protocol type.
1460 else if (mimeFlavor
.EqualsLiteral(gXdndDirectSaveType
)) {
1461 // Indicate failure by default.
1462 gtk_selection_data_set(aSelectionData
, target
, 8, (guchar
*)"E", 1);
1464 GdkAtom property
= gdk_atom_intern(gXdndDirectSaveType
, FALSE
);
1465 GdkAtom type
= gdk_atom_intern(kTextMime
, FALSE
);
1469 if (!gdk_property_get(gdk_drag_context_get_source_window(aContext
),
1470 property
, type
, 0, INT32_MAX
, FALSE
, nullptr,
1471 nullptr, &length
, &data
)) {
1475 // Zero-terminate the string.
1476 data
= (guchar
*)g_realloc(data
, length
+ 1);
1478 data
[length
] = '\0';
1482 g_filename_from_uri((const gchar
*)data
, &hostname
, nullptr);
1484 if (!gfullpath
) return;
1486 nsCString
fullpath(gfullpath
);
1489 LOG(("XdndDirectSave filepath is %s\n", fullpath
.get()));
1491 // If there is no hostname in the URI, NULL will be stored.
1492 // We should not accept uris with from a different host.
1494 nsCOMPtr
<nsIPropertyBag2
> infoService
=
1495 do_GetService(NS_SYSTEMINFO_CONTRACTID
);
1496 if (!infoService
) return;
1500 infoService
->GetPropertyAsACString(u
"host"_ns
, host
))) {
1501 if (!host
.Equals(hostname
)) {
1502 LOG(("ignored drag because of different host.\n"));
1504 // Special error code "F" for this case.
1505 gtk_selection_data_set(aSelectionData
, target
, 8, (guchar
*)"F", 1);
1514 nsCOMPtr
<nsIFile
> file
;
1516 NS_NewNativeLocalFile(fullpath
, false, getter_AddRefs(file
)))) {
1520 // We have to split the path into a directory and filename,
1521 // because our internal file-promise API is based on these.
1523 nsCOMPtr
<nsIFile
> directory
;
1524 file
->GetParent(getter_AddRefs(directory
));
1526 item
->SetTransferData(kFilePromiseDirectoryMime
, directory
);
1528 nsCOMPtr
<nsISupportsString
> filenamePrimitive
=
1529 do_CreateInstance(NS_SUPPORTS_STRING_CONTRACTID
);
1530 if (!filenamePrimitive
) return;
1532 nsAutoString leafName
;
1533 file
->GetLeafName(leafName
);
1534 filenamePrimitive
->SetData(leafName
);
1536 item
->SetTransferData(kFilePromiseDestFilename
, filenamePrimitive
);
1538 // Request a different type in GetTransferData.
1539 actualFlavor
= kFilePromiseMime
;
1541 actualFlavor
= typeName
;
1544 nsCOMPtr
<nsISupports
> data
;
1545 rv
= item
->GetTransferData(actualFlavor
, getter_AddRefs(data
));
1547 if (strcmp(actualFlavor
, kFilePromiseMime
) == 0) {
1548 if (NS_SUCCEEDED(rv
)) {
1549 // Indicate success.
1550 gtk_selection_data_set(aSelectionData
, target
, 8, (guchar
*)"S", 1);
1555 if (NS_SUCCEEDED(rv
)) {
1556 void* tmpData
= nullptr;
1557 uint32_t tmpDataLen
= 0;
1558 nsPrimitiveHelpers::CreateDataFromPrimitive(
1559 nsDependentCString(actualFlavor
), data
, &tmpData
, &tmpDataLen
);
1560 // if required, do the extra work to convert unicode to plain
1561 // text and replace the output values with the plain text.
1562 if (needToDoConversionToPlainText
) {
1563 char* plainTextData
= nullptr;
1564 char16_t
* castedUnicode
= reinterpret_cast<char16_t
*>(tmpData
);
1565 uint32_t plainTextLen
= 0;
1566 UTF16ToNewUTF8(castedUnicode
, tmpDataLen
/ 2, &plainTextData
,
1569 // this was not allocated using glib
1571 tmpData
= plainTextData
;
1572 tmpDataLen
= plainTextLen
;
1576 // this copies the data
1577 gtk_selection_data_set(aSelectionData
, target
, 8, (guchar
*)tmpData
,
1579 // this wasn't allocated with glib
1583 if (mimeFlavor
.EqualsLiteral(gTextUriListType
)) {
1584 // fall back for text/uri-list
1586 CreateURIList(mSourceDataItems
, list
);
1587 gtk_selection_data_set(aSelectionData
, target
, 8, (guchar
*)list
.get(),
1595 void nsDragService::SourceBeginDrag(GdkDragContext
* aContext
) {
1596 nsCOMPtr
<nsITransferable
> transferable
=
1597 do_QueryElementAt(mSourceDataItems
, 0);
1598 if (!transferable
) return;
1600 nsTArray
<nsCString
> flavors
;
1601 nsresult rv
= transferable
->FlavorsTransferableCanImport(flavors
);
1602 NS_ENSURE_SUCCESS(rv
, );
1604 for (uint32_t i
= 0; i
< flavors
.Length(); ++i
) {
1605 if (flavors
[i
].EqualsLiteral(kFilePromiseDestFilename
)) {
1606 nsCOMPtr
<nsISupports
> data
;
1607 rv
= transferable
->GetTransferData(kFilePromiseDestFilename
,
1608 getter_AddRefs(data
));
1609 if (NS_FAILED(rv
)) {
1613 nsCOMPtr
<nsISupportsString
> fileName
= do_QueryInterface(data
);
1618 nsAutoString fileNameStr
;
1619 fileName
->GetData(fileNameStr
);
1621 nsCString fileNameCStr
;
1622 CopyUTF16toUTF8(fileNameStr
, fileNameCStr
);
1624 GdkAtom property
= gdk_atom_intern(gXdndDirectSaveType
, FALSE
);
1625 GdkAtom type
= gdk_atom_intern(kTextMime
, FALSE
);
1627 gdk_property_change(gdk_drag_context_get_source_window(aContext
),
1628 property
, type
, 8, GDK_PROP_MODE_REPLACE
,
1629 (const guchar
*)fileNameCStr
.get(),
1630 fileNameCStr
.Length());
1635 void nsDragService::SetDragIcon(GdkDragContext
* aContext
) {
1636 if (!mHasImage
&& !mSelection
) return;
1638 LayoutDeviceIntRect dragRect
;
1640 RefPtr
<SourceSurface
> surface
;
1641 DrawDrag(mSourceNode
, mRegion
, mScreenPosition
, &dragRect
, &surface
, &pc
);
1644 LayoutDeviceIntPoint screenPoint
=
1645 ConvertToUnscaledDevPixels(pc
, mScreenPosition
);
1646 int32_t offsetX
= screenPoint
.x
- dragRect
.x
;
1647 int32_t offsetY
= screenPoint
.y
- dragRect
.y
;
1649 // If a popup is set as the drag image, use its widget. Otherwise, use
1650 // the surface that DrawDrag created.
1652 // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454.
1653 // Fix this once a new GTK version ships that does not destroy our
1654 // widget in gtk_drag_set_icon_widget.
1655 if (mDragPopup
&& gtk_check_version(3, 19, 4)) {
1656 GtkWidget
* gtkWidget
= nullptr;
1657 nsIFrame
* frame
= mDragPopup
->GetPrimaryFrame();
1659 // DrawDrag ensured that this is a popup frame.
1660 nsCOMPtr
<nsIWidget
> widget
= frame
->GetNearestWidget();
1662 gtkWidget
= (GtkWidget
*)widget
->GetNativeData(NS_NATIVE_SHELLWIDGET
);
1665 gtk_drag_set_icon_widget(aContext
, gtkWidget
, offsetX
, offsetY
);
1669 } else if (surface
) {
1670 if (!SetAlphaPixmap(surface
, aContext
, offsetX
, offsetY
, dragRect
)) {
1671 GdkPixbuf
* dragPixbuf
= nsImageToPixbuf::SourceSurfaceToPixbuf(
1672 surface
, dragRect
.width
, dragRect
.height
);
1674 gtk_drag_set_icon_pixbuf(aContext
, dragPixbuf
, offsetX
, offsetY
);
1675 g_object_unref(dragPixbuf
);
1681 static void invisibleSourceDragBegin(GtkWidget
* aWidget
,
1682 GdkDragContext
* aContext
, gpointer aData
) {
1683 LOG(("invisibleSourceDragBegin"));
1684 nsDragService
* dragService
= (nsDragService
*)aData
;
1686 dragService
->SourceBeginDrag(aContext
);
1687 dragService
->SetDragIcon(aContext
);
1690 static void invisibleSourceDragDataGet(GtkWidget
* aWidget
,
1691 GdkDragContext
* aContext
,
1692 GtkSelectionData
* aSelectionData
,
1693 guint aInfo
, guint32 aTime
,
1695 LOG(("invisibleSourceDragDataGet"));
1696 nsDragService
* dragService
= (nsDragService
*)aData
;
1697 dragService
->SourceDataGet(aWidget
, aContext
, aSelectionData
, aTime
);
1700 static gboolean
invisibleSourceDragFailed(GtkWidget
* aWidget
,
1701 GdkDragContext
* aContext
,
1702 gint aResult
, gpointer aData
) {
1704 // Wayland and X11 uses different drag results here. When drag target is
1705 // missing X11 passes GDK_DRAG_CANCEL_NO_TARGET
1706 // (from gdk_dnd_handle_button_event()/gdkdnd-x11.c)
1707 // as backend X11 has info about other application windows.
1708 // Wayland does not have such info so it always passes
1709 // GDK_DRAG_CANCEL_ERROR error code
1710 // (see data_source_cancelled/gdkselection-wayland.c).
1712 if (widget::GdkIsWaylandDisplay() && aResult
== MOZ_GTK_DRAG_RESULT_ERROR
) {
1713 for (GList
* tmp
= gdk_drag_context_list_targets(aContext
); tmp
;
1715 GdkAtom atom
= GDK_POINTER_TO_ATOM(tmp
->data
);
1716 gchar
* name
= gdk_atom_name(atom
);
1717 if (name
&& (strcmp(name
, gTabDropType
) == 0)) {
1718 aResult
= MOZ_GTK_DRAG_RESULT_NO_TARGET
;
1719 LOG(("invisibleSourceDragFailed: Wayland tab drop\n"));
1725 LOG(("invisibleSourceDragFailed %i", aResult
));
1726 nsDragService
* dragService
= (nsDragService
*)aData
;
1727 // End the drag session now (rather than waiting for the drag-end signal)
1728 // so that operations performed on dropEffect == none can start immediately
1729 // rather than waiting for the drag-failed animation to finish.
1730 dragService
->SourceEndDragSession(aContext
, aResult
);
1732 // We should return TRUE to disable the drag-failed animation iff the
1733 // source performed an operation when dropEffect was none, but the handler
1734 // of the dragend DOM event doesn't provide this information.
1738 static void invisibleSourceDragEnd(GtkWidget
* aWidget
, GdkDragContext
* aContext
,
1740 LOG(("invisibleSourceDragEnd"));
1741 nsDragService
* dragService
= (nsDragService
*)aData
;
1743 // The drag has ended. Release the hostages!
1744 dragService
->SourceEndDragSession(aContext
, MOZ_GTK_DRAG_RESULT_SUCCESS
);
1747 // The following methods handle responding to GTK drag signals and
1748 // tracking state between these signals.
1750 // In general, GTK does not expect us to run the event loop while handling its
1751 // drag signals, however our drag event handlers may run the
1752 // event loop, most often to fetch information about the drag data.
1754 // GTK, for example, uses the return value from drag-motion signals to
1755 // determine whether drag-leave signals should be sent. If an event loop is
1756 // run during drag-motion the XdndLeave message can get processed but when GTK
1757 // receives the message it does not yet know that it needs to send the
1758 // drag-leave signal to our widget.
1760 // After a drag-drop signal, we need to reply with gtk_drag_finish().
1761 // However, gtk_drag_finish should happen after the drag-drop signal handler
1762 // returns so that when the Motif drag protocol is used, the
1763 // XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START
1764 // reply sent on return from the drag-drop signal handler.
1766 // Similarly drag-end for a successful drag and drag-failed are not good
1767 // times to run a nested event loop as gtk_drag_drop_finished() and
1768 // gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove
1769 // drop_timeout until after at least the first of these signals is sent.
1770 // Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop
1771 // timeout) could cause gtk_drag_drop_finished to be called again with the
1772 // same GtkDragSourceInfo, which won't like being destroyed twice.
1774 // Therefore we reply to the signals immediately and schedule a task to
1775 // dispatch the Gecko events, which may run the event loop.
1777 // Action in response to drag-leave signals is also delayed until the event
1778 // loop runs again so that we find out whether a drag-drop signal follows.
1780 // A single task is scheduled to manage responses to all three GTK signals.
1781 // If further signals are received while the task is scheduled, the scheduled
1782 // response is updated, sometimes effectively compressing successive signals.
1784 // No Gecko drag events are dispatched (during nested event loops) while other
1785 // Gecko drag events are in flight. This helps event handlers that may not
1786 // expect nested events, while accessing an event's dataTransfer for example.
1788 gboolean
nsDragService::ScheduleMotionEvent(
1789 nsWindow
* aWindow
, GdkDragContext
* aDragContext
,
1790 nsWaylandDragContext
* aWaylandDragContext
,
1791 LayoutDeviceIntPoint aWindowPoint
, guint aTime
) {
1792 if (aDragContext
&& mScheduledTask
== eDragTaskMotion
) {
1793 // The drag source has sent another motion message before we've
1794 // replied to the previous. That shouldn't happen with Xdnd. The
1795 // spec for Motif drags is less clear, but we'll just update the
1796 // scheduled task with the new position reply only to the most
1798 NS_WARNING("Drag Motion message received before previous reply was sent");
1801 // Returning TRUE means we'll reply with a status message, unless we first
1803 return Schedule(eDragTaskMotion
, aWindow
, aDragContext
, aWaylandDragContext
,
1804 aWindowPoint
, aTime
);
1807 void nsDragService::ScheduleLeaveEvent() {
1808 // We don't know at this stage whether a drop signal will immediately
1809 // follow. If the drop signal gets sent it will happen before we return
1810 // to the main loop and the scheduled leave task will be replaced.
1811 if (!Schedule(eDragTaskLeave
, nullptr, nullptr, nullptr,
1812 LayoutDeviceIntPoint(), 0)) {
1813 NS_WARNING("Drag leave after drop");
1817 gboolean
nsDragService::ScheduleDropEvent(
1818 nsWindow
* aWindow
, GdkDragContext
* aDragContext
,
1819 nsWaylandDragContext
* aWaylandDragContext
,
1820 LayoutDeviceIntPoint aWindowPoint
, guint aTime
) {
1821 if (!Schedule(eDragTaskDrop
, aWindow
, aDragContext
, aWaylandDragContext
,
1822 aWindowPoint
, aTime
)) {
1823 NS_WARNING("Additional drag drop ignored");
1827 SetDragEndPoint(aWindowPoint
+ aWindow
->WidgetToScreenOffset());
1829 // We'll reply with gtk_drag_finish().
1833 gboolean
nsDragService::Schedule(DragTask aTask
, nsWindow
* aWindow
,
1834 GdkDragContext
* aDragContext
,
1835 nsWaylandDragContext
* aWaylandDragContext
,
1836 LayoutDeviceIntPoint aWindowPoint
,
1838 // If there is an existing leave or motion task scheduled, then that
1839 // will be replaced. When the new task is run, it will dispatch
1840 // any necessary leave or motion events.
1842 // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled
1843 // drop event (which could happen if the drop event has not been processed
1844 // within the allowed time). Otherwise, if we haven't yet run a scheduled
1845 // drop or end task, just say that we are not ready to receive another
1847 if (mScheduledTask
== eDragTaskSourceEnd
||
1848 (mScheduledTask
== eDragTaskDrop
&& aTask
!= eDragTaskSourceEnd
))
1851 mScheduledTask
= aTask
;
1852 mPendingWindow
= aWindow
;
1853 mPendingDragContext
= aDragContext
;
1855 mPendingWaylandDragContext
= aWaylandDragContext
;
1857 mPendingWindowPoint
= aWindowPoint
;
1858 mPendingTime
= aTime
;
1861 // High priority is used here because the native events involved have
1862 // already waited at default priority. Perhaps a lower than default
1863 // priority could be used for motion tasks because there is a chance
1864 // that a leave or drop is waiting, but managing different priorities
1865 // may not be worth the effort. Motion tasks shouldn't queue up as
1866 // they should be throttled based on replies.
1868 g_idle_add_full(G_PRIORITY_HIGH
, TaskDispatchCallback
, this, nullptr);
1873 gboolean
nsDragService::TaskDispatchCallback(gpointer data
) {
1874 RefPtr
<nsDragService
> dragService
= static_cast<nsDragService
*>(data
);
1875 return dragService
->RunScheduledTask();
1878 gboolean
nsDragService::RunScheduledTask() {
1879 if (mTargetWindow
&& mTargetWindow
!= mPendingWindow
) {
1880 LOG(("nsDragService: dispatch drag leave (%p)\n", mTargetWindow
.get()));
1881 mTargetWindow
->DispatchDragEvent(eDragExit
, mTargetWindowPoint
, 0);
1884 // The drag that was initiated in a different app. End the drag
1885 // session, since we're done with it for now (until the user drags
1886 // back into this app).
1887 EndDragSession(false, GetCurrentModifiers());
1891 // It is possible that the pending state has been updated during dispatch
1892 // of the leave event. That's fine.
1894 // Now we collect the pending state because, from this point on, we want
1895 // to use the same state for all events dispatched. All state is updated
1896 // so that when other tasks are scheduled during dispatch here, this
1897 // task is considered to have already been run.
1898 bool positionHasChanged
= mPendingWindow
!= mTargetWindow
||
1899 mPendingWindowPoint
!= mTargetWindowPoint
;
1900 DragTask task
= mScheduledTask
;
1901 mScheduledTask
= eDragTaskNone
;
1902 mTargetWindow
= std::move(mPendingWindow
);
1903 mTargetWindowPoint
= mPendingWindowPoint
;
1905 if (task
== eDragTaskLeave
|| task
== eDragTaskSourceEnd
) {
1906 if (task
== eDragTaskSourceEnd
) {
1907 // Dispatch drag end events.
1908 EndDragSession(true, GetCurrentModifiers());
1911 // Nothing more to do
1912 // Returning false removes the task source from the event loop.
1917 // This may be the start of a destination drag session.
1920 // mTargetWidget may be nullptr if the window has been destroyed.
1921 // (The leave event is not scheduled if a drop task is still scheduled.)
1922 // We still reply appropriately to indicate that the drop will or didn't
1924 mTargetWidget
= mTargetWindow
->GetMozContainerWidget();
1925 mTargetDragContext
= std::move(mPendingDragContext
);
1927 mTargetWaylandDragContext
= std::move(mPendingWaylandDragContext
);
1929 mTargetTime
= mPendingTime
;
1931 mCachedData
.Clear();
1933 // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model
1934 // (as at 27 December 2010) indicates that a "drop" event should only be
1935 // fired (at the current target element) if the current drag operation is
1936 // not none. The current drag operation will only be set to a non-none
1937 // value during a "dragover" event.
1939 // If the user has ended the drag before any dragover events have been
1940 // sent, then the spec recommends skipping the drop (because the current
1941 // drag operation is none). However, here we assume that, by releasing
1942 // the mouse button, the user has indicated that they want to drop, so we
1943 // proceed with the drop where possible.
1945 // In order to make the events appear to content in the same way as if the
1946 // spec is being followed we make sure to dispatch a "dragover" event with
1947 // appropriate coordinates and check canDrop before the "drop" event.
1949 // When the Xdnd protocol is used for source/destination communication (as
1950 // should be the case with GTK source applications) a dragover event
1951 // should have already been sent during the drag-motion signal, which
1952 // would have already been received because XdndDrop messages do not
1953 // contain a position. However, we can't assume the same when the Motif
1954 // protocol is used.
1955 if (task
== eDragTaskMotion
|| positionHasChanged
) {
1957 TakeDragEventDispatchedToChildProcess(); // Clear the old value.
1958 DispatchMotionEvents();
1959 if (task
== eDragTaskMotion
) {
1960 if (TakeDragEventDispatchedToChildProcess()) {
1961 mTargetDragContextForRemote
= mTargetDragContext
;
1963 mTargetWaylandDragContextForRemote
= mTargetWaylandDragContext
;
1966 // Reply to tell the source whether we can drop and what
1967 // action would be taken.
1968 if (mTargetDragContext
) {
1969 ReplyToDragMotion(mTargetDragContext
);
1972 else if (mTargetWaylandDragContext
) {
1973 ReplyToDragMotion(mTargetWaylandDragContext
);
1980 if (task
== eDragTaskDrop
) {
1981 gboolean success
= DispatchDropEvent();
1983 // Perhaps we should set the del parameter to TRUE when the drag
1984 // action is move, but we don't know whether the data was successfully
1986 if (mTargetDragContext
) {
1987 gtk_drag_finish(mTargetDragContext
, success
,
1988 /* del = */ FALSE
, mTargetTime
);
1991 // This drag is over, so clear out our reference to the previous
1993 mTargetWindow
= nullptr;
1994 // Make sure to end the drag session. If this drag started in a
1995 // different app, we won't get a drag_end signal to end it from.
1996 EndDragSession(true, GetCurrentModifiers());
1999 // We're done with the drag context.
2000 mTargetWidget
= nullptr;
2001 mTargetDragContext
= nullptr;
2003 mTargetWaylandDragContext
= nullptr;
2006 mCachedData
.Clear();
2008 // If we got another drag signal while running the sheduled task, that
2009 // must have happened while running a nested event loop. Leave the task
2010 // source on the event loop.
2011 if (mScheduledTask
!= eDragTaskNone
) return TRUE
;
2013 // We have no task scheduled.
2014 // Returning false removes the task source from the event loop.
2019 // This will update the drag action based on the information in the
2020 // drag context. Gtk gets this from a combination of the key settings
2021 // and what the source is offering.
2023 void nsDragService::UpdateDragAction() {
2024 // This doesn't look right. dragSession.dragAction is used by
2025 // nsContentUtils::SetDataTransferInEvent() to set the initial
2026 // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be
2027 // more appropriate. GdkDragContext::actions should be used to set
2028 // dataTransfer.effectAllowed, which doesn't currently happen with
2029 // external sources.
2031 // default is to do nothing
2032 int action
= nsIDragService::DRAGDROP_ACTION_NONE
;
2033 GdkDragAction gdkAction
= GDK_ACTION_DEFAULT
;
2034 if (mTargetDragContext
) {
2035 gdkAction
= gdk_drag_context_get_actions(mTargetDragContext
);
2038 else if (mTargetWaylandDragContext
) {
2039 gdkAction
= mTargetWaylandDragContext
->GetAvailableDragActions();
2043 // set the default just in case nothing matches below
2044 if (gdkAction
& GDK_ACTION_DEFAULT
)
2045 action
= nsIDragService::DRAGDROP_ACTION_MOVE
;
2047 // first check to see if move is set
2048 if (gdkAction
& GDK_ACTION_MOVE
)
2049 action
= nsIDragService::DRAGDROP_ACTION_MOVE
;
2051 // then fall to the others
2052 else if (gdkAction
& GDK_ACTION_LINK
)
2053 action
= nsIDragService::DRAGDROP_ACTION_LINK
;
2056 else if (gdkAction
& GDK_ACTION_COPY
)
2057 action
= nsIDragService::DRAGDROP_ACTION_COPY
;
2059 // update the drag information
2060 SetDragAction(action
);
2064 nsDragService::UpdateDragEffect() {
2065 if (mTargetDragContextForRemote
) {
2066 ReplyToDragMotion(mTargetDragContextForRemote
);
2067 mTargetDragContextForRemote
= nullptr;
2070 else if (mTargetWaylandDragContextForRemote
) {
2071 ReplyToDragMotion(mTargetWaylandDragContextForRemote
);
2072 mTargetWaylandDragContextForRemote
= nullptr;
2078 void nsDragService::DispatchMotionEvents() {
2081 FireDragEventAtSource(eDrag
, GetCurrentModifiers());
2083 mTargetWindow
->DispatchDragEvent(eDragOver
, mTargetWindowPoint
, mTargetTime
);
2086 // Returns true if the drop was successful
2087 gboolean
nsDragService::DispatchDropEvent() {
2088 // We need to check IsDestroyed here because the nsRefPtr
2089 // only protects this from being deleted, it does NOT protect
2090 // against nsView::~nsView() calling Destroy() on it, bug 378273.
2091 if (mTargetWindow
->IsDestroyed()) return FALSE
;
2093 EventMessage msg
= mCanDrop
? eDrop
: eDragExit
;
2095 mTargetWindow
->DispatchDragEvent(msg
, mTargetWindowPoint
, mTargetTime
);
2101 uint32_t nsDragService::GetCurrentModifiers() {
2102 return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers();