Bug 508479 - HTML5 Drag and Drop: Drop event on elements that are not drop targets, r=enn, sr=sicking
authorOlli Pettay <Olli.Pettay@helsinki.fi>
Thu, 29 Oct 2009 13:11:02 +0200
changeset 34272 674972c0556ae8349f229060940ff151ec50f66b
parent 34271 d7e464f89e7c3b47e53b019de6a236f6b047785d
child 34273 554d3a8cc6ff9e3db2b765389cfc0d8dd7877884
push idunknown
push userunknown
push dateunknown
reviewersenn, sicking
bugs508479
milestone1.9.3a1pre
Bug 508479 - HTML5 Drag and Drop: Drop event on elements that are not drop targets, r=enn, sr=sicking
content/base/src/nsContentAreaDragDrop.cpp
content/events/src/nsDOMEvent.cpp
content/events/src/nsEventDispatcher.cpp
content/events/src/nsEventStateManager.cpp
content/events/test/Makefile.in
content/events/test/test_bug508479.html
dom/base/nsDOMWindowUtils.cpp
dom/interfaces/base/nsIDOMWindowUtils.idl
layout/base/nsPresShell.cpp
widget/public/nsGUIEvent.h
widget/public/nsIDragSession.idl
widget/src/xpwidgets/nsBaseDragService.cpp
widget/src/xpwidgets/nsBaseDragService.h
--- a/content/base/src/nsContentAreaDragDrop.cpp
+++ b/content/base/src/nsContentAreaDragDrop.cpp
@@ -323,17 +323,35 @@ nsContentAreaDragDrop::DragOver(nsIDOMDr
 
         if (sourceRoot && sourceRoot == eventRoot) {
           dropAllowed = PR_FALSE;
         }
       }
     }
   }
 
-  session->SetCanDrop(dropAllowed);
+  nsCOMPtr<nsIDOMNSEvent> e = do_QueryInterface(inEvent);
+  NS_ENSURE_STATE(e);
+  nsCOMPtr<nsIDOMEventTarget> target;
+  e->GetOriginalTarget(getter_AddRefs(target));
+  nsCOMPtr<nsINode> node = do_QueryInterface(target);
+  if (!node) {
+    nsCOMPtr<nsPIDOMWindow> 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;
 }
 
 
 //
 // ExtractURLFromData
 //
 // build up a url from whatever data we get from the OS. How we
--- a/content/events/src/nsDOMEvent.cpp
+++ b/content/events/src/nsDOMEvent.cpp
@@ -454,17 +454,33 @@ nsDOMEvent::GetIsTrusted(PRBool *aIsTrus
   return NS_OK;
 }
 
 NS_IMETHODIMP
 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<nsINode> node = do_QueryInterface(mEvent->currentTarget);
