Bug 684555, r=bbondy
authorJim Mathies <jmathies@mozilla.com>
Wed, 07 Dec 2011 13:01:48 -0600
changeset 83830 1236e039a9e3adae6b0edb7191b5bcf22a1fe06b
parent 83829 fe9f200f7d289c39647d46267370304402abf914
child 83831 66a2ddade3f0a66e4c1521461cbd0fbb64d84b6b
push id519
push userakeybl@mozilla.com
push dateWed, 01 Feb 2012 00:38:35 +0000
treeherdermozilla-beta@788ea1ef610b [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbbondy
bugs684555
milestone11.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 684555, r=bbondy
widget/src/windows/nsFilePicker.cpp
widget/src/windows/nsFilePicker.h
widget/src/windows/nsWindow.cpp
widget/src/windows/nsWindow.h
--- a/widget/src/windows/nsFilePicker.cpp
+++ b/widget/src/windows/nsFilePicker.cpp
@@ -51,19 +51,16 @@
 #include "nsILocalFile.h"
 #include "nsIURL.h"
 #include "nsIStringBundle.h"
 #include "nsEnumeratorUtils.h"
 #include "nsCRT.h"
 #include <windows.h>
 #include <shlobj.h>
 #include <shlwapi.h>
-
-// commdlg.h and cderr.h are needed to build with WIN32_LEAN_AND_MEAN
-#include <commdlg.h>
 #include <cderr.h>
 
 #include "nsString.h"
 #include "nsToolkit.h"
 
 NS_IMPL_ISUPPORTS1(nsFilePicker, nsIFilePicker)
 
 PRUnichar *nsFilePicker::mLastUsedUnicodeDirectory;
@@ -110,17 +107,18 @@ static void EnsureWindowVisible(HWND hwn
     GetWindowRect(parentHwnd, &parentRect);
     BOOL b = SetWindowPos(hwnd, NULL, 
                           parentRect.left, 
                           parentRect.top, 0, 0, 
                           SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER);
   }
 }
 
-// Callback hook which will ensure that the window is visible
+// Callback hook which will ensure that the window is visible. Currently
+// only in use on os <= XP.
 static UINT_PTR CALLBACK FilePickerHook(HWND hwnd, UINT msg,
                                         WPARAM wParam, LPARAM lParam) 
 {
   if (msg == WM_NOTIFY) {
     LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam;
     if (!lpofn || !lpofn->lpOFN) {
       return 0;
     }
@@ -133,18 +131,18 @@ static UINT_PTR CALLBACK FilePickerHook(
     }
   } else if (msg == MOZ_WM_ENSUREVISIBLE) {
     EnsureWindowVisible(GetParent(hwnd));
   }
   return 0;
 }
 
 
-// Callback hook which will dynamically allocate a buffer large
-// enough for the file picker dialog.
+// Callback hook which will dynamically allocate a buffer large enough
+// for the file picker dialog.  Currently only in use on  os <= XP.
 static UINT_PTR CALLBACK MultiFilePickerHook(HWND hwnd, UINT msg,
                                              WPARAM wParam, LPARAM lParam)
 {
   switch (msg) {
     case WM_INITDIALOG:
       {
         // Finds the child drop down of a File Picker dialog and sets the 
         // maximum amount of text it can hold when typed in manually.
@@ -204,16 +202,39 @@ static UINT_PTR CALLBACK MultiFilePicker
           }
         }
       }
       break;
   }
 
   return FilePickerHook(hwnd, msg, wParam, lParam);
 }
