/src/mozilla-central/widget/gtk/nsDragService.cpp
Line | Count | Source (jump to first uncovered line) |
1 | | /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ |
2 | | /* vim: set ts=4 et sw=4 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/. */ |
6 | | |
7 | | #include "nsDragService.h" |
8 | | #include "nsArrayUtils.h" |
9 | | #include "nsIObserverService.h" |
10 | | #include "nsWidgetsCID.h" |
11 | | #include "nsWindow.h" |
12 | | #include "nsIServiceManager.h" |
13 | | #include "nsXPCOM.h" |
14 | | #include "nsISupportsPrimitives.h" |
15 | | #include "nsIIOService.h" |
16 | | #include "nsIFileURL.h" |
17 | | #include "nsNetUtil.h" |
18 | | #include "mozilla/Logging.h" |
19 | | #include "nsTArray.h" |
20 | | #include "nsPrimitiveHelpers.h" |
21 | | #include "prtime.h" |
22 | | #include "prthread.h" |
23 | | #include <dlfcn.h> |
24 | | #include <gtk/gtk.h> |
25 | | #include <gdk/gdkx.h> |
26 | | #include "nsCRT.h" |
27 | | #include "mozilla/BasicEvents.h" |
28 | | #include "mozilla/Services.h" |
29 | | #include "mozilla/ClearOnShutdown.h" |
30 | | |
31 | | #include "gfxXlibSurface.h" |
32 | | #include "gfxContext.h" |
33 | | #include "nsImageToPixbuf.h" |
34 | | #include "nsPresContext.h" |
35 | | #include "nsIContent.h" |
36 | | #include "nsIDocument.h" |
37 | | #include "nsViewManager.h" |
38 | | #include "nsIFrame.h" |
39 | | #include "nsGtkUtils.h" |
40 | | #include "nsGtkKeyUtils.h" |
41 | | #include "mozilla/gfx/2D.h" |
42 | | #include "gfxPlatform.h" |
43 | | #include "ScreenHelperGTK.h" |
44 | | #include "nsArrayUtils.h" |
45 | | #ifdef MOZ_WAYLAND |
46 | | #include "nsClipboardWayland.h" |
47 | | #endif |
48 | | |
49 | | using namespace mozilla; |
50 | | using namespace mozilla::gfx; |
51 | | |
52 | | // This sets how opaque the drag image is |
53 | 0 | #define DRAG_IMAGE_ALPHA_LEVEL 0.5 |
54 | | |
55 | | // These values are copied from GtkDragResult (rather than using GtkDragResult |
56 | | // directly) so that this code can be compiled against versions of GTK+ that |
57 | | // do not have GtkDragResult. |
58 | | // GtkDragResult is available from GTK+ version 2.12. |
59 | | enum { |
60 | | MOZ_GTK_DRAG_RESULT_SUCCESS, |
61 | | MOZ_GTK_DRAG_RESULT_NO_TARGET |
62 | | }; |
63 | | |
64 | | static LazyLogModule sDragLm("nsDragService"); |
65 | | |
66 | | // data used for synthetic periodic motion events sent to the source widget |
67 | | // grabbing real events for the drag. |
68 | | static guint sMotionEventTimerID; |
69 | | static GdkEvent *sMotionEvent; |
70 | | static GtkWidget *sGrabWidget; |
71 | | |
72 | | static const char gMimeListType[] = "application/x-moz-internal-item-list"; |
73 | | static const char gMozUrlType[] = "_NETSCAPE_URL"; |
74 | | static const char gTextUriListType[] = "text/uri-list"; |
75 | | static const char gTextPlainUTF8Type[] = "text/plain;charset=utf-8"; |
76 | | |
77 | | static void |
78 | | invisibleSourceDragBegin(GtkWidget *aWidget, |
79 | | GdkDragContext *aContext, |
80 | | gpointer aData); |
81 | | |
82 | | static void |
83 | | invisibleSourceDragEnd(GtkWidget *aWidget, |
84 | | GdkDragContext *aContext, |
85 | | gpointer aData); |
86 | | |
87 | | static gboolean |
88 | | invisibleSourceDragFailed(GtkWidget *aWidget, |
89 | | GdkDragContext *aContext, |
90 | | gint aResult, |
91 | | gpointer aData); |
92 | | |
93 | | static void |
94 | | invisibleSourceDragDataGet(GtkWidget *aWidget, |
95 | | GdkDragContext *aContext, |
96 | | GtkSelectionData *aSelectionData, |
97 | | guint aInfo, |
98 | | guint32 aTime, |
99 | | gpointer aData); |
100 | | |
101 | | nsDragService::nsDragService() |
102 | | : mScheduledTask(eDragTaskNone) |
103 | | , mTaskSource(0) |
104 | | #ifdef MOZ_WAYLAND |
105 | | , mPendingWaylandDragContext(nullptr) |
106 | | , mTargetWaylandDragContext(nullptr) |
107 | | #endif |
108 | 0 | { |
109 | 0 | // We have to destroy the hidden widget before the event loop stops |
110 | 0 | // running. |
111 | 0 | nsCOMPtr<nsIObserverService> obsServ = |
112 | 0 | mozilla::services::GetObserverService(); |
113 | 0 | obsServ->AddObserver(this, "quit-application", false); |
114 | 0 |
|
115 | 0 | // our hidden source widget |
116 | 0 | // Using an offscreen window works around bug 983843. |
117 | 0 | mHiddenWidget = gtk_offscreen_window_new(); |
118 | 0 | // make sure that the widget is realized so that |
119 | 0 | // we can use it as a drag source. |
120 | 0 | gtk_widget_realize(mHiddenWidget); |
121 | 0 | // hook up our internal signals so that we can get some feedback |
122 | 0 | // from our drag source |
123 | 0 | g_signal_connect(mHiddenWidget, "drag_begin", |
124 | 0 | G_CALLBACK(invisibleSourceDragBegin), this); |
125 | 0 | g_signal_connect(mHiddenWidget, "drag_data_get", |
126 | 0 | G_CALLBACK(invisibleSourceDragDataGet), this); |
127 | 0 | g_signal_connect(mHiddenWidget, "drag_end", |
128 | 0 | G_CALLBACK(invisibleSourceDragEnd), this); |
129 | 0 | // drag-failed is available from GTK+ version 2.12 |
130 | 0 | guint dragFailedID = g_signal_lookup("drag-failed", |
131 | 0 | G_TYPE_FROM_INSTANCE(mHiddenWidget)); |
132 | 0 | if (dragFailedID) { |
133 | 0 | g_signal_connect_closure_by_id(mHiddenWidget, dragFailedID, 0, |
134 | 0 | g_cclosure_new(G_CALLBACK(invisibleSourceDragFailed), |
135 | 0 | this, nullptr), |
136 | 0 | FALSE); |
137 | 0 | } |
138 | 0 |
|
139 | 0 | // set up our logging module |
140 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::nsDragService")); |
141 | 0 | mCanDrop = false; |
142 | 0 | mTargetDragDataReceived = false; |
143 | 0 | mTargetDragData = 0; |
144 | 0 | mTargetDragDataLen = 0; |
145 | 0 | } |
146 | | |
147 | | nsDragService::~nsDragService() |
148 | 0 | { |
149 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::~nsDragService")); |
150 | 0 | if (mTaskSource) |
151 | 0 | g_source_remove(mTaskSource); |
152 | 0 |
|
153 | 0 | } |
154 | | |
155 | | NS_IMPL_ISUPPORTS_INHERITED(nsDragService, nsBaseDragService, nsIObserver) |
156 | | |
157 | | mozilla::StaticRefPtr<nsDragService> sDragServiceInstance; |
158 | | /* static */ already_AddRefed<nsDragService> |
159 | | nsDragService::GetInstance() |
160 | 0 | { |
161 | 0 | if (gfxPlatform::IsHeadless()) { |
162 | 0 | return nullptr; |
163 | 0 | } |
164 | 0 | if (!sDragServiceInstance) { |
165 | 0 | sDragServiceInstance = new nsDragService(); |
166 | 0 | ClearOnShutdown(&sDragServiceInstance); |
167 | 0 | } |
168 | 0 |
|
169 | 0 | RefPtr<nsDragService> service = sDragServiceInstance.get(); |
170 | 0 | return service.forget(); |
171 | 0 | } |
172 | | |
173 | | // nsIObserver |
174 | | |
175 | | NS_IMETHODIMP |
176 | | nsDragService::Observe(nsISupports *aSubject, const char *aTopic, |
177 | | const char16_t *aData) |
178 | 0 | { |
179 | 0 | if (!nsCRT::strcmp(aTopic, "quit-application")) { |
180 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
181 | 0 | ("nsDragService::Observe(\"quit-application\")")); |
182 | 0 | if (mHiddenWidget) { |
183 | 0 | gtk_widget_destroy(mHiddenWidget); |
184 | 0 | mHiddenWidget = 0; |
185 | 0 | } |
186 | 0 | TargetResetData(); |
187 | 0 | } else { |
188 | 0 | MOZ_ASSERT_UNREACHABLE("unexpected topic"); |
189 | 0 | return NS_ERROR_UNEXPECTED; |
190 | 0 | } |
191 | 0 |
|
192 | 0 | return NS_OK; |
193 | 0 | } |
194 | | |
195 | | // Support for periodic drag events |
196 | | |
197 | | // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
198 | | // and the Xdnd protocol both recommend that drag events are sent periodically, |
199 | | // but GTK does not normally provide this. |
200 | | // |
201 | | // Here GTK is periodically stimulated by copies of the most recent mouse |
202 | | // motion events so as to send drag position messages to the destination when |
203 | | // appropriate (after it has received a status event from the previous |
204 | | // message). |
205 | | // |
206 | | // (If events were sent only on the destination side then the destination |
207 | | // would have no message to which it could reply with a drag status. Without |
208 | | // sending a drag status to the source, the destination would not be able to |
209 | | // change its feedback re whether it could accept the drop, and so the |
210 | | // source's behavior on drop will not be consistent.) |
211 | | |
212 | | static gboolean |
213 | | DispatchMotionEventCopy(gpointer aData) |
214 | 0 | { |
215 | 0 | // Clear the timer id before OnSourceGrabEventAfter is called during event |
216 | 0 | // dispatch. |
217 | 0 | sMotionEventTimerID = 0; |
218 | 0 |
|
219 | 0 | GdkEvent *event = sMotionEvent; |
220 | 0 | sMotionEvent = nullptr; |
221 | 0 | // If there is no longer a grab on the widget, then the drag is over and |
222 | 0 | // there is no need to continue drag motion. |
223 | 0 | if (gtk_widget_has_grab(sGrabWidget)) { |
224 | 0 | gtk_propagate_event(sGrabWidget, event); |
225 | 0 | } |
226 | 0 | gdk_event_free(event); |
227 | 0 |
|
228 | 0 | // Cancel this timer; |
229 | 0 | // We've already started another if the motion event was dispatched. |
230 | 0 | return FALSE; |
231 | 0 | } |
232 | | |
233 | | static void |
234 | | OnSourceGrabEventAfter(GtkWidget *widget, GdkEvent *event, gpointer user_data) |
235 | 0 | { |
236 | 0 | // If there is no longer a grab on the widget, then the drag motion is |
237 | 0 | // over (though the data may not be fetched yet). |
238 | 0 | if (!gtk_widget_has_grab(sGrabWidget)) |
239 | 0 | return; |
240 | 0 | |
241 | 0 | if (event->type == GDK_MOTION_NOTIFY) { |
242 | 0 | if (sMotionEvent) { |
243 | 0 | gdk_event_free(sMotionEvent); |
244 | 0 | } |
245 | 0 | sMotionEvent = gdk_event_copy(event); |
246 | 0 |
|
247 | 0 | // Update the cursor position. The last of these recorded gets used for |
248 | 0 | // the eDragEnd event. |
249 | 0 | nsDragService *dragService = static_cast<nsDragService*>(user_data); |
250 | 0 | gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor(); |
251 | 0 | auto p = LayoutDeviceIntPoint::Round(event->motion.x_root * scale, |
252 | 0 | event->motion.y_root * scale); |
253 | 0 | dragService->SetDragEndPoint(p); |
254 | 0 | } else if (sMotionEvent && (event->type == GDK_KEY_PRESS || |
255 | 0 | event->type == GDK_KEY_RELEASE)) { |
256 | 0 | // Update modifier state from key events. |
257 | 0 | sMotionEvent->motion.state = event->key.state; |
258 | 0 | } else { |
259 | 0 | return; |
260 | 0 | } |
261 | 0 | |
262 | 0 | if (sMotionEventTimerID) { |
263 | 0 | g_source_remove(sMotionEventTimerID); |
264 | 0 | } |
265 | 0 |
|
266 | 0 | // G_PRIORITY_DEFAULT_IDLE is lower priority than GDK's redraw idle source |
267 | 0 | // and lower than GTK's idle source that sends drag position messages after |
268 | 0 | // motion-notify signals. |
269 | 0 | // |
270 | 0 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
271 | 0 | // recommends an interval of 350ms +/- 200ms. |
272 | 0 | sMotionEventTimerID = |
273 | 0 | g_timeout_add_full(G_PRIORITY_DEFAULT_IDLE, 350, |
274 | 0 | DispatchMotionEventCopy, nullptr, nullptr); |
275 | 0 | } |
276 | | |
277 | | static GtkWindow* |
278 | | GetGtkWindow(nsIDocument *aDocument) |
279 | 0 | { |
280 | 0 | if (!aDocument) |
281 | 0 | return nullptr; |
282 | 0 | |
283 | 0 | nsCOMPtr<nsIPresShell> presShell = aDocument->GetShell(); |
284 | 0 | if (!presShell) |
285 | 0 | return nullptr; |
286 | 0 | |
287 | 0 | RefPtr<nsViewManager> vm = presShell->GetViewManager(); |
288 | 0 | if (!vm) |
289 | 0 | return nullptr; |
290 | 0 | |
291 | 0 | nsCOMPtr<nsIWidget> widget; |
292 | 0 | vm->GetRootWidget(getter_AddRefs(widget)); |
293 | 0 | if (!widget) |
294 | 0 | return nullptr; |
295 | 0 | |
296 | 0 | GtkWidget *gtkWidget = |
297 | 0 | static_cast<nsWindow*>(widget.get())->GetMozContainerWidget(); |
298 | 0 | if (!gtkWidget) |
299 | 0 | return nullptr; |
300 | 0 | |
301 | 0 | GtkWidget *toplevel = nullptr; |
302 | 0 | toplevel = gtk_widget_get_toplevel(gtkWidget); |
303 | 0 | if (!GTK_IS_WINDOW(toplevel)) |
304 | 0 | return nullptr; |
305 | 0 | |
306 | 0 | return GTK_WINDOW(toplevel); |
307 | 0 | } |
308 | | |
309 | | // nsIDragService |
310 | | |
311 | | NS_IMETHODIMP |
312 | | nsDragService::InvokeDragSession(nsINode *aDOMNode, |
313 | | const nsACString& aPrincipalURISpec, |
314 | | nsIArray * aArrayTransferables, |
315 | | uint32_t aActionType, |
316 | | nsContentPolicyType aContentPolicyType = |
317 | | nsIContentPolicy::TYPE_OTHER) |
318 | 0 | { |
319 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::InvokeDragSession")); |
320 | 0 |
|
321 | 0 | // If the previous source drag has not yet completed, signal handlers need |
322 | 0 | // to be removed from sGrabWidget and dragend needs to be dispatched to |
323 | 0 | // the source node, but we can't call EndDragSession yet because we don't |
324 | 0 | // know whether or not the drag succeeded. |
325 | 0 | if (mSourceNode) |
326 | 0 | return NS_ERROR_NOT_AVAILABLE; |
327 | 0 | |
328 | 0 | return nsBaseDragService::InvokeDragSession(aDOMNode, aPrincipalURISpec, |
329 | 0 | aArrayTransferables, |
330 | 0 | aActionType, |
331 | 0 | aContentPolicyType); |
332 | 0 | } |
333 | | |
334 | | // nsBaseDragService |
335 | | nsresult |
336 | | nsDragService::InvokeDragSessionImpl(nsIArray* aArrayTransferables, |
337 | | const Maybe<CSSIntRegion>& aRegion, |
338 | | uint32_t aActionType) |
339 | 0 | { |
340 | 0 | // make sure that we have an array of transferables to use |
341 | 0 | if (!aArrayTransferables) |
342 | 0 | return NS_ERROR_INVALID_ARG; |
343 | 0 | // set our reference to the transferables. this will also addref |
344 | 0 | // the transferables since we're going to hang onto this beyond the |
345 | 0 | // length of this call |
346 | 0 | mSourceDataItems = aArrayTransferables; |
347 | 0 | // get the list of items we offer for drags |
348 | 0 | GtkTargetList *sourceList = GetSourceList(); |
349 | 0 |
|
350 | 0 | if (!sourceList) |
351 | 0 | return NS_OK; |
352 | 0 | |
353 | 0 | // save our action type |
354 | 0 | GdkDragAction action = GDK_ACTION_DEFAULT; |
355 | 0 |
|
356 | 0 | if (aActionType & DRAGDROP_ACTION_COPY) |
357 | 0 | action = (GdkDragAction)(action | GDK_ACTION_COPY); |
358 | 0 | if (aActionType & DRAGDROP_ACTION_MOVE) |
359 | 0 | action = (GdkDragAction)(action | GDK_ACTION_MOVE); |
360 | 0 | if (aActionType & DRAGDROP_ACTION_LINK) |
361 | 0 | action = (GdkDragAction)(action | GDK_ACTION_LINK); |
362 | 0 |
|
363 | 0 | // Create a fake event for the drag so we can pass the time (so to speak). |
364 | 0 | // If we don't do this, then, when the timestamp for the pending button |
365 | 0 | // release event is used for the ungrab, the ungrab can fail due to the |
366 | 0 | // timestamp being _earlier_ than CurrentTime. |
367 | 0 | GdkEvent event; |
368 | 0 | memset(&event, 0, sizeof(GdkEvent)); |
369 | 0 | event.type = GDK_BUTTON_PRESS; |
370 | 0 | event.button.window = gtk_widget_get_window(mHiddenWidget); |
371 | 0 | event.button.time = nsWindow::GetLastUserInputTime(); |
372 | 0 |
|
373 | 0 | // Put the drag widget in the window group of the source node so that the |
374 | 0 | // gtk_grab_add during gtk_drag_begin is effective. |
375 | 0 | // gtk_window_get_group(nullptr) returns the default window group. |
376 | 0 | GtkWindowGroup *window_group = |
377 | 0 | gtk_window_get_group(GetGtkWindow(mSourceDocument)); |
378 | 0 | gtk_window_group_add_window(window_group, |
379 | 0 | GTK_WINDOW(mHiddenWidget)); |
380 | 0 |
|
381 | 0 | #ifdef MOZ_WIDGET_GTK |
382 | 0 | // Get device for event source |
383 | 0 | GdkDisplay *display = gdk_display_get_default(); |
384 | 0 | GdkDeviceManager *device_manager = gdk_display_get_device_manager(display); |
385 | 0 | event.button.device = gdk_device_manager_get_client_pointer(device_manager); |
386 | 0 | #endif |
387 | 0 |
|
388 | 0 | // start our drag. |
389 | 0 | GdkDragContext *context = gtk_drag_begin(mHiddenWidget, |
390 | 0 | sourceList, |
391 | 0 | action, |
392 | 0 | 1, |
393 | 0 | &event); |
394 | 0 |
|
395 | 0 | nsresult rv; |
396 | 0 | if (context) { |
397 | 0 | StartDragSession(); |
398 | 0 |
|
399 | 0 | // GTK uses another hidden window for receiving mouse events. |
400 | 0 | sGrabWidget = gtk_window_group_get_current_grab(window_group); |
401 | 0 | if (sGrabWidget) { |
402 | 0 | g_object_ref(sGrabWidget); |
403 | 0 | // Only motion and key events are required but connect to |
404 | 0 | // "event-after" as this is never blocked by other handlers. |
405 | 0 | g_signal_connect(sGrabWidget, "event-after", |
406 | 0 | G_CALLBACK(OnSourceGrabEventAfter), this); |
407 | 0 | } |
408 | 0 | // We don't have a drag end point yet. |
409 | 0 | mEndDragPoint = LayoutDeviceIntPoint(-1, -1); |
410 | 0 | rv = NS_OK; |
411 | 0 | } |
412 | 0 | else { |
413 | 0 | rv = NS_ERROR_FAILURE; |
414 | 0 | } |
415 | 0 |
|
416 | 0 | gtk_target_list_unref(sourceList); |
417 | 0 |
|
418 | 0 | return rv; |
419 | 0 | } |
420 | | |
421 | | bool |
422 | | nsDragService::SetAlphaPixmap(SourceSurface *aSurface, |
423 | | GdkDragContext *aContext, |
424 | | int32_t aXOffset, |
425 | | int32_t aYOffset, |
426 | | const LayoutDeviceIntRect& dragRect) |
427 | 0 | { |
428 | 0 | GdkScreen* screen = gtk_widget_get_screen(mHiddenWidget); |
429 | 0 |
|
430 | 0 | // Transparent drag icons need, like a lot of transparency-related things, |
431 | 0 | // a compositing X window manager |
432 | 0 | if (!gdk_screen_is_composited(screen)) |
433 | 0 | return false; |
434 | 0 | |
435 | | #ifdef cairo_image_surface_create |
436 | | #error "Looks like we're including Mozilla's cairo instead of system cairo" |
437 | | #endif |
438 | | // Prior to GTK 3.9.12, cairo surfaces passed into gtk_drag_set_icon_surface |
439 | 0 | // had their shape information derived from the alpha channel and used with |
440 | 0 | // the X SHAPE extension instead of being displayed as an ARGB window. |
441 | 0 | // See bug 1249604. |
442 | 0 | if (gtk_check_version(3, 9, 12)) |
443 | 0 | return false; |
444 | 0 | |
445 | 0 | // TODO: grab X11 pixmap or image data instead of expensive readback. |
446 | 0 | cairo_surface_t *surf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, |
447 | 0 | dragRect.width, |
448 | 0 | dragRect.height); |
449 | 0 | if (!surf) |
450 | 0 | return false; |
451 | 0 | |
452 | 0 | RefPtr<DrawTarget> dt = gfxPlatform::CreateDrawTargetForData( |
453 | 0 | cairo_image_surface_get_data(surf), |
454 | 0 | nsIntSize(dragRect.width, dragRect.height), |
455 | 0 | cairo_image_surface_get_stride(surf), |
456 | 0 | SurfaceFormat::B8G8R8A8); |
457 | 0 | if (!dt) |
458 | 0 | return false; |
459 | 0 | |
460 | 0 | dt->ClearRect(Rect(0, 0, dragRect.width, dragRect.height)); |
461 | 0 | dt->DrawSurface(aSurface, |
462 | 0 | Rect(0, 0, dragRect.width, dragRect.height), |
463 | 0 | Rect(0, 0, dragRect.width, dragRect.height), |
464 | 0 | DrawSurfaceOptions(), |
465 | 0 | DrawOptions(DRAG_IMAGE_ALPHA_LEVEL, CompositionOp::OP_SOURCE)); |
466 | 0 |
|
467 | 0 | cairo_surface_mark_dirty(surf); |
468 | 0 | cairo_surface_set_device_offset(surf, -aXOffset, -aYOffset); |
469 | 0 |
|
470 | 0 | // Ensure that the surface is drawn at the correct scale on HiDPI displays. |
471 | 0 | static auto sCairoSurfaceSetDeviceScalePtr = |
472 | 0 | (void (*)(cairo_surface_t*,double,double)) |
473 | 0 | dlsym(RTLD_DEFAULT, "cairo_surface_set_device_scale"); |
474 | 0 | if (sCairoSurfaceSetDeviceScalePtr) { |
475 | 0 | gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor(); |
476 | 0 | sCairoSurfaceSetDeviceScalePtr(surf, scale, scale); |
477 | 0 | } |
478 | 0 |
|
479 | 0 | gtk_drag_set_icon_surface(aContext, surf); |
480 | 0 | cairo_surface_destroy(surf); |
481 | 0 | return true; |
482 | 0 | } |
483 | | |
484 | | NS_IMETHODIMP |
485 | | nsDragService::StartDragSession() |
486 | 0 | { |
487 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::StartDragSession")); |
488 | 0 | return nsBaseDragService::StartDragSession(); |
489 | 0 | } |
490 | | |
491 | | NS_IMETHODIMP |
492 | | nsDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) |
493 | 0 | { |
494 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::EndDragSession %d", |
495 | 0 | aDoneDrag)); |
496 | 0 |
|
497 | 0 | if (sGrabWidget) { |
498 | 0 | g_signal_handlers_disconnect_by_func(sGrabWidget, |
499 | 0 | FuncToGpointer(OnSourceGrabEventAfter), this); |
500 | 0 | g_object_unref(sGrabWidget); |
501 | 0 | sGrabWidget = nullptr; |
502 | 0 |
|
503 | 0 | if (sMotionEventTimerID) { |
504 | 0 | g_source_remove(sMotionEventTimerID); |
505 | 0 | sMotionEventTimerID = 0; |
506 | 0 | } |
507 | 0 | if (sMotionEvent) { |
508 | 0 | gdk_event_free(sMotionEvent); |
509 | 0 | sMotionEvent = nullptr; |
510 | 0 | } |
511 | 0 | } |
512 | 0 |
|
513 | 0 | // unset our drag action |
514 | 0 | SetDragAction(DRAGDROP_ACTION_NONE); |
515 | 0 |
|
516 | 0 | // We're done with the drag context. |
517 | 0 | mTargetDragContextForRemote = nullptr; |
518 | | #ifdef MOZ_WAYLAND |
519 | | mTargetWaylandDragContextForRemote = nullptr; |
520 | | #endif |
521 | |
|
522 | 0 | return nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers); |
523 | 0 | } |
524 | | |
525 | | // nsIDragSession |
526 | | NS_IMETHODIMP |
527 | | nsDragService::SetCanDrop(bool aCanDrop) |
528 | 0 | { |
529 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SetCanDrop %d", |
530 | 0 | aCanDrop)); |
531 | 0 | mCanDrop = aCanDrop; |
532 | 0 | return NS_OK; |
533 | 0 | } |
534 | | |
535 | | NS_IMETHODIMP |
536 | | nsDragService::GetCanDrop(bool *aCanDrop) |
537 | 0 | { |
538 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetCanDrop")); |
539 | 0 | *aCanDrop = mCanDrop; |
540 | 0 | return NS_OK; |
541 | 0 | } |
542 | | |
543 | | static void |
544 | | UTF16ToNewUTF8(const char16_t* aUTF16, |
545 | | uint32_t aUTF16Len, |
546 | | char** aUTF8, |
547 | | uint32_t* aUTF8Len) |
548 | 0 | { |
549 | 0 | nsDependentSubstring utf16(aUTF16, aUTF16Len); |
550 | 0 | *aUTF8 = ToNewUTF8String(utf16, aUTF8Len); |
551 | 0 | } |
552 | | |
553 | | static void |
554 | | UTF8ToNewUTF16(const char* aUTF8, |
555 | | uint32_t aUTF8Len, |
556 | | char16_t** aUTF16, |
557 | | uint32_t* aUTF16Len) |
558 | 0 | { |
559 | 0 | nsDependentCSubstring utf8(aUTF8, aUTF8Len); |
560 | 0 | *aUTF16 = UTF8ToNewUnicode(utf8, aUTF16Len); |
561 | 0 | } |
562 | | |
563 | | // count the number of URIs in some text/uri-list format data. |
564 | | static uint32_t |
565 | | CountTextUriListItems(const char *data, |
566 | | uint32_t datalen) |
567 | 0 | { |
568 | 0 | const char *p = data; |
569 | 0 | const char *endPtr = p + datalen; |
570 | 0 | uint32_t count = 0; |
571 | 0 |
|
572 | 0 | while (p < endPtr) { |
573 | 0 | // skip whitespace (if any) |
574 | 0 | while (p < endPtr && *p != '\0' && isspace(*p)) |
575 | 0 | p++; |
576 | 0 | // if we aren't at the end of the line ... |
577 | 0 | if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') |
578 | 0 | count++; |
579 | 0 | // skip to the end of the line |
580 | 0 | while (p < endPtr && *p != '\0' && *p != '\n') |
581 | 0 | p++; |
582 | 0 | p++; // skip the actual newline as well. |
583 | 0 | } |
584 | 0 | return count; |
585 | 0 | } |
586 | | |
587 | | // extract an item from text/uri-list formatted data and convert it to |
588 | | // unicode. |
589 | | static void |
590 | | GetTextUriListItem(const char *data, |
591 | | uint32_t datalen, |
592 | | uint32_t aItemIndex, |
593 | | char16_t **convertedText, |
594 | | uint32_t *convertedTextLen) |
595 | 0 | { |
596 | 0 | const char *p = data; |
597 | 0 | const char *endPtr = p + datalen; |
598 | 0 | unsigned int count = 0; |
599 | 0 |
|
600 | 0 | *convertedText = nullptr; |
601 | 0 | while (p < endPtr) { |
602 | 0 | // skip whitespace (if any) |
603 | 0 | while (p < endPtr && *p != '\0' && isspace(*p)) |
604 | 0 | p++; |
605 | 0 | // if we aren't at the end of the line, we have a url |
606 | 0 | if (p != endPtr && *p != '\0' && *p != '\n' && *p != '\r') |
607 | 0 | count++; |
608 | 0 | // this is the item we are after ... |
609 | 0 | if (aItemIndex + 1 == count) { |
610 | 0 | const char *q = p; |
611 | 0 | while (q < endPtr && *q != '\0' && *q != '\n' && *q != '\r') |
612 | 0 | q++; |
613 | 0 | UTF8ToNewUTF16(p, q - p, convertedText, convertedTextLen); |
614 | 0 | break; |
615 | 0 | } |
616 | 0 | // skip to the end of the line |
617 | 0 | while (p < endPtr && *p != '\0' && *p != '\n') |
618 | 0 | p++; |
619 | 0 | p++; // skip the actual newline as well. |
620 | 0 | } |
621 | 0 |
|
622 | 0 | // didn't find the desired item, so just pass the whole lot |
623 | 0 | if (!*convertedText) { |
624 | 0 | UTF8ToNewUTF16(data, datalen, convertedText, convertedTextLen); |
625 | 0 | } |
626 | 0 | } |
627 | | |
628 | | NS_IMETHODIMP |
629 | | nsDragService::GetNumDropItems(uint32_t * aNumItems) |
630 | 0 | { |
631 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetNumDropItems")); |
632 | 0 |
|
633 | 0 | if (!mTargetWidget) { |
634 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
635 | 0 | ("*** warning: GetNumDropItems \ |
636 | 0 | called without a valid target widget!\n")); |
637 | 0 | *aNumItems = 0; |
638 | 0 | return NS_OK; |
639 | 0 | } |
640 | 0 |
|
641 | | #ifdef MOZ_WAYLAND |
642 | | // TODO: Wayland implementation of text/uri-list. |
643 | | if (!mTargetDragContext) { |
644 | | *aNumItems = 1; |
645 | | return NS_OK; |
646 | | } |
647 | | #endif |
648 | | |
649 | 0 | bool isList = IsTargetContextList(); |
650 | 0 | if (isList) |
651 | 0 | mSourceDataItems->GetLength(aNumItems); |
652 | 0 | else { |
653 | 0 | GdkAtom gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
654 | 0 | GetTargetDragData(gdkFlavor); |
655 | 0 | if (mTargetDragData) { |
656 | 0 | const char *data = reinterpret_cast<char*>(mTargetDragData); |
657 | 0 | *aNumItems = CountTextUriListItems(data, mTargetDragDataLen); |
658 | 0 | } else |
659 | 0 | *aNumItems = 1; |
660 | 0 | } |
661 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("%d items", *aNumItems)); |
662 | 0 | return NS_OK; |
663 | 0 | } |
664 | | |
665 | | |
666 | | NS_IMETHODIMP |
667 | | nsDragService::GetData(nsITransferable * aTransferable, |
668 | | uint32_t aItemIndex) |
669 | 0 | { |
670 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::GetData %d", aItemIndex)); |
671 | 0 |
|
672 | 0 | // make sure that we have a transferable |
673 | 0 | if (!aTransferable) |
674 | 0 | return NS_ERROR_INVALID_ARG; |
675 | 0 | |
676 | 0 | if (!mTargetWidget) { |
677 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
678 | 0 | ("*** warning: GetData \ |
679 | 0 | called without a valid target widget!\n")); |
680 | 0 | return NS_ERROR_FAILURE; |
681 | 0 | } |
682 | 0 |
|
683 | 0 | // get flavor list that includes all acceptable flavors (including |
684 | 0 | // ones obtained through conversion). Flavors are nsISupportsStrings |
685 | 0 | // so that they can be seen from JS. |
686 | 0 | nsCOMPtr<nsIArray> flavorList; |
687 | 0 | nsresult rv = aTransferable->FlavorsTransferableCanImport( |
688 | 0 | getter_AddRefs(flavorList)); |
689 | 0 | if (NS_FAILED(rv)) |
690 | 0 | return rv; |
691 | 0 | |
692 | 0 | // count the number of flavors |
693 | 0 | uint32_t cnt; |
694 | 0 | flavorList->GetLength(&cnt); |
695 | 0 | unsigned int i; |
696 | 0 |
|
697 | 0 | // check to see if this is an internal list |
698 | 0 | bool isList = IsTargetContextList(); |
699 | 0 |
|
700 | 0 | if (isList) { |
701 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("it's a list...")); |
702 | 0 | // find a matching flavor |
703 | 0 | for (i = 0; i < cnt; ++i) { |
704 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
705 | 0 | currentFlavor = do_QueryElementAt(flavorList, i); |
706 | 0 | if (!currentFlavor) |
707 | 0 | continue; |
708 | 0 | |
709 | 0 | nsCString flavorStr; |
710 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
711 | 0 | MOZ_LOG(sDragLm, |
712 | 0 | LogLevel::Debug, |
713 | 0 | ("flavor is %s\n", flavorStr.get())); |
714 | 0 | // get the item with the right index |
715 | 0 | nsCOMPtr<nsITransferable> item = |
716 | 0 | do_QueryElementAt(mSourceDataItems, aItemIndex); |
717 | 0 | if (!item) |
718 | 0 | continue; |
719 | 0 | |
720 | 0 | nsCOMPtr<nsISupports> data; |
721 | 0 | uint32_t tmpDataLen = 0; |
722 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
723 | 0 | ("trying to get transfer data for %s\n", |
724 | 0 | flavorStr.get())); |
725 | 0 | rv = item->GetTransferData(flavorStr.get(), |
726 | 0 | getter_AddRefs(data), |
727 | 0 | &tmpDataLen); |
728 | 0 | if (NS_FAILED(rv)) { |
729 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("failed.\n")); |
730 | 0 | continue; |
731 | 0 | } |
732 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("succeeded.\n")); |
733 | 0 | rv = aTransferable->SetTransferData(flavorStr.get(), data, |
734 | 0 | tmpDataLen); |
735 | 0 | if (NS_FAILED(rv)) { |
736 | 0 | MOZ_LOG(sDragLm, |
737 | 0 | LogLevel::Debug, |
738 | 0 | ("fail to set transfer data into transferable!\n")); |
739 | 0 | continue; |
740 | 0 | } |
741 | 0 | // ok, we got the data |
742 | 0 | return NS_OK; |
743 | 0 | } |
744 | 0 | // if we got this far, we failed |
745 | 0 | return NS_ERROR_FAILURE; |
746 | 0 | } |
747 | 0 | |
748 | 0 | // Now walk down the list of flavors. When we find one that is |
749 | 0 | // actually present, copy out the data into the transferable in that |
750 | 0 | // format. SetTransferData() implicitly handles conversions. |
751 | 0 | for ( i = 0; i < cnt; ++i ) { |
752 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
753 | 0 | currentFlavor = do_QueryElementAt(flavorList, i); |
754 | 0 | if (currentFlavor) { |
755 | 0 | // find our gtk flavor |
756 | 0 | nsCString flavorStr; |
757 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
758 | 0 | GdkAtom gdkFlavor = gdk_atom_intern(flavorStr.get(), FALSE); |
759 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
760 | 0 | ("looking for data in type %s, gdk flavor %p\n", |
761 | 0 | flavorStr.get(), gdkFlavor)); |
762 | 0 | bool dataFound = false; |
763 | 0 | if (gdkFlavor) { |
764 | 0 | GetTargetDragData(gdkFlavor); |
765 | 0 | } |
766 | 0 | if (mTargetDragData) { |
767 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = true\n")); |
768 | 0 | dataFound = true; |
769 | 0 | } |
770 | 0 | else { |
771 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound = false\n")); |
772 | 0 |
|
773 | 0 | // Dragging and dropping from the file manager would cause us |
774 | 0 | // to parse the source text as a nsIFile URL. |
775 | 0 | if (flavorStr.EqualsLiteral(kFileMime)) { |
776 | 0 | gdkFlavor = gdk_atom_intern(kTextMime, FALSE); |
777 | 0 | GetTargetDragData(gdkFlavor); |
778 | 0 | if (!mTargetDragData) { |
779 | 0 | gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
780 | 0 | GetTargetDragData(gdkFlavor); |
781 | 0 | } |
782 | 0 | if (mTargetDragData) { |
783 | 0 | const char* text = static_cast<char*>(mTargetDragData); |
784 | 0 | char16_t* convertedText = nullptr; |
785 | 0 | uint32_t convertedTextLen = 0; |
786 | 0 |
|
787 | 0 | GetTextUriListItem(text, mTargetDragDataLen, aItemIndex, |
788 | 0 | &convertedText, &convertedTextLen); |
789 | 0 |
|
790 | 0 | if (convertedText) { |
791 | 0 | nsCOMPtr<nsIIOService> ioService = do_GetIOService(&rv); |
792 | 0 | nsCOMPtr<nsIURI> fileURI; |
793 | 0 | rv = ioService->NewURI(NS_ConvertUTF16toUTF8(convertedText), |
794 | 0 | nullptr, nullptr, getter_AddRefs(fileURI)); |
795 | 0 | if (NS_SUCCEEDED(rv)) { |
796 | 0 | nsCOMPtr<nsIFileURL> fileURL = do_QueryInterface(fileURI, &rv); |
797 | 0 | if (NS_SUCCEEDED(rv)) { |
798 | 0 | nsCOMPtr<nsIFile> file; |
799 | 0 | rv = fileURL->GetFile(getter_AddRefs(file)); |
800 | 0 | if (NS_SUCCEEDED(rv)) { |
801 | 0 | // The common wrapping code at the end of |
802 | 0 | // this function assumes the data is text |
803 | 0 | // and calls text-specific operations. |
804 | 0 | // Make a secret hideout here for nsIFile |
805 | 0 | // objects and return early. |
806 | 0 | aTransferable->SetTransferData(flavorStr.get(), file, |
807 | 0 | convertedTextLen); |
808 | 0 | g_free(convertedText); |
809 | 0 | return NS_OK; |
810 | 0 | } |
811 | 0 | } |
812 | 0 | } |
813 | 0 | g_free(convertedText); |
814 | 0 | } |
815 | 0 | continue; |
816 | 0 | } |
817 | 0 | } |
818 | 0 | |
819 | 0 | // if we are looking for text/unicode and we fail to find it |
820 | 0 | // on the clipboard first, try again with text/plain. If that |
821 | 0 | // is present, convert it to unicode. |
822 | 0 | if (flavorStr.EqualsLiteral(kUnicodeMime)) { |
823 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
824 | 0 | ("we were looking for text/unicode... \ |
825 | 0 | trying with text/plain;charset=utf-8\n")); |
826 | 0 | gdkFlavor = gdk_atom_intern(gTextPlainUTF8Type, FALSE); |
827 | 0 | GetTargetDragData(gdkFlavor); |
828 | 0 | if (mTargetDragData) { |
829 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n")); |
830 | 0 | const char* castedText = |
831 | 0 | reinterpret_cast<char*>(mTargetDragData); |
832 | 0 | char16_t* convertedText = nullptr; |
833 | 0 | NS_ConvertUTF8toUTF16 ucs2string(castedText, |
834 | 0 | mTargetDragDataLen); |
835 | 0 | convertedText = ToNewUnicode(ucs2string); |
836 | 0 | if ( convertedText ) { |
837 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
838 | 0 | ("successfully converted plain text \ |
839 | 0 | to unicode.\n")); |
840 | 0 | // out with the old, in with the new |
841 | 0 | g_free(mTargetDragData); |
842 | 0 | mTargetDragData = convertedText; |
843 | 0 | mTargetDragDataLen = ucs2string.Length() * 2; |
844 | 0 | dataFound = true; |
845 | 0 | } // if plain text data on clipboard |
846 | 0 | } else { |
847 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
848 | 0 | ("we were looking for text/unicode... \ |
849 | 0 | trying again with text/plain\n")); |
850 | 0 | gdkFlavor = gdk_atom_intern(kTextMime, FALSE); |
851 | 0 | GetTargetDragData(gdkFlavor); |
852 | 0 | if (mTargetDragData) { |
853 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("Got textplain data\n")); |
854 | 0 | const char* castedText = |
855 | 0 | reinterpret_cast<char*>(mTargetDragData); |
856 | 0 | char16_t* convertedText = nullptr; |
857 | 0 | uint32_t convertedTextLen = 0; |
858 | 0 | UTF8ToNewUTF16(castedText, mTargetDragDataLen, |
859 | 0 | &convertedText, &convertedTextLen); |
860 | 0 | if ( convertedText ) { |
861 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
862 | 0 | ("successfully converted plain text \ |
863 | 0 | to unicode.\n")); |
864 | 0 | // out with the old, in with the new |
865 | 0 | g_free(mTargetDragData); |
866 | 0 | mTargetDragData = convertedText; |
867 | 0 | mTargetDragDataLen = convertedTextLen * 2; |
868 | 0 | dataFound = true; |
869 | 0 | } // if plain text data on clipboard |
870 | 0 | } // if plain text flavor present |
871 | 0 | } // if plain text charset=utf-8 flavor present |
872 | 0 | } // if looking for text/unicode |
873 | 0 |
|
874 | 0 | // if we are looking for text/x-moz-url and we failed to find |
875 | 0 | // it on the clipboard, try again with text/uri-list, and then |
876 | 0 | // _NETSCAPE_URL |
877 | 0 | if (flavorStr.EqualsLiteral(kURLMime)) { |
878 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
879 | 0 | ("we were looking for text/x-moz-url...\ |
880 | 0 | trying again with text/uri-list\n")); |
881 | 0 | gdkFlavor = gdk_atom_intern(gTextUriListType, FALSE); |
882 | 0 | GetTargetDragData(gdkFlavor); |
883 | 0 | if (mTargetDragData) { |
884 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
885 | 0 | ("Got text/uri-list data\n")); |
886 | 0 | const char *data = |
887 | 0 | reinterpret_cast<char*>(mTargetDragData); |
888 | 0 | char16_t* convertedText = nullptr; |
889 | 0 | uint32_t convertedTextLen = 0; |
890 | 0 |
|
891 | 0 | GetTextUriListItem(data, mTargetDragDataLen, aItemIndex, |
892 | 0 | &convertedText, &convertedTextLen); |
893 | 0 |
|
894 | 0 | if ( convertedText ) { |
895 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
896 | 0 | ("successfully converted \ |
897 | 0 | _NETSCAPE_URL to unicode.\n")); |
898 | 0 | // out with the old, in with the new |
899 | 0 | g_free(mTargetDragData); |
900 | 0 | mTargetDragData = convertedText; |
901 | 0 | mTargetDragDataLen = convertedTextLen * 2; |
902 | 0 | dataFound = true; |
903 | 0 | } |
904 | 0 | } |
905 | 0 | else { |
906 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
907 | 0 | ("failed to get text/uri-list data\n")); |
908 | 0 | } |
909 | 0 | if (!dataFound) { |
910 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
911 | 0 | ("we were looking for text/x-moz-url...\ |
912 | 0 | trying again with _NETSCAP_URL\n")); |
913 | 0 | gdkFlavor = gdk_atom_intern(gMozUrlType, FALSE); |
914 | 0 | GetTargetDragData(gdkFlavor); |
915 | 0 | if (mTargetDragData) { |
916 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
917 | 0 | ("Got _NETSCAPE_URL data\n")); |
918 | 0 | const char* castedText = |
919 | 0 | reinterpret_cast<char*>(mTargetDragData); |
920 | 0 | char16_t* convertedText = nullptr; |
921 | 0 | uint32_t convertedTextLen = 0; |
922 | 0 | UTF8ToNewUTF16(castedText, mTargetDragDataLen, &convertedText, &convertedTextLen); |
923 | 0 | if ( convertedText ) { |
924 | 0 | MOZ_LOG(sDragLm, |
925 | 0 | LogLevel::Debug, |
926 | 0 | ("successfully converted _NETSCAPE_URL \ |
927 | 0 | to unicode.\n")); |
928 | 0 | // out with the old, in with the new |
929 | 0 | g_free(mTargetDragData); |
930 | 0 | mTargetDragData = convertedText; |
931 | 0 | mTargetDragDataLen = convertedTextLen * 2; |
932 | 0 | dataFound = true; |
933 | 0 | } |
934 | 0 | } |
935 | 0 | else { |
936 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
937 | 0 | ("failed to get _NETSCAPE_URL data\n")); |
938 | 0 | } |
939 | 0 | } |
940 | 0 | } |
941 | 0 |
|
942 | 0 | } // else we try one last ditch effort to find our data |
943 | 0 |
|
944 | 0 | if (dataFound) { |
945 | 0 | if (!flavorStr.EqualsLiteral(kCustomTypesMime)) { |
946 | 0 | // the DOM only wants LF, so convert from MacOS line endings |
947 | 0 | // to DOM line endings. |
948 | 0 | nsLinebreakHelpers::ConvertPlatformToDOMLinebreaks( |
949 | 0 | flavorStr, |
950 | 0 | &mTargetDragData, |
951 | 0 | reinterpret_cast<int*>(&mTargetDragDataLen)); |
952 | 0 | } |
953 | 0 |
|
954 | 0 | // put it into the transferable. |
955 | 0 | nsCOMPtr<nsISupports> genericDataWrapper; |
956 | 0 | nsPrimitiveHelpers::CreatePrimitiveForData(flavorStr, |
957 | 0 | mTargetDragData, mTargetDragDataLen, |
958 | 0 | getter_AddRefs(genericDataWrapper)); |
959 | 0 | aTransferable->SetTransferData(flavorStr.get(), |
960 | 0 | genericDataWrapper, |
961 | 0 | mTargetDragDataLen); |
962 | 0 | // we found one, get out of this loop! |
963 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("dataFound and converted!\n")); |
964 | 0 | break; |
965 | 0 | } |
966 | 0 | } // if (currentFlavor) |
967 | 0 | } // foreach flavor |
968 | 0 |
|
969 | 0 | return NS_OK; |
970 | 0 |
|
971 | 0 | } |
972 | | |
973 | | NS_IMETHODIMP |
974 | | nsDragService::IsDataFlavorSupported(const char *aDataFlavor, |
975 | | bool *_retval) |
976 | 0 | { |
977 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::IsDataFlavorSupported %s", |
978 | 0 | aDataFlavor)); |
979 | 0 | if (!_retval) |
980 | 0 | return NS_ERROR_INVALID_ARG; |
981 | 0 | |
982 | 0 | // set this to no by default |
983 | 0 | *_retval = false; |
984 | 0 |
|
985 | 0 | // check to make sure that we have a drag object set, here |
986 | 0 | if (!mTargetWidget) { |
987 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
988 | 0 | ("*** warning: IsDataFlavorSupported \ |
989 | 0 | called without a valid target widget!\n")); |
990 | 0 | return NS_OK; |
991 | 0 | } |
992 | 0 |
|
993 | 0 | // check to see if the target context is a list. |
994 | 0 | bool isList = IsTargetContextList(); |
995 | 0 | // if it is, just look in the internal data since we are the source |
996 | 0 | // for it. |
997 | 0 | if (isList) { |
998 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("It's a list..")); |
999 | 0 | uint32_t numDragItems = 0; |
1000 | 0 | // if we don't have mDataItems we didn't start this drag so it's |
1001 | 0 | // an external client trying to fool us. |
1002 | 0 | if (!mSourceDataItems) |
1003 | 0 | return NS_OK; |
1004 | 0 | mSourceDataItems->GetLength(&numDragItems); |
1005 | 0 | for (uint32_t itemIndex = 0; itemIndex < numDragItems; ++itemIndex) { |
1006 | 0 | nsCOMPtr<nsITransferable> currItem = |
1007 | 0 | do_QueryElementAt(mSourceDataItems, itemIndex); |
1008 | 0 | if (currItem) { |
1009 | 0 | nsCOMPtr <nsIArray> flavorList; |
1010 | 0 | currItem->FlavorsTransferableCanExport( |
1011 | 0 | getter_AddRefs(flavorList)); |
1012 | 0 | if (flavorList) { |
1013 | 0 | uint32_t numFlavors; |
1014 | 0 | flavorList->GetLength( &numFlavors ); |
1015 | 0 | for ( uint32_t flavorIndex = 0; |
1016 | 0 | flavorIndex < numFlavors ; |
1017 | 0 | ++flavorIndex ) { |
1018 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
1019 | 0 | currentFlavor = do_QueryElementAt(flavorList, flavorIndex); |
1020 | 0 | if (currentFlavor) { |
1021 | 0 | nsCString flavorStr; |
1022 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
1023 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1024 | 0 | ("checking %s against %s\n", |
1025 | 0 | flavorStr.get(), aDataFlavor)); |
1026 | 0 | if (flavorStr.Equals(aDataFlavor)) { |
1027 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1028 | 0 | ("boioioioiooioioioing!\n")); |
1029 | 0 | *_retval = true; |
1030 | 0 | } |
1031 | 0 | } |
1032 | 0 | } |
1033 | 0 | } |
1034 | 0 | } |
1035 | 0 | } |
1036 | 0 | return NS_OK; |
1037 | 0 | } |
1038 | 0 |
|
1039 | 0 | // check the target context vs. this flavor, one at a time |
1040 | 0 | GList *tmp = nullptr; |
1041 | 0 | if (mTargetDragContext) { |
1042 | 0 | tmp = gdk_drag_context_list_targets(mTargetDragContext); |
1043 | 0 | } |
1044 | | #ifdef MOZ_WAYLAND |
1045 | | else if (mTargetWaylandDragContext) { |
1046 | | tmp = mTargetWaylandDragContext->GetTargets(); |
1047 | | } |
1048 | | GList *tmp_head = tmp; |
1049 | | #endif |
1050 | |
|
1051 | 0 | for (; tmp; tmp = tmp->next) { |
1052 | 0 | /* Bug 331198 */ |
1053 | 0 | GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); |
1054 | 0 | gchar *name = nullptr; |
1055 | 0 | name = gdk_atom_name(atom); |
1056 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1057 | 0 | ("checking %s against %s\n", name, aDataFlavor)); |
1058 | 0 | if (name && (strcmp(name, aDataFlavor) == 0)) { |
1059 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("good!\n")); |
1060 | 0 | *_retval = true; |
1061 | 0 | } |
1062 | 0 | // check for automatic text/uri-list -> text/x-moz-url mapping |
1063 | 0 | if (!*_retval && |
1064 | 0 | name && |
1065 | 0 | (strcmp(name, gTextUriListType) == 0) && |
1066 | 0 | (strcmp(aDataFlavor, kURLMime) == 0 || |
1067 | 0 | strcmp(aDataFlavor, kFileMime) == 0)) { |
1068 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1069 | 0 | ("good! ( it's text/uri-list and \ |
1070 | 0 | we're checking against text/x-moz-url )\n")); |
1071 | 0 | *_retval = true; |
1072 | 0 | } |
1073 | 0 | // check for automatic _NETSCAPE_URL -> text/x-moz-url mapping |
1074 | 0 | if (!*_retval && |
1075 | 0 | name && |
1076 | 0 | (strcmp(name, gMozUrlType) == 0) && |
1077 | 0 | (strcmp(aDataFlavor, kURLMime) == 0)) { |
1078 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1079 | 0 | ("good! ( it's _NETSCAPE_URL and \ |
1080 | 0 | we're checking against text/x-moz-url )\n")); |
1081 | 0 | *_retval = true; |
1082 | 0 | } |
1083 | 0 | // check for auto text/plain -> text/unicode mapping |
1084 | 0 | if (!*_retval && |
1085 | 0 | name && |
1086 | 0 | (strcmp(name, kTextMime) == 0) && |
1087 | 0 | ((strcmp(aDataFlavor, kUnicodeMime) == 0) || |
1088 | 0 | (strcmp(aDataFlavor, kFileMime) == 0))) { |
1089 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1090 | 0 | ("good! ( it's text plain and we're checking \ |
1091 | 0 | against text/unicode or application/x-moz-file)\n")); |
1092 | 0 | *_retval = true; |
1093 | 0 | } |
1094 | 0 | g_free(name); |
1095 | 0 | } |
1096 | 0 |
|
1097 | | #ifdef MOZ_WAYLAND |
1098 | | // mTargetWaylandDragContext->GetTargets allocates the list |
1099 | | // so we need to free it here. |
1100 | | if (!mTargetDragContext && tmp_head) { |
1101 | | g_list_free(tmp_head); |
1102 | | } |
1103 | | #endif |
1104 | |
|
1105 | 0 | return NS_OK; |
1106 | 0 | } |
1107 | | |
1108 | | void |
1109 | | nsDragService::ReplyToDragMotion(GdkDragContext* aDragContext) |
1110 | 0 | { |
1111 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1112 | 0 | ("nsDragService::ReplyToDragMotion %d", mCanDrop)); |
1113 | 0 |
|
1114 | 0 | GdkDragAction action = (GdkDragAction)0; |
1115 | 0 | if (mCanDrop) { |
1116 | 0 | // notify the dragger if we can drop |
1117 | 0 | switch (mDragAction) { |
1118 | 0 | case DRAGDROP_ACTION_COPY: |
1119 | 0 | action = GDK_ACTION_COPY; |
1120 | 0 | break; |
1121 | 0 | case DRAGDROP_ACTION_LINK: |
1122 | 0 | action = GDK_ACTION_LINK; |
1123 | 0 | break; |
1124 | 0 | case DRAGDROP_ACTION_NONE: |
1125 | 0 | action = (GdkDragAction)0; |
1126 | 0 | break; |
1127 | 0 | default: |
1128 | 0 | action = GDK_ACTION_MOVE; |
1129 | 0 | break; |
1130 | 0 | } |
1131 | 0 | } |
1132 | 0 | |
1133 | 0 | gdk_drag_status(aDragContext, action, mTargetTime); |
1134 | 0 | } |
1135 | | |
1136 | | #ifdef MOZ_WAYLAND |
1137 | | void |
1138 | | nsDragService::ReplyToDragMotion(nsWaylandDragContext* aDragContext) |
1139 | | { |
1140 | | MOZ_LOG(sDragLm, LogLevel::Debug, |
1141 | | ("nsDragService::ReplyToDragMotion %d", mCanDrop)); |
1142 | | |
1143 | | GdkDragAction action = (GdkDragAction)0; |
1144 | | if (mCanDrop) { |
1145 | | // notify the dragger if we can drop |
1146 | | switch (mDragAction) { |
1147 | | case DRAGDROP_ACTION_COPY: |
1148 | | action = GDK_ACTION_COPY; |
1149 | | break; |
1150 | | case DRAGDROP_ACTION_LINK: |
1151 | | action = GDK_ACTION_LINK; |
1152 | | break; |
1153 | | case DRAGDROP_ACTION_NONE: |
1154 | | action = (GdkDragAction)0; |
1155 | | break; |
1156 | | default: |
1157 | | action = GDK_ACTION_MOVE; |
1158 | | break; |
1159 | | } |
1160 | | } |
1161 | | |
1162 | | aDragContext->SetDragStatus(action); |
1163 | | } |
1164 | | #endif |
1165 | | |
1166 | | void |
1167 | | nsDragService::TargetDataReceived(GtkWidget *aWidget, |
1168 | | GdkDragContext *aContext, |
1169 | | gint aX, |
1170 | | gint aY, |
1171 | | GtkSelectionData *aSelectionData, |
1172 | | guint aInfo, |
1173 | | guint32 aTime) |
1174 | 0 | { |
1175 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::TargetDataReceived")); |
1176 | 0 | TargetResetData(); |
1177 | 0 | mTargetDragDataReceived = true; |
1178 | 0 | gint len = gtk_selection_data_get_length(aSelectionData); |
1179 | 0 | const guchar* data = gtk_selection_data_get_data(aSelectionData); |
1180 | 0 | if (len > 0 && data) { |
1181 | 0 | mTargetDragDataLen = len; |
1182 | 0 | mTargetDragData = g_malloc(mTargetDragDataLen); |
1183 | 0 | memcpy(mTargetDragData, data, mTargetDragDataLen); |
1184 | 0 | } |
1185 | 0 | else { |
1186 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1187 | 0 | ("Failed to get data. selection data len was %d\n", |
1188 | 0 | mTargetDragDataLen)); |
1189 | 0 | } |
1190 | 0 | } |
1191 | | |
1192 | | bool |
1193 | | nsDragService::IsTargetContextList(void) |
1194 | 0 | { |
1195 | 0 | bool retval = false; |
1196 | 0 |
|
1197 | | #ifdef MOZ_WAYLAND |
1198 | | // TODO: We need a wayland implementation here. |
1199 | | if (!mTargetDragContext) |
1200 | | return retval; |
1201 | | #endif |
1202 | |
|
1203 | 0 | // gMimeListType drags only work for drags within a single process. The |
1204 | 0 | // gtk_drag_get_source_widget() function will return nullptr if the source |
1205 | 0 | // of the drag is another app, so we use it to check if a gMimeListType |
1206 | 0 | // drop will work or not. |
1207 | 0 | if (gtk_drag_get_source_widget(mTargetDragContext) == nullptr) |
1208 | 0 | return retval; |
1209 | 0 | |
1210 | 0 | GList *tmp; |
1211 | 0 |
|
1212 | 0 | // walk the list of context targets and see if one of them is a list |
1213 | 0 | // of items. |
1214 | 0 | for (tmp = gdk_drag_context_list_targets(mTargetDragContext); |
1215 | 0 | tmp; tmp = tmp->next) { |
1216 | 0 | /* Bug 331198 */ |
1217 | 0 | GdkAtom atom = GDK_POINTER_TO_ATOM(tmp->data); |
1218 | 0 | gchar *name = nullptr; |
1219 | 0 | name = gdk_atom_name(atom); |
1220 | 0 | if (name && strcmp(name, gMimeListType) == 0) |
1221 | 0 | retval = true; |
1222 | 0 | g_free(name); |
1223 | 0 | if (retval) |
1224 | 0 | break; |
1225 | 0 | } |
1226 | 0 | return retval; |
1227 | 0 | } |
1228 | | |
1229 | | // Maximum time to wait for a "drag_received" arrived, in microseconds |
1230 | 0 | #define NS_DND_TIMEOUT 500000 |
1231 | | |
1232 | | void |
1233 | | nsDragService::GetTargetDragData(GdkAtom aFlavor) |
1234 | 0 | { |
1235 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("getting data flavor %p\n", aFlavor)); |
1236 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("mLastWidget is %p and mLastContext is %p\n", |
1237 | 0 | mTargetWidget.get(), |
1238 | 0 | mTargetDragContext.get())); |
1239 | 0 | // reset our target data areas |
1240 | 0 | TargetResetData(); |
1241 | 0 |
|
1242 | 0 | if (mTargetDragContext) { |
1243 | 0 | gtk_drag_get_data(mTargetWidget, mTargetDragContext, aFlavor, mTargetTime); |
1244 | 0 |
|
1245 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("about to start inner iteration.")); |
1246 | 0 | PRTime entryTime = PR_Now(); |
1247 | 0 | while (!mTargetDragDataReceived && mDoingDrag) { |
1248 | 0 | // check the number of iterations |
1249 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("doing iteration...\n")); |
1250 | 0 | PR_Sleep(20*PR_TicksPerSecond()/1000); /* sleep for 20 ms/iteration */ |
1251 | 0 | if (PR_Now()-entryTime > NS_DND_TIMEOUT) break; |
1252 | 0 | gtk_main_iteration(); |
1253 | 0 | } |
1254 | 0 | } |
1255 | | #ifdef MOZ_WAYLAND |
1256 | | else { |
1257 | | mTargetDragData = |
1258 | | mTargetWaylandDragContext->GetData(gdk_atom_name(aFlavor), |
1259 | | &mTargetDragDataLen); |
1260 | | mTargetDragDataReceived = true; |
1261 | | } |
1262 | | #endif |
1263 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("finished inner iteration\n")); |
1264 | 0 | } |
1265 | | |
1266 | | void |
1267 | | nsDragService::TargetResetData(void) |
1268 | 0 | { |
1269 | 0 | mTargetDragDataReceived = false; |
1270 | 0 | // make sure to free old data if we have to |
1271 | 0 | g_free(mTargetDragData); |
1272 | 0 | mTargetDragData = 0; |
1273 | 0 | mTargetDragDataLen = 0; |
1274 | 0 | } |
1275 | | |
1276 | | GtkTargetList * |
1277 | | nsDragService::GetSourceList(void) |
1278 | 0 | { |
1279 | 0 | if (!mSourceDataItems) |
1280 | 0 | return nullptr; |
1281 | 0 | nsTArray<GtkTargetEntry*> targetArray; |
1282 | 0 | GtkTargetEntry *targets; |
1283 | 0 | GtkTargetList *targetList = 0; |
1284 | 0 | uint32_t targetCount = 0; |
1285 | 0 | unsigned int numDragItems = 0; |
1286 | 0 |
|
1287 | 0 | mSourceDataItems->GetLength(&numDragItems); |
1288 | 0 |
|
1289 | 0 | // Check to see if we're dragging > 1 item. |
1290 | 0 | if (numDragItems > 1) { |
1291 | 0 | // as the Xdnd protocol only supports a single item (or is it just |
1292 | 0 | // gtk's implementation?), we don't advertise all flavours listed |
1293 | 0 | // in the nsITransferable. |
1294 | 0 |
|
1295 | 0 | // the application/x-moz-internal-item-list format, which preserves |
1296 | 0 | // all information for drags within the same mozilla instance. |
1297 | 0 | GtkTargetEntry *listTarget = |
1298 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1299 | 0 | listTarget->target = g_strdup(gMimeListType); |
1300 | 0 | listTarget->flags = 0; |
1301 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1302 | 0 | ("automatically adding target %s\n", listTarget->target)); |
1303 | 0 | targetArray.AppendElement(listTarget); |
1304 | 0 |
|
1305 | 0 | // check what flavours are supported so we can decide what other |
1306 | 0 | // targets to advertise. |
1307 | 0 | nsCOMPtr<nsITransferable> currItem = |
1308 | 0 | do_QueryElementAt(mSourceDataItems, 0); |
1309 | 0 |
|
1310 | 0 | if (currItem) { |
1311 | 0 | nsCOMPtr <nsIArray> flavorList; |
1312 | 0 | currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); |
1313 | 0 | if (flavorList) { |
1314 | 0 | uint32_t numFlavors; |
1315 | 0 | flavorList->GetLength( &numFlavors ); |
1316 | 0 | for (uint32_t flavorIndex = 0; |
1317 | 0 | flavorIndex < numFlavors ; |
1318 | 0 | ++flavorIndex ) { |
1319 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
1320 | 0 | currentFlavor = do_QueryElementAt(flavorList, flavorIndex); |
1321 | 0 | if (currentFlavor) { |
1322 | 0 | nsCString flavorStr; |
1323 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
1324 | 0 |
|
1325 | 0 | // check if text/x-moz-url is supported. |
1326 | 0 | // If so, advertise |
1327 | 0 | // text/uri-list. |
1328 | 0 | if (flavorStr.EqualsLiteral(kURLMime)) { |
1329 | 0 | listTarget = |
1330 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1331 | 0 | listTarget->target = g_strdup(gTextUriListType); |
1332 | 0 | listTarget->flags = 0; |
1333 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1334 | 0 | ("automatically adding target %s\n", |
1335 | 0 | listTarget->target)); |
1336 | 0 | targetArray.AppendElement(listTarget); |
1337 | 0 | } |
1338 | 0 | } |
1339 | 0 | } // foreach flavor in item |
1340 | 0 | } // if valid flavor list |
1341 | 0 | } // if item is a transferable |
1342 | 0 | } else if (numDragItems == 1) { |
1343 | 0 | nsCOMPtr<nsITransferable> currItem = |
1344 | 0 | do_QueryElementAt(mSourceDataItems, 0); |
1345 | 0 | if (currItem) { |
1346 | 0 | nsCOMPtr <nsIArray> flavorList; |
1347 | 0 | currItem->FlavorsTransferableCanExport(getter_AddRefs(flavorList)); |
1348 | 0 | if (flavorList) { |
1349 | 0 | uint32_t numFlavors; |
1350 | 0 | flavorList->GetLength( &numFlavors ); |
1351 | 0 | for (uint32_t flavorIndex = 0; |
1352 | 0 | flavorIndex < numFlavors ; |
1353 | 0 | ++flavorIndex ) { |
1354 | 0 | nsCOMPtr<nsISupportsCString> currentFlavor; |
1355 | 0 | currentFlavor = do_QueryElementAt(flavorList, flavorIndex); |
1356 | 0 | if (currentFlavor) { |
1357 | 0 | nsCString flavorStr; |
1358 | 0 | currentFlavor->ToString(getter_Copies(flavorStr)); |
1359 | 0 | GtkTargetEntry *target = |
1360 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1361 | 0 | target->target = g_strdup(flavorStr.get()); |
1362 | 0 | target->flags = 0; |
1363 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1364 | 0 | ("adding target %s\n", target->target)); |
1365 | 0 | targetArray.AppendElement(target); |
1366 | 0 |
|
1367 | 0 | // If there is a file, add the text/uri-list type. |
1368 | 0 | if (flavorStr.EqualsLiteral(kFileMime)) { |
1369 | 0 | GtkTargetEntry *urilistTarget = |
1370 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1371 | 0 | urilistTarget->target = g_strdup(gTextUriListType); |
1372 | 0 | urilistTarget->flags = 0; |
1373 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1374 | 0 | ("automatically adding target %s\n", |
1375 | 0 | urilistTarget->target)); |
1376 | 0 | targetArray.AppendElement(urilistTarget); |
1377 | 0 | } |
1378 | 0 | // Check to see if this is text/unicode. |
1379 | 0 | // If it is, add text/plain |
1380 | 0 | // since we automatically support text/plain |
1381 | 0 | // if we support text/unicode. |
1382 | 0 | else if (flavorStr.EqualsLiteral(kUnicodeMime)) { |
1383 | 0 | GtkTargetEntry *plainUTF8Target = |
1384 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1385 | 0 | plainUTF8Target->target = g_strdup(gTextPlainUTF8Type); |
1386 | 0 | plainUTF8Target->flags = 0; |
1387 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1388 | 0 | ("automatically adding target %s\n", |
1389 | 0 | plainUTF8Target->target)); |
1390 | 0 | targetArray.AppendElement(plainUTF8Target); |
1391 | 0 |
|
1392 | 0 | GtkTargetEntry *plainTarget = |
1393 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1394 | 0 | plainTarget->target = g_strdup(kTextMime); |
1395 | 0 | plainTarget->flags = 0; |
1396 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1397 | 0 | ("automatically adding target %s\n", |
1398 | 0 | plainTarget->target)); |
1399 | 0 | targetArray.AppendElement(plainTarget); |
1400 | 0 | } |
1401 | 0 | // Check to see if this is the x-moz-url type. |
1402 | 0 | // If it is, add _NETSCAPE_URL |
1403 | 0 | // this is a type used by everybody. |
1404 | 0 | else if (flavorStr.EqualsLiteral(kURLMime)) { |
1405 | 0 | GtkTargetEntry *urlTarget = |
1406 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry)); |
1407 | 0 | urlTarget->target = g_strdup(gMozUrlType); |
1408 | 0 | urlTarget->flags = 0; |
1409 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1410 | 0 | ("automatically adding target %s\n", |
1411 | 0 | urlTarget->target)); |
1412 | 0 | targetArray.AppendElement(urlTarget); |
1413 | 0 | } |
1414 | 0 | } |
1415 | 0 | } // foreach flavor in item |
1416 | 0 | } // if valid flavor list |
1417 | 0 | } // if item is a transferable |
1418 | 0 | } // if it is a single item drag |
1419 | 0 |
|
1420 | 0 | // get all the elements that we created. |
1421 | 0 | targetCount = targetArray.Length(); |
1422 | 0 | if (targetCount) { |
1423 | 0 | // allocate space to create the list of valid targets |
1424 | 0 | targets = |
1425 | 0 | (GtkTargetEntry *)g_malloc(sizeof(GtkTargetEntry) * targetCount); |
1426 | 0 | uint32_t targetIndex; |
1427 | 0 | for ( targetIndex = 0; targetIndex < targetCount; ++targetIndex) { |
1428 | 0 | GtkTargetEntry *disEntry = targetArray.ElementAt(targetIndex); |
1429 | 0 | // this is a string reference but it will be freed later. |
1430 | 0 | targets[targetIndex].target = disEntry->target; |
1431 | 0 | targets[targetIndex].flags = disEntry->flags; |
1432 | 0 | targets[targetIndex].info = 0; |
1433 | 0 | } |
1434 | 0 | targetList = gtk_target_list_new(targets, targetCount); |
1435 | 0 | // clean up the target list |
1436 | 0 | for (uint32_t cleanIndex = 0; cleanIndex < targetCount; ++cleanIndex) { |
1437 | 0 | GtkTargetEntry *thisTarget = targetArray.ElementAt(cleanIndex); |
1438 | 0 | g_free(thisTarget->target); |
1439 | 0 | g_free(thisTarget); |
1440 | 0 | } |
1441 | 0 | g_free(targets); |
1442 | 0 | } |
1443 | 0 | return targetList; |
1444 | 0 | } |
1445 | | |
1446 | | void |
1447 | | nsDragService::SourceEndDragSession(GdkDragContext *aContext, |
1448 | | gint aResult) |
1449 | 0 | { |
1450 | 0 | // this just releases the list of data items that we provide |
1451 | 0 | mSourceDataItems = nullptr; |
1452 | 0 |
|
1453 | 0 | if (!mDoingDrag || mScheduledTask == eDragTaskSourceEnd) |
1454 | 0 | // EndDragSession() was already called on drop |
1455 | 0 | // or SourceEndDragSession on drag-failed |
1456 | 0 | return; |
1457 | 0 | |
1458 | 0 | if (mEndDragPoint.x < 0) { |
1459 | 0 | // We don't have a drag end point, so guess |
1460 | 0 | gint x, y; |
1461 | 0 | GdkDisplay* display = gdk_display_get_default(); |
1462 | 0 | if (display) { |
1463 | 0 | gint scale = ScreenHelperGTK::GetGTKMonitorScaleFactor(); |
1464 | 0 | gdk_display_get_pointer(display, nullptr, &x, &y, nullptr); |
1465 | 0 | SetDragEndPoint(LayoutDeviceIntPoint(x * scale, y * scale)); |
1466 | 0 | } |
1467 | 0 | } |
1468 | 0 |
|
1469 | 0 | // Either the drag was aborted or the drop occurred outside the app. |
1470 | 0 | // The dropEffect of mDataTransfer is not updated for motion outside the |
1471 | 0 | // app, but is needed for the dragend event, so set it now. |
1472 | 0 |
|
1473 | 0 | uint32_t dropEffect; |
1474 | 0 |
|
1475 | 0 | if (aResult == MOZ_GTK_DRAG_RESULT_SUCCESS) { |
1476 | 0 |
|
1477 | 0 | // With GTK+ versions 2.10.x and prior the drag may have been |
1478 | 0 | // cancelled (but no drag-failed signal would have been sent). |
1479 | 0 | // aContext->dest_window will be non-nullptr only if the drop was |
1480 | 0 | // sent. |
1481 | 0 | GdkDragAction action = |
1482 | 0 | gdk_drag_context_get_dest_window(aContext) ? |
1483 | 0 | gdk_drag_context_get_actions(aContext) : (GdkDragAction)0; |
1484 | 0 |
|
1485 | 0 | // Only one bit of action should be set, but, just in case someone |
1486 | 0 | // does something funny, erring away from MOVE, and not recording |
1487 | 0 | // unusual action combinations as NONE. |
1488 | 0 | if (!action) |
1489 | 0 | dropEffect = DRAGDROP_ACTION_NONE; |
1490 | 0 | else if (action & GDK_ACTION_COPY) |
1491 | 0 | dropEffect = DRAGDROP_ACTION_COPY; |
1492 | 0 | else if (action & GDK_ACTION_LINK) |
1493 | 0 | dropEffect = DRAGDROP_ACTION_LINK; |
1494 | 0 | else if (action & GDK_ACTION_MOVE) |
1495 | 0 | dropEffect = DRAGDROP_ACTION_MOVE; |
1496 | 0 | else |
1497 | 0 | dropEffect = DRAGDROP_ACTION_COPY; |
1498 | 0 |
|
1499 | 0 | } else { |
1500 | 0 |
|
1501 | 0 | dropEffect = DRAGDROP_ACTION_NONE; |
1502 | 0 |
|
1503 | 0 | if (aResult != MOZ_GTK_DRAG_RESULT_NO_TARGET) { |
1504 | 0 | mUserCancelled = true; |
1505 | 0 | } |
1506 | 0 | } |
1507 | 0 |
|
1508 | 0 | if (mDataTransfer) { |
1509 | 0 | mDataTransfer->SetDropEffectInt(dropEffect); |
1510 | 0 | } |
1511 | 0 |
|
1512 | 0 | // Schedule the appropriate drag end dom events. |
1513 | 0 | Schedule(eDragTaskSourceEnd, nullptr, nullptr, nullptr, LayoutDeviceIntPoint(), 0); |
1514 | 0 | } |
1515 | | |
1516 | | static void |
1517 | | CreateUriList(nsIArray *items, gchar **text, gint *length) |
1518 | 0 | { |
1519 | 0 | uint32_t i, count; |
1520 | 0 | GString *uriList = g_string_new(nullptr); |
1521 | 0 |
|
1522 | 0 | items->GetLength(&count); |
1523 | 0 | for (i = 0; i < count; i++) { |
1524 | 0 | nsCOMPtr<nsITransferable> item; |
1525 | 0 | item = do_QueryElementAt(items, i); |
1526 | 0 |
|
1527 | 0 | if (item) { |
1528 | 0 | uint32_t tmpDataLen = 0; |
1529 | 0 | void *tmpData = nullptr; |
1530 | 0 | nsresult rv = NS_OK; |
1531 | 0 | nsCOMPtr<nsISupports> data; |
1532 | 0 | rv = item->GetTransferData(kURLMime, |
1533 | 0 | getter_AddRefs(data), |
1534 | 0 | &tmpDataLen); |
1535 | 0 |
|
1536 | 0 | if (NS_SUCCEEDED(rv)) { |
1537 | 0 | nsPrimitiveHelpers::CreateDataFromPrimitive( |
1538 | 0 | nsDependentCString(kURLMime), data, &tmpData, tmpDataLen); |
1539 | 0 | char* plainTextData = nullptr; |
1540 | 0 | char16_t* castedUnicode = reinterpret_cast<char16_t*> |
1541 | 0 | (tmpData); |
1542 | 0 | uint32_t plainTextLen = 0; |
1543 | 0 | UTF16ToNewUTF8(castedUnicode, |
1544 | 0 | tmpDataLen / 2, |
1545 | 0 | &plainTextData, |
1546 | 0 | &plainTextLen); |
1547 | 0 | if (plainTextData) { |
1548 | 0 | uint32_t j; |
1549 | 0 |
|
1550 | 0 | // text/x-moz-url is of form url + "\n" + title. |
1551 | 0 | // We just want the url. |
1552 | 0 | for (j = 0; j < plainTextLen; j++) |
1553 | 0 | if (plainTextData[j] == '\n' || |
1554 | 0 | plainTextData[j] == '\r') { |
1555 | 0 | plainTextData[j] = '\0'; |
1556 | 0 | break; |
1557 | 0 | } |
1558 | 0 | g_string_append(uriList, plainTextData); |
1559 | 0 | g_string_append(uriList, "\r\n"); |
1560 | 0 | // this wasn't allocated with glib |
1561 | 0 | free(plainTextData); |
1562 | 0 | } |
1563 | 0 | if (tmpData) { |
1564 | 0 | // this wasn't allocated with glib |
1565 | 0 | free(tmpData); |
1566 | 0 | } |
1567 | 0 | } else { |
1568 | 0 | // There is no uri available. If there is a file available, |
1569 | 0 | // create a uri from the file. |
1570 | 0 | nsCOMPtr<nsISupports> data; |
1571 | 0 | rv = item->GetTransferData(kFileMime, |
1572 | 0 | getter_AddRefs(data), |
1573 | 0 | &tmpDataLen); |
1574 | 0 | if (NS_SUCCEEDED(rv)) { |
1575 | 0 | nsCOMPtr<nsIFile> file = do_QueryInterface(data); |
1576 | 0 | if (!file) { |
1577 | 0 | // Sometimes the file is wrapped in a |
1578 | 0 | // nsISupportsInterfacePointer. See bug 1310193 for |
1579 | 0 | // removing this distinction. |
1580 | 0 | nsCOMPtr<nsISupportsInterfacePointer> ptr = |
1581 | 0 | do_QueryInterface(data); |
1582 | 0 | if (ptr) { |
1583 | 0 | ptr->GetData(getter_AddRefs(data)); |
1584 | 0 | file = do_QueryInterface(data); |
1585 | 0 | } |
1586 | 0 | } |
1587 | 0 |
|
1588 | 0 | if (file) { |
1589 | 0 | nsCOMPtr<nsIURI> fileURI; |
1590 | 0 | NS_NewFileURI(getter_AddRefs(fileURI), file); |
1591 | 0 | if (fileURI) { |
1592 | 0 | nsAutoCString uristring; |
1593 | 0 | fileURI->GetSpec(uristring); |
1594 | 0 | g_string_append(uriList, uristring.get()); |
1595 | 0 | g_string_append(uriList, "\r\n"); |
1596 | 0 | } |
1597 | 0 | } |
1598 | 0 | } |
1599 | 0 | } |
1600 | 0 | } |
1601 | 0 | } |
1602 | 0 | *text = uriList->str; |
1603 | 0 | *length = uriList->len + 1; |
1604 | 0 | g_string_free(uriList, FALSE); // don't free the data |
1605 | 0 | } |
1606 | | |
1607 | | |
1608 | | void |
1609 | | nsDragService::SourceDataGet(GtkWidget *aWidget, |
1610 | | GdkDragContext *aContext, |
1611 | | GtkSelectionData *aSelectionData, |
1612 | | guint32 aTime) |
1613 | 0 | { |
1614 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("nsDragService::SourceDataGet")); |
1615 | 0 | GdkAtom target = gtk_selection_data_get_target(aSelectionData); |
1616 | 0 | nsCString mimeFlavor; |
1617 | 0 | gchar *typeName = 0; |
1618 | 0 | typeName = gdk_atom_name(target); |
1619 | 0 | if (!typeName) { |
1620 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("failed to get atom name.\n")); |
1621 | 0 | return; |
1622 | 0 | } |
1623 | 0 |
|
1624 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("Type is %s\n", typeName)); |
1625 | 0 | // make a copy since |nsCString| won't use |g_free|... |
1626 | 0 | mimeFlavor.Adopt(strdup(typeName)); |
1627 | 0 | g_free(typeName); |
1628 | 0 | // check to make sure that we have data items to return. |
1629 | 0 | if (!mSourceDataItems) { |
1630 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("Failed to get our data items\n")); |
1631 | 0 | return; |
1632 | 0 | } |
1633 | 0 |
|
1634 | 0 | nsCOMPtr<nsITransferable> item; |
1635 | 0 | item = do_QueryElementAt(mSourceDataItems, 0); |
1636 | 0 | if (item) { |
1637 | 0 | // if someone was asking for text/plain, lookup unicode instead so |
1638 | 0 | // we can convert it. |
1639 | 0 | bool needToDoConversionToPlainText = false; |
1640 | 0 | const char* actualFlavor; |
1641 | 0 | if (mimeFlavor.EqualsLiteral(kTextMime) || |
1642 | 0 | mimeFlavor.EqualsLiteral(gTextPlainUTF8Type)) { |
1643 | 0 | actualFlavor = kUnicodeMime; |
1644 | 0 | needToDoConversionToPlainText = true; |
1645 | 0 | } |
1646 | 0 | // if someone was asking for _NETSCAPE_URL we need to convert to |
1647 | 0 | // plain text but we also need to look for x-moz-url |
1648 | 0 | else if (mimeFlavor.EqualsLiteral(gMozUrlType)) { |
1649 | 0 | actualFlavor = kURLMime; |
1650 | 0 | needToDoConversionToPlainText = true; |
1651 | 0 | } |
1652 | 0 | // if someone was asking for text/uri-list we need to convert to |
1653 | 0 | // plain text. |
1654 | 0 | else if (mimeFlavor.EqualsLiteral(gTextUriListType)) { |
1655 | 0 | actualFlavor = gTextUriListType; |
1656 | 0 | needToDoConversionToPlainText = true; |
1657 | 0 | } |
1658 | 0 | else |
1659 | 0 | actualFlavor = mimeFlavor.get(); |
1660 | 0 |
|
1661 | 0 | uint32_t tmpDataLen = 0; |
1662 | 0 | void *tmpData = nullptr; |
1663 | 0 | nsresult rv; |
1664 | 0 | nsCOMPtr<nsISupports> data; |
1665 | 0 | rv = item->GetTransferData(actualFlavor, |
1666 | 0 | getter_AddRefs(data), |
1667 | 0 | &tmpDataLen); |
1668 | 0 | if (NS_SUCCEEDED(rv)) { |
1669 | 0 | nsPrimitiveHelpers::CreateDataFromPrimitive( |
1670 | 0 | nsDependentCString(actualFlavor), data, &tmpData, tmpDataLen); |
1671 | 0 | // if required, do the extra work to convert unicode to plain |
1672 | 0 | // text and replace the output values with the plain text. |
1673 | 0 | if (needToDoConversionToPlainText) { |
1674 | 0 | char* plainTextData = nullptr; |
1675 | 0 | char16_t* castedUnicode = reinterpret_cast<char16_t*> |
1676 | 0 | (tmpData); |
1677 | 0 | uint32_t plainTextLen = 0; |
1678 | 0 | UTF16ToNewUTF8(castedUnicode, |
1679 | 0 | tmpDataLen / 2, |
1680 | 0 | &plainTextData, |
1681 | 0 | &plainTextLen); |
1682 | 0 | if (tmpData) { |
1683 | 0 | // this was not allocated using glib |
1684 | 0 | free(tmpData); |
1685 | 0 | tmpData = plainTextData; |
1686 | 0 | tmpDataLen = plainTextLen; |
1687 | 0 | } |
1688 | 0 | } |
1689 | 0 | if (tmpData) { |
1690 | 0 | // this copies the data |
1691 | 0 | gtk_selection_data_set(aSelectionData, target, |
1692 | 0 | 8, |
1693 | 0 | (guchar *)tmpData, tmpDataLen); |
1694 | 0 | // this wasn't allocated with glib |
1695 | 0 | free(tmpData); |
1696 | 0 | } |
1697 | 0 | } else { |
1698 | 0 | if (mimeFlavor.EqualsLiteral(gTextUriListType)) { |
1699 | 0 | // fall back for text/uri-list |
1700 | 0 | gchar *uriList; |
1701 | 0 | gint length; |
1702 | 0 | CreateUriList(mSourceDataItems, &uriList, &length); |
1703 | 0 | gtk_selection_data_set(aSelectionData, target, |
1704 | 0 | 8, (guchar *)uriList, length); |
1705 | 0 | g_free(uriList); |
1706 | 0 | return; |
1707 | 0 | } |
1708 | 0 | } |
1709 | 0 | } |
1710 | 0 | } |
1711 | | |
1712 | | void nsDragService::SetDragIcon(GdkDragContext* aContext) |
1713 | 0 | { |
1714 | 0 | if (!mHasImage && !mSelection) |
1715 | 0 | return; |
1716 | 0 | |
1717 | 0 | LayoutDeviceIntRect dragRect; |
1718 | 0 | nsPresContext* pc; |
1719 | 0 | RefPtr<SourceSurface> surface; |
1720 | 0 | DrawDrag(mSourceNode, mRegion, |
1721 | 0 | mScreenPosition, &dragRect, &surface, &pc); |
1722 | 0 | if (!pc) |
1723 | 0 | return; |
1724 | 0 | |
1725 | 0 | LayoutDeviceIntPoint screenPoint = |
1726 | 0 | ConvertToUnscaledDevPixels(pc, mScreenPosition); |
1727 | 0 | int32_t offsetX = screenPoint.x - dragRect.x; |
1728 | 0 | int32_t offsetY = screenPoint.y - dragRect.y; |
1729 | 0 |
|
1730 | 0 | // If a popup is set as the drag image, use its widget. Otherwise, use |
1731 | 0 | // the surface that DrawDrag created. |
1732 | 0 | // |
1733 | 0 | // XXX: Disable drag popups on GTK 3.19.4 and above: see bug 1264454. |
1734 | 0 | // Fix this once a new GTK version ships that does not destroy our |
1735 | 0 | // widget in gtk_drag_set_icon_widget. |
1736 | 0 | if (mDragPopup && gtk_check_version(3, 19, 4)) { |
1737 | 0 | GtkWidget* gtkWidget = nullptr; |
1738 | 0 | nsIFrame* frame = mDragPopup->GetPrimaryFrame(); |
1739 | 0 | if (frame) { |
1740 | 0 | // DrawDrag ensured that this is a popup frame. |
1741 | 0 | nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget(); |
1742 | 0 | if (widget) { |
1743 | 0 | gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET); |
1744 | 0 | if (gtkWidget) { |
1745 | 0 | OpenDragPopup(); |
1746 | 0 | gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY); |
1747 | 0 | } |
1748 | 0 | } |
1749 | 0 | } |
1750 | 0 | } |
1751 | 0 | else if (surface) { |
1752 | 0 | if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) { |
1753 | 0 | GdkPixbuf* dragPixbuf = |
1754 | 0 | nsImageToPixbuf::SourceSurfaceToPixbuf(surface, dragRect.width, dragRect.height); |
1755 | 0 | if (dragPixbuf) { |
1756 | 0 | gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY); |
1757 | 0 | g_object_unref(dragPixbuf); |
1758 | 0 | } |
1759 | 0 | } |
1760 | 0 | } |
1761 | 0 | } |
1762 | | |
1763 | | static void |
1764 | | invisibleSourceDragBegin(GtkWidget *aWidget, |
1765 | | GdkDragContext *aContext, |
1766 | | gpointer aData) |
1767 | 0 | { |
1768 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragBegin")); |
1769 | 0 | nsDragService *dragService = (nsDragService *)aData; |
1770 | 0 |
|
1771 | 0 | dragService->SetDragIcon(aContext); |
1772 | 0 | } |
1773 | | |
1774 | | static void |
1775 | | invisibleSourceDragDataGet(GtkWidget *aWidget, |
1776 | | GdkDragContext *aContext, |
1777 | | GtkSelectionData *aSelectionData, |
1778 | | guint aInfo, |
1779 | | guint32 aTime, |
1780 | | gpointer aData) |
1781 | 0 | { |
1782 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragDataGet")); |
1783 | 0 | nsDragService *dragService = (nsDragService *)aData; |
1784 | 0 | dragService->SourceDataGet(aWidget, aContext, |
1785 | 0 | aSelectionData, aTime); |
1786 | 0 | } |
1787 | | |
1788 | | static gboolean |
1789 | | invisibleSourceDragFailed(GtkWidget *aWidget, |
1790 | | GdkDragContext *aContext, |
1791 | | gint aResult, |
1792 | | gpointer aData) |
1793 | 0 | { |
1794 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragFailed %i", aResult)); |
1795 | 0 | nsDragService *dragService = (nsDragService *)aData; |
1796 | 0 | // End the drag session now (rather than waiting for the drag-end signal) |
1797 | 0 | // so that operations performed on dropEffect == none can start immediately |
1798 | 0 | // rather than waiting for the drag-failed animation to finish. |
1799 | 0 | dragService->SourceEndDragSession(aContext, aResult); |
1800 | 0 |
|
1801 | 0 | // We should return TRUE to disable the drag-failed animation iff the |
1802 | 0 | // source performed an operation when dropEffect was none, but the handler |
1803 | 0 | // of the dragend DOM event doesn't provide this information. |
1804 | 0 | return FALSE; |
1805 | 0 | } |
1806 | | |
1807 | | static void |
1808 | | invisibleSourceDragEnd(GtkWidget *aWidget, |
1809 | | GdkDragContext *aContext, |
1810 | | gpointer aData) |
1811 | 0 | { |
1812 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, ("invisibleSourceDragEnd")); |
1813 | 0 | nsDragService *dragService = (nsDragService *)aData; |
1814 | 0 |
|
1815 | 0 | // The drag has ended. Release the hostages! |
1816 | 0 | dragService->SourceEndDragSession(aContext, MOZ_GTK_DRAG_RESULT_SUCCESS); |
1817 | 0 | } |
1818 | | |
1819 | | // The following methods handle responding to GTK drag signals and |
1820 | | // tracking state between these signals. |
1821 | | // |
1822 | | // In general, GTK does not expect us to run the event loop while handling its |
1823 | | // drag signals, however our drag event handlers may run the |
1824 | | // event loop, most often to fetch information about the drag data. |
1825 | | // |
1826 | | // GTK, for example, uses the return value from drag-motion signals to |
1827 | | // determine whether drag-leave signals should be sent. If an event loop is |
1828 | | // run during drag-motion the XdndLeave message can get processed but when GTK |
1829 | | // receives the message it does not yet know that it needs to send the |
1830 | | // drag-leave signal to our widget. |
1831 | | // |
1832 | | // After a drag-drop signal, we need to reply with gtk_drag_finish(). |
1833 | | // However, gtk_drag_finish should happen after the drag-drop signal handler |
1834 | | // returns so that when the Motif drag protocol is used, the |
1835 | | // XmTRANSFER_SUCCESS during gtk_drag_finish is sent after the XmDROP_START |
1836 | | // reply sent on return from the drag-drop signal handler. |
1837 | | // |
1838 | | // Similarly drag-end for a successful drag and drag-failed are not good |
1839 | | // times to run a nested event loop as gtk_drag_drop_finished() and |
1840 | | // gtk_drag_source_info_destroy() don't gtk_drag_clear_source_info() or remove |
1841 | | // drop_timeout until after at least the first of these signals is sent. |
1842 | | // Processing other events (e.g. a slow GDK_DROP_FINISHED reply, or the drop |
1843 | | // timeout) could cause gtk_drag_drop_finished to be called again with the |
1844 | | // same GtkDragSourceInfo, which won't like being destroyed twice. |
1845 | | // |
1846 | | // Therefore we reply to the signals immediately and schedule a task to |
1847 | | // dispatch the Gecko events, which may run the event loop. |
1848 | | // |
1849 | | // Action in response to drag-leave signals is also delayed until the event |
1850 | | // loop runs again so that we find out whether a drag-drop signal follows. |
1851 | | // |
1852 | | // A single task is scheduled to manage responses to all three GTK signals. |
1853 | | // If further signals are received while the task is scheduled, the scheduled |
1854 | | // response is updated, sometimes effectively compressing successive signals. |
1855 | | // |
1856 | | // No Gecko drag events are dispatched (during nested event loops) while other |
1857 | | // Gecko drag events are in flight. This helps event handlers that may not |
1858 | | // expect nested events, while accessing an event's dataTransfer for example. |
1859 | | |
1860 | | gboolean |
1861 | | nsDragService::ScheduleMotionEvent(nsWindow *aWindow, |
1862 | | GdkDragContext *aDragContext, |
1863 | | nsWaylandDragContext *aWaylandDragContext, |
1864 | | LayoutDeviceIntPoint aWindowPoint, guint aTime) |
1865 | 0 | { |
1866 | 0 | if (aDragContext && mScheduledTask == eDragTaskMotion) { |
1867 | 0 | // The drag source has sent another motion message before we've |
1868 | 0 | // replied to the previous. That shouldn't happen with Xdnd. The |
1869 | 0 | // spec for Motif drags is less clear, but we'll just update the |
1870 | 0 | // scheduled task with the new position reply only to the most |
1871 | 0 | // recent message. |
1872 | 0 | NS_WARNING("Drag Motion message received before previous reply was sent"); |
1873 | 0 | } |
1874 | 0 |
|
1875 | 0 | // Returning TRUE means we'll reply with a status message, unless we first |
1876 | 0 | // get a leave. |
1877 | 0 | return Schedule(eDragTaskMotion, aWindow, aDragContext, aWaylandDragContext, |
1878 | 0 | aWindowPoint, aTime); |
1879 | 0 | } |
1880 | | |
1881 | | void |
1882 | | nsDragService::ScheduleLeaveEvent() |
1883 | 0 | { |
1884 | 0 | // We don't know at this stage whether a drop signal will immediately |
1885 | 0 | // follow. If the drop signal gets sent it will happen before we return |
1886 | 0 | // to the main loop and the scheduled leave task will be replaced. |
1887 | 0 | if (!Schedule(eDragTaskLeave, nullptr, nullptr, nullptr, |
1888 | 0 | LayoutDeviceIntPoint(), 0)) { |
1889 | 0 | NS_WARNING("Drag leave after drop"); |
1890 | 0 | } |
1891 | 0 | } |
1892 | | |
1893 | | gboolean |
1894 | | nsDragService::ScheduleDropEvent(nsWindow *aWindow, |
1895 | | GdkDragContext *aDragContext, |
1896 | | nsWaylandDragContext *aWaylandDragContext, |
1897 | | LayoutDeviceIntPoint aWindowPoint, guint aTime) |
1898 | 0 | { |
1899 | 0 | if (!Schedule(eDragTaskDrop, aWindow, |
1900 | 0 | aDragContext, aWaylandDragContext, aWindowPoint, aTime)) { |
1901 | 0 | NS_WARNING("Additional drag drop ignored"); |
1902 | 0 | return FALSE; |
1903 | 0 | } |
1904 | 0 |
|
1905 | 0 | SetDragEndPoint(aWindowPoint + aWindow->WidgetToScreenOffset()); |
1906 | 0 |
|
1907 | 0 | // We'll reply with gtk_drag_finish(). |
1908 | 0 | return TRUE; |
1909 | 0 | } |
1910 | | |
1911 | | gboolean |
1912 | | nsDragService::Schedule(DragTask aTask, nsWindow *aWindow, |
1913 | | GdkDragContext *aDragContext, |
1914 | | nsWaylandDragContext *aWaylandDragContext, |
1915 | | LayoutDeviceIntPoint aWindowPoint, guint aTime) |
1916 | 0 | { |
1917 | 0 | // If there is an existing leave or motion task scheduled, then that |
1918 | 0 | // will be replaced. When the new task is run, it will dispatch |
1919 | 0 | // any necessary leave or motion events. |
1920 | 0 |
|
1921 | 0 | // If aTask is eDragTaskSourceEnd, then it will replace even a scheduled |
1922 | 0 | // drop event (which could happen if the drop event has not been processed |
1923 | 0 | // within the allowed time). Otherwise, if we haven't yet run a scheduled |
1924 | 0 | // drop or end task, just say that we are not ready to receive another |
1925 | 0 | // drop. |
1926 | 0 | if (mScheduledTask == eDragTaskSourceEnd || |
1927 | 0 | (mScheduledTask == eDragTaskDrop && aTask != eDragTaskSourceEnd)) |
1928 | 0 | return FALSE; |
1929 | 0 | |
1930 | 0 | mScheduledTask = aTask; |
1931 | 0 | mPendingWindow = aWindow; |
1932 | 0 | mPendingDragContext = aDragContext; |
1933 | | #ifdef MOZ_WAYLAND |
1934 | | mPendingWaylandDragContext = aWaylandDragContext; |
1935 | | #endif |
1936 | | mPendingWindowPoint = aWindowPoint; |
1937 | 0 | mPendingTime = aTime; |
1938 | 0 |
|
1939 | 0 | if (!mTaskSource) { |
1940 | 0 | // High priority is used here because the native events involved have |
1941 | 0 | // already waited at default priority. Perhaps a lower than default |
1942 | 0 | // priority could be used for motion tasks because there is a chance |
1943 | 0 | // that a leave or drop is waiting, but managing different priorities |
1944 | 0 | // may not be worth the effort. Motion tasks shouldn't queue up as |
1945 | 0 | // they should be throttled based on replies. |
1946 | 0 | mTaskSource = g_idle_add_full(G_PRIORITY_HIGH, TaskDispatchCallback, |
1947 | 0 | this, nullptr); |
1948 | 0 | } |
1949 | 0 | return TRUE; |
1950 | 0 | } |
1951 | | |
1952 | | gboolean |
1953 | | nsDragService::TaskDispatchCallback(gpointer data) |
1954 | 0 | { |
1955 | 0 | RefPtr<nsDragService> dragService = static_cast<nsDragService*>(data); |
1956 | 0 | return dragService->RunScheduledTask(); |
1957 | 0 | } |
1958 | | |
1959 | | gboolean |
1960 | | nsDragService::RunScheduledTask() |
1961 | 0 | { |
1962 | 0 | if (mTargetWindow && mTargetWindow != mPendingWindow) { |
1963 | 0 | MOZ_LOG(sDragLm, LogLevel::Debug, |
1964 | 0 | ("nsDragService: dispatch drag leave (%p)\n", |
1965 | 0 | mTargetWindow.get())); |
1966 | 0 | mTargetWindow->DispatchDragEvent(eDragExit, mTargetWindowPoint, 0); |
1967 | 0 |
|
1968 | 0 | if (!mSourceNode) { |
1969 | 0 | // The drag that was initiated in a different app. End the drag |
1970 | 0 | // session, since we're done with it for now (until the user drags |
1971 | 0 | // back into this app). |
1972 | 0 | EndDragSession(false, GetCurrentModifiers()); |
1973 | 0 | } |
1974 | 0 | } |
1975 | 0 |
|
1976 | 0 | // It is possible that the pending state has been updated during dispatch |
1977 | 0 | // of the leave event. That's fine. |
1978 | 0 |
|
1979 | 0 | // Now we collect the pending state because, from this point on, we want |
1980 | 0 | // to use the same state for all events dispatched. All state is updated |
1981 | 0 | // so that when other tasks are scheduled during dispatch here, this |
1982 | 0 | // task is considered to have already been run. |
1983 | 0 | bool positionHasChanged = |
1984 | 0 | mPendingWindow != mTargetWindow || |
1985 | 0 | mPendingWindowPoint != mTargetWindowPoint; |
1986 | 0 | DragTask task = mScheduledTask; |
1987 | 0 | mScheduledTask = eDragTaskNone; |
1988 | 0 | mTargetWindow = mPendingWindow.forget(); |
1989 | 0 | mTargetWindowPoint = mPendingWindowPoint; |
1990 | 0 |
|
1991 | 0 | if (task == eDragTaskLeave || task == eDragTaskSourceEnd) { |
1992 | 0 | if (task == eDragTaskSourceEnd) { |
1993 | 0 | // Dispatch drag end events. |
1994 | 0 | EndDragSession(true, GetCurrentModifiers()); |
1995 | 0 | } |
1996 | 0 |
|
1997 | 0 | // Nothing more to do |
1998 | 0 | // Returning false removes the task source from the event loop. |
1999 | 0 | mTaskSource = 0; |
2000 | 0 | return FALSE; |
2001 | 0 | } |
2002 | 0 |
|
2003 | 0 | // This may be the start of a destination drag session. |
2004 | 0 | StartDragSession(); |
2005 | 0 |
|
2006 | 0 | // mTargetWidget may be nullptr if the window has been destroyed. |
2007 | 0 | // (The leave event is not scheduled if a drop task is still scheduled.) |
2008 | 0 | // We still reply appropriately to indicate that the drop will or didn't |
2009 | 0 | // succeeed. |
2010 | 0 | mTargetWidget = mTargetWindow->GetMozContainerWidget(); |
2011 | 0 | mTargetDragContext.steal(mPendingDragContext); |
2012 | | #ifdef MOZ_WAYLAND |
2013 | | mTargetWaylandDragContext = mPendingWaylandDragContext.forget(); |
2014 | | #endif |
2015 | | mTargetTime = mPendingTime; |
2016 | 0 |
|
2017 | 0 | // http://www.whatwg.org/specs/web-apps/current-work/multipage/dnd.html#drag-and-drop-processing-model |
2018 | 0 | // (as at 27 December 2010) indicates that a "drop" event should only be |
2019 | 0 | // fired (at the current target element) if the current drag operation is |
2020 | 0 | // not none. The current drag operation will only be set to a non-none |
2021 | 0 | // value during a "dragover" event. |
2022 | 0 | // |
2023 | 0 | // If the user has ended the drag before any dragover events have been |
2024 | 0 | // sent, then the spec recommends skipping the drop (because the current |
2025 | 0 | // drag operation is none). However, here we assume that, by releasing |
2026 | 0 | // the mouse button, the user has indicated that they want to drop, so we |
2027 | 0 | // proceed with the drop where possible. |
2028 | 0 | // |
2029 | 0 | // In order to make the events appear to content in the same way as if the |
2030 | 0 | // spec is being followed we make sure to dispatch a "dragover" event with |
2031 | 0 | // appropriate coordinates and check canDrop before the "drop" event. |
2032 | 0 | // |
2033 | 0 | // When the Xdnd protocol is used for source/destination communication (as |
2034 | 0 | // should be the case with GTK source applications) a dragover event |
2035 | 0 | // should have already been sent during the drag-motion signal, which |
2036 | 0 | // would have already been received because XdndDrop messages do not |
2037 | 0 | // contain a position. However, we can't assume the same when the Motif |
2038 | 0 | // protocol is used. |
2039 | 0 | if (task == eDragTaskMotion || positionHasChanged) { |
2040 | 0 | UpdateDragAction(); |
2041 | 0 | TakeDragEventDispatchedToChildProcess(); // Clear the old value. |
2042 | 0 | DispatchMotionEvents(); |
2043 | 0 | if (task == eDragTaskMotion) { |
2044 | 0 | if (TakeDragEventDispatchedToChildProcess()) { |
2045 | 0 | mTargetDragContextForRemote = mTargetDragContext; |
2046 | | #ifdef MOZ_WAYLAND |
2047 | | mTargetWaylandDragContextForRemote = mTargetWaylandDragContext; |
2048 | | #endif |
2049 | 0 | } else { |
2050 | 0 | // Reply to tell the source whether we can drop and what |
2051 | 0 | // action would be taken. |
2052 | 0 | if (mTargetDragContext) { |
2053 | 0 | ReplyToDragMotion(mTargetDragContext); |
2054 | 0 | } |
2055 | | #ifdef MOZ_WAYLAND |
2056 | | else if (mTargetWaylandDragContext) { |
2057 | | ReplyToDragMotion(mTargetWaylandDragContext); |
2058 | | } |
2059 | | #endif |
2060 | | } |
2061 | 0 | } |
2062 | 0 | } |
2063 | 0 |
|
2064 | 0 | if (task == eDragTaskDrop) { |
2065 | 0 | gboolean success = DispatchDropEvent(); |
2066 | 0 |
|
2067 | 0 | // Perhaps we should set the del parameter to TRUE when the drag |
2068 | 0 | // action is move, but we don't know whether the data was successfully |
2069 | 0 | // transferred. |
2070 | 0 | if (mTargetDragContext) { |
2071 | 0 | gtk_drag_finish(mTargetDragContext, success, |
2072 | 0 | /* del = */ FALSE, mTargetTime); |
2073 | 0 | } |
2074 | 0 |
|
2075 | 0 | // This drag is over, so clear out our reference to the previous |
2076 | 0 | // window. |
2077 | 0 | mTargetWindow = nullptr; |
2078 | 0 | // Make sure to end the drag session. If this drag started in a |
2079 | 0 | // different app, we won't get a drag_end signal to end it from. |
2080 | 0 | EndDragSession(true, GetCurrentModifiers()); |
2081 | 0 | } |
2082 | 0 |
|
2083 | 0 | // We're done with the drag context. |
2084 | 0 | mTargetWidget = nullptr; |
2085 | 0 | mTargetDragContext = nullptr; |
2086 | | #ifdef MOZ_WAYLAND |
2087 | | mTargetWaylandDragContext = nullptr; |
2088 | | #endif |
2089 | |
|
2090 | 0 | // If we got another drag signal while running the sheduled task, that |
2091 | 0 | // must have happened while running a nested event loop. Leave the task |
2092 | 0 | // source on the event loop. |
2093 | 0 | if (mScheduledTask != eDragTaskNone) |
2094 | 0 | return TRUE; |
2095 | 0 | |
2096 | 0 | // We have no task scheduled. |
2097 | 0 | // Returning false removes the task source from the event loop. |
2098 | 0 | mTaskSource = 0; |
2099 | 0 | return FALSE; |
2100 | 0 | } |
2101 | | |
2102 | | // This will update the drag action based on the information in the |
2103 | | // drag context. Gtk gets this from a combination of the key settings |
2104 | | // and what the source is offering. |
2105 | | |
2106 | | void |
2107 | | nsDragService::UpdateDragAction() |
2108 | 0 | { |
2109 | 0 | // This doesn't look right. dragSession.dragAction is used by |
2110 | 0 | // nsContentUtils::SetDataTransferInEvent() to set the initial |
2111 | 0 | // dataTransfer.dropEffect, so GdkDragContext::suggested_action would be |
2112 | 0 | // more appropriate. GdkDragContext::actions should be used to set |
2113 | 0 | // dataTransfer.effectAllowed, which doesn't currently happen with |
2114 | 0 | // external sources. |
2115 | 0 |
|
2116 | 0 | // default is to do nothing |
2117 | 0 | int action = nsIDragService::DRAGDROP_ACTION_NONE; |
2118 | 0 | GdkDragAction gdkAction = GDK_ACTION_DEFAULT; |
2119 | 0 | if (mTargetDragContext) { |
2120 | 0 | gdkAction = gdk_drag_context_get_actions(mTargetDragContext); |
2121 | 0 | } |
2122 | | #ifdef MOZ_WAYLAND |
2123 | | else if (mTargetWaylandDragContext) { |
2124 | | // We got the selected D&D action from compositor on Wayland. |
2125 | | gdkAction = mTargetWaylandDragContext->GetSelectedDragAction(); |
2126 | | } |
2127 | | #endif |
2128 | |
|
2129 | 0 | // set the default just in case nothing matches below |
2130 | 0 | if (gdkAction & GDK_ACTION_DEFAULT) |
2131 | 0 | action = nsIDragService::DRAGDROP_ACTION_MOVE; |
2132 | 0 |
|
2133 | 0 | // first check to see if move is set |
2134 | 0 | if (gdkAction & GDK_ACTION_MOVE) |
2135 | 0 | action = nsIDragService::DRAGDROP_ACTION_MOVE; |
2136 | 0 |
|
2137 | 0 | // then fall to the others |
2138 | 0 | else if (gdkAction & GDK_ACTION_LINK) |
2139 | 0 | action = nsIDragService::DRAGDROP_ACTION_LINK; |
2140 | 0 |
|
2141 | 0 | // copy is ctrl |
2142 | 0 | else if (gdkAction & GDK_ACTION_COPY) |
2143 | 0 | action = nsIDragService::DRAGDROP_ACTION_COPY; |
2144 | 0 |
|
2145 | 0 | // update the drag information |
2146 | 0 | SetDragAction(action); |
2147 | 0 | } |
2148 | | |
2149 | | NS_IMETHODIMP |
2150 | | nsDragService::UpdateDragEffect() |
2151 | 0 | { |
2152 | 0 | if (mTargetDragContextForRemote) { |
2153 | 0 | ReplyToDragMotion(mTargetDragContextForRemote); |
2154 | 0 | mTargetDragContextForRemote = nullptr; |
2155 | 0 | } |
2156 | | #ifdef MOZ_WAYLAND |
2157 | | else if (mTargetWaylandDragContextForRemote) { |
2158 | | ReplyToDragMotion(mTargetWaylandDragContextForRemote); |
2159 | | mTargetWaylandDragContextForRemote = nullptr; |
2160 | | } |
2161 | | #endif |
2162 | | return NS_OK; |
2163 | 0 | } |
2164 | | |
2165 | | void |
2166 | | nsDragService::DispatchMotionEvents() |
2167 | 0 | { |
2168 | 0 | mCanDrop = false; |
2169 | 0 |
|
2170 | 0 | FireDragEventAtSource(eDrag, GetCurrentModifiers()); |
2171 | 0 |
|
2172 | 0 | mTargetWindow->DispatchDragEvent(eDragOver, mTargetWindowPoint, |
2173 | 0 | mTargetTime); |
2174 | 0 | } |
2175 | | |
2176 | | // Returns true if the drop was successful |
2177 | | gboolean |
2178 | | nsDragService::DispatchDropEvent() |
2179 | 0 | { |
2180 | 0 | // We need to check IsDestroyed here because the nsRefPtr |
2181 | 0 | // only protects this from being deleted, it does NOT protect |
2182 | 0 | // against nsView::~nsView() calling Destroy() on it, bug 378273. |
2183 | 0 | if (mTargetWindow->IsDestroyed()) |
2184 | 0 | return FALSE; |
2185 | 0 | |
2186 | 0 | EventMessage msg = mCanDrop ? eDrop : eDragExit; |
2187 | 0 |
|
2188 | 0 | mTargetWindow->DispatchDragEvent(msg, mTargetWindowPoint, mTargetTime); |
2189 | 0 |
|
2190 | 0 | return mCanDrop; |
2191 | 0 | } |
2192 | | |
2193 | | /* static */ uint32_t |
2194 | | nsDragService::GetCurrentModifiers() |
2195 | 0 | { |
2196 | 0 | return mozilla::widget::KeymapWrapper::ComputeCurrentKeyModifiers(); |
2197 | 0 | } |