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 "nsArrayUtils.h"
10 #include "nsITransferable.h"
14 #include "nsIInterfaceRequestorUtils.h"
16 #include "nsFrameLoaderOwner.h"
17 #include "nsIContent.h"
18 #include "nsViewManager.h"
20 #include "nsPresContext.h"
21 #include "nsIImageLoadingContent.h"
22 #include "imgIContainer.h"
23 #include "imgIRequest.h"
24 #include "ImageRegion.h"
25 #include "nsQueryObject.h"
27 #include "nsXULPopupManager.h"
28 #include "nsMenuPopupFrame.h"
29 #include "nsTreeBodyFrame.h"
30 #include "mozilla/MouseEvents.h"
31 #include "mozilla/Preferences.h"
32 #include "mozilla/PresShell.h"
33 #include "mozilla/ProfilerLabels.h"
34 #include "mozilla/SVGImageContext.h"
35 #include "mozilla/TextControlElement.h"
36 #include "mozilla/Unused.h"
37 #include "mozilla/ViewportUtils.h"
38 #include "mozilla/dom/BindingDeclarations.h"
39 #include "mozilla/dom/DataTransferItemList.h"
40 #include "mozilla/dom/DataTransfer.h"
41 #include "mozilla/dom/Document.h"
42 #include "mozilla/dom/DocumentInlines.h"
43 #include "mozilla/dom/DragEvent.h"
44 #include "mozilla/dom/MouseEventBinding.h"
45 #include "mozilla/dom/Selection.h"
46 #include "mozilla/gfx/2D.h"
47 #include "nsFrameLoader.h"
48 #include "BrowserParent.h"
49 #include "nsIMutableArray.h"
50 #include "gfxContext.h"
51 #include "gfxPlatform.h"
54 using namespace mozilla
;
55 using namespace mozilla::dom
;
56 using namespace mozilla::gfx
;
57 using namespace mozilla::image
;
59 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
61 nsBaseDragService::nsBaseDragService()
63 mOnlyChromeDrop(false),
65 mSessionIsSynthesizedForTests(false),
66 mIsDraggingTextInTextControl(false),
67 mEndingSession(false),
69 mUserCancelled(false),
70 mDragEventDispatchedToChildProcess(false),
71 mDragAction(DRAGDROP_ACTION_NONE
),
72 mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED
),
73 mEffectAllowedForTests(DRAGDROP_ACTION_UNINITIALIZED
),
74 mContentPolicyType(nsIContentPolicy::TYPE_OTHER
),
76 mInputSource(MouseEvent_Binding::MOZ_SOURCE_MOUSE
) {}
78 nsBaseDragService::~nsBaseDragService() = default;
80 NS_IMPL_ISUPPORTS(nsBaseDragService
, nsIDragService
, nsIDragSession
)
82 //---------------------------------------------------------
84 nsBaseDragService::SetCanDrop(bool aCanDrop
) {
89 //---------------------------------------------------------
91 nsBaseDragService::GetCanDrop(bool* aCanDrop
) {
95 //---------------------------------------------------------
97 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome
) {
98 mOnlyChromeDrop
= aOnlyChrome
;
102 //---------------------------------------------------------
104 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome
) {
105 *aOnlyChrome
= mOnlyChromeDrop
;
109 //---------------------------------------------------------
111 nsBaseDragService::SetDragAction(uint32_t anAction
) {
112 mDragAction
= anAction
;
116 //---------------------------------------------------------
118 nsBaseDragService::GetDragAction(uint32_t* anAction
) {
119 *anAction
= mDragAction
;
123 //-------------------------------------------------------------------------
126 nsBaseDragService::GetNumDropItems(uint32_t* aNumItems
) {
128 return NS_ERROR_FAILURE
;
132 // GetSourceWindowContext
134 // Returns the window context where the drag was initiated. This will be
135 // nullptr if the drag began outside of our application.
138 nsBaseDragService::GetSourceWindowContext(
139 WindowContext
** aSourceWindowContext
) {
140 *aSourceWindowContext
= mSourceWindowContext
.get();
141 NS_IF_ADDREF(*aSourceWindowContext
);
146 nsBaseDragService::SetSourceWindowContext(WindowContext
* aSourceWindowContext
) {
147 // This should only be called in a child process.
148 MOZ_ASSERT(!XRE_IsParentProcess());
149 mSourceWindowContext
= aSourceWindowContext
;
156 // Returns the DOM node where the drag was initiated. This will be
157 // nullptr if the drag began outside of our application.
160 nsBaseDragService::GetSourceNode(nsINode
** aSourceNode
) {
161 *aSourceNode
= do_AddRef(mSourceNode
).take();
165 void nsBaseDragService::UpdateSource(nsINode
* aNewSourceNode
,
166 Selection
* aNewSelection
) {
167 MOZ_ASSERT(mSourceNode
);
168 MOZ_ASSERT(aNewSourceNode
);
169 MOZ_ASSERT(mSourceNode
->IsInNativeAnonymousSubtree() ||
170 aNewSourceNode
->IsInNativeAnonymousSubtree());
171 MOZ_ASSERT(mSourceDocument
== aNewSourceNode
->OwnerDoc());
172 mSourceNode
= aNewSourceNode
;
173 // Don't set mSelection if the session was invoked without selection or
174 // making it becomes nullptr. The latter occurs when the old frame is
176 if (mSelection
&& aNewSelection
) {
177 // XXX If the dragging image is created once (e.g., at drag start), the
178 // image won't be updated unless we notify `DrawDrag` callers.
179 // However, it must be okay for now to keep using older image of
181 mSelection
= aNewSelection
;
186 nsBaseDragService::GetTriggeringPrincipal(nsIPrincipal
** aPrincipal
) {
187 NS_IF_ADDREF(*aPrincipal
= mTriggeringPrincipal
);
192 nsBaseDragService::SetTriggeringPrincipal(nsIPrincipal
* aPrincipal
) {
193 mTriggeringPrincipal
= aPrincipal
;
198 nsBaseDragService::GetCsp(nsIContentSecurityPolicy
** aCsp
) {
199 NS_IF_ADDREF(*aCsp
= mCsp
);
204 nsBaseDragService::SetCsp(nsIContentSecurityPolicy
* aCsp
) {
209 //-------------------------------------------------------------------------
212 nsBaseDragService::GetData(nsITransferable
* aTransferable
,
213 uint32_t aItemIndex
) {
214 return NS_ERROR_FAILURE
;
217 //-------------------------------------------------------------------------
219 nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor
,
221 return NS_ERROR_FAILURE
;
225 nsBaseDragService::GetDataTransferXPCOM(DataTransfer
** aDataTransfer
) {
226 *aDataTransfer
= mDataTransfer
;
227 NS_IF_ADDREF(*aDataTransfer
);
232 nsBaseDragService::SetDataTransferXPCOM(DataTransfer
* aDataTransfer
) {
233 NS_ENSURE_STATE(aDataTransfer
);
234 mDataTransfer
= aDataTransfer
;
238 DataTransfer
* nsBaseDragService::GetDataTransfer() { return mDataTransfer
; }
240 void nsBaseDragService::SetDataTransfer(DataTransfer
* aDataTransfer
) {
241 mDataTransfer
= aDataTransfer
;
244 bool nsBaseDragService::IsSynthesizedForTests() {
245 return mSessionIsSynthesizedForTests
;
248 bool nsBaseDragService::IsDraggingTextInTextControl() {
249 return mIsDraggingTextInTextControl
;
252 uint32_t nsBaseDragService::GetEffectAllowedForTests() {
253 MOZ_ASSERT(mSessionIsSynthesizedForTests
);
254 return mEffectAllowedForTests
;
257 NS_IMETHODIMP
nsBaseDragService::SetDragEndPointForTests(int32_t aScreenX
,
259 MOZ_ASSERT(mDoingDrag
);
260 MOZ_ASSERT(mSourceDocument
);
261 MOZ_ASSERT(mSessionIsSynthesizedForTests
);
262 if (!mDoingDrag
|| !mSourceDocument
|| !mSessionIsSynthesizedForTests
) {
263 return NS_ERROR_FAILURE
;
265 nsPresContext
* presContext
= mSourceDocument
->GetPresContext();
266 if (NS_WARN_IF(!presContext
)) {
267 return NS_ERROR_FAILURE
;
270 LayoutDeviceIntPoint(presContext
->CSSPixelsToDevPixels(aScreenX
),
271 presContext
->CSSPixelsToDevPixels(aScreenY
)));
275 //-------------------------------------------------------------------------
277 nsBaseDragService::InvokeDragSession(
278 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
279 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
280 uint32_t aActionType
,
281 nsContentPolicyType aContentPolicyType
= nsIContentPolicy::TYPE_OTHER
) {
282 AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER
);
284 NS_ENSURE_TRUE(aDOMNode
, NS_ERROR_INVALID_ARG
);
285 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
287 // stash the document of the dom node
288 mSourceDocument
= aDOMNode
->OwnerDoc();
289 mTriggeringPrincipal
= aPrincipal
;
291 mSourceNode
= aDOMNode
;
292 mIsDraggingTextInTextControl
=
293 mSourceNode
->IsInNativeAnonymousSubtree() &&
294 TextControlElement::FromNodeOrNull(
295 mSourceNode
->GetClosestNativeAnonymousSubtreeRootParent());
296 mContentPolicyType
= aContentPolicyType
;
297 mEndDragPoint
= LayoutDeviceIntPoint(0, 0);
299 // When the mouse goes down, the selection code starts a mouse
300 // capture. However, this gets in the way of determining drag
301 // feedback for things like trees because the event coordinates
302 // are in the wrong coord system, so turn off mouse capture.
303 PresShell::ClearMouseCapture();
305 if (mSessionIsSynthesizedForTests
) {
307 mDragAction
= aActionType
;
308 mEffectAllowedForTests
= aActionType
;
312 // If you're hitting this, a test is causing the browser to attempt to enter
313 // the drag-drop native nested event loop, which will put the browser in a
314 // state that won't run tests properly until there's manual intervention
315 // to exit the drag-drop loop (either by moving the mouse or hitting escape),
316 // which can't be done from script since we're in the nested loop.
318 // The best way to avoid this is to catch the dragstart event on the item
319 // being dragged, and then to call preventDefault() and stopPropagating() on
321 if (XRE_IsParentProcess()) {
323 !xpc::IsInAutomation(),
324 "About to start drag-drop native loop on which will prevent later "
325 "tests from running properly.");
329 mozilla::Unused
<< aTransferableArray
->GetLength(&length
);
331 nsCOMPtr
<nsIMutableArray
> mutableArray
=
332 do_QueryInterface(aTransferableArray
);
334 // In order to be able trigger dnd, we need to have some transferable
336 nsCOMPtr
<nsITransferable
> trans
=
337 do_CreateInstance("@mozilla.org/widget/transferable;1");
338 trans
->Init(nullptr);
339 trans
->SetRequestingPrincipal(mSourceNode
->NodePrincipal());
340 trans
->SetContentPolicyType(mContentPolicyType
);
341 trans
->SetCookieJarSettings(aCookieJarSettings
);
342 mutableArray
->AppendElement(trans
);
345 for (uint32_t i
= 0; i
< length
; ++i
) {
346 nsCOMPtr
<nsITransferable
> trans
=
347 do_QueryElementAt(aTransferableArray
, i
);
349 // Set the requestingPrincipal on the transferable.
350 trans
->SetRequestingPrincipal(mSourceNode
->NodePrincipal());
351 trans
->SetContentPolicyType(mContentPolicyType
);
352 trans
->SetCookieJarSettings(aCookieJarSettings
);
357 nsresult rv
= InvokeDragSessionImpl(aTransferableArray
, mRegion
, aActionType
);
360 // Set mDoingDrag so that EndDragSession cleans up and sends the dragend
361 // event after the aborted drag.
363 EndDragSession(true, 0);
370 nsBaseDragService::InvokeDragSessionWithImage(
371 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
372 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
373 uint32_t aActionType
, nsINode
* aImage
, int32_t aImageX
, int32_t aImageY
,
374 DragEvent
* aDragEvent
, DataTransfer
* aDataTransfer
) {
375 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
376 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
377 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
379 mSessionIsSynthesizedForTests
=
380 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
381 mDataTransfer
= aDataTransfer
;
382 mSelection
= nullptr;
384 mDragPopup
= nullptr;
386 mImageOffset
= CSSIntPoint(aImageX
, aImageY
);
387 mDragStartData
= nullptr;
388 mSourceWindowContext
=
389 aDOMNode
? aDOMNode
->OwnerDoc()->GetWindowContext() : nullptr;
391 mScreenPosition
.x
= aDragEvent
->ScreenX(CallerType::System
);
392 mScreenPosition
.y
= aDragEvent
->ScreenY(CallerType::System
);
393 mInputSource
= aDragEvent
->MozInputSource();
395 // If dragging within a XUL tree and no custom drag image was
396 // set, the region argument to InvokeDragSessionWithImage needs
397 // to be set to the area encompassing the selected rows of the
398 // tree to ensure that the drag feedback gets clipped to those
399 // rows. For other content, region should be null.
401 if (aDOMNode
&& aDOMNode
->IsContent() && !aImage
) {
402 if (aDOMNode
->NodeInfo()->Equals(nsGkAtoms::treechildren
,
404 nsTreeBodyFrame
* treeBody
=
405 do_QueryFrame(aDOMNode
->AsContent()->GetPrimaryFrame());
407 mRegion
= treeBody
->GetSelectionRegion();
412 nsresult rv
= InvokeDragSession(
413 aDOMNode
, aPrincipal
, aCsp
, aCookieJarSettings
, aTransferableArray
,
414 aActionType
, nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
420 nsBaseDragService::InvokeDragSessionWithRemoteImage(
421 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
422 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
423 uint32_t aActionType
, RemoteDragStartData
* aDragStartData
,
424 DragEvent
* aDragEvent
, DataTransfer
* aDataTransfer
) {
425 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
426 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
427 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
429 mSessionIsSynthesizedForTests
=
430 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
431 mDataTransfer
= aDataTransfer
;
432 mSelection
= nullptr;
434 mDragPopup
= nullptr;
436 mDragStartData
= aDragStartData
;
437 mImageOffset
= CSSIntPoint(0, 0);
438 mSourceWindowContext
= mDragStartData
->GetSourceWindowContext();
440 mScreenPosition
.x
= aDragEvent
->ScreenX(CallerType::System
);
441 mScreenPosition
.y
= aDragEvent
->ScreenY(CallerType::System
);
442 mInputSource
= aDragEvent
->MozInputSource();
444 nsresult rv
= InvokeDragSession(
445 aDOMNode
, aPrincipal
, aCsp
, aCookieJarSettings
, aTransferableArray
,
446 aActionType
, nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
452 nsBaseDragService::InvokeDragSessionWithSelection(
453 Selection
* aSelection
, nsIPrincipal
* aPrincipal
,
454 nsIContentSecurityPolicy
* aCsp
, nsICookieJarSettings
* aCookieJarSettings
,
455 nsIArray
* aTransferableArray
, uint32_t aActionType
, DragEvent
* aDragEvent
,
456 DataTransfer
* aDataTransfer
) {
457 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
458 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
459 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
461 mSessionIsSynthesizedForTests
=
462 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
463 mDataTransfer
= aDataTransfer
;
464 mSelection
= aSelection
;
466 mDragPopup
= nullptr;
468 mImageOffset
= CSSIntPoint();
469 mDragStartData
= nullptr;
472 mScreenPosition
.x
= aDragEvent
->ScreenX(CallerType::System
);
473 mScreenPosition
.y
= aDragEvent
->ScreenY(CallerType::System
);
474 mInputSource
= aDragEvent
->MozInputSource();
476 // just get the focused node from the selection
477 // XXXndeakin this should actually be the deepest node that contains both
478 // endpoints of the selection
479 nsCOMPtr
<nsINode
> node
= aSelection
->GetFocusNode();
480 mSourceWindowContext
= node
? node
->OwnerDoc()->GetWindowContext() : nullptr;
482 return InvokeDragSession(node
, aPrincipal
, aCsp
, aCookieJarSettings
,
483 aTransferableArray
, aActionType
,
484 nsIContentPolicy::TYPE_OTHER
);
487 //-------------------------------------------------------------------------
489 nsBaseDragService::GetCurrentSession(nsIDragSession
** aSession
) {
490 if (!aSession
) return NS_ERROR_INVALID_ARG
;
492 // "this" also implements a drag session, so say we are one but only
493 // if there is currently a drag going on.
494 if (!mSuppressLevel
&& mDoingDrag
) {
496 NS_ADDREF(*aSession
); // addRef because we're a "getter"
503 //-------------------------------------------------------------------------
505 nsBaseDragService::StartDragSession() {
507 return NS_ERROR_FAILURE
;
510 // By default dispatch drop also to content.
511 mOnlyChromeDrop
= false;
516 NS_IMETHODIMP
nsBaseDragService::StartDragSessionForTests(
517 uint32_t aAllowedEffect
) {
518 if (NS_WARN_IF(NS_FAILED(StartDragSession()))) {
519 return NS_ERROR_FAILURE
;
521 mDragAction
= aAllowedEffect
;
522 mEffectAllowedForTests
= aAllowedEffect
;
523 mSessionIsSynthesizedForTests
= true;
527 void nsBaseDragService::OpenDragPopup() {
529 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
531 pm
->ShowPopupAtScreen(mDragPopup
, mScreenPosition
.x
- mImageOffset
.x
,
532 mScreenPosition
.y
- mImageOffset
.y
, false, nullptr);
537 int32_t nsBaseDragService::TakeChildProcessDragAction() {
538 // If the last event was dispatched to the child process, use the drag action
539 // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is
540 // returned otherwise.
541 int32_t retval
= DRAGDROP_ACTION_UNINITIALIZED
;
542 if (TakeDragEventDispatchedToChildProcess() &&
543 mDragActionFromChildProcess
!= DRAGDROP_ACTION_UNINITIALIZED
) {
544 retval
= mDragActionFromChildProcess
;
550 //-------------------------------------------------------------------------
552 nsBaseDragService::EndDragSession(bool aDoneDrag
, uint32_t aKeyModifiers
) {
553 if (!mDoingDrag
|| mEndingSession
) {
554 return NS_ERROR_FAILURE
;
557 mEndingSession
= true;
559 if (aDoneDrag
&& !mSuppressLevel
) {
560 FireDragEventAtSource(eDragEnd
, aKeyModifiers
);
564 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
566 pm
->HidePopup(mDragPopup
, false, true, false, false);
570 for (uint32_t i
= 0; i
< mChildProcesses
.Length(); ++i
) {
571 mozilla::Unused
<< mChildProcesses
[i
]->SendEndDragSession(
572 aDoneDrag
, mUserCancelled
, mEndDragPoint
, aKeyModifiers
);
573 // Continue sending input events with input priority when stopping the dnd
575 mChildProcesses
[i
]->SetInputPriorityEventEnabled(true);
577 mChildProcesses
.Clear();
579 // mDataTransfer and the items it owns are going to die anyway, but we
580 // explicitly deref the contained data here so that we don't have to wait for
581 // CC to reclaim the memory.
582 if (XRE_IsParentProcess()) {
583 DiscardInternalTransferData();
587 mSessionIsSynthesizedForTests
= false;
588 mIsDraggingTextInTextControl
= false;
589 mEffectAllowedForTests
= nsIDragService::DRAGDROP_ACTION_UNINITIALIZED
;
590 mEndingSession
= false;
593 // release the source we've been holding on to.
594 mSourceDocument
= nullptr;
595 mSourceNode
= nullptr;
596 mSourceWindowContext
= nullptr;
597 mTriggeringPrincipal
= nullptr;
599 mSelection
= nullptr;
600 mDataTransfer
= nullptr;
602 mUserCancelled
= false;
603 mDragPopup
= nullptr;
604 mDragStartData
= nullptr;
606 mImageOffset
= CSSIntPoint();
607 mScreenPosition
= CSSIntPoint();
608 mEndDragPoint
= LayoutDeviceIntPoint(0, 0);
609 mInputSource
= MouseEvent_Binding::MOZ_SOURCE_MOUSE
;
615 void nsBaseDragService::DiscardInternalTransferData() {
616 if (mDataTransfer
&& mSourceNode
) {
617 MOZ_ASSERT(mDataTransfer
);
619 DataTransferItemList
* items
= mDataTransfer
->Items();
620 for (size_t i
= 0; i
< items
->Length(); i
++) {
622 DataTransferItem
* item
= items
->IndexedGetter(i
, found
);
624 // Non-OTHER items may still be needed by JS. Skip them.
625 if (!found
|| item
->Kind() != DataTransferItem::KIND_OTHER
) {
629 nsCOMPtr
<nsIVariant
> variant
= item
->DataNoSecurityCheck();
630 nsCOMPtr
<nsIWritableVariant
> writable
= do_QueryInterface(variant
);
633 writable
->SetAsEmpty();
640 nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage
,
641 uint32_t aKeyModifiers
) {
642 if (!mSourceNode
|| !mSourceDocument
|| mSuppressLevel
) {
645 RefPtr
<PresShell
> presShell
= mSourceDocument
->GetPresShell();
650 RefPtr
<nsPresContext
> pc
= presShell
->GetPresContext();
651 nsCOMPtr
<nsIWidget
> widget
= pc
? pc
->GetRootWidget() : nullptr;
653 nsEventStatus status
= nsEventStatus_eIgnore
;
654 WidgetDragEvent
event(true, aEventMessage
, widget
);
655 event
.mFlags
.mIsSynthesizedForTests
= mSessionIsSynthesizedForTests
;
656 event
.mInputSource
= mInputSource
;
657 if (aEventMessage
== eDragEnd
) {
658 event
.mRefPoint
= mEndDragPoint
;
659 event
.mUserCancelled
= mUserCancelled
;
661 event
.mModifiers
= aKeyModifiers
;
664 // Send the drag event to APZ, which needs to know about them to be
665 // able to accurately detect the end of a drag gesture.
666 widget
->DispatchEventToAPZOnly(&event
);
669 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mSourceNode
);
670 return presShell
->HandleDOMEventWithTarget(content
, &event
, &status
);
673 /* This is used by Windows and Mac to update the position of a popup being
674 * used as a drag image during the drag. This isn't used on GTK as it manages
675 * the drag popup itself.
678 nsBaseDragService::DragMoved(int32_t aX
, int32_t aY
) {
680 nsIFrame
* frame
= mDragPopup
->GetPrimaryFrame();
681 if (frame
&& frame
->IsMenuPopupFrame()) {
683 RoundedToInt(LayoutDeviceIntPoint(aX
, aY
) /
684 frame
->PresContext()->CSSToDevPixelScale()) -
686 (static_cast<nsMenuPopupFrame
*>(frame
))->MoveTo(cssPos
, true);
693 static PresShell
* GetPresShellForContent(nsINode
* aDOMNode
) {
694 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aDOMNode
);
695 if (!content
) return nullptr;
697 RefPtr
<Document
> document
= content
->GetComposedDoc();
699 document
->FlushPendingNotifications(FlushType::Display
);
700 return document
->GetPresShell();
706 nsresult
nsBaseDragService::DrawDrag(nsINode
* aDOMNode
,
707 const Maybe
<CSSIntRegion
>& aRegion
,
708 CSSIntPoint aScreenPosition
,
709 LayoutDeviceIntRect
* aScreenDragRect
,
710 RefPtr
<SourceSurface
>* aSurface
,
711 nsPresContext
** aPresContext
) {
713 *aPresContext
= nullptr;
715 // use a default size, in case of an error.
716 aScreenDragRect
->SetRect(aScreenPosition
.x
- mImageOffset
.x
,
717 aScreenPosition
.y
- mImageOffset
.y
, 1, 1);
719 // if a drag image was specified, use that, otherwise, use the source node
720 nsCOMPtr
<nsINode
> dragNode
= mImage
? mImage
.get() : aDOMNode
;
722 // get the presshell for the node being dragged. If the drag image is not in
723 // a document or has no frame, get the presshell from the source drag node
724 PresShell
* presShell
= GetPresShellForContent(dragNode
);
725 if (!presShell
&& mImage
) {
726 presShell
= GetPresShellForContent(aDOMNode
);
729 return NS_ERROR_FAILURE
;
732 *aPresContext
= presShell
->GetPresContext();
734 if (mDragStartData
) {
736 // Just clear the surface if chrome has overridden it with an image.
739 *aSurface
= mDragStartData
->TakeVisualization(aScreenDragRect
);
742 mDragStartData
= nullptr;
746 // convert mouse position to dev pixels of the prescontext
747 CSSIntPoint
screenPosition(aScreenPosition
);
748 screenPosition
.x
-= mImageOffset
.x
;
749 screenPosition
.y
-= mImageOffset
.y
;
750 LayoutDeviceIntPoint screenPoint
=
751 ConvertToUnscaledDevPixels(*aPresContext
, screenPosition
);
752 aScreenDragRect
->MoveTo(screenPoint
.x
, screenPoint
.y
);
754 // check if drag images are disabled
755 bool enableDragImages
= Preferences::GetBool(DRAGIMAGES_PREF
, true);
757 // didn't want an image, so just set the screen rectangle to the frame size
758 if (!enableDragImages
|| !mHasImage
) {
759 // This holds a quantity in RelativeTo{presShell->GetRootFrame(),
760 // ViewportType::Layout} space.
761 nsRect presLayoutRect
;
763 // if a region was specified, set the screen rectangle to the area that
764 // the region occupies
765 presLayoutRect
= ToAppUnits(aRegion
->GetBounds(), AppUnitsPerCSSPixel());
767 // otherwise, there was no region so just set the rectangle to
768 // the size of the primary frame of the content.
769 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
770 if (nsIFrame
* frame
= content
->GetPrimaryFrame()) {
771 presLayoutRect
= frame
->GetBoundingClientRect();
775 LayoutDeviceRect screenVisualRect
= ViewportUtils::ToScreenRelativeVisual(
776 LayoutDeviceRect::FromAppUnits(presLayoutRect
,
777 (*aPresContext
)->AppUnitsPerDevPixel()),
779 aScreenDragRect
->SizeTo(screenVisualRect
.Width(),
780 screenVisualRect
.Height());
784 // draw the image for selections
786 LayoutDeviceIntPoint
pnt(aScreenDragRect
->TopLeft());
787 *aSurface
= presShell
->RenderSelection(
788 mSelection
, pnt
, aScreenDragRect
,
789 mImage
? RenderImageFlags::None
: RenderImageFlags::AutoScale
);
793 // if a custom image was specified, check if it is an image node and draw
794 // using the source rather than the displayed image. But if mImage isn't
795 // an image or canvas, fall through to RenderNode below.
797 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
798 HTMLCanvasElement
* canvas
= HTMLCanvasElement::FromNodeOrNull(content
);
800 return DrawDragForImage(*aPresContext
, nullptr, canvas
, aScreenDragRect
,
804 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(dragNode
);
805 // for image nodes, create the drag image from the actual image data
807 return DrawDragForImage(*aPresContext
, imageLoader
, nullptr,
808 aScreenDragRect
, aSurface
);
811 // If the image is a popup, use that as the image. This allows custom drag
812 // images that can change during the drag, but means that any platform
813 // default image handling won't occur.
814 // XXXndeakin this should be chrome-only
816 nsIFrame
* frame
= content
->GetPrimaryFrame();
817 if (frame
&& frame
->IsMenuPopupFrame()) {
818 mDragPopup
= content
;
823 // otherwise, just draw the node
824 RenderImageFlags renderFlags
=
825 mImage
? RenderImageFlags::None
: RenderImageFlags::AutoScale
;
826 if (renderFlags
!= RenderImageFlags::None
) {
827 // check if the dragged node itself is an img element
828 if (dragNode
->NodeName().LowerCaseEqualsLiteral("img")) {
829 renderFlags
= renderFlags
| RenderImageFlags::IsImage
;
831 nsINodeList
* childList
= dragNode
->ChildNodes();
832 uint32_t length
= childList
->Length();
833 // check every childnode for being an img element
834 // XXXbz why don't we need to check descendants recursively?
835 for (uint32_t count
= 0; count
< length
; ++count
) {
836 if (childList
->Item(count
)->NodeName().LowerCaseEqualsLiteral(
838 // if the dragnode contains an image, set RenderImageFlags::IsImage
840 renderFlags
= renderFlags
| RenderImageFlags::IsImage
;
846 LayoutDeviceIntPoint
pnt(aScreenDragRect
->TopLeft());
847 *aSurface
= presShell
->RenderNode(dragNode
, aRegion
, pnt
, aScreenDragRect
,
851 // If an image was specified, reset the position from the offset that was
854 aScreenDragRect
->MoveTo(screenPoint
.x
, screenPoint
.y
);
860 nsresult
nsBaseDragService::DrawDragForImage(
861 nsPresContext
* aPresContext
, nsIImageLoadingContent
* aImageLoader
,
862 HTMLCanvasElement
* aCanvas
, LayoutDeviceIntRect
* aScreenDragRect
,
863 RefPtr
<SourceSurface
>* aSurface
) {
864 nsCOMPtr
<imgIContainer
> imgContainer
;
866 nsCOMPtr
<imgIRequest
> imgRequest
;
867 nsresult rv
= aImageLoader
->GetRequest(
868 nsIImageLoadingContent::CURRENT_REQUEST
, getter_AddRefs(imgRequest
));
869 NS_ENSURE_SUCCESS(rv
, rv
);
870 if (!imgRequest
) return NS_ERROR_NOT_AVAILABLE
;
872 rv
= imgRequest
->GetImage(getter_AddRefs(imgContainer
));
873 NS_ENSURE_SUCCESS(rv
, rv
);
874 if (!imgContainer
) return NS_ERROR_NOT_AVAILABLE
;
876 // use the size of the image as the size of the drag image
877 int32_t imageWidth
, imageHeight
;
878 rv
= imgContainer
->GetWidth(&imageWidth
);
879 NS_ENSURE_SUCCESS(rv
, rv
);
881 rv
= imgContainer
->GetHeight(&imageHeight
);
882 NS_ENSURE_SUCCESS(rv
, rv
);
884 aScreenDragRect
->SizeTo(aPresContext
->CSSPixelsToDevPixels(imageWidth
),
885 aPresContext
->CSSPixelsToDevPixels(imageHeight
));
887 // XXX The canvas size should be converted to dev pixels.
888 NS_ASSERTION(aCanvas
, "both image and canvas are null");
889 nsIntSize sz
= aCanvas
->GetSize();
890 aScreenDragRect
->SizeTo(sz
.width
, sz
.height
);
894 destSize
.width
= aScreenDragRect
->Width();
895 destSize
.height
= aScreenDragRect
->Height();
896 if (destSize
.width
== 0 || destSize
.height
== 0) return NS_ERROR_FAILURE
;
898 nsresult result
= NS_OK
;
900 RefPtr
<DrawTarget
> dt
=
901 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
902 destSize
, SurfaceFormat::B8G8R8A8
);
903 if (!dt
|| !dt
->IsValid()) return NS_ERROR_FAILURE
;
905 RefPtr
<gfxContext
> ctx
= gfxContext::CreateOrNull(dt
);
906 if (!ctx
) return NS_ERROR_FAILURE
;
909 imgContainer
->Draw(ctx
, destSize
, ImageRegion::Create(destSize
),
910 imgIContainer::FRAME_CURRENT
, SamplingFilter::GOOD
,
911 /* no SVGImageContext */ Nothing(),
912 imgIContainer::FLAG_SYNC_DECODE
, 1.0);
913 if (res
== ImgDrawResult::BAD_IMAGE
|| res
== ImgDrawResult::BAD_ARGS
||
914 res
== ImgDrawResult::NOT_SUPPORTED
) {
915 return NS_ERROR_FAILURE
;
917 *aSurface
= dt
->Snapshot();
919 *aSurface
= aCanvas
->GetSurfaceSnapshot();
925 LayoutDeviceIntPoint
nsBaseDragService::ConvertToUnscaledDevPixels(
926 nsPresContext
* aPresContext
, CSSIntPoint aScreenPosition
) {
928 aPresContext
->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
929 return LayoutDeviceIntPoint(
930 nsPresContext::CSSPixelsToAppUnits(aScreenPosition
.x
) / adj
,
931 nsPresContext::CSSPixelsToAppUnits(aScreenPosition
.y
) / adj
);
935 nsBaseDragService::Suppress() {
936 EndDragSession(false, 0);
942 nsBaseDragService::Unsuppress() {
948 nsBaseDragService::UserCancelled() {
949 mUserCancelled
= true;
954 nsBaseDragService::UpdateDragEffect() {
955 mDragActionFromChildProcess
= mDragAction
;
960 nsBaseDragService::UpdateDragImage(nsINode
* aImage
, int32_t aImageX
,
962 // Don't change the image if this is a drag from another source or if there
964 if (!mSourceNode
|| mDragPopup
) return NS_OK
;
967 mImageOffset
= CSSIntPoint(aImageX
, aImageY
);
972 nsBaseDragService::DragEventDispatchedToChildProcess() {
973 mDragEventDispatchedToChildProcess
= true;
977 bool nsBaseDragService::MaybeAddChildProcess(
978 mozilla::dom::ContentParent
* aChild
) {
979 if (!mChildProcesses
.Contains(aChild
)) {
980 mChildProcesses
.AppendElement(aChild
);
986 bool nsBaseDragService::RemoveAllChildProcesses() {
987 for (uint32_t c
= 0; c
< mChildProcesses
.Length(); c
++) {
988 mozilla::Unused
<< mChildProcesses
[c
]->SendEndDragSession(
989 true, false, LayoutDeviceIntPoint(), 0);
991 mChildProcesses
.Clear();