Unreviewed, rolling out r191728.
[WebKit-https.git] / Source / WebKit2 / UIProcess / gtk / DragAndDropHandler.cpp
1 /*
2  * Copyright (C) 2014 Igalia S.L.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS''
14  * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
15  * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS
17  * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
18  * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
19  * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
20  * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
21  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
22  * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
23  * THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "DragAndDropHandler.h"
28
29 #if ENABLE(DRAG_SUPPORT)
30
31 #include "WebPageProxy.h"
32 #include <WebCore/DataObjectGtk.h>
33 #include <WebCore/DragData.h>
34 #include <WebCore/GRefPtrGtk.h>
35 #include <WebCore/GtkUtilities.h>
36 #include <WebCore/PasteboardHelper.h>
37 #include <gtk/gtk.h>
38 #include <wtf/glib/GMainLoopSource.h>
39 #include <wtf/glib/GUniquePtr.h>
40
41 using namespace WebCore;
42
43 namespace WebKit {
44
45 DragAndDropHandler::DragAndDropHandler(WebPageProxy& page)
46     : m_page(page)
47 {
48 }
49
50 DragAndDropHandler::DroppingContext::DroppingContext(GdkDragContext* gdkContext, const IntPoint& position)
51     : gdkContext(gdkContext)
52     , dataObject(DataObjectGtk::create())
53     , lastMotionPosition(position)
54     , dropHappened(false)
55 {
56 }
57
58 static inline GdkDragAction dragOperationToGdkDragActions(DragOperation coreAction)
59 {
60     GdkDragAction gdkAction = static_cast<GdkDragAction>(0);
61     if (coreAction == DragOperationNone)
62         return gdkAction;
63
64     if (coreAction & DragOperationCopy)
65         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_COPY | gdkAction);
66     if (coreAction & DragOperationMove)
67         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_MOVE | gdkAction);
68     if (coreAction & DragOperationLink)
69         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_LINK | gdkAction);
70     if (coreAction & DragOperationPrivate)
71         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_PRIVATE | gdkAction);
72
73     return gdkAction;
74 }
75
76 static inline GdkDragAction dragOperationToSingleGdkDragAction(DragOperation coreAction)
77 {
78     if (coreAction == DragOperationEvery || coreAction & DragOperationCopy)
79         return GDK_ACTION_COPY;
80     if (coreAction & DragOperationMove)
81         return GDK_ACTION_MOVE;
82     if (coreAction & DragOperationLink)
83         return GDK_ACTION_LINK;
84     if (coreAction & DragOperationPrivate)
85         return GDK_ACTION_PRIVATE;
86     return static_cast<GdkDragAction>(0);
87 }
88
89 static inline DragOperation gdkDragActionToDragOperation(GdkDragAction gdkAction)
90 {
91     // We have no good way to detect DragOperationEvery other than
92     // to use it when all applicable flags are on.
93     if (gdkAction & GDK_ACTION_COPY
94         && gdkAction & GDK_ACTION_MOVE
95         && gdkAction & GDK_ACTION_LINK
96         && gdkAction & GDK_ACTION_PRIVATE)
97         return DragOperationEvery;
98
99     unsigned action = DragOperationNone;
100     if (gdkAction & GDK_ACTION_COPY)
101         action |= DragOperationCopy;
102     if (gdkAction & GDK_ACTION_MOVE)
103         action |= DragOperationMove;
104     if (gdkAction & GDK_ACTION_LINK)
105         action |= DragOperationLink;
106     if (gdkAction & GDK_ACTION_PRIVATE)
107         action |= DragOperationPrivate;
108     return static_cast<DragOperation>(action);
109 }
110
111 void DragAndDropHandler::startDrag(const DragData& dragData, PassRefPtr<ShareableBitmap> dragImage)
112 {
113     RefPtr<DataObjectGtk> dataObject = adoptRef(dragData.platformData());
114     GRefPtr<GtkTargetList> targetList = adoptGRef(PasteboardHelper::singleton().targetListForDataObject(dataObject.get()));
115     GUniquePtr<GdkEvent> currentEvent(gtk_get_current_event());
116
117     GdkDragContext* context = gtk_drag_begin(m_page.viewWidget(), targetList.get(), dragOperationToGdkDragActions(dragData.draggingSourceOperationMask()),
118         GDK_BUTTON_PRIMARY, currentEvent.get());
119     m_draggingDataObjects.set(context, dataObject.get());
120
121     if (dragImage) {
122         RefPtr<cairo_surface_t> image(dragImage->createCairoSurface());
123         // Use the center of the drag image as hotspot.
124         cairo_surface_set_device_offset(image.get(), -cairo_image_surface_get_width(image.get()) / 2, -cairo_image_surface_get_height(image.get()) / 2);
125         gtk_drag_set_icon_surface(context, image.get());
126     } else
127         gtk_drag_set_icon_default(context);
128 }
129
130 void DragAndDropHandler::fillDragData(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info)
131 {
132     if (DataObjectGtk* dataObject = m_draggingDataObjects.get(context))
133         PasteboardHelper::singleton().fillSelectionData(selectionData, info, dataObject);
134 }
135
136 void DragAndDropHandler::finishDrag(GdkDragContext* context)
137 {
138     if (!m_draggingDataObjects.remove(context))
139         return;
140
141     GdkDevice* device = gdk_drag_context_get_device(context);
142     int x = 0, y = 0;
143     gdk_device_get_window_at_position(device, &x, &y);
144     int xRoot = 0, yRoot = 0;
145     gdk_device_get_position(device, nullptr, &xRoot, &yRoot);
146     m_page.dragEnded(IntPoint(x, y), IntPoint(xRoot, yRoot), gdkDragActionToDragOperation(gdk_drag_context_get_selected_action(context)));
147 }
148
149 DataObjectGtk* DragAndDropHandler::dataObjectForDropData(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, IntPoint& position)
150 {
151     DroppingContext* droppingContext = m_droppingContexts.get(context);
152     if (!droppingContext)
153         return nullptr;
154
155     droppingContext->pendingDataRequests--;
156     PasteboardHelper::singleton().fillDataObjectFromDropData(selectionData, info, droppingContext->dataObject.get());
157     if (droppingContext->pendingDataRequests)
158         return nullptr;
159
160     // The coordinates passed to drag-data-received signal are sometimes
161     // inaccurate in WTR, so use the coordinates of the last motion event.
162     position = droppingContext->lastMotionPosition;
163
164     // If there are no more pending requests, start sending dragging data to WebCore.
165     return droppingContext->dataObject.get();
166 }
167
168 void DragAndDropHandler::dragEntered(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, unsigned time)
169 {
170     IntPoint position;
171     DataObjectGtk* dataObject = dataObjectForDropData(context, selectionData, info, position);
172     if (!dataObject)
173         return;
174
175     DragData dragData(dataObject, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)));
176     m_page.resetCurrentDragInformation();
177     m_page.dragEntered(dragData);
178     DragOperation operation = m_page.currentDragOperation();
179     gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time);
180 }
181
182 DataObjectGtk* DragAndDropHandler::requestDragData(GdkDragContext* context, const IntPoint& position, unsigned time)
183 {
184     std::unique_ptr<DroppingContext>& droppingContext = m_droppingContexts.add(context, nullptr).iterator->value;
185     if (!droppingContext) {
186         GtkWidget* widget = m_page.viewWidget();
187         droppingContext = std::make_unique<DroppingContext>(context, position);
188         Vector<GdkAtom> acceptableTargets(PasteboardHelper::singleton().dropAtomsForContext(widget, droppingContext->gdkContext));
189         droppingContext->pendingDataRequests = acceptableTargets.size();
190         for (auto& target : acceptableTargets)
191             gtk_drag_get_data(widget, droppingContext->gdkContext, target, time);
192     } else
193         droppingContext->lastMotionPosition = position;
194
195     // Don't send any drag information to WebCore until we've retrieved all the data for this drag operation.
196     // Otherwise we'd have to block to wait for the drag's data.
197     if (droppingContext->pendingDataRequests > 0)
198         return nullptr;
199
200     return droppingContext->dataObject.get();
201 }
202
203 void DragAndDropHandler::dragMotion(GdkDragContext* context, const IntPoint& position, unsigned time)
204 {
205     DataObjectGtk* dataObject = requestDragData(context, position, time);
206     if (!dataObject)
207         return;
208
209     DragData dragData(dataObject, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)));
210     m_page.dragUpdated(dragData);
211     DragOperation operation = m_page.currentDragOperation();
212     gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time);
213 }
214
215 void DragAndDropHandler::dragLeave(GdkDragContext* context)
216 {
217     DroppingContext* droppingContext = m_droppingContexts.get(context);
218     if (!droppingContext)
219         return;
220
221     // During a drop GTK+ will fire a drag-leave signal right before firing
222     // the drag-drop signal. We want the actions for drag-leave to happen after
223     // those for drag-drop, so schedule them to happen asynchronously here.
224     GMainLoopSource::scheduleAndDeleteOnDestroy("[WebKit] handleDragLeaveLater", [this, droppingContext]() {
225         auto it = m_droppingContexts.find(droppingContext->gdkContext);
226         if (it == m_droppingContexts.end())
227             return;
228
229         // If the view doesn't know about the drag yet (there are still pending data requests),
230         // don't update it with information about the drag.
231         if (droppingContext->pendingDataRequests)
232             return;
233
234         if (!droppingContext->dropHappened) {
235             // Don't call dragExited if we have just received a drag-drop signal. This
236             // happens in the case of a successful drop onto the view.
237             const IntPoint& position = droppingContext->lastMotionPosition;
238             DragData dragData(droppingContext->dataObject.get(), position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), DragOperationNone);
239             m_page.dragExited(dragData);
240             m_page.resetCurrentDragInformation();
241         }
242
243         m_droppingContexts.remove(it);
244     });
245 }
246
247 bool DragAndDropHandler::drop(GdkDragContext* context, const IntPoint& position, unsigned time)
248 {
249     DroppingContext* droppingContext = m_droppingContexts.get(context);
250     if (!droppingContext)
251         return false;
252
253     droppingContext->dropHappened = true;
254
255     DataObjectGtk* dataObject = droppingContext->dataObject.get();
256     if (!dataObject)
257         return false;
258
259     DragData dragData(dataObject, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)));
260     SandboxExtension::Handle handle;
261     SandboxExtension::HandleArray sandboxExtensionForUpload;
262     m_page.performDragOperation(dragData, String(), handle, sandboxExtensionForUpload);
263     gtk_drag_finish(context, TRUE, FALSE, time);
264     return true;
265 }
266
267 } // namespace WebKit
268
269 #endif // ENABLE(DRAG_SUPPORT)