[WPE][GTK] Bump minimum versions of GLib, GTK, libsoup, ATK, GStreamer, and Cairo
[WebKit-https.git] / Source / WebKit / 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/DragData.h>
33 #include <WebCore/GRefPtrGtk.h>
34 #include <WebCore/GUniquePtrGtk.h>
35 #include <WebCore/GtkUtilities.h>
36 #include <WebCore/PasteboardHelper.h>
37 #include <wtf/RunLoop.h>
38
39 namespace WebKit {
40 using namespace WebCore;
41
42 DragAndDropHandler::DragAndDropHandler(WebPageProxy& page)
43     : m_page(page)
44 {
45 }
46
47 DragAndDropHandler::DroppingContext::DroppingContext(GdkDragContext* gdkContext, const IntPoint& position)
48     : gdkContext(gdkContext)
49     , lastMotionPosition(position)
50     , selectionData(SelectionData::create())
51 {
52 }
53
54 static inline GdkDragAction dragOperationToGdkDragActions(DragOperation coreAction)
55 {
56     GdkDragAction gdkAction = static_cast<GdkDragAction>(0);
57     if (coreAction == DragOperationNone)
58         return gdkAction;
59
60     if (coreAction & DragOperationCopy)
61         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_COPY | gdkAction);
62     if (coreAction & DragOperationMove)
63         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_MOVE | gdkAction);
64     if (coreAction & DragOperationLink)
65         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_LINK | gdkAction);
66     if (coreAction & DragOperationPrivate)
67         gdkAction = static_cast<GdkDragAction>(GDK_ACTION_PRIVATE | gdkAction);
68
69     return gdkAction;
70 }
71
72 static inline GdkDragAction dragOperationToSingleGdkDragAction(DragOperation coreAction)
73 {
74     if (coreAction == DragOperationEvery || coreAction & DragOperationCopy)
75         return GDK_ACTION_COPY;
76     if (coreAction & DragOperationMove)
77         return GDK_ACTION_MOVE;
78     if (coreAction & DragOperationLink)
79         return GDK_ACTION_LINK;
80     if (coreAction & DragOperationPrivate)
81         return GDK_ACTION_PRIVATE;
82     return static_cast<GdkDragAction>(0);
83 }
84
85 static inline DragOperation gdkDragActionToDragOperation(GdkDragAction gdkAction)
86 {
87     // We have no good way to detect DragOperationEvery other than
88     // to use it when all applicable flags are on.
89     if (gdkAction & GDK_ACTION_COPY
90         && gdkAction & GDK_ACTION_MOVE
91         && gdkAction & GDK_ACTION_LINK
92         && gdkAction & GDK_ACTION_PRIVATE)
93         return DragOperationEvery;
94
95     unsigned action = DragOperationNone;
96     if (gdkAction & GDK_ACTION_COPY)
97         action |= DragOperationCopy;
98     if (gdkAction & GDK_ACTION_MOVE)
99         action |= DragOperationMove;
100     if (gdkAction & GDK_ACTION_LINK)
101         action |= DragOperationLink;
102     if (gdkAction & GDK_ACTION_PRIVATE)
103         action |= DragOperationPrivate;
104     return static_cast<DragOperation>(action);
105 }
106
107 void DragAndDropHandler::startDrag(Ref<SelectionData>&& selection, DragOperation dragOperation, RefPtr<ShareableBitmap>&& dragImage)
108 {
109     // WebCore::EventHandler does not support more than one DnD operation at the same time for
110     // a given page, so we should cancel any previous operation whose context we might have
111     // stored, should we receive a new startDrag event before finishing a previous DnD operation.
112     if (m_dragContext) {
113         gtk_drag_cancel(m_dragContext.get());
114         m_dragContext = nullptr;
115     }
116
117     m_draggingSelectionData = WTFMove(selection);
118     GRefPtr<GtkTargetList> targetList = PasteboardHelper::singleton().targetListForSelectionData(*m_draggingSelectionData);
119
120     GUniquePtr<GdkEvent> currentEvent(gtk_get_current_event());
121     GdkDragContext* context = gtk_drag_begin(m_page.viewWidget(), targetList.get(), dragOperationToGdkDragActions(dragOperation),
122         GDK_BUTTON_PRIMARY, currentEvent.get());
123
124     m_dragContext = context;
125
126     if (dragImage) {
127         RefPtr<cairo_surface_t> image(dragImage->createCairoSurface());
128         // Use the center of the drag image as hotspot.
129         cairo_surface_set_device_offset(image.get(), -cairo_image_surface_get_width(image.get()) / 2, -cairo_image_surface_get_height(image.get()) / 2);
130         gtk_drag_set_icon_surface(context, image.get());
131     } else
132         gtk_drag_set_icon_default(context);
133 }
134
135 void DragAndDropHandler::fillDragData(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info)
136 {
137     // This can happen when attempting to call finish drag from webkitWebViewBaseDragDataGet()
138     // for a obsolete DnD operation that got previously cancelled in startDrag().
139     if (m_dragContext.get() != context)
140         return;
141
142     ASSERT(m_draggingSelectionData);
143     PasteboardHelper::singleton().fillSelectionData(*m_draggingSelectionData, info, selectionData);
144 }
145
146 void DragAndDropHandler::finishDrag(GdkDragContext* context)
147 {
148     // This can happen when attempting to call finish drag from webkitWebViewBaseDragEnd()
149     // for a obsolete DnD operation that got previously cancelled in startDrag().
150     if (m_dragContext.get() != context)
151         return;
152
153     if (!m_draggingSelectionData)
154         return;
155
156     m_dragContext = nullptr;
157     m_draggingSelectionData = nullptr;
158
159     GdkDevice* device = gdk_drag_context_get_device(context);
160     int x = 0, y = 0;
161     gdk_device_get_window_at_position(device, &x, &y);
162     int xRoot = 0, yRoot = 0;
163     gdk_device_get_position(device, nullptr, &xRoot, &yRoot);
164     m_page.dragEnded(IntPoint(x, y), IntPoint(xRoot, yRoot), gdkDragActionToDragOperation(gdk_drag_context_get_selected_action(context)));
165 }
166
167 SelectionData* DragAndDropHandler::dropDataSelection(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, IntPoint& position)
168 {
169     DroppingContext* droppingContext = m_droppingContexts.get(context);
170     if (!droppingContext)
171         return nullptr;
172
173     droppingContext->pendingDataRequests--;
174     PasteboardHelper::singleton().fillSelectionData(selectionData, info, droppingContext->selectionData);
175     if (droppingContext->pendingDataRequests)
176         return nullptr;
177
178     // The coordinates passed to drag-data-received signal are sometimes
179     // inaccurate in WTR, so use the coordinates of the last motion event.
180     position = droppingContext->lastMotionPosition;
181
182     // If there are no more pending requests, start sending dragging data to WebCore.
183     return droppingContext->selectionData.ptr();
184 }
185
186 void DragAndDropHandler::dragEntered(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, unsigned time)
187 {
188     IntPoint position;
189     auto* selection = dropDataSelection(context, selectionData, info, position);
190     if (!selection)
191         return;
192
193     DragData dragData(selection, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)));
194     m_page.resetCurrentDragInformation();
195     m_page.dragEntered(dragData);
196     DragOperation operation = m_page.currentDragOperation();
197     gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time);
198 }
199
200 SelectionData* DragAndDropHandler::dragDataSelection(GdkDragContext* context, const IntPoint& position, unsigned time)
201 {
202     std::unique_ptr<DroppingContext>& droppingContext = m_droppingContexts.add(context, nullptr).iterator->value;
203     if (!droppingContext) {
204         GtkWidget* widget = m_page.viewWidget();
205         droppingContext = std::make_unique<DroppingContext>(context, position);
206         Vector<GdkAtom> acceptableTargets(PasteboardHelper::singleton().dropAtomsForContext(widget, droppingContext->gdkContext));
207         droppingContext->pendingDataRequests = acceptableTargets.size();
208         for (auto& target : acceptableTargets)
209             gtk_drag_get_data(widget, droppingContext->gdkContext, target, time);
210     } else
211         droppingContext->lastMotionPosition = position;
212
213     // Don't send any drag information to WebCore until we've retrieved all the data for this drag operation.
214     // Otherwise we'd have to block to wait for the drag's data.
215     if (droppingContext->pendingDataRequests > 0)
216         return nullptr;
217
218     return droppingContext->selectionData.ptr();
219 }
220
221 void DragAndDropHandler::dragMotion(GdkDragContext* context, const IntPoint& position, unsigned time)
222 {
223     auto* selection = dragDataSelection(context, position, time);
224     if (!selection)
225         return;
226
227     DragData dragData(selection, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)));
228     m_page.dragUpdated(dragData);
229     DragOperation operation = m_page.currentDragOperation();
230     gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time);
231 }
232
233 void DragAndDropHandler::dragLeave(GdkDragContext* context)
234 {
235     DroppingContext* droppingContext = m_droppingContexts.get(context);
236     if (!droppingContext)
237         return;
238
239     // During a drop GTK+ will fire a drag-leave signal right before firing
240     // the drag-drop signal. We want the actions for drag-leave to happen after
241     // those for drag-drop, so schedule them to happen asynchronously here.
242     RunLoop::main().dispatch([this, context, droppingContext]() {
243         auto it = m_droppingContexts.find(context);
244         if (it == m_droppingContexts.end())
245             return;
246
247         // If the view doesn't know about the drag yet (there are still pending data requests),
248         // don't update it with information about the drag.
249         if (droppingContext->pendingDataRequests)
250             return;
251
252         if (!droppingContext->dropHappened) {
253             // Don't call dragExited if we have just received a drag-drop signal. This
254             // happens in the case of a successful drop onto the view.
255             const IntPoint& position = droppingContext->lastMotionPosition;
256             DragData dragData(droppingContext->selectionData.ptr(), position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), DragOperationNone);
257             m_page.dragExited(dragData);
258             m_page.resetCurrentDragInformation();
259         }
260
261         m_droppingContexts.remove(it);
262     });
263 }
264
265 bool DragAndDropHandler::drop(GdkDragContext* context, const IntPoint& position, unsigned time)
266 {
267     DroppingContext* droppingContext = m_droppingContexts.get(context);
268     if (!droppingContext)
269         return false;
270
271     droppingContext->dropHappened = true;
272
273     uint32_t flags = 0;
274     if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_COPY)
275         flags |= WebCore::DragApplicationIsCopyKeyDown;
276     DragData dragData(droppingContext->selectionData.ptr(), position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)), static_cast<WebCore::DragApplicationFlags>(flags));
277     m_page.performDragOperation(dragData, String(), { }, { });
278     gtk_drag_finish(context, TRUE, FALSE, time);
279     return true;
280 }
281
282 } // namespace WebKit
283
284 #endif // ENABLE(DRAG_SUPPORT)