+      if (!node) {
+        nsCOMPtr<nsPIDOMWindow> 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;
 }
 
 nsresult
 nsDOMEvent::SetEventType(const nsAString& aEventTypeArg)
 {
   nsCOMPtr<nsIAtom> atom = do_GetAtom(NS_LITERAL_STRING("on") + aEventTypeArg);
   mEvent->message = NS_USER_DEFINED_EVENT;
--- a/content/events/src/nsEventDispatcher.cpp
+++ b/content/events/src/nsEventDispatcher.cpp
@@ -41,16 +41,18 @@
 #include "nsPresContext.h"
 #include "nsIPrivateDOMEvent.h"
 #include "nsEventListenerManager.h"
 #include "nsContentUtils.h"
 #include "nsDOMError.h"
 #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)
 
 // nsEventTargetChainItem represents a single item in the event target chain.
 class nsEventTargetChainItem
 {
 private:
@@ -439,16 +441,38 @@ nsEventDispatcher::Dispatch(nsISupports*
                             nsDispatchingCallback* aCallback,
                             nsCOMArray<nsPIDOMEventTarget>* aTargets)
 {
   NS_ASSERTION(aEvent, "Trying to dispatch without nsEvent!");
   NS_ENSURE_TRUE(!NS_IS_EVENT_IN_DISPATCH(aEvent),
                  NS_ERROR_ILLEGAL_VALUE);
   NS_ASSERTION(!aTargets || !aEvent->message, "Wrong parameters!");
 
+  if (aEvent->flags & NS_EVENT_FLAG_ONLY_CHROME_DISPATCH) {
+    nsCOMPtr<nsINode> node = do_QueryInterface(aTarget);
+    if (!node) {
+      nsCOMPtr<nsPIDOMWindow> 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<nsPIDOMEventTarget> target = do_QueryInterface(aTarget);
 #ifdef DEBUG
   if (aDOMEvent) {
     nsCOMPtr<nsIPrivateDOMEvent> privEvt(do_QueryInterface(aDOMEvent));
     if (privEvt) {
       nsEvent* innerEvent = privEvt->GetInternalNSEvent();
       NS_ASSERTION(innerEvent == aEvent,
                     "The inner event of aDOMEvent is not the same as aEvent!");
--- a/content/events/src/nsEventStateManager.cpp
+++ b/content/events/src/nsEventStateManager.cpp
@@ -3037,16 +3037,23 @@ nsEventStateManager::PostHandleEvent(nsP
   case NS_DRAGDROP_OVER:
     {
       NS_ASSERTION(aEvent->eventStructType == NS_DRAG_EVENT, "Expected a drag event");
 
       nsCOMPtr<nsIDragSession> dragSession = nsContentUtils::GetDragSession();
       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<nsIDOMNSDataTransfer> dataTransfer;
       nsCOMPtr<nsIDOMDataTransfer> initialDataTransfer;
       dragSession->GetDataTransfer(getter_AddRefs(initialDataTransfer));
 
       nsCOMPtr<nsIDOMNSDataTransfer> initialDataTransferNS = 
         do_QueryInterface(initialDataTransfer);
@@ -3112,16 +3119,26 @@ nsEventStateManager::PostHandleEvent(nsP
           action = dropEffect;
 
         if (action == nsIDragService::DRAGDROP_ACTION_NONE)
           dropEffect = nsIDragService::DRAGDROP_ACTION_NONE;
 
         // 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
       // that we can get the desired drop effect in the drop event.
       if (initialDataTransferNS)
         initialDataTransferNS->SetDropEffectInt(dropEffect);
     }
     break;
--- a/content/events/test/Makefile.in
+++ b/content/events/test/Makefile.in
@@ -75,16 +75,17 @@ include $(topsrcdir)/config/rules.mk
 		test_bug457672.html \
 		test_bug428988.html \
 		bug457672.html \
 		test_draggableprop.html \
 		test_dragstart.html \
 		test_bug489671.html \
 		test_bug493251.html \
 		test_bug502818.html \
+		test_bug508479.html \
 		test_bug517851.html \
 		$(NULL)
 
 _CHROME_FILES = \
 		test_bug415498.xul \
 		bug415498-doc1.html \
 		bug415498-doc2.html \
 		$(NULL)
new file mode 100644
--- /dev/null
+++ b/content/events/test/test_bug508479.html
@@ -0,0 +1,81 @@
+<html>
+<head>
+  <title>Tests for the dragstart event</title>
+  <link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css">
+  <script type="application/javascript" src="/MochiKit/packed.js"></script>
+  <script type="application/javascript" src="/tests/SimpleTest/SimpleTest.js"></script>      
+  <script type="application/javascript" src="/tests/SimpleTest/EventUtils.js"></script>      
+
+<script>
+  
+var gGotHandlingDrop = false;
+var gGotNotHandlingDrop = false;
+
+SimpleTest.waitForExplicitFinish();
+
+function fireEvent(target, event) {
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  var utils =
+    window.QueryInterface(Components.interfaces.nsIInterfaceRequestor).
+           getInterface(Components.interfaces.nsIDOMWindowUtils);
+  utils.dispatchDOMEventViaPresShell(target, event, true);
+}
+
+function fireDrop(element, dragData, effectAllowed) {
+  netscape.security.PrivilegeManager.enablePrivilege('UniversalXPConnect');
+  var ds = Components.classes["@mozilla.org/widget/dragservice;1"].
+    getService(Components.interfaces.nsIDragService);
+
+  ds.startDragSession();
+
+  var event = document.createEvent("DragEvents");
+  event.initDragEvent("dragover", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+  fireEvent(element, event);
+
+  event = document.createEvent("DragEvents");
+  event.initDragEvent("drop", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null, null);
+  fireEvent(element, event);
+
+  ds.endDragSession(false);
+  ok(!ds.getCurrentSession(), "There shouldn't be a drag session anymore!");
+}
+
+function runTests()
+{
+  var targetHandling = document.getElementById("handling_target");
+  fireDrop(targetHandling, [{"test/plain": "Hello!"}]);
+
+  is(gGotHandlingDrop, true, "Got drop on accepting element (1)");
+  is(gGotNotHandlingDrop, false, "Didn't get drop on unaccepting element (1)");
+
+  // reset
+  gGotHandlingDrop = false;
+  gGotNotHandlingDrop = false;
+
+  var targetNotHandling = document.getElementById("nothandling_target");
+  fireDrop(targetNotHandling, [{"test/plain": "Hello!"}]);
+
+  is(gGotHandlingDrop, false, "Didn't get drop on accepting element (2)");
+  is(gGotNotHandlingDrop, false, "Didn't get drop on unaccepting element (2)");
+
+  SimpleTest.finish();
+}
+
+</script>
+
+<body onload="window.setTimeout(runTests, 0);">
+
+<img style="width: 100px; height: 100px;"
+     src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"
+     id="handling_target"
+     ondragenter="event.preventDefault()"
+     ondragover="event.preventDefault()"
+     ondrop="gGotHandlingDrop = true;">
+
+<img style="width: 100px; height: 100px;"
+     src="data:image/png,%89PNG%0D%0A%1A%0A%00%00%00%0DIHDR%00%00%00%18%00%00%00%18%02%03%00%00%00%9D%19%D5k%00%00%00%04gAMA%00%00%B1%8F%0B%FCa%05%00%00%00%0CPLTE%FF%FF%FF%FF%FF%FF%F7%DC%13%00%00%00%03%80%01X%00%00%00%01tRNS%08N%3DPT%00%00%00%01bKGD%00%88%05%1DH%00%00%00%09pHYs%00%00%0B%11%00%00%0B%11%01%7Fd_%91%00%00%00%07tIME%07%D2%05%0C%14%0C%0D%D8%3F%1FQ%00%00%00%5CIDATx%9C%7D%8E%CB%09%C0%20%10D%07r%B7%20%2F%E9wV0%15h%EA%D9%12D4%BB%C1x%CC%5C%1E%0C%CC%07%C0%9C0%9Dd7()%C0A%D3%8D%E0%B8%10%1DiCHM%D0%AC%D2d%C3M%F1%B4%E7%FF%10%0BY%AC%25%93%CD%CBF%B5%B2%C0%3Alh%CD%AE%13%DF%A5%F7%E0%03byW%09A%B4%F3%E2%00%00%00%00IEND%AEB%60%82"
+     id="nothandling_target"
+     ondrop="gGotNotHandlingDrop = true;">
+
+</body>
+</html>
--- a/dom/base/nsDOMWindowUtils.cpp
+++ b/dom/base/nsDOMWindowUtils.cpp
@@ -35,17 +35,17 @@
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsIDocShell.h"
 #include "nsPresContext.h"
 #include "nsDOMClassInfo.h"
 #include "nsDOMError.h"
 #include "nsIDOMNSEvent.h"
-
+#include "nsIPrivateDOMEvent.h"
 #include "nsDOMWindowUtils.h"
 #include "nsGlobalWindow.h"
 #include "nsIDocument.h"
 #include "nsFocusManager.h"
 #include "nsIEventStateManager.h"
 
 #include "nsIScrollableView.h"
 
@@ -915,8 +915,37 @@ nsDOMWindowUtils::GetCOWForObject()
                             object, rval);
 
   if (NS_FAILED(rv))
     return rv;
 
   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<nsIPresShell> shell = presContext->GetPresShell();
+  NS_ENSURE_STATE(shell);
+  nsCOMPtr<nsIPrivateDOMEvent> event = do_QueryInterface(aEvent);
+  NS_ENSURE_STATE(event);
+  event->SetTrusted(aTrusted);
+  nsEvent* internalEvent = event->GetInternalNSEvent();
+  NS_ENSURE_STATE(internalEvent);
+  nsCOMPtr<nsIContent> 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;
+}
--- a/dom/interfaces/base/nsIDOMWindowUtils.idl
+++ b/dom/interfaces/base/nsIDOMWindowUtils.idl
@@ -40,20 +40,22 @@
 /**
  * nsIDOMWindowUtils is intended for infrequently-used methods related
  * to the current nsIDOMWindow.  Some of the methods may require
  * elevated privileges; the method implementations should contain the
  * necessary security checks.  Access this interface by calling
  * 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 {
 
   /**
    * Image animation mode of the window. When this attribute's value
    * is changed, the implementation should set all images in the window
    * to the given value. That is, when set to kDontAnimMode, all images
    * will stop animating. The attribute's value must be one of the
    * animationMode values from imgIContainer.
@@ -398,9 +400,25 @@ interface nsIDOMWindowUtils : nsISupport
    * Get IME status, see above IME_STATUS_* definitions.
    */
   readonly attribute unsigned long IMEStatus;
 
   /**
    * 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);
 };
--- a/layout/base/nsPresShell.cpp
+++ b/layout/base/nsPresShell.cpp
@@ -6483,16 +6483,27 @@ PresShell::HandleEventInternal(nsEvent* 
     if (NS_IS_TRUSTED_EVENT(aEvent)) {
       switch (aEvent->message) {
       case NS_MOUSE_BUTTON_DOWN:
       case NS_MOUSE_BUTTON_UP:
       case NS_KEY_PRESS:
       case NS_KEY_DOWN:
       case NS_KEY_UP:
         isHandlingUserInput = PR_TRUE;
+        break;
+      case NS_DRAGDROP_DROP:
+        nsCOMPtr<nsIDragSession> session = nsContentUtils::GetDragSession();
+        if (session) {
+          PRBool onlyChromeDrop = PR_FALSE;
+          session->GetOnlyChromeDrop(&onlyChromeDrop);
+          if (onlyChromeDrop) {
+            aEvent->flags |= NS_EVENT_FLAG_ONLY_CHROME_DISPATCH;
+          }
+        }
+        break;
       }
     }
 
     if (aEvent->message == NS_CONTEXTMENU) {
       nsMouseEvent* me = static_cast<nsMouseEvent*>(aEvent);
       if (!CanHandleContextMenuEvent(me, GetCurrentEventFrame())) {
         return NS_OK;
       }
--- a/widget/public/nsGUIEvent.h
+++ b/widget/public/nsGUIEvent.h
@@ -127,16 +127,22 @@ class nsHashKey;
 #define NS_EVENT_DISPATCHED               0x0400
 #define NS_EVENT_FLAG_DISPATCHING         0x0800
 // When an event is synthesized for testing, this flag will be set.
 // Note that this is currently used only with mouse events, because this
 // flag is not needed on other events now.  It could be added to other
 // 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))
 #define NS_EVENT_BUBBLE_MASK              (~(NS_EVENT_FLAG_CAPTURE | NS_EVENT_FLAG_NO_CONTENT_DISPATCH))
 
 #define NS_EVENT_TYPE_NULL                   0
 
 /**
--- a/widget/public/nsIDragSession.idl
+++ b/widget/public/nsIDragSession.idl
@@ -48,24 +48,29 @@
 
 native nsSize (nsSize);
 
 
 interface nsIDOMDocument;
 interface nsIDOMNode;
 interface nsIDOMDataTransfer;
 
-[scriptable, uuid(15860D52-FE2C-4DDD-AC50-9C23E24916C4)]
+[scriptable, uuid(fde41f6a-c710-46f8-a0a8-1ff76ca4ff57)]
 interface nsIDragSession : nsISupports
 {
   /**
     * Set the current state of the drag, whether it can be dropped or not.
     * 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 
     */ 
   attribute unsigned long dragAction;
   
   /**
     * Sets the current width and height of the drag target area. 
--- a/widget/src/xpwidgets/nsBaseDragService.cpp
+++ b/widget/src/xpwidgets/nsBaseDragService.cpp
@@ -71,17 +71,18 @@
 #include "nsIPrefService.h"
 
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 
 #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)
 {
 }
 
 nsBaseDragService::~nsBaseDragService()
 {
 }
@@ -98,16 +99,31 @@ nsBaseDragService::SetCanDrop(PRBool aCa
 
 //---------------------------------------------------------
 NS_IMETHODIMP
 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
 nsBaseDragService::SetDragAction(PRUint32 anAction)
 {
   mDragAction = anAction;
   return NS_OK;
 }
@@ -318,16 +334,18 @@ nsBaseDragService::GetCurrentSession(nsI
 //-------------------------------------------------------------------------
 NS_IMETHODIMP
 nsBaseDragService::StartDragSession()
 {
   if (mDoingDrag) {
     return NS_ERROR_FAILURE;
   }
   mDoingDrag = PR_TRUE;
+  // By default dispatch drop also to content.
+  mOnlyChromeDrop = PR_FALSE;
   return NS_OK;
 }
 
 //-------------------------------------------------------------------------
 NS_IMETHODIMP
 nsBaseDragService::EndDragSession(PRBool aDoneDrag)
 {
   if (!mDoingDrag) {
--- a/widget/src/xpwidgets/nsBaseDragService.h
+++ b/widget/src/xpwidgets/nsBaseDragService.h
@@ -123,16 +123,17 @@ protected:
   /**
    * Convert aScreenX and aScreenY from CSS pixels into unscaled device pixels.
    */
   void
   ConvertToUnscaledDevPixels(nsPresContext* aPresContext,
                              PRInt32* aScreenX, PRInt32* aScreenY);
 
   PRPackedBool mCanDrop;
+  PRPackedBool mOnlyChromeDrop;
   PRPackedBool mDoingDrag;
   // true if mImage should be used to set a drag image
   PRPackedBool mHasImage;
   // true if the user cancelled the drag operation
   PRPackedBool mUserCancelled;
 
   PRUint32 mDragAction;
   nsSize mTargetSize;