Bug 1793629 - Implement attention indicator for the unified extensions button, r...
[gecko.git] / widget / windows / nsDragService.cpp
blobc0b41b75f38dccec2325241cc1d9c3940cc92a3d
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/. */
6 #include <ole2.h>
7 #include <oleidl.h>
8 #include <shlobj.h>
9 #include <shlwapi.h>
11 // shellapi.h is needed to build with WIN32_LEAN_AND_MEAN
12 #include <shellapi.h>
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"
28 #include "nsString.h"
29 #include "nsEscape.h"
30 #include "nsIScreenManager.h"
31 #include "nsToolkit.h"
32 #include "nsCRT.h"
33 #include "nsDirectoryServiceDefs.h"
34 #include "nsUnicharUtils.h"
35 #include "nsRect.h"
36 #include "nsMathUtils.h"
37 #include "WinUtils.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,
65 SHDRAGIMAGE* psdi) {
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;
74 nsPresContext* pc;
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)) {
90 return false;
93 RefPtr<DrawTarget> dt = Factory::CreateDrawTargetForData(
94 BackendType::CAIRO, map.mData, dataSurface->GetSize(), map.mStride,
95 dataSurface->GetFormat());
96 if (!dt) {
97 dataSurface->Unmap();
98 return false;
101 dt->DrawSurface(
102 surface,
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));
106 dt->Flush();
108 BITMAPV5HEADER bmih;
109 memset((void*)&bmih, 0, sizeof(BITMAPV5HEADER));
110 bmih.bV5Size = sizeof(BITMAPV5HEADER);
111 bmih.bV5Width = bmWidth;
112 bmih.bV5Height = -(int32_t)bmHeight; // flip vertical
113 bmih.bV5Planes = 1;
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;
123 if (hdcSrc) {
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();
141 DeleteDC(hdcSrc);
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);
157 if (doc) {
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);
180 if (trans) {
181 RefPtr<IDataObject> dataObj;
182 rv = nsClipboard::CreateNativeDataObject(trans, getter_AddRefs(dataObj),
183 uri);
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
193 else {
194 nsCOMPtr<nsITransferable> trans =
195 do_QueryElementAt(anArrayTransferables, 0);
196 if (trans) {
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,
207 (void**)&pdsh))) {
208 SHDRAGIMAGE sdi;
209 if (CreateDragImage(mSourceNode, aRegion, &sdi)) {
210 if (FAILED(pdsh->InitializeFromBitmap(&sdi, itemToDrag)))
211 DeleteObject(sdi.hbmpDragImage);
213 pdsh->Release();
216 // Kick off the native drag session
217 return StartInvokingDragSession(itemToDrag, aActionType);
220 static HWND GetSourceWindow(dom::Document* aSourceDocument) {
221 if (!aSourceDocument) {
222 return nullptr;
225 auto* pc = aSourceDocument->GetPresContext();
226 if (!pc) {
227 return nullptr;
230 nsCOMPtr<nsIWidget> widget = pc->GetRootWidget();
231 if (!widget) {
232 return nullptr;
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
247 DWORD winDropRes;
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
260 // the drag
261 mDragAction = aActionType;
262 mSentLocalDropEvent = false;
264 // Start dragging
265 StartDragSession();
266 OpenDragPopup();
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);
273 } else {
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) {
283 uint32_t dropResult;
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;
291 else
292 dropResult = DRAGDROP_ACTION_NONE;
294 if (mDataTransfer) {
295 if (res == DRAGDROP_S_DROP) // Success
296 mDataTransfer->SetDropEffectInt(dropResult);
297 else
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();
310 POINT cpos;
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());
322 mDoingDrag = false;
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;
332 if (aDataObj) {
333 nsIDataObjCollection* dataObj;
334 if (aDataObj->QueryInterface(IID_IDataObjCollection, (void**)&dataObj) ==
335 S_OK) {
336 dataObjCol = static_cast<nsDataObjCollection*>(aDataObj);
337 dataObj->Release();
341 return dataObjCol;
344 //-------------------------------------------------------------------------
345 NS_IMETHODIMP
346 nsDragService::GetNumDropItems(uint32_t* aNumItems) {
347 if (!mDataObject) {
348 *aNumItems = 0;
349 return NS_OK;
352 if (IsCollectionObject(mDataObject)) {
353 nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
354 if (dataObjCol) {
355 *aNumItems = dataObjCol->GetNumDataObjects();
356 } else {
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.
362 *aNumItems = 0;
364 } else {
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.
368 FORMATETC fe2;
369 SET_FORMATETC(fe2, CF_HDROP, 0, DVASPECT_CONTENT, -1, TYMED_HGLOBAL);
370 if (mDataObject->QueryGetData(&fe2) == S_OK) {
371 STGMEDIUM stm;
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;
379 } else
380 *aNumItems = 1;
381 } else
382 *aNumItems = 1;
385 return NS_OK;
388 //-------------------------------------------------------------------------
389 NS_IMETHODIMP
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();
402 if (anItem < cnt) {
403 IDataObject* dataObj = dataObjCol->GetDataObjectAt(anItem);
404 dataFound = nsClipboard::GetDataFromDataObject(dataObj, 0, nullptr,
405 aTransferable);
406 } else
407 NS_WARNING("Index out of range!");
408 } else {
409 // If they are asking for item "0", we can just get it...
410 if (anItem == 0) {
411 dataFound = nsClipboard::GetDataFromDataObject(mDataObject, anItem,
412 nullptr, aTransferable);
413 } else {
414 // It better be a file drop, or else non-zero indexes are invalid!
415 FORMATETC fe2;
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);
420 else
421 NS_WARNING(
422 "Reqesting non-zero index, but clipboard data is not a "
423 "collection!");
426 return dataFound;
429 //---------------------------------------------------------
430 NS_IMETHODIMP
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);
438 return NS_OK;
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;
446 return;
449 //-------------------------------------------------------------------------
450 NS_IMETHODIMP
451 nsDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) {
452 if (!aDataFlavor || !mDataObject || !_retval) return NS_ERROR_FAILURE;
454 #ifdef DEBUG
455 if (strcmp(aDataFlavor, kTextMime) == 0)
456 NS_WARNING(
457 "DO NOT USE THE text/plain DATA FLAVOR ANY MORE. USE text/unicode "
458 "INSTEAD");
459 #endif
461 *_retval = false;
463 FORMATETC fe;
464 UINT format = 0;
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
473 // this data type
474 nsDataObjCollection* dataObjCol = GetDataObjCollection(mDataObject);
475 if (dataObjCol) {
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
483 else {
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!
493 else {
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
496 // clipboard
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
500 // the data object.
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!
516 } // else try again
519 return NS_OK;
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;
536 if (!sFormat) {
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
542 // object
543 if (inDataObj->QueryGetData(&sFE) == S_OK) isCollection = true;
545 return isCollection;
547 } // IsCollectionObject
550 // EndDragSession
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
556 NS_IMETHODIMP
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()) {
562 ::ReleaseCapture();
565 nsBaseDragService::EndDragSession(aDoneDrag, aKeyModifiers);
566 NS_IF_RELEASE(mDataObject);
568 return NS_OK;
571 NS_IMETHODIMP
572 nsDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
573 int32_t aImageY) {
574 if (!mDataObject) {
575 return NS_OK;
578 nsBaseDragService::UpdateDragImage(aImage, aImageX, aImageY);
580 IDragSourceHelper* pdsh;
581 if (SUCCEEDED(CoCreateInstance(CLSID_DragDropHelper, nullptr,
582 CLSCTX_INPROC_SERVER, IID_IDragSourceHelper,
583 (void**)&pdsh))) {
584 SHDRAGIMAGE sdi;
585 if (CreateDragImage(mSourceNode, Nothing(), &sdi)) {
586 nsNativeDragTarget::DragImageChanged();
587 if (FAILED(pdsh->InitializeFromBitmap(&sdi, mDataObject)))
588 DeleteObject(sdi.hbmpDragImage);
590 pdsh->Release();
593 return NS_OK;