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 "MockDragServiceController.h"
7 #include "mozilla/dom/CanonicalBrowsingContext.h"
8 #include "mozilla/dom/Document.h"
9 #include "mozilla/dom/DragEvent.h"
10 #include "mozilla/MouseEvents.h"
11 #include "mozilla/SpinEventLoopUntil.h"
13 #include "nsPresContext.h"
14 #include "nsBaseDragService.h"
16 namespace mozilla::test
{
18 NS_IMPL_ISUPPORTS(MockDragServiceController
, nsIMockDragServiceController
)
20 class MockDragSession
: public nsBaseDragSession
{
22 MOZ_CAN_RUN_SCRIPT nsresult
23 InvokeDragSessionImpl(nsIWidget
* aWidget
, nsIArray
* aTransferableArray
,
24 const mozilla::Maybe
<mozilla::CSSIntRegion
>& aRegion
,
25 uint32_t aActionType
) override
{
26 // In Windows' nsDragService, InvokeDragSessionImpl would establish a
27 // nested (native) event loop that runs as long as the drag is happening.
28 // See DoDragDrop in MSDN.
29 // We cannot do anything like that here since it would block mochitest
30 // from scripting behavior with MockDragServiceController::SendDragEvent.
32 // mDragAction is not yet handled properly in the MockDragService.
33 // This should be updated with each drag event. Instead, we always MOVE.
35 // We still need to end the drag session on the source widget.
36 // In normal (non-mock) Gecko operation, this happens regardless
37 // of whether the drop/cancel happened over one of our widgets.
38 // For instance, Windows does this in StartInvokingDragSession, after
39 // DoDragDrop returns, gtk does this on eDragTaskSourceEnd and cocoa
40 // does this in -[NSDraggingSource draggingSession: endedAt: operation:].
42 // Here, we know the source and target are Gecko windows (the test
43 // framework does not support dragging to/from other apps). We could
44 // end the source drag session on drop and cancel, but sometimes we
45 // will want a dragleave to end the session (representing a drag cancel
46 // from Gecko) and other we times don't (like when dragging from one Gecko
47 // widget to another). Therefore, we instead rely on the test to tell
48 // us when to end the source session by calling
49 // MockDragServiceController::EndSourceDragSession().
50 // Note that, like in non-mocked DND, we do this regardless of whether
51 // the source and target were the same widget -- in that case,
52 // EndDragSession is just called twice.
53 mDragAction
= nsIDragService::DRAGDROP_ACTION_MOVE
;
58 class MockDragService
: public nsBaseDragService
{
60 NS_IMETHOD
GetIsMockService(bool* aRet
) override
{
64 uint32_t mLastModifierKeyState
= 0;
67 already_AddRefed
<nsIDragSession
> CreateDragSession() override
{
68 RefPtr
<nsIDragSession
> session
= new MockDragSession();
69 return session
.forget();
73 static void SetDragEndPointFromScreenPoint(
74 nsIDragSession
* aSession
, nsPresContext
* aPc
,
75 const LayoutDeviceIntPoint
& aScreenPt
) {
76 // aScreenPt is screen-relative, and we want to be
77 // top-level-widget-relative.
78 auto* widget
= aPc
->GetRootWidget();
79 auto pt
= aScreenPt
- widget
->WidgetToScreenOffset();
80 pt
+= widget
->WidgetToTopLevelWidgetOffset();
81 aSession
->SetDragEndPoint(pt
.x
, pt
.y
);
84 static bool IsMouseEvent(nsIMockDragServiceController::EventType aEventType
) {
85 using EventType
= nsIMockDragServiceController::EventType
;
87 case EventType::eMouseDown
:
88 case EventType::eMouseMove
:
89 case EventType::eMouseUp
:
96 static EventMessage
MockEventTypeToEventMessage(
97 nsIMockDragServiceController::EventType aEventType
) {
98 using EventType
= nsIMockDragServiceController::EventType
;
100 switch (aEventType
) {
101 case EventType::eDragEnter
:
102 return EventMessage::eDragEnter
;
103 case EventType::eDragOver
:
104 return EventMessage::eDragOver
;
105 case EventType::eDragExit
:
106 return EventMessage::eDragExit
;
107 case EventType::eDrop
:
108 return EventMessage::eDrop
;
109 case EventType::eMouseDown
:
110 return EventMessage::eMouseDown
;
111 case EventType::eMouseMove
:
112 return EventMessage::eMouseMove
;
113 case EventType::eMouseUp
:
114 return EventMessage::eMouseUp
;
116 MOZ_ASSERT(false, "Invalid event type");
117 return EventMessage::eVoidEvent
;
121 MockDragServiceController::MockDragServiceController()
122 : mDragService(new MockDragService()) {}
124 MockDragServiceController::~MockDragServiceController() = default;
127 MockDragServiceController::GetMockDragService(nsIDragService
** aService
) {
128 RefPtr
<nsIDragService
> ds
= mDragService
;
134 MockDragServiceController::SendEvent(
135 dom::BrowsingContext
* aBC
,
136 nsIMockDragServiceController::EventType aEventType
, int32_t aScreenX
,
137 int32_t aScreenY
, uint32_t aKeyModifiers
= 0) {
138 auto* embedder
= aBC
->Top()->GetEmbedderElement();
139 NS_ENSURE_TRUE(embedder
, NS_ERROR_UNEXPECTED
);
140 auto* frame
= embedder
->GetPrimaryFrame();
141 NS_ENSURE_TRUE(frame
, NS_ERROR_UNEXPECTED
);
142 auto* presCxt
= frame
->PresContext();
144 RefPtr
<nsIWidget
> widget
= nsContentUtils::WidgetForContent(embedder
);
145 NS_ENSURE_TRUE(widget
, NS_ERROR_UNEXPECTED
);
147 EventMessage eventType
= MockEventTypeToEventMessage(aEventType
);
148 UniquePtr
<WidgetInputEvent
> widgetEvent
;
149 if (IsMouseEvent(aEventType
)) {
150 widgetEvent
= MakeUnique
<WidgetMouseEvent
>(true, eventType
, widget
,
151 WidgetMouseEvent::Reason::eReal
);
153 widgetEvent
= MakeUnique
<WidgetDragEvent
>(true, eventType
, widget
);
156 widgetEvent
->mWidget
= widget
;
157 widgetEvent
->mFlags
.mIsSynthesizedForTests
= true;
159 auto clientPosInScreenCoords
= widget
->GetClientBounds().TopLeft();
160 widgetEvent
->mRefPoint
=
161 LayoutDeviceIntPoint(aScreenX
, aScreenY
) - clientPosInScreenCoords
;
163 RefPtr
<MockDragService
> ds
= mDragService
;
165 if (aEventType
== EventType::eDragEnter
) {
166 // We expect StartDragSession to return an "error" when a drag session
167 // already exists, which it will since we are testing dragging from
168 // inside Gecko, so we don't check the return value.
169 ds
->StartDragSession(widget
);
172 nsCOMPtr
<nsIDragSession
> currentDragSession
= ds
->GetCurrentSession(widget
);
175 switch (aEventType
) {
176 case EventType::eMouseDown
:
177 case EventType::eMouseMove
:
178 case EventType::eMouseUp
:
179 widget
->DispatchInputEvent(widgetEvent
.get());
181 case EventType::eDragEnter
:
182 NS_ENSURE_TRUE(currentDragSession
, NS_ERROR_UNEXPECTED
);
183 currentDragSession
->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE
);
184 widget
->DispatchInputEvent(widgetEvent
.get());
186 case EventType::eDragExit
: {
187 NS_ENSURE_TRUE(currentDragSession
, NS_ERROR_UNEXPECTED
);
188 currentDragSession
->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE
);
189 widget
->DispatchInputEvent(widgetEvent
.get());
190 SetDragEndPointFromScreenPoint(currentDragSession
, presCxt
,
191 LayoutDeviceIntPoint(aScreenX
, aScreenY
));
192 nsCOMPtr
<nsINode
> sourceNode
;
193 rv
= currentDragSession
->GetSourceNode(getter_AddRefs(sourceNode
));
194 NS_ENSURE_SUCCESS(rv
, rv
);
196 rv
= currentDragSession
->EndDragSession(false /* doneDrag */,
198 NS_ENSURE_SUCCESS(rv
, rv
);
201 case EventType::eDragOver
:
202 NS_ENSURE_TRUE(currentDragSession
, NS_ERROR_UNEXPECTED
);
203 rv
= currentDragSession
->FireDragEventAtSource(EventMessage::eDrag
,
205 NS_ENSURE_SUCCESS(rv
, rv
);
206 currentDragSession
->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE
);
207 widget
->DispatchInputEvent(widgetEvent
.get());
209 case EventType::eDrop
: {
210 NS_ENSURE_TRUE(currentDragSession
, NS_ERROR_UNEXPECTED
);
211 currentDragSession
->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE
);
212 widget
->DispatchInputEvent(widgetEvent
.get());
213 SetDragEndPointFromScreenPoint(currentDragSession
, presCxt
,
214 LayoutDeviceIntPoint(aScreenX
, aScreenY
));
215 rv
= currentDragSession
->EndDragSession(true /* doneDrag */,
217 NS_ENSURE_SUCCESS(rv
, rv
);
220 MOZ_ASSERT_UNREACHABLE("Impossible event type?");
221 return NS_ERROR_FAILURE
;
227 MockDragServiceController::CancelDrag(uint32_t aKeyModifiers
= 0) {
228 RefPtr
<MockDragService
> ds
= mDragService
;
229 nsCOMPtr
<nsIDragSession
> currentDragSession
;
230 ds
->GetCurrentSession(nullptr, getter_AddRefs(currentDragSession
));
231 MOZ_ASSERT(currentDragSession
);
232 return currentDragSession
->EndDragSession(false /* doneDrag */,
235 } // namespace mozilla::test