From 62344a3ec7de81d90921f083b81fe459e3f3bb8f Mon Sep 17 00:00:00 2001 From: Olli Pettay Date: Thu, 29 Oct 2009 13:11:02 +0200 Subject: [PATCH] Bug 508479 - HTML5 Drag and Drop: Drop event on elements that are not drop targets, r=enn, sr=sicking --- content/base/src/nsContentAreaDragDrop.cpp | 20 +++++- content/events/src/nsDOMEvent.cpp | 16 +++++ content/events/src/nsEventDispatcher.cpp | 24 +++++++ content/events/src/nsEventStateManager.cpp | 17 +++++ content/events/test/Makefile.in | 1 + content/events/test/test_bug508479.html | 81 ++++++++++++++++++++++ dom/base/nsDOMWindowUtils.cpp | 31 ++++++++- dom/interfaces/base/nsIDOMWindowUtils.idl | 20 +++++- layout/base/nsPresShell.cpp | 11 +++ widget/public/nsGUIEvent.h | 6 ++ widget/public/nsIDragSession.idl | 7 +- widget/src/xpwidgets/nsBaseDragService.cpp | 20 +++++- widget/src/xpwidgets/nsBaseDragService.h | 1 + 13 files changed, 250 insertions(+), 5 deletions(-) create mode 100644 content/events/test/test_bug508479.html diff --git a/content/base/src/nsContentAreaDragDrop.cpp b/content/base/src/nsContentAreaDragDrop.cpp index ab5dd95fa223..d62dfd7b3f52 100644 --- a/content/base/src/nsContentAreaDragDrop.cpp +++ b/content/base/src/nsContentAreaDragDrop.cpp @@ -328,7 +328,25 @@ nsContentAreaDragDrop::DragOver(nsIDOMDragEvent* inEvent) } } - session->SetCanDrop(dropAllowed); + nsCOMPtr e = do_QueryInterface(inEvent); + NS_ENSURE_STATE(e); + nsCOMPtr target; + e->GetOriginalTarget(getter_AddRefs(target)); + nsCOMPtr node = do_QueryInterface(target); + if (!node) { + nsCOMPtr win = do_QueryInterface(target); + if (win) { + node = do_QueryInterface(win->GetExtantDocument()); + } + } + PRBool isChrome = + node ? nsContentUtils::IsChromeDoc(node->GetOwnerDoc()) : PR_FALSE; + if (isChrome) { + session->SetCanDrop(dropAllowed); + } else if (dropAllowed) { + inEvent->PreventDefault(); + } + return NS_OK; } diff --git a/content/events/src/nsDOMEvent.cpp b/content/events/src/nsDOMEvent.cpp index 0c60fc6d2f46..cbac26cf7a23 100644 --- a/content/events/src/nsDOMEvent.cpp +++ b/content/events/src/nsDOMEvent.cpp @@ -459,7 +459,23 @@ nsDOMEvent::PreventDefault() { if (!(mEvent->flags & NS_EVENT_FLAG_CANT_CANCEL)) { mEvent->flags |= NS_EVENT_FLAG_NO_DEFAULT; + + // Need to set an extra flag for drag events. + if (mEvent->eventStructType == NS_DRAG_EVENT && + NS_IS_TRUSTED_EVENT(mEvent)) { + nsCOMPtr node = do_QueryInterface(mEvent->currentTarget); + if (!node) { + nsCOMPtr win = do_QueryInterface(mEvent->currentTarget); + if (win) { + node = do_QueryInterface(win->GetExtantDocument()); + } + } + if (node && !nsContentUtils::IsChromeDoc(node->GetOwnerDoc())) { + mEvent->flags |= NS_EVENT_FLAG_NO_DEFAULT_CALLED_IN_CONTENT; + } + } } + return NS_OK; } diff --git a/content/events/src/nsEventDispatcher.cpp b/content/events/src/nsEventDispatcher.cpp index a18fdb2e5cdd..72ce16e52f56 100644 --- a/content/events/src/nsEventDispatcher.cpp +++ b/content/events/src/nsEventDispatcher.cpp @@ -46,6 +46,8 @@ #include "nsMutationEvent.h" #include NEW_H #include "nsFixedSizeAllocator.h" +#include "nsINode.h" +#include "nsPIDOMWindow.h" #define NS_TARGET_CHAIN_FORCE_CONTENT_DISPATCH (1 << 0) #define NS_TARGET_CHAIN_WANTS_WILL_HANDLE_EVENT (1 << 1) @@ -444,6 +446,28 @@ nsEventDispatcher::Dispatch(nsISupports* aTarget, NS_ERROR_ILLEGAL_VALUE); NS_ASSERTION(!aTargets || !aEvent->message, "Wrong parameters!"); + if (aEvent->flags & NS_EVENT_FLAG_ONLY_CHROME_DISPATCH) { + nsCOMPtr node = do_QueryInterface(aTarget); + if (!node) { + nsCOMPtr win = do_QueryInterface(aTarget); + if (win) { + node = do_QueryInterface(win->GetExtantDocument()); + } + } + + NS_ENSURE_STATE(node); + nsIDocument* doc = node->GetOwnerDoc(); + if (!nsContentUtils::IsChromeDoc(doc)) { + nsPIDOMWindow* win = doc ? doc->GetInnerWindow() : nsnull; + // If we can't dispatch the event to chrome, do nothing. + NS_ENSURE_TRUE(win && win->GetChromeEventHandler(), NS_OK); + // Set the target to be the original dispatch target, + aEvent->target = aTarget; + // but use chrome event handler for event target chain. + aTarget = win->GetChromeEventHandler(); + } + } + nsCOMPtr target = do_QueryInterface(aTarget); #ifdef DEBUG if (aDOMEvent) { diff --git a/content/events/src/nsEventStateManager.cpp b/content/events/src/nsEventStateManager.cpp index 0b227d847899..ad3b3e0cc629 100644 --- a/content/events/src/nsEventStateManager.cpp +++ b/content/events/src/nsEventStateManager.cpp @@ -3042,6 +3042,13 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext, if (!dragSession) break; + // Reset the flag. + dragSession->SetOnlyChromeDrop(PR_FALSE); + if (mPresContext) { + EnsureDocument(mPresContext); + } + PRBool isChromeDoc = nsContentUtils::IsChromeDoc(mDocument); + // the initial dataTransfer is the one from the dragstart event that // was set on the dragSession when the drag began. nsCOMPtr dataTransfer; @@ -3117,6 +3124,16 @@ nsEventStateManager::PostHandleEvent(nsPresContext* aPresContext, // inform the drag session that a drop is allowed on this node. dragSession->SetDragAction(action); dragSession->SetCanDrop(action != nsIDragService::DRAGDROP_ACTION_NONE); + + // For now, do this only for dragover. + //XXXsmaug dragenter needs some more work. + if (aEvent->message == NS_DRAGDROP_OVER && !isChromeDoc) { + // Someone has called preventDefault(), check whether is was content. + dragSession->SetOnlyChromeDrop( + !(aEvent->flags & NS_EVENT_FLAG_NO_DEFAULT_CALLED_IN_CONTENT)); + } + } else if (aEvent->message == NS_DRAGDROP_OVER && !isChromeDoc) { + dragSession->SetCanDrop(PR_FALSE); } // now set the drop effect in the initial dataTransfer. This ensures diff --git a/content/events/test/Makefile.in b/content/events/test/Makefile.in index dece95478185..b1ae84b1d5fd 100644 --- a/content/events/test/Makefile.in +++ b/content/events/test/Makefile.in @@ -80,6 +80,7 @@ _TEST_FILES = \ test_bug489671.html \ test_bug493251.html \ test_bug502818.html \ + test_bug508479.html \ test_bug517851.html \ $(NULL) diff --git a/content/events/test/test_bug508479.html b/content/events/test/test_bug508479.html new file mode 100644 index 000000000000..6285d05bbe6d --- /dev/null +++ b/content/events/test/test_bug508479.html @@ -0,0 +1,81 @@ + + + Tests for the dragstart event + + + + + + + + + + + + + + + diff --git a/dom/base/nsDOMWindowUtils.cpp b/dom/base/nsDOMWindowUtils.cpp index cba8776993ad..e10f0a5abb28 100644 --- a/dom/base/nsDOMWindowUtils.cpp +++ b/dom/base/nsDOMWindowUtils.cpp @@ -40,7 +40,7 @@ #include "nsDOMClassInfo.h" #include "nsDOMError.h" #include "nsIDOMNSEvent.h" - +#include "nsIPrivateDOMEvent.h" #include "nsDOMWindowUtils.h" #include "nsGlobalWindow.h" #include "nsIDocument.h" @@ -920,3 +920,32 @@ nsDOMWindowUtils::GetCOWForObject() cc->SetReturnValueWasSet(PR_TRUE); return NS_OK; } + +NS_IMETHODIMP +nsDOMWindowUtils::DispatchDOMEventViaPresShell(nsIDOMNode* aTarget, + nsIDOMEvent* aEvent, + PRBool aTrusted, + PRBool* aRetVal) +{ + if (!nsContentUtils::IsCallerTrustedForRead()) { + return NS_ERROR_DOM_SECURITY_ERR; + } + + nsPresContext* presContext = GetPresContext(); + NS_ENSURE_STATE(presContext); + nsCOMPtr shell = presContext->GetPresShell(); + NS_ENSURE_STATE(shell); + nsCOMPtr event = do_QueryInterface(aEvent); + NS_ENSURE_STATE(event); + event->SetTrusted(aTrusted); + nsEvent* internalEvent = event->GetInternalNSEvent(); + NS_ENSURE_STATE(internalEvent); + nsCOMPtr content = do_QueryInterface(aTarget); + NS_ENSURE_STATE(content); + + nsEventStatus status = nsEventStatus_eIgnore; + shell->HandleEventWithTarget(internalEvent, nsnull, content, + &status); + *aRetVal = (status != nsEventStatus_eConsumeNoDefault); + return NS_OK; +} diff --git a/dom/interfaces/base/nsIDOMWindowUtils.idl b/dom/interfaces/base/nsIDOMWindowUtils.idl index 71fb0ead60c9..7a44bc212e68 100644 --- a/dom/interfaces/base/nsIDOMWindowUtils.idl +++ b/dom/interfaces/base/nsIDOMWindowUtils.idl @@ -45,10 +45,12 @@ * getInterface on a DOMWindow. */ +interface nsIDOMNode; interface nsIDOMElement; interface nsIDOMHTMLCanvasElement; +interface nsIDOMEvent; -[scriptable, uuid(4171ea1a-3752-4bc3-8c66-1b2936ecde7a)] +[scriptable, uuid(4775e623-d596-4364-8637-0968a5ce5e3d)] interface nsIDOMWindowUtils : nsISupports { /** @@ -403,4 +405,20 @@ interface nsIDOMWindowUtils : nsISupports { * Get the number of screen pixels per CSS pixel. */ readonly attribute float screenPixelsPerCSSPixel; + + /** + * Dispatches aEvent via the nsIPresShell object of the window's document. + * The event is dispatched to aTarget, which should be an object + * which implements nsIContent interface (#element, #text, etc). + * + * Cannot be accessed from unprivileged context (not + * content-accessible) Will throw a DOM security error if called + * without UniversalXPConnect privileges. + * + * @note Event handlers won't get aEvent as parameter, but a similar event. + * Also, aEvent should not be reused. + */ + boolean dispatchDOMEventViaPresShell(in nsIDOMNode aTarget, + in nsIDOMEvent aEvent, + in boolean aTrusted); }; diff --git a/layout/base/nsPresShell.cpp b/layout/base/nsPresShell.cpp index bb2f565baed2..3cfdb411237f 100644 --- a/layout/base/nsPresShell.cpp +++ b/layout/base/nsPresShell.cpp @@ -6488,6 +6488,17 @@ PresShell::HandleEventInternal(nsEvent* aEvent, nsIView *aView, case NS_KEY_DOWN: case NS_KEY_UP: isHandlingUserInput = PR_TRUE; + break; + case NS_DRAGDROP_DROP: + nsCOMPtr session = nsContentUtils::GetDragSession(); + if (session) { + PRBool onlyChromeDrop = PR_FALSE; + session->GetOnlyChromeDrop(&onlyChromeDrop); + if (onlyChromeDrop) { + aEvent->flags |= NS_EVENT_FLAG_ONLY_CHROME_DISPATCH; + } + } + break; } } diff --git a/widget/public/nsGUIEvent.h b/widget/public/nsGUIEvent.h index 342b6ef7a803..c5cea8d34050 100644 --- a/widget/public/nsGUIEvent.h +++ b/widget/public/nsGUIEvent.h @@ -132,6 +132,12 @@ class nsHashKey; // events. #define NS_EVENT_FLAG_SYNTHETIC_TEST_EVENT 0x1000 +// Use this flag if the event should be dispatched only to chrome. +#define NS_EVENT_FLAG_ONLY_CHROME_DISPATCH 0x2000 + +// A flag for drag&drop handling. +#define NS_EVENT_FLAG_NO_DEFAULT_CALLED_IN_CONTENT 0x4000 + #define NS_PRIV_EVENT_UNTRUSTED_PERMITTED 0x8000 #define NS_EVENT_CAPTURE_MASK (~(NS_EVENT_FLAG_BUBBLE | NS_EVENT_FLAG_NO_CONTENT_DISPATCH)) diff --git a/widget/public/nsIDragSession.idl b/widget/public/nsIDragSession.idl index d13ba448049a..82a1c5262eb9 100644 --- a/widget/public/nsIDragSession.idl +++ b/widget/public/nsIDragSession.idl @@ -53,7 +53,7 @@ interface nsIDOMDocument; interface nsIDOMNode; interface nsIDOMDataTransfer; -[scriptable, uuid(15860D52-FE2C-4DDD-AC50-9C23E24916C4)] +[scriptable, uuid(fde41f6a-c710-46f8-a0a8-1ff76ca4ff57)] interface nsIDragSession : nsISupports { /** @@ -61,6 +61,11 @@ interface nsIDragSession : nsISupports * usually the target "frame" sets this so the native system can render the correct feedback */ attribute boolean canDrop; + + /** + * Indicates if the drop event should be dispatched only to chrome. + */ + attribute boolean onlyChromeDrop; /** * Sets the action (copy, move, link, et.c) for the current drag diff --git a/widget/src/xpwidgets/nsBaseDragService.cpp b/widget/src/xpwidgets/nsBaseDragService.cpp index 783efd22edc7..e1c1f37f14eb 100644 --- a/widget/src/xpwidgets/nsBaseDragService.cpp +++ b/widget/src/xpwidgets/nsBaseDragService.cpp @@ -76,7 +76,8 @@ #define DRAGIMAGES_PREF "nglayout.enable_drag_images" nsBaseDragService::nsBaseDragService() - : mCanDrop(PR_FALSE), mDoingDrag(PR_FALSE), mHasImage(PR_FALSE), mUserCancelled(PR_FALSE), + : mCanDrop(PR_FALSE), mOnlyChromeDrop(PR_FALSE), mDoingDrag(PR_FALSE), + mHasImage(PR_FALSE), mUserCancelled(PR_FALSE), mDragAction(DRAGDROP_ACTION_NONE), mTargetSize(0,0), mImageX(0), mImageY(0), mScreenX(-1), mScreenY(-1), mSuppressLevel(0) { @@ -103,6 +104,21 @@ nsBaseDragService::GetCanDrop(PRBool * aCanDrop) *aCanDrop = mCanDrop; return NS_OK; } +//--------------------------------------------------------- +NS_IMETHODIMP +nsBaseDragService::SetOnlyChromeDrop(PRBool aOnlyChrome) +{ + mOnlyChromeDrop = aOnlyChrome; + return NS_OK; +} + +//--------------------------------------------------------- +NS_IMETHODIMP +nsBaseDragService::GetOnlyChromeDrop(PRBool* aOnlyChrome) +{ + *aOnlyChrome = mOnlyChromeDrop; + return NS_OK; +} //--------------------------------------------------------- NS_IMETHODIMP @@ -323,6 +339,8 @@ nsBaseDragService::StartDragSession() return NS_ERROR_FAILURE; } mDoingDrag = PR_TRUE; + // By default dispatch drop also to content. + mOnlyChromeDrop = PR_FALSE; return NS_OK; } diff --git a/widget/src/xpwidgets/nsBaseDragService.h b/widget/src/xpwidgets/nsBaseDragService.h index 49b8cf88018d..915ba15d5612 100644 --- a/widget/src/xpwidgets/nsBaseDragService.h +++ b/widget/src/xpwidgets/nsBaseDragService.h @@ -128,6 +128,7 @@ protected: PRInt32* aScreenX, PRInt32* aScreenY); PRPackedBool mCanDrop; + PRPackedBool mOnlyChromeDrop; PRPackedBool mDoingDrag; // true if mImage should be used to set a drag image PRPackedBool mHasImage;