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