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