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