Bumping manifests a=b2g-bump
[gecko.git] / widget / nsBaseDragService.cpp
blob4097f4944db51b01659b698c8d074cd65ae405cb
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"
12 #include "nsSize.h"
13 #include "nsXPCOM.h"
14 #include "nsISupportsPrimitives.h"
15 #include "nsCOMPtr.h"
16 #include "nsIInterfaceRequestorUtils.h"
17 #include "nsIFrame.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"
32 #include "nsRegion.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"
42 #include <algorithm>
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 //---------------------------------------------------------
67 NS_IMETHODIMP
68 nsBaseDragService::SetCanDrop(bool aCanDrop)
70 mCanDrop = aCanDrop;
71 return NS_OK;
74 //---------------------------------------------------------
75 NS_IMETHODIMP
76 nsBaseDragService::GetCanDrop(bool * aCanDrop)
78 *aCanDrop = mCanDrop;
79 return NS_OK;
81 //---------------------------------------------------------
82 NS_IMETHODIMP
83 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome)
85 mOnlyChromeDrop = aOnlyChrome;
86 return NS_OK;
89 //---------------------------------------------------------
90 NS_IMETHODIMP
91 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome)
93 *aOnlyChrome = mOnlyChromeDrop;
94 return NS_OK;
97 //---------------------------------------------------------
98 NS_IMETHODIMP
99 nsBaseDragService::SetDragAction(uint32_t anAction)
101 mDragAction = anAction;
102 return NS_OK;
105 //---------------------------------------------------------
106 NS_IMETHODIMP
107 nsBaseDragService::GetDragAction(uint32_t * anAction)
109 *anAction = mDragAction;
110 return NS_OK;
113 //---------------------------------------------------------
114 NS_IMETHODIMP
115 nsBaseDragService::SetTargetSize(nsSize aDragTargetSize)
117 mTargetSize = aDragTargetSize;
118 return NS_OK;
121 //---------------------------------------------------------
122 NS_IMETHODIMP
123 nsBaseDragService::GetTargetSize(nsSize * aDragTargetSize)
125 *aDragTargetSize = mTargetSize;
126 return NS_OK;
129 //-------------------------------------------------------------------------
131 NS_IMETHODIMP
132 nsBaseDragService::GetNumDropItems(uint32_t * aNumItems)
134 *aNumItems = 0;
135 return NS_ERROR_FAILURE;
140 // GetSourceDocument
142 // Returns the DOM document where the drag was initiated. This will be
143 // nullptr if the drag began outside of our application.
145 NS_IMETHODIMP
146 nsBaseDragService::GetSourceDocument(nsIDOMDocument** aSourceDocument)
148 *aSourceDocument = mSourceDocument.get();
149 NS_IF_ADDREF(*aSourceDocument);
151 return NS_OK;
155 // GetSourceNode
157 // Returns the DOM node where the drag was initiated. This will be
158 // nullptr if the drag began outside of our application.
160 NS_IMETHODIMP
161 nsBaseDragService::GetSourceNode(nsIDOMNode** aSourceNode)
163 *aSourceNode = mSourceNode.get();
164 NS_IF_ADDREF(*aSourceNode);
166 return NS_OK;
170 //-------------------------------------------------------------------------
172 NS_IMETHODIMP
173 nsBaseDragService::GetData(nsITransferable * aTransferable,
174 uint32_t aItemIndex)
176 return NS_ERROR_FAILURE;
179 //-------------------------------------------------------------------------
180 NS_IMETHODIMP
181 nsBaseDragService::IsDataFlavorSupported(const char *aDataFlavor,
182 bool *_retval)
184 return NS_ERROR_FAILURE;
187 NS_IMETHODIMP
188 nsBaseDragService::GetDataTransfer(nsIDOMDataTransfer** aDataTransfer)
190 *aDataTransfer = mDataTransfer;
191 NS_IF_ADDREF(*aDataTransfer);
192 return NS_OK;
195 NS_IMETHODIMP
196 nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer* aDataTransfer)
198 mDataTransfer = aDataTransfer;
199 return NS_OK;
202 //-------------------------------------------------------------------------
203 NS_IMETHODIMP
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);
223 return NS_OK;
226 NS_IMETHODIMP
227 nsBaseDragService::InvokeDragSessionWithImage(nsIDOMNode* aDOMNode,
228 nsISupportsArray* aTransferableArray,
229 nsIScriptableRegion* aRegion,
230 uint32_t aActionType,
231 nsIDOMNode* aImage,
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;
242 mHasImage = true;
243 mDragPopup = nullptr;
244 mImage = aImage;
245 mImageX = aImageX;
246 mImageY = aImageY;
248 aDragEvent->GetScreenX(&mScreenX);
249 aDragEvent->GetScreenY(&mScreenY);
250 aDragEvent->GetMozInputSource(&mInputSource);
252 return InvokeDragSession(aDOMNode, aTransferableArray, aRegion, aActionType);
255 NS_IMETHODIMP
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;
268 mHasImage = true;
269 mDragPopup = nullptr;
270 mImage = nullptr;
271 mImageX = 0;
272 mImageY = 0;
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 //-------------------------------------------------------------------------
288 NS_IMETHODIMP
289 nsBaseDragService::GetCurrentSession(nsIDragSession ** aSession)
291 if (!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) {
297 *aSession = this;
298 NS_ADDREF(*aSession); // addRef because we're a "getter"
300 else
301 *aSession = nullptr;
303 return NS_OK;
306 //-------------------------------------------------------------------------
307 NS_IMETHODIMP
308 nsBaseDragService::StartDragSession()
310 if (mDoingDrag) {
311 return NS_ERROR_FAILURE;
313 mDoingDrag = true;
314 // By default dispatch drop also to content.
315 mOnlyChromeDrop = false;
317 return NS_OK;
320 void
321 nsBaseDragService::OpenDragPopup()
323 if (mDragPopup) {
324 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
325 if (pm) {
326 pm->ShowPopupAtScreen(mDragPopup, mScreenX - mImageX, mScreenY - mImageY, false, nullptr);
331 //-------------------------------------------------------------------------
332 NS_IMETHODIMP
333 nsBaseDragService::EndDragSession(bool aDoneDrag)
335 if (!mDoingDrag) {
336 return NS_ERROR_FAILURE;
339 if (aDoneDrag && !mSuppressLevel)
340 FireDragEventAtSource(NS_DRAGDROP_END);
342 if (mDragPopup) {
343 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
344 if (pm) {
345 pm->HidePopup(mDragPopup, false, true, false, false);
349 mDoingDrag = false;
351 // release the source we've been holding on to.
352 mSourceDocument = nullptr;
353 mSourceNode = nullptr;
354 mSelection = nullptr;
355 mDataTransfer = nullptr;
356 mHasImage = false;
357 mUserCancelled = false;
358 mDragPopup = nullptr;
359 mImage = nullptr;
360 mImageX = 0;
361 mImageY = 0;
362 mScreenX = -1;
363 mScreenY = -1;
364 mInputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
366 return NS_OK;
369 NS_IMETHODIMP
370 nsBaseDragService::FireDragEventAtSource(uint32_t aMsg)
372 if (mSourceNode && !mSuppressLevel) {
373 nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
374 if (doc) {
375 nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
376 if (presShell) {
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);
392 return NS_OK;
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.
399 NS_IMETHODIMP
400 nsBaseDragService::DragMoved(int32_t aX, int32_t aY)
402 if (mDragPopup) {
403 nsIFrame* frame = mDragPopup->GetPrimaryFrame();
404 if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
405 (static_cast<nsMenuPopupFrame *>(frame))->MoveTo(aX - mImageX, aY - mImageY, true);
409 return NS_OK;
412 static nsIPresShell*
413 GetPresShellForContent(nsIDOMNode* aDOMNode)
415 nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
416 if (!content)
417 return nullptr;
419 nsCOMPtr<nsIDocument> document = content->GetCurrentDoc();
420 if (document) {
421 document->FlushPendingNotifications(Flush_Display);
423 return document->GetShell();
426 return nullptr;
429 nsresult
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)
437 *aSurface = nullptr;
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);
454 if (!presShell)
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
473 if (aRegion) {
474 // the region's coordinates are relative to the root frame
475 nsIFrame* rootFrame = presShell->GetRootFrame();
476 if (rootFrame && *aPresContext) {
477 nsIntRect dragRect;
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);
487 else {
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();
492 if (frame) {
493 nsIntRect screenRect = frame->GetScreenRectExternal();
494 aScreenDragRect->SetRect(screenRect.x, screenRect.y,
495 screenRect.width, screenRect.height);
499 return NS_OK;
502 // draw the image for selections
503 if (mSelection) {
504 nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
505 *aSurface = presShell->RenderSelection(mSelection, pnt, aScreenDragRect);
506 return NS_OK;
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.
512 if (mImage) {
513 nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
514 HTMLCanvasElement *canvas = HTMLCanvasElement::FromContentOrNull(content);
515 if (canvas) {
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
522 if (imageLoader) {
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;
538 if (!mDragPopup) {
539 // otherwise, just draw the node
540 nsIntRegion clipRegion;
541 if (aRegion) {
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.
552 if (mImage) {
553 aScreenDragRect->x = sx - mImageX;
554 aScreenDragRect->y = sy - mImageY;
557 return NS_OK;
560 nsresult
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;
569 if (aImageLoader) {
570 nsCOMPtr<imgIRequest> imgRequest;
571 nsresult rv = aImageLoader->GetRequest(nsIImageLoadingContent::CURRENT_REQUEST,
572 getter_AddRefs(imgRequest));
573 NS_ENSURE_SUCCESS(rv, rv);
574 if (!imgRequest)
575 return NS_ERROR_NOT_AVAILABLE;
577 rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
578 NS_ENSURE_SUCCESS(rv, rv);
579 if (!imgContainer)
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);
586 else {
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();
602 nsRect maxSize;
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) {
607 float scale = 1.0;
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;
623 if (aImageLoader) {
624 RefPtr<DrawTarget> dt =
625 gfxPlatform::GetPlatform()->
626 CreateOffscreenContentDrawTarget(destSize.ToIntSize(),
627 SurfaceFormat::B8G8R8A8);
628 if (!dt)
629 return NS_ERROR_FAILURE;
631 nsRefPtr<gfxContext> ctx = new gfxContext(dt);
632 if (!ctx)
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();
640 } else {
641 *aSurface = aCanvas->GetSurfaceSnapshot();
644 return result;
647 void
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;
656 NS_IMETHODIMP
657 nsBaseDragService::Suppress()
659 EndDragSession(false);
660 ++mSuppressLevel;
661 return NS_OK;
664 NS_IMETHODIMP
665 nsBaseDragService::Unsuppress()
667 --mSuppressLevel;
668 return NS_OK;