diff options
Diffstat (limited to 'Source/WebKit2/UIProcess/gtk/DragAndDropHandler.cpp')
-rw-r--r-- | Source/WebKit2/UIProcess/gtk/DragAndDropHandler.cpp | 305 |
1 files changed, 305 insertions, 0 deletions
diff --git a/Source/WebKit2/UIProcess/gtk/DragAndDropHandler.cpp b/Source/WebKit2/UIProcess/gtk/DragAndDropHandler.cpp new file mode 100644 index 000000000..73fb54d37 --- /dev/null +++ b/Source/WebKit2/UIProcess/gtk/DragAndDropHandler.cpp @@ -0,0 +1,305 @@ +/* + * Copyright (C) 2014 Igalia S.L. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include "DragAndDropHandler.h" + +#if ENABLE(DRAG_SUPPORT) + +#include "WebPageProxy.h" +#include <WebCore/DragData.h> +#include <WebCore/GRefPtrGtk.h> +#include <WebCore/GtkUtilities.h> +#include <WebCore/PasteboardHelper.h> +#include <wtf/RunLoop.h> +#include <wtf/glib/GUniquePtr.h> + +using namespace WebCore; + +namespace WebKit { + +DragAndDropHandler::DragAndDropHandler(WebPageProxy& page) + : m_page(page) +{ +} + +DragAndDropHandler::DroppingContext::DroppingContext(GdkDragContext* gdkContext, const IntPoint& position) + : gdkContext(gdkContext) + , lastMotionPosition(position) + , selectionData(SelectionData::create()) +{ +} + +static inline GdkDragAction dragOperationToGdkDragActions(DragOperation coreAction) +{ + GdkDragAction gdkAction = static_cast<GdkDragAction>(0); + if (coreAction == DragOperationNone) + return gdkAction; + + if (coreAction & DragOperationCopy) + gdkAction = static_cast<GdkDragAction>(GDK_ACTION_COPY | gdkAction); + if (coreAction & DragOperationMove) + gdkAction = static_cast<GdkDragAction>(GDK_ACTION_MOVE | gdkAction); + if (coreAction & DragOperationLink) + gdkAction = static_cast<GdkDragAction>(GDK_ACTION_LINK | gdkAction); + if (coreAction & DragOperationPrivate) + gdkAction = static_cast<GdkDragAction>(GDK_ACTION_PRIVATE | gdkAction); + + return gdkAction; +} + +static inline GdkDragAction dragOperationToSingleGdkDragAction(DragOperation coreAction) +{ + if (coreAction == DragOperationEvery || coreAction & DragOperationCopy) + return GDK_ACTION_COPY; + if (coreAction & DragOperationMove) + return GDK_ACTION_MOVE; + if (coreAction & DragOperationLink) + return GDK_ACTION_LINK; + if (coreAction & DragOperationPrivate) + return GDK_ACTION_PRIVATE; + return static_cast<GdkDragAction>(0); +} + +static inline DragOperation gdkDragActionToDragOperation(GdkDragAction gdkAction) +{ + // We have no good way to detect DragOperationEvery other than + // to use it when all applicable flags are on. + if (gdkAction & GDK_ACTION_COPY + && gdkAction & GDK_ACTION_MOVE + && gdkAction & GDK_ACTION_LINK + && gdkAction & GDK_ACTION_PRIVATE) + return DragOperationEvery; + + unsigned action = DragOperationNone; + if (gdkAction & GDK_ACTION_COPY) + action |= DragOperationCopy; + if (gdkAction & GDK_ACTION_MOVE) + action |= DragOperationMove; + if (gdkAction & GDK_ACTION_LINK) + action |= DragOperationLink; + if (gdkAction & GDK_ACTION_PRIVATE) + action |= DragOperationPrivate; + return static_cast<DragOperation>(action); +} + +void DragAndDropHandler::startDrag(Ref<SelectionData>&& selection, DragOperation dragOperation, RefPtr<ShareableBitmap>&& dragImage) +{ +#if GTK_CHECK_VERSION(3, 16, 0) + m_draggingSelectionData = WTFMove(selection); + GRefPtr<GtkTargetList> targetList = PasteboardHelper::singleton().targetListForSelectionData(*m_draggingSelectionData); +#else + RefPtr<SelectionData> selectionData = WTFMove(selection); + GRefPtr<GtkTargetList> targetList = PasteboardHelper::singleton().targetListForSelectionData(*selectionData); +#endif + + GUniquePtr<GdkEvent> currentEvent(gtk_get_current_event()); + GdkDragContext* context = gtk_drag_begin(m_page.viewWidget(), targetList.get(), dragOperationToGdkDragActions(dragOperation), + GDK_BUTTON_PRIMARY, currentEvent.get()); + +#if GTK_CHECK_VERSION(3, 16, 0) + // WebCore::EventHandler does not support more than one DnD operation at the same time for + // a given page, so we should cancel any previous operation whose context we might have + // stored, should we receive a new startDrag event before finishing a previous DnD operation. + if (m_dragContext) + gtk_drag_cancel(m_dragContext.get()); + m_dragContext = context; +#else + // We don't have gtk_drag_cancel() in GTK+ < 3.16, so we use the old code. + // See https://bugs.webkit.org/show_bug.cgi?id=138468 + m_draggingSelectionDataMap.set(context, WTFMove(selectionData)); +#endif + + if (dragImage) { + RefPtr<cairo_surface_t> image(dragImage->createCairoSurface()); + // Use the center of the drag image as hotspot. + cairo_surface_set_device_offset(image.get(), -cairo_image_surface_get_width(image.get()) / 2, -cairo_image_surface_get_height(image.get()) / 2); + gtk_drag_set_icon_surface(context, image.get()); + } else + gtk_drag_set_icon_default(context); +} + +void DragAndDropHandler::fillDragData(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info) +{ +#if GTK_CHECK_VERSION(3, 16, 0) + // This can happen when attempting to call finish drag from webkitWebViewBaseDragDataGet() + // for a obsolete DnD operation that got previously cancelled in startDrag(). + if (m_dragContext.get() != context) + return; + + ASSERT(m_draggingSelectionData); + PasteboardHelper::singleton().fillSelectionData(*m_draggingSelectionData, info, selectionData); +#else + if (auto* selection = m_draggingSelectionDataMap.get(context)) + PasteboardHelper::singleton().fillSelectionData(*selection, info, selectionData); +#endif +} + +void DragAndDropHandler::finishDrag(GdkDragContext* context) +{ +#if GTK_CHECK_VERSION(3, 16, 0) + // This can happen when attempting to call finish drag from webkitWebViewBaseDragEnd() + // for a obsolete DnD operation that got previously cancelled in startDrag(). + if (m_dragContext.get() != context) + return; + + if (!m_draggingSelectionData) + return; + + m_dragContext = nullptr; + m_draggingSelectionData = nullptr; +#else + if (!m_draggingSelectionDataMap.remove(context)) + return; +#endif + + GdkDevice* device = gdk_drag_context_get_device(context); + int x = 0, y = 0; + gdk_device_get_window_at_position(device, &x, &y); + int xRoot = 0, yRoot = 0; + gdk_device_get_position(device, nullptr, &xRoot, &yRoot); + m_page.dragEnded(IntPoint(x, y), IntPoint(xRoot, yRoot), gdkDragActionToDragOperation(gdk_drag_context_get_selected_action(context))); +} + +SelectionData* DragAndDropHandler::dropDataSelection(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, IntPoint& position) +{ + DroppingContext* droppingContext = m_droppingContexts.get(context); + if (!droppingContext) + return nullptr; + + droppingContext->pendingDataRequests--; + PasteboardHelper::singleton().fillSelectionData(selectionData, info, droppingContext->selectionData); + if (droppingContext->pendingDataRequests) + return nullptr; + + // The coordinates passed to drag-data-received signal are sometimes + // inaccurate in WTR, so use the coordinates of the last motion event. + position = droppingContext->lastMotionPosition; + + // If there are no more pending requests, start sending dragging data to WebCore. + return droppingContext->selectionData.ptr(); +} + +void DragAndDropHandler::dragEntered(GdkDragContext* context, GtkSelectionData* selectionData, unsigned info, unsigned time) +{ + IntPoint position; + auto* selection = dropDataSelection(context, selectionData, info, position); + if (!selection) + return; + + DragData dragData(selection, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context))); + m_page.resetCurrentDragInformation(); + m_page.dragEntered(dragData); + DragOperation operation = m_page.currentDragOperation(); + gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time); +} + +SelectionData* DragAndDropHandler::dragDataSelection(GdkDragContext* context, const IntPoint& position, unsigned time) +{ + std::unique_ptr<DroppingContext>& droppingContext = m_droppingContexts.add(context, nullptr).iterator->value; + if (!droppingContext) { + GtkWidget* widget = m_page.viewWidget(); + droppingContext = std::make_unique<DroppingContext>(context, position); + Vector<GdkAtom> acceptableTargets(PasteboardHelper::singleton().dropAtomsForContext(widget, droppingContext->gdkContext)); + droppingContext->pendingDataRequests = acceptableTargets.size(); + for (auto& target : acceptableTargets) + gtk_drag_get_data(widget, droppingContext->gdkContext, target, time); + } else + droppingContext->lastMotionPosition = position; + + // Don't send any drag information to WebCore until we've retrieved all the data for this drag operation. + // Otherwise we'd have to block to wait for the drag's data. + if (droppingContext->pendingDataRequests > 0) + return nullptr; + + return droppingContext->selectionData.ptr(); +} + +void DragAndDropHandler::dragMotion(GdkDragContext* context, const IntPoint& position, unsigned time) +{ + auto* selection = dragDataSelection(context, position, time); + if (!selection) + return; + + DragData dragData(selection, position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context))); + m_page.dragUpdated(dragData); + DragOperation operation = m_page.currentDragOperation(); + gdk_drag_status(context, dragOperationToSingleGdkDragAction(operation), time); +} + +void DragAndDropHandler::dragLeave(GdkDragContext* context) +{ + DroppingContext* droppingContext = m_droppingContexts.get(context); + if (!droppingContext) + return; + + // During a drop GTK+ will fire a drag-leave signal right before firing + // the drag-drop signal. We want the actions for drag-leave to happen after + // those for drag-drop, so schedule them to happen asynchronously here. + RunLoop::main().dispatch([this, droppingContext]() { + auto it = m_droppingContexts.find(droppingContext->gdkContext); + if (it == m_droppingContexts.end()) + return; + + // If the view doesn't know about the drag yet (there are still pending data requests), + // don't update it with information about the drag. + if (droppingContext->pendingDataRequests) + return; + + if (!droppingContext->dropHappened) { + // Don't call dragExited if we have just received a drag-drop signal. This + // happens in the case of a successful drop onto the view. + const IntPoint& position = droppingContext->lastMotionPosition; + DragData dragData(droppingContext->selectionData.ptr(), position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), DragOperationNone); + m_page.dragExited(dragData); + m_page.resetCurrentDragInformation(); + } + + m_droppingContexts.remove(it); + }); +} + +bool DragAndDropHandler::drop(GdkDragContext* context, const IntPoint& position, unsigned time) +{ + DroppingContext* droppingContext = m_droppingContexts.get(context); + if (!droppingContext) + return false; + + droppingContext->dropHappened = true; + + uint32_t flags = 0; + if (gdk_drag_context_get_selected_action(context) == GDK_ACTION_COPY) + flags |= WebCore::DragApplicationIsCopyKeyDown; + DragData dragData(droppingContext->selectionData.ptr(), position, convertWidgetPointToScreenPoint(m_page.viewWidget(), position), gdkDragActionToDragOperation(gdk_drag_context_get_actions(context)), static_cast<WebCore::DragApplicationFlags>(flags)); + SandboxExtension::Handle handle; + SandboxExtension::HandleArray sandboxExtensionForUpload; + m_page.performDragOperation(dragData, String(), handle, sandboxExtensionForUpload); + gtk_drag_finish(context, TRUE, FALSE, time); + return true; +} + +} // namespace WebKit + +#endif // ENABLE(DRAG_SUPPORT) |