Split the class inheritance trees for nsIDragService and nsIDragSession. Remember that, before the start of this patch series, the inheritance diagram was: nsDragService -> nsBaseDragService -> nsIDragService + nsIDragSession and the only instance was a singleton. We switched it to: nsDragService -> nsDragSession -> nsBaseDragService -> nsIDragService + nsBaseDragSession -> nsIDragSession and maintained the singleton. This allowed us to allow us to move things to the new classes without breaking behavior. We are done with that, so we can now change the inheritance to its final form: nsDragService -> nsBaseDragService -> nsIDragService nsDragSession -> nsBaseDragSession -> nsIDragSession Of course, we also need to properly construct and release the nsIDragSessions (formerly part of the singleton), so that is done here as well. That happens in nsBaseDrag[Service|Session] for parent process drags and in nsDrag[Service|Session]Proxy for content. This is all fairly straightforward, except in the case of gtk, where we need to change some callback behavior. Differential Revision: https://phabricator.services.mozilla.com/D211084
473 lines
15 KiB
C++
473 lines
15 KiB
C++
/* -*- 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/dom/MouseEventBinding.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);
|
|
|
|
nsDragSession* currSession =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
if (currSession) {
|
|
event.mInputSource = currSession->GetInputSource();
|
|
} else {
|
|
event.mInputSource = dom::MouseEvent_Binding::MOZ_SOURCE_MOUSE;
|
|
}
|
|
|
|
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
|
|
RefPtr<nsDragSession> currSession =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
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.
|
|
currSession->GetDragAction(&geckoAction);
|
|
|
|
int32_t childDragAction = currSession->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(mWidget);
|
|
|
|
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
|
|
RefPtr<nsDragSession> session =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
session->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);
|
|
|
|
RefPtr<nsDragSession> currentDragSession =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
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};
|
|
GetDropTargetHelper()->DragEnter(
|
|
mHWnd, currentDragSession->GetDataObject(), &pt, *pdwEffect);
|
|
}
|
|
POINT pt = {ptl.x, ptl.y};
|
|
GetDropTargetHelper()->DragOver(&pt, *pdwEffect);
|
|
}
|
|
|
|
ModifierKeyState modifierKeyState;
|
|
currentDragSession->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(mWidget);
|
|
|
|
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;
|
|
currentDragSession->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;
|
|
RefPtr<nsIDragSession> session =
|
|
mDragService->GetCurrentSession(mWidget);
|
|
if (session) {
|
|
session->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
|
|
RefPtr<nsDragSession> currentDragSession =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
if (!currentDragSession) {
|
|
return S_OK;
|
|
}
|
|
currentDragSession->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;
|
|
|
|
// Now process the native drag state and then dispatch the event
|
|
ProcessDrag(eDrop, grfKeyState, aPT, pdwEffect);
|
|
|
|
currentDragSession =
|
|
static_cast<nsDragSession*>(mDragService->GetCurrentSession(mWidget));
|
|
if (!currentDragSession) {
|
|
return S_OK; // DragCancel() was called.
|
|
}
|
|
|
|
// Let the win drag session know whether it 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)
|
|
currentDragSession->SetDroppedLocal();
|
|
|
|
// Tell the drag session we're done with it.
|
|
// 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);
|
|
currentDragSession->SetDragEndPoint(cpos.x, cpos.y);
|
|
ModifierKeyState modifierKeyState;
|
|
currentDragSession->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;
|
|
}
|