/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ #include "nsBaseDragService.h" #include "nsITransferable.h" #include "nsArrayUtils.h" #include "nsITransferable.h" #include "nsSize.h" #include "nsXPCOM.h" #include "nsCOMPtr.h" #include "nsIInterfaceRequestorUtils.h" #include "nsIFrame.h" #include "nsFrameLoaderOwner.h" #include "nsIContent.h" #include "nsViewManager.h" #include "nsINode.h" #include "nsPresContext.h" #include "nsIImageLoadingContent.h" #include "imgIContainer.h" #include "imgIRequest.h" #include "ImageRegion.h" #include "nsQueryObject.h" #include "nsRegion.h" #include "nsXULPopupManager.h" #include "nsMenuPopupFrame.h" #include "nsTreeBodyFrame.h" #include "mozilla/MouseEvents.h" #include "mozilla/Preferences.h" #include "mozilla/PresShell.h" #include "mozilla/ProfilerLabels.h" #include "mozilla/SVGImageContext.h" #include "mozilla/TextControlElement.h" #include "mozilla/Unused.h" #include "mozilla/ViewportUtils.h" #include "mozilla/dom/BindingDeclarations.h" #include "mozilla/dom/DataTransferItemList.h" #include "mozilla/dom/DataTransfer.h" #include "mozilla/dom/Document.h" #include "mozilla/dom/DocumentInlines.h" #include "mozilla/dom/DragEvent.h" #include "mozilla/dom/MouseEventBinding.h" #include "mozilla/dom/Selection.h" #include "mozilla/gfx/2D.h" #include "nsFrameLoader.h" #include "BrowserParent.h" #include "nsIMutableArray.h" #include "gfxContext.h" #include "gfxPlatform.h" #include "nscore.h" #include using namespace mozilla; using namespace mozilla::dom; using namespace mozilla::gfx; using namespace mozilla::image; LazyLogModule sWidgetDragServiceLog("WidgetDragService"); #define DRAGIMAGES_PREF "nglayout.enable_drag_images" nsBaseDragService::nsBaseDragService() : mCanDrop(false), mOnlyChromeDrop(false), mDoingDrag(false), mSessionIsSynthesizedForTests(false), mIsDraggingTextInTextControl(false), mEndingSession(false), mHasImage(false), mUserCancelled(false), mDragEventDispatchedToChildProcess(false), mDragAction(DRAGDROP_ACTION_NONE), mDragActionFromChildProcess(DRAGDROP_ACTION_UNINITIALIZED), mEffectAllowedForTests(DRAGDROP_ACTION_UNINITIALIZED), mContentPolicyType(nsIContentPolicy::TYPE_OTHER), mSuppressLevel(0), mInputSource(MouseEvent_Binding::MOZ_SOURCE_MOUSE) {} nsBaseDragService::~nsBaseDragService() = default; NS_IMPL_ISUPPORTS(nsBaseDragService, nsIDragService, nsIDragSession) //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::SetCanDrop(bool aCanDrop) { mCanDrop = aCanDrop; return NS_OK; } //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetCanDrop(bool* aCanDrop) { *aCanDrop = mCanDrop; return NS_OK; } //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::SetOnlyChromeDrop(bool aOnlyChrome) { mOnlyChromeDrop = aOnlyChrome; return NS_OK; } //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetOnlyChromeDrop(bool* aOnlyChrome) { *aOnlyChrome = mOnlyChromeDrop; return NS_OK; } //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::SetDragAction(uint32_t anAction) { mDragAction = anAction; return NS_OK; } //--------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetDragAction(uint32_t* anAction) { *anAction = mDragAction; return NS_OK; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetNumDropItems(uint32_t* aNumItems) { *aNumItems = 0; return NS_ERROR_FAILURE; } // // GetSourceWindowContext // // Returns the window context where the drag was initiated. This will be // nullptr if the drag began outside of our application. // NS_IMETHODIMP nsBaseDragService::GetSourceWindowContext( WindowContext** aSourceWindowContext) { *aSourceWindowContext = mSourceWindowContext.get(); NS_IF_ADDREF(*aSourceWindowContext); return NS_OK; } NS_IMETHODIMP nsBaseDragService::SetSourceWindowContext(WindowContext* aSourceWindowContext) { // This should only be called in a child process. MOZ_ASSERT(!XRE_IsParentProcess()); mSourceWindowContext = aSourceWindowContext; return NS_OK; } // // GetSourceTopWindowContext // // Returns the top-level window context where the drag was initiated. This will // be nullptr if the drag began outside of our application. // NS_IMETHODIMP nsBaseDragService::GetSourceTopWindowContext( WindowContext** aSourceTopWindowContext) { *aSourceTopWindowContext = mSourceTopWindowContext.get(); NS_IF_ADDREF(*aSourceTopWindowContext); return NS_OK; } NS_IMETHODIMP nsBaseDragService::SetSourceTopWindowContext( WindowContext* aSourceTopWindowContext) { // This should only be called in a child process. MOZ_ASSERT(!XRE_IsParentProcess()); mSourceTopWindowContext = aSourceTopWindowContext; return NS_OK; } // // GetSourceNode // // Returns the DOM node where the drag was initiated. This will be // nullptr if the drag began outside of our application. // NS_IMETHODIMP nsBaseDragService::GetSourceNode(nsINode** aSourceNode) { *aSourceNode = do_AddRef(mSourceNode).take(); return NS_OK; } void nsBaseDragService::UpdateSource(nsINode* aNewSourceNode, Selection* aNewSelection) { MOZ_ASSERT(mSourceNode); MOZ_ASSERT(aNewSourceNode); MOZ_ASSERT(mSourceNode->IsInNativeAnonymousSubtree() || aNewSourceNode->IsInNativeAnonymousSubtree()); MOZ_ASSERT(mSourceDocument == aNewSourceNode->OwnerDoc()); mSourceNode = aNewSourceNode; // Don't set mSelection if the session was invoked without selection or // making it becomes nullptr. The latter occurs when the old frame is // being destroyed. if (mSelection && aNewSelection) { // XXX If the dragging image is created once (e.g., at drag start), the // image won't be updated unless we notify `DrawDrag` callers. // However, it must be okay for now to keep using older image of // Selection. mSelection = aNewSelection; } } NS_IMETHODIMP nsBaseDragService::GetTriggeringPrincipal(nsIPrincipal** aPrincipal) { NS_IF_ADDREF(*aPrincipal = mTriggeringPrincipal); return NS_OK; } NS_IMETHODIMP nsBaseDragService::SetTriggeringPrincipal(nsIPrincipal* aPrincipal) { mTriggeringPrincipal = aPrincipal; return NS_OK; } NS_IMETHODIMP nsBaseDragService::GetCsp(nsIContentSecurityPolicy** aCsp) { NS_IF_ADDREF(*aCsp = mCsp); return NS_OK; } NS_IMETHODIMP nsBaseDragService::SetCsp(nsIContentSecurityPolicy* aCsp) { mCsp = aCsp; return NS_OK; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetData(nsITransferable* aTransferable, uint32_t aItemIndex) { return NS_ERROR_FAILURE; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::IsDataFlavorSupported(const char* aDataFlavor, bool* _retval) { return NS_ERROR_FAILURE; } NS_IMETHODIMP nsBaseDragService::GetDataTransferXPCOM(DataTransfer** aDataTransfer) { *aDataTransfer = mDataTransfer; NS_IF_ADDREF(*aDataTransfer); return NS_OK; } NS_IMETHODIMP nsBaseDragService::SetDataTransferXPCOM(DataTransfer* aDataTransfer) { NS_ENSURE_STATE(aDataTransfer); mDataTransfer = aDataTransfer; return NS_OK; } DataTransfer* nsBaseDragService::GetDataTransfer() { return mDataTransfer; } void nsBaseDragService::SetDataTransfer(DataTransfer* aDataTransfer) { mDataTransfer = aDataTransfer; } bool nsBaseDragService::IsSynthesizedForTests() { return mSessionIsSynthesizedForTests; } bool nsBaseDragService::IsDraggingTextInTextControl() { return mIsDraggingTextInTextControl; } uint32_t nsBaseDragService::GetEffectAllowedForTests() { MOZ_ASSERT(mSessionIsSynthesizedForTests); return mEffectAllowedForTests; } NS_IMETHODIMP nsBaseDragService::SetDragEndPointForTests(int32_t aScreenX, int32_t aScreenY) { MOZ_ASSERT(mDoingDrag); MOZ_ASSERT(mSourceDocument); MOZ_ASSERT(mSessionIsSynthesizedForTests); if (!mDoingDrag || !mSourceDocument || !mSessionIsSynthesizedForTests) { return NS_ERROR_FAILURE; } nsPresContext* pc = mSourceDocument->GetPresContext(); if (NS_WARN_IF(!pc)) { return NS_ERROR_FAILURE; } auto p = LayoutDeviceIntPoint::Round(CSSIntPoint(aScreenX, aScreenY) * pc->CSSToDevPixelScale()); // p is screen-relative, and we want them to be top-level-widget-relative. if (nsCOMPtr widget = pc->GetRootWidget()) { p -= widget->WidgetToScreenOffset(); p += widget->WidgetToTopLevelWidgetOffset(); } SetDragEndPoint(p); return NS_OK; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::InvokeDragSession( nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray, uint32_t aActionType, nsContentPolicyType aContentPolicyType = nsIContentPolicy::TYPE_OTHER) { AUTO_PROFILER_LABEL("nsBaseDragService::InvokeDragSession", OTHER); NS_ENSURE_TRUE(aDOMNode, NS_ERROR_INVALID_ARG); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); // stash the document of the dom node mSourceDocument = aDOMNode->OwnerDoc(); mTriggeringPrincipal = aPrincipal; mCsp = aCsp; mSourceNode = aDOMNode; mIsDraggingTextInTextControl = mSourceNode->IsInNativeAnonymousSubtree() && TextControlElement::FromNodeOrNull( mSourceNode->GetClosestNativeAnonymousSubtreeRootParentOrHost()); mContentPolicyType = aContentPolicyType; mEndDragPoint = LayoutDeviceIntPoint(0, 0); // When the mouse goes down, the selection code starts a mouse // capture. However, this gets in the way of determining drag // feedback for things like trees because the event coordinates // are in the wrong coord system, so turn off mouse capture. PresShell::ClearMouseCapture(); if (mSessionIsSynthesizedForTests) { mDoingDrag = true; mDragAction = aActionType; mEffectAllowedForTests = aActionType; return NS_OK; } // If you're hitting this, a test is causing the browser to attempt to enter // the drag-drop native nested event loop, which will put the browser in a // state that won't run tests properly until there's manual intervention // to exit the drag-drop loop (either by moving the mouse or hitting escape), // which can't be done from script since we're in the nested loop. // // The best way to avoid this is to catch the dragstart event on the item // being dragged, and then to call preventDefault() and stopPropagating() on // it. if (XRE_IsParentProcess()) { MOZ_ASSERT( !xpc::IsInAutomation(), "About to start drag-drop native loop on which will prevent later " "tests from running properly."); } uint32_t length = 0; mozilla::Unused << aTransferableArray->GetLength(&length); if (!length) { nsCOMPtr mutableArray = do_QueryInterface(aTransferableArray); if (mutableArray) { // In order to be able trigger dnd, we need to have some transferable // object. nsCOMPtr trans = do_CreateInstance("@mozilla.org/widget/transferable;1"); trans->Init(nullptr); trans->SetRequestingPrincipal(mSourceNode->NodePrincipal()); trans->SetContentPolicyType(mContentPolicyType); trans->SetCookieJarSettings(aCookieJarSettings); mutableArray->AppendElement(trans); } } else { for (uint32_t i = 0; i < length; ++i) { nsCOMPtr trans = do_QueryElementAt(aTransferableArray, i); if (trans) { // Set the requestingPrincipal on the transferable. trans->SetRequestingPrincipal(mSourceNode->NodePrincipal()); trans->SetContentPolicyType(mContentPolicyType); trans->SetCookieJarSettings(aCookieJarSettings); } } } nsresult rv = InvokeDragSessionImpl(aTransferableArray, mRegion, aActionType); if (NS_FAILED(rv)) { // Set mDoingDrag so that EndDragSession cleans up and sends the dragend // event after the aborted drag. mDoingDrag = true; EndDragSession(true, 0); } return rv; } NS_IMETHODIMP nsBaseDragService::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) { NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); mSessionIsSynthesizedForTests = aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests; mDataTransfer = aDataTransfer; mSelection = nullptr; mHasImage = true; mDragPopup = nullptr; mImage = aImage; mImageOffset = CSSIntPoint(aImageX, aImageY); mDragStartData = nullptr; mSourceWindowContext = aDOMNode ? aDOMNode->OwnerDoc()->GetWindowContext() : nullptr; mSourceTopWindowContext = mSourceWindowContext ? mSourceWindowContext->TopWindowContext() : nullptr; mScreenPosition = aDragEvent->ScreenPoint(CallerType::System); mInputSource = aDragEvent->InputSource(); // If dragging within a XUL tree and no custom drag image was // set, the region argument to InvokeDragSessionWithImage needs // to be set to the area encompassing the selected rows of the // tree to ensure that the drag feedback gets clipped to those // rows. For other content, region should be null. mRegion = Nothing(); if (aDOMNode && aDOMNode->IsContent() && !aImage) { if (aDOMNode->NodeInfo()->Equals(nsGkAtoms::treechildren, kNameSpaceID_XUL)) { nsTreeBodyFrame* treeBody = do_QueryFrame(aDOMNode->AsContent()->GetPrimaryFrame()); if (treeBody) { mRegion = treeBody->GetSelectionRegion(); } } } nsresult rv = InvokeDragSession( aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aTransferableArray, aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE); mRegion = Nothing(); return rv; } NS_IMETHODIMP nsBaseDragService::InvokeDragSessionWithRemoteImage( nsINode* aDOMNode, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray, uint32_t aActionType, RemoteDragStartData* aDragStartData, DragEvent* aDragEvent, DataTransfer* aDataTransfer) { NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); mSessionIsSynthesizedForTests = aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests; mDataTransfer = aDataTransfer; mSelection = nullptr; mHasImage = true; mDragPopup = nullptr; mImage = nullptr; mDragStartData = aDragStartData; mImageOffset = CSSIntPoint(0, 0); mSourceWindowContext = mDragStartData->GetSourceWindowContext(); mSourceTopWindowContext = mDragStartData->GetSourceTopWindowContext(); mScreenPosition = aDragEvent->ScreenPoint(CallerType::System); mInputSource = aDragEvent->InputSource(); nsresult rv = InvokeDragSession( aDOMNode, aPrincipal, aCsp, aCookieJarSettings, aTransferableArray, aActionType, nsIContentPolicy::TYPE_INTERNAL_IMAGE); mRegion = Nothing(); return rv; } NS_IMETHODIMP nsBaseDragService::InvokeDragSessionWithSelection( Selection* aSelection, nsIPrincipal* aPrincipal, nsIContentSecurityPolicy* aCsp, nsICookieJarSettings* aCookieJarSettings, nsIArray* aTransferableArray, uint32_t aActionType, DragEvent* aDragEvent, DataTransfer* aDataTransfer) { NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER); NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE); mSessionIsSynthesizedForTests = aDragEvent->WidgetEventPtr()->mFlags.mIsSynthesizedForTests; mDataTransfer = aDataTransfer; mSelection = aSelection; mHasImage = true; mDragPopup = nullptr; mImage = nullptr; mImageOffset = CSSIntPoint(); mDragStartData = nullptr; mRegion = Nothing(); mScreenPosition.x = aDragEvent->ScreenX(CallerType::System); mScreenPosition.y = aDragEvent->ScreenY(CallerType::System); mInputSource = aDragEvent->InputSource(); // just get the focused node from the selection // XXXndeakin this should actually be the deepest node that contains both // endpoints of the selection nsCOMPtr node = aSelection->GetFocusNode(); mSourceWindowContext = node ? node->OwnerDoc()->GetWindowContext() : nullptr; mSourceTopWindowContext = mSourceWindowContext ? mSourceWindowContext->TopWindowContext() : nullptr; return InvokeDragSession(node, aPrincipal, aCsp, aCookieJarSettings, aTransferableArray, aActionType, nsIContentPolicy::TYPE_OTHER); } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::GetCurrentSession(nsIDragSession** aSession) { if (!aSession) return NS_ERROR_INVALID_ARG; // "this" also implements a drag session, so say we are one but only // if there is currently a drag going on. if (!mSuppressLevel && mDoingDrag) { *aSession = this; NS_ADDREF(*aSession); // addRef because we're a "getter" } else *aSession = nullptr; return NS_OK; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::StartDragSession() { if (mDoingDrag) { return NS_ERROR_FAILURE; } mDoingDrag = true; // By default dispatch drop also to content. mOnlyChromeDrop = false; return NS_OK; } NS_IMETHODIMP nsBaseDragService::StartDragSessionForTests( uint32_t aAllowedEffect) { if (NS_WARN_IF(NS_FAILED(StartDragSession()))) { return NS_ERROR_FAILURE; } mDragAction = aAllowedEffect; mEffectAllowedForTests = aAllowedEffect; mSessionIsSynthesizedForTests = true; return NS_OK; } void nsBaseDragService::OpenDragPopup() { if (mDragPopup) { nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); if (pm) { pm->ShowPopupAtScreen(mDragPopup, mScreenPosition.x - mImageOffset.x, mScreenPosition.y - mImageOffset.y, false, nullptr); } } } int32_t nsBaseDragService::TakeChildProcessDragAction() { // If the last event was dispatched to the child process, use the drag action // assigned from it instead and return it. DRAGDROP_ACTION_UNINITIALIZED is // returned otherwise. int32_t retval = DRAGDROP_ACTION_UNINITIALIZED; if (TakeDragEventDispatchedToChildProcess() && mDragActionFromChildProcess != DRAGDROP_ACTION_UNINITIALIZED) { retval = mDragActionFromChildProcess; } return retval; } //------------------------------------------------------------------------- NS_IMETHODIMP nsBaseDragService::EndDragSession(bool aDoneDrag, uint32_t aKeyModifiers) { if (!mDoingDrag || mEndingSession) { return NS_ERROR_FAILURE; } mEndingSession = true; if (aDoneDrag && !mSuppressLevel) { FireDragEventAtSource(eDragEnd, aKeyModifiers); } if (mDragPopup) { nsXULPopupManager* pm = nsXULPopupManager::GetInstance(); if (pm) { pm->HidePopup(mDragPopup, {HidePopupOption::DeselectMenu}); } } uint32_t dropEffect = nsIDragService::DRAGDROP_ACTION_NONE; if (mDataTransfer) { dropEffect = mDataTransfer->DropEffectInt(); } for (uint32_t i = 0; i < mChildProcesses.Length(); ++i) { mozilla::Unused << mChildProcesses[i]->SendEndDragSession( aDoneDrag, mUserCancelled, mEndDragPoint, aKeyModifiers, dropEffect); // Continue sending input events with input priority when stopping the dnd // session. mChildProcesses[i]->SetInputPriorityEventEnabled(true); } mChildProcesses.Clear(); // mDataTransfer and the items it owns are going to die anyway, but we // explicitly deref the contained data here so that we don't have to wait for // CC to reclaim the memory. if (XRE_IsParentProcess()) { DiscardInternalTransferData(); } mDoingDrag = false; mSessionIsSynthesizedForTests = false; mIsDraggingTextInTextControl = false; mEffectAllowedForTests = nsIDragService::DRAGDROP_ACTION_UNINITIALIZED; mEndingSession = false; mCanDrop = false; // release the source we've been holding on to. mSourceDocument = nullptr; mSourceNode = nullptr; mSourceWindowContext = nullptr; mSourceTopWindowContext = nullptr; mTriggeringPrincipal = nullptr; mCsp = nullptr; mSelection = nullptr; mDataTransfer = nullptr; mHasImage = false; mUserCancelled = false; mDragPopup = nullptr; mDragStartData = nullptr; mImage = nullptr; mImageOffset = CSSIntPoint(); mScreenPosition = CSSIntPoint(); mEndDragPoint = LayoutDeviceIntPoint(0, 0); mInputSource = MouseEvent_Binding::MOZ_SOURCE_MOUSE; mRegion = Nothing(); return NS_OK; } void nsBaseDragService::DiscardInternalTransferData() { if (mDataTransfer && mSourceNode) { MOZ_ASSERT(mDataTransfer); DataTransferItemList* items = mDataTransfer->Items(); for (size_t i = 0; i < items->Length(); i++) { bool found; DataTransferItem* item = items->IndexedGetter(i, found); // Non-OTHER items may still be needed by JS. Skip them. if (!found || item->Kind() != DataTransferItem::KIND_OTHER) { continue; } nsCOMPtr variant = item->DataNoSecurityCheck(); nsCOMPtr writable = do_QueryInterface(variant); if (writable) { writable->SetAsEmpty(); } } } } NS_IMETHODIMP nsBaseDragService::FireDragEventAtSource(EventMessage aEventMessage, uint32_t aKeyModifiers) { if (!mSourceNode || !mSourceDocument || mSuppressLevel) { return NS_OK; } RefPtr presShell = mSourceDocument->GetPresShell(); if (!presShell) { return NS_OK; } RefPtr pc = presShell->GetPresContext(); nsCOMPtr widget = pc ? pc->GetRootWidget() : nullptr; nsEventStatus status = nsEventStatus_eIgnore; WidgetDragEvent event(true, aEventMessage, widget); event.mFlags.mIsSynthesizedForTests = mSessionIsSynthesizedForTests; event.mInputSource = mInputSource; if (aEventMessage == eDragEnd) { event.mRefPoint = mEndDragPoint; if (widget) { event.mRefPoint -= widget->WidgetToTopLevelWidgetOffset(); } event.mUserCancelled = mUserCancelled; } event.mModifiers = aKeyModifiers; // Most drag events aren't able to converted to MouseEvent except to // eDragStart and eDragEnd. if (widget && event.CanConvertToInputData()) { // Send the drag event to APZ, which needs to know about them to be // able to accurately detect the end of a drag gesture. widget->DispatchEventToAPZOnly(&event); } nsCOMPtr content = do_QueryInterface(mSourceNode); return presShell->HandleDOMEventWithTarget(content, &event, &status); } /* This is used by Windows and Mac to update the position of a popup being * used as a drag image during the drag. This isn't used on GTK as it manages * the drag popup itself. */ NS_IMETHODIMP nsBaseDragService::DragMoved(int32_t aX, int32_t aY) { if (mDragPopup) { nsIFrame* frame = mDragPopup->GetPrimaryFrame(); if (frame && frame->IsMenuPopupFrame()) { CSSIntPoint cssPos = RoundedToInt(LayoutDeviceIntPoint(aX, aY) / frame->PresContext()->CSSToDevPixelScale()) - mImageOffset; static_cast(frame)->MoveTo(cssPos, true); } } return NS_OK; } static PresShell* GetPresShellForContent(nsINode* aDOMNode) { nsCOMPtr content = do_QueryInterface(aDOMNode); if (!content) return nullptr; RefPtr document = content->GetComposedDoc(); if (document) { document->FlushPendingNotifications(FlushType::Display); return document->GetPresShell(); } return nullptr; } nsresult nsBaseDragService::DrawDrag(nsINode* aDOMNode, const Maybe& aRegion, CSSIntPoint aScreenPosition, LayoutDeviceIntRect* aScreenDragRect, RefPtr* aSurface, nsPresContext** aPresContext) { *aSurface = nullptr; *aPresContext = nullptr; // use a default size, in case of an error. aScreenDragRect->SetRect(aScreenPosition.x - mImageOffset.x, aScreenPosition.y - mImageOffset.y, 1, 1); // if a drag image was specified, use that, otherwise, use the source node nsCOMPtr dragNode = mImage ? mImage.get() : aDOMNode; // get the presshell for the node being dragged. If the drag image is not in // a document or has no frame, get the presshell from the source drag node PresShell* presShell = GetPresShellForContent(dragNode); if (!presShell && mImage) { presShell = GetPresShellForContent(aDOMNode); } if (!presShell) { return NS_ERROR_FAILURE; } *aPresContext = presShell->GetPresContext(); if (mDragStartData) { if (mImage) { // Just clear the surface if chrome has overridden it with an image. *aSurface = nullptr; } else { *aSurface = mDragStartData->TakeVisualization(aScreenDragRect); } mDragStartData = nullptr; return NS_OK; } // convert mouse position to dev pixels of the prescontext const CSSIntPoint screenPosition = aScreenPosition - mImageOffset; const auto screenPoint = LayoutDeviceIntPoint::Round( screenPosition * (*aPresContext)->CSSToDevPixelScale()); aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y); // check if drag images are disabled bool enableDragImages = Preferences::GetBool(DRAGIMAGES_PREF, true); // didn't want an image, so just set the screen rectangle to the frame size if (!enableDragImages || !mHasImage) { // This holds a quantity in RelativeTo{presShell->GetRootFrame(), // ViewportType::Layout} space. nsRect presLayoutRect; if (aRegion) { // if a region was specified, set the screen rectangle to the area that // the region occupies presLayoutRect = ToAppUnits(aRegion->GetBounds(), AppUnitsPerCSSPixel()); } else { // otherwise, there was no region so just set the rectangle to // the size of the primary frame of the content. nsCOMPtr content = do_QueryInterface(dragNode); if (nsIFrame* frame = content->GetPrimaryFrame()) { presLayoutRect = frame->GetBoundingClientRect(); } } LayoutDeviceRect screenVisualRect = ViewportUtils::ToScreenRelativeVisual( LayoutDeviceRect::FromAppUnits(presLayoutRect, (*aPresContext)->AppUnitsPerDevPixel()), *aPresContext); aScreenDragRect->SizeTo(screenVisualRect.Width(), screenVisualRect.Height()); return NS_OK; } // draw the image for selections if (mSelection) { LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft()); *aSurface = presShell->RenderSelection( mSelection, pnt, aScreenDragRect, mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale); return NS_OK; } // if a custom image was specified, check if it is an image node and draw // using the source rather than the displayed image. But if mImage isn't // an image or canvas, fall through to RenderNode below. if (mImage) { nsCOMPtr content = do_QueryInterface(dragNode); HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(content); if (canvas) { return DrawDragForImage(*aPresContext, nullptr, canvas, aScreenDragRect, aSurface); } nsCOMPtr imageLoader = do_QueryInterface(dragNode); // for image nodes, create the drag image from the actual image data if (imageLoader) { return DrawDragForImage(*aPresContext, imageLoader, nullptr, aScreenDragRect, aSurface); } // If the image is a popup, use that as the image. This allows custom drag // images that can change during the drag, but means that any platform // default image handling won't occur. // XXXndeakin this should be chrome-only nsIFrame* frame = content->GetPrimaryFrame(); if (frame && frame->IsMenuPopupFrame()) { mDragPopup = content->AsElement(); } } if (!mDragPopup) { // otherwise, just draw the node RenderImageFlags renderFlags = mImage ? RenderImageFlags::None : RenderImageFlags::AutoScale; if (renderFlags != RenderImageFlags::None) { // check if the dragged node itself is an img element if (dragNode->NodeName().LowerCaseEqualsLiteral("img")) { renderFlags = renderFlags | RenderImageFlags::IsImage; } else { nsINodeList* childList = dragNode->ChildNodes(); uint32_t length = childList->Length(); // check every childnode for being an img element // XXXbz why don't we need to check descendants recursively? for (uint32_t count = 0; count < length; ++count) { if (childList->Item(count)->NodeName().LowerCaseEqualsLiteral( "img")) { // if the dragnode contains an image, set RenderImageFlags::IsImage // flag renderFlags = renderFlags | RenderImageFlags::IsImage; break; } } } } LayoutDeviceIntPoint pnt(aScreenDragRect->TopLeft()); *aSurface = presShell->RenderNode(dragNode, aRegion, pnt, aScreenDragRect, renderFlags); } // If an image was specified, reset the position from the offset that was // supplied. if (mImage) { aScreenDragRect->MoveTo(screenPoint.x, screenPoint.y); } return NS_OK; } nsresult nsBaseDragService::DrawDragForImage( nsPresContext* aPresContext, nsIImageLoadingContent* aImageLoader, HTMLCanvasElement* aCanvas, LayoutDeviceIntRect* aScreenDragRect, RefPtr* aSurface) { nsCOMPtr imgContainer; if (aImageLoader) { nsCOMPtr imgRequest; nsresult rv = aImageLoader->GetRequest( nsIImageLoadingContent::CURRENT_REQUEST, getter_AddRefs(imgRequest)); NS_ENSURE_SUCCESS(rv, rv); if (!imgRequest) return NS_ERROR_NOT_AVAILABLE; rv = imgRequest->GetImage(getter_AddRefs(imgContainer)); NS_ENSURE_SUCCESS(rv, rv); if (!imgContainer) return NS_ERROR_NOT_AVAILABLE; // use the size of the image as the size of the drag image int32_t imageWidth, imageHeight; rv = imgContainer->GetWidth(&imageWidth); NS_ENSURE_SUCCESS(rv, rv); rv = imgContainer->GetHeight(&imageHeight); NS_ENSURE_SUCCESS(rv, rv); aScreenDragRect->SizeTo(aPresContext->CSSPixelsToDevPixels(imageWidth), aPresContext->CSSPixelsToDevPixels(imageHeight)); } else { // XXX The canvas size should be converted to dev pixels. NS_ASSERTION(aCanvas, "both image and canvas are null"); nsIntSize sz = aCanvas->GetSize(); aScreenDragRect->SizeTo(sz.width, sz.height); } nsIntSize destSize; destSize.width = aScreenDragRect->Width(); destSize.height = aScreenDragRect->Height(); if (destSize.width == 0 || destSize.height == 0) return NS_ERROR_FAILURE; nsresult result = NS_OK; if (aImageLoader) { RefPtr dt = gfxPlatform::GetPlatform()->CreateOffscreenContentDrawTarget( destSize, SurfaceFormat::B8G8R8A8); if (!dt || !dt->IsValid()) return NS_ERROR_FAILURE; gfxContext ctx(dt); ImgDrawResult res = imgContainer->Draw( &ctx, destSize, ImageRegion::Create(destSize), imgIContainer::FRAME_CURRENT, SamplingFilter::GOOD, SVGImageContext(), imgIContainer::FLAG_SYNC_DECODE, 1.0); if (res == ImgDrawResult::BAD_IMAGE || res == ImgDrawResult::BAD_ARGS || res == ImgDrawResult::NOT_SUPPORTED) { return NS_ERROR_FAILURE; } *aSurface = dt->Snapshot(); } else { *aSurface = aCanvas->GetSurfaceSnapshot(); } return result; } NS_IMETHODIMP nsBaseDragService::Suppress() { EndDragSession(false, 0); ++mSuppressLevel; return NS_OK; } NS_IMETHODIMP nsBaseDragService::Unsuppress() { --mSuppressLevel; return NS_OK; } NS_IMETHODIMP nsBaseDragService::UserCancelled() { mUserCancelled = true; return NS_OK; } NS_IMETHODIMP nsBaseDragService::UpdateDragEffect() { mDragActionFromChildProcess = mDragAction; return NS_OK; } NS_IMETHODIMP nsBaseDragService::UpdateDragImage(nsINode* aImage, int32_t aImageX, int32_t aImageY) { // Don't change the image if this is a drag from another source or if there // is a drag popup. if (!mSourceNode || mDragPopup) return NS_OK; mImage = aImage; mImageOffset = CSSIntPoint(aImageX, aImageY); return NS_OK; } NS_IMETHODIMP nsBaseDragService::DragEventDispatchedToChildProcess() { mDragEventDispatchedToChildProcess = true; return NS_OK; } bool nsBaseDragService::MaybeAddChildProcess( mozilla::dom::ContentParent* aChild) { if (!mChildProcesses.Contains(aChild)) { mChildProcesses.AppendElement(aChild); return true; } return false; } bool nsBaseDragService::RemoveAllChildProcesses() { for (uint32_t c = 0; c < mChildProcesses.Length(); c++) { mozilla::Unused << mChildProcesses[c]->SendEndDragSession( true, false, LayoutDeviceIntPoint(), 0, nsIDragService::DRAGDROP_ACTION_NONE); } mChildProcesses.Clear(); return true; } bool nsBaseDragService::MustUpdateDataTransfer(EventMessage aMessage) { return false; } NS_IMETHODIMP nsBaseDragService::MaybeEditorDeletedSourceNode(Element* aEditingHost) { // If builtin editor of Blink and WebKit deletes the source node,they retarget // the source node to the editing host. // https://source.chromium.org/chromium/chromium/src/+/main:third_party/blink/renderer/core/page/drag_controller.cc;l=724;drc=d9ba13b8cd8ac0faed7afc3d1f7e4b67ebac2a0b // That allows editor apps listens to "dragend" event in editing host or its // ancestors. Therefore, we should follow them for compatibility. if (mSourceNode && !mSourceNode->IsInComposedDoc()) { mSourceNode = aEditingHost; } return NS_OK; }