diff options
Diffstat (limited to '')
-rw-r--r-- | widget/windows/nsNativeDragTarget.cpp | 472 |
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; +} |