Bug 36619 - allow file input .click() to bring up filepicker r=sicking a=blocking
authorDavid Zbarsky <dzbarsky@gmail.com>
Fri, 27 Aug 2010 11:49:06 -0400
changeset 51580 a5a97bafc953463ead28733d769132e0a37ec31d
parent 51579 1c724924c92d1d132d2b00c0a224a827e0122c1e
child 51581 af7d35e5d336396844d00cc1c09463be9fb76668
push idunknown
push userunknown
push dateunknown
reviewerssicking, blocking
bugs36619
milestone2.0b5pre
Bug 36619 - allow file input .click() to bring up filepicker r=sicking a=blocking
content/html/content/src/nsHTMLInputElement.cpp
content/html/content/src/nsHTMLInputElement.h
layout/build/nsLayoutStatics.cpp
layout/forms/nsFileControlFrame.cpp
layout/forms/nsFileControlFrame.h
--- a/content/html/content/src/nsHTMLInputElement.cpp
+++ b/content/html/content/src/nsHTMLInputElement.cpp
@@ -88,24 +88,33 @@
 
 #include "nsIDOMMutationEvent.h"
 #include "nsIDOMEventTarget.h"
 #include "nsMutationEvent.h"
 #include "nsIEventListenerManager.h"
 
 #include "nsRuleData.h"
 
-// input type=radio
-#include "nsIRadioGroupContainer.h"
+// input type=radio
+#include "nsIRadioGroupContainer.h"
 
 // input type=file
 #include "nsIFile.h"
 #include "nsILocalFile.h"
 #include "nsNetUtil.h"
 #include "nsDOMFile.h"
+#include "nsFileControlFrame.h"
+#include "nsTextControlFrame.h"
+#include "nsIFilePicker.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsIPrivateBrowsingService.h"
+#include "nsIContentURIGrouper.h"
+#include "nsIContentPrefService.h"
+#include "nsIObserverService.h"
+#include "nsIPopupWindowManager.h"
 
 // input type=image
 #include "nsImageLoadingContent.h"
 #include "nsIDOMWindowInternal.h"
 
 #include "mozAutoDocUpdate.h"
 #include "nsHTMLFormElement.h"
 #include "nsContentCreatorFunctions.h"
@@ -134,16 +143,17 @@ static NS_DEFINE_CID(kLookAndFeelCID, NS
 #define NS_ORIGINAL_INDETERMINATE_VALUE (1 << 12)
 #define NS_CONTROL_TYPE(bits)  ((bits) & ~( \
   NS_OUTER_ACTIVATE_EVENT | NS_ORIGINAL_CHECKED_VALUE | NS_NO_CONTENT_DISPATCH | \
   NS_ORIGINAL_INDETERMINATE_VALUE))
 
 // whether textfields should be selected once focused:
 //  -1: no, 1: yes, 0: uninitialized
 static PRInt32 gSelectTextFieldOnFocus;
+UploadLastDir* nsHTMLInputElement::gUploadLastDir;
 
 static const nsAttrValue::EnumTable kInputTypeTable[] = {
   { "button", NS_FORM_INPUT_BUTTON },
   { "checkbox", NS_FORM_INPUT_CHECKBOX },
   { "email", NS_FORM_INPUT_EMAIL },
   { "file", NS_FORM_INPUT_FILE },
   { "hidden", NS_FORM_INPUT_HIDDEN },
   { "reset", NS_FORM_INPUT_RESET },
@@ -215,16 +225,371 @@ class nsHTMLInputElementState : public n
     nsTArray<nsString> mFilenames;
     PRPackedBool mChecked;
     PRPackedBool mCheckedSet;
 };
 
 NS_IMPL_ISUPPORTS1(nsHTMLInputElementState, nsHTMLInputElementState)
 NS_DEFINE_STATIC_IID_ACCESSOR(nsHTMLInputElementState, NS_INPUT_ELEMENT_STATE_IID)
 
