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"
55 using namespace mozilla
;
56 using namespace mozilla::dom
;
57 using namespace mozilla::gfx
;
58 using namespace mozilla::image
;
60 LazyLogModule
sWidgetDragServiceLog("WidgetDragService");
62 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
64 nsBaseDragService::nsBaseDragService()
66 mOnlyChromeDrop(false),
68 mSessionIsSynthesizedForTests(false),
69 mIsDraggingTextInTextControl(false),
70 mEndingSession(false),
72 mUserCancelled(false),
73 mDragEventDispatchedToChildProcess(false),
74 mDragAction(DRAGDROP_ACTION_NONE
),
75 mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED
),
76 mEffectAllowedForTests(DRAGDROP_ACTION_UNINITIALIZED
),
77 mContentPolicyType(nsIContentPolicy::TYPE_OTHER
),
79 mInputSource(MouseEvent_Binding::MOZ_SOURCE_MOUSE
) {}
81 nsBaseDragService::~nsBaseDragService() = default;
83 NS_IMPL_ISUPPORTS(nsBaseDragService
, nsIDragService
, nsIDragSession
)
85 //---------------------------------------------------------
87 nsBaseDragService::SetCanDrop(bool aCanDrop
) {
92 //---------------------------------------------------------
94 nsBaseDragService::GetCanDrop(bool* aCanDrop
) {
98 //---------------------------------------------------------
100 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome
) {
101 mOnlyChromeDrop
= aOnlyChrome
;
105 //---------------------------------------------------------
107 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome
) {
108 *aOnlyChrome
= mOnlyChromeDrop
;
112 //---------------------------------------------------------
114 nsBaseDragService::SetDragAction(uint32_t anAction
) {
115 mDragAction
= anAction
;
119 //---------------------------------------------------------
121 nsBaseDragService::GetDragAction(uint32_t* anAction
) {
122 *anAction
= mDragAction
;
126 //-------------------------------------------------------------------------
129 nsBaseDragService::GetNumDropItems(uint32_t* aNumItems
) {
131 return NS_ERROR_FAILURE
;
135 // GetSourceWindowContext
137 // Returns the window context where the drag was initiated. This will be
138 // nullptr if the drag began outside of our application.
141 nsBaseDragService::GetSourceWindowContext(
142 WindowContext
** aSourceWindowContext
) {
143 *aSourceWindowContext
= mSourceWindowContext
.get();
144 NS_IF_ADDREF(*aSourceWindowContext
);
149 nsBaseDragService::SetSourceWindowContext(WindowContext
* aSourceWindowContext
) {
150 // This should only be called in a child process.
151 MOZ_ASSERT(!XRE_IsParentProcess());
152 mSourceWindowContext
= aSourceWindowContext
;
157 // GetSourceTopWindowContext
159 // Returns the top-level window context where the drag was initiated. This will
160 // be nullptr if the drag began outside of our application.
163 nsBaseDragService::GetSourceTopWindowContext(
164 WindowContext
** aSourceTopWindowContext
) {
165 *aSourceTopWindowContext
= mSourceTopWindowContext
.get();
166 NS_IF_ADDREF(*aSourceTopWindowContext
);
171 nsBaseDragService::SetSourceTopWindowContext(
172 WindowContext
* aSourceTopWindowContext
) {
173 // This should only be called in a child process.
174 MOZ_ASSERT(!XRE_IsParentProcess());
175 mSourceTopWindowContext
= aSourceTopWindowContext
;
182 // Returns the DOM node where the drag was initiated. This will be
183 // nullptr if the drag began outside of our application.
186 nsBaseDragService::GetSourceNode(nsINode
** aSourceNode
) {
187 *aSourceNode
= do_AddRef(mSourceNode
).take();
191 void nsBaseDragService::UpdateSource(nsINode
* aNewSourceNode
,
192 Selection
* aNewSelection
) {
193 MOZ_ASSERT(mSourceNode
);
194 MOZ_ASSERT(aNewSourceNode
);
195 MOZ_ASSERT(mSourceNode
->IsInNativeAnonymousSubtree() ||
196 aNewSourceNode
->IsInNativeAnonymousSubtree());
197 MOZ_ASSERT(mSourceDocument
== aNewSourceNode
->OwnerDoc());
198 mSourceNode
= aNewSourceNode
;
199 // Don't set mSelection if the session was invoked without selection or
200 // making it becomes nullptr. The latter occurs when the old frame is
202 if (mSelection
&& aNewSelection
) {
203 // XXX If the dragging image is created once (e.g., at drag start), the
204 // image won't be updated unless we notify `DrawDrag` callers.
205 // However, it must be okay for now to keep using older image of
207 mSelection
= aNewSelection
;
212 nsBaseDragService::GetTriggeringPrincipal(nsIPrincipal
** aPrincipal
) {
213 NS_IF_ADDREF(*aPrincipal
= mTriggeringPrincipal
);
218 nsBaseDragService::SetTriggeringPrincipal(nsIPrincipal
* aPrincipal
) {
219 mTriggeringPrincipal
= aPrincipal
;
224 nsBaseDragService::GetCsp(nsIContentSecurityPolicy
** aCsp
) {
225 NS_IF_ADDREF(*aCsp
= mCsp
);
230 nsBaseDragService::SetCsp(nsIContentSecurityPolicy
* aCsp
) {
235 //-------------------------------------------------------------------------
238 nsBaseDragService::GetData(nsITransferable
* aTransferable
,
239 uint32_t aItemIndex
) {
240 return NS_ERROR_FAILURE
;
243 //-------------------------------------------------------------------------
245 nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor
,
247 return NS_ERROR_FAILURE
;
251 nsBaseDragService::GetDataTransferXPCOM(DataTransfer
** aDataTransfer
) {
252 *aDataTransfer
= mDataTransfer
;
253 NS_IF_ADDREF(*aDataTransfer
);
258 nsBaseDragService::SetDataTransferXPCOM(DataTransfer
* aDataTransfer
) {
259 NS_ENSURE_STATE(aDataTransfer
);
260 mDataTransfer
= aDataTransfer
;
264 DataTransfer
* nsBaseDragService::GetDataTransfer() { return mDataTransfer
; }
266 void nsBaseDragService::SetDataTransfer(DataTransfer
* aDataTransfer
) {
267 mDataTransfer
= aDataTransfer
;
270 bool nsBaseDragService::IsSynthesizedForTests() {
271 return mSessionIsSynthesizedForTests
;
274 bool nsBaseDragService::IsDraggingTextInTextControl() {
275 return mIsDraggingTextInTextControl
;
278 uint32_t nsBaseDragService::GetEffectAllowedForTests() {
279 MOZ_ASSERT(mSessionIsSynthesizedForTests
);
280 return mEffectAllowedForTests
;
283 NS_IMETHODIMP
nsBaseDragService::SetDragEndPointForTests(int32_t aScreenX
,
285 MOZ_ASSERT(mDoingDrag
);
286 MOZ_ASSERT(mSourceDocument
);
287 MOZ_ASSERT(mSessionIsSynthesizedForTests
);
289 if (!mDoingDrag
|| !mSourceDocument
|| !mSessionIsSynthesizedForTests
) {
290 return NS_ERROR_FAILURE
;
292 nsPresContext
* pc
= mSourceDocument
->GetPresContext();
293 if (NS_WARN_IF(!pc
)) {
294 return NS_ERROR_FAILURE
;
296 auto p
= LayoutDeviceIntPoint::Round(CSSIntPoint(aScreenX
, aScreenY
) *
297 pc
->CSSToDevPixelScale());
298 // p is screen-relative, and we want them to be top-level-widget-relative.
299 if (nsCOMPtr
<nsIWidget
> widget
= pc
->GetRootWidget()) {
300 p
-= widget
->WidgetToScreenOffset();
301 p
+= widget
->WidgetToTopLevelWidgetOffset();
307 //-------------------------------------------------------------------------
309 nsBaseDragService::InvokeDragSession(
310 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
311 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
312 uint32_t aActionType
,
313 nsContentPolicyType aContentPolicyType
= nsIContentPolicy::TYPE_OTHER
) {
314 AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER
);
316 NS_ENSURE_TRUE(aDOMNode
, NS_ERROR_INVALID_ARG
);
317 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
319 // stash the document of the dom node
320 mSourceDocument
= aDOMNode
->OwnerDoc();
321 mTriggeringPrincipal
= aPrincipal
;
323 mSourceNode
= aDOMNode
;
324 mIsDraggingTextInTextControl
=
325 mSourceNode
->IsInNativeAnonymousSubtree() &&
326 TextControlElement::FromNodeOrNull(
327 mSourceNode
->GetClosestNativeAnonymousSubtreeRootParentOrHost());
328 mContentPolicyType
= aContentPolicyType
;
329 mEndDragPoint
= LayoutDeviceIntPoint(0, 0);
331 // When the mouse goes down, the selection code starts a mouse
332 // capture. However, this gets in the way of determining drag
333 // feedback for things like trees because the event coordinates
334 // are in the wrong coord system, so turn off mouse capture.
335 PresShell::ClearMouseCapture();
337 if (mSessionIsSynthesizedForTests
) {
339 mDragAction
= aActionType
;
340 mEffectAllowedForTests
= aActionType
;
344 // If you're hitting this, a test is causing the browser to attempt to enter
345 // the drag-drop native nested event loop, which will put the browser in a
346 // state that won't run tests properly until there's manual intervention
347 // to exit the drag-drop loop (either by moving the mouse or hitting escape),
348 // which can't be done from script since we're in the nested loop.
350 // The best way to avoid this is to catch the dragstart event on the item
351 // being dragged, and then to call preventDefault() and stopPropagating() on
353 if (XRE_IsParentProcess()) {
355 !xpc::IsInAutomation(),
356 "About to start drag-drop native loop on which will prevent later "
357 "tests from running properly.");
361 mozilla::Unused
<< aTransferableArray
->GetLength(&length
);
363 nsCOMPtr
<nsIMutableArray
> mutableArray
=
364 do_QueryInterface(aTransferableArray
);
366 // In order to be able trigger dnd, we need to have some transferable
368 nsCOMPtr
<nsITransferable
> trans
=
369 do_CreateInstance("@mozilla.org/widget/transferable;1");
370 trans
->Init(nullptr);
371 trans
->SetRequestingPrincipal(mSourceNode
->NodePrincipal());
372 trans
->SetContentPolicyType(mContentPolicyType
);
373 trans
->SetCookieJarSettings(aCookieJarSettings
);
374 mutableArray
->AppendElement(trans
);
377 for (uint32_t i
= 0; i
< length
; ++i
) {
378 nsCOMPtr
<nsITransferable
> trans
=
379 do_QueryElementAt(aTransferableArray
, i
);
381 // Set the requestingPrincipal on the transferable.
382 trans
->SetRequestingPrincipal(mSourceNode
->NodePrincipal());
383 trans
->SetContentPolicyType(mContentPolicyType
);
384 trans
->SetCookieJarSettings(aCookieJarSettings
);
389 nsresult rv
= InvokeDragSessionImpl(aTransferableArray
, mRegion
, aActionType
);
392 // Set mDoingDrag so that EndDragSession cleans up and sends the dragend
393 // event after the aborted drag.
395 EndDragSession(true, 0);
402 nsBaseDragService::InvokeDragSessionWithImage(
403 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
404 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
405 uint32_t aActionType
, nsINode
* aImage
, int32_t aImageX
, int32_t aImageY
,
406 DragEvent
* aDragEvent
, DataTransfer
* aDataTransfer
) {
407 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
408 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
409 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
411 mSessionIsSynthesizedForTests
=
412 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
413 mDataTransfer
= aDataTransfer
;
414 mSelection
= nullptr;
416 mDragPopup
= nullptr;
418 mImageOffset
= CSSIntPoint(aImageX
, aImageY
);
419 mDragStartData
= nullptr;
420 mSourceWindowContext
=
421 aDOMNode
? aDOMNode
->OwnerDoc()->GetWindowContext() : nullptr;
422 mSourceTopWindowContext
=
423 mSourceWindowContext
? mSourceWindowContext
->TopWindowContext() : nullptr;
425 mScreenPosition
= aDragEvent
->ScreenPoint(CallerType::System
);
426 mInputSource
= aDragEvent
->InputSource();
428 // If dragging within a XUL tree and no custom drag image was
429 // set, the region argument to InvokeDragSessionWithImage needs
430 // to be set to the area encompassing the selected rows of the
431 // tree to ensure that the drag feedback gets clipped to those
432 // rows. For other content, region should be null.
434 if (aDOMNode
&& aDOMNode
->IsContent() && !aImage
) {
435 if (aDOMNode
->NodeInfo()->Equals(nsGkAtoms::treechildren
,
437 nsTreeBodyFrame
* treeBody
=
438 do_QueryFrame(aDOMNode
->AsContent()->GetPrimaryFrame());
440 mRegion
= treeBody
->GetSelectionRegion();
445 nsresult rv
= InvokeDragSession(
446 aDOMNode
, aPrincipal
, aCsp
, aCookieJarSettings
, aTransferableArray
,
447 aActionType
, nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
453 nsBaseDragService::InvokeDragSessionWithRemoteImage(
454 nsINode
* aDOMNode
, nsIPrincipal
* aPrincipal
, nsIContentSecurityPolicy
* aCsp
,
455 nsICookieJarSettings
* aCookieJarSettings
, nsIArray
* aTransferableArray
,
456 uint32_t aActionType
, RemoteDragStartData
* aDragStartData
,
457 DragEvent
* aDragEvent
, DataTransfer
* aDataTransfer
) {
458 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
459 NS_ENSURE_TRUE(aDataTransfer
, NS_ERROR_NULL_POINTER
);
460 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
462 mSessionIsSynthesizedForTests
=
463 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
464 mDataTransfer
= aDataTransfer
;
465 mSelection
= nullptr;
467 mDragPopup
= nullptr;
469 mDragStartData
= aDragStartData
;
470 mImageOffset
= CSSIntPoint(0, 0);
471 mSourceWindowContext
= mDragStartData
->GetSourceWindowContext();
472 mSourceTopWindowContext
= mDragStartData
->GetSourceTopWindowContext();
474 mScreenPosition
= aDragEvent
->ScreenPoint(CallerType::System
);
475 mInputSource
= aDragEvent
->InputSource();
477 nsresult rv
= InvokeDragSession(
478 aDOMNode
, aPrincipal
, aCsp
, aCookieJarSettings
, aTransferableArray
,
479 aActionType
, nsIContentPolicy::TYPE_INTERNAL_IMAGE
);
485 nsBaseDragService::InvokeDragSessionWithSelection(
486 Selection
* aSelection
, nsIPrincipal
* aPrincipal
,
487 nsIContentSecurityPolicy
* aCsp
, nsICookieJarSettings
* aCookieJarSettings
,
488 nsIArray
* aTransferableArray
, uint32_t aActionType
, DragEvent
* aDragEvent
,
489 DataTransfer
* aDataTransfer
) {
490 NS_ENSURE_TRUE(aSelection
, NS_ERROR_NULL_POINTER
);
491 NS_ENSURE_TRUE(aDragEvent
, NS_ERROR_NULL_POINTER
);
492 NS_ENSURE_TRUE(mSuppressLevel
== 0, NS_ERROR_FAILURE
);
494 mSessionIsSynthesizedForTests
=
495 aDragEvent
->WidgetEventPtr()->mFlags
.mIsSynthesizedForTests
;
496 mDataTransfer
= aDataTransfer
;
497 mSelection
= aSelection
;
499 mDragPopup
= nullptr;
501 mImageOffset
= CSSIntPoint();
502 mDragStartData
= nullptr;
505 mScreenPosition
.x
= aDragEvent
->ScreenX(CallerType::System
);
506 mScreenPosition
.y
= aDragEvent
->ScreenY(CallerType::System
);
507 mInputSource
= aDragEvent
->InputSource();
509 // just get the focused node from the selection
510 // XXXndeakin this should actually be the deepest node that contains both
511 // endpoints of the selection
512 nsCOMPtr
<nsINode
> node
= aSelection
->GetFocusNode();
513 mSourceWindowContext
= node
? node
->OwnerDoc()->GetWindowContext() : nullptr;
514 mSourceTopWindowContext
=
515 mSourceWindowContext
? mSourceWindowContext
->TopWindowContext() : nullptr;
517 return InvokeDragSession(node
, aPrincipal
, aCsp
, aCookieJarSettings
,
518 aTransferableArray
, aActionType
,
519 nsIContentPolicy::TYPE_OTHER
);
522 //-------------------------------------------------------------------------
524 nsBaseDragService::GetCurrentSession(nsIDragSession
** aSession
) {
525 if (!aSession
) return NS_ERROR_INVALID_ARG
;
527 // "this" also implements a drag session, so say we are one but only
528 // if there is currently a drag going on.
529 if (!mSuppressLevel
&& mDoingDrag
) {
531 NS_ADDREF(*aSession
); // addRef because we're a "getter"
538 //-------------------------------------------------------------------------
540 nsBaseDragService::StartDragSession() {
542 return NS_ERROR_FAILURE
;
545 // By default dispatch drop also to content.
546 mOnlyChromeDrop
= false;
551 NS_IMETHODIMP
nsBaseDragService::StartDragSessionForTests(
552 uint32_t aAllowedEffect
) {
553 if (NS_WARN_IF(NS_FAILED(StartDragSession()))) {
554 return NS_ERROR_FAILURE
;
556 mDragAction
= aAllowedEffect
;
557 mEffectAllowedForTests
= aAllowedEffect
;
558 mSessionIsSynthesizedForTests
= true;
562 void nsBaseDragService::OpenDragPopup() {
564 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
566 pm
->ShowPopupAtScreen(mDragPopup
, mScreenPosition
.x
- mImageOffset
.x
,
567 mScreenPosition
.y
- mImageOffset
.y
, false, nullptr);
572 int32_t nsBaseDragService::TakeChildProcessDragAction() {
573 // If the last event was dispatched to the child process, use the drag action
574 // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is
575 // returned otherwise.
576 int32_t retval
= DRAGDROP_ACTION_UNINITIALIZED
;
577 if (TakeDragEventDispatchedToChildProcess() &&
578 mDragActionFromChildProcess
!= DRAGDROP_ACTION_UNINITIALIZED
) {
579 retval
= mDragActionFromChildProcess
;
585 //-------------------------------------------------------------------------
587 nsBaseDragService::EndDragSession(bool aDoneDrag
, uint32_t aKeyModifiers
) {
588 if (!mDoingDrag
|| mEndingSession
) {
589 return NS_ERROR_FAILURE
;
592 mEndingSession
= true;
594 if (aDoneDrag
&& !mSuppressLevel
) {
595 FireDragEventAtSource(eDragEnd
, aKeyModifiers
);
599 nsXULPopupManager
* pm
= nsXULPopupManager::GetInstance();
601 pm
->HidePopup(mDragPopup
, {HidePopupOption::DeselectMenu
});
605 uint32_t dropEffect
= nsIDragService::DRAGDROP_ACTION_NONE
;
607 dropEffect
= mDataTransfer
->DropEffectInt();
610 for (uint32_t i
= 0; i
< mChildProcesses
.Length(); ++i
) {
611 mozilla::Unused
<< mChildProcesses
[i
]->SendEndDragSession(
612 aDoneDrag
, mUserCancelled
, mEndDragPoint
, aKeyModifiers
, dropEffect
);
613 // Continue sending input events with input priority when stopping the dnd
615 mChildProcesses
[i
]->SetInputPriorityEventEnabled(true);
617 mChildProcesses
.Clear();
619 // mDataTransfer and the items it owns are going to die anyway, but we
620 // explicitly deref the contained data here so that we don't have to wait for
621 // CC to reclaim the memory.
622 if (XRE_IsParentProcess()) {
623 DiscardInternalTransferData();
627 mSessionIsSynthesizedForTests
= false;
628 mIsDraggingTextInTextControl
= false;
629 mEffectAllowedForTests
= nsIDragService::DRAGDROP_ACTION_UNINITIALIZED
;
630 mEndingSession
= false;
633 // release the source we've been holding on to.
634 mSourceDocument
= nullptr;
635 mSourceNode
= nullptr;
636 mSourceWindowContext
= nullptr;
637 mSourceTopWindowContext
= nullptr;
638 mTriggeringPrincipal
= nullptr;
640 mSelection
= nullptr;
641 mDataTransfer
= nullptr;
643 mUserCancelled
= false;
644 mDragPopup
= nullptr;
645 mDragStartData
= nullptr;
647 mImageOffset
= CSSIntPoint();
648 mScreenPosition
= CSSIntPoint();
649 mEndDragPoint
= LayoutDeviceIntPoint(0, 0);
650 mInputSource
= MouseEvent_Binding::MOZ_SOURCE_MOUSE
;
656 void nsBaseDragService::DiscardInternalTransferData() {
657 if (mDataTransfer
&& mSourceNode
) {
658 MOZ_ASSERT(mDataTransfer
);
660 DataTransferItemList
* items
= mDataTransfer
->Items();
661 for (size_t i
= 0; i
< items
->Length(); i
++) {
663 DataTransferItem
* item
= items
->IndexedGetter(i
, found
);
665 // Non-OTHER items may still be needed by JS. Skip them.
666 if (!found
|| item
->Kind() != DataTransferItem::KIND_OTHER
) {
670 nsCOMPtr
<nsIVariant
> variant
= item
->DataNoSecurityCheck();
671 nsCOMPtr
<nsIWritableVariant
> writable
= do_QueryInterface(variant
);
674 writable
->SetAsEmpty();
681 nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage
,
682 uint32_t aKeyModifiers
) {
683 if (!mSourceNode
|| !mSourceDocument
|| mSuppressLevel
) {
686 RefPtr
<PresShell
> presShell
= mSourceDocument
->GetPresShell();
691 RefPtr
<nsPresContext
> pc
= presShell
->GetPresContext();
692 nsCOMPtr
<nsIWidget
> widget
= pc
? pc
->GetRootWidget() : nullptr;
694 nsEventStatus status
= nsEventStatus_eIgnore
;
695 WidgetDragEvent
event(true, aEventMessage
, widget
);
696 event
.mFlags
.mIsSynthesizedForTests
= mSessionIsSynthesizedForTests
;
697 event
.mInputSource
= mInputSource
;
698 if (aEventMessage
== eDragEnd
) {
699 event
.mRefPoint
= mEndDragPoint
;
701 event
.mRefPoint
-= widget
->WidgetToTopLevelWidgetOffset();
703 event
.mUserCancelled
= mUserCancelled
;
705 event
.mModifiers
= aKeyModifiers
;
707 // Most drag events aren't able to converted to MouseEvent except to
708 // eDragStart and eDragEnd.
709 if (widget
&& event
.CanConvertToInputData()) {
710 // Send the drag event to APZ, which needs to know about them to be
711 // able to accurately detect the end of a drag gesture.
712 widget
->DispatchEventToAPZOnly(&event
);
715 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(mSourceNode
);
716 return presShell
->HandleDOMEventWithTarget(content
, &event
, &status
);
719 /* This is used by Windows and Mac to update the position of a popup being
720 * used as a drag image during the drag. This isn't used on GTK as it manages
721 * the drag popup itself.
724 nsBaseDragService::DragMoved(int32_t aX
, int32_t aY
) {
726 nsIFrame
* frame
= mDragPopup
->GetPrimaryFrame();
727 if (frame
&& frame
->IsMenuPopupFrame()) {
729 RoundedToInt(LayoutDeviceIntPoint(aX
, aY
) /
730 frame
->PresContext()->CSSToDevPixelScale()) -
732 static_cast<nsMenuPopupFrame
*>(frame
)->MoveTo(cssPos
, true);
739 static PresShell
* GetPresShellForContent(nsINode
* aDOMNode
) {
740 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(aDOMNode
);
741 if (!content
) return nullptr;
743 RefPtr
<Document
> document
= content
->GetComposedDoc();
745 document
->FlushPendingNotifications(FlushType::Display
);
746 return document
->GetPresShell();
752 nsresult
nsBaseDragService::DrawDrag(nsINode
* aDOMNode
,
753 const Maybe
<CSSIntRegion
>& aRegion
,
754 CSSIntPoint aScreenPosition
,
755 LayoutDeviceIntRect
* aScreenDragRect
,
756 RefPtr
<SourceSurface
>* aSurface
,
757 nsPresContext
** aPresContext
) {
759 *aPresContext
= nullptr;
761 // use a default size, in case of an error.
762 aScreenDragRect
->SetRect(aScreenPosition
.x
- mImageOffset
.x
,
763 aScreenPosition
.y
- mImageOffset
.y
, 1, 1);
765 // if a drag image was specified, use that, otherwise, use the source node
766 nsCOMPtr
<nsINode
> dragNode
= mImage
? mImage
.get() : aDOMNode
;
768 // get the presshell for the node being dragged. If the drag image is not in
769 // a document or has no frame, get the presshell from the source drag node
770 PresShell
* presShell
= GetPresShellForContent(dragNode
);
771 if (!presShell
&& mImage
) {
772 presShell
= GetPresShellForContent(aDOMNode
);
775 return NS_ERROR_FAILURE
;
778 *aPresContext
= presShell
->GetPresContext();
780 if (mDragStartData
) {
782 // Just clear the surface if chrome has overridden it with an image.
785 *aSurface
= mDragStartData
->TakeVisualization(aScreenDragRect
);
788 mDragStartData
= nullptr;
792 // convert mouse position to dev pixels of the prescontext
793 const CSSIntPoint screenPosition
= aScreenPosition
- mImageOffset
;
794 const auto screenPoint
= LayoutDeviceIntPoint::Round(
795 screenPosition
* (*aPresContext
)->CSSToDevPixelScale());
796 aScreenDragRect
->MoveTo(screenPoint
.x
, screenPoint
.y
);
798 // check if drag images are disabled
799 bool enableDragImages
= Preferences::GetBool(DRAGIMAGES_PREF
, true);
801 // didn't want an image, so just set the screen rectangle to the frame size
802 if (!enableDragImages
|| !mHasImage
) {
803 // This holds a quantity in RelativeTo{presShell->GetRootFrame(),
804 // ViewportType::Layout} space.
805 nsRect presLayoutRect
;
807 // if a region was specified, set the screen rectangle to the area that
808 // the region occupies
809 presLayoutRect
= ToAppUnits(aRegion
->GetBounds(), AppUnitsPerCSSPixel());
811 // otherwise, there was no region so just set the rectangle to
812 // the size of the primary frame of the content.
813 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
814 if (nsIFrame
* frame
= content
->GetPrimaryFrame()) {
815 presLayoutRect
= frame
->GetBoundingClientRect();
819 LayoutDeviceRect screenVisualRect
= ViewportUtils::ToScreenRelativeVisual(
820 LayoutDeviceRect::FromAppUnits(presLayoutRect
,
821 (*aPresContext
)->AppUnitsPerDevPixel()),
823 aScreenDragRect
->SizeTo(screenVisualRect
.Width(),
824 screenVisualRect
.Height());
828 // draw the image for selections
830 LayoutDeviceIntPoint
pnt(aScreenDragRect
->TopLeft());
831 *aSurface
= presShell
->RenderSelection(
832 mSelection
, pnt
, aScreenDragRect
,
833 mImage
? RenderImageFlags::None
: RenderImageFlags::AutoScale
);
837 // if a custom image was specified, check if it is an image node and draw
838 // using the source rather than the displayed image. But if mImage isn't
839 // an image or canvas, fall through to RenderNode below.
841 nsCOMPtr
<nsIContent
> content
= do_QueryInterface(dragNode
);
842 HTMLCanvasElement
* canvas
= HTMLCanvasElement::FromNodeOrNull(content
);
844 return DrawDragForImage(*aPresContext
, nullptr, canvas
, aScreenDragRect
,
848 nsCOMPtr
<nsIImageLoadingContent
> imageLoader
= do_QueryInterface(dragNode
);
849 // for image nodes, create the drag image from the actual image data
851 return DrawDragForImage(*aPresContext
, imageLoader
, nullptr,
852 aScreenDragRect
, aSurface
);
855 // If the image is a popup, use that as the image. This allows custom drag
856 // images that can change during the drag, but means that any platform
857 // default image handling won't occur.
858 // XXXndeakin this should be chrome-only
860 nsIFrame
* frame
= content
->GetPrimaryFrame();
861 if (frame
&& frame
->IsMenuPopupFrame()) {
862 mDragPopup
= content
->AsElement();
867 // otherwise, just draw the node
868 RenderImageFlags renderFlags
=
869 mImage
? RenderImageFlags::None
: RenderImageFlags::AutoScale
;
870 if (renderFlags
!= RenderImageFlags::None
) {
871 // check if the dragged node itself is an img element
872 if (dragNode
->NodeName().LowerCaseEqualsLiteral("img")) {
873 renderFlags
= renderFlags
| RenderImageFlags::IsImage
;
875 nsINodeList
* childList
= dragNode
->ChildNodes();
876 uint32_t length
= childList
->Length();
877 // check every childnode for being an img element
878 // XXXbz why don't we need to check descendants recursively?
879 for (uint32_t count
= 0; count
< length
; ++count
) {
880 if (childList
->Item(count
)->NodeName().LowerCaseEqualsLiteral(
882 // if the dragnode contains an image, set RenderImageFlags::IsImage
884 renderFlags
= renderFlags
| RenderImageFlags::IsImage
;
890 LayoutDeviceIntPoint
pnt(aScreenDragRect
->TopLeft());
891 *aSurface
= presShell
->RenderNode(dragNode
, aRegion
, pnt
, aScreenDragRect
,
895 // If an image was specified, reset the position from the offset that was
898 aScreenDragRect
->MoveTo(screenPoint
.x
, screenPoint
.y
);
904 nsresult
nsBaseDragService::DrawDragForImage(
905 nsPresContext
* aPresContext
, nsIImageLoadingContent
* aImageLoader
,
906 HTMLCanvasElement
* aCanvas
, LayoutDeviceIntRect
* aScreenDragRect
,
907 RefPtr
<SourceSurface
>* aSurface
) {
908 nsCOMPtr
<imgIContainer
> imgContainer
;
910 nsCOMPtr
<imgIRequest
> imgRequest
;
911 nsresult rv
= aImageLoader
->GetRequest(
912 nsIImageLoadingContent::CURRENT_REQUEST
, getter_AddRefs(imgRequest
));
913 NS_ENSURE_SUCCESS(rv
, rv
);
914 if (!imgRequest
) return NS_ERROR_NOT_AVAILABLE
;
916 rv
= imgRequest
->GetImage(getter_AddRefs(imgContainer
));
917 NS_ENSURE_SUCCESS(rv
, rv
);
918 if (!imgContainer
) return NS_ERROR_NOT_AVAILABLE
;
920 // use the size of the image as the size of the drag image
921 int32_t imageWidth
, imageHeight
;
922 rv
= imgContainer
->GetWidth(&imageWidth
);
923 NS_ENSURE_SUCCESS(rv
, rv
);
925 rv
= imgContainer
->GetHeight(&imageHeight
);
926 NS_ENSURE_SUCCESS(rv
, rv
);
928 aScreenDragRect
->SizeTo(aPresContext
->CSSPixelsToDevPixels(imageWidth
),
929 aPresContext
->CSSPixelsToDevPixels(imageHeight
));
931 // XXX The canvas size should be converted to dev pixels.
932 NS_ASSERTION(aCanvas
, "both image and canvas are null");
933 nsIntSize sz
= aCanvas
->GetSize();
934 aScreenDragRect
->SizeTo(sz
.width
, sz
.height
);
938 destSize
.width
= aScreenDragRect
->Width();
939 destSize
.height
= aScreenDragRect
->Height();
940 if (destSize
.width
== 0 || destSize
.height
== 0) return NS_ERROR_FAILURE
;
942 nsresult result
= NS_OK
;
944 RefPtr
<DrawTarget
> dt
=
945 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
946 destSize
, SurfaceFormat::B8G8R8A8
);
947 if (!dt
|| !dt
->IsValid()) return NS_ERROR_FAILURE
;
951 ImgDrawResult res
= imgContainer
->Draw(
952 &ctx
, destSize
, ImageRegion::Create(destSize
),
953 imgIContainer::FRAME_CURRENT
, SamplingFilter::GOOD
, SVGImageContext(),
954 imgIContainer::FLAG_SYNC_DECODE
, 1.0);
955 if (res
== ImgDrawResult::BAD_IMAGE
|| res
== ImgDrawResult::BAD_ARGS
||
956 res
== ImgDrawResult::NOT_SUPPORTED
) {
957 return NS_ERROR_FAILURE
;
959 *aSurface
= dt
->Snapshot();
961 *aSurface
= aCanvas
->GetSurfaceSnapshot();
968 nsBaseDragService::Suppress() {
969 EndDragSession(false, 0);
975 nsBaseDragService::Unsuppress() {
981 nsBaseDragService::UserCancelled() {
982 mUserCancelled
= true;
987 nsBaseDragService::UpdateDragEffect() {
988 mDragActionFromChildProcess
= mDragAction
;
993 nsBaseDragService::UpdateDragImage(nsINode
* aImage
, int32_t aImageX
,
995 // Don't change the image if this is a drag from another source or if there
997 if (!mSourceNode
|| mDragPopup
) return NS_OK
;
1000 mImageOffset
= CSSIntPoint(aImageX
, aImageY
);
1005 nsBaseDragService::DragEventDispatchedToChildProcess() {
1006 mDragEventDispatchedToChildProcess
= true;
1010 bool nsBaseDragService::MaybeAddChildProcess(
1011 mozilla::dom::ContentParent
* aChild
) {
1012 if (!mChildProcesses
.Contains(aChild
)) {
1013 mChildProcesses
.AppendElement(aChild
);
1019 bool nsBaseDragService::RemoveAllChildProcesses() {
1020 for (uint32_t c
= 0; c
< mChildProcesses
.Length(); c
++) {
1021 mozilla::Unused
<< mChildProcesses
[c
]->SendEndDragSession(
1022 true, false, LayoutDeviceIntPoint(), 0,
1023 nsIDragService::DRAGDROP_ACTION_NONE
);
1025 mChildProcesses
.Clear();
1029 bool nsBaseDragService::MustUpdateDataTransfer(EventMessage aMessage
) {
1034 nsBaseDragService::MaybeEditorDeletedSourceNode(Element
* aEditingHost
) {
1035 // If builtin editor of Blink and WebKit deletes the source node,they retarget
1036 // the source node to the editing host.
1037 // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/drag_controller.cc;l=724;drc=d9ba13b8cd8ac0faed7afc3d1f7e4b67ebac2a0b
1038 // That allows editor apps listens to "dragend" event in editing host or its
1039 // ancestors. Therefore, we should follow them for compatibility.
1040 if (mSourceNode
&& !mSourceNode
->IsInComposedDoc()) {
1041 mSourceNode
= aEditingHost
;