Bug 1606095 [wpt PR 20924] - [LayoutInstability] Fix flaky tests, a=testonly
[gecko.git] / widget / nsBaseDragService.cpp
blob52e57b8768ca8265a910c54070286eca95bd1b65
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"
11 #include "nsSize.h"
12 #include "nsXPCOM.h"
13 #include "nsCOMPtr.h"
14 #include "nsIInterfaceRequestorUtils.h"
15 #include "nsIFrame.h"
16 #include "nsFrameLoaderOwner.h"
17 #include "mozilla/dom/Document.h"
18 #include "nsIContent.h"
19 #include "nsViewManager.h"
20 #include "nsINode.h"
21 #include "nsPresContext.h"
22 #include "nsIImageLoadingContent.h"
23 #include "imgIContainer.h"
24 #include "imgIRequest.h"
25 #include "ImageRegion.h"
26 #include "nsQueryObject.h"
27 #include "nsRegion.h"
28 #include "nsXULPopupManager.h"
29 #include "nsMenuPopupFrame.h"
30 #include "SVGImageContext.h"
31 #ifdef MOZ_XUL
32 # include "nsTreeBodyFrame.h"
33 #endif
34 #include "mozilla/MouseEvents.h"
35 #include "mozilla/Preferences.h"
36 #include "mozilla/PresShell.h"
37 #include "mozilla/dom/BindingDeclarations.h"
38 #include "mozilla/dom/DataTransferItemList.h"
39 #include "mozilla/dom/DataTransfer.h"
40 #include "mozilla/dom/DocumentInlines.h"
41 #include "mozilla/dom/DragEvent.h"
42 #include "mozilla/dom/MouseEventBinding.h"
43 #include "mozilla/dom/Selection.h"
44 #include "mozilla/gfx/2D.h"
45 #include "mozilla/Unused.h"
46 #include "nsFrameLoader.h"
47 #include "BrowserParent.h"
48 #include "nsIMutableArray.h"
49 #include "gfxContext.h"
50 #include "gfxPlatform.h"
51 #include <algorithm>
53 using namespace mozilla;
54 using namespace mozilla::dom;
55 using namespace mozilla::gfx;
56 using namespace mozilla::image;
58 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
60 nsBaseDragService::nsBaseDragService()
61 : mCanDrop(false),
62 mOnlyChromeDrop(false),
63 mDoingDrag(false),
64 mSessionIsSynthesizedForTests(false),
65 mEndingSession(false),
66 mHasImage(false),
67 mUserCancelled(false),
68 mDragEventDispatchedToChildProcess(false),
69 mDragAction(DRAGDROP_ACTION_NONE),
70 mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED),
71 mEffectAllowedForTests(DRAGDROP_ACTION_UNINITIALIZED),
72 mContentPolicyType(nsIContentPolicy::TYPE_OTHER),
73 mSuppressLevel(0),
74 mInputSource(MouseEvent_Binding::MOZ_SOURCE_MOUSE) {}
76 nsBaseDragService::~nsBaseDragService() = default;
78 NS_IMPL_ISUPPORTS(nsBaseDragService, nsIDragService, nsIDragSession)
80 //---------------------------------------------------------
81 NS_IMETHODIMP
82 nsBaseDragService::SetCanDrop(bool aCanDrop) {
83 mCanDrop = aCanDrop;
84 return NS_OK;
87 //---------------------------------------------------------
88 NS_IMETHODIMP
89 nsBaseDragService::GetCanDrop(bool* aCanDrop) {
90 *aCanDrop = mCanDrop;
91 return NS_OK;
93 //---------------------------------------------------------
94 NS_IMETHODIMP
95 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome) {
96 mOnlyChromeDrop = aOnlyChrome;
97 return NS_OK;
100 //---------------------------------------------------------
101 NS_IMETHODIMP
102 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome) {
103 *aOnlyChrome = mOnlyChromeDrop;
104 return NS_OK;
107 //---------------------------------------------------------
108 NS_IMETHODIMP
109 nsBaseDragService::SetDragAction(uint32_t anAction) {
110 mDragAction = anAction;
111 return NS_OK;
114 //---------------------------------------------------------
115 NS_IMETHODIMP
116 nsBaseDragService::GetDragAction(uint32_t* anAction) {
117 *anAction = mDragAction;
118 return NS_OK;
121 //-------------------------------------------------------------------------
123 NS_IMETHODIMP
124 nsBaseDragService::GetNumDropItems(uint32_t* aNumItems) {
125 *aNumItems = 0;
126 return NS_ERROR_FAILURE;
130 // GetSourceDocument
132 // Returns the DOM document where the drag was initiated. This will be
133 // nullptr if the drag began outside of our application.
135 NS_IMETHODIMP
136 nsBaseDragService::GetSourceDocument(Document** aSourceDocument) {
137 *aSourceDocument = mSourceDocument.get();
138 NS_IF_ADDREF(*aSourceDocument);
140 return NS_OK;
144 // GetSourceNode
146 // Returns the DOM node where the drag was initiated. This will be
147 // nullptr if the drag began outside of our application.
149 NS_IMETHODIMP
150 nsBaseDragService::GetSourceNode(nsINode** aSourceNode) {
151 *aSourceNode = do_AddRef(mSourceNode).take();
152 return NS_OK;
155 NS_IMETHODIMP
156 nsBaseDragService::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) {
157 NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal);
158 return NS_OK;
161 NS_IMETHODIMP
162 nsBaseDragService::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) {
163 mTriggeringPrincipal = aPrincipal;
164 return NS_OK;
167 NS_IMETHODIMP
168 nsBaseDragService::GetCsp(nsIContentSecurityPolicy** aCsp) {
169 NS_IF_ADDREF(*aCsp = mCsp);
170 return NS_OK;
173 NS_IMETHODIMP
174 nsBaseDragService::SetCsp(nsIContentSecurityPolicy* aCsp) {
175 mCsp = aCsp;
176 return NS_OK;
179 //-------------------------------------------------------------------------
181 NS_IMETHODIMP
182 nsBaseDragService::GetData(nsITransferable* aTransferable,
183 uint32_t aItemIndex) {
184 return NS_ERROR_FAILURE;
187 //-------------------------------------------------------------------------
188 NS_IMETHODIMP
189 nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor,
190 bool* _retval) {
191 return NS_ERROR_FAILURE;
194 NS_IMETHODIMP
195 nsBaseDragService::GetDataTransferXPCOM(DataTransfer** aDataTransfer) {
196 *aDataTransfer = mDataTransfer;
197 NS_IF_ADDREF(*aDataTransfer);
198 return NS_OK;
201 NS_IMETHODIMP
202 nsBaseDragService::SetDataTransferXPCOM(DataTransfer* aDataTransfer) {
203 NS_ENSURE_STATE(aDataTransfer);
204 mDataTransfer = aDataTransfer;
205 return NS_OK;
208 DataTransfer* nsBaseDragService::GetDataTransfer() { return mDataTransfer; }
210 void nsBaseDragService::SetDataTransfer(DataTransfer* aDataTransfer) {
211 mDataTransfer = aDataTransfer;
214 bool nsBaseDragService::IsSynthesizedForTests() {
215 return mSessionIsSynthesizedForTests;
218 uint32_t nsBaseDragService::GetEffectAllowedForTests() {
219 MOZ_ASSERT(mSessionIsSynthesizedForTests);
220 return mEffectAllowedForTests;
223 NS_IMETHODIMP nsBaseDragService::SetDragEndPointForTests(int32_t aScreenX,
224 int32_t aScreenY) {
225 MOZ_ASSERT(mDoingDrag);
226 MOZ_ASSERT(mSourceDocument);
227 MOZ_ASSERT(mSessionIsSynthesizedForTests);
228 if (!mDoingDrag || !mSourceDocument || !mSessionIsSynthesizedForTests) {
229 return NS_ERROR_FAILURE;
231 nsPresContext* presContext = mSourceDocument->GetPresContext();
232 if (NS_WARN_IF(!presContext)) {
233 return NS_ERROR_FAILURE;
235 SetDragEndPoint(
236 LayoutDeviceIntPoint(presContext->CSSPixelsToDevPixels(aScreenX),
237 presContext->CSSPixelsToDevPixels(aScreenY)));
238 return NS_OK;
241 //-------------------------------------------------------------------------
242 NS_IMETHODIMP
243 nsBaseDragService::InvokeDragSession(
244 nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
245 nsIArray* aTransferableArray, uint32_t aActionType,
246 nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
247 AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER);
249 NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG);
250 NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
252 // stash the document of the dom node
253 mSourceDocument = aDOMNode->OwnerDoc();
254 mTriggeringPrincipal = aPrincipal;
255 mCsp = aCsp;
256 mSourceNode = aDOMNode;
257 mContentPolicyType = aContentPolicyType;
258 mEndDragPoint = LayoutDeviceIntPoint(0, 0);
260 // When the mouse goes down, the selection code starts a mouse
261 // capture. However, this gets in the way of determining drag
262 // feedback for things like trees because the event coordinates
263 // are in the wrong coord system, so turn off mouse capture.
264 PresShell::ClearMouseCapture(nullptr);
266 if (mSessionIsSynthesizedForTests) {
267 mDoingDrag = true;
268 mDragAction = aActionType;
269 mEffectAllowedForTests = aActionType;
270 return NS_OK;
273 // If you're hitting this, a test is causing the browser to attempt to enter
274 // the drag-drop native nested event loop, which will put the browser in a
275 // state that won't run tests properly until there's manual intervention
276 // to exit the drag-drop loop (either by moving the mouse or hitting escape),
277 // which can't be done from script since we're in the nested loop.
279 // The best way to avoid this is to catch the dragstart event on the item
280 // being dragged, and then to call preventDefault() and stopPropagating() on
281 // it.
282 if (XRE_IsParentProcess()) {
283 MOZ_ASSERT(
284 !xpc::IsInAutomation(),
285 "About to start drag-drop native loop on which will prevent later "
286 "tests from running properly.");
289 uint32_t length = 0;
290 mozilla::Unused << aTransferableArray->GetLength(&length);
291 if (!length) {
292 nsCOMPtr<nsIMutableArray> mutableArray =
293 do_QueryInterface(aTransferableArray);
294 if (mutableArray) {
295 // In order to be able trigger dnd, we need to have some transferable
296 // object.
297 nsCOMPtr<nsITransferable> trans =
298 do_CreateInstance("@mozilla.org/widget/transferable;1");
299 trans->Init(nullptr);
300 trans->SetRequestingPrincipal(mSourceNode->NodePrincipal());
301 trans->SetContentPolicyType(mContentPolicyType);
302 mutableArray->AppendElement(trans);
304 } else {
305 for (uint32_t i = 0; i < length; ++i) {
306 nsCOMPtr<nsITransferable> trans =
307 do_QueryElementAt(aTransferableArray, i);
308 if (trans) {
309 // Set the requestingPrincipal on the transferable.
310 trans->SetRequestingPrincipal(mSourceNode->NodePrincipal());
311 trans->SetContentPolicyType(mContentPolicyType);
316 nsresult rv = InvokeDragSessionImpl(aTransferableArray, mRegion, aActionType);
318 if (NS_FAILED(rv)) {
319 // Set mDoingDrag so that EndDragSession cleans up and sends the dragend
320 // event after the aborted drag.
321 mDoingDrag = true;
322 EndDragSession(true, 0);
325 return rv;
328 NS_IMETHODIMP
329 nsBaseDragService::InvokeDragSessionWithImage(
330 nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
331 nsIArray* aTransferableArray, uint32_t aActionType, nsINode* aImage,
332 int32_t aImageX, int32_t aImageY, DragEvent* aDragEvent,
333 DataTransfer* aDataTransfer) {
334 NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
335 NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
336 NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
338 mSessionIsSynthesizedForTests =
339 aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
340 mDataTransfer = aDataTransfer;
341 mSelection = nullptr;
342 mHasImage = true;
343 mDragPopup = nullptr;
344 mImage = aImage;
345 mImageOffset = CSSIntPoint(aImageX, aImageY);
346 mDragStartData = nullptr;
348 mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
349 mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
350 mInputSource = aDragEvent->MozInputSource();
352 // If dragging within a XUL tree and no custom drag image was
353 // set, the region argument to InvokeDragSessionWithImage needs
354 // to be set to the area encompassing the selected rows of the
355 // tree to ensure that the drag feedback gets clipped to those
356 // rows. For other content, region should be null.
357 mRegion = Nothing();
358 #ifdef MOZ_XUL
359 if (aDOMNode && aDOMNode->IsContent() && !aImage) {
360 if (aDOMNode->NodeInfo()->Equals(nsGkAtoms::treechildren,
361 kNameSpaceID_XUL)) {
362 nsTreeBodyFrame* treeBody =
363 do_QueryFrame(aDOMNode->AsContent()->GetPrimaryFrame());
364 if (treeBody) {
365 mRegion = treeBody->GetSelectionRegion();
369 #endif
371 nsresult rv =
372 InvokeDragSession(aDOMNode, aPrincipal, aCsp, aTransferableArray,
373 aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE);
374 mRegion = Nothing();
375 return rv;
378 NS_IMETHODIMP
379 nsBaseDragService::InvokeDragSessionWithRemoteImage(
380 nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp,
381 nsIArray* aTransferableArray, uint32_t aActionType,
382 RemoteDragStartData* aDragStartData, DragEvent* aDragEvent,
383 DataTransfer* aDataTransfer) {
384 NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
385 NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
386 NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
388 mSessionIsSynthesizedForTests =
389 aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
390 mDataTransfer = aDataTransfer;
391 mSelection = nullptr;
392 mHasImage = true;
393 mDragPopup = nullptr;
394 mImage = nullptr;
395 mDragStartData = aDragStartData;
396 mImageOffset = CSSIntPoint(0, 0);
398 mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
399 mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
400 mInputSource = aDragEvent->MozInputSource();
402 nsresult rv =
403 InvokeDragSession(aDOMNode, aPrincipal, aCsp, aTransferableArray,
404 aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE);
405 mRegion = Nothing();
406 return rv;
409 NS_IMETHODIMP
410 nsBaseDragService::InvokeDragSessionWithSelection(
411 Selection* aSelection, nsIPrincipal* aPrincipal,
412 nsIContentSecurityPolicy* aCsp, nsIArray* aTransferableArray,
413 uint32_t aActionType, DragEvent* aDragEvent, DataTransfer* aDataTransfer) {
414 NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
415 NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
416 NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
418 mSessionIsSynthesizedForTests =
419 aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests;
420 mDataTransfer = aDataTransfer;
421 mSelection = aSelection;
422 mHasImage = true;
423 mDragPopup = nullptr;
424 mImage = nullptr;
425 mImageOffset = CSSIntPoint();
426 mDragStartData = nullptr;
427 mRegion = Nothing();
429 mScreenPosition.x = aDragEvent->ScreenX(CallerType::System);
430 mScreenPosition.y = aDragEvent->ScreenY(CallerType::System);
431 mInputSource = aDragEvent->MozInputSource();
433 // just get the focused node from the selection
434 // XXXndeakin this should actually be the deepest node that contains both
435 // endpoints of the selection
436 nsCOMPtr<nsINode> node = aSelection->GetFocusNode();
438 return InvokeDragSession(node, aPrincipal, aCsp, aTransferableArray,
439 aActionType, nsIContentPolicy::TYPE_OTHER);
442 //-------------------------------------------------------------------------
443 NS_IMETHODIMP
444 nsBaseDragService::GetCurrentSession(nsIDragSession** aSession) {
445 if (!aSession) return NS_ERROR_INVALID_ARG;
447 // "this" also implements a drag session, so say we are one but only
448 // if there is currently a drag going on.
449 if (!mSuppressLevel && mDoingDrag) {
450 *aSession = this;
451 NS_ADDREF(*aSession); // addRef because we're a "getter"
452 } else
453 *aSession = nullptr;
455 return NS_OK;
458 //-------------------------------------------------------------------------
459 NS_IMETHODIMP
460 nsBaseDragService::StartDragSession() {
461 if (mDoingDrag) {
462 return NS_ERROR_FAILURE;
464 mDoingDrag = true;
465 // By default dispatch drop also to content.
466 mOnlyChromeDrop = false;
468 return NS_OK;
471 NS_IMETHODIMP nsBaseDragService::StartDragSessionForTests(
472 uint32_t aAllowedEffect) {
473 if (NS_WARN_IF(NS_FAILED(StartDragSession()))) {
474 return NS_ERROR_FAILURE;
476 mDragAction = aAllowedEffect;
477 mEffectAllowedForTests = aAllowedEffect;
478 mSessionIsSynthesizedForTests = true;
479 return NS_OK;
482 void nsBaseDragService::OpenDragPopup() {
483 if (mDragPopup) {
484 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
485 if (pm) {
486 pm->ShowPopupAtScreen(mDragPopup, mScreenPosition.x - mImageOffset.x,
487 mScreenPosition.y - mImageOffset.y, false, nullptr);
492 int32_t nsBaseDragService::TakeChildProcessDragAction() {
493 // If the last event was dispatched to the child process, use the drag action
494 // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is
495 // returned otherwise.
496 int32_t retval = DRAGDROP_ACTION_UNINITIALIZED;
497 if (TakeDragEventDispatchedToChildProcess() &&
498 mDragActionFromChildProcess != DRAGDROP_ACTION_UNINITIALIZED) {
499 retval = mDragActionFromChildProcess;
502 return retval;
505 //-------------------------------------------------------------------------
506 NS_IMETHODIMP
507 nsBaseDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
508 if (!mDoingDrag || mEndingSession) {
509 return NS_ERROR_FAILURE;
512 mEndingSession = true;
514 if (aDoneDrag && !mSuppressLevel) {
515 FireDragEventAtSource(eDragEnd, aKeyModifiers);
518 if (mDragPopup) {
519 nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
520 if (pm) {
521 pm->HidePopup(mDragPopup, false, true, false, false);
525 for (uint32_t i = 0; i < mChildProcesses.Length(); ++i) {
526 mozilla::Unused << mChildProcesses[i]->SendEndDragSession(
527 aDoneDrag, mUserCancelled, mEndDragPoint, aKeyModifiers);
528 // Continue sending input events with input priority when stopping the dnd
529 // session.
530 mChildProcesses[i]->SetInputPriorityEventEnabled(true);
532 mChildProcesses.Clear();
534 // mDataTransfer and the items it owns are going to die anyway, but we
535 // explicitly deref the contained data here so that we don't have to wait for
536 // CC to reclaim the memory.
537 if (XRE_IsParentProcess()) {
538 DiscardInternalTransferData();
541 mDoingDrag = false;
542 mSessionIsSynthesizedForTests = false;
543 mEffectAllowedForTests = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED;
544 mEndingSession = false;
545 mCanDrop = false;
547 // release the source we've been holding on to.
548 mSourceDocument = nullptr;
549 mSourceNode = nullptr;
550 mTriggeringPrincipal = nullptr;
551 mCsp = nullptr;
552 mSelection = nullptr;
553 mDataTransfer = nullptr;
554 mHasImage = false;
555 mUserCancelled = false;
556 mDragPopup = nullptr;
557 mDragStartData = nullptr;
558 mImage = nullptr;
559 mImageOffset = CSSIntPoint();
560 mScreenPosition = CSSIntPoint();
561 mEndDragPoint = LayoutDeviceIntPoint(0, 0);
562 mInputSource = MouseEvent_Binding::MOZ_SOURCE_MOUSE;
563 mRegion = Nothing();
565 return NS_OK;
568 void nsBaseDragService::DiscardInternalTransferData() {
569 if (mDataTransfer && mSourceNode) {
570 MOZ_ASSERT(mDataTransfer);
572 DataTransferItemList* items = mDataTransfer->Items();
573 for (size_t i = 0; i < items->Length(); i++) {
574 bool found;
575 DataTransferItem* item = items->IndexedGetter(i, found);
577 // Non-OTHER items may still be needed by JS. Skip them.
578 if (!found || item->Kind() != DataTransferItem::KIND_OTHER) {
579 continue;
582 nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck();
583 nsCOMPtr<nsIWritableVariant> writable = do_QueryInterface(variant);
585 if (writable) {
586 writable->SetAsEmpty();
592 NS_IMETHODIMP
593 nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage,
594 uint32_t aKeyModifiers) {
595 if (mSourceNode && mSourceDocument && !mSuppressLevel) {
596 RefPtr<PresShell> presShell = mSourceDocument->GetPresShell();
597 if (presShell) {
598 nsEventStatus status = nsEventStatus_eIgnore;
599 WidgetDragEvent event(true, aEventMessage, nullptr);
600 event.mFlags.mIsSynthesizedForTests = mSessionIsSynthesizedForTests;
601 event.mInputSource = mInputSource;
602 if (aEventMessage == eDragEnd) {
603 event.mRefPoint = mEndDragPoint;
604 event.mUserCancelled = mUserCancelled;
606 event.mModifiers = aKeyModifiers;
607 // Send the drag event to APZ, which needs to know about them to be
608 // able to accurately detect the end of a drag gesture.
609 if (nsPresContext* presContext = presShell->GetPresContext()) {
610 if (nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget()) {
611 widget->DispatchEventToAPZOnly(&event);
615 nsCOMPtr<nsIContent> content = do_QueryInterface(mSourceNode);
616 return presShell->HandleDOMEventWithTarget(content, &event, &status);
620 return NS_OK;
623 /* This is used by Windows and Mac to update the position of a popup being
624 * used as a drag image during the drag. This isn't used on GTK as it manages
625 * the drag popup itself.
627 NS_IMETHODIMP
628 nsBaseDragService::DragMoved(int32_t aX, int32_t aY) {
629 if (mDragPopup) {
630 nsIFrame* frame = mDragPopup->GetPrimaryFrame();
631 if (frame && frame->IsMenuPopupFrame()) {
632 CSSIntPoint cssPos =
633 RoundedToInt(LayoutDeviceIntPoint(aX, aY) /
634 frame->PresContext()->CSSToDevPixelScale()) -
635 mImageOffset;
636 (static_cast<nsMenuPopupFrame*>(frame))->MoveTo(cssPos, true);
640 return NS_OK;
643 static PresShell* GetPresShellForContent(nsINode* aDOMNode) {
644 nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
645 if (!content) return nullptr;
647 RefPtr<Document> document = content->GetComposedDoc();
648 if (document) {
649 document->FlushPendingNotifications(FlushType::Display);
650 return document->GetPresShell();
653 return nullptr;
656 nsresult nsBaseDragService::DrawDrag(nsINode* aDOMNode,
657 const Maybe<CSSIntRegion>& aRegion,
658 CSSIntPoint aScreenPosition,
659 LayoutDeviceIntRect* aScreenDragRect,
660 RefPtr<SourceSurface>* aSurface,
661 nsPresContext** aPresContext) {
662 *aSurface = nullptr;
663 *aPresContext = nullptr;
665 // use a default size, in case of an error.
666 aScreenDragRect->SetRect(aScreenPosition.x - mImageOffset.x,
667 aScreenPosition.y - mImageOffset.y, 1, 1);
669 // if a drag image was specified, use that, otherwise, use the source node
670 nsCOMPtr<nsINode> dragNode = mImage ? mImage.get() : aDOMNode;
672 // get the presshell for the node being dragged. If the drag image is not in
673 // a document or has no frame, get the presshell from the source drag node
674 PresShell* presShell = GetPresShellForContent(dragNode);
675 if (!presShell && mImage) {
676 presShell = GetPresShellForContent(aDOMNode);
678 if (!presShell) {
679 return NS_ERROR_FAILURE;
682 *aPresContext = presShell->GetPresContext();
684 if (mDragStartData) {
685 if (mImage) {
686 // Just clear the surface if chrome has overridden it with an image.
687 *aSurface = nullptr;
688 } else {
689 *aSurface = mDragStartData->TakeVisualization(aScreenDragRect);
692 mDragStartData = nullptr;
693 return NS_OK;
696 // convert mouse position to dev pixels of the prescontext
697 CSSIntPoint screenPosition(aScreenPosition);
698 screenPosition.x -= mImageOffset.x;
699 screenPosition.y -= mImageOffset.y;
700 LayoutDeviceIntPoint screenPoint =
701 ConvertToUnscaledDevPixels(*aPresContext, screenPosition);
702 aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
704 // check if drag images are disabled
705 bool enableDragImages = Preferences::GetBool(DRAGIMAGES_PREF, true);
707 // didn't want an image, so just set the screen rectangle to the frame size
708 if (!enableDragImages || !mHasImage) {
709 // if a region was specified, set the screen rectangle to the area that
710 // the region occupies
711 CSSIntRect dragRect;
712 if (aRegion) {
713 // the region's coordinates are relative to the root frame
714 dragRect = aRegion->GetBounds();
716 nsIFrame* rootFrame = presShell->GetRootFrame();
717 CSSIntRect screenRect = rootFrame->GetScreenRect();
718 dragRect.MoveBy(screenRect.TopLeft());
719 } else {
720 // otherwise, there was no region so just set the rectangle to
721 // the size of the primary frame of the content.
722 nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
723 nsIFrame* frame = content->GetPrimaryFrame();
724 if (frame) {
725 dragRect = frame->GetScreenRect();
729 nsIntRect dragRectDev =
730 ToAppUnits(dragRect, AppUnitsPerCSSPixel())
731 .ToOutsidePixels((*aPresContext)->AppUnitsPerDevPixel());
732 aScreenDragRect->SizeTo(dragRectDev.Width(), dragRectDev.Height());
733 return NS_OK;
736 // draw the image for selections
737 if (mSelection) {
738 LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
739 *aSurface = presShell->RenderSelection(
740 mSelection, pnt, aScreenDragRect,
741 mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale);
742 return NS_OK;
745 // if a custom image was specified, check if it is an image node and draw
746 // using the source rather than the displayed image. But if mImage isn't
747 // an image or canvas, fall through to RenderNode below.
748 if (mImage) {
749 nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
750 HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(content);
751 if (canvas) {
752 return DrawDragForImage(*aPresContext, nullptr, canvas, aScreenDragRect,
753 aSurface);
756 nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(dragNode);
757 // for image nodes, create the drag image from the actual image data
758 if (imageLoader) {
759 return DrawDragForImage(*aPresContext, imageLoader, nullptr,
760 aScreenDragRect, aSurface);
763 // If the image is a popup, use that as the image. This allows custom drag
764 // images that can change during the drag, but means that any platform
765 // default image handling won't occur.
766 // XXXndeakin this should be chrome-only
768 nsIFrame* frame = content->GetPrimaryFrame();
769 if (frame && frame->IsMenuPopupFrame()) {
770 mDragPopup = content;
774 if (!mDragPopup) {
775 // otherwise, just draw the node
776 RenderImageFlags renderFlags =
777 mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale;
778 if (renderFlags != RenderImageFlags::None) {
779 // check if the dragged node itself is an img element
780 if (dragNode->NodeName().LowerCaseEqualsLiteral("img")) {
781 renderFlags = renderFlags | RenderImageFlags::IsImage;
782 } else {
783 nsINodeList* childList = dragNode->ChildNodes();
784 uint32_t length = childList->Length();
785 // check every childnode for being an img element
786 // XXXbz why don't we need to check descendants recursively?
787 for (uint32_t count = 0; count < length; ++count) {
788 if (childList->Item(count)->NodeName().LowerCaseEqualsLiteral(
789 "img")) {
790 // if the dragnode contains an image, set RenderImageFlags::IsImage
791 // flag
792 renderFlags = renderFlags | RenderImageFlags::IsImage;
793 break;
798 LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
799 *aSurface = presShell->RenderNode(dragNode, aRegion, pnt, aScreenDragRect,
800 renderFlags);
803 // If an image was specified, reset the position from the offset that was
804 // supplied.
805 if (mImage) {
806 aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
809 return NS_OK;
812 nsresult nsBaseDragService::DrawDragForImage(
813 nsPresContext* aPresContext, nsIImageLoadingContent* aImageLoader,
814 HTMLCanvasElement* aCanvas, LayoutDeviceIntRect* aScreenDragRect,
815 RefPtr<SourceSurface>* aSurface) {
816 nsCOMPtr<imgIContainer> imgContainer;
817 if (aImageLoader) {
818 nsCOMPtr<imgIRequest> imgRequest;
819 nsresult rv = aImageLoader->GetRequest(
820 nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest));
821 NS_ENSURE_SUCCESS(rv, rv);
822 if (!imgRequest) return NS_ERROR_NOT_AVAILABLE;
824 rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
825 NS_ENSURE_SUCCESS(rv, rv);
826 if (!imgContainer) return NS_ERROR_NOT_AVAILABLE;
828 // use the size of the image as the size of the drag image
829 int32_t imageWidth, imageHeight;
830 rv = imgContainer->GetWidth(&imageWidth);
831 NS_ENSURE_SUCCESS(rv, rv);
833 rv = imgContainer->GetHeight(&imageHeight);
834 NS_ENSURE_SUCCESS(rv, rv);
836 aScreenDragRect->SizeTo(aPresContext->CSSPixelsToDevPixels(imageWidth),
837 aPresContext->CSSPixelsToDevPixels(imageHeight));
838 } else {
839 // XXX The canvas size should be converted to dev pixels.
840 NS_ASSERTION(aCanvas, "both image and canvas are null");
841 nsIntSize sz = aCanvas->GetSize();
842 aScreenDragRect->SizeTo(sz.width, sz.height);
845 nsIntSize destSize;
846 destSize.width = aScreenDragRect->Width();
847 destSize.height = aScreenDragRect->Height();
848 if (destSize.width == 0 || destSize.height == 0) return NS_ERROR_FAILURE;
850 nsresult result = NS_OK;
851 if (aImageLoader) {
852 RefPtr<DrawTarget> dt =
853 gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
854 destSize, SurfaceFormat::B8G8R8A8);
855 if (!dt || !dt->IsValid()) return NS_ERROR_FAILURE;
857 RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
858 if (!ctx) return NS_ERROR_FAILURE;
860 ImgDrawResult res =
861 imgContainer->Draw(ctx, destSize, ImageRegion::Create(destSize),
862 imgIContainer::FRAME_CURRENT, SamplingFilter::GOOD,
863 /* no SVGImageContext */ Nothing(),
864 imgIContainer::FLAG_SYNC_DECODE, 1.0);
865 if (res == ImgDrawResult::BAD_IMAGE || res == ImgDrawResult::BAD_ARGS ||
866 res == ImgDrawResult::NOT_SUPPORTED) {
867 return NS_ERROR_FAILURE;
869 *aSurface = dt->Snapshot();
870 } else {
871 *aSurface = aCanvas->GetSurfaceSnapshot();
874 return result;
877 LayoutDeviceIntPoint nsBaseDragService::ConvertToUnscaledDevPixels(
878 nsPresContext* aPresContext, CSSIntPoint aScreenPosition) {
879 int32_t adj =
880 aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
881 return LayoutDeviceIntPoint(
882 nsPresContext::CSSPixelsToAppUnits(aScreenPosition.x) / adj,
883 nsPresContext::CSSPixelsToAppUnits(aScreenPosition.y) / adj);
886 NS_IMETHODIMP
887 nsBaseDragService::Suppress() {
888 EndDragSession(false, 0);
889 ++mSuppressLevel;
890 return NS_OK;
893 NS_IMETHODIMP
894 nsBaseDragService::Unsuppress() {
895 --mSuppressLevel;
896 return NS_OK;
899 NS_IMETHODIMP
900 nsBaseDragService::UserCancelled() {
901 mUserCancelled = true;
902 return NS_OK;
905 NS_IMETHODIMP
906 nsBaseDragService::UpdateDragEffect() {
907 mDragActionFromChildProcess = mDragAction;
908 return NS_OK;
911 NS_IMETHODIMP
912 nsBaseDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX,
913 int32_t aImageY) {
914 // Don't change the image if this is a drag from another source or if there
915 // is a drag popup.
916 if (!mSourceNode || mDragPopup) return NS_OK;
918 mImage = aImage;
919 mImageOffset = CSSIntPoint(aImageX, aImageY);
920 return NS_OK;
923 NS_IMETHODIMP
924 nsBaseDragService::DragEventDispatchedToChildProcess() {
925 mDragEventDispatchedToChildProcess = true;
926 return NS_OK;
929 bool nsBaseDragService::MaybeAddChildProcess(
930 mozilla::dom::ContentParent* aChild) {
931 if (!mChildProcesses.Contains(aChild)) {
932 mChildProcesses.AppendElement(aChild);
933 return true;
935 return false;
938 bool nsBaseDragService::RemoveAllChildProcesses() {
939 for (uint32_t c = 0; c < mChildProcesses.Length(); c++) {
940 mozilla::Unused << mChildProcesses[c]->SendEndDragSession(
941 true, false, LayoutDeviceIntPoint(), 0);
943 mChildProcesses.Clear();
944 return true;