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 "nsIServiceManager.h"
10 #include "nsITransferable.h"
11 #include "nsSize.h"
12 #include "nsXPCOM.h"
13 #include "nsISupportsPrimitives.h"
14 #include "nsCOMPtr.h"
15 #include "nsIInterfaceRequestorUtils.h"
16 #include "nsIFrame.h"
17 #include "nsIDocument.h"
18 #include "nsIContent.h"
19 #include "nsIPresShell.h"
20 #include "nsViewManager.h"
21 #include "nsIDOMNode.h"
22 #include "nsIDOMDragEvent.h"
23 #include "nsISelection.h"
24 #include "nsISelectionPrivate.h"
25 #include "nsPresContext.h"
26 #include "nsIDOMDataTransfer.h"
27 #include "nsIImageLoadingContent.h"
28 #include "imgIContainer.h"
29 #include "imgIRequest.h"
30 #include "ImageRegion.h"
31 #include "nsRegion.h"
32 #include "nsXULPopupManager.h"
33 #include "nsMenuPopupFrame.h"
34 #include "SVGImageContext.h"
35 #include "mozilla/MouseEvents.h"
36 #include "mozilla/Preferences.h"
37 #include "mozilla/dom/DataTransferItemList.h"
38 #include "mozilla/gfx/2D.h"
39 #include "mozilla/Unused.h"
40 #include "nsFrameLoader.h"
41 #include "TabParent.h"
42 
43 #include "gfxContext.h"
44 #include "gfxPlatform.h"
45 #include <algorithm>
46 
47 using namespace mozilla;
48 using namespace mozilla::dom;
49 using namespace mozilla::gfx;
50 using namespace mozilla::image;
51 
52 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
53 
nsBaseDragService()54 nsBaseDragService::nsBaseDragService()
55     : mCanDrop(false),
56       mOnlyChromeDrop(false),
57       mDoingDrag(false),
58       mHasImage(false),
59       mUserCancelled(false),
60       mDragEventDispatchedToChildProcess(false),
61       mDragAction(DRAGDROP_ACTION_NONE),
62       mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED),
63       mTargetSize(0, 0),
64       mContentPolicyType(nsIContentPolicy::TYPE_OTHER),
65       mSuppressLevel(0),
66       mInputSource(nsIDOMMouseEvent::MOZ_SOURCE_MOUSE) {}
67 
68 nsBaseDragService::~nsBaseDragService() = default;
69 
NS_IMPL_ISUPPORTS(nsBaseDragService,nsIDragService,nsIDragSession)70 NS_IMPL_ISUPPORTS(nsBaseDragService, nsIDragService, nsIDragSession)
71 
72 //---------------------------------------------------------
73 NS_IMETHODIMP
74 nsBaseDragService::SetCanDrop(bool aCanDrop) {
75   mCanDrop = aCanDrop;
76   return NS_OK;
77 }
78 
79 //---------------------------------------------------------
80 NS_IMETHODIMP
GetCanDrop(bool * aCanDrop)81 nsBaseDragService::GetCanDrop(bool* aCanDrop) {
82   *aCanDrop = mCanDrop;
83   return NS_OK;
84 }
85 //---------------------------------------------------------
86 NS_IMETHODIMP
SetOnlyChromeDrop(bool aOnlyChrome)87 nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome) {
88   mOnlyChromeDrop = aOnlyChrome;
89   return NS_OK;
90 }
91 
92 //---------------------------------------------------------
93 NS_IMETHODIMP
GetOnlyChromeDrop(bool * aOnlyChrome)94 nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome) {
95   *aOnlyChrome = mOnlyChromeDrop;
96   return NS_OK;
97 }
98 
99 //---------------------------------------------------------
100 NS_IMETHODIMP
SetDragAction(uint32_t anAction)101 nsBaseDragService::SetDragAction(uint32_t anAction) {
102   mDragAction = anAction;
103   return NS_OK;
104 }
105 
106 //---------------------------------------------------------
107 NS_IMETHODIMP
GetDragAction(uint32_t * anAction)108 nsBaseDragService::GetDragAction(uint32_t* anAction) {
109   *anAction = mDragAction;
110   return NS_OK;
111 }
112 
113 //---------------------------------------------------------
114 NS_IMETHODIMP
SetTargetSize(nsSize aDragTargetSize)115 nsBaseDragService::SetTargetSize(nsSize aDragTargetSize) {
116   mTargetSize = aDragTargetSize;
117   return NS_OK;
118 }
119 
120 //---------------------------------------------------------
121 NS_IMETHODIMP
GetTargetSize(nsSize * aDragTargetSize)122 nsBaseDragService::GetTargetSize(nsSize* aDragTargetSize) {
123   *aDragTargetSize = mTargetSize;
124   return NS_OK;
125 }
126 
127 //-------------------------------------------------------------------------
128 
129 NS_IMETHODIMP
GetNumDropItems(uint32_t * aNumItems)130 nsBaseDragService::GetNumDropItems(uint32_t* aNumItems) {
131   *aNumItems = 0;
132   return NS_ERROR_FAILURE;
133 }
134 
135 //
136 // GetSourceDocument
137 //
138 // Returns the DOM document where the drag was initiated. This will be
139 // nullptr if the drag began outside of our application.
140 //
141 NS_IMETHODIMP
GetSourceDocument(nsIDOMDocument ** aSourceDocument)142 nsBaseDragService::GetSourceDocument(nsIDOMDocument** aSourceDocument) {
143   *aSourceDocument = mSourceDocument.get();
144   NS_IF_ADDREF(*aSourceDocument);
145 
146   return NS_OK;
147 }
148 
149 //
150 // GetSourceNode
151 //
152 // Returns the DOM node where the drag was initiated. This will be
153 // nullptr if the drag began outside of our application.
154 //
155 NS_IMETHODIMP
GetSourceNode(nsIDOMNode ** aSourceNode)156 nsBaseDragService::GetSourceNode(nsIDOMNode** aSourceNode) {
157   *aSourceNode = mSourceNode.get();
158   NS_IF_ADDREF(*aSourceNode);
159 
160   return NS_OK;
161 }
162 
163 NS_IMETHODIMP
GetTriggeringPrincipalURISpec(nsACString & aPrincipalURISpec)164 nsBaseDragService::GetTriggeringPrincipalURISpec(
165     nsACString& aPrincipalURISpec) {
166   aPrincipalURISpec = mTriggeringPrincipalURISpec;
167   return NS_OK;
168 }
169 
170 NS_IMETHODIMP
SetTriggeringPrincipalURISpec(const nsACString & aPrincipalURISpec)171 nsBaseDragService::SetTriggeringPrincipalURISpec(
172     const nsACString& aPrincipalURISpec) {
173   mTriggeringPrincipalURISpec = aPrincipalURISpec;
174   return NS_OK;
175 }
176 
177 //-------------------------------------------------------------------------
178 
179 NS_IMETHODIMP
GetData(nsITransferable * aTransferable,uint32_t aItemIndex)180 nsBaseDragService::GetData(nsITransferable* aTransferable,
181                            uint32_t aItemIndex) {
182   return NS_ERROR_FAILURE;
183 }
184 
185 //-------------------------------------------------------------------------
186 NS_IMETHODIMP
IsDataFlavorSupported(const char * aDataFlavor,bool * _retval)187 nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor,
188                                          bool* _retval) {
189   return NS_ERROR_FAILURE;
190 }
191 
192 NS_IMETHODIMP
GetDataTransfer(nsIDOMDataTransfer ** aDataTransfer)193 nsBaseDragService::GetDataTransfer(nsIDOMDataTransfer** aDataTransfer) {
194   *aDataTransfer = mDataTransfer;
195   NS_IF_ADDREF(*aDataTransfer);
196   return NS_OK;
197 }
198 
199 NS_IMETHODIMP
SetDataTransfer(nsIDOMDataTransfer * aDataTransfer)200 nsBaseDragService::SetDataTransfer(nsIDOMDataTransfer* aDataTransfer) {
201   mDataTransfer = aDataTransfer;
202   return NS_OK;
203 }
204 
205 //-------------------------------------------------------------------------
206 NS_IMETHODIMP
InvokeDragSession(nsIDOMNode * aDOMNode,const nsACString & aPrincipalURISpec,nsIArray * aTransferableArray,nsIScriptableRegion * aDragRgn,uint32_t aActionType,nsContentPolicyType aContentPolicyType=nsIContentPolicy::TYPE_OTHER)207 nsBaseDragService::InvokeDragSession(
208     nsIDOMNode* aDOMNode, const nsACString& aPrincipalURISpec,
209     nsIArray* aTransferableArray, nsIScriptableRegion* aDragRgn,
210     uint32_t aActionType,
211     nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) {
212   AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER);
213 
214   NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG);
215   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
216 
217   // stash the document of the dom node
218   nsCOMPtr<nsINode> node = do_QueryInterface(aDOMNode);
219   mSourceDocument = do_QueryInterface(node->OwnerDoc());
220   mTriggeringPrincipalURISpec.Assign(aPrincipalURISpec);
221   mSourceNode = aDOMNode;
222   mContentPolicyType = aContentPolicyType;
223   mEndDragPoint = LayoutDeviceIntPoint(0, 0);
224 
225   // When the mouse goes down, the selection code starts a mouse
226   // capture. However, this gets in the way of determining drag
227   // feedback for things like trees because the event coordinates
228   // are in the wrong coord system, so turn off mouse capture.
229   nsIPresShell::ClearMouseCapture(nullptr);
230 
231   nsresult rv =
232       InvokeDragSessionImpl(aTransferableArray, aDragRgn, aActionType);
233 
234   if (NS_FAILED(rv)) {
235     mSourceNode = nullptr;
236     mTriggeringPrincipalURISpec.Truncate(0);
237     mSourceDocument = nullptr;
238   }
239 
240   return rv;
241 }
242 
243 NS_IMETHODIMP
InvokeDragSessionWithImage(nsIDOMNode * aDOMNode,const nsACString & aPrincipalURISpec,nsIArray * aTransferableArray,nsIScriptableRegion * aRegion,uint32_t aActionType,nsIDOMNode * aImage,int32_t aImageX,int32_t aImageY,nsIDOMDragEvent * aDragEvent,nsIDOMDataTransfer * aDataTransfer)244 nsBaseDragService::InvokeDragSessionWithImage(
245     nsIDOMNode* aDOMNode, const nsACString& aPrincipalURISpec,
246     nsIArray* aTransferableArray, nsIScriptableRegion* aRegion,
247     uint32_t aActionType, nsIDOMNode* aImage, int32_t aImageX, int32_t aImageY,
248     nsIDOMDragEvent* aDragEvent, nsIDOMDataTransfer* aDataTransfer) {
249   NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
250   NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
251   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
252 
253   mDataTransfer = aDataTransfer;
254   mSelection = nullptr;
255   mHasImage = true;
256   mDragPopup = nullptr;
257   mImage = aImage;
258   mImageOffset = CSSIntPoint(aImageX, aImageY);
259 
260   aDragEvent->GetScreenX(&mScreenPosition.x);
261   aDragEvent->GetScreenY(&mScreenPosition.y);
262   aDragEvent->GetMozInputSource(&mInputSource);
263 
264   nsresult rv = InvokeDragSession(aDOMNode, aPrincipalURISpec,
265                                   aTransferableArray, aRegion, aActionType,
266                                   nsIContentPolicy::TYPE_INTERNAL_IMAGE);
267 
268   if (NS_FAILED(rv)) {
269     mImage = nullptr;
270     mHasImage = false;
271     mDataTransfer = nullptr;
272   }
273 
274   return rv;
275 }
276 
277 NS_IMETHODIMP
InvokeDragSessionWithSelection(nsISelection * aSelection,const nsACString & aPrincipalURISpec,nsIArray * aTransferableArray,uint32_t aActionType,nsIDOMDragEvent * aDragEvent,nsIDOMDataTransfer * aDataTransfer)278 nsBaseDragService::InvokeDragSessionWithSelection(
279     nsISelection* aSelection, const nsACString& aPrincipalURISpec,
280     nsIArray* aTransferableArray, uint32_t aActionType,
281     nsIDOMDragEvent* aDragEvent, nsIDOMDataTransfer* aDataTransfer) {
282   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
283   NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
284   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
285 
286   mDataTransfer = aDataTransfer;
287   mSelection = aSelection;
288   mHasImage = true;
289   mDragPopup = nullptr;
290   mImage = nullptr;
291   mImageOffset = CSSIntPoint();
292 
293   aDragEvent->GetScreenX(&mScreenPosition.x);
294   aDragEvent->GetScreenY(&mScreenPosition.y);
295   aDragEvent->GetMozInputSource(&mInputSource);
296 
297   // just get the focused node from the selection
298   // XXXndeakin this should actually be the deepest node that contains both
299   // endpoints of the selection
300   nsCOMPtr<nsIDOMNode> node;
301   aSelection->GetFocusNode(getter_AddRefs(node));
302 
303   nsresult rv =
304       InvokeDragSession(node, aPrincipalURISpec, aTransferableArray, nullptr,
305                         aActionType, nsIContentPolicy::TYPE_OTHER);
306 
307   if (NS_FAILED(rv)) {
308     mHasImage = false;
309     mSelection = nullptr;
310     mDataTransfer = nullptr;
311   }
312 
313   return rv;
314 }
315 
316 //-------------------------------------------------------------------------
317 NS_IMETHODIMP
GetCurrentSession(nsIDragSession ** aSession)318 nsBaseDragService::GetCurrentSession(nsIDragSession** aSession) {
319   if (!aSession) return NS_ERROR_INVALID_ARG;
320 
321   // "this" also implements a drag session, so say we are one but only
322   // if there is currently a drag going on.
323   if (!mSuppressLevel && mDoingDrag) {
324     *aSession = this;
325     NS_ADDREF(*aSession);  // addRef because we're a "getter"
326   } else
327     *aSession = nullptr;
328 
329   return NS_OK;
330 }
331 
332 //-------------------------------------------------------------------------
333 NS_IMETHODIMP
StartDragSession()334 nsBaseDragService::StartDragSession() {
335   if (mDoingDrag) {
336     return NS_ERROR_FAILURE;
337   }
338   mDoingDrag = true;
339   // By default dispatch drop also to content.
340   mOnlyChromeDrop = false;
341 
342   return NS_OK;
343 }
344 
OpenDragPopup()345 void nsBaseDragService::OpenDragPopup() {
346   if (mDragPopup) {
347     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
348     if (pm) {
349       pm->ShowPopupAtScreen(mDragPopup, mScreenPosition.x - mImageOffset.x,
350                             mScreenPosition.y - mImageOffset.y, false, nullptr);
351     }
352   }
353 }
354 
TakeChildProcessDragAction()355 int32_t nsBaseDragService::TakeChildProcessDragAction() {
356   // If the last event was dispatched to the child process, use the drag action
357   // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is
358   // returned otherwise.
359   int32_t retval = DRAGDROP_ACTION_UNINITIALIZED;
360   if (TakeDragEventDispatchedToChildProcess() &&
361       mDragActionFromChildProcess != DRAGDROP_ACTION_UNINITIALIZED) {
362     retval = mDragActionFromChildProcess;
363   }
364 
365   return retval;
366 }
367 
368 //-------------------------------------------------------------------------
369 NS_IMETHODIMP
EndDragSession(bool aDoneDrag,uint32_t aKeyModifiers)370 nsBaseDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) {
371   if (!mDoingDrag) {
372     return NS_ERROR_FAILURE;
373   }
374 
375   if (aDoneDrag && !mSuppressLevel) {
376     FireDragEventAtSource(eDragEnd, aKeyModifiers);
377   }
378 
379   if (mDragPopup) {
380     nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
381     if (pm) {
382       pm->HidePopup(mDragPopup, false, true, false, false);
383     }
384   }
385 
386   for (uint32_t i = 0; i < mChildProcesses.Length(); ++i) {
387     mozilla::Unused << mChildProcesses[i]->SendEndDragSession(
388         aDoneDrag, mUserCancelled, mEndDragPoint, aKeyModifiers);
389     // Continue sending input events with input priority when stopping the dnd
390     // session.
391     mChildProcesses[i]->SetInputPriorityEventEnabled(true);
392   }
393   mChildProcesses.Clear();
394 
395   // mDataTransfer and the items it owns are going to die anyway, but we
396   // explicitly deref the contained data here so that we don't have to wait for
397   // CC to reclaim the memory.
398   if (XRE_IsParentProcess()) {
399     DiscardInternalTransferData();
400   }
401 
402   mDoingDrag = false;
403   mCanDrop = false;
404 
405   // release the source we've been holding on to.
406   mSourceDocument = nullptr;
407   mSourceNode = nullptr;
408   mTriggeringPrincipalURISpec.Truncate(0);
409   mSelection = nullptr;
410   mDataTransfer = nullptr;
411   mHasImage = false;
412   mUserCancelled = false;
413   mDragPopup = nullptr;
414   mImage = nullptr;
415   mImageOffset = CSSIntPoint();
416   mScreenPosition = CSSIntPoint();
417   mEndDragPoint = LayoutDeviceIntPoint(0, 0);
418   mInputSource = nsIDOMMouseEvent::MOZ_SOURCE_MOUSE;
419 
420   return NS_OK;
421 }
422 
DiscardInternalTransferData()423 void nsBaseDragService::DiscardInternalTransferData() {
424   if (mDataTransfer && mSourceNode) {
425     MOZ_ASSERT(!!DataTransfer::Cast(mDataTransfer));
426 
427     DataTransferItemList* items = DataTransfer::Cast(mDataTransfer)->Items();
428     for (size_t i = 0; i < items->Length(); i++) {
429       bool found;
430       DataTransferItem* item = items->IndexedGetter(i, found);
431 
432       // Non-OTHER items may still be needed by JS. Skip them.
433       if (!found || item->Kind() != DataTransferItem::KIND_OTHER) {
434         continue;
435       }
436 
437       nsCOMPtr<nsIVariant> variant = item->DataNoSecurityCheck();
438       nsCOMPtr<nsIWritableVariant> writable = do_QueryInterface(variant);
439 
440       if (writable) {
441         writable->SetAsEmpty();
442       }
443     }
444   }
445 }
446 
447 NS_IMETHODIMP
FireDragEventAtSource(EventMessage aEventMessage,uint32_t aKeyModifiers)448 nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage,
449                                          uint32_t aKeyModifiers) {
450   if (mSourceNode && !mSuppressLevel) {
451     nsCOMPtr<nsIDocument> doc = do_QueryInterface(mSourceDocument);
452     if (doc) {
453       nsCOMPtr<nsIPresShell> presShell = doc->GetShell();
454       if (presShell) {
455         nsEventStatus status = nsEventStatus_eIgnore;
456         WidgetDragEvent event(true, aEventMessage, nullptr);
457         event.inputSource = mInputSource;
458         if (aEventMessage == eDragEnd) {
459           event.mRefPoint = mEndDragPoint;
460           event.mUserCancelled = mUserCancelled;
461         }
462         event.mModifiers = aKeyModifiers;
463         // Send the drag event to APZ, which needs to know about them to be
464         // able to accurately detect the end of a drag gesture.
465         if (nsPresContext* presContext = presShell->GetPresContext()) {
466           if (nsCOMPtr<nsIWidget> widget = presContext->GetRootWidget()) {
467             widget->DispatchEventToAPZOnly(&event);
468           }
469         }
470 
471         nsCOMPtr<nsIContent> content = do_QueryInterface(mSourceNode);
472         return presShell->HandleDOMEventWithTarget(content, &event, &status);
473       }
474     }
475   }
476 
477   return NS_OK;
478 }
479 
480 /* This is used by Windows and Mac to update the position of a popup being
481  * used as a drag image during the drag. This isn't used on GTK as it manages
482  * the drag popup itself.
483  */
484 NS_IMETHODIMP
DragMoved(int32_t aX,int32_t aY)485 nsBaseDragService::DragMoved(int32_t aX, int32_t aY) {
486   if (mDragPopup) {
487     nsIFrame* frame = mDragPopup->GetPrimaryFrame();
488     if (frame && frame->IsMenuPopupFrame()) {
489       CSSIntPoint cssPos =
490           RoundedToInt(LayoutDeviceIntPoint(aX, aY) /
491                        frame->PresContext()->CSSToDevPixelScale()) -
492           mImageOffset;
493       (static_cast<nsMenuPopupFrame*>(frame))->MoveTo(cssPos, true);
494     }
495   }
496 
497   return NS_OK;
498 }
499 
GetPresShellForContent(nsIDOMNode * aDOMNode)500 static nsIPresShell* GetPresShellForContent(nsIDOMNode* aDOMNode) {
501   nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
502   if (!content) return nullptr;
503 
504   nsCOMPtr<nsIDocument> document = content->GetUncomposedDoc();
505   if (document) {
506     document->FlushPendingNotifications(FlushType::Display);
507 
508     return document->GetShell();
509   }
510 
511   return nullptr;
512 }
513 
DrawDrag(nsIDOMNode * aDOMNode,nsIScriptableRegion * aRegion,CSSIntPoint aScreenPosition,LayoutDeviceIntRect * aScreenDragRect,RefPtr<SourceSurface> * aSurface,nsPresContext ** aPresContext)514 nsresult nsBaseDragService::DrawDrag(nsIDOMNode* aDOMNode,
515                                      nsIScriptableRegion* aRegion,
516                                      CSSIntPoint aScreenPosition,
517                                      LayoutDeviceIntRect* aScreenDragRect,
518                                      RefPtr<SourceSurface>* aSurface,
519                                      nsPresContext** aPresContext) {
520   *aSurface = nullptr;
521   *aPresContext = nullptr;
522 
523   // use a default size, in case of an error.
524   aScreenDragRect->SetRect(aScreenPosition.x - mImageOffset.x,
525                            aScreenPosition.y - mImageOffset.y, 1, 1);
526 
527   // if a drag image was specified, use that, otherwise, use the source node
528   nsCOMPtr<nsIDOMNode> dragNode = mImage ? mImage.get() : aDOMNode;
529 
530   // get the presshell for the node being dragged. If the drag image is not in
531   // a document or has no frame, get the presshell from the source drag node
532   nsIPresShell* presShell = GetPresShellForContent(dragNode);
533   if (!presShell && mImage) presShell = GetPresShellForContent(aDOMNode);
534   if (!presShell) return NS_ERROR_FAILURE;
535 
536   *aPresContext = presShell->GetPresContext();
537 
538   nsCOMPtr<nsIFrameLoaderOwner> flo = do_QueryInterface(dragNode);
539   if (flo) {
540     RefPtr<nsFrameLoader> fl = flo->GetFrameLoader();
541     if (fl) {
542       auto* tp = static_cast<mozilla::dom::TabParent*>(fl->GetRemoteBrowser());
543       if (tp && tp->TakeDragVisualization(*aSurface, aScreenDragRect)) {
544         if (mImage) {
545           // Just clear the surface if chrome has overridden it with an image.
546           *aSurface = nullptr;
547         }
548 
549         return NS_OK;
550       }
551     }
552   }
553 
554   // convert mouse position to dev pixels of the prescontext
555   CSSIntPoint screenPosition(aScreenPosition);
556   screenPosition.x -= mImageOffset.x;
557   screenPosition.y -= mImageOffset.y;
558   LayoutDeviceIntPoint screenPoint =
559       ConvertToUnscaledDevPixels(*aPresContext, screenPosition);
560   aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
561 
562   // check if drag images are disabled
563   bool enableDragImages = Preferences::GetBool(DRAGIMAGES_PREF, true);
564 
565   // didn't want an image, so just set the screen rectangle to the frame size
566   if (!enableDragImages || !mHasImage) {
567     // if a region was specified, set the screen rectangle to the area that
568     // the region occupies
569     CSSIntRect dragRect;
570     if (aRegion) {
571       // the region's coordinates are relative to the root frame
572       int32_t dragRectX, dragRectY, dragRectW, dragRectH;
573       aRegion->GetBoundingBox(&dragRectX, &dragRectY, &dragRectW, &dragRectH);
574       dragRect.SetRect(dragRectX, dragRectY, dragRectW, dragRectH);
575 
576       nsIFrame* rootFrame = presShell->GetRootFrame();
577       CSSIntRect screenRect = rootFrame->GetScreenRect();
578       dragRect.MoveBy(screenRect.TopLeft());
579     } else {
580       // otherwise, there was no region so just set the rectangle to
581       // the size of the primary frame of the content.
582       nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
583       nsIFrame* frame = content->GetPrimaryFrame();
584       if (frame) {
585         dragRect = frame->GetScreenRect();
586       }
587     }
588 
589     nsIntRect dragRectDev =
590         ToAppUnits(dragRect, nsPresContext::AppUnitsPerCSSPixel())
591             .ToOutsidePixels((*aPresContext)->AppUnitsPerDevPixel());
592     aScreenDragRect->SizeTo(dragRectDev.Width(), dragRectDev.Height());
593     return NS_OK;
594   }
595 
596   // draw the image for selections
597   if (mSelection) {
598     LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
599     *aSurface = presShell->RenderSelection(
600         mSelection, pnt, aScreenDragRect,
601         mImage ? 0 : nsIPresShell::RENDER_AUTO_SCALE);
602     return NS_OK;
603   }
604 
605   // if a custom image was specified, check if it is an image node and draw
606   // using the source rather than the displayed image. But if mImage isn't
607   // an image or canvas, fall through to RenderNode below.
608   if (mImage) {
609     nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
610     HTMLCanvasElement* canvas = HTMLCanvasElement::FromContentOrNull(content);
611     if (canvas) {
612       return DrawDragForImage(*aPresContext, nullptr, canvas, aScreenDragRect,
613                               aSurface);
614     }
615 
616     nsCOMPtr<nsIImageLoadingContent> imageLoader = do_QueryInterface(dragNode);
617     // for image nodes, create the drag image from the actual image data
618     if (imageLoader) {
619       return DrawDragForImage(*aPresContext, imageLoader, nullptr,
620                               aScreenDragRect, aSurface);
621     }
622 
623     // If the image is a popup, use that as the image. This allows custom drag
624     // images that can change during the drag, but means that any platform
625     // default image handling won't occur.
626     // XXXndeakin this should be chrome-only
627 
628     nsIFrame* frame = content->GetPrimaryFrame();
629     if (frame && frame->IsMenuPopupFrame()) {
630       mDragPopup = content;
631     }
632   }
633 
634   if (!mDragPopup) {
635     // otherwise, just draw the node
636     nsIntRegion clipRegion;
637     uint32_t renderFlags = mImage ? 0 : nsIPresShell::RENDER_AUTO_SCALE;
638     if (aRegion) {
639       aRegion->GetRegion(&clipRegion);
640     }
641 
642     if (renderFlags) {
643       nsCOMPtr<nsINode> dragINode = do_QueryInterface(dragNode);
644       // check if the dragged node itself is an img element
645       if (dragINode->NodeName().LowerCaseEqualsLiteral("img")) {
646         renderFlags = renderFlags | nsIPresShell::RENDER_IS_IMAGE;
647       } else {
648         nsINodeList* childList = dragINode->ChildNodes();
649         uint32_t length = childList->Length();
650         // check every childnode for being an img element
651         // XXXbz why don't we need to check descendants recursively?
652         for (uint32_t count = 0; count < length; ++count) {
653           if (childList->Item(count)->NodeName().LowerCaseEqualsLiteral(
654                   "img")) {
655             // if the dragnode contains an image, set RENDER_IS_IMAGE flag
656             renderFlags = renderFlags | nsIPresShell::RENDER_IS_IMAGE;
657             break;
658           }
659         }
660       }
661     }
662     LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft());
663     *aSurface = presShell->RenderNode(dragNode, aRegion ? &clipRegion : nullptr,
664                                       pnt, aScreenDragRect, renderFlags);
665   }
666 
667   // If an image was specified, reset the position from the offset that was
668   // supplied.
669   if (mImage) {
670     aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y);
671   }
672 
673   return NS_OK;
674 }
675 
DrawDragForImage(nsPresContext * aPresContext,nsIImageLoadingContent * aImageLoader,HTMLCanvasElement * aCanvas,LayoutDeviceIntRect * aScreenDragRect,RefPtr<SourceSurface> * aSurface)676 nsresult nsBaseDragService::DrawDragForImage(
677     nsPresContext* aPresContext, nsIImageLoadingContent* aImageLoader,
678     HTMLCanvasElement* aCanvas, LayoutDeviceIntRect* aScreenDragRect,
679     RefPtr<SourceSurface>* aSurface) {
680   nsCOMPtr<imgIContainer> imgContainer;
681   if (aImageLoader) {
682     nsCOMPtr<imgIRequest> imgRequest;
683     nsresult rv = aImageLoader->GetRequest(
684         nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest));
685     NS_ENSURE_SUCCESS(rv, rv);
686     if (!imgRequest) return NS_ERROR_NOT_AVAILABLE;
687 
688     rv = imgRequest->GetImage(getter_AddRefs(imgContainer));
689     NS_ENSURE_SUCCESS(rv, rv);
690     if (!imgContainer) return NS_ERROR_NOT_AVAILABLE;
691 
692     // use the size of the image as the size of the drag image
693     int32_t imageWidth, imageHeight;
694     rv = imgContainer->GetWidth(&imageWidth);
695     NS_ENSURE_SUCCESS(rv, rv);
696 
697     rv = imgContainer->GetHeight(&imageHeight);
698     NS_ENSURE_SUCCESS(rv, rv);
699 
700     aScreenDragRect->SizeTo(aPresContext->CSSPixelsToDevPixels(imageWidth),
701                             aPresContext->CSSPixelsToDevPixels(imageHeight));
702   } else {
703     // XXX The canvas size should be converted to dev pixels.
704     NS_ASSERTION(aCanvas, "both image and canvas are null");
705     nsIntSize sz = aCanvas->GetSize();
706     aScreenDragRect->SizeTo(sz.width, sz.height);
707   }
708 
709   nsIntSize destSize;
710   destSize.width = aScreenDragRect->Width();
711   destSize.height = aScreenDragRect->Height();
712   if (destSize.width == 0 || destSize.height == 0) return NS_ERROR_FAILURE;
713 
714   nsresult result = NS_OK;
715   if (aImageLoader) {
716     RefPtr<DrawTarget> dt =
717         gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget(
718             destSize, SurfaceFormat::B8G8R8A8);
719     if (!dt || !dt->IsValid()) return NS_ERROR_FAILURE;
720 
721     RefPtr<gfxContext> ctx = gfxContext::CreateOrNull(dt);
722     if (!ctx) return NS_ERROR_FAILURE;
723 
724     ImgDrawResult res =
725         imgContainer->Draw(ctx, destSize, ImageRegion::Create(destSize),
726                            imgIContainer::FRAME_CURRENT, SamplingFilter::GOOD,
727                            /* no SVGImageContext */ Nothing(),
728                            imgIContainer::FLAG_SYNC_DECODE, 1.0);
729     if (res == ImgDrawResult::BAD_IMAGE || res == ImgDrawResult::BAD_ARGS) {
730       return NS_ERROR_FAILURE;
731     }
732     *aSurface = dt->Snapshot();
733   } else {
734     *aSurface = aCanvas->GetSurfaceSnapshot();
735   }
736 
737   return result;
738 }
739 
ConvertToUnscaledDevPixels(nsPresContext * aPresContext,CSSIntPoint aScreenPosition)740 LayoutDeviceIntPoint nsBaseDragService::ConvertToUnscaledDevPixels(
741     nsPresContext* aPresContext, CSSIntPoint aScreenPosition) {
742   int32_t adj =
743       aPresContext->DeviceContext()->AppUnitsPerDevPixelAtUnitFullZoom();
744   return LayoutDeviceIntPoint(
745       nsPresContext::CSSPixelsToAppUnits(aScreenPosition.x) / adj,
746       nsPresContext::CSSPixelsToAppUnits(aScreenPosition.y) / adj);
747 }
748 
749 NS_IMETHODIMP
Suppress()750 nsBaseDragService::Suppress() {
751   EndDragSession(false, 0);
752   ++mSuppressLevel;
753   return NS_OK;
754 }
755 
756 NS_IMETHODIMP
Unsuppress()757 nsBaseDragService::Unsuppress() {
758   --mSuppressLevel;
759   return NS_OK;
760 }
761 
762 NS_IMETHODIMP
UserCancelled()763 nsBaseDragService::UserCancelled() {
764   mUserCancelled = true;
765   return NS_OK;
766 }
767 
768 NS_IMETHODIMP
UpdateDragEffect()769 nsBaseDragService::UpdateDragEffect() {
770   mDragActionFromChildProcess = mDragAction;
771   return NS_OK;
772 }
773 
774 NS_IMETHODIMP
UpdateDragImage(nsIDOMNode * aImage,int32_t aImageX,int32_t aImageY)775 nsBaseDragService::UpdateDragImage(nsIDOMNode* aImage, int32_t aImageX,
776                                    int32_t aImageY) {
777   // Don't change the image if this is a drag from another source or if there
778   // is a drag popup.
779   if (!mSourceNode || mDragPopup) return NS_OK;
780 
781   mImage = aImage;
782   mImageOffset = CSSIntPoint(aImageX, aImageY);
783   return NS_OK;
784 }
785 
786 NS_IMETHODIMP
DragEventDispatchedToChildProcess()787 nsBaseDragService::DragEventDispatchedToChildProcess() {
788   mDragEventDispatchedToChildProcess = true;
789   return NS_OK;
790 }
791 
MaybeAddChildProcess(mozilla::dom::ContentParent * aChild)792 bool nsBaseDragService::MaybeAddChildProcess(
793     mozilla::dom::ContentParent* aChild) {
794   if (!mChildProcesses.Contains(aChild)) {
795     mChildProcesses.AppendElement(aChild);
796     return true;
797   }
798   return false;
799 }
800