Bug 50660 - Drag and drop for file upload form control r=jst,enndeakin
authorMichael Ventnor <mventnor@mozilla.com>
Wed, 01 Jun 2011 16:06:38 +1000
changeset 70389 5c6d107ede5a071a20ec087ca77fcb0f9abf473f
parent 70388 316299946b8ff6561e3c7bcbc5b93c68062b1252
child 70390 16dc1fd9c28a920c1669e3a9963ca810f08432ab
child 70412 c2360fe696603dcd48dbda51074d6f12b60fe9ef
push id20312
push usermventnor@mozilla.com
push dateWed, 01 Jun 2011 06:22:22 +0000
treeherdermozilla-central@5c6d107ede5a [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjst, enndeakin
bugs50660
milestone7.0a1
first release with
nightly linux32
5c6d107ede5a / 7.0a1 / 20110601030746 / files
nightly linux64
5c6d107ede5a / 7.0a1 / 20110601030746 / files
nightly mac
5c6d107ede5a / 7.0a1 / 20110601030746 / files
nightly win32
5c6d107ede5a / 7.0a1 / 20110601030746 / files
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
releases
nightly linux32
nightly linux64
nightly mac
nightly win32
Bug 50660 - Drag and drop for file upload form control r=jst,enndeakin
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
layout/forms/nsFileControlFrame.cpp
layout/forms/nsFileControlFrame.h
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -1367,16 +1367,41 @@ nsHTMLInputElement::GetDisplayFileName(n
 
 void
 nsHTMLInputElement::SetFiles(const nsCOMArray<nsIDOMFile>& aFiles,
                              bool aSetValueChanged)
 {
   mFiles.Clear();
   mFiles.AppendObjects(aFiles);
 
+  AfterSetFiles(aSetValueChanged);
+}
+
+void
+nsHTMLInputElement::SetFiles(nsIDOMFileList* aFiles,
+                             bool aSetValueChanged)
+{
+  mFiles.Clear();
+
+  if (aFiles) {
+    PRUint32 listLength;
+    aFiles->GetLength(&listLength);
+    for (PRUint32 i = 0; i < listLength; i++) {
+      nsCOMPtr<nsIDOMFile> file;
+      aFiles->Item(i, getter_AddRefs(file));
+      mFiles.AppendObject(file);
+    }
+  }
+
+  AfterSetFiles(aSetValueChanged);
+}
+
+void
+nsHTMLInputElement::AfterSetFiles(bool aSetValueChanged)
+{
   // No need to flush here, if there's no frame at this point we
   // don't need to force creation of one just to tell it about this
   // new value.  We just want the display to update as needed.
   nsIFormControlFrame* formControlFrame = GetFormControlFrame(PR_FALSE);
   if (formControlFrame) {
     nsAutoString readableValue;
     GetDisplayFileName(readableValue);
     formControlFrame->SetFormProperty(nsGkAtoms::value, readableValue);
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -140,21 +140,21 @@ public:
   // nsIPhonetic
   NS_DECL_NSIPHONETIC
 
   // nsIDOMNSEditableElement
   NS_IMETHOD GetEditor(nsIEditor** aEditor)
   {
     return nsGenericHTMLElement::GetEditor(aEditor);
   }
-
-  // Forward nsIDOMHTMLElement
-  NS_FORWARD_NSIDOMHTMLELEMENT_NOFOCUSCLICK(nsGenericHTMLFormElement::)
-  NS_IMETHOD Focus();
-  NS_IMETHOD Click();
+
+  // Forward nsIDOMHTMLElement
+  NS_FORWARD_NSIDOMHTMLELEMENT_NOFOCUSCLICK(nsGenericHTMLFormElement::)
+  NS_IMETHOD Focus();
+  NS_IMETHOD Click();
 
   NS_IMETHOD SetUserInput(const nsAString& aInput);
 
   // Overriden nsIFormControl methods
   NS_IMETHOD_(PRUint32) GetType() const { return mType; }
   NS_IMETHOD Reset();
   NS_IMETHOD SubmitNamesValues(nsFormSubmission* aFormSubmission);
   NS_IMETHOD SaveState();
@@ -213,16 +213,17 @@ public:
   NS_IMETHOD_(void) UpdatePlaceholderText(PRBool aNotify);
   NS_IMETHOD_(void) SetPlaceholderClass(PRBool aVisible, PRBool aNotify);
   NS_IMETHOD_(void) InitializeKeyboardEventListeners();
   NS_IMETHOD_(void) OnValueChanged(PRBool aNotify);
 
   void GetDisplayFileName(nsAString& aFileName) const;
   const nsCOMArray<nsIDOMFile>& GetFiles() const;
   void SetFiles(const nsCOMArray<nsIDOMFile>& aFiles, bool aSetValueChanged);
+  void SetFiles(nsIDOMFileList* aFiles, bool aSetValueChanged);
 
   void SetCheckedChangedInternal(PRBool aCheckedChanged);
   PRBool GetCheckedChanged() const {
     return GET_BOOLBIT(mBitField, BF_CHECKED_CHANGED);
   }
   void AddedToRadioGroup();
   void WillRemoveFromRadioGroup();
 
@@ -453,16 +454,21 @@ protected:
   nsresult MaybeSubmitForm(nsPresContext* aPresContext);
 
   /**
    * Update mFileList with the currently selected file.
    */
   nsresult UpdateFileList();
 
   /**
+   * Called after calling one of the SetFiles() functions.
+   */
+  void AfterSetFiles(bool aSetValueChanged);
+
+  /**
    * Determine whether the editor needs to be initialized explicitly for
    * a particular event.
    */
   PRBool NeedToInitializeEditorForEvent(nsEventChainPreVisitor& aVisitor) const;
 
   /**
    * Get the value mode of the element, depending of the type.
    */
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -88,16 +88,19 @@
 #include "nsDirectoryServiceDefs.h"
 #include "nsCharSeparatedTokenizer.h"
 #include "nsHTMLInputElement.h"
 #include "nsICapturePicker.h"
 #include "nsIFileURL.h"
 #include "nsDOMFile.h"
 #include "nsEventStates.h"
 
+#include "nsIDOMDOMStringList.h"
+#include "nsIDOMDragEvent.h"
+
 namespace dom = mozilla::dom;
 
 #define SYNC_TEXT 0x1
 #define SYNC_BUTTON 0x2
 
 nsIFrame*
 NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
@@ -131,16 +134,25 @@ nsFileControlFrame::Init(nsIContent* aCo
 }
 
 void
 nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   mTextFrame = nsnull;
   ENSURE_TRUE(mContent);
 
+  // Remove the drag events
+  nsCOMPtr<nsIDOMEventTarget> dragTarget = do_QueryInterface(mContent);
+  if (dragTarget) {
+    dragTarget->RemoveEventListener(NS_LITERAL_STRING("drop"),
+                                    mMouseListener, PR_FALSE);
+    dragTarget->RemoveEventListener(NS_LITERAL_STRING("dragover"),
+                                    mMouseListener, PR_FALSE);
+  }
+
   // remove mMouseListener as a mouse event listener (bug 40533, bug 355931)
   NS_NAMED_LITERAL_STRING(click, "click");
 
   nsCOMPtr<nsIDOMEventGroup> systemGroup;
   mContent->GetSystemEventGroup(getter_AddRefs(systemGroup));
 
   nsCOMPtr<nsIDOM3EventTarget> dom3Capture = do_QueryInterface(mCapture);
   if (dom3Capture) {
@@ -253,16 +265,24 @@ nsFileControlFrame::CreateAnonymousConte
   textControl->SetValue(value);
 
   textControl->SetTabIndex(-1);
   textControl->SetReadOnly(PR_TRUE);
 
   if (!aElements.AppendElement(mTextContent))
     return NS_ERROR_OUT_OF_MEMORY;
 
+  // Register the whole frame as an event listener of drag events
+  nsCOMPtr<nsIDOMEventTarget> dragTarget = do_QueryInterface(mContent);
+  NS_ENSURE_STATE(dragTarget);
+  dragTarget->AddEventListener(NS_LITERAL_STRING("drop"),
+                               mMouseListener, PR_FALSE);
+  dragTarget->AddEventListener(NS_LITERAL_STRING("dragover"),
+                               mMouseListener, PR_FALSE);
+
   NS_NAMED_LITERAL_STRING(click, "click");
   nsCOMPtr<nsIDOMEventGroup> systemGroup;
   mContent->GetSystemEventGroup(getter_AddRefs(systemGroup));
   nsCOMPtr<nsIDOM3EventTarget> dom3TextContent =
     do_QueryInterface(mTextContent);
   NS_ENSURE_STATE(dom3TextContent);
   // Register as an event listener of the textbox
   // to open file dialog on mouse click
@@ -493,16 +513,84 @@ nsFileControlFrame::BrowseMouseListener:
   if (!ShouldProcessMouseClick(aMouseEvent))
     return NS_OK;
   
   nsHTMLInputElement* input =
     nsHTMLInputElement::FromContent(mFrame->GetContent());
   return input ? input->FireAsyncClickHandler() : NS_OK;
 }
 
+/**
+ * This is called when we receive any registered events on the control.
+ * We've only registered for drop, dragover and click events, and click events
+ * already call MouseClick() for us. Here, we handle file drops.
+ */
+NS_IMETHODIMP
+nsFileControlFrame::BrowseMouseListener::HandleEvent(nsIDOMEvent* aEvent)
+{
+  NS_ASSERTION(mFrame, "We should have been unregistered");
+  nsCOMPtr<nsIDOMNSUIEvent> uiEvent = do_QueryInterface(aEvent);
+  NS_ENSURE_STATE(uiEvent);
+  PRBool defaultPrevented = PR_FALSE;
+  uiEvent->GetPreventDefault(&defaultPrevented);
+  if (defaultPrevented) {
+    return NS_OK;
+  }
+  
+  nsCOMPtr<nsIDOMDragEvent> dragEvent = do_QueryInterface(aEvent);
+  if (!dragEvent || !IsValidDropData(dragEvent)) {
+    return NS_OK;
+  }
+
+  nsAutoString eventType;
+  aEvent->GetType(eventType);
+  if (eventType.EqualsLiteral("dragover")) {
+    // Prevent default if we can accept this drag data
+    aEvent->PreventDefault();
+    return NS_OK;
+  }
+
+  if (eventType.EqualsLiteral("drop")) {
+    aEvent->StopPropagation();
+    aEvent->PreventDefault();
+
+    nsIContent* content = mFrame->GetContent();
+    NS_ASSERTION(content, "The frame has no content???");
+
+    nsHTMLInputElement* inputElement = nsHTMLInputElement::FromContent(content);
+    NS_ASSERTION(inputElement, "No input element for this file upload control frame!");
+
+    nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+    dragEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
+
+    nsCOMPtr<nsIDOMFileList> fileList;
+    dataTransfer->GetFiles(getter_AddRefs(fileList));
+    inputElement->SetFiles(fileList, true);
+  }
+
+  return NS_OK;
+}
+
+/* static */ PRBool
+nsFileControlFrame::BrowseMouseListener::IsValidDropData(nsIDOMDragEvent* aEvent)
+{
+  nsCOMPtr<nsIDOMDataTransfer> dataTransfer;
+  aEvent->GetDataTransfer(getter_AddRefs(dataTransfer));
+  NS_ENSURE_TRUE(dataTransfer, PR_FALSE);
+
+  nsCOMPtr<nsIDOMDOMStringList> types;
+  dataTransfer->GetTypes(getter_AddRefs(types));
+  NS_ENSURE_TRUE(types, PR_FALSE);
+
+  // We only support dropping files onto a file upload control
+  PRBool typeSupported;
+  types->Contains(NS_LITERAL_STRING("Files"), &typeSupported);
+  return typeSupported;
+}
+
 nscoord
 nsFileControlFrame::GetMinWidth(nsRenderingContext *aRenderingContext)
 {
   nscoord result;
   DISPLAY_MIN_WIDTH(this, result);
 
   // Our min width is our pref width
   result = GetPrefWidth(aRenderingContext);
--- a/layout/forms/nsFileControlFrame.h
+++ b/layout/forms/nsFileControlFrame.h
@@ -43,16 +43,18 @@
 #include "nsIDOMMouseListener.h"
 #include "nsIAnonymousContentCreator.h"
 #include "nsICapturePicker.h"
 #include "nsCOMPtr.h"
 
 #include "nsTextControlFrame.h"
 typedef   nsTextControlFrame nsNewFrame;
 
+class nsIDOMDragEvent;
+
 class nsFileControlFrame : public nsBlockFrame,
                            public nsIFormControlFrame,
                            public nsIAnonymousContentCreator
 {
 public:
   nsFileControlFrame(nsStyleContext* aContext);
 
   NS_IMETHOD Init(nsIContent* aContent,
@@ -164,16 +166,19 @@ protected:
     NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
     PRUint32 mMode;
   };
   
   class BrowseMouseListener: public MouseListener {
   public:
     BrowseMouseListener(nsFileControlFrame* aFrame) : MouseListener(aFrame) {};
      NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
+     NS_IMETHOD HandleEvent(nsIDOMEvent* aEvent);
+
+     static PRBool IsValidDropData(nsIDOMDragEvent* aEvent);
   };
 
   virtual PRBool IsFrameOfType(PRUint32 aFlags) const
   {
     return nsBlockFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
   }