+class AsyncClickHandler : public nsRunnable {
+public:
+  AsyncClickHandler(nsHTMLInputElement* aInput)
+   : mInput(aInput) {
+    
+    nsIDocument* doc = aInput->GetOwnerDoc();
+    if (doc) {
+      nsPIDOMWindow* win = doc->GetWindow();
+      if (win)
+        mPopupControlState = win->GetPopupControlState();
+    }
+  };
+
+  NS_IMETHOD Run();
+
+protected:
+  nsRefPtr<nsHTMLInputElement> mInput;
+  PopupControlState mPopupControlState;
+};
+
+NS_IMETHODIMP
+AsyncClickHandler::Run()
+{
+  nsresult rv;
+
+  // Get parent nsIDOMWindowInternal object.
+  nsCOMPtr<nsIDocument> doc = mInput->GetOwnerDoc();
+  if (!doc)
+    return NS_ERROR_FAILURE;
+
+  nsPIDOMWindow* win = doc->GetWindow();
+  if (!win) {
+    return NS_ERROR_FAILURE;
+  }
+
+  // Check if page is allowed to open the popup
+  if (mPopupControlState != openAllowed) {
+    nsCOMPtr<nsIPopupWindowManager> pm =
+      do_GetService(NS_POPUPWINDOWMANAGER_CONTRACTID);
+ 
+    if (!pm) {
+      return NS_OK;
+    }
+
+    PRUint32 permission;
+    pm->TestPermission(doc->GetDocumentURI(), &permission);
+    if (permission == nsIPopupWindowManager::DENY_POPUP)
+      return NS_OK;
+  }
+
+  // Get Loc title
+  nsXPIDLString title;
+  nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
+                                     "FileUpload", title);
+
+  nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
+  if (!filePicker)
+    return NS_ERROR_FAILURE;
+
+  nsFileControlFrame* frame = static_cast<nsFileControlFrame*>(mInput->GetPrimaryFrame());
+  nsTextControlFrame* textFrame = nsnull;
+  if (frame)
+    textFrame = static_cast<nsTextControlFrame*>(frame->GetTextFrame());
+
+  PRBool multi;
+  rv = mInput->GetMultiple(&multi);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  rv = filePicker->Init(win, title, multi ?
+                        (PRInt16)nsIFilePicker::modeOpenMultiple :
+                        (PRInt16)nsIFilePicker::modeOpen);
+  NS_ENSURE_SUCCESS(rv, rv);
+
+  // We want to get the file filter from the accept attribute and we add the
+  // |filterAll| filter to be sure the user has a valid fallback.
+  PRUint32 filter = 0;
+  if (frame)
+    filter = frame->GetFileFilterFromAccept();
+  filePicker->AppendFilters(filter | nsIFilePicker::filterAll);
+
+  // If the accept attribute asks for a filter, it has to be the default one.
+  if (filter) {
+    // We have two filters: |filterAll| and another one. |filterAll| is
+    // always the first one (index=0) so we can assume the one we want to be
+    // the default is at index 1.
+    filePicker->SetFilterIndex(1);
+  }
+
+  // Set default directry and filename
+  nsAutoString defaultName;
+
+  nsCOMArray<nsIFile> oldFiles;
+  mInput->GetFileArray(oldFiles);
+
+  if (oldFiles.Count()) {
+    // set directory
+    nsCOMPtr<nsIFile> parentFile;
+    oldFiles[0]->GetParent(getter_AddRefs(parentFile));
+    if (parentFile) {
+      nsCOMPtr<nsILocalFile> parentLocalFile = do_QueryInterface(parentFile, &rv);
+      if (parentLocalFile) {
+        filePicker->SetDisplayDirectory(parentLocalFile);
+      }
+    }
+
+    // Unfortunately nsIFilePicker doesn't allow multiple files to be
+    // default-selected, so only select something by default if exactly
+    // one file was selected before.
+    if (oldFiles.Count() == 1) {
+      nsAutoString leafName;
+      oldFiles[0]->GetLeafName(leafName);
+      if (!leafName.IsEmpty()) {
+        filePicker->SetDefaultString(leafName);
+      }
+    }
+  } else {
+    // Attempt to retrieve the last used directory from the content pref service
+    nsCOMPtr<nsILocalFile> localFile;
+    nsHTMLInputElement::gUploadLastDir->FetchLastUsedDirectory(doc->GetDocumentURI(),
+                                                               getter_AddRefs(localFile));
+    if (!localFile) {
+      // Default to "desktop" directory for each platform
+      nsCOMPtr<nsIFile> homeDir;
+      NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
+      localFile = do_QueryInterface(homeDir);
+    }
+    filePicker->SetDisplayDirectory(localFile);
+  }
+
+  // Tell our textframe to remember the currently focused value
+  if (textFrame)
+    textFrame->InitFocusedValue();
+
+  // Open dialog
+  PRInt16 mode;
+  rv = filePicker->Show(&mode);
+  NS_ENSURE_SUCCESS(rv, rv);
+  if (mode == nsIFilePicker::returnCancel)
+    return NS_OK;
+  
+  // Collect new selected filenames
+  nsTArray<nsString> newFileNames;
+  if (multi) {
+    nsCOMPtr<nsISimpleEnumerator> iter;
+    rv = filePicker->GetFiles(getter_AddRefs(iter));
+    NS_ENSURE_SUCCESS(rv, rv);
+
+    nsCOMPtr<nsISupports> tmp;
+    PRBool prefSaved = PR_FALSE;
+    while (NS_SUCCEEDED(iter->GetNext(getter_AddRefs(tmp)))) {
+      nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(tmp);
+      if (localFile) {
+        nsString unicodePath;
+        rv = localFile->GetPath(unicodePath);
+        if (!unicodePath.IsEmpty()) {
+          newFileNames.AppendElement(unicodePath);
+        }
+        if (!prefSaved) {
+          // Store the last used directory using the content pref service
+          rv = nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(doc->GetDocumentURI(), 
+                                                                          localFile);
+          NS_ENSURE_SUCCESS(rv, rv);
+          prefSaved = PR_TRUE;
+        }
+      }
+    }
+  }
+  else {
+    nsCOMPtr<nsILocalFile> localFile;
+    rv = filePicker->GetFile(getter_AddRefs(localFile));
+    if (localFile) {
+      nsString unicodePath;
+      rv = localFile->GetPath(unicodePath);
+      if (!unicodePath.IsEmpty()) {
+        newFileNames.AppendElement(unicodePath);
+      }
+      // Store the last used directory using the content pref service
+      rv = nsHTMLInputElement::gUploadLastDir->StoreLastUsedDirectory(doc->GetDocumentURI(),
+                                                                      localFile);
+      NS_ENSURE_SUCCESS(rv, rv);
+    }
+  }
+
+  // Set new selected files
+  if (!newFileNames.IsEmpty()) {
+    // Tell mTextFrame that this update of the value is a user initiated
+    // change. Otherwise it'll think that the value is being set by a script
+    // and not fire onchange when it should.
+    PRBool oldState;
+    if (textFrame) {
+      oldState = textFrame->GetFireChangeEventState();
+      textFrame->SetFireChangeEventState(PR_TRUE);
+    }
+
+    mInput->SetFileNames(newFileNames);
+    if (textFrame) {
+      textFrame->SetFireChangeEventState(oldState);
+      // May need to fire an onchange here
+      textFrame->CheckFireOnChange();
+    }
+  }
+
+  return NS_OK;
+}
+
+#define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
+
+NS_IMPL_ISUPPORTS2(UploadLastDir, nsIObserver, nsISupportsWeakReference)
+
+void
+nsHTMLInputElement::InitUploadLastDir() {
+  gUploadLastDir = new UploadLastDir();
+  NS_ADDREF(gUploadLastDir);
+
+  nsCOMPtr<nsIObserverService> observerService =
+    mozilla::services::GetObserverService();
+  if (observerService && gUploadLastDir) {
+    observerService->AddObserver(gUploadLastDir, NS_PRIVATE_BROWSING_SWITCH_TOPIC, PR_TRUE);
+    observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", PR_TRUE);
+  }
+}
+
+void 
+nsHTMLInputElement::DestroyUploadLastDir() {
+  NS_IF_RELEASE(gUploadLastDir);
+}
+
+UploadLastDir::UploadLastDir():
+  mInPrivateBrowsing(PR_FALSE)
+{
+  nsCOMPtr<nsIPrivateBrowsingService> pbService =
+    do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
+  if (pbService) {
+    pbService->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
+  }
+
+  mUploadLastDirStore.Init();
+}
+
+nsresult
+UploadLastDir::FetchLastUsedDirectory(nsIURI* aURI, nsILocalFile** aFile)
+{
+  NS_PRECONDITION(aURI, "aURI is null");
+  NS_PRECONDITION(aFile, "aFile is null");
+  // Retrieve the data from memory if it's present during private browsing mode,
+  // otherwise fall through to check the CPS
+  if (mInPrivateBrowsing) {
+    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
+      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
+    if (!hostnameGrouperService)
+      return NS_ERROR_NOT_AVAILABLE;
+    nsString group;
+    hostnameGrouperService->Group(aURI, group);
+
+    if (mUploadLastDirStore.Get(group, aFile)) {
+      return NS_OK;
+    }
+  }
+
+  // Attempt to get the CPS, if it's not present we'll just return
+  nsCOMPtr<nsIContentPrefService> contentPrefService =
+    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+  if (!contentPrefService)
+    return NS_ERROR_NOT_AVAILABLE;
+  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  if (!uri)
+    return NS_ERROR_OUT_OF_MEMORY;
+  uri->SetAsISupports(aURI);
+
+  // Get the last used directory, if it is stored
+  PRBool hasPref;
+  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
+    nsCOMPtr<nsIVariant> pref;
+    contentPrefService->GetPref(uri, CPS_PREF_NAME, nsnull, getter_AddRefs(pref));
+    nsString prefStr;
+    pref->GetAsAString(prefStr);
+
+    nsCOMPtr<nsILocalFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
+    if (!localFile)
+      return NS_ERROR_OUT_OF_MEMORY;
+    localFile->InitWithPath(prefStr);
+
+    *aFile = localFile;
+    NS_ADDREF(*aFile);
+  }
+  return NS_OK;
+}
+
+nsresult
+UploadLastDir::StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile)
+{
+  NS_PRECONDITION(aURI, "aURI is null");
+  NS_PRECONDITION(aFile, "aFile is null");
+  nsCOMPtr<nsIFile> parentFile;
+  aFile->GetParent(getter_AddRefs(parentFile));
+  nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(parentFile);
+
+  // Store the data in memory instead of the CPS during private browsing mode
+  if (mInPrivateBrowsing) {
+    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
+      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
+    if (!hostnameGrouperService)
+      return NS_ERROR_NOT_AVAILABLE;
+    nsString group;
+    hostnameGrouperService->Group(aURI, group);
+
+    return mUploadLastDirStore.Put(group, localFile);
+  }
+
+  // Attempt to get the CPS, if it's not present we'll just return
+  nsCOMPtr<nsIContentPrefService> contentPrefService =
+    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+  if (!contentPrefService)
+    return NS_ERROR_NOT_AVAILABLE;
+  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  if (!uri)
+    return NS_ERROR_OUT_OF_MEMORY;
+  uri->SetAsISupports(aURI);
+ 
+  // Find the parent of aFile, and store it
+  nsString unicodePath;
+  parentFile->GetPath(unicodePath);
+  if (unicodePath.IsEmpty()) // nothing to do
+    return NS_OK;
+  nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
+  if (!prefValue)
+    return NS_ERROR_OUT_OF_MEMORY;
+  prefValue->SetAsAString(unicodePath);
+  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
+}
+
+NS_IMETHODIMP
+UploadLastDir::Observe(nsISupports *aSubject, char const *aTopic, PRUnichar const *aData)
+{
+  if (strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0) {
+    if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
+      mInPrivateBrowsing = PR_TRUE;
+    } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
+      mInPrivateBrowsing = PR_FALSE;
+      if (mUploadLastDirStore.IsInitialized()) {
+        mUploadLastDirStore.Clear();
+      }
+    }
+  } else if (strcmp(aTopic, "browser:purge-session-history") == 0) {
+    if (mUploadLastDirStore.IsInitialized()) {
+      mUploadLastDirStore.Clear();
+    }
+    nsCOMPtr<nsIContentPrefService> contentPrefService =
+      do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
+    if (contentPrefService)
+      contentPrefService->RemovePrefsByName(CPS_PREF_NAME);
+  }
+  return NS_OK;
+}
+
 #ifdef ACCESSIBILITY
 //Helper method
 static nsresult FireEventForAccessibility(nsIDOMHTMLInputElement* aTarget,
                                           nsPresContext* aPresContext,
                                           const nsAString& aEventType);
 #endif
 
 //
