summaryrefslogtreecommitdiffstats
path: root/widget/windows/nsNativeDragTarget.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'widget/windows/nsNativeDragTarget.cpp')
-rw-r--r--widget/windows/nsNativeDragTarget.cpp472
1 files changed, 472 insertions, 0 deletions
diff --git a/widget/windows/nsNativeDragTarget.cpp b/widget/windows/nsNativeDragTarget.cpp
new file mode 100644
index 0000000000..b615a3e4bc
--- /dev/null
+++ b/widget/windows/nsNativeDragTarget.cpp
@@ -0,0 +1,472 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include "nsIDragService.h"
+#include "nsWidgetsCID.h"
+#include "nsNativeDragTarget.h"
+#include "nsDragService.h"
+#include "nsINode.h"
+#include "nsCOMPtr.h"
+
+#include "nsIWidget.h"
+#include "nsWindow.h"
+#include "nsClipboard.h"
+#include "KeyboardLayout.h"
+
+#include "mozilla/MouseEvents.h"
+
+using namespace mozilla;
+using namespace mozilla::widget;
+
+// This is cached for Leave notification
+static POINTL gDragLastPoint;
+
+bool nsNativeDragTarget::gDragImageChanged = false;
+
+/*
+ * class nsNativeDragTarget
+ */
+nsNativeDragTarget::nsNativeDragTarget(nsIWidget* aWidget)
+ : m_cRef(0),
+ mEffectsAllowed(DROPEFFECT_MOVE | DROPEFFECT_COPY | DROPEFFECT_LINK),
+ mEffectsPreferred(DROPEFFECT_NONE),
+ mTookOwnRef(false),
+ mWidget(aWidget),
+ mDropTargetHelper(nullptr) {
+ mHWnd = (HWND)mWidget->GetNativeData(NS_NATIVE_WINDOW);
+
+ mDragService = do_GetService("@mozilla.org/widget/dragservice;1");
+}
+
+nsNativeDragTarget::~nsNativeDragTarget() {
+ if (mDropTargetHelper) {
+ mDropTargetHelper->Release();
+ mDropTargetHelper = nullptr;
+ }
+}
+
+// IUnknown methods - see iunknown.h for documentation
+STDMETHODIMP
+nsNativeDragTarget::QueryInterface(REFIID riid, void** ppv) {
+ *ppv = nullptr;
+
+ if (IID_IUnknown == riid || IID_IDropTarget == riid) *ppv = this;
+
+ if (nullptr != *ppv) {
+ ((LPUNKNOWN)*ppv)->AddRef();
+ return S_OK;
+ }
+
+ return E_NOINTERFACE;
+}
+
+STDMETHODIMP_(ULONG)
+nsNativeDragTarget::AddRef(void) {
+ ++m_cRef;
+ NS_LOG_ADDREF(this, m_cRef, "nsNativeDragTarget", sizeof(*this));
+ return m_cRef;
+}
+
+STDMETHODIMP_(ULONG) nsNativeDragTarget::Release(void) {
+ --m_cRef;
+ NS_LOG_RELEASE(this, m_cRef, "nsNativeDragTarget");
+ if (0 != m_cRef) return m_cRef;
+
+ delete this;
+ return 0;
+}
+
+void nsNativeDragTarget::GetGeckoDragAction(DWORD grfKeyState,
+ LPDWORD pdwEffect,
+ uint32_t* aGeckoAction) {
+ // If a window is disabled or a modal window is on top of it
+ // (which implies it is disabled), then we should not allow dropping.
+ if (!mWidget->IsEnabled()) {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ return;
+ }
+
+ // If the user explicitly uses a modifier key, they want the associated action
+ // Shift + Control -> LINK, Shift -> MOVE, Ctrl -> COPY
+ DWORD desiredEffect = DROPEFFECT_NONE;
+ if ((grfKeyState & MK_CONTROL) && (grfKeyState & MK_SHIFT)) {
+ desiredEffect = DROPEFFECT_LINK;
+ } else if (grfKeyState & MK_SHIFT) {
+ desiredEffect = DROPEFFECT_MOVE;
+ } else if (grfKeyState & MK_CONTROL) {
+ desiredEffect = DROPEFFECT_COPY;
+ }
+
+ // Determine the desired effect from what is allowed and preferred.
+ if (!(desiredEffect &= mEffectsAllowed)) {
+ // No modifier key effect is set which is also allowed, check
+ // the preference of the data.
+ desiredEffect = mEffectsPreferred & mEffectsAllowed;
+ if (!desiredEffect) {
+ // No preference is set, so just fall back to the allowed effect itself
+ desiredEffect = mEffectsAllowed;
+ }
+ }
+
+ // Otherwise we should specify the first available effect
+ // from MOVE, COPY, or LINK.
+ if (desiredEffect & DROPEFFECT_MOVE) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_MOVE;
+ } else if (desiredEffect & DROPEFFECT_COPY) {
+ *pdwEffect = DROPEFFECT_COPY;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_COPY;
+ } else if (desiredEffect & DROPEFFECT_LINK) {
+ *pdwEffect = DROPEFFECT_LINK;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_LINK;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ *aGeckoAction = nsIDragService::DRAGDROP_ACTION_NONE;
+ }
+}
+
+inline bool IsKeyDown(char key) { return GetKeyState(key) < 0; }
+
+void nsNativeDragTarget::DispatchDragDropEvent(EventMessage aEventMessage,
+ const POINTL& aPT) {
+ WidgetDragEvent event(true, aEventMessage, mWidget);
+
+ nsWindow* win = static_cast<nsWindow*>(mWidget);
+ win->InitEvent(event);
+ POINT cpos;
+
+ cpos.x = aPT.x;
+ cpos.y = aPT.y;
+
+ if (mHWnd != nullptr) {
+ ::ScreenToClient(mHWnd, &cpos);
+ event.mRefPoint = LayoutDeviceIntPoint(cpos.x, cpos.y);
+ } else {
+ event.mRefPoint = LayoutDeviceIntPoint(0, 0);
+ }
+
+ ModifierKeyState modifierKeyState;
+ modifierKeyState.InitInputEvent(event);
+
+ event.mInputSource =
+ static_cast<nsBaseDragService*>(mDragService.get())->GetInputSource();
+
+ mWidget->DispatchInputEvent(&event);
+}
+
+void nsNativeDragTarget::ProcessDrag(EventMessage aEventMessage,
+ DWORD grfKeyState, POINTL ptl,
+ DWORD* pdwEffect) {
+ // Before dispatching the event make sure we have the correct drop action set
+ uint32_t geckoAction;
+ GetGeckoDragAction(grfKeyState, pdwEffect, &geckoAction);
+
+ // Set the current action into the Gecko specific type
+ nsCOMPtr<nsIDragSession> currSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currSession));
+ if (!currSession) {
+ return;
+ }
+
+ currSession->SetDragAction(geckoAction);
+
+ // Dispatch the event into Gecko
+ DispatchDragDropEvent(aEventMessage, ptl);
+
+ // If TakeChildProcessDragAction returns something other than
+ // DRAGDROP_ACTION_UNINITIALIZED, it means that the last event was sent
+ // to the child process and this event is also being sent to the child
+ // process. In this case, use the last event's action instead.
+ nsDragService* dragService = static_cast<nsDragService*>(mDragService.get());
+ currSession->GetDragAction(&geckoAction);
+
+ int32_t childDragAction = dragService->TakeChildProcessDragAction();
+ if (childDragAction != nsIDragService::DRAGDROP_ACTION_UNINITIALIZED) {
+ geckoAction = childDragAction;
+ }
+
+ if (nsIDragService::DRAGDROP_ACTION_LINK & geckoAction) {
+ *pdwEffect = DROPEFFECT_LINK;
+ } else if (nsIDragService::DRAGDROP_ACTION_COPY & geckoAction) {
+ *pdwEffect = DROPEFFECT_COPY;
+ } else if (nsIDragService::DRAGDROP_ACTION_MOVE & geckoAction) {
+ *pdwEffect = DROPEFFECT_MOVE;
+ } else {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+
+ if (aEventMessage != eDrop) {
+ // Get the cached drag effect from the drag service, the data member should
+ // have been set by whoever handled the WidgetGUIEvent or nsIDOMEvent on
+ // drags.
+ bool canDrop;
+ currSession->GetCanDrop(&canDrop);
+ if (!canDrop) {
+ *pdwEffect = DROPEFFECT_NONE;
+ }
+ }
+
+ // Clear the cached value
+ currSession->SetCanDrop(false);
+}
+
+// IDropTarget methods
+STDMETHODIMP
+nsNativeDragTarget::DragEnter(LPDATAOBJECT pIDataSource, DWORD grfKeyState,
+ POINTL ptl, DWORD* pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pIDataSource);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ // We get a lot of crashes (often uncaught by our handler) later on during
+ // DragOver calls, see bug 1465513. It looks like this might be because
+ // we're not cleaning up previous drags fully and now released resources get
+ // used. Calling IDropTargetHelper::DragLeave before DragEnter seems to fix
+ // this for at least one reproduction of this crash.
+ GetDropTargetHelper()->DragLeave();
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragEnter(mHWnd, pIDataSource, &pt, *pdwEffect);
+ }
+
+ // save a ref to this, in case the window is destroyed underneath us
+ NS_ASSERTION(!mTookOwnRef, "own ref already taken!");
+ this->AddRef();
+ mTookOwnRef = true;
+
+ // tell the drag service about this drag (it may have come from an
+ // outside app).
+ mDragService->StartDragSession();
+
+ void* tempOutData = nullptr;
+ uint32_t tempDataLen = 0;
+ nsresult loadResult = nsClipboard::GetNativeDataOffClipboard(
+ pIDataSource, 0, ::RegisterClipboardFormat(CFSTR_PREFERREDDROPEFFECT),
+ nullptr, &tempOutData, &tempDataLen);
+ if (NS_SUCCEEDED(loadResult) && tempOutData) {
+ mEffectsPreferred = *((DWORD*)tempOutData);
+ free(tempOutData);
+ } else {
+ // We have no preference if we can't obtain it
+ mEffectsPreferred = DROPEFFECT_NONE;
+ }
+
+ // Set the native data object into drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService. It should be a private interface, though.
+ nsDragService* winDragService =
+ static_cast<nsDragService*>(mDragService.get());
+ winDragService->SetIDataObject(pIDataSource);
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragEnter, grfKeyState, ptl, pdwEffect);
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::AddLinkSupportIfCanBeGenerated(
+ LPDATAOBJECT aIDataSource) {
+ // If we don't have a link effect, but we can generate one, fix the
+ // drop effect to include it.
+ if (!(mEffectsAllowed & DROPEFFECT_LINK) && aIDataSource) {
+ if (S_OK == ::OleQueryLinkFromData(aIDataSource)) {
+ mEffectsAllowed |= DROPEFFECT_LINK;
+ }
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragOver(DWORD grfKeyState, POINTL ptl, LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ bool dragImageChanged = gDragImageChanged;
+ gDragImageChanged = false;
+
+ // If a LINK effect could be generated previously from a DragEnter(),
+ // then we should include it as an allowed effect.
+ mEffectsAllowed = (*pdwEffect) | (mEffectsAllowed & DROPEFFECT_LINK);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // Drag was canceled.
+ }
+
+ // without the AddRef() |this| can get destroyed in an event handler
+ this->AddRef();
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ if (dragImageChanged) {
+ // See comment in nsNativeDragTarget::DragEnter.
+ GetDropTargetHelper()->DragLeave();
+ // The drop helper only updates the image during DragEnter, so emulate
+ // a DragEnter if the image was changed.
+ POINT pt = {ptl.x, ptl.y};
+ nsDragService* dragService =
+ static_cast<nsDragService*>(mDragService.get());
+ GetDropTargetHelper()->DragEnter(mHWnd, dragService->GetDataObject(), &pt,
+ *pdwEffect);
+ }
+ POINT pt = {ptl.x, ptl.y};
+ GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
+ }
+
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->FireDragEventAtSource(eDrag, modifierKeyState.GetModifiers());
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDragOver, grfKeyState, ptl, pdwEffect);
+
+ this->Release();
+
+ return S_OK;
+}
+
+STDMETHODIMP
+nsNativeDragTarget::DragLeave() {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+
+ // dispatch the event into Gecko
+ DispatchDragDropEvent(eDragExit, gDragLastPoint);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ mDragService->GetCurrentSession(getter_AddRefs(currentDragSession));
+
+ if (currentDragSession) {
+ nsCOMPtr<nsINode> sourceNode;
+ currentDragSession->GetSourceNode(getter_AddRefs(sourceNode));
+
+ if (!sourceNode) {
+ // We're leaving a window while doing a drag that was
+ // initiated in a different app. End the drag session, since
+ // we're done with it for now (until the user drags back into
+ // mozilla).
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ }
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+void nsNativeDragTarget::DragCancel() {
+ // Cancel the drag session if we did DragEnter.
+ if (mTookOwnRef) {
+ if (GetDropTargetHelper()) {
+ GetDropTargetHelper()->DragLeave();
+ }
+ if (mDragService) {
+ ModifierKeyState modifierKeyState;
+ nsCOMPtr<nsIDragService> dragService = mDragService;
+ dragService->EndDragSession(false, modifierKeyState.GetModifiers());
+ }
+ this->Release(); // matching the AddRef in DragEnter
+ mTookOwnRef = false;
+ }
+}
+
+STDMETHODIMP
+nsNativeDragTarget::Drop(LPDATAOBJECT pData, DWORD grfKeyState, POINTL aPT,
+ LPDWORD pdwEffect) {
+ if (!mDragService) {
+ return E_FAIL;
+ }
+
+ mEffectsAllowed = *pdwEffect;
+ AddLinkSupportIfCanBeGenerated(pData);
+
+ // Drag and drop image helper
+ if (GetDropTargetHelper()) {
+ POINT pt = {aPT.x, aPT.y};
+ GetDropTargetHelper()->Drop(pData, &pt, *pdwEffect);
+ }
+
+ // Set the native data object into the drag service
+ //
+ // This cast is ok because in the constructor we created a
+ // the actual implementation we wanted, so we know this is
+ // a nsDragService (but it should still be a private interface)
+ nsDragService* winDragService =
+ static_cast<nsDragService*>(mDragService.get());
+ winDragService->SetIDataObject(pData);
+
+ // NOTE: ProcessDrag spins the event loop which may destroy arbitrary objects.
+ // We use strong refs to prevent it from destroying these:
+ RefPtr<nsNativeDragTarget> kungFuDeathGrip = this;
+ nsCOMPtr<nsIDragService> serv = mDragService;
+
+ // Now process the native drag state and then dispatch the event
+ ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect);
+
+ nsCOMPtr<nsIDragSession> currentDragSession;
+ serv->GetCurrentSession(getter_AddRefs(currentDragSession));
+ if (!currentDragSession) {
+ return S_OK; // DragCancel() was called.
+ }
+
+ // Let the win drag service know whether this session experienced
+ // a drop event within the application. Drop will not oocur if the
+ // drop landed outside the app. (used in tab tear off, bug 455884)
+ winDragService->SetDroppedLocal();
+
+ // tell the drag service we're done with the session
+ // Use GetMessagePos to get the position of the mouse at the last message
+ // seen by the event loop. (Bug 489729)
+ DWORD pos = ::GetMessagePos();
+ POINT cpos;
+ cpos.x = GET_X_LPARAM(pos);
+ cpos.y = GET_Y_LPARAM(pos);
+ winDragService->SetDragEndPoint(nsIntPoint(cpos.x, cpos.y));
+ ModifierKeyState modifierKeyState;
+ serv->EndDragSession(true, modifierKeyState.GetModifiers());
+
+ // release the ref that was taken in DragEnter
+ NS_ASSERTION(mTookOwnRef, "want to release own ref, but not taken!");
+ if (mTookOwnRef) {
+ this->Release();
+ mTookOwnRef = false;
+ }
+
+ return S_OK;
+}
+
+/**
+ * By lazy loading mDropTargetHelper we save 50-70ms of startup time
+ * which is ~5% of startup time.
+ */
+IDropTargetHelper* nsNativeDragTarget::GetDropTargetHelper() {
+ if (!mDropTargetHelper) {
+ CoCreateInstance(CLSID_DragDropHelper, nullptr, CLSCTX_INPROC_SERVER,
+ IID_IDropTargetHelper, (LPVOID*)&mDropTargetHelper);
+ }
+
+ return mDropTargetHelper;
+}