diff options
Diffstat (limited to 'widget/windows/nsFilePicker.cpp')
-rw-r--r-- | widget/windows/nsFilePicker.cpp | 542 |
1 files changed, 15 insertions, 527 deletions
diff --git a/widget/windows/nsFilePicker.cpp b/widget/windows/nsFilePicker.cpp index 53857cf5e..4e942968a 100644 --- a/widget/windows/nsFilePicker.cpp +++ b/widget/windows/nsFilePicker.cpp @@ -28,7 +28,6 @@ #include "nsPIDOMWindow.h" #include "GeckoProfiler.h" -using mozilla::IsVistaOrLater; using mozilla::IsWin8OrLater; using mozilla::MakeUnique; using mozilla::mscom::EnsureMTA; @@ -219,191 +218,6 @@ STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult) return E_NOINTERFACE; } -/* - * XP picker callbacks - */ - -// Show - Display the file dialog -int CALLBACK -BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) -{ - if (uMsg == BFFM_INITIALIZED) - { - char16_t * filePath = (char16_t *) lpData; - if (filePath) - ::SendMessageW(hwnd, BFFM_SETSELECTIONW, - TRUE /* true because lpData is a path string */, - lpData); - } - return 0; -} - -static void -EnsureWindowVisible(HWND hwnd) -{ - // Obtain the monitor which has the largest area of intersection - // with the window, or nullptr if there is no intersection. - HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); - if (!monitor) { - // The window is not visible, we should reposition it to the same place as its parent - HWND parentHwnd = GetParent(hwnd); - RECT parentRect; - GetWindowRect(parentHwnd, &parentRect); - SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0, - SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); - } -} - -// Callback hook which will ensure that the window is visible. Currently -// only in use on os <= XP. -UINT_PTR CALLBACK -nsFilePicker::FilePickerHook(HWND hwnd, - UINT msg, - WPARAM wParam, - LPARAM lParam) -{ - switch(msg) { - case WM_NOTIFY: - { - LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; - if (!lpofn || !lpofn->lpOFN) { - return 0; - } - - if (CDN_INITDONE == lpofn->hdr.code) { - // The Window will be automatically moved to the last position after - // CDN_INITDONE. We post a message to ensure the window will be visible - // so it will be done after the automatic last position window move. - PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); - } - } - break; - case MOZ_WM_ENSUREVISIBLE: - EnsureWindowVisible(GetParent(hwnd)); - break; - case WM_INITDIALOG: - { - OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); - SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); - nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData); - if (picker) { - picker->SetDialogHandle(hwnd); - SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); - } - } - break; - case WM_TIMER: - { - // Check to see if our parent has been torn down, if so, we close too. - if (wParam == kDialogTimerID) { - nsFilePicker* picker = - reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); - if (picker && picker->ClosePickerIfNeeded(true)) { - KillTimer(hwnd, kDialogTimerID); - } - } - } - break; - } - return 0; -} - - -// Callback hook which will dynamically allocate a buffer large enough -// for the file picker dialog. Currently only in use on os <= XP. -UINT_PTR CALLBACK -nsFilePicker::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. - // A wParam of 0 mean 0x7FFFFFFE characters. - HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr, - L"ComboBoxEx32", nullptr ); - if(comboBox) - SendMessage(comboBox, CB_LIMITTEXT, 0, 0); - // Store our nsFilePicker ptr for future use - OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); - SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); - nsFilePicker* picker = - reinterpret_cast<nsFilePicker*>(pofn->lCustData); - if (picker) { - picker->SetDialogHandle(hwnd); - SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); - } - } - break; - case WM_NOTIFY: - { - LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; - if (!lpofn || !lpofn->lpOFN) { - return 0; - } - // CDN_SELCHANGE is sent when the selection in the list box of the file - // selection dialog changes - if (lpofn->hdr.code == CDN_SELCHANGE) { - HWND parentHWND = GetParent(hwnd); - - // Get the required size for the selected files buffer - UINT newBufLength = 0; - int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND, - nullptr, 0); - if(requiredBufLength >= 0) - newBufLength += requiredBufLength; - else - newBufLength += MAX_PATH; - - // If the user selects multiple files, the buffer contains the - // current directory followed by the file names of the selected - // files. So make room for the directory path. If the user - // selects a single file, it is no harm to add extra space. - requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND, - nullptr, 0); - if(requiredBufLength >= 0) - newBufLength += requiredBufLength; - else - newBufLength += MAX_PATH; - - // Check if lpstrFile and nMaxFile are large enough - if (newBufLength > lpofn->lpOFN->nMaxFile) { - if (lpofn->lpOFN->lpstrFile) - delete[] lpofn->lpOFN->lpstrFile; - - // We allocate FILE_BUFFER_SIZE more bytes than is needed so that - // if the user selects a file and holds down shift and down to - // select additional items, we will not continuously reallocate - newBufLength += FILE_BUFFER_SIZE; - - wchar_t* filesBuffer = new wchar_t[newBufLength]; - ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t)); - - lpofn->lpOFN->lpstrFile = filesBuffer; - lpofn->lpOFN->nMaxFile = newBufLength; - } - } - } - break; - case WM_TIMER: - { - // Check to see if our parent has been torn down, if so, we close too. - if (wParam == kDialogTimerID) { - nsFilePicker* picker = - reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); - if (picker && picker->ClosePickerIfNeeded(true)) { - KillTimer(hwnd, kDialogTimerID); - } - } - } - break; - } - - return FilePickerHook(hwnd, msg, wParam, lParam); -} /* * Vista+ callbacks @@ -476,25 +290,18 @@ nsFilePicker::OnOverwrite(IFileDialog *pfd, */ bool -nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog) +nsFilePicker::ClosePickerIfNeeded() { if (!mParentWidget || !mDlgWnd) return false; nsWindow *win = static_cast<nsWindow *>(mParentWidget.get()); - // Note, the xp callbacks hand us an inner window, so we have to step up - // one to get the actual dialog. - HWND dlgWnd; - if (aIsXPDialog) - dlgWnd = GetParent(mDlgWnd); - else - dlgWnd = mDlgWnd; - if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) { + if (IsWindow(mDlgWnd) && IsWindowVisible(mDlgWnd) && win->DestroyCalled()) { wchar_t className[64]; // Make sure we have the right window - if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) && + if (GetClassNameW(mDlgWnd, className, mozilla::ArrayLength(className)) && !wcscmp(className, L"#32770") && - DestroyWindow(dlgWnd)) { + DestroyWindow(mDlgWnd)) { mDlgWnd = nullptr; return true; } @@ -506,7 +313,7 @@ void nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx) { nsFilePicker* picker = (nsFilePicker*)aCtx; - if (picker->ClosePickerIfNeeded(false)) { + if (picker->ClosePickerIfNeeded()) { aTimer->Cancel(); } } @@ -523,62 +330,15 @@ nsFilePicker::SetDialogHandle(HWND aWnd) * Folder picker invocation */ -// Open the older XP style folder picker dialog. We end up in this call -// on XP systems or when platform is built without the longhorn SDK. -bool -nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir) -{ - bool result = false; - - auto dirBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE); - wcsncpy(dirBuffer.get(), aInitialDir.get(), FILE_BUFFER_SIZE); - dirBuffer[FILE_BUFFER_SIZE-1] = '\0'; - - AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? - mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); - - BROWSEINFOW browserInfo = {0}; - browserInfo.pidlRoot = nullptr; - browserInfo.pszDisplayName = dirBuffer.get(); - browserInfo.lpszTitle = mTitle.get(); - browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; - browserInfo.hwndOwner = adtw.get(); - browserInfo.iImage = 0; - browserInfo.lParam = reinterpret_cast<LPARAM>(this); - - if (!aInitialDir.IsEmpty()) { - // the dialog is modal so that |initialDir.get()| will be valid in - // BrowserCallbackProc. Thus, we don't need to clone it. - browserInfo.lParam = (LPARAM) aInitialDir.get(); - browserInfo.lpfn = &BrowseCallbackProc; - } else { - browserInfo.lParam = 0; - browserInfo.lpfn = nullptr; - } - - LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); - if (list) { - result = ::SHGetPathFromIDListW(list, dirBuffer.get()); - if (result) - mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer.get())); - // free PIDL - CoTaskMemFree(list); - } - - return result; -} - /* - * Show a folder picker post Windows XP - * + * Show a folder picker. + * * @param aInitialDir The initial directory, the last used directory will be * used if left blank. - * @param aWasInitError Out parameter will hold true if there was an error - * before the folder picker is shown. * @return true if a file was selected successfully. */ bool -nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) +nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) { if (!IsWin8OrLater()) { // Some Windows 7 users are experiencing a race condition when some dlls @@ -593,10 +353,8 @@ nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, IID_IFileOpenDialog, getter_AddRefs(dialog)))) { - aWasInitError = true; return false; } - aWasInitError = false; // hook up event callbacks dialog->Advise(this, &mFDECookie); @@ -658,233 +416,15 @@ nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) * File open and save picker invocation */ -/* static */ bool -nsFilePicker::GetFileNameWrapper(OPENFILENAMEW* ofn, PickerType aType) -{ - MOZ_SEH_TRY { - if (aType == PICKER_TYPE_OPEN) - return ::GetOpenFileNameW(ofn); - else if (aType == PICKER_TYPE_SAVE) - return ::GetSaveFileNameW(ofn); - } MOZ_SEH_EXCEPT(true) { - NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!"); - } - return false; -} - -bool -nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType) -{ - if (!ofn) - return false; - AutoWidgetPickerState awps(mParentWidget); - return GetFileNameWrapper(ofn, aType); -} - -bool -nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir) -{ - OPENFILENAMEW ofn = {0}; - ofn.lStructSize = sizeof(ofn); - nsString filterBuffer = mFilterList; - - auto fileBuffer = MakeUnique<wchar_t[]>(FILE_BUFFER_SIZE); - wcsncpy(fileBuffer.get(), mDefaultFilePath.get(), FILE_BUFFER_SIZE); - fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated - - if (!aInitialDir.IsEmpty()) { - ofn.lpstrInitialDir = aInitialDir.get(); - } - - AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? - mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); - - ofn.lpstrTitle = (LPCWSTR)mTitle.get(); - ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); - ofn.nFilterIndex = mSelectedType; - ofn.lpstrFile = fileBuffer.get(); - ofn.nMaxFile = FILE_BUFFER_SIZE; - ofn.hwndOwner = adtw.get(); - ofn.lCustData = reinterpret_cast<LPARAM>(this); - 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. - if (!IsVistaOrLater()) { - ofn.lpfnHook = FilePickerHook; - ofn.Flags |= OFN_ENABLEHOOK; - } - - // Handle add to recent docs settings - if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { - ofn.Flags |= OFN_DONTADDTORECENT; - } - - NS_NAMED_LITERAL_STRING(htmExt, "html"); - - if (!mDefaultExtension.IsEmpty()) { - ofn.lpstrDefExt = mDefaultExtension.get(); - } else if (IsDefaultPathHtml()) { - // Get file extension from suggested filename to detect if we are - // saving an html file. - // This is supposed to append ".htm" if user doesn't supply an - // extension but the behavior is sort of weird: - // - Often appends ".html" even if you have an extension - // - It obeys your extension if you put quotes around name - ofn.lpstrDefExt = htmExt.get(); - } - - // When possible, instead of using OFN_NOCHANGEDIR to ensure the current - // working directory will not change from this call, we will retrieve the - // current working directory before the call and restore it after the - // call. This flag causes problems on Windows XP for paths that are - // selected like C:test.txt where the user is currently at C:\somepath - // 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; - } - - bool result = false; - - switch(mMode) { - case modeOpen: - // FILE MUST EXIST! - ofn.Flags |= OFN_FILEMUSTEXIST; - result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); - break; - - case 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 (!IsVistaOrLater()) { - ofn.lpfnHook = MultiFilePickerHook; - fileBuffer.release(); - result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); - fileBuffer.reset(ofn.lpstrFile); - } else { - result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); - } - break; - - case modeSave: - { - ofn.Flags |= OFN_NOREADONLYRETURN; - - // Don't follow shortcuts when saving a shortcut, this can be used - // to trick users (bug 271732) - if (IsDefaultPathLink()) - ofn.Flags |= OFN_NODEREFERENCELINKS; - - result = FilePickerWrapper(&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] = L'\0'; - result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); - } - } - } - break; - - default: - NS_NOTREACHED("unsupported file picker mode"); - return false; - } - - if (!result) - return false; - - // Remember what filter type the user selected - mSelectedType = (int16_t)ofn.nFilterIndex; - - // Single file selection, we're done - if (mMode != modeOpenMultiple) { - GetQualifiedPath(fileBuffer.get(), mUnicodeFile); - return true; - } - - // Set user-selected location of file or directory. From msdn's "Open and - // Save As Dialog Boxes" section: - // If you specify OFN_EXPLORER, the directory and file name strings are '\0' - // separated, with an extra '\0' character after the last file name. This - // format enables the Explorer-style dialog boxes to return long file names - // that include spaces. - wchar_t *current = fileBuffer.get(); - - nsAutoString dirName(current); - // Sometimes dirName contains a trailing slash and sometimes it doesn't: - if (current[dirName.Length() - 1] != '\\') - dirName.Append((char16_t)'\\'); - - while (current && *current && *(current + wcslen(current) + 1)) { - current = current + wcslen(current) + 1; - - nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); - NS_ENSURE_TRUE(file, false); - - // Only prepend the directory if the path specified is a relative path - nsAutoString path; - if (PathIsRelativeW(current)) { - path = dirName + nsDependentString(current); - } else { - path = current; - } - - nsAutoString canonicalizedPath; - GetQualifiedPath(path.get(), canonicalizedPath); - if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || - !mFiles.AppendObject(file)) - return false; - } - - // Handle the case where the user selected just one file. From msdn: If you - // specify OFN_ALLOWMULTISELECT and the user selects only one file the - // lpstrFile string does not have a separator between the path and file name. - if (current && *current && (current == fileBuffer.get())) { - nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); - NS_ENSURE_TRUE(file, false); - - nsAutoString canonicalizedPath; - GetQualifiedPath(current, canonicalizedPath); - if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || - !mFiles.AppendObject(file)) - return false; - } - - return true; -} - /* - * Show a file picker post Windows XP - * + * Show a file picker. + * * @param aInitialDir The initial directory, the last used directory will be * used if left blank. - * @param aWasInitError Out parameter will hold true if there was an error - * before the file picker is shown. * @return true if a file was selected successfully. */ bool -nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError) +nsFilePicker::ShowFilePicker(const nsString& aInitialDir) { PROFILER_LABEL_FUNC(js::ProfileEntry::Category::OTHER); @@ -902,18 +442,15 @@ nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError) if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, IID_IFileOpenDialog, getter_AddRefs(dialog)))) { - aWasInitError = true; return false; } } else { if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, IID_IFileSaveDialog, getter_AddRefs(dialog)))) { - aWasInitError = true; return false; } } - aWasInitError = false; // hook up event callbacks dialog->Advise(this, &mFDECookie); @@ -1080,22 +617,11 @@ nsFilePicker::ShowW(int16_t *aReturnVal) // with our context set temporarily to system-dpi-aware WinUtils::AutoSystemDpiAware dpiAwareness; - // Launch the XP file/folder picker on XP and as a fallback on Vista+. - // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111) - // ClassFactory cannot supply requested class" when the checkbox for - // Disable Visual Themes is on in the compatability tab within the shortcut - // properties. - bool result = false, wasInitError = true; + bool result = false; if (mMode == modeGetFolder) { - if (IsVistaOrLater()) - result = ShowFolderPicker(initialDir, wasInitError); - if (!result && wasInitError) - result = ShowXPFolderPicker(initialDir); + result = ShowFolderPicker(initialDir); } else { - if (IsVistaOrLater()) - result = ShowFilePicker(initialDir, wasInitError); - if (!result && wasInitError) - result = ShowXPFilePicker(initialDir); + result = ShowFilePicker(initialDir); } // exit, and return returnCancel in aReturnVal @@ -1247,48 +773,10 @@ nsFilePicker::InitNative(nsIWidget *aParent, mTitle.Assign(aTitle); } -void -nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath) -{ - // Prefer a qualified path over a non qualified path. - // Things like c:file.txt would be accepted in Win XP but would later - // fail to open from the download manager. - wchar_t qualifiedFileBuffer[MAX_PATH]; - if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) { - aOutPath.Assign(qualifiedFileBuffer); - } else { - aOutPath.Assign(aInPath); - } -} - -void -nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter) -{ - mFilterList.Append(aTitle); - mFilterList.Append(char16_t('\0')); - - if (aFilter.EqualsLiteral("..apps")) - mFilterList.AppendLiteral("*.exe;*.com"); - else - { - nsAutoString filter(aFilter); - filter.StripWhitespace(); - if (filter.EqualsLiteral("*")) - filter.AppendLiteral(".*"); - mFilterList.Append(filter); - } - - mFilterList.Append(char16_t('\0')); -} - NS_IMETHODIMP nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) { - if (IsVistaOrLater()) { - mComFilterList.Append(aTitle, aFilter); - } else { - AppendXPFilter(aTitle, aFilter); - } + mComFilterList.Append(aTitle, aFilter); return NS_OK; } |