Bug 533460 - Allow custom panels/windows to be used as drag/drop feedback images. r=karlt,josh,roc
authorNeil Deakin <enndeakin@gmail.com>
Mon, 25 Apr 2011 18:37:20 -0700
changeset 75739 f86747fb659edf2e66ee42c0004e4b9492940beb
parent 75738 566f15cb9c5b8e01468f51e2b0f1b6ff692c8d3e
child 75740 4ec64d3561f0edf1a33c5e9e7f2009721f0e1027
push id3
push userfelipc@gmail.com
push dateFri, 30 Sep 2011 20:09:13 +0000
reviewerskarlt, josh, roc
bugs533460
milestone9.0a1
Bug 533460 - Allow custom panels/windows to be used as drag/drop feedback images. r=karlt,josh,roc
layout/xul/base/src/nsMenuPopupFrame.cpp
layout/xul/base/src/nsMenuPopupFrame.h
layout/xul/base/src/nsXULPopupManager.cpp
widget/public/nsIDragService.idl
widget/public/nsWidgetInitData.h
widget/src/cocoa/nsChildView.mm
widget/src/cocoa/nsCocoaWindow.mm
widget/src/cocoa/nsDragService.mm
widget/src/gtk2/nsDragService.cpp
widget/src/gtk2/nsWindow.cpp
widget/src/windows/nsDragService.cpp
widget/src/windows/nsNativeDragSource.cpp
widget/src/windows/nsWindow.cpp
widget/src/xpwidgets/nsBaseDragService.cpp
widget/src/xpwidgets/nsBaseDragService.h
--- a/layout/xul/base/src/nsMenuPopupFrame.cpp
+++ b/layout/xul/base/src/nsMenuPopupFrame.cpp
@@ -80,16 +80,17 @@
 #include "nsBindingManager.h"
 #include "nsIDocShellTreeOwner.h"
 #include "nsIBaseWindow.h"
 #include "nsISound.h"
 #include "nsIRootBox.h"
 #include "nsIScreenManager.h"
 #include "nsIServiceManager.h"
 #include "nsThemeConstants.h"
+#include "nsDisplayList.h"
 #include "mozilla/Preferences.h"
 
 using namespace mozilla;
 
 PRInt8 nsMenuPopupFrame::sDefaultLevelIsTop = -1;
 
 // NS_NewMenuPopupFrame
 //
