summaryrefslogtreecommitdiffstats
path: root/widget/nsBaseDragService.cpp
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 17:32:43 +0000
commit6bf0a5cb5034a7e684dcc3500e841785237ce2dd (patch)
treea68f146d7fa01f0134297619fbe7e33db084e0aa /widget/nsBaseDragService.cpp
parentInitial commit. (diff)
downloadthunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.tar.xz
thunderbird-6bf0a5cb5034a7e684dcc3500e841785237ce2dd.zip
Adding upstream version 1:115.7.0.upstream/1%115.7.0upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/nsBaseDragService.cpp')
-rw-r--r--widget/nsBaseDragService.cpp1035
1 files changed, 1035 insertions, 0 deletions
diff --git a/widget/nsBaseDragService.cpp b/widget/nsBaseDragService.cpp
new file mode 100644
index 0000000000..bdd8d2bd28
--- /dev/null
+++ b/widget/nsBaseDragService.cpp
@@ -0,0 +1,1035 @@
+/* -*- 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 <algorithm>
+
+using namespace mozilla;
+using namespace mozilla::dom;
+using namespace mozilla::gfx;
+using namespace mozilla::image;
+
+#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<nsIWidget> 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<nsIMutableArray> mutableArray =
+ do_QueryInterface(aTransferableArray);
+ if (mutableArray) {
+ // In order to be able trigger dnd, we need to have some transferable
+ // object.
+ nsCOMPtr<nsITransferable> 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<nsITransferable> 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->MozInputSource();
+
+ // 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->MozInputSource();
+
+ 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->MozInputSource();
+
+ // just get the focused node from the selection
+ // XXXndeakin this should actually be the deepest node that contains both
+ // endpoints of the selection
+ nsCOMPtr<nsINode> 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;
+ 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<nsIVariant> variant = item->DataNoSecurityCheck();
+ nsCOMPtr<nsIWritableVariant> 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> presShell = mSourceDocument->GetPresShell();
+ if (!presShell) {
+ return NS_OK;
+ }
+
+ RefPtr<nsPresContext> pc = presShell->GetPresContext();
+ nsCOMPtr<nsIWidget> 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;
+
+ if (widget) {
+ // 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<nsIContent> 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<nsMenuPopupFrame*>(frame)->MoveTo(cssPos, true);
+ }
+ }
+
+ return NS_OK;
+}
+
+static PresShell* GetPresShellForContent(nsINode* aDOMNode) {
+ nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
+ if (!content) return nullptr;
+
+ RefPtr<Document> document = content->GetComposedDoc();
+ if (document) {
+ document->FlushPendingNotifications(FlushType::Display);
+ return document->GetPresShell();
+ }
+
+ return nullptr;
+}
+
+nsresult nsBaseDragService::DrawDrag(nsINode* aDOMNode,
+ const Maybe<CSSIntRegion>& aRegion,
+ CSSIntPoint aScreenPosition,
+ LayoutDeviceIntRect* aScreenDragRect,
+ RefPtr<SourceSurface>* 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<nsINode> 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<nsIContent> 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<nsIContent> content = do_QueryInterface(dragNode);
+ HTMLCanvasElement* canvas = HTMLCanvasElement::FromNodeOrNull(content);
+ if (canvas) {
+ return DrawDragForImage(*aPresContext, nullptr, canvas, aScreenDragRect,
+ aSurface);
+ }
+
+ nsCOMPtr<nsIImageLoadingContent> 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<SourceSurface>* aSurface) {
+ nsCOMPtr<imgIContainer> imgContainer;
+ if (aImageLoader) {
+ nsCOMPtr<imgIRequest> 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<DrawTarget> 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;
+}
+
+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;
+}