+  
+bool nsFilePicker::GetFileName(OPENFILENAMEW* ofn, PickerType aType)
+{
+  NS_ENSURE_ARG_POINTER(ofn);
+  bool result = false;
+  // This should be safe, we have mParentWidget ref'd
+  nsWindow* win = static_cast<nsWindow*>(mParentWidget.get());
+  if (win) {
+    win->PickerOpen();
+  }
+  MOZ_SEH_TRY {
+    if (aType == PICKER_TYPE_OPEN) 
+      result = ::GetOpenFileNameW(ofn);
+    else if (aType == PICKER_TYPE_SAVE)
+      result = ::GetSaveFileNameW(ofn);
+  } MOZ_SEH_EXCEPT(true) {
+    NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!");
+  }
+  if (win) {
+    win->PickerClosed();
+  }
+  return result;
+}
 
 NS_IMETHODIMP nsFilePicker::ShowW(PRInt16 *aReturnVal)
 {
   NS_ENSURE_ARG_POINTER(aReturnVal);
 
   // suppress blur events
   if (mParentWidget) {
     nsIWidget *tmp = mParentWidget;
@@ -252,17 +273,17 @@ NS_IMETHODIMP nsFilePicker::ShowW(PRInt1
     browserInfo.lpszTitle      = mTitle.get();
     browserInfo.ulFlags        = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;
     if (initialDir.Length()) {
       // the dialog is modal so that |initialDir.get()| will be valid in 
       // BrowserCallbackProc. Thus, we don't need to clone it.
       browserInfo.lParam       = (LPARAM) initialDir.get();
       browserInfo.lpfn         = &BrowseCallbackProc;
     } else {
-    browserInfo.lParam         = nsnull;
+      browserInfo.lParam       = nsnull;
       browserInfo.lpfn         = nsnull;
     }
     browserInfo.iImage         = nsnull;
 
     LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo);
     if (list != NULL) {
       result = ::SHGetPathFromIDListW(list, (LPWSTR)fileBuffer);
       if (result) {
@@ -275,25 +296,32 @@ NS_IMETHODIMP nsFilePicker::ShowW(PRInt1
   } else {
 
     OPENFILENAMEW ofn;
     memset(&ofn, 0, sizeof(ofn));
     ofn.lStructSize = sizeof(ofn);
     nsString filterBuffer = mFilterList;
                                   
     if (!initialDir.IsEmpty()) {
+      // The initial directory
       ofn.lpstrInitialDir = initialDir.get();
     }
-    
+
+    // A string to be placed in the title bar of the dialog box
     ofn.lpstrTitle   = (LPCWSTR)mTitle.get();
+    // A buffer containing pairs of null-terminated filter strings
     ofn.lpstrFilter  = (LPCWSTR)filterBuffer.get();
+    // The index of the currently selected filter in the File Types control
     ofn.nFilterIndex = mSelectedType;
-    ofn.hwndOwner    = (HWND) (mParentWidget.get() ? mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : 0); 
+    // The file name used to initialize the File Name edit control
     ofn.lpstrFile    = fileBuffer;
+    // The max length of lpstrFile
     ofn.nMaxFile     = FILE_BUFFER_SIZE;
+    ofn.hwndOwner    = (HWND) (mParentWidget.get() ?
+      mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : 0); 
     ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT |
                 OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | 
                 OFN_EXPLORER;
 
     // Windows Vista and up won't allow you to use the new looking dialogs with
     // a hook procedure.  The hook procedure fixes a problem on XP dialogs for
     // file picker visibility.  Vista and up automatically ensures the file 
     // picker is always visible.
@@ -347,81 +375,72 @@ NS_IMETHODIMP nsFilePicker::ShowW(PRInt1
     // In which case expected result should be C:\somepath\test.txt
     AutoRestoreWorkingPath restoreWorkingPath;
     // If we can't get the current working directory, the best case is to
     // use the OFN_NOCHANGEDIR flag
     if (!restoreWorkingPath.HasWorkingPath()) {
       ofn.Flags |= OFN_NOCHANGEDIR;
     }
     
-    MOZ_SEH_TRY {
-      if (mMode == modeOpen) {
-        // FILE MUST EXIST!
-        ofn.Flags |= OFN_FILEMUSTEXIST;
-        result = ::GetOpenFileNameW(&ofn);
-      }
-      else if (mMode == modeOpenMultiple) {
-        ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
+    if (mMode == modeOpen) {
+      // FILE MUST EXIST!
+      ofn.Flags |= OFN_FILEMUSTEXIST;
+      result = GetFileName(&ofn, PICKER_TYPE_OPEN);
+    }
+    else if (mMode == modeOpenMultiple) {
+      ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT;
 
-        // The hook set here ensures that the buffer returned will always be
-        // large enough to hold all selected files.  The hook may modify the
-        // value of ofn.lpstrFile and deallocate the old buffer that it pointed
-        // to (fileBuffer). The hook assumes that the passed in value is heap 
-        // allocated and that the returned value should be freed by the caller.
-        // If the hook changes the buffer, it will deallocate the old buffer.
-        // This fix would be nice to have in Vista and up, but it would force
-        // the file picker to use the old style dialogs because hooks are not
-        // allowed in the new file picker UI.  We need to eventually move to
-        // the new Common File Dialogs for Vista and up.
-        if (nsWindow::GetWindowsVersion() < VISTA_VERSION) {
-          ofn.lpfnHook = MultiFilePickerHook;
-          fileBuffer.forget();
-          result = ::GetOpenFileNameW(&ofn);
-          fileBuffer = ofn.lpstrFile;
-        }
-        else {
-          result = ::GetOpenFileNameW(&ofn);
+      // The hook set here ensures that the buffer returned will always be
+      // large enough to hold all selected files.  The hook may modify the
+      // value of ofn.lpstrFile and deallocate the old buffer that it pointed
+      // to (fileBuffer). The hook assumes that the passed in value is heap 
+      // allocated and that the returned value should be freed by the caller.
+      // If the hook changes the buffer, it will deallocate the old buffer.
+      // This fix would be nice to have in Vista and up, but it would force
+      // the file picker to use the old style dialogs because hooks are not
+      // allowed in the new file picker UI.  We need to eventually move to
+      // the new Common File Dialogs for Vista and up.
+      if (nsWindow::GetWindowsVersion() < VISTA_VERSION) {
+        ofn.lpfnHook = MultiFilePickerHook;
+        fileBuffer.forget();
+        result = GetFileName(&ofn, PICKER_TYPE_OPEN);
+        fileBuffer = ofn.lpstrFile;
+      }
+      else {
+        result = GetFileName(&ofn, PICKER_TYPE_OPEN);
+      }
+    }
+    else if (mMode == modeSave) {
+      ofn.Flags |= OFN_NOREADONLYRETURN;
+
+      // Don't follow shortcuts when saving a shortcut, this can be used
+      // to trick users (bug 271732)
+      NS_ConvertUTF16toUTF8 ext(mDefault);
+      ext.Trim(" .", false, true); // watch out for trailing space and dots
+      ToLowerCase(ext);
+      if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) ||
+          StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
+          StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
+        ofn.Flags |= OFN_NODEREFERENCELINKS;
+
+      result = GetFileName(&ofn, PICKER_TYPE_SAVE);
+      if (!result) {
+        // Error, find out what kind.
+        if (::GetLastError() == ERROR_INVALID_PARAMETER 
+            || ::CommDlgExtendedError() == FNERR_INVALIDFILENAME
+            ) {
+          // probably the default file name is too long or contains illegal characters!
+          // Try again, without a starting file name.
+          ofn.lpstrFile[0] = 0;
+          result = GetFileName(&ofn, PICKER_TYPE_SAVE);
         }
       }
-      else if (mMode == modeSave) {
-        ofn.Flags |= OFN_NOREADONLYRETURN;
-
-        // Don't follow shortcuts when saving a shortcut, this can be used
-        // to trick users (bug 271732)
-        NS_ConvertUTF16toUTF8 ext(mDefault);
-        ext.Trim(" .", false, true); // watch out for trailing space and dots
-        ToLowerCase(ext);
-        if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) ||
-            StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) ||
-            StringEndsWith(ext, NS_LITERAL_CSTRING(".url")))
-          ofn.Flags |= OFN_NODEREFERENCELINKS;
-
-        result = ::GetSaveFileNameW(&ofn);
-        if (!result) {
-          // Error, find out what kind.
-          if (::GetLastError() == ERROR_INVALID_PARAMETER 
-              || ::CommDlgExtendedError() == FNERR_INVALIDFILENAME
-              ) {
-            // probably the default file name is too long or contains illegal characters!
-            // Try again, without a starting file name.
-            ofn.lpstrFile[0] = 0;
-            result = ::GetSaveFileNameW(&ofn);
-          }
-        }
-      } 
-      else {
-        NS_ERROR("unsupported mode"); 
-      }
-    }
-    MOZ_SEH_EXCEPT(true) {
-      MessageBoxW(ofn.hwndOwner,
-                  0,
-                  L"The filepicker was unexpectedly closed by Windows.",
-                  MB_ICONERROR);
-      result = false;
+    } 
+    else {
+      NS_ERROR("unsupported mode"); 
     }
 
     if (result) {
       // Remember what filter type the user selected
       mSelectedType = (PRInt16)ofn.nFilterIndex;
 
       // Clear out any files from previous Show calls
       mFiles.Clear();
--- a/widget/src/windows/nsFilePicker.h
+++ b/widget/src/windows/nsFilePicker.h
@@ -46,16 +46,17 @@
 #include "nsCOMArray.h"
 #include "nsAutoPtr.h"
 
 #include "nsICharsetConverterManager.h"
 #include "nsBaseFilePicker.h"
 #include "nsString.h"
 #include "nsdefs.h"
 #include <windows.h>
+#include <commdlg.h>
 /**
  * Native Windows FileSelector wrapper
  */
 
 class nsFilePicker : public nsBaseFilePicker
 {
 public:
   nsFilePicker(); 
@@ -73,22 +74,28 @@ public:
   NS_IMETHOD GetFile(nsILocalFile * *aFile);
   NS_IMETHOD GetFileURL(nsIURI * *aFileURL);
   NS_IMETHOD GetFiles(nsISimpleEnumerator **aFiles);
   NS_IMETHOD Show(PRInt16 *aReturnVal); 
   NS_IMETHOD ShowW(PRInt16 *aReturnVal); 
   NS_IMETHOD AppendFilter(const nsAString& aTitle, const nsAString& aFilter);
 
 protected:
+  enum PickerType {
+    PICKER_TYPE_OPEN,
+    PICKER_TYPE_SAVE,
+  };
+
   /* method from nsBaseFilePicker */
   virtual void InitNative(nsIWidget *aParent,
                           const nsAString& aTitle,
                           PRInt16 aMode);
   static void GetQualifiedPath(const PRUnichar *aInPath, nsString &aOutPath);
   void GetFilterListArray(nsString& aFilterList);
+  bool GetFileName(OPENFILENAMEW* ofn, PickerType aType);
 
   nsCOMPtr<nsIWidget>    mParentWidget;
   nsString               mTitle;
   PRInt16                mMode;
   nsCString              mFile;
   nsString               mDefault;
   nsString               mDefaultExtension;
   nsString               mFilterList;
--- a/widget/src/windows/nsWindow.cpp
+++ b/widget/src/windows/nsWindow.cpp
@@ -387,16 +387,18 @@ nsWindow::nsWindow() : nsBaseWidget()
   mIsTopWidgetWindow    = false;
   mUnicodeWidget        = true;
   mDisplayPanFeedback   = false;
   mTouchWindow          = false;
   mCustomNonClient      = false;
   mHideChrome           = false;
   mFullscreenMode       = false;
   mMousePresent         = false;
+  mDestroyCalled        = false;
+  mPickerDisplayCount   = 0;
   mWindowType           = eWindowType_child;
   mBorderStyle          = eBorderStyle_default;
   mPopupType            = ePopupTypeAny;
   mOldSizeMode          = nsSizeMode_Normal;
   mLastPoint.x          = 0;
   mLastPoint.y          = 0;
   mLastSize.width       = 0;
   mLastSize.height      = 0;
@@ -687,16 +689,22 @@ nsWindow::Create(nsIWidget *aParent,
 
 // Close this nsWindow
 NS_METHOD nsWindow::Destroy()
 {
   // WM_DESTROY has already fired, avoid calling it twice
   if (mOnDestroyCalled)
     return NS_OK;
 
+  // Don't destroy windows that have file pickers open, we'll tear these down
+  // later once the picker is closed.
+  mDestroyCalled = true;
+  if (mPickerDisplayCount)
+    return NS_OK;
+
   // During the destruction of all of our children, make sure we don't get deleted.
   nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
 
   /**
    * On windows the LayerManagerOGL destructor wants the widget to be around for
    * cleanup. It also would like to have the HWND intact, so we NULL it here.
    */
   if (mLayerManager) {
@@ -9111,16 +9119,32 @@ LPARAM nsWindow::lParamToClient(LPARAM l
 {
   POINT pt;
   pt.x = GET_X_LPARAM(lParam);
   pt.y = GET_Y_LPARAM(lParam);
   ::ScreenToClient(mWnd, &pt);
   return MAKELPARAM(pt.x, pt.y);
 }
 
+void nsWindow::PickerOpen()
+{
+  mPickerDisplayCount++;
+}
+
+void nsWindow::PickerClosed()
+{
+  NS_ASSERTION(mPickerDisplayCount > 0, "mPickerDisplayCount out of sync!");
+  if (!mPickerDisplayCount)
+    return;
+  mPickerDisplayCount--;
+  if (!mPickerDisplayCount && mDestroyCalled) {
+    Destroy();
+  }
+}
+
 /**************************************************************
  **************************************************************
  **
  ** BLOCK: ChildWindow impl.
  **
  ** Child window overrides.
  **
  **************************************************************
--- a/widget/src/windows/nsWindow.h
+++ b/widget/src/windows/nsWindow.h
@@ -300,16 +300,21 @@ public:
   already_AddRefed<nsITaskbarWindowPreview> GetTaskbarPreview() {
     nsCOMPtr<nsITaskbarWindowPreview> preview(do_QueryReferent(mTaskbarPreview));
     return preview.forget();
   }
   void SetTaskbarPreview(nsITaskbarWindowPreview *preview) { mTaskbarPreview = do_GetWeakReference(preview); }
 #endif
 
   NS_IMETHOD              ReparentNativeWidget(nsIWidget* aNewParent);
+
+  // Open file picker tracking
+  void                    PickerOpen();
+  void                    PickerClosed();
+
 protected:
 
   // A magic number to identify the FAKETRACKPOINTSCROLLABLE window created
   // when the trackpoint hack is enabled.
   enum { eFakeTrackPointScrollableID = 0x46545053 };
 
   /**
    * Callbacks
@@ -526,26 +531,28 @@ protected:
   bool                  mUnicodeWidget;
   bool                  mPainting;
   bool                  mTouchWindow;
   bool                  mDisplayPanFeedback;
   bool                  mHideChrome;
   bool                  mIsRTL;
   bool                  mFullscreenMode;
   bool                  mMousePresent;
+  bool                  mDestroyCalled;
   PRUint32              mBlurSuppressLevel;
   DWORD_PTR             mOldStyle;
   DWORD_PTR             mOldExStyle;
   InputContext mInputContext;
   nsNativeDragTarget*   mNativeDragTarget;
   HKL                   mLastKeyboardLayout;
   nsPopupType           mPopupType;
   nsSizeMode            mOldSizeMode;
   WindowHook            mWindowHook;
   DWORD                 mAssumeWheelIsZoomUntil;
+  PRUint32              mPickerDisplayCount;
   static bool           sDropShadowEnabled;
   static PRUint32       sInstanceCount;
   static TriStateBool   sCanQuit;
   static nsWindow*      sCurrentWindow;
   static BOOL           sIsOleInitialized;
   static HCURSOR        sHCursor;
   static imgIContainer* sCursorImgContainer;
   static bool           sSwitchKeyboardLayout;