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 "nsBaseDragService.h"
7 #include "nsITransferable.h"
9 #include "nsIServiceManager.h"
10 #include "nsITransferable.h"
11 #include "nsISupportsArray.h"
14 #include "nsISupportsPrimitives.h"
16 #include "nsIInterfaceRequestorUtils.h"
18 #include "nsIDocument.h"
19 #include "nsIContent.h"
20 #include "nsIPresShell.h"
21 #include "nsViewManager.h"
22 #include "nsIDOMNode.h"
23 #include "nsIDOMDragEvent.h"
24 #include "nsISelection.h"
25 #include "nsISelectionPrivate.h"
26 #include "nsPresContext.h"
27 #include "nsIDOMDataTransfer.h"
28 #include "nsIImageLoadingContent.h"
29 #include "imgIContainer.h"
30 #include "imgIRequest.h"
31 #include "ImageRegion.h"
33 #include "nsXULPopupManager.h"
34 #include "nsMenuPopupFrame.h"
35 #include "SVGImageContext.h"
36 #include "mozilla/MouseEvents.h"
37 #include "mozilla/Preferences.h"
38 #include "mozilla/gfx/2D.h"
40 #include "gfxContext.h"
41 #include "gfxPlatform.h"
44 using namespace mozilla
;
45 using namespace mozilla::dom
;
46 using namespace mozilla::gfx
;
47 using namespace mozilla::image
;
49 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
51 nsBaseDragService::nsBaseDragService()
52 : mCanDrop(false), mOnlyChromeDrop(false), mDoingDrag(false),
53 mHasImage(false), mUserCancelled(false),
54 mDragAction(DRAGDROP_ACTION_NONE
), mTargetSize(0,0),
55 mImageX(0), mImageY(0), mScreenX(-1), mScreenY(-1), mSuppressLevel(0),
56 mInputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE
)
60 nsBaseDragService::~nsBaseDragService()
64 NS_IMPL_ISUPPORTS(nsBaseDragService
, nsIDragService
, nsIDragSession
)
66 //---------------------------------------------------------
68 nsBaseDragService::SetCanDrop(bool aCanDrop
)
74 //---------------------------------------------------------
76 nsBaseDragService::GetCanDrop(bool * aCanDrop
)
81 //---------------------------------------------------------
83 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome
)
85 mOnlyChromeDrop
= aOnlyChrome
;
89 //---------------------------------------------------------
91 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome
)
93 *aOnlyChrome
= mOnlyChromeDrop
;
97 //---------------------------------------------------------
99 nsBaseDragService::SetDragAction(uint32_t anAction
)
101 mDragAction
= anAction
;
105 //---------------------------------------------------------
107 nsBaseDragService::GetDragAction(uint32_t * anAction
)
109 *anAction
= mDragAction
;
113 //---------------------------------------------------------
115 nsBaseDragService::SetTargetSize(nsSize aDragTargetSize
)
117 mTargetSize
= aDragTargetSize
;
121 //---------------------------------------------------------
123 nsBaseDragService::GetTargetSize(nsSize
* aDragTargetSize
)
125 *aDragTargetSize
= mTargetSize
;
129 //-------------------------------------------------------------------------
132 nsBaseDragService::GetNumDropItems(uint32_t * aNumItems
)
135 return NS_ERROR_FAILURE
;
142 // Returns the DOM document where the drag was initiated. This will be
143 // nullptr if the drag began outside of our application.
146 nsBaseDragService::GetSourceDocument(nsIDOMDocument
** aSourceDocument
)
148 *aSourceDocument
= mSourceDocument
.get();
149 NS_IF_ADDREF(*aSourceDocument
);
157 // Returns the DOM node where the drag was initiated. This will be
158 // nullptr if the drag began outside of our application.
161 nsBaseDragService::GetSourceNode(nsIDOMNode
** aSourceNode
)
163 *aSourceNode
= mSourceNode
.get();
164 NS_IF_ADDREF(*aSourceNode
);
170 //-------------------------------------------------------------------------
173 nsBaseDragService::GetData(nsITransferable
* aTransferable
,
176 return NS_ERROR_FAILURE
;
179 //-------------------------------------------------------------------------
181 nsBaseDragService::IsDataFlavorSupported(const char *aDataFlavor
,
184 return NS_ERROR_FAILURE
;
188 nsBaseDragService::GetDataTransfer(nsIDOMDataTransfer
** aDataTransfer
)
190 *aDataTransfer
= mDataTransfer
;
191 NS_IF_ADDREF(*aDataTransfer
);
196 nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer
* aDataTransfer
)
198 mDataTransfer
= aDataTransfer
;
202 //-------------------------------------------------------------------------
204 nsBaseDragService::InvokeDragSession(nsIDOMNode
*aDOMNode
,
205 nsISupportsArray
* aTransferableArray
,
206 nsIScriptableRegion
* aDragRgn
,
207 uint32_t aActionType
)
209 NS_ENSURE_TRUE(aDOMNode
, NS_ERROR_INVALID_ARG
);
210 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
212 // stash the document of the dom node
213 aDOMNode
->GetOwnerDocument(getter_AddRefs(mSourceDocument
));
214 mSourceNode
= aDOMNode
;
215 mEndDragPoint
= nsIntPoint(0, 0);
217 // When the mouse goes down, the selection code starts a mouse
218 // capture. However, this gets in the way of determining drag
219 // feedback for things like trees because the event coordinates
220 // are in the wrong coord system, so turn off mouse capture.
221 nsIPresShell::ClearMouseCapture(nullptr);
227 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode
* aDOMNode
,
228 nsISupportsArray
* aTransferableArray
,
229 nsIScriptableRegion
* aRegion
,
230 uint32_t aActionType
,
232 int32_t aImageX
, int32_t aImageY
,
233 nsIDOMDragEvent
* aDragEvent
,
234 nsIDOMDataTransfer
* aDataTransfer
)
236 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
237 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
238 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
240 mDataTransfer
= aDataTransfer
;
241 mSelection
= nullptr;
243 mDragPopup
= nullptr;
248 aDragEvent
->GetScreenX(&mScreenX
);
249 aDragEvent
->GetScreenY(&mScreenY
);
250 aDragEvent
->GetMozInputSource(&mInputSource
);
252 return InvokeDragSession(aDOMNode
, aTransferableArray
, aRegion
, aActionType
);
256 nsBaseDragService::InvokeDragSessionWithSelection(nsISelection
* aSelection
,
257 nsISupportsArray
* aTransferableArray
,
258 uint32_t aActionType
,
259 nsIDOMDragEvent
* aDragEvent
,
260 nsIDOMDataTransfer
* aDataTransfer
)
262 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
263 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
264 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
266 mDataTransfer
= aDataTransfer
;
267 mSelection
= aSelection
;
269 mDragPopup
= nullptr;
274 aDragEvent
->GetScreenX(&mScreenX
);
275 aDragEvent
->GetScreenY(&mScreenY
);
276 aDragEvent
->GetMozInputSource(&mInputSource
);
278 // just get the focused node from the selection
279 // XXXndeakin this should actually be the deepest node that contains both
280 // endpoints of the selection
281 nsCOMPtr
<nsIDOMNode
> node
;
282 aSelection
->GetFocusNode(getter_AddRefs(node
));
284 return InvokeDragSession(node
, aTransferableArray
, nullptr, aActionType
);
287 //-------------------------------------------------------------------------
289 nsBaseDragService::GetCurrentSession(nsIDragSession
** aSession
)
292 return NS_ERROR_INVALID_ARG
;
294 // "this" also implements a drag session, so say we are one but only
295 // if there is currently a drag going on.
296 if (!mSuppressLevel
&& mDoingDrag
) {
298 NS_ADDREF(*aSession
); // addRef because we're a "getter"
306 //-------------------------------------------------------------------------
308 nsBaseDragService::StartDragSession()
311 return NS_ERROR_FAILURE
;
314 // By default dispatch drop also to content.
315 mOnlyChromeDrop
= false;
321 nsBaseDragService::OpenDragPopup()
324 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
326 pm
->ShowPopupAtScreen(mDragPopup
, mScreenX
- mImageX
, mScreenY
- mImageY
, false, nullptr);
331 //-------------------------------------------------------------------------
333 nsBaseDragService::EndDragSession(bool aDoneDrag
)
336 return NS_ERROR_FAILURE
;
339 if (aDoneDrag
&& !mSuppressLevel
)
340 FireDragEventAtSource(NS_DRAGDROP_END
);
343 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
345 pm
->HidePopup(mDragPopup
, false, true, false, false);
351 // release the source we've been holding on to.
352 mSourceDocument
= nullptr;
353 mSourceNode
= nullptr;
354 mSelection
= nullptr;
355 mDataTransfer
= nullptr;
357 mUserCancelled
= false;
358 mDragPopup
= nullptr;
364 mInputSource
= nsIDOMMouseEvent::MOZ_SOURCE_MOUSE
;
370 nsBaseDragService::FireDragEventAtSource(uint32_t aMsg
)
372 if (mSourceNode
&& !mSuppressLevel
) {
373 nsCOMPtr
<nsIDocument
> doc
= do_QueryInterface(mSourceDocument
);
375 nsCOMPtr
<nsIPresShell
> presShell
= doc
->GetShell();
377 nsEventStatus status
= nsEventStatus_eIgnore
;
378 WidgetDragEvent
event(true, aMsg
, nullptr);
379 event
.inputSource
= mInputSource
;
380 if (aMsg
== NS_DRAGDROP_END
) {
381 event
.refPoint
.x
= mEndDragPoint
.x
;
382 event
.refPoint
.y
= mEndDragPoint
.y
;
383 event
.userCancelled
= mUserCancelled
;
386 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mSourceNode
);
387 return presShell
->HandleDOMEventWithTarget(content
, &event
, &status
);
395 /* This is used by Windows and Mac to update the position of a popup being
396 * used as a drag image during the drag. This isn't used on GTK as it manages
397 * the drag popup itself.
400 nsBaseDragService::DragMoved(int32_t aX
, int32_t aY
)
403 nsIFrame
* frame
= mDragPopup
->GetPrimaryFrame();
404 if (frame
&& frame
->GetType() == nsGkAtoms::menuPopupFrame
) {
405 (static_cast<nsMenuPopupFrame
*>(frame
))->MoveTo(aX
- mImageX
, aY
- mImageY
, true);
413 GetPresShellForContent(nsIDOMNode
* aDOMNode
)
415 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aDOMNode
);
419 nsCOMPtr
<nsIDocument
> document
= content
->GetCurrentDoc();
421 document
->FlushPendingNotifications(Flush_Display
);
423 return document
->GetShell();
430 nsBaseDragService::DrawDrag(nsIDOMNode
* aDOMNode
,
431 nsIScriptableRegion
* aRegion
,
432 int32_t aScreenX
, int32_t aScreenY
,
433 nsIntRect
* aScreenDragRect
,
434 RefPtr
<SourceSurface
>* aSurface
,
435 nsPresContext
** aPresContext
)
438 *aPresContext
= nullptr;
440 // use a default size, in case of an error.
441 aScreenDragRect
->x
= aScreenX
- mImageX
;
442 aScreenDragRect
->y
= aScreenY
- mImageY
;
443 aScreenDragRect
->width
= 1;
444 aScreenDragRect
->height
= 1;
446 // if a drag image was specified, use that, otherwise, use the source node
447 nsCOMPtr
<nsIDOMNode
> dragNode
= mImage
? mImage
.get() : aDOMNode
;
449 // get the presshell for the node being dragged. If the drag image is not in
450 // a document or has no frame, get the presshell from the source drag node
451 nsIPresShell
* presShell
= GetPresShellForContent(dragNode
);
452 if (!presShell
&& mImage
)
453 presShell
= GetPresShellForContent(aDOMNode
);
455 return NS_ERROR_FAILURE
;
457 *aPresContext
= presShell
->GetPresContext();
459 // convert mouse position to dev pixels of the prescontext
460 int32_t sx
= aScreenX
, sy
= aScreenY
;
461 ConvertToUnscaledDevPixels(*aPresContext
, &sx
, &sy
);
463 aScreenDragRect
->x
= sx
- mImageX
;
464 aScreenDragRect
->y
= sy
- mImageY
;
466 // check if drag images are disabled
467 bool enableDragImages
= Preferences::GetBool(DRAGIMAGES_PREF
, true);
469 // didn't want an image, so just set the screen rectangle to the frame size
470 if (!enableDragImages
|| !mHasImage
) {
471 // if a region was specified, set the screen rectangle to the area that
472 // the region occupies
474 // the region's coordinates are relative to the root frame
475 nsIFrame
* rootFrame
= presShell
->GetRootFrame();
476 if (rootFrame
&& *aPresContext
) {
478 aRegion
->GetBoundingBox(&dragRect
.x
, &dragRect
.y
, &dragRect
.width
, &dragRect
.height
);
479 dragRect
= dragRect
.ToAppUnits(nsPresContext::AppUnitsPerCSSPixel()).
480 ToOutsidePixels((*aPresContext
)->AppUnitsPerDevPixel());
482 nsIntRect screenRect
= rootFrame
->GetScreenRectExternal();
483 aScreenDragRect
->SetRect(screenRect
.x
+ dragRect
.x
, screenRect
.y
+ dragRect
.y
,
484 dragRect
.width
, dragRect
.height
);
488 // otherwise, there was no region so just set the rectangle to
489 // the size of the primary frame of the content.
490 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
491 nsIFrame
* frame
= content
->GetPrimaryFrame();
493 nsIntRect screenRect
= frame
->GetScreenRectExternal();
494 aScreenDragRect
->SetRect(screenRect
.x
, screenRect
.y
,
495 screenRect
.width
, screenRect
.height
);
502 // draw the image for selections
504 nsIntPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
505 *aSurface
= presShell
->RenderSelection(mSelection
, pnt
, aScreenDragRect
);
509 // if a custom image was specified, check if it is an image node and draw
510 // using the source rather than the displayed image. But if mImage isn't
511 // an image or canvas, fall through to RenderNode below.
513 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
514 HTMLCanvasElement
*canvas
= HTMLCanvasElement::FromContentOrNull(content
);
516 return DrawDragForImage(*aPresContext
, nullptr, canvas
, sx
, sy
,
517 aScreenDragRect
, aSurface
);
520 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(dragNode
);
521 // for image nodes, create the drag image from the actual image data
523 return DrawDragForImage(*aPresContext
, imageLoader
, nullptr, sx
, sy
,
524 aScreenDragRect
, aSurface
);
527 // If the image is a popup, use that as the image. This allows custom drag
528 // images that can change during the drag, but means that any platform
529 // default image handling won't occur.
530 // XXXndeakin this should be chrome-only
532 nsIFrame
* frame
= content
->GetPrimaryFrame();
533 if (frame
&& frame
->GetType() == nsGkAtoms::menuPopupFrame
) {
534 mDragPopup
= content
;
539 // otherwise, just draw the node
540 nsIntRegion clipRegion
;
542 aRegion
->GetRegion(&clipRegion
);
545 nsIntPoint
pnt(aScreenDragRect
->x
, aScreenDragRect
->y
);
546 *aSurface
= presShell
->RenderNode(dragNode
, aRegion
? &clipRegion
: nullptr,
547 pnt
, aScreenDragRect
);
550 // if an image was specified, reposition the drag rectangle to
551 // the supplied offset in mImageX and mImageY.
553 aScreenDragRect
->x
= sx
- mImageX
;
554 aScreenDragRect
->y
= sy
- mImageY
;
561 nsBaseDragService::DrawDragForImage(nsPresContext
* aPresContext
,
562 nsIImageLoadingContent
* aImageLoader
,
563 HTMLCanvasElement
* aCanvas
,
564 int32_t aScreenX
, int32_t aScreenY
,
565 nsIntRect
* aScreenDragRect
,
566 RefPtr
<SourceSurface
>* aSurface
)
568 nsCOMPtr
<imgIContainer
> imgContainer
;
570 nsCOMPtr
<imgIRequest
> imgRequest
;
571 nsresult rv
= aImageLoader
->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST
,
572 getter_AddRefs(imgRequest
));
573 NS_ENSURE_SUCCESS(rv
, rv
);
575 return NS_ERROR_NOT_AVAILABLE
;
577 rv
= imgRequest
->GetImage(getter_AddRefs(imgContainer
));
578 NS_ENSURE_SUCCESS(rv
, rv
);
580 return NS_ERROR_NOT_AVAILABLE
;
582 // use the size of the image as the size of the drag image
583 imgContainer
->GetWidth(&aScreenDragRect
->width
);
584 imgContainer
->GetHeight(&aScreenDragRect
->height
);
587 NS_ASSERTION(aCanvas
, "both image and canvas are null");
588 nsIntSize sz
= aCanvas
->GetSize();
589 aScreenDragRect
->width
= sz
.width
;
590 aScreenDragRect
->height
= sz
.height
;
593 nsIntSize srcSize
= aScreenDragRect
->Size();
594 nsIntSize destSize
= srcSize
;
596 if (destSize
.width
== 0 || destSize
.height
== 0)
597 return NS_ERROR_FAILURE
;
599 // if the image is larger than half the screen size, scale it down. This
600 // scaling algorithm is the same as is used in nsPresShell::PaintRangePaintInfo
601 nsDeviceContext
* deviceContext
= aPresContext
->DeviceContext();
603 deviceContext
->GetClientRect(maxSize
);
604 nscoord maxWidth
= aPresContext
->AppUnitsToDevPixels(maxSize
.width
>> 1);
605 nscoord maxHeight
= aPresContext
->AppUnitsToDevPixels(maxSize
.height
>> 1);
606 if (destSize
.width
> maxWidth
|| destSize
.height
> maxHeight
) {
608 if (destSize
.width
> maxWidth
)
609 scale
= std::min(scale
, float(maxWidth
) / destSize
.width
);
610 if (destSize
.height
> maxHeight
)
611 scale
= std::min(scale
, float(maxHeight
) / destSize
.height
);
613 destSize
.width
= NSToIntFloor(float(destSize
.width
) * scale
);
614 destSize
.height
= NSToIntFloor(float(destSize
.height
) * scale
);
616 aScreenDragRect
->x
= NSToIntFloor(aScreenX
- float(mImageX
) * scale
);
617 aScreenDragRect
->y
= NSToIntFloor(aScreenY
- float(mImageY
) * scale
);
618 aScreenDragRect
->width
= destSize
.width
;
619 aScreenDragRect
->height
= destSize
.height
;
622 nsresult result
= NS_OK
;
624 RefPtr
<DrawTarget
> dt
=
625 gfxPlatform::GetPlatform()->
626 CreateOffscreenContentDrawTarget(destSize
.ToIntSize(),
627 SurfaceFormat::B8G8R8A8
);
629 return NS_ERROR_FAILURE
;
631 nsRefPtr
<gfxContext
> ctx
= new gfxContext(dt
);
633 return NS_ERROR_FAILURE
;
635 imgContainer
->Draw(ctx
, destSize
, ImageRegion::Create(destSize
),
636 imgIContainer::FRAME_CURRENT
,
637 GraphicsFilter::FILTER_GOOD
, Nothing(),
638 imgIContainer::FLAG_SYNC_DECODE
);
639 *aSurface
= dt
->Snapshot();
641 *aSurface
= aCanvas
->GetSurfaceSnapshot();
648 nsBaseDragService::ConvertToUnscaledDevPixels(nsPresContext
* aPresContext
,
649 int32_t* aScreenX
, int32_t* aScreenY
)
651 int32_t adj
= aPresContext
->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
652 *aScreenX
= nsPresContext::CSSPixelsToAppUnits(*aScreenX
) / adj
;
653 *aScreenY
= nsPresContext::CSSPixelsToAppUnits(*aScreenY
) / adj
;
657 nsBaseDragService::Suppress()
659 EndDragSession(false);
665 nsBaseDragService::Unsuppress()