@@ -239,16 +604,19 @@ nsHTMLInputElement::nsHTMLInputElement(a
     mType(kInputDefaultType->value),
     mBitField(0)
 {
   SET_BOOLBIT(mBitField, BF_PARSER_CREATING, aFromParser);
   SET_BOOLBIT(mBitField, BF_INHIBIT_RESTORATION,
       aFromParser & NS_FROM_PARSER_FRAGMENT);
   mInputData.mState = new nsTextEditorState(this);
   NS_ADDREF(mInputData.mState);
+  
+  if (!gUploadLastDir)
+    nsHTMLInputElement::InitUploadLastDir();
 }
 
 nsHTMLInputElement::~nsHTMLInputElement()
 {
   DestroyImageLoadingContent();
   FreeData();
 }
 
@@ -1409,17 +1777,18 @@ nsHTMLInputElement::Click()
 
   // see what type of input we are.  Only click button, checkbox, radio,
   // reset, submit, & image
   if (mType == NS_FORM_INPUT_BUTTON   ||
       mType == NS_FORM_INPUT_CHECKBOX ||
       mType == NS_FORM_INPUT_RADIO    ||
       mType == NS_FORM_INPUT_RESET    ||
       mType == NS_FORM_INPUT_SUBMIT   ||
-      mType == NS_FORM_INPUT_IMAGE) {
+      mType == NS_FORM_INPUT_IMAGE    ||
+      mType == NS_FORM_INPUT_FILE) {
 
     // Strong in case the event kills it
     nsCOMPtr<nsIDocument> doc = GetCurrentDoc();
     if (!doc) {
       return rv;
     }
 
     nsCOMPtr<nsIPresShell> shell = doc->GetShell();
@@ -1441,27 +1810,36 @@ nsHTMLInputElement::Click()
       // called from chrome JS. Mark this event trusted if Click()
       // is called from chrome code.
       nsMouseEvent event(nsContentUtils::IsCallerChrome(),
                          NS_MOUSE_CLICK, nsnull, nsMouseEvent::eReal);
       event.inputSource = nsIDOMNSMouseEvent::MOZ_SOURCE_UNKNOWN;
       nsEventStatus status = nsEventStatus_eIgnore;
 
       SET_BOOLBIT(mBitField, BF_HANDLING_CLICK, PR_TRUE);
-
+      if (mType == NS_FORM_INPUT_FILE){
+        FireAsyncClickHandler();
+      }
       nsEventDispatcher::Dispatch(static_cast<nsIContent*>(this), context,
                                   &event, nsnull, &status);
 
       SET_BOOLBIT(mBitField, BF_HANDLING_CLICK, PR_FALSE);
     }
   }
 
   return NS_OK;
 }
 
