1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "nsFilePicker.h"
13 #include "mozilla/WindowsVersion.h"
14 #include "nsReadableUtils.h"
15 #include "nsNetUtil.h"
17 #include "nsILoadContext.h"
18 #include "nsIServiceManager.h"
20 #include "nsIStringBundle.h"
21 #include "nsEnumeratorUtils.h"
24 #include "nsToolkit.h"
26 #include "nsPIDOMWindow.h"
28 using mozilla::IsVistaOrLater
;
29 using namespace mozilla::widget
;
31 char16_t
*nsFilePicker::mLastUsedUnicodeDirectory
;
32 char nsFilePicker::mLastUsedDirectory
[MAX_PATH
+1] = { 0 };
34 static const wchar_t kDialogPtrProp
[] = L
"DialogPtrProperty";
35 static const DWORD kDialogTimerID
= 9999;
36 static const unsigned long kDialogTimerTimeout
= 300;
38 #define MAX_EXTENSION_LENGTH 10
39 #define FILE_BUFFER_SIZE 4096
41 typedef DWORD FILEOPENDIALOGOPTIONS
;
43 ///////////////////////////////////////////////////////////////////////////////
46 // Manages matching SuppressBlurEvents calls on the parent widget.
47 class AutoSuppressEvents
50 explicit AutoSuppressEvents(nsIWidget
* aWidget
) :
51 mWindow(static_cast<nsWindow
*>(aWidget
)) {
52 SuppressWidgetEvents(true);
55 ~AutoSuppressEvents() {
56 SuppressWidgetEvents(false);
59 void SuppressWidgetEvents(bool aFlag
) {
61 mWindow
->SuppressBlurEvents(aFlag
);
64 nsRefPtr
<nsWindow
> mWindow
;
67 // Manages the current working path.
68 class AutoRestoreWorkingPath
71 AutoRestoreWorkingPath() {
72 DWORD bufferLength
= GetCurrentDirectoryW(0, nullptr);
73 mWorkingPath
= new wchar_t[bufferLength
];
74 if (GetCurrentDirectoryW(bufferLength
, mWorkingPath
) == 0) {
75 mWorkingPath
= nullptr;
79 ~AutoRestoreWorkingPath() {
80 if (HasWorkingPath()) {
81 ::SetCurrentDirectoryW(mWorkingPath
);
85 inline bool HasWorkingPath() const {
86 return mWorkingPath
!= nullptr;
89 nsAutoArrayPtr
<wchar_t> mWorkingPath
;
92 // Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are
93 // temporary child windows of mParentWidget created to address RTL issues
94 // in picker dialogs. We are responsible for destroying these.
95 class AutoDestroyTmpWindow
98 explicit AutoDestroyTmpWindow(HWND aTmpWnd
) :
102 ~AutoDestroyTmpWindow() {
107 inline HWND
get() const { return mWnd
; }
112 // Manages matching PickerOpen/PickerClosed calls on the parent widget.
113 class AutoWidgetPickerState
116 explicit AutoWidgetPickerState(nsIWidget
* aWidget
) :
117 mWindow(static_cast<nsWindow
*>(aWidget
)) {
121 ~AutoWidgetPickerState() {
125 void PickerState(bool aFlag
) {
128 mWindow
->PickerOpen();
130 mWindow
->PickerClosed();
133 nsRefPtr
<nsWindow
> mWindow
;
136 // Manages a simple callback timer
137 class AutoTimerCallbackCancel
140 AutoTimerCallbackCancel(nsFilePicker
* aTarget
,
141 nsTimerCallbackFunc aCallbackFunc
) {
142 Init(aTarget
, aCallbackFunc
);
145 ~AutoTimerCallbackCancel() {
146 if (mPickerCallbackTimer
) {
147 mPickerCallbackTimer
->Cancel();
152 void Init(nsFilePicker
* aTarget
,
153 nsTimerCallbackFunc aCallbackFunc
) {
154 mPickerCallbackTimer
= do_CreateInstance("@mozilla.org/timer;1");
155 if (!mPickerCallbackTimer
) {
156 NS_WARNING("do_CreateInstance for timer failed??");
159 mPickerCallbackTimer
->InitWithFuncCallback(aCallbackFunc
,
162 nsITimer::TYPE_REPEATING_SLACK
);
164 nsCOMPtr
<nsITimer
> mPickerCallbackTimer
;
168 ///////////////////////////////////////////////////////////////////////////////
171 nsFilePicker::nsFilePicker() :
176 CoInitialize(nullptr);
179 nsFilePicker::~nsFilePicker()
181 if (mLastUsedUnicodeDirectory
) {
182 NS_Free(mLastUsedUnicodeDirectory
);
183 mLastUsedUnicodeDirectory
= nullptr;
188 NS_IMPL_ISUPPORTS(nsFilePicker
, nsIFilePicker
)
190 NS_IMETHODIMP
nsFilePicker::Init(nsIDOMWindow
*aParent
, const nsAString
& aTitle
, int16_t aMode
)
192 nsCOMPtr
<nsPIDOMWindow
> window
= do_QueryInterface(aParent
);
193 nsIDocShell
* docShell
= window
? window
->GetDocShell() : nullptr;
194 mLoadContext
= do_QueryInterface(docShell
);
196 return nsBaseFilePicker::Init(aParent
, aTitle
, aMode
);
199 STDMETHODIMP
nsFilePicker::QueryInterface(REFIID refiid
, void** ppvResult
)
201 *ppvResult
= nullptr;
202 if (IID_IUnknown
== refiid
||
203 refiid
== IID_IFileDialogEvents
) {
207 if (nullptr != *ppvResult
) {
208 ((LPUNKNOWN
)*ppvResult
)->AddRef();
212 return E_NOINTERFACE
;
216 * XP picker callbacks
219 // Show - Display the file dialog
221 BrowseCallbackProc(HWND hwnd
, UINT uMsg
, LPARAM lParam
, LPARAM lpData
)
223 if (uMsg
== BFFM_INITIALIZED
)
225 char16_t
* filePath
= (char16_t
*) lpData
;
227 ::SendMessageW(hwnd
, BFFM_SETSELECTIONW
,
228 TRUE
/* true because lpData is a path string */,
235 EnsureWindowVisible(HWND hwnd
)
237 // Obtain the monitor which has the largest area of intersection
238 // with the window, or nullptr if there is no intersection.
239 HMONITOR monitor
= MonitorFromWindow(hwnd
, MONITOR_DEFAULTTONULL
);
241 // The window is not visible, we should reposition it to the same place as its parent
242 HWND parentHwnd
= GetParent(hwnd
);
244 GetWindowRect(parentHwnd
, &parentRect
);
245 SetWindowPos(hwnd
, nullptr, parentRect
.left
, parentRect
.top
, 0, 0,
246 SWP_NOACTIVATE
| SWP_NOSIZE
| SWP_NOZORDER
);
250 // Callback hook which will ensure that the window is visible. Currently
251 // only in use on os <= XP.
253 nsFilePicker::FilePickerHook(HWND hwnd
,
261 LPOFNOTIFYW lpofn
= (LPOFNOTIFYW
) lParam
;
262 if (!lpofn
|| !lpofn
->lpOFN
) {
266 if (CDN_INITDONE
== lpofn
->hdr
.code
) {
267 // The Window will be automatically moved to the last position after
268 // CDN_INITDONE. We post a message to ensure the window will be visible
269 // so it will be done after the automatic last position window move.
270 PostMessage(hwnd
, MOZ_WM_ENSUREVISIBLE
, 0, 0);
274 case MOZ_WM_ENSUREVISIBLE
:
275 EnsureWindowVisible(GetParent(hwnd
));
279 OPENFILENAMEW
* pofn
= reinterpret_cast<OPENFILENAMEW
*>(lParam
);
280 SetProp(hwnd
, kDialogPtrProp
, (HANDLE
)pofn
->lCustData
);
281 nsFilePicker
* picker
= reinterpret_cast<nsFilePicker
*>(pofn
->lCustData
);
283 picker
->SetDialogHandle(hwnd
);
284 SetTimer(hwnd
, kDialogTimerID
, kDialogTimerTimeout
, nullptr);
290 // Check to see if our parent has been torn down, if so, we close too.
291 if (wParam
== kDialogTimerID
) {
292 nsFilePicker
* picker
=
293 reinterpret_cast<nsFilePicker
*>(GetProp(hwnd
, kDialogPtrProp
));
294 if (picker
&& picker
->ClosePickerIfNeeded(true)) {
295 KillTimer(hwnd
, kDialogTimerID
);
305 // Callback hook which will dynamically allocate a buffer large enough
306 // for the file picker dialog. Currently only in use on os <= XP.
308 nsFilePicker::MultiFilePickerHook(HWND hwnd
,
316 // Finds the child drop down of a File Picker dialog and sets the
317 // maximum amount of text it can hold when typed in manually.
318 // A wParam of 0 mean 0x7FFFFFFE characters.
319 HWND comboBox
= FindWindowEx(GetParent(hwnd
), nullptr,
320 L
"ComboBoxEx32", nullptr );
322 SendMessage(comboBox
, CB_LIMITTEXT
, 0, 0);
323 // Store our nsFilePicker ptr for future use
324 OPENFILENAMEW
* pofn
= reinterpret_cast<OPENFILENAMEW
*>(lParam
);
325 SetProp(hwnd
, kDialogPtrProp
, (HANDLE
)pofn
->lCustData
);
326 nsFilePicker
* picker
=
327 reinterpret_cast<nsFilePicker
*>(pofn
->lCustData
);
329 picker
->SetDialogHandle(hwnd
);
330 SetTimer(hwnd
, kDialogTimerID
, kDialogTimerTimeout
, nullptr);
336 LPOFNOTIFYW lpofn
= (LPOFNOTIFYW
) lParam
;
337 if (!lpofn
|| !lpofn
->lpOFN
) {
340 // CDN_SELCHANGE is sent when the selection in the list box of the file
341 // selection dialog changes
342 if (lpofn
->hdr
.code
== CDN_SELCHANGE
) {
343 HWND parentHWND
= GetParent(hwnd
);
345 // Get the required size for the selected files buffer
346 UINT newBufLength
= 0;
347 int requiredBufLength
= CommDlg_OpenSave_GetSpecW(parentHWND
,
349 if(requiredBufLength
>= 0)
350 newBufLength
+= requiredBufLength
;
352 newBufLength
+= MAX_PATH
;
354 // If the user selects multiple files, the buffer contains the
355 // current directory followed by the file names of the selected
356 // files. So make room for the directory path. If the user
357 // selects a single file, it is no harm to add extra space.
358 requiredBufLength
= CommDlg_OpenSave_GetFolderPathW(parentHWND
,
360 if(requiredBufLength
>= 0)
361 newBufLength
+= requiredBufLength
;
363 newBufLength
+= MAX_PATH
;
365 // Check if lpstrFile and nMaxFile are large enough
366 if (newBufLength
> lpofn
->lpOFN
->nMaxFile
) {
367 if (lpofn
->lpOFN
->lpstrFile
)
368 delete[] lpofn
->lpOFN
->lpstrFile
;
370 // We allocate FILE_BUFFER_SIZE more bytes than is needed so that
371 // if the user selects a file and holds down shift and down to
372 // select additional items, we will not continuously reallocate
373 newBufLength
+= FILE_BUFFER_SIZE
;
375 wchar_t* filesBuffer
= new wchar_t[newBufLength
];
376 ZeroMemory(filesBuffer
, newBufLength
* sizeof(wchar_t));
378 lpofn
->lpOFN
->lpstrFile
= filesBuffer
;
379 lpofn
->lpOFN
->nMaxFile
= newBufLength
;
386 // Check to see if our parent has been torn down, if so, we close too.
387 if (wParam
== kDialogTimerID
) {
388 nsFilePicker
* picker
=
389 reinterpret_cast<nsFilePicker
*>(GetProp(hwnd
, kDialogPtrProp
));
390 if (picker
&& picker
->ClosePickerIfNeeded(true)) {
391 KillTimer(hwnd
, kDialogTimerID
);
398 return FilePickerHook(hwnd
, msg
, wParam
, lParam
);
406 nsFilePicker::OnFileOk(IFileDialog
*pfd
)
412 nsFilePicker::OnFolderChanging(IFileDialog
*pfd
,
413 IShellItem
*psiFolder
)
419 nsFilePicker::OnFolderChange(IFileDialog
*pfd
)
425 nsFilePicker::OnSelectionChange(IFileDialog
*pfd
)
431 nsFilePicker::OnShareViolation(IFileDialog
*pfd
,
433 FDE_SHAREVIOLATION_RESPONSE
*pResponse
)
439 nsFilePicker::OnTypeChange(IFileDialog
*pfd
)
441 // Failures here result in errors due to security concerns.
442 nsRefPtr
<IOleWindow
> win
;
443 pfd
->QueryInterface(IID_IOleWindow
, getter_AddRefs(win
));
445 NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog.");
449 win
->GetWindow(&hwnd
);
451 NS_ERROR("Could not retrieve the HWND for IFileDialog.");
455 SetDialogHandle(hwnd
);
460 nsFilePicker::OnOverwrite(IFileDialog
*pfd
,
462 FDE_OVERWRITE_RESPONSE
*pResponse
)
468 * Close on parent close logic
472 nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog
)
474 if (!mParentWidget
|| !mDlgWnd
)
477 nsWindow
*win
= static_cast<nsWindow
*>(mParentWidget
.get());
478 // Note, the xp callbacks hand us an inner window, so we have to step up
479 // one to get the actual dialog.
482 dlgWnd
= GetParent(mDlgWnd
);
485 if (IsWindow(dlgWnd
) && IsWindowVisible(dlgWnd
) && win
->DestroyCalled()) {
486 wchar_t className
[64];
487 // Make sure we have the right window
488 if (GetClassNameW(dlgWnd
, className
, mozilla::ArrayLength(className
)) &&
489 !wcscmp(className
, L
"#32770") &&
490 DestroyWindow(dlgWnd
)) {
499 nsFilePicker::PickerCallbackTimerFunc(nsITimer
*aTimer
, void *aCtx
)
501 nsFilePicker
* picker
= (nsFilePicker
*)aCtx
;
502 if (picker
->ClosePickerIfNeeded(false)) {
508 nsFilePicker::SetDialogHandle(HWND aWnd
)
510 if (!aWnd
|| mDlgWnd
)
516 * Folder picker invocation
519 // Open the older XP style folder picker dialog. We end up in this call
520 // on XP systems or when platform is built without the longhorn SDK.
522 nsFilePicker::ShowXPFolderPicker(const nsString
& aInitialDir
)
526 nsAutoArrayPtr
<wchar_t> dirBuffer(new wchar_t[FILE_BUFFER_SIZE
]);
527 wcsncpy(dirBuffer
, aInitialDir
.get(), FILE_BUFFER_SIZE
);
528 dirBuffer
[FILE_BUFFER_SIZE
-1] = '\0';
530 AutoDestroyTmpWindow
adtw((HWND
)(mParentWidget
.get() ?
531 mParentWidget
->GetNativeData(NS_NATIVE_TMP_WINDOW
) : nullptr));
533 BROWSEINFOW browserInfo
= {0};
534 browserInfo
.pidlRoot
= nullptr;
535 browserInfo
.pszDisplayName
= dirBuffer
;
536 browserInfo
.lpszTitle
= mTitle
.get();
537 browserInfo
.ulFlags
= BIF_USENEWUI
| BIF_RETURNONLYFSDIRS
;
538 browserInfo
.hwndOwner
= adtw
.get();
539 browserInfo
.iImage
= 0;
540 browserInfo
.lParam
= reinterpret_cast<LPARAM
>(this);
542 if (!aInitialDir
.IsEmpty()) {
543 // the dialog is modal so that |initialDir.get()| will be valid in
544 // BrowserCallbackProc. Thus, we don't need to clone it.
545 browserInfo
.lParam
= (LPARAM
) aInitialDir
.get();
546 browserInfo
.lpfn
= &BrowseCallbackProc
;
548 browserInfo
.lParam
= 0;
549 browserInfo
.lpfn
= nullptr;
552 LPITEMIDLIST list
= ::SHBrowseForFolderW(&browserInfo
);
554 result
= ::SHGetPathFromIDListW(list
, dirBuffer
);
556 mUnicodeFile
.Assign(static_cast<const wchar_t*>(dirBuffer
));
565 * Show a folder picker post Windows XP
567 * @param aInitialDir The initial directory, the last used directory will be
568 * used if left blank.
569 * @param aWasInitError Out parameter will hold true if there was an error
570 * before the folder picker is shown.
571 * @return true if a file was selected successfully.
574 nsFilePicker::ShowFolderPicker(const nsString
& aInitialDir
, bool &aWasInitError
)
576 nsRefPtr
<IFileOpenDialog
> dialog
;
577 if (FAILED(CoCreateInstance(CLSID_FileOpenDialog
, nullptr, CLSCTX_INPROC
,
579 getter_AddRefs(dialog
)))) {
580 aWasInitError
= true;
583 aWasInitError
= false;
585 // hook up event callbacks
586 dialog
->Advise(this, &mFDECookie
);
589 FILEOPENDIALOGOPTIONS fos
= FOS_PICKFOLDERS
;
590 dialog
->SetOptions(fos
);
593 dialog
->SetTitle(mTitle
.get());
594 if (!aInitialDir
.IsEmpty()) {
595 nsRefPtr
<IShellItem
> folder
;
597 WinUtils::SHCreateItemFromParsingName(aInitialDir
.get(), nullptr,
599 getter_AddRefs(folder
)))) {
600 dialog
->SetFolder(folder
);
604 AutoDestroyTmpWindow
adtw((HWND
)(mParentWidget
.get() ?
605 mParentWidget
->GetNativeData(NS_NATIVE_TMP_WINDOW
) : nullptr));
608 nsRefPtr
<IShellItem
> item
;
609 if (FAILED(dialog
->Show(adtw
.get())) ||
610 FAILED(dialog
->GetResult(getter_AddRefs(item
))) ||
612 dialog
->Unadvise(mFDECookie
);
615 dialog
->Unadvise(mFDECookie
);
619 // If the user chose a Win7 Library, resolve to the library's
620 // default save folder.
621 nsRefPtr
<IShellItem
> folderPath
;
622 nsRefPtr
<IShellLibrary
> shellLib
;
623 CoCreateInstance(CLSID_ShellLibrary
, nullptr, CLSCTX_INPROC
,
624 IID_IShellLibrary
, getter_AddRefs(shellLib
));
626 SUCCEEDED(shellLib
->LoadLibraryFromItem(item
, STGM_READ
)) &&
627 SUCCEEDED(shellLib
->GetDefaultSaveFolder(DSFT_DETECT
, IID_IShellItem
,
628 getter_AddRefs(folderPath
)))) {
629 item
.swap(folderPath
);
632 // get the folder's file system path
633 return WinUtils::GetShellItemPath(item
, mUnicodeFile
);
637 * File open and save picker invocation
641 nsFilePicker::FilePickerWrapper(OPENFILENAMEW
* ofn
, PickerType aType
)
647 AutoWidgetPickerState
awps(mParentWidget
);
649 if (aType
== PICKER_TYPE_OPEN
)
650 result
= ::GetOpenFileNameW(ofn
);
651 else if (aType
== PICKER_TYPE_SAVE
)
652 result
= ::GetSaveFileNameW(ofn
);
653 } MOZ_SEH_EXCEPT(true) {
654 NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!");
660 nsFilePicker::ShowXPFilePicker(const nsString
& aInitialDir
)
662 OPENFILENAMEW ofn
= {0};
663 ofn
.lStructSize
= sizeof(ofn
);
664 nsString filterBuffer
= mFilterList
;
666 nsAutoArrayPtr
<wchar_t> fileBuffer(new wchar_t[FILE_BUFFER_SIZE
]);
667 wcsncpy(fileBuffer
, mDefaultFilePath
.get(), FILE_BUFFER_SIZE
);
668 fileBuffer
[FILE_BUFFER_SIZE
-1] = '\0'; // null terminate in case copy truncated
670 if (!aInitialDir
.IsEmpty()) {
671 ofn
.lpstrInitialDir
= aInitialDir
.get();
674 AutoDestroyTmpWindow
adtw((HWND
) (mParentWidget
.get() ?
675 mParentWidget
->GetNativeData(NS_NATIVE_TMP_WINDOW
) : nullptr));
677 ofn
.lpstrTitle
= (LPCWSTR
)mTitle
.get();
678 ofn
.lpstrFilter
= (LPCWSTR
)filterBuffer
.get();
679 ofn
.nFilterIndex
= mSelectedType
;
680 ofn
.lpstrFile
= fileBuffer
;
681 ofn
.nMaxFile
= FILE_BUFFER_SIZE
;
682 ofn
.hwndOwner
= adtw
.get();
683 ofn
.lCustData
= reinterpret_cast<LPARAM
>(this);
684 ofn
.Flags
= OFN_SHAREAWARE
| OFN_LONGNAMES
| OFN_OVERWRITEPROMPT
|
685 OFN_HIDEREADONLY
| OFN_PATHMUSTEXIST
| OFN_ENABLESIZING
|
688 // Windows Vista and up won't allow you to use the new looking dialogs with
689 // a hook procedure. The hook procedure fixes a problem on XP dialogs for
690 // file picker visibility. Vista and up automatically ensures the file
691 // picker is always visible.
692 if (!IsVistaOrLater()) {
693 ofn
.lpfnHook
= FilePickerHook
;
694 ofn
.Flags
|= OFN_ENABLEHOOK
;
697 // Handle add to recent docs settings
698 if (IsPrivacyModeEnabled() || !mAddToRecentDocs
) {
699 ofn
.Flags
|= OFN_DONTADDTORECENT
;
702 NS_NAMED_LITERAL_STRING(htmExt
, "html");
704 if (!mDefaultExtension
.IsEmpty()) {
705 ofn
.lpstrDefExt
= mDefaultExtension
.get();
706 } else if (IsDefaultPathHtml()) {
707 // Get file extension from suggested filename to detect if we are
708 // saving an html file.
709 // This is supposed to append ".htm" if user doesn't supply an
710 // extension but the behavior is sort of weird:
711 // - Often appends ".html" even if you have an extension
712 // - It obeys your extension if you put quotes around name
713 ofn
.lpstrDefExt
= htmExt
.get();
716 // When possible, instead of using OFN_NOCHANGEDIR to ensure the current
717 // working directory will not change from this call, we will retrieve the
718 // current working directory before the call and restore it after the
719 // call. This flag causes problems on Windows XP for paths that are
720 // selected like C:test.txt where the user is currently at C:\somepath
721 // In which case expected result should be C:\somepath\test.txt
722 AutoRestoreWorkingPath restoreWorkingPath
;
723 // If we can't get the current working directory, the best case is to
724 // use the OFN_NOCHANGEDIR flag
725 if (!restoreWorkingPath
.HasWorkingPath()) {
726 ofn
.Flags
|= OFN_NOCHANGEDIR
;
734 ofn
.Flags
|= OFN_FILEMUSTEXIST
;
735 result
= FilePickerWrapper(&ofn
, PICKER_TYPE_OPEN
);
738 case modeOpenMultiple
:
739 ofn
.Flags
|= OFN_FILEMUSTEXIST
| OFN_ALLOWMULTISELECT
;
741 // The hook set here ensures that the buffer returned will always be
742 // large enough to hold all selected files. The hook may modify the
743 // value of ofn.lpstrFile and deallocate the old buffer that it pointed
744 // to (fileBuffer). The hook assumes that the passed in value is heap
745 // allocated and that the returned value should be freed by the caller.
746 // If the hook changes the buffer, it will deallocate the old buffer.
747 // This fix would be nice to have in Vista and up, but it would force
748 // the file picker to use the old style dialogs because hooks are not
749 // allowed in the new file picker UI. We need to eventually move to
750 // the new Common File Dialogs for Vista and up.
751 if (!IsVistaOrLater()) {
752 ofn
.lpfnHook
= MultiFilePickerHook
;
754 result
= FilePickerWrapper(&ofn
, PICKER_TYPE_OPEN
);
755 fileBuffer
= ofn
.lpstrFile
;
757 result
= FilePickerWrapper(&ofn
, PICKER_TYPE_OPEN
);
763 ofn
.Flags
|= OFN_NOREADONLYRETURN
;
765 // Don't follow shortcuts when saving a shortcut, this can be used
766 // to trick users (bug 271732)
767 if (IsDefaultPathLink())
768 ofn
.Flags
|= OFN_NODEREFERENCELINKS
;
770 result
= FilePickerWrapper(&ofn
, PICKER_TYPE_SAVE
);
772 // Error, find out what kind.
773 if (GetLastError() == ERROR_INVALID_PARAMETER
||
774 CommDlgExtendedError() == FNERR_INVALIDFILENAME
) {
775 // Probably the default file name is too long or contains illegal
776 // characters. Try again, without a starting file name.
777 ofn
.lpstrFile
[0] = L
'\0';
778 result
= FilePickerWrapper(&ofn
, PICKER_TYPE_SAVE
);
785 NS_NOTREACHED("unsupported file picker mode");
792 // Remember what filter type the user selected
793 mSelectedType
= (int16_t)ofn
.nFilterIndex
;
795 // Single file selection, we're done
796 if (mMode
!= modeOpenMultiple
) {
797 GetQualifiedPath(fileBuffer
, mUnicodeFile
);
801 // Set user-selected location of file or directory. From msdn's "Open and
802 // Save As Dialog Boxes" section:
803 // If you specify OFN_EXPLORER, the directory and file name strings are '\0'
804 // separated, with an extra '\0' character after the last file name. This
805 // format enables the Explorer-style dialog boxes to return long file names
806 // that include spaces.
807 wchar_t *current
= fileBuffer
;
809 nsAutoString
dirName(current
);
810 // Sometimes dirName contains a trailing slash and sometimes it doesn't:
811 if (current
[dirName
.Length() - 1] != '\\')
812 dirName
.Append((char16_t
)'\\');
814 while (current
&& *current
&& *(current
+ wcslen(current
) + 1)) {
815 current
= current
+ wcslen(current
) + 1;
817 nsCOMPtr
<nsIFile
> file
= do_CreateInstance("@mozilla.org/file/local;1");
818 NS_ENSURE_TRUE(file
, false);
820 // Only prepend the directory if the path specified is a relative path
822 if (PathIsRelativeW(current
)) {
823 path
= dirName
+ nsDependentString(current
);
828 nsAutoString canonicalizedPath
;
829 GetQualifiedPath(path
.get(), canonicalizedPath
);
830 if (NS_FAILED(file
->InitWithPath(canonicalizedPath
)) ||
831 !mFiles
.AppendObject(file
))
835 // Handle the case where the user selected just one file. From msdn: If you
836 // specify OFN_ALLOWMULTISELECT and the user selects only one file the
837 // lpstrFile string does not have a separator between the path and file name.
838 if (current
&& *current
&& (current
== fileBuffer
)) {
839 nsCOMPtr
<nsIFile
> file
= do_CreateInstance("@mozilla.org/file/local;1");
840 NS_ENSURE_TRUE(file
, false);
842 nsAutoString canonicalizedPath
;
843 GetQualifiedPath(current
, canonicalizedPath
);
844 if (NS_FAILED(file
->InitWithPath(canonicalizedPath
)) ||
845 !mFiles
.AppendObject(file
))
853 * Show a file picker post Windows XP
855 * @param aInitialDir The initial directory, the last used directory will be
856 * used if left blank.
857 * @param aWasInitError Out parameter will hold true if there was an error
858 * before the file picker is shown.
859 * @return true if a file was selected successfully.
862 nsFilePicker::ShowFilePicker(const nsString
& aInitialDir
, bool &aWasInitError
)
864 nsRefPtr
<IFileDialog
> dialog
;
865 if (mMode
!= modeSave
) {
866 if (FAILED(CoCreateInstance(CLSID_FileOpenDialog
, nullptr, CLSCTX_INPROC
,
868 getter_AddRefs(dialog
)))) {
869 aWasInitError
= true;
873 if (FAILED(CoCreateInstance(CLSID_FileSaveDialog
, nullptr, CLSCTX_INPROC
,
875 getter_AddRefs(dialog
)))) {
876 aWasInitError
= true;
880 aWasInitError
= false;
882 // hook up event callbacks
883 dialog
->Advise(this, &mFDECookie
);
887 FILEOPENDIALOGOPTIONS fos
= 0;
888 fos
|= FOS_SHAREAWARE
| FOS_OVERWRITEPROMPT
|
891 // Handle add to recent docs settings
892 if (IsPrivacyModeEnabled() || !mAddToRecentDocs
) {
893 fos
|= FOS_DONTADDTORECENT
;
896 // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this
898 AutoRestoreWorkingPath arw
;
903 fos
|= FOS_FILEMUSTEXIST
;
906 case modeOpenMultiple
:
907 fos
|= FOS_FILEMUSTEXIST
| FOS_ALLOWMULTISELECT
;
911 fos
|= FOS_NOREADONLYRETURN
;
912 // Don't follow shortcuts when saving a shortcut, this can be used
913 // to trick users (bug 271732)
914 if (IsDefaultPathLink())
915 fos
|= FOS_NODEREFERENCELINKS
;
919 dialog
->SetOptions(fos
);
924 dialog
->SetTitle(mTitle
.get());
927 if (!mDefaultFilename
.IsEmpty()) {
928 dialog
->SetFileName(mDefaultFilename
.get());
931 NS_NAMED_LITERAL_STRING(htmExt
, "html");
933 // default extension to append to new files
934 if (!mDefaultExtension
.IsEmpty()) {
935 dialog
->SetDefaultExtension(mDefaultExtension
.get());
936 } else if (IsDefaultPathHtml()) {
937 dialog
->SetDefaultExtension(htmExt
.get());
941 if (!aInitialDir
.IsEmpty()) {
942 nsRefPtr
<IShellItem
> folder
;
944 WinUtils::SHCreateItemFromParsingName(aInitialDir
.get(), nullptr,
946 getter_AddRefs(folder
)))) {
947 dialog
->SetFolder(folder
);
951 // filter types and the default index
952 if (!mComFilterList
.IsEmpty()) {
953 dialog
->SetFileTypes(mComFilterList
.Length(), mComFilterList
.get());
954 dialog
->SetFileTypeIndex(mSelectedType
);
960 AutoDestroyTmpWindow
adtw((HWND
)(mParentWidget
.get() ?
961 mParentWidget
->GetNativeData(NS_NATIVE_TMP_WINDOW
) : nullptr));
962 AutoTimerCallbackCancel
atcc(this, PickerCallbackTimerFunc
);
963 AutoWidgetPickerState
awps(mParentWidget
);
965 if (FAILED(dialog
->Show(adtw
.get()))) {
966 dialog
->Unadvise(mFDECookie
);
969 dialog
->Unadvise(mFDECookie
);
974 // Remember what filter type the user selected
975 UINT filterIdxResult
;
976 if (SUCCEEDED(dialog
->GetFileTypeIndex(&filterIdxResult
))) {
977 mSelectedType
= (int16_t)filterIdxResult
;
981 if (mMode
!= modeOpenMultiple
) {
982 nsRefPtr
<IShellItem
> item
;
983 if (FAILED(dialog
->GetResult(getter_AddRefs(item
))) || !item
)
985 return WinUtils::GetShellItemPath(item
, mUnicodeFile
);
988 // multiple selection
989 nsRefPtr
<IFileOpenDialog
> openDlg
;
990 dialog
->QueryInterface(IID_IFileOpenDialog
, getter_AddRefs(openDlg
));
996 nsRefPtr
<IShellItemArray
> items
;
997 if (FAILED(openDlg
->GetResults(getter_AddRefs(items
))) || !items
) {
1002 items
->GetCount(&count
);
1003 for (unsigned int idx
= 0; idx
< count
; idx
++) {
1004 nsRefPtr
<IShellItem
> item
;
1006 if (SUCCEEDED(items
->GetItemAt(idx
, getter_AddRefs(item
)))) {
1007 if (!WinUtils::GetShellItemPath(item
, str
))
1009 nsCOMPtr
<nsIFile
> file
= do_CreateInstance("@mozilla.org/file/local;1");
1010 if (file
&& NS_SUCCEEDED(file
->InitWithPath(str
)))
1011 mFiles
.AppendObject(file
);
1017 ///////////////////////////////////////////////////////////////////////////////
1018 // nsIFilePicker impl.
1021 nsFilePicker::ShowW(int16_t *aReturnVal
)
1023 NS_ENSURE_ARG_POINTER(aReturnVal
);
1025 *aReturnVal
= returnCancel
;
1027 AutoSuppressEvents
supress(mParentWidget
);
1029 nsAutoString initialDir
;
1030 if (mDisplayDirectory
)
1031 mDisplayDirectory
->GetPath(initialDir
);
1033 // If no display directory, re-use the last one.
1034 if(initialDir
.IsEmpty()) {
1035 // Allocate copy of last used dir.
1036 initialDir
= mLastUsedUnicodeDirectory
;
1039 // Clear previous file selections
1040 mUnicodeFile
.Truncate();
1043 // Launch the XP file/folder picker on XP and as a fallback on Vista+.
1044 // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111)
1045 // ClassFactory cannot supply requested class" when the checkbox for
1046 // Disable Visual Themes is on in the compatability tab within the shortcut
1048 bool result
= false, wasInitError
= true;
1049 if (mMode
== modeGetFolder
) {
1050 if (IsVistaOrLater())
1051 result
= ShowFolderPicker(initialDir
, wasInitError
);
1052 if (!result
&& wasInitError
)
1053 result
= ShowXPFolderPicker(initialDir
);
1055 if (IsVistaOrLater())
1056 result
= ShowFilePicker(initialDir
, wasInitError
);
1057 if (!result
&& wasInitError
)
1058 result
= ShowXPFilePicker(initialDir
);
1061 // exit, and return returnCancel in aReturnVal
1065 RememberLastUsedDirectory();
1067 int16_t retValue
= returnOK
;
1068 if (mMode
== modeSave
) {
1069 // Windows does not return resultReplace, we must check if file
1071 nsCOMPtr
<nsIFile
> file(do_CreateInstance("@mozilla.org/file/local;1"));
1073 if (file
&& NS_SUCCEEDED(file
->InitWithPath(mUnicodeFile
)) &&
1074 NS_SUCCEEDED(file
->Exists(&flag
)) && flag
) {
1075 retValue
= returnReplace
;
1079 *aReturnVal
= retValue
;
1084 nsFilePicker::Show(int16_t *aReturnVal
)
1086 return ShowW(aReturnVal
);
1090 nsFilePicker::GetFile(nsIFile
**aFile
)
1092 NS_ENSURE_ARG_POINTER(aFile
);
1095 if (mUnicodeFile
.IsEmpty())
1098 nsCOMPtr
<nsIFile
> file(do_CreateInstance("@mozilla.org/file/local;1"));
1100 NS_ENSURE_TRUE(file
, NS_ERROR_FAILURE
);
1102 file
->InitWithPath(mUnicodeFile
);
1104 NS_ADDREF(*aFile
= file
);
1110 nsFilePicker::GetFileURL(nsIURI
**aFileURL
)
1112 *aFileURL
= nullptr;
1113 nsCOMPtr
<nsIFile
> file
;
1114 nsresult rv
= GetFile(getter_AddRefs(file
));
1118 return NS_NewFileURI(aFileURL
, file
);
1122 nsFilePicker::GetFiles(nsISimpleEnumerator
**aFiles
)
1124 NS_ENSURE_ARG_POINTER(aFiles
);
1125 return NS_NewArrayEnumerator(aFiles
, mFiles
);
1128 // Get the file + path
1130 nsBaseWinFilePicker::SetDefaultString(const nsAString
& aString
)
1132 mDefaultFilePath
= aString
;
1134 // First, make sure the file name is not too long.
1136 int32_t nameIndex
= mDefaultFilePath
.RFind("\\");
1137 if (nameIndex
== kNotFound
)
1141 nameLength
= mDefaultFilePath
.Length() - nameIndex
;
1142 mDefaultFilename
.Assign(Substring(mDefaultFilePath
, nameIndex
));
1144 if (nameLength
> MAX_PATH
) {
1145 int32_t extIndex
= mDefaultFilePath
.RFind(".");
1146 if (extIndex
== kNotFound
)
1147 extIndex
= mDefaultFilePath
.Length();
1149 // Let's try to shave the needed characters from the name part.
1150 int32_t charsToRemove
= nameLength
- MAX_PATH
;
1151 if (extIndex
- nameIndex
>= charsToRemove
) {
1152 mDefaultFilePath
.Cut(extIndex
- charsToRemove
, charsToRemove
);
1156 // Then, we need to replace illegal characters. At this stage, we cannot
1157 // replace the backslash as the string might represent a file path.
1158 mDefaultFilePath
.ReplaceChar(FILE_ILLEGAL_CHARACTERS
, '-');
1159 mDefaultFilename
.ReplaceChar(FILE_ILLEGAL_CHARACTERS
, '-');
1165 nsBaseWinFilePicker::GetDefaultString(nsAString
& aString
)
1167 return NS_ERROR_FAILURE
;
1170 // The default extension to use for files
1172 nsBaseWinFilePicker::GetDefaultExtension(nsAString
& aExtension
)
1174 aExtension
= mDefaultExtension
;
1179 nsBaseWinFilePicker::SetDefaultExtension(const nsAString
& aExtension
)
1181 mDefaultExtension
= aExtension
;
1185 // Set the filter index
1187 nsFilePicker::GetFilterIndex(int32_t *aFilterIndex
)
1189 // Windows' filter index is 1-based, we use a 0-based system.
1190 *aFilterIndex
= mSelectedType
- 1;
1195 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
)
1197 // Windows' filter index is 1-based, we use a 0-based system.
1198 mSelectedType
= aFilterIndex
+ 1;
1203 nsFilePicker::InitNative(nsIWidget
*aParent
,
1204 const nsAString
& aTitle
)
1206 mParentWidget
= aParent
;
1207 mTitle
.Assign(aTitle
);
1211 nsFilePicker::GetQualifiedPath(const wchar_t *aInPath
, nsString
&aOutPath
)
1213 // Prefer a qualified path over a non qualified path.
1214 // Things like c:file.txt would be accepted in Win XP but would later
1215 // fail to open from the download manager.
1216 wchar_t qualifiedFileBuffer
[MAX_PATH
];
1217 if (PathSearchAndQualifyW(aInPath
, qualifiedFileBuffer
, MAX_PATH
)) {
1218 aOutPath
.Assign(qualifiedFileBuffer
);
1220 aOutPath
.Assign(aInPath
);
1225 nsFilePicker::AppendXPFilter(const nsAString
& aTitle
, const nsAString
& aFilter
)
1227 mFilterList
.Append(aTitle
);
1228 mFilterList
.Append(char16_t('\0'));
1230 if (aFilter
.EqualsLiteral("..apps"))
1231 mFilterList
.AppendLiteral("*.exe;*.com");
1234 nsAutoString
filter(aFilter
);
1235 filter
.StripWhitespace();
1236 if (filter
.EqualsLiteral("*"))
1237 filter
.AppendLiteral(".*");
1238 mFilterList
.Append(filter
);
1241 mFilterList
.Append(char16_t('\0'));
1245 nsFilePicker::AppendFilter(const nsAString
& aTitle
, const nsAString
& aFilter
)
1247 if (IsVistaOrLater()) {
1248 mComFilterList
.Append(aTitle
, aFilter
);
1250 AppendXPFilter(aTitle
, aFilter
);
1256 nsFilePicker::RememberLastUsedDirectory()
1258 nsCOMPtr
<nsIFile
> file(do_CreateInstance("@mozilla.org/file/local;1"));
1259 if (!file
|| NS_FAILED(file
->InitWithPath(mUnicodeFile
))) {
1260 NS_WARNING("RememberLastUsedDirectory failed to init file path.");
1264 nsCOMPtr
<nsIFile
> dir
;
1265 nsAutoString newDir
;
1266 if (NS_FAILED(file
->GetParent(getter_AddRefs(dir
))) ||
1267 !(mDisplayDirectory
= do_QueryInterface(dir
)) ||
1268 NS_FAILED(mDisplayDirectory
->GetPath(newDir
)) ||
1270 NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
1274 if (mLastUsedUnicodeDirectory
) {
1275 NS_Free(mLastUsedUnicodeDirectory
);
1276 mLastUsedUnicodeDirectory
= nullptr;
1278 mLastUsedUnicodeDirectory
= ToNewUnicode(newDir
);
1282 nsFilePicker::IsPrivacyModeEnabled()
1284 return mLoadContext
&& mLoadContext
->UsePrivateBrowsing();
1288 nsFilePicker::IsDefaultPathLink()
1290 NS_ConvertUTF16toUTF8
ext(mDefaultFilePath
);
1291 ext
.Trim(" .", false, true); // watch out for trailing space and dots
1293 if (StringEndsWith(ext
, NS_LITERAL_CSTRING(".lnk")) ||
1294 StringEndsWith(ext
, NS_LITERAL_CSTRING(".pif")) ||
1295 StringEndsWith(ext
, NS_LITERAL_CSTRING(".url")))
1301 nsFilePicker::IsDefaultPathHtml()
1303 int32_t extIndex
= mDefaultFilePath
.RFind(".");
1304 if (extIndex
>= 0) {
1306 mDefaultFilePath
.Right(ext
, mDefaultFilePath
.Length() - extIndex
);
1307 if (ext
.LowerCaseEqualsLiteral(".htm") ||
1308 ext
.LowerCaseEqualsLiteral(".html") ||
1309 ext
.LowerCaseEqualsLiteral(".shtml"))
1316 nsFilePicker::ComDlgFilterSpec::Append(const nsAString
& aTitle
, const nsAString
& aFilter
)
1318 COMDLG_FILTERSPEC
* pSpecForward
= mSpecList
.AppendElement();
1319 if (!pSpecForward
) {
1320 NS_WARNING("mSpecList realloc failed.");
1323 memset(pSpecForward
, 0, sizeof(*pSpecForward
));
1324 nsString
* pStr
= mStrings
.AppendElement(aTitle
);
1326 NS_WARNING("mStrings.AppendElement failed.");
1329 pSpecForward
->pszName
= pStr
->get();
1330 pStr
= mStrings
.AppendElement(aFilter
);
1332 NS_WARNING("mStrings.AppendElement failed.");
1335 if (aFilter
.EqualsLiteral("..apps"))
1336 pStr
->AssignLiteral("*.exe;*.com");
1338 pStr
->StripWhitespace();
1339 if (pStr
->EqualsLiteral("*"))
1340 pStr
->AppendLiteral(".*");
1342 pSpecForward
->pszSpec
= pStr
->get();