Backed out changeset b71c8c052463 (bug 1943846) for causing mass failures. CLOSED...
[gecko.git] / widget / MockDragServiceController.cpp
blob38add2d7071db6e06f96b6dc2409399049384957
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"
12 #include "nsIFrame.h"
13 #include "nsPresContext.h"
14 #include "nsBaseDragService.h"
16 namespace mozilla::test {
18 NS_IMPL_ISUPPORTS(MockDragServiceController, nsIMockDragServiceController)
20 class MockDragSession : public nsBaseDragSession {
21 protected:
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;
54 return NS_OK;
58 class MockDragService : public nsBaseDragService {
59 public:
60 NS_IMETHOD GetIsMockService(bool* aRet) override {
61 *aRet = true;
62 return NS_OK;
64 uint32_t mLastModifierKeyState = 0;
66 protected:
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;
86 switch (aEventType) {
87 case EventType::eMouseDown:
88 case EventType::eMouseMove:
89 case EventType::eMouseUp:
90 return true;
91 default:
92 return false;
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;
115 default:
116 MOZ_ASSERT(false, "Invalid event type");
117 return EventMessage::eVoidEvent;
121 MockDragServiceController::MockDragServiceController()
122 : mDragService(new MockDragService()) {}
124 MockDragServiceController::~MockDragServiceController() = default;
126 NS_IMETHODIMP
127 MockDragServiceController::GetMockDragService(nsIDragService** aService) {
128 RefPtr<nsIDragService> ds = mDragService;
129 ds.forget(aService);
130 return NS_OK;
133 NS_IMETHODIMP
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();
143 MOZ_ASSERT(presCxt);
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);
152 } else {
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);
174 nsresult rv;
175 switch (aEventType) {
176 case EventType::eMouseDown:
177 case EventType::eMouseMove:
178 case EventType::eMouseUp:
179 widget->DispatchInputEvent(widgetEvent.get());
180 break;
181 case EventType::eDragEnter:
182 NS_ENSURE_TRUE(currentDragSession, NS_ERROR_UNEXPECTED);
183 currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
184 widget->DispatchInputEvent(widgetEvent.get());
185 break;
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);
195 if (!sourceNode) {
196 rv = currentDragSession->EndDragSession(false /* doneDrag */,
197 aKeyModifiers);
198 NS_ENSURE_SUCCESS(rv, rv);
200 } break;
201 case EventType::eDragOver:
202 NS_ENSURE_TRUE(currentDragSession, NS_ERROR_UNEXPECTED);
203 rv = currentDragSession->FireDragEventAtSource(EventMessage::eDrag,
204 aKeyModifiers);
205 NS_ENSURE_SUCCESS(rv, rv);
206 currentDragSession->SetDragAction(nsIDragService::DRAGDROP_ACTION_MOVE);
207 widget->DispatchInputEvent(widgetEvent.get());
208 break;
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 */,
216 aKeyModifiers);
217 NS_ENSURE_SUCCESS(rv, rv);
218 } break;
219 default:
220 MOZ_ASSERT_UNREACHABLE("Impossible event type?");
221 return NS_ERROR_FAILURE;
223 return NS_OK;
226 NS_IMETHODIMP
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 */,
233 aKeyModifiers);
235 } // namespace mozilla::test