+NS_IMETHODIMP
+nsHTMLInputElement::FireAsyncClickHandler()
+{
+  nsCOMPtr<nsIRunnable> event = new AsyncClickHandler(this);
+  return NS_DispatchToMainThread(event);
+}
+
 PRBool
 nsHTMLInputElement::NeedToInitializeEditorForEvent(nsEventChainPreVisitor& aVisitor) const
 {
   // We only need to initialize the editor for single line input controls because they
   // are lazily initialized.  We don't need to initialize the control for
   // certain types of events, because we know that those events are safe to be
   // handled without the editor being initialized.  These events include:
   // mousein/move/out, and DOM mutation events.
--- a/content/html/content/src/nsHTMLInputElement.h
+++ b/content/html/content/src/nsHTMLInputElement.h
@@ -70,16 +70,49 @@
 #define GET_BOOLBIT(bitfield, field) (((bitfield) & (0x01 << (field))) \
                                         ? PR_TRUE : PR_FALSE)
 #define SET_BOOLBIT(bitfield, field, b) ((b) \
                                         ? ((bitfield) |=  (0x01 << (field))) \
                                         : ((bitfield) &= ~(0x01 << (field))))
 
 class nsDOMFileList;
 
+class UploadLastDir : public nsIObserver, public nsSupportsWeakReference {
+public:
+  NS_DECL_ISUPPORTS
+  NS_DECL_NSIOBSERVER
+
+  UploadLastDir();
+
+  /**
+   * Fetch the last used directory for this location from the content
+   * pref service, if it is available.
+   *
+   * @param aURI URI of the current page
+   * @param aFile path to the last used directory
+   */
+  nsresult FetchLastUsedDirectory(nsIURI* aURI, nsILocalFile** aFile);
+
+  /**
+   * Store the last used directory for this location using the
+   * content pref service, if it is available
+   * @param aURI URI of the current page
+   * @param aFile file chosen by the user - the path to the parent of this
+   *        file will be stored
+   */
+  nsresult StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile);
+private:
+  // Directories are stored here during private browsing mode
+  nsInterfaceHashtable<nsStringHashKey, nsILocalFile> mUploadLastDirStore;
+  PRBool mInPrivateBrowsing;
+};
+
+class nsIRadioGroupContainer;
+class nsIRadioVisitor;
+
 class nsHTMLInputElement : public nsGenericHTMLFormElement,
                            public nsImageLoadingContent,
                            public nsIDOMHTMLInputElement,
                            public nsITextControlElement,
                            public nsIPhonetic,
                            public nsIDOMNSEditableElement,
                            public nsIFileControlElement,
                            public nsIConstraintValidation