@@ -119,16 +120,17 @@ nsMenuPopupFrame::nsMenuPopupFrame(nsIPr
   mIsOpenChanged(PR_FALSE),
   mIsContextMenu(PR_FALSE),
   mAdjustOffsetForContextMenu(PR_FALSE),
   mGeneratedChildren(PR_FALSE),
   mMenuCanOverlapOSBar(PR_FALSE),
   mShouldAutoPosition(PR_TRUE),
   mInContentShell(PR_TRUE),
   mIsMenuLocked(PR_FALSE),
+  mIsDragPopup(PR_FALSE),
   mHFlip(PR_FALSE),
   mVFlip(PR_FALSE)
 {
   // the preference name is backwards here. True means that the 'top' level is
   // the default, and false means that the 'parent' level is the default.
   if (sDefaultLevelIsTop >= 0)
     return;
   sDefaultLevelIsTop =
@@ -171,16 +173,22 @@ nsMenuPopupFrame::Init(nsIContent*      
     if (namespaceID == kNameSpaceID_XUL) {
       if (tag == nsGkAtoms::menupopup || tag == nsGkAtoms::popup)
         mPopupType = ePopupTypeMenu;
       else if (tag == nsGkAtoms::tooltip)
         mPopupType = ePopupTypeTooltip;
     }
   }
 
+  if (mPopupType == ePopupTypePanel &&
+      aContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::type,
+                            nsGkAtoms::drag, eIgnoreCase)) {
+    mIsDragPopup = PR_TRUE;
+  }
+
   nsCOMPtr<nsISupports> cont = PresContext()->GetContainer();
   nsCOMPtr<nsIDocShellTreeItem> dsti = do_QueryInterface(cont);
   PRInt32 type = -1;
   if (dsti && NS_SUCCEEDED(dsti->GetItemType(&type)) &&
       type == nsIDocShellTreeItem::typeChrome)
     mInContentShell = PR_FALSE;
 
   // To improve performance, create the widget for the popup only if it is not
@@ -269,16 +277,17 @@ nsMenuPopupFrame::CreateWidgetForView(ns
 {
   // Create a widget for ourselves.
   nsWidgetInitData widgetData;
   widgetData.mWindowType = eWindowType_popup;
   widgetData.mBorderStyle = eBorderStyle_default;
   widgetData.clipSiblings = PR_TRUE;
   widgetData.mPopupHint = mPopupType;
   widgetData.mNoAutoHide = IsNoAutoHide();
+  widgetData.mIsDragPopup = mIsDragPopup;
 
   nsAutoString title;
   if (mContent && widgetData.mNoAutoHide) {
     if (mContent->AttrValueIs(kNameSpaceID_None, nsGkAtoms::titlebar,
                               nsGkAtoms::normal, eCaseMatters)) {
       widgetData.mBorderStyle = eBorderStyle_title;
 
       mContent->GetAttr(kNameSpaceID_None, nsGkAtoms::label, title);
@@ -1110,16 +1119,22 @@ nsMenuPopupFrame::FlipOrResize(nscoord& 
 }
 
 nsresult
 nsMenuPopupFrame::SetPopupPosition(nsIFrame* aAnchorFrame, PRBool aIsMove)
 {
   if (!mShouldAutoPosition)
     return NS_OK;
 
+  // If this is due to a move, return early if the popup hasn't been laid out
+  // yet. On Windows, this can happen when using a drag popup before it opens.
+  if (aIsMove && (mPrefSize.width == -1 || mPrefSize.height == -1)) {
+    return NS_OK;
+  }
+
   nsPresContext* presContext = PresContext();
   nsIFrame* rootFrame = presContext->PresShell()->FrameManager()->GetRootFrame();
   NS_ASSERTION(rootFrame->GetView() && GetView() &&
                rootFrame->GetView() == GetView()->GetParent(),
                "rootFrame's view is not our view's parent???");
 
   // if the frame is not specified, use the anchor node passed to OpenPopup. If
   // that wasn't specified either, use the root frame. Note that mAnchorContent
@@ -1763,16 +1778,29 @@ nsMenuPopupFrame::GetWidget(nsIWidget **
 }
 
 void
 nsMenuPopupFrame::AttachedDismissalListener()
 {
   mConsumeRollupEvent = nsIPopupBoxObject::ROLLUP_DEFAULT;
 }
 
+nsresult
+nsMenuPopupFrame::BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                                   const nsRect&           aDirtyRect,
+                                   const nsDisplayListSet& aLists)
+{
+  // don't pass events to drag popups
+  if (aBuilder->IsForEventDelivery() && mIsDragPopup) {
+    return NS_OK;
+  }
+
+  return nsBoxFrame::BuildDisplayList(aBuilder, aDirtyRect, aLists);
+}
+
 // helpers /////////////////////////////////////////////////////////////
 
 NS_IMETHODIMP 
 nsMenuPopupFrame::AttributeChanged(PRInt32 aNameSpaceID,
                                    nsIAtom* aAttribute,
                                    PRInt32 aModType)
 
 {
--- a/layout/xul/base/src/nsMenuPopupFrame.h
+++ b/layout/xul/base/src/nsMenuPopupFrame.h
@@ -235,16 +235,18 @@ public:
   // reset the current incremental search string, calculated in
   // FindMenuWithShortcut.
   nsMenuFrame* Enter(nsGUIEvent* aEvent);
 
   nsPopupType PopupType() const { return mPopupType; }
   PRBool IsMenu() { return mPopupType == ePopupTypeMenu; }
   PRBool IsOpen() { return mPopupState == ePopupOpen || mPopupState == ePopupOpenAndVisible; }
 
+  PRBool IsDragPopup() { return mIsDragPopup; }
+
   // returns the parent menupopup, if any
   nsMenuFrame* GetParentMenu() {
     nsIFrame* parent = GetParent();
     if (parent && parent->GetType() == nsGkAtoms::menuFrame) {
       return static_cast<nsMenuFrame *>(parent);
     }
     return nsnull;
   }
@@ -340,16 +342,19 @@ public:
   PRBool IsAnchored() const { return mScreenXPos == -1 && mScreenYPos == -1; }
 
   // Return the anchor if there is one.
   nsIContent* GetAnchor() const { return mAnchorContent; }
 
   // Return the screen coordinates of the popup, or (-1, -1) if anchored.
   nsIntPoint ScreenPosition() const { return nsIntPoint(mScreenXPos, mScreenYPos); }
 
+  NS_IMETHOD BuildDisplayList(nsDisplayListBuilder*   aBuilder,
+                              const nsRect&           aDirtyRect,
+                              const nsDisplayListSet& aLists);
 protected:
 
   // returns the popup's level.
   nsPopupLevel PopupLevel(PRBool aIsNoAutoHide) const;
 
   // redefine to tell the box system not to move the views.
   virtual void GetLayoutFlags(PRUint32& aFlags);
 
@@ -444,16 +449,17 @@ protected:
   // true if we need to offset the popup to ensure it's not under the mouse
   PRPackedBool mAdjustOffsetForContextMenu;
   PRPackedBool mGeneratedChildren; // true if the contents have been created
 
   PRPackedBool mMenuCanOverlapOSBar;    // can we appear over the taskbar/menubar?
   PRPackedBool mShouldAutoPosition; // Should SetPopupPosition be allowed to auto position popup?
   PRPackedBool mInContentShell; // True if the popup is in a content shell
   PRPackedBool mIsMenuLocked; // Should events inside this menu be ignored?
+  PRPackedBool mIsDragPopup; // True if this is a popup used for drag feedback
 
   // the flip modes that were used when the popup was opened
   PRPackedBool mHFlip;
   PRPackedBool mVFlip;
 
   static PRInt8 sDefaultLevelIsTop;
 }; // class nsMenuPopupFrame
 
--- a/layout/xul/base/src/nsXULPopupManager.cpp
+++ b/layout/xul/base/src/nsXULPopupManager.cpp
@@ -1397,18 +1397,21 @@ nsXULPopupManager::GetVisiblePopups()
   while (item) {
     if (item->Frame()->PopupState() == ePopupOpenAndVisible)
       popups.AppendElement(static_cast<nsIFrame*>(item->Frame()));
     item = item->GetParent();
   }
 
   item = mNoHidePanels;
   while (item) {
-    if (item->Frame()->PopupState() == ePopupOpenAndVisible)
+    // skip panels which are not open and visible as well as draggable popups,
+    // as those don't respond to events.
+    if (item->Frame()->PopupState() == ePopupOpenAndVisible && !item->Frame()->IsDragPopup()) {
       popups.AppendElement(static_cast<nsIFrame*>(item->Frame()));
+    }
     item = item->GetParent();
   }
 
   return popups;
 }
 
 already_AddRefed<nsIDOMNode>
 nsXULPopupManager::GetLastTriggerNode(nsIDocument* aDocument, PRBool aIsTooltip)
--- a/widget/public/nsIDragService.idl
+++ b/widget/public/nsIDragService.idl
@@ -140,14 +140,16 @@ interface nsIDragService : nsISupports
   void fireDragEventAtSource ( in unsigned long aMsg );
 
   /**
    * Increase/decrease dragging suppress level by one.
    * If level is greater than one, dragging is disabled.
    */
   void suppress();
   void unsuppress();
+
+  [noscript] void dragMoved(in long aX, in long aY);
 };
 
 
 %{ C++
 
 %}
--- a/widget/public/nsWidgetInitData.h
+++ b/widget/public/nsWidgetInitData.h
@@ -141,11 +141,12 @@ struct nsWidgetInitData {
   nsPopupType   mPopupHint;
   nsPopupLevel  mPopupLevel;
   // when painting exclude area occupied by child windows and sibling windows
   PRPackedBool  clipChildren, clipSiblings, mDropShadow;
   PRPackedBool  mListenForResizes;
   PRPackedBool  mUnicode;
   PRPackedBool  mRTL;
   PRPackedBool  mNoAutoHide; // true for noautohide panels
+  PRPackedBool  mIsDragPopup;  // true for drag feedback panels
 };
 
 #endif // nsWidgetInitData_h__
--- a/widget/src/cocoa/nsChildView.mm
+++ b/widget/src/cocoa/nsChildView.mm
@@ -4499,16 +4499,33 @@ NSEvent* gLastDragMouseDownEvent = nil;
 {
   nsAutoRetainCocoaObject kungFuDeathGrip(self);
   BOOL handled = [self doDragAction:NS_DRAGDROP_DROP sender:sender] != NSDragOperationNone;
   NS_IF_RELEASE(mDragService);
   return handled;
 }
 
 // NSDraggingSource
+- (void)draggedImage:(NSImage *)anImage movedTo:(NSPoint)aPoint
+{
+  // Get the drag service if it isn't already cached. The drag service
+  // isn't cached when dragging over a different application.
+  nsCOMPtr<nsIDragService> dragService = mDragService;
+  if (!dragService) {
+    dragService = do_GetService(kDragServiceContractID);
+  }
+
+  if (dragService) {
+    NSPoint pnt = [NSEvent mouseLocation];
+    FlipCocoaScreenCoordinate(pnt);
+    dragService->DragMoved(NSToIntRound(pnt.x), NSToIntRound(pnt.y));
+  }
+}
+
+// NSDraggingSource
 - (void)draggedImage:(NSImage *)anImage endedAt:(NSPoint)aPoint operation:(NSDragOperation)operation
 {
   NS_OBJC_BEGIN_TRY_ABORT_BLOCK;
 
   gDraggedTransferables = nsnull;
 
   NSEvent *currentEvent = [NSApp currentEvent];
   gUserCancelledDrag = ([currentEvent type] == NSKeyDown &&
--- a/widget/src/cocoa/nsCocoaWindow.mm
+++ b/widget/src/cocoa/nsCocoaWindow.mm
@@ -293,18 +293,22 @@ nsresult nsCocoaWindow::Create(nsIWidget
   // Applications that use native popups don't want us to create popup windows.
   if ((mWindowType == eWindowType_popup) && UseNativePopupWindows())
     return NS_OK;
 
   nsresult rv = CreateNativeWindow(nsCocoaUtils::GeckoRectToCocoaRect(newBounds),
                                    mBorderStyle, PR_FALSE);
   NS_ENSURE_SUCCESS(rv, rv);
 
-  if (mWindowType == eWindowType_popup)
+  if (mWindowType == eWindowType_popup) {
+    if (aInitData->mIsDragPopup) {
+      [mWindow setIgnoresMouseEvents:YES];
+    }
     return CreatePopupContentView(newBounds, aHandleEventFunction, aContext, aAppShell, aToolkit);
+  }
 
   return NS_OK;
 
   NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT;
 }
 
 static unsigned int WindowMaskForBorderStyle(nsBorderStyle aBorderStyle)
 {
--- a/widget/src/cocoa/nsDragService.mm
+++ b/widget/src/cocoa/nsDragService.mm
@@ -297,16 +297,17 @@ nsDragService::InvokeDragSession(nsIDOMN
 
   point = [[gLastDragView window] convertScreenToBase: point];
   NSPoint localPoint = [gLastDragView convertPoint:point fromView:nil];
  
   // Save the transferables away in case a promised file callback is invoked.
   gDraggedTransferables = aTransferableArray;
 
   nsBaseDragService::StartDragSession();
+  nsBaseDragService::OpenDragPopup();
 
   // We need to retain the view and the event during the drag in case either gets destroyed.
   mNativeDragView = [gLastDragView retain];
   mNativeDragEvent = [gLastDragMouseDownEvent retain];
 
   gUserCancelledDrag = PR_FALSE;
   [mNativeDragView dragImage:image
                           at:localPoint
--- a/widget/src/gtk2/nsDragService.cpp
+++ b/widget/src/gtk2/nsDragService.cpp
@@ -64,16 +64,17 @@
 
 #include "gfxASurface.h"
 #include "gfxXlibSurface.h"
 #include "gfxContext.h"
 #include "nsImageToPixbuf.h"
 #include "nsPresContext.h"
 #include "nsIDocument.h"
 #include "nsISelection.h"
+#include "nsIFrame.h"
 
 // This sets how opaque the drag image is
 #define DRAG_IMAGE_ALPHA_LEVEL 0.5
 
 // These values are copied from GtkDragResult (rather than using GtkDragResult
 // directly) so that this code can be compiled against versions of GTK+ that
 // do not have GtkDragResult.
 // GtkDragResult is available from GTK+ version 2.12.
@@ -1563,30 +1564,51 @@ nsDragService::SourceDataGet(GtkWidget  
                 return;
             }
         }
     }
 }
 
 void nsDragService::SetDragIcon(GdkDragContext* aContext)
 {
+    if (!mHasImage && !mSelection)
+        return;
+
     nsIntRect dragRect;
     nsPresContext* pc;
     nsRefPtr<gfxASurface> surface;
-    if (mHasImage || mSelection) {
-      DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY,
-               &dragRect, getter_AddRefs(surface), &pc);
-    }
+    DrawDrag(mSourceNode, mSourceRegion, mScreenX, mScreenY,
+             &dragRect, getter_AddRefs(surface), &pc);
+    if (!pc)
+        return;
+
+    PRInt32 sx = mScreenX, sy = mScreenY;
+    ConvertToUnscaledDevPixels(pc, &sx, &sy);
+
+    PRInt32 offsetX = sx - dragRect.x;
+    PRInt32 offsetY = sy - dragRect.y;
 
-    if (surface) {
-        PRInt32 sx = mScreenX, sy = mScreenY;
-        ConvertToUnscaledDevPixels(pc, &sx, &sy);
-
-        PRInt32 offsetX = sx - dragRect.x;
-        PRInt32 offsetY = sy - dragRect.y;
+    // If a popup is set as the drag image, use its widget. Otherwise, use
+    // the surface that DrawDrag created.
+    if (mDragPopup) {
+        GtkWidget* gtkWidget = nsnull;
+        nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+        if (frame) {
+            // DrawDrag ensured that this is a popup frame.
+            nsCOMPtr<nsIWidget> widget = frame->GetNearestWidget();
+            if (widget) {
+                gtkWidget = (GtkWidget *)widget->GetNativeData(NS_NATIVE_SHELLWIDGET);
+                if (gtkWidget) {
+                    OpenDragPopup();
+                    gtk_drag_set_icon_widget(aContext, gtkWidget, offsetX, offsetY);
+                }
+            }
+        }
+    }
+    else if (surface) {
         if (!SetAlphaPixmap(surface, aContext, offsetX, offsetY, dragRect)) {
             GdkPixbuf* dragPixbuf =
               nsImageToPixbuf::SurfaceToPixbuf(surface, dragRect.width, dragRect.height);
             if (dragPixbuf) {
                 gtk_drag_set_icon_pixbuf(aContext, dragPixbuf, offsetX, offsetY);
                 g_object_unref(dragPixbuf);
             }
         }
--- a/widget/src/gtk2/nsWindow.cpp
+++ b/widget/src/gtk2/nsWindow.cpp
@@ -4054,26 +4054,31 @@ nsWindow::Create(nsIWidget        *aPare
                 // WM_TAKE_FOCUS, focus is requested on the parent window.
                 gtk_widget_realize(mShell);
                 gdk_window_add_filter(mShell->window,
                                       popup_take_focus_filter, NULL); 
 #endif
             }
 
             GdkWindowTypeHint gtkTypeHint;
-            switch (aInitData->mPopupHint) {
-                case ePopupTypeMenu:
-                    gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
-                    break;
-                case ePopupTypeTooltip:
-                    gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
-                    break;
-                default:
-                    gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
-                    break;
+            if (aInitData->mIsDragPopup) {
+                gtkTypeHint = GDK_WINDOW_TYPE_HINT_DND;
+            }
+            else {
+                switch (aInitData->mPopupHint) {
+                    case ePopupTypeMenu:
+                        gtkTypeHint = GDK_WINDOW_TYPE_HINT_POPUP_MENU;
+                        break;
+                    case ePopupTypeTooltip:
+                        gtkTypeHint = GDK_WINDOW_TYPE_HINT_TOOLTIP;
+                        break;
+                    default:
+                        gtkTypeHint = GDK_WINDOW_TYPE_HINT_UTILITY;
+                        break;
+                }
             }
             gtk_window_set_type_hint(GTK_WINDOW(mShell), gtkTypeHint);
 
             if (topLevelParent) {
                 gtk_window_set_transient_for(GTK_WINDOW(mShell),
                                             topLevelParent);
                 mTransientParent = topLevelParent;
 
--- a/widget/src/windows/nsDragService.cpp
+++ b/widget/src/windows/nsDragService.cpp
@@ -293,21 +293,21 @@ nsDragService::StartInvokingDragSession(
   }
   if (aActionType & DRAGDROP_ACTION_LINK) {
     effects |= DROPEFFECT_LINK;
   }
 
   // XXX not sure why we bother to cache this, it can change during
   // the drag
   mDragAction = aActionType;
-  mDoingDrag  = PR_TRUE;
   mSentLocalDropEvent = PR_FALSE;
 
   // Start dragging
   StartDragSession();
+  OpenDragPopup();
 
   nsRefPtr<IAsyncOperation> pAsyncOp;
   // Offer to do an async drag
   if (SUCCEEDED(aDataObj->QueryInterface(IID_IAsyncOperation,
                                          getter_AddRefs(pAsyncOp)))) {
     pAsyncOp->SetAsyncMode(VARIANT_TRUE);
   } else {
     NS_NOTREACHED("When did our data object stop being async");
--- a/widget/src/windows/nsNativeDragSource.cpp
+++ b/widget/src/windows/nsNativeDragSource.cpp
@@ -34,16 +34,22 @@
  * the terms of any one of the MPL, the GPL or the LGPL.
  *
  * ***** END LICENSE BLOCK ***** */
 
 #include "nsNativeDragSource.h"
 #include <stdio.h>
 #include "nsISupportsImpl.h"
 #include "nsString.h"
+#include "nsIServiceManager.h"
+#include "nsToolkit.h"
+#include "nsWidgetsCID.h"
+#include "nsIDragService.h"
+
+static NS_DEFINE_IID(kCDragServiceCID,  NS_DRAGSERVICE_CID);
 
 /*
  * class nsNativeDragSource
  */
 nsNativeDragSource::nsNativeDragSource(nsIDOMDataTransfer* aDataTransfer) :
   m_cRef(0),
   m_hCursor(nsnull),
   mUserCancelled(PR_FALSE)
@@ -89,16 +95,22 @@ nsNativeDragSource::Release(void)
 
   delete this;
   return 0;
 }
 
 STDMETHODIMP
 nsNativeDragSource::QueryContinueDrag(BOOL fEsc, DWORD grfKeyState)
 {
+  nsCOMPtr<nsIDragService> dragService = do_GetService(kCDragServiceCID);
+  if (dragService) {
+    DWORD pos = ::GetMessagePos();
+    dragService->DragMoved(GET_X_LPARAM(pos), GET_Y_LPARAM(pos));
+  }
+
   if (fEsc) {
     mUserCancelled = PR_TRUE;
     return DRAGDROP_S_CANCEL;
   }
 
   if (!(grfKeyState & MK_LBUTTON) || (grfKeyState & MK_RBUTTON))
     return DRAGDROP_S_DROP;
 
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -528,16 +528,21 @@ nsWindow::Create(nsIWidget *aParent,
   mIsRTL = aInitData->mRTL;
 
   DWORD style = WindowStyle();
   DWORD extendedStyle = WindowExStyle();
 
   if (mWindowType == eWindowType_popup) {
     if (!aParent)
       parent = NULL;
+
+    if (aInitData->mIsDragPopup) {
+      // This flag makes the window transparent to mouse events
+      extendedStyle |= WS_EX_TRANSPARENT;
+    }
   } else if (mWindowType == eWindowType_invisible) {
     // Make sure CreateWindowEx succeeds at creating a toplevel window
     style &= ~0x40000000; // WS_CHILDWINDOW
   } else {
     // See if the caller wants to explictly set clip children and clip siblings
     if (aInitData->clipChildren) {
       style |= WS_CLIPCHILDREN;
     } else {
--- a/widget/src/xpwidgets/nsBaseDragService.cpp
+++ b/widget/src/xpwidgets/nsBaseDragService.cpp
@@ -60,16 +60,18 @@
 #include "nsIDOMDataTransfer.h"
 #include "nsICanvasElementExternal.h"
 #include "nsIImageLoadingContent.h"
 #include "imgIContainer.h"
 #include "imgIRequest.h"
 #include "nsIViewObserver.h"
 #include "nsRegion.h"
 #include "nsGUIEvent.h"
+#include "nsXULPopupManager.h"
+#include "nsMenuPopupFrame.h"
 #include "mozilla/Preferences.h"
 
 #include "gfxContext.h"
 #include "gfxPlatform.h"
 
 using namespace mozilla;
 
 #define DRAGIMAGES_PREF "nglayout.enable_drag_images"
@@ -267,16 +269,17 @@ nsBaseDragService::InvokeDragSessionWith
 {
   NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aDataTransfer, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
 
   mDataTransfer = aDataTransfer;
   mSelection = nsnull;
   mHasImage = PR_TRUE;
+  mDragPopup = nsnull;
   mImage = aImage;
   mImageX = aImageX;
   mImageY = aImageY;
 
   aDragEvent->GetScreenX(&mScreenX);
   aDragEvent->GetScreenY(&mScreenY);
 
   nsCOMPtr<nsIDOMNSMouseEvent> mouseEvent = do_QueryInterface(aDragEvent);
@@ -294,16 +297,17 @@ nsBaseDragService::InvokeDragSessionWith
 {
   NS_ENSURE_TRUE(aSelection, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(aDragEvent, NS_ERROR_NULL_POINTER);
   NS_ENSURE_TRUE(mSuppressLevel == 0, NS_ERROR_FAILURE);
 
   mDataTransfer = aDataTransfer;
   mSelection = aSelection;
   mHasImage = PR_TRUE;
+  mDragPopup = nsnull;
   mImage = nsnull;
   mImageX = 0;
   mImageY = 0;
 
   aDragEvent->GetScreenX(&mScreenX);
   aDragEvent->GetScreenY(&mScreenY);
 
   nsCOMPtr<nsIDOMNSMouseEvent> mouseEvent = do_QueryInterface(aDragEvent);
@@ -342,39 +346,59 @@ 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;
 }
 
+void
+nsBaseDragService::OpenDragPopup()
+{
+  if (mDragPopup) {
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+      pm->ShowPopupAtScreen(mDragPopup, mScreenX - mImageX, mScreenY - mImageY, PR_FALSE, nsnull);
+    }
+  }
+}
+
 //-------------------------------------------------------------------------
 NS_IMETHODIMP
 nsBaseDragService::EndDragSession(PRBool aDoneDrag)
 {
   if (!mDoingDrag) {
     return NS_ERROR_FAILURE;
   }
 
   if (aDoneDrag && !mSuppressLevel)
     FireDragEventAtSource(NS_DRAGDROP_END);
 
+  if (mDragPopup) {
+    nsXULPopupManager* pm = nsXULPopupManager::GetInstance();
+    if (pm) {
+      pm->HidePopup(mDragPopup, PR_FALSE, PR_TRUE, PR_FALSE);
+    }
+  }
+
   mDoingDrag = PR_FALSE;
 
   // release the source we've been holding on to.
   mSourceDocument = nsnull;
   mSourceNode = nsnull;
   mSelection = nsnull;
   mDataTransfer = nsnull;
   mHasImage = PR_FALSE;
   mUserCancelled = PR_FALSE;
+  mDragPopup = nsnull;
   mImage = nsnull;
   mImageX = 0;
   mImageY = 0;
   mScreenX = -1;
   mScreenY = -1;
   mInputSource = nsIDOMNSMouseEvent::MOZ_SOURCE_MOUSE;
 
   return NS_OK;
@@ -401,16 +425,33 @@ nsBaseDragService::FireDragEventAtSource
         return presShell->HandleDOMEventWithTarget(content, &event, &status);
       }
     }
   }
 
   return NS_OK;
 }
 
+/* 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(PRInt32 aX, PRInt32 aY)
+{
+  if (mDragPopup) {
+    nsIFrame* frame = mDragPopup->GetPrimaryFrame();
+    if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
+      (static_cast<nsMenuPopupFrame *>(frame))->MoveTo(aX - mImageX, aY - mImageY, PR_TRUE);
+    }
+  }
+
+  return NS_OK;
+}
+
 static nsIPresShell*
 GetPresShellForContent(nsIDOMNode* aDOMNode)
 {
   nsCOMPtr<nsIContent> content = do_QueryInterface(aDOMNode);
   if (!content)
     return nsnull;
 
   nsCOMPtr<nsIDocument> document = content->GetCurrentDoc();
@@ -432,18 +473,18 @@ nsBaseDragService::DrawDrag(nsIDOMNode* 
                             nsPresContext** aPresContext)
 {
   *aSurface = nsnull;
   *aPresContext = nsnull;
 
   // use a default size, in case of an error.
   aScreenDragRect->x = aScreenX - mImageX;
   aScreenDragRect->y = aScreenY - mImageY;
-  aScreenDragRect->width = 20;
-  aScreenDragRect->height = 20;
+  aScreenDragRect->width = 1;
+  aScreenDragRect->height = 1;
 
   // if a drag image was specified, use that, otherwise, use the source node
   nsCOMPtr<nsIDOMNode> 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
   nsIPresShell* presShell = GetPresShellForContent(dragNode);
   if (!presShell && mImage)
@@ -493,45 +534,58 @@ nsBaseDragService::DrawDrag(nsIDOMNode* 
   if (mSelection) {
     nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
     nsRefPtr<gfxASurface> surface = presShell->RenderSelection(mSelection, pnt, aScreenDragRect);
     *aSurface = surface;
     NS_IF_ADDREF(*aSurface);
     return NS_OK;
   }
 
-  // if an custom image was specified, check if it is an image node and draw
+  // 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, fall through to RenderNode below.
+  // an image or canvas, fall through to RenderNode below.
   if (mImage) {
     nsCOMPtr<nsICanvasElementExternal> canvas = do_QueryInterface(dragNode);
     if (canvas) {
       return DrawDragForImage(*aPresContext, nsnull, canvas, aScreenX,
                               aScreenY, 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, nsnull, aScreenX,
                               aScreenY, 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
+
+    nsCOMPtr<nsIContent> content = do_QueryInterface(dragNode);
+    nsIFrame* frame = content->GetPrimaryFrame();
+    if (frame && frame->GetType() == nsGkAtoms::menuPopupFrame) {
+      mDragPopup = content;
+    }
   }
 
-  // otherwise, just draw the node
-  nsIntRegion clipRegion;
-  if (aRegion) {
-    aRegion->GetRegion(&clipRegion);
+  nsRefPtr<gfxASurface> surface;
+  if (!mDragPopup) {
+    // otherwise, just draw the node
+    nsIntRegion clipRegion;
+    if (aRegion) {
+      aRegion->GetRegion(&clipRegion);
+    }
+
+    nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
+    surface = presShell->RenderNode(dragNode, aRegion ? &clipRegion : nsnull,
+                                    pnt, aScreenDragRect);
   }
 
-  nsIntPoint pnt(aScreenDragRect->x, aScreenDragRect->y);
-  nsRefPtr<gfxASurface> surface =
-    presShell->RenderNode(dragNode, aRegion ? &clipRegion : nsnull,
-                          pnt, aScreenDragRect);
-
   // if an image was specified, reposition the drag rectangle to
   // the supplied offset in mImageX and mImageY.
   if (mImage) {
     aScreenDragRect->x = aScreenX - mImageX;
     aScreenDragRect->y = aScreenY - mImageY;
   }
 
   *aSurface = surface;
--- a/widget/src/xpwidgets/nsBaseDragService.h
+++ b/widget/src/xpwidgets/nsBaseDragService.h
@@ -39,16 +39,17 @@
 #define nsBaseDragService_h__
 
 #include "nsIDragService.h"
 #include "nsIDragSession.h"
 #include "nsITransferable.h"
 #include "nsISupportsArray.h"
 #include "nsIDOMDocument.h"
 #include "nsIDOMDataTransfer.h"
+#include "nsIContent.h"
 #include "nsCOMPtr.h"
 #include "nsPoint.h"
 
 #include "gfxImageSurface.h"
 
 // translucency level for drag images
 #define DRAG_TRANSLUCENCY 0.65
 
@@ -124,16 +125,21 @@ protected:
 
   /**
    * Convert aScreenX and aScreenY from CSS pixels into unscaled device pixels.
    */
   void
   ConvertToUnscaledDevPixels(nsPresContext* aPresContext,
                              PRInt32* aScreenX, PRInt32* aScreenY);
 
+  /**
+   * If the drag image is a popup, open the popup when the drag begins.
+   */
+  void OpenDragPopup();
+
   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;
 
@@ -148,16 +154,20 @@ protected:
   nsCOMPtr<nsIDOMNode> mImage;
   // offset of cursor within the image 
   PRInt32 mImageX;
   PRInt32 mImageY;
 
   // set if a selection is being dragged
   nsCOMPtr<nsISelection> mSelection;
 
+  // set if the image in mImage is a popup. If this case, the popup will be opened
+  // and moved instead of using a drag image.
+  nsCOMPtr<nsIContent> mDragPopup;
+
   // the screen position where drag gesture occurred, used for positioning the
   // drag image when no image is specified. If a value is -1, no event was
   // supplied so the screen position is not known
   PRInt32 mScreenX;
   PRInt32 mScreenY;
 
   // the screen position where the drag ended
   nsIntPoint mEndDragPoint;