1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3 * License, v. 2.0. If a copy of the MPL was not distributed with this
4 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
11 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
14 #include "mozilla/RefPtr.h"
15 #include "nsDragService.h"
16 #include "nsITransferable.h"
17 #include "nsDataObj.h"
19 #include "nsWidgetsCID.h"
20 #include "nsNativeDragTarget.h"
21 #include "nsNativeDragSource.h"
22 #include "nsClipboard.h"
23 #include "mozilla/dom/Document.h"
24 #include "mozilla/dom/DocumentInlines.h"
25 #include "nsDataObjCollection.h"
27 #include "nsArrayUtils.h"
30 #include "nsIScreenManager.h"
31 #include "nsToolkit.h"
33 #include "nsDirectoryServiceDefs.h"
34 #include "nsUnicharUtils.h"
36 #include "nsMathUtils.h"
38 #include "KeyboardLayout.h"
39 #include "gfxContext.h"
40 #include "mozilla/gfx/2D.h"
41 #include "mozilla/gfx/DataSurfaceHelpers.h"
42 #include "mozilla/gfx/Tools.h"
44 using namespace mozilla
;
45 using namespace mozilla::gfx
;
46 using namespace mozilla::widget
;
48 //-------------------------------------------------------------------------
50 // DragService constructor
52 //-------------------------------------------------------------------------
53 nsDragService::nsDragService()
54 : mDataObject(nullptr), mSentLocalDropEvent(false) {}
56 //-------------------------------------------------------------------------
58 // DragService destructor
60 //-------------------------------------------------------------------------
61 nsDragService::~nsDragService() { NS_IF_RELEASE(mDataObject
); }
63 bool nsDragService::CreateDragImage(nsINode
* aDOMNode
,
64 const Maybe
<CSSIntRegion
>& aRegion
,
66 if (!psdi
) return false;
68 memset(psdi
, 0, sizeof(SHDRAGIMAGE
));
69 if (!aDOMNode
) return false;
71 // Prepare the drag image
72 LayoutDeviceIntRect dragRect
;
73 RefPtr
<SourceSurface
> surface
;
75 DrawDrag(aDOMNode
, aRegion
, mScreenPosition
, &dragRect
, &surface
, &pc
);
76 if (!surface
) return false;
78 uint32_t bmWidth
= dragRect
.Width(), bmHeight
= dragRect
.Height();
80 if (bmWidth
== 0 || bmHeight
== 0) return false;
82 psdi
->crColorKey
= CLR_NONE
;
84 RefPtr
<DataSourceSurface
> dataSurface
= Factory::CreateDataSourceSurface(
85 IntSize(bmWidth
, bmHeight
), SurfaceFormat::B8G8R8A8
);
86 NS_ENSURE_TRUE(dataSurface
, false);
88 DataSourceSurface::MappedSurface map
;
89 if (!dataSurface
->Map(DataSourceSurface::MapType::READ_WRITE
, &map
)) {
93 RefPtr
<DrawTarget
> dt
= Factory::CreateDrawTargetForData(
94 BackendType::CAIRO
, map
.mData
, dataSurface
->GetSize(), map
.mStride
,
95 dataSurface
->GetFormat());
103 Rect(0, 0, dataSurface
->GetSize().width
, dataSurface
->GetSize().height
),
104 Rect(0, 0, surface
->GetSize().width
, surface
->GetSize().height
),
105 DrawSurfaceOptions(), DrawOptions(1.0f
, CompositionOp::OP_SOURCE
));
109 memset((void*)&bmih
, 0, sizeof(BITMAPV5HEADER
));
110 bmih
.bV5Size
= sizeof(BITMAPV5HEADER
);
111 bmih
.bV5Width
= bmWidth
;
112 bmih
.bV5Height
= -(int32_t)bmHeight
; // flip vertical
114 bmih
.bV5BitCount
= 32;
115 bmih
.bV5Compression
= BI_BITFIELDS
;
116 bmih
.bV5RedMask
= 0x00FF0000;
117 bmih
.bV5GreenMask
= 0x0000FF00;
118 bmih
.bV5BlueMask
= 0x000000FF;
119 bmih
.bV5AlphaMask
= 0xFF000000;
121 HDC hdcSrc
= CreateCompatibleDC(nullptr);
122 void* lpBits
= nullptr;
124 psdi
->hbmpDragImage
=
125 ::CreateDIBSection(hdcSrc
, (BITMAPINFO
*)&bmih
, DIB_RGB_COLORS
,
126 (void**)&lpBits
, nullptr, 0);
127 if (psdi
->hbmpDragImage
&& lpBits
) {
128 CopySurfaceDataToPackedArray(map
.mData
, static_cast<uint8_t*>(lpBits
),
129 dataSurface
->GetSize(), map
.mStride
,
130 BytesPerPixel(dataSurface
->GetFormat()));
133 psdi
->sizeDragImage
.cx
= bmWidth
;
134 psdi
->sizeDragImage
.cy
= bmHeight
;
136 const auto screenPoint
=
137 LayoutDeviceIntPoint::Round(mScreenPosition
* pc
->CSSToDevPixelScale());
138 psdi
->ptOffset
.x
= screenPoint
.x
- dragRect
.X();
139 psdi
->ptOffset
.y
= screenPoint
.y
- dragRect
.Y();
144 dataSurface
->Unmap();
146 return psdi
->hbmpDragImage
!= nullptr;
149 //-------------------------------------------------------------------------
150 nsresult
nsDragService::InvokeDragSessionImpl(
151 nsIArray
* anArrayTransferables
, const Maybe
<CSSIntRegion
>& aRegion
,
152 uint32_t aActionType
) {
153 // Try and get source URI of the items that are being dragged
154 nsIURI
* uri
= nullptr;
156 RefPtr
<dom::Document
> doc(mSourceDocument
);
158 uri
= doc
->GetDocumentURI();
161 uint32_t numItemsToDrag
= 0;
162 nsresult rv
= anArrayTransferables
->GetLength(&numItemsToDrag
);
163 if (!numItemsToDrag
) return NS_ERROR_FAILURE
;
165 // The clipboard class contains some static utility methods that we
166 // can use to create an IDataObject from the transferable
168 // if we're dragging more than one item, we need to create a
169 // "collection" object to fake out the OS. This collection contains
170 // one |IDataObject| for each transferable. If there is just the one
171 // (most cases), only pass around the native |IDataObject|.
172 RefPtr
<IDataObject
> itemToDrag
;
173 if (numItemsToDrag
> 1) {
174 nsDataObjCollection
* dataObjCollection
= new nsDataObjCollection();
175 if (!dataObjCollection
) return NS_ERROR_OUT_OF_MEMORY
;
176 itemToDrag
= dataObjCollection
;
177 for (uint32_t i
= 0; i
< numItemsToDrag
; ++i
) {
178 nsCOMPtr
<nsITransferable
> trans
=
179 do_QueryElementAt(anArrayTransferables
, i
);
181 RefPtr
<IDataObject
> dataObj
;
182 rv
= nsClipboard::CreateNativeDataObject(trans
, getter_AddRefs(dataObj
),
184 NS_ENSURE_SUCCESS(rv
, rv
);
185 // Add the flavors to the collection object too
186 rv
= nsClipboard::SetupNativeDataObject(trans
, dataObjCollection
);
187 NS_ENSURE_SUCCESS(rv
, rv
);
189 dataObjCollection
->AddDataObject(dataObj
);
192 } // if dragging multiple items
194 nsCOMPtr
<nsITransferable
> trans
=
195 do_QueryElementAt(anArrayTransferables
, 0);
197 rv
= nsClipboard::CreateNativeDataObject(trans
,
198 getter_AddRefs(itemToDrag
), uri
);
199 NS_ENSURE_SUCCESS(rv
, rv
);
201 } // else dragging a single object
203 // Create a drag image if support is available
204 IDragSourceHelper
* pdsh
;
205 if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper
, nullptr,
206 CLSCTX_INPROC_SERVER
, IID_IDragSourceHelper
,
209 if (CreateDragImage(mSourceNode
, aRegion
, &sdi
)) {
210 if (FAILED(pdsh
->InitializeFromBitmap(&sdi
, itemToDrag
)))
211 DeleteObject(sdi
.hbmpDragImage
);
216 // Kick off the native drag session
217 return StartInvokingDragSession(itemToDrag
, aActionType
);
220 static HWND
GetSourceWindow(dom::Document
* aSourceDocument
) {
221 if (!aSourceDocument
) {
225 auto* pc
= aSourceDocument
->GetPresContext();
230 nsCOMPtr
<nsIWidget
> widget
= pc
->GetRootWidget();
235 return (HWND
)widget
->GetNativeData(NS_NATIVE_WINDOW
);
238 //-------------------------------------------------------------------------
239 nsresult
nsDragService::StartInvokingDragSession(IDataObject
* aDataObj
,
240 uint32_t aActionType
) {
241 // To do the drag we need to create an object that
242 // implements the IDataObject interface (for OLE)
243 RefPtr
<nsNativeDragSource
> nativeDragSrc
=
244 new nsNativeDragSource(mDataTransfer
);
246 // Now figure out what the native drag effect should be
248 DWORD effects
= DROPEFFECT_SCROLL
;
249 if (aActionType
& DRAGDROP_ACTION_COPY
) {
250 effects
|= DROPEFFECT_COPY
;
252 if (aActionType
& DRAGDROP_ACTION_MOVE
) {
253 effects
|= DROPEFFECT_MOVE
;
255 if (aActionType
& DRAGDROP_ACTION_LINK
) {
256 effects
|= DROPEFFECT_LINK
;
259 // XXX not sure why we bother to cache this, it can change during
261 mDragAction
= aActionType
;
262 mSentLocalDropEvent
= false;
268 RefPtr
<IDataObjectAsyncCapability
> pAsyncOp
;
269 // Offer to do an async drag
270 if (SUCCEEDED(aDataObj
->QueryInterface(IID_IDataObjectAsyncCapability
,
271 getter_AddRefs(pAsyncOp
)))) {
272 pAsyncOp
->SetAsyncMode(VARIANT_TRUE
);
274 MOZ_ASSERT_UNREACHABLE("When did our data object stop being async");
277 // Call the native D&D method
278 HRESULT res
= ::DoDragDrop(aDataObj
, nativeDragSrc
, effects
, &winDropRes
);
280 // In cases where the drop operation completed outside the application,
281 // update the source node's DataTransfer dropEffect value so it is up to date.
282 if (!mSentLocalDropEvent
) {
284 // Order is important, since multiple flags can be returned.
285 if (winDropRes
& DROPEFFECT_COPY
)
286 dropResult
= DRAGDROP_ACTION_COPY
;
287 else if (winDropRes
& DROPEFFECT_LINK
)
288 dropResult
= DRAGDROP_ACTION_LINK
;
289 else if (winDropRes
& DROPEFFECT_MOVE
)
290 dropResult
= DRAGDROP_ACTION_MOVE
;
292 dropResult
= DRAGDROP_ACTION_NONE
;
295 if (res
== DRAGDROP_S_DROP
) // Success
296 mDataTransfer
->SetDropEffectInt(dropResult
);
298 mDataTransfer
->SetDropEffectInt(DRAGDROP_ACTION_NONE
);
302 mUserCancelled
= nativeDragSrc
->UserCancelled();
304 // We're done dragging, get the cursor position and end the drag
305 // Use GetMessagePos to get the position of the mouse at the last message
306 // seen by the event loop. (Bug 489729)
307 // Note that we must convert this from device pixels back to Windows logical
308 // pixels (bug 818927).
309 DWORD pos
= ::GetMessagePos();
311 cpos
.x
= GET_X_LPARAM(pos
);
312 cpos
.y
= GET_Y_LPARAM(pos
);
313 if (auto wnd
= GetSourceWindow(mSourceDocument
)) {
314 // Convert from screen to client coordinates like nsWindow::InitEvent does.
315 ::ScreenToClient(wnd
, &cpos
);
317 SetDragEndPoint(LayoutDeviceIntPoint(cpos
.x
, cpos
.y
));
319 ModifierKeyState modifierKeyState
;
320 EndDragSession(true, modifierKeyState
.GetModifiers());
324 return DRAGDROP_S_DROP
== res
? NS_OK
: NS_ERROR_FAILURE
;
327 //-------------------------------------------------------------------------
328 // Make Sure we have the right kind of object
329 nsDataObjCollection
* nsDragService::GetDataObjCollection(
330 IDataObject
* aDataObj
) {
331 nsDataObjCollection
* dataObjCol
= nullptr;
333 nsIDataObjCollection
* dataObj
;
334 if (aDataObj
->QueryInterface(IID_IDataObjCollection
, (void**)&dataObj
) ==
336 dataObjCol
= static_cast<nsDataObjCollection
*>(aDataObj
);
344 //-------------------------------------------------------------------------
346 nsDragService::GetNumDropItems(uint32_t* aNumItems
) {
352 if (IsCollectionObject(mDataObject
)) {
353 nsDataObjCollection
* dataObjCol
= GetDataObjCollection(mDataObject
);
355 *aNumItems
= dataObjCol
->GetNumDataObjects();
357 // If the count cannot be determined just return 0.
358 // This can happen if we have collection data of type
359 // MULTI_MIME ("Mozilla/IDataObjectCollectionFormat") on the clipboard
360 // from another process but we can't obtain an IID_IDataObjCollection
361 // from this process.
365 // Next check if we have a file drop. Return the number of files in
366 // the file drop as the number of items we have, pretending like we
367 // actually have > 1 drag item.
369 SET_FORMATETC(fe2
, CF_HDROP
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
370 if (mDataObject
->QueryGetData(&fe2
) == S_OK
) {
372 if (mDataObject
->GetData(&fe2
, &stm
) == S_OK
) {
373 HDROP hdrop
= (HDROP
)GlobalLock(stm
.hGlobal
);
374 *aNumItems
= ::DragQueryFileW(hdrop
, 0xFFFFFFFF, nullptr, 0);
375 ::GlobalUnlock(stm
.hGlobal
);
376 ::ReleaseStgMedium(&stm
);
377 // Data may be provided later, so assume we have 1 item
378 if (*aNumItems
== 0) *aNumItems
= 1;
388 //-------------------------------------------------------------------------
390 nsDragService::GetData(nsITransferable
* aTransferable
, uint32_t anItem
) {
391 // This typcially happens on a drop, the target would be asking
392 // for it's transferable to be filled in
393 // Use a static clipboard utility method for this
394 if (!mDataObject
) return NS_ERROR_FAILURE
;
396 nsresult dataFound
= NS_ERROR_FAILURE
;
398 if (IsCollectionObject(mDataObject
)) {
399 // multiple items, use |anItem| as an index into our collection
400 nsDataObjCollection
* dataObjCol
= GetDataObjCollection(mDataObject
);
401 uint32_t cnt
= dataObjCol
->GetNumDataObjects();
403 IDataObject
* dataObj
= dataObjCol
->GetDataObjectAt(anItem
);
404 dataFound
= nsClipboard::GetDataFromDataObject(dataObj
, 0, nullptr,
407 NS_WARNING("Index out of range!");
409 // If they are asking for item "0", we can just get it...
411 dataFound
= nsClipboard::GetDataFromDataObject(mDataObject
, anItem
,
412 nullptr, aTransferable
);
414 // It better be a file drop, or else non-zero indexes are invalid!
416 SET_FORMATETC(fe2
, CF_HDROP
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
417 if (mDataObject
->QueryGetData(&fe2
) == S_OK
)
418 dataFound
= nsClipboard::GetDataFromDataObject(mDataObject
, anItem
,
419 nullptr, aTransferable
);
422 "Reqesting non-zero index, but clipboard data is not a "
429 //---------------------------------------------------------
431 nsDragService::SetIDataObject(IDataObject
* aDataObj
) {
432 // When the native drag starts the DragService gets
433 // the IDataObject that is being dragged
434 NS_IF_RELEASE(mDataObject
);
435 mDataObject
= aDataObj
;
436 NS_IF_ADDREF(mDataObject
);
441 //---------------------------------------------------------
442 void nsDragService::SetDroppedLocal() {
443 // Sent from the native drag handler, letting us know
444 // a drop occurred within the application vs. outside of it.
445 mSentLocalDropEvent
= true;
449 //-------------------------------------------------------------------------
451 nsDragService::IsDataFlavorSupported(const char* aDataFlavor
, bool* _retval
) {
452 if (!aDataFlavor
|| !mDataObject
|| !_retval
) return NS_ERROR_FAILURE
;
455 if (strcmp(aDataFlavor
, kTextMime
) == 0)
457 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
466 if (IsCollectionObject(mDataObject
)) {
467 // We know we have one of our special collection objects.
468 format
= nsClipboard::GetFormat(aDataFlavor
);
469 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1,
470 TYMED_HGLOBAL
| TYMED_FILE
| TYMED_GDI
);
472 // See if any one of the IDataObjects in the collection supports
474 nsDataObjCollection
* dataObjCol
= GetDataObjCollection(mDataObject
);
476 uint32_t cnt
= dataObjCol
->GetNumDataObjects();
477 for (uint32_t i
= 0; i
< cnt
; ++i
) {
478 IDataObject
* dataObj
= dataObjCol
->GetDataObjectAt(i
);
479 if (S_OK
== dataObj
->QueryGetData(&fe
)) *_retval
= true; // found it!
482 } // if special collection object
484 // Ok, so we have a single object. Check to see if has the correct
485 // data type. Since this can come from an outside app, we also
486 // need to see if we need to perform text->unicode conversion if
487 // the client asked for unicode and it wasn't available.
488 format
= nsClipboard::GetFormat(aDataFlavor
);
489 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1,
490 TYMED_HGLOBAL
| TYMED_FILE
| TYMED_GDI
);
491 if (mDataObject
->QueryGetData(&fe
) == S_OK
)
492 *_retval
= true; // found it!
494 // We haven't found the exact flavor the client asked for, but
495 // maybe we can still find it from something else that's on the
497 if (strcmp(aDataFlavor
, kUnicodeMime
) == 0) {
498 // client asked for unicode and it wasn't present, check if we
499 // have CF_TEXT. We'll handle the actual data substitution in
501 format
= nsClipboard::GetFormat(kTextMime
);
502 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1,
503 TYMED_HGLOBAL
| TYMED_FILE
| TYMED_GDI
);
504 if (mDataObject
->QueryGetData(&fe
) == S_OK
)
505 *_retval
= true; // found it!
506 } else if (strcmp(aDataFlavor
, kURLMime
) == 0) {
507 // client asked for a url and it wasn't present, but if we
508 // have a file, then we have a URL to give them (the path, or
509 // the internal URL if an InternetShortcut).
510 format
= nsClipboard::GetFormat(kFileMime
);
511 SET_FORMATETC(fe
, format
, 0, DVASPECT_CONTENT
, -1,
512 TYMED_HGLOBAL
| TYMED_FILE
| TYMED_GDI
);
513 if (mDataObject
->QueryGetData(&fe
) == S_OK
)
514 *_retval
= true; // found it!
523 // IsCollectionObject
525 // Determine if this is a single |IDataObject| or one of our private
526 // collection objects. We know the difference because our collection
527 // object will respond to supporting the private |MULTI_MIME| format.
529 bool nsDragService::IsCollectionObject(IDataObject
* inDataObj
) {
530 bool isCollection
= false;
532 // setup the format object to ask for the MULTI_MIME format. We only
533 // need to do this once
534 static UINT sFormat
= 0;
535 static FORMATETC sFE
;
537 sFormat
= nsClipboard::GetFormat(MULTI_MIME
);
538 SET_FORMATETC(sFE
, sFormat
, 0, DVASPECT_CONTENT
, -1, TYMED_HGLOBAL
);
541 // ask the object if it supports it. If yes, we have a collection
543 if (inDataObj
->QueryGetData(&sFE
) == S_OK
) isCollection
= true;
547 } // IsCollectionObject
552 // Override the default to make sure that we release the data object
553 // when the drag ends. It seems that OLE doesn't like to let apps quit
554 // w/out crashing when we're still holding onto their data
557 nsDragService::EndDragSession(bool aDoneDrag
, uint32_t aKeyModifiers
) {
558 // Bug 100180: If we've got mouse events captured, make sure we release it -
559 // that way, if we happen to call EndDragSession before diving into a nested
560 // event loop, we can still respond to mouse events.
561 if (::GetCapture()) {
565 nsBaseDragService::EndDragSession(aDoneDrag
, aKeyModifiers
);
566 NS_IF_RELEASE(mDataObject
);
572 nsDragService::UpdateDragImage(nsINode
* aImage
, int32_t aImageX
,
578 nsBaseDragService::UpdateDragImage(aImage
, aImageX
, aImageY
);
580 IDragSourceHelper
* pdsh
;
581 if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper
, nullptr,
582 CLSCTX_INPROC_SERVER
, IID_IDragSourceHelper
,
585 if (CreateDragImage(mSourceNode
, Nothing(), &sdi
)) {
586 nsNativeDragTarget::DragImageChanged();
587 if (FAILED(pdsh
->InitializeFromBitmap(&sdi
, mDataObject
)))
588 DeleteObject(sdi
.hbmpDragImage
);