@@ -194,24 +227,32 @@ public:
    * button in the group.
    *
    * @return the selected button (or null).
    */
   already_AddRefed<nsIDOMHTMLInputElement> GetSelectedRadioButton();
 
   virtual nsresult Clone(nsINodeInfo *aNodeInfo, nsINode **aResult) const;
 
+  NS_IMETHOD FireAsyncClickHandler();
+
   virtual void UpdateEditableState()
   {
     return UpdateEditableFormControlState();
   }
 
   NS_DECL_CYCLE_COLLECTION_CLASS_INHERITED_NO_UNLINK(nsHTMLInputElement,
                                                      nsGenericHTMLFormElement)
 
+  static UploadLastDir* gUploadLastDir;
+  // create and destroy the static UploadLastDir object for remembering
+  // which directory was last used on a site-by-site basis
+  static void InitUploadLastDir();
+  static void DestroyUploadLastDir();
+
   void MaybeLoadImage();
 
   virtual nsXPCClassInfo* GetClassInfo();
 
   // nsIConstraintValidation
   PRBool   IsTooLong();
   PRBool   IsValueMissing();
   PRBool   HasTypeMismatch();
--- a/layout/build/nsLayoutStatics.cpp
+++ b/layout/build/nsLayoutStatics.cpp
@@ -83,17 +83,17 @@
 #include "nsWebSocket.h"
 #include "nsDOMThreadService.h"
 #include "nsHTMLDNSPrefetch.h"
 #include "nsHtml5Module.h"
 #include "nsCrossSiteListenerProxy.h"
 #include "nsFocusManager.h"
 #include "nsFrameList.h"
 #include "nsListControlFrame.h"
-#include "nsFileControlFrame.h"
+#include "nsHTMLInputElement.h"
 #ifdef MOZ_SVG
 #include "nsSVGUtils.h"
 #endif
 
 #ifdef MOZ_XUL
 #include "nsXULPopupManager.h"
 #include "nsXULContentUtils.h"
 #include "nsXULElement.h"
@@ -378,10 +378,10 @@ nsLayoutStatics::Shutdown()
   nsHtml5Module::ReleaseStatics();
 
   nsRegion::ShutdownStatic();
 
   NS_ShutdownChainItemPool();
 
   nsFrameList::Shutdown();
 
-  nsFileControlFrame::DestroyUploadLastDir();
+  nsHTMLInputElement::DestroyUploadLastDir();
 }
--- a/layout/forms/nsFileControlFrame.cpp
+++ b/layout/forms/nsFileControlFrame.cpp
@@ -76,69 +76,30 @@
 #include "nsIDOMHTMLInputElement.h"
 #ifdef ACCESSIBILITY
 #include "nsIAccessibilityService.h"
 #endif
 
 #include "nsInterfaceHashtable.h"
 #include "nsURIHashKey.h"
 #include "nsILocalFile.h"
-#include "nsIPrivateBrowsingService.h"
 #include "nsNetCID.h"
-#include "nsIObserver.h"
-#include "nsIObserverService.h"
 #include "nsWeakReference.h"
 #include "nsIVariant.h"
-#include "nsIContentPrefService.h"
-#include "nsIContentURIGrouper.h"
 #include "mozilla/Services.h"
 #include "nsDirectoryServiceDefs.h"
 #include "nsCharSeparatedTokenizer.h"
+#include "nsHTMLInputElement.h"
 #include "nsICapturePicker.h"
 #include "nsIFileURL.h"
 
 #define SYNC_TEXT 0x1
 #define SYNC_BUTTON 0x2
 #define SYNC_BOTH 0x3
 
-#define CPS_PREF_NAME NS_LITERAL_STRING("browser.upload.lastDir")
-
-class UploadLastDir : public nsIObserver, public nsSupportsWeakReference {
-public:
-  NS_DECL_ISUPPORTS
-  NS_DECL_NSIOBSERVER
-
-  UploadLastDir();
-
-  /**
-   * Fetch the last used directory for this location from the content
-   * pref service, if it is available.
-   *
-   * @param aURI URI of the current page
-   * @param aFile path to the last used directory
-   */
-  nsresult FetchLastUsedDirectory(nsIURI* aURI, nsILocalFile** aFile);
-
-  /**
-   * Store the last used directory for this location using the
-   * content pref service, if it is available
-   * @param aURI URI of the current page
-   * @param aFile file chosen by the user - the path to the parent of this
-   *        file will be stored
-   */
-  nsresult StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile);
-private:
-  // Directories are stored here during private browsing mode
-  nsInterfaceHashtable<nsStringHashKey, nsILocalFile> mUploadLastDirStore;
-  PRBool mInPrivateBrowsing;
-};
-
-NS_IMPL_ISUPPORTS2(UploadLastDir, nsIObserver, nsISupportsWeakReference)
-UploadLastDir* gUploadLastDir = nsnull;
-
 nsIFrame*
 NS_NewFileControlFrame(nsIPresShell* aPresShell, nsStyleContext* aContext)
 {
   return new (aPresShell) nsFileControlFrame(aContext);
 }
 
 NS_IMPL_FRAMEARENA_HELPERS(nsFileControlFrame)
 
@@ -158,19 +119,16 @@ nsFileControlFrame::Init(nsIContent* aCo
   nsresult rv = nsBlockFrame::Init(aContent, aParent, aPrevInFlow);
   NS_ENSURE_SUCCESS(rv, rv);
 
   mMouseListener = new BrowseMouseListener(this);
   NS_ENSURE_TRUE(mMouseListener, NS_ERROR_OUT_OF_MEMORY);
   mCaptureMouseListener = new CaptureMouseListener(this);
   NS_ENSURE_TRUE(mCaptureMouseListener, NS_ERROR_OUT_OF_MEMORY);
 
-  if (!gUploadLastDir)
-    nsFileControlFrame::InitUploadLastDir();
-
   return rv;
 }
 
 void
 nsFileControlFrame::DestroyFrom(nsIFrame* aDestructRoot)
 {
   mTextFrame = nsnull;
   ENSURE_TRUE(mContent);
@@ -524,329 +482,24 @@ nsFileControlFrame::CaptureMouseListener
 }
 
 /**
  * This is called when our browse button is clicked
  */
 NS_IMETHODIMP
 nsFileControlFrame::BrowseMouseListener::MouseClick(nsIDOMEvent* aMouseEvent)
 {
-  nsresult rv;
-
   NS_ASSERTION(mFrame, "We should have been unregistered");
   if (!ShouldProcessMouseClick(aMouseEvent))
     return NS_OK;
-
-  // Get parent nsIDOMWindowInternal object.
+  
   nsIContent* content = mFrame->GetContent();
-  nsCOMPtr<nsIDOMHTMLInputElement> inputElem = do_QueryInterface(content);
-  nsCOMPtr<nsIFileControlElement> fileControl = do_QueryInterface(content);
-  if (!content || !inputElem || !fileControl)
-    return NS_ERROR_FAILURE;
-
-  nsCOMPtr<nsIDocument> doc = content->GetDocument();
-  if (!doc)
-    return NS_ERROR_FAILURE;
-
-  // Get Loc title
-  nsXPIDLString title;
-  nsContentUtils::GetLocalizedString(nsContentUtils::eFORMS_PROPERTIES,
-                                     "FileUpload", title);
-
-  nsCOMPtr<nsIFilePicker> filePicker = do_CreateInstance("@mozilla.org/filepicker;1");
-  if (!filePicker)
-    return NS_ERROR_FAILURE;
-
-  nsPIDOMWindow* win = doc->GetWindow();
-  if (!win) {
-    return NS_ERROR_FAILURE;
-  }
-
-  PRBool multi;
-  rv = inputElem->GetMultiple(&multi);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  rv = filePicker->Init(win, title, multi ?
-                        (PRInt16)nsIFilePicker::modeOpenMultiple :
-                        (PRInt16)nsIFilePicker::modeOpen);
-  NS_ENSURE_SUCCESS(rv, rv);
-
-  // We want to get the file filter from the accept attribute and we add the
-  // |filterAll| filter to be sure the user has a valid fallback.
-  PRUint32 filter = mFrame->GetFileFilterFromAccept();
-  filePicker->AppendFilters(filter | nsIFilePicker::filterAll);
-
-  // If the accept attribute asks for a filter, it has to be the default one.
-  if (filter) {
-    // We have two filters: |filterAll| and another one. |filterAll| is
-    // always the first one (index=0) so we can assume the one we want to be
-    // the default is at index 1.
-    filePicker->SetFilterIndex(1);
-  }
-
-  // Set default directry and filename
-  nsAutoString defaultName;
-
-  nsCOMArray<nsIFile> oldFiles;
-  fileControl->GetFileArray(oldFiles);
-
-  if (oldFiles.Count()) {
-    // set directory
-    nsCOMPtr<nsIFile> parentFile;
-    oldFiles[0]->GetParent(getter_AddRefs(parentFile));
-    if (parentFile) {
-      nsCOMPtr<nsILocalFile> parentLocalFile = do_QueryInterface(parentFile, &rv);
-      if (parentLocalFile) {
-        filePicker->SetDisplayDirectory(parentLocalFile);
-      }
-    }
-
-    // Unfortunately nsIFilePicker doesn't allow multiple files to be
-    // default-selected, so only select something by default if exactly
-    // one file was selected before.
-    if (oldFiles.Count() == 1) {
-      nsAutoString leafName;
-      oldFiles[0]->GetLeafName(leafName);
-      if (!leafName.IsEmpty()) {
-        filePicker->SetDefaultString(leafName);
-      }
-    }
-  } else {
-    // Attempt to retrieve the last used directory from the content pref service
-    nsCOMPtr<nsILocalFile> localFile;
-    gUploadLastDir->FetchLastUsedDirectory(doc->GetDocumentURI(),
-                                           getter_AddRefs(localFile));
-    if (!localFile) {
-      // Default to "desktop" directory for each platform
-      nsCOMPtr<nsIFile> homeDir;
-      NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(homeDir));
-      localFile = do_QueryInterface(homeDir);
-    }
-    filePicker->SetDisplayDirectory(localFile);
-  }
-
-  // Tell our textframe to remember the currently focused value
-  mFrame->mTextFrame->InitFocusedValue();
-
-  // Open dialog
-  PRInt16 mode;
-  rv = filePicker->Show(&mode);
-  NS_ENSURE_SUCCESS(rv, rv);
-  if (mode == nsIFilePicker::returnCancel)
-    return NS_OK;
-
-  if (!mFrame) {
-    // The frame got destroyed while the filepicker was up.  Don't do
-    // anything here.
-    // (This listener itself can't be destroyed because the event listener
-    // manager holds a strong reference to us while it fires the event.)
-    return NS_OK;
-  }
-  
-  // Collect new selected filenames
-  nsTArray<nsString> newFileNames;
-  if (multi) {
-    nsCOMPtr<nsISimpleEnumerator> iter;
-    rv = filePicker->GetFiles(getter_AddRefs(iter));
-    NS_ENSURE_SUCCESS(rv, rv);
-
-    nsCOMPtr<nsISupports> tmp;
-    PRBool prefSaved = PR_FALSE;
-    while (NS_SUCCEEDED(iter->GetNext(getter_AddRefs(tmp)))) {
-      nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(tmp);
-      if (localFile) {
-        nsString unicodePath;
-        rv = localFile->GetPath(unicodePath);
-        if (!unicodePath.IsEmpty()) {
-          newFileNames.AppendElement(unicodePath);
-        }
-        if (!prefSaved) {
-          // Store the last used directory using the content pref service
-          rv = gUploadLastDir->StoreLastUsedDirectory(doc->GetDocumentURI(), 
-                                                      localFile);
-          NS_ENSURE_SUCCESS(rv, rv);
-          prefSaved = PR_TRUE;
-        }
-      }
-    }
-  }
-  else {
-    nsCOMPtr<nsILocalFile> localFile;
-    rv = filePicker->GetFile(getter_AddRefs(localFile));
-    if (localFile) {
-      nsString unicodePath;
-      rv = localFile->GetPath(unicodePath);
-      if (!unicodePath.IsEmpty()) {
-        newFileNames.AppendElement(unicodePath);
-      }
-      // Store the last used directory using the content pref service
-      rv = gUploadLastDir->StoreLastUsedDirectory(doc->GetDocumentURI(),
-                                                  localFile);
-      NS_ENSURE_SUCCESS(rv, rv);
-    }
-  }
-
-  // Set new selected files
-  if (!newFileNames.IsEmpty()) {
-    // Tell mTextFrame that this update of the value is a user initiated
-    // change. Otherwise it'll think that the value is being set by a script
-    // and not fire onchange when it should.
-    PRBool oldState = mFrame->mTextFrame->GetFireChangeEventState();
-    mFrame->mTextFrame->SetFireChangeEventState(PR_TRUE);
-    fileControl->SetFileNames(newFileNames);
-
-    mFrame->mTextFrame->SetFireChangeEventState(oldState);
-    // May need to fire an onchange here
-    mFrame->mTextFrame->CheckFireOnChange();
-  }
-
-  return NS_OK;
-}
-
-void nsFileControlFrame::InitUploadLastDir() {
-  gUploadLastDir = new UploadLastDir();
-  NS_IF_ADDREF(gUploadLastDir);
-
-  nsCOMPtr<nsIObserverService> observerService =
-    mozilla::services::GetObserverService();
-  if (observerService && gUploadLastDir) {
-    observerService->AddObserver(gUploadLastDir, NS_PRIVATE_BROWSING_SWITCH_TOPIC, PR_TRUE);
-    observerService->AddObserver(gUploadLastDir, "browser:purge-session-history", PR_TRUE);
-  }
-}
-
-void nsFileControlFrame::DestroyUploadLastDir() {
-  if (gUploadLastDir)
-    NS_RELEASE(gUploadLastDir);
-}
-
-UploadLastDir::UploadLastDir():
-  mInPrivateBrowsing(PR_FALSE)
-{
-  nsCOMPtr<nsIPrivateBrowsingService> pbService =
-    do_GetService(NS_PRIVATE_BROWSING_SERVICE_CONTRACTID);
-  if (pbService) {
-    pbService->GetPrivateBrowsingEnabled(&mInPrivateBrowsing);
-  }
-
-  mUploadLastDirStore.Init();
-}
-
-nsresult
-UploadLastDir::FetchLastUsedDirectory(nsIURI* aURI, nsILocalFile** aFile)
-{
-  NS_PRECONDITION(aURI, "aURI is null");
-  NS_PRECONDITION(aFile, "aFile is null");
-  // Retrieve the data from memory if it's present during private browsing mode,
-  // otherwise fall through to check the CPS
-  if (mInPrivateBrowsing) {
-    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
-      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
-    if (!hostnameGrouperService)
-      return NS_ERROR_NOT_AVAILABLE;
-    nsString group;
-    hostnameGrouperService->Group(aURI, group);
-
-    if (mUploadLastDirStore.Get(group, aFile)) {
-      return NS_OK;
-    }
-  }
-
-  // Attempt to get the CPS, if it's not present we'll just return
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
-    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
-  if (!contentPrefService)
-    return NS_ERROR_NOT_AVAILABLE;
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  if (!uri)
-    return NS_ERROR_OUT_OF_MEMORY;
-  uri->SetAsISupports(aURI);
-
-  // Get the last used directory, if it is stored
-  PRBool hasPref;
-  if (NS_SUCCEEDED(contentPrefService->HasPref(uri, CPS_PREF_NAME, &hasPref)) && hasPref) {
-    nsCOMPtr<nsIVariant> pref;
-    contentPrefService->GetPref(uri, CPS_PREF_NAME, nsnull, getter_AddRefs(pref));
-    nsString prefStr;
-    pref->GetAsAString(prefStr);
-
-    nsCOMPtr<nsILocalFile> localFile = do_CreateInstance(NS_LOCAL_FILE_CONTRACTID);
-    if (!localFile)
-      return NS_ERROR_OUT_OF_MEMORY;
-    localFile->InitWithPath(prefStr);
-
-    *aFile = localFile;
-    NS_ADDREF(*aFile);
-  }
-  return NS_OK;
-}
-
-nsresult
-UploadLastDir::StoreLastUsedDirectory(nsIURI* aURI, nsILocalFile* aFile)
-{
-  NS_PRECONDITION(aURI, "aURI is null");
-  NS_PRECONDITION(aFile, "aFile is null");
-  nsCOMPtr<nsIFile> parentFile;
-  aFile->GetParent(getter_AddRefs(parentFile));
-  nsCOMPtr<nsILocalFile> localFile = do_QueryInterface(parentFile);
-
-  // Store the data in memory instead of the CPS during private browsing mode
-  if (mInPrivateBrowsing) {
-    nsCOMPtr<nsIContentURIGrouper> hostnameGrouperService =
-      do_GetService(NS_HOSTNAME_GROUPER_SERVICE_CONTRACTID);
-    if (!hostnameGrouperService)
-      return NS_ERROR_NOT_AVAILABLE;
-    nsString group;
-    hostnameGrouperService->Group(aURI, group);
-
-    return mUploadLastDirStore.Put(group, localFile);
-  }
-
-  // Attempt to get the CPS, if it's not present we'll just return
-  nsCOMPtr<nsIContentPrefService> contentPrefService =
-    do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
-  if (!contentPrefService)
-    return NS_ERROR_NOT_AVAILABLE;
-  nsCOMPtr<nsIWritableVariant> uri = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  if (!uri)
-    return NS_ERROR_OUT_OF_MEMORY;
-  uri->SetAsISupports(aURI);
- 
-  // Find the parent of aFile, and store it
-  nsString unicodePath;
-  parentFile->GetPath(unicodePath);
-  if (unicodePath.IsEmpty()) // nothing to do
-    return NS_OK;
-  nsCOMPtr<nsIWritableVariant> prefValue = do_CreateInstance(NS_VARIANT_CONTRACTID);
-  if (!prefValue)
-    return NS_ERROR_OUT_OF_MEMORY;
-  prefValue->SetAsAString(unicodePath);
-  return contentPrefService->SetPref(uri, CPS_PREF_NAME, prefValue);
-}
-
-NS_IMETHODIMP
-UploadLastDir::Observe(nsISupports *aSubject, char const *aTopic, PRUnichar const *aData)
-{
-  if (strcmp(aTopic, NS_PRIVATE_BROWSING_SWITCH_TOPIC) == 0) {
-    if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_ENTER).Equals(aData)) {
-      mInPrivateBrowsing = PR_TRUE;
-    } else if (NS_LITERAL_STRING(NS_PRIVATE_BROWSING_LEAVE).Equals(aData)) {
-      mInPrivateBrowsing = PR_FALSE;
-      if (mUploadLastDirStore.IsInitialized()) {
-        mUploadLastDirStore.Clear();
-      }
-    }
-  } else if (strcmp(aTopic, "browser:purge-session-history") == 0) {
-    if (mUploadLastDirStore.IsInitialized()) {
-      mUploadLastDirStore.Clear();
-    }
-    nsCOMPtr<nsIContentPrefService> contentPrefService =
-      do_GetService(NS_CONTENT_PREF_SERVICE_CONTRACTID);
-    if (contentPrefService)
-      contentPrefService->RemovePrefsByName(CPS_PREF_NAME);
+  if (content->IsHTML() && content->Tag() == nsGkAtoms::input) {
+    nsHTMLInputElement* input = static_cast<nsHTMLInputElement*>(content);
+    return input->FireAsyncClickHandler();
   }
   return NS_OK;
 }
 
 nscoord
 nsFileControlFrame::GetMinWidth(nsIRenderingContext *aRenderingContext)
 {
   nscoord result;
--- a/layout/forms/nsFileControlFrame.h
+++ b/layout/forms/nsFileControlFrame.h
@@ -94,37 +94,35 @@ public:
   // nsIAnonymousContentCreator
   virtual nsresult CreateAnonymousContent(nsTArray<nsIContent*>& aElements);
   virtual void AppendAnonymousContentTo(nsBaseContentList& aElements);
 
 #ifdef ACCESSIBILITY
   virtual already_AddRefed<nsAccessible> CreateAccessible();
 #endif
 
-  // create and destroy the static UploadLastDir object for remembering
-  // which directory was last used on a site-by-site basis
-  static void InitUploadLastDir();
-  static void DestroyUploadLastDir();
-
   /**
    * This methods return the file filter mask requested by the HTML5 accept
    * attribute. If the accept attribute isn't present or the value isn't valid,
    * the returned value will be 0.
    *
    * See:
    * http://dev.w3.org/html5/spec/forms.html#attr-input-accept
    *
    * @return the file picker filter mask or 0 if there is no filter.
    */
   PRInt32 GetFileFilterFromAccept() const;
 
   typedef PRBool (*AcceptAttrCallback)(const nsAString&, void*);
   void ParseAcceptAttribute(AcceptAttrCallback aCallback, void* aClosure) const;
 
+  nsIFrame* GetTextFrame() { return mTextFrame; }
+
 protected:
+
   class MouseListener;
   friend class MouseListener;
   class MouseListener : public nsIDOMMouseListener {
   public:
     NS_DECL_ISUPPORTS
     
     MouseListener(nsFileControlFrame* aFrame) :
       mFrame(aFrame)
@@ -156,17 +154,17 @@ protected:
     PRUint32 mMode;
   };
   
   class BrowseMouseListener: public MouseListener {
   public:
     BrowseMouseListener(nsFileControlFrame* aFrame) : MouseListener(aFrame) {};
      NS_IMETHOD MouseClick(nsIDOMEvent* aMouseEvent);
   };
-  
+
   virtual PRBool IsFrameOfType(PRUint32 aFlags) const
   {
     return nsBlockFrame::IsFrameOfType(aFlags &
       ~(nsIFrame::eReplaced | nsIFrame::eReplacedContainsBlock));
   }
 
   virtual PRIntn GetSkipSides() const;