1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/widget/windows/nsFilePicker.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,1343 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "nsFilePicker.h" 1.11 + 1.12 +#include <shlobj.h> 1.13 +#include <shlwapi.h> 1.14 +#include <cderr.h> 1.15 + 1.16 +#include "mozilla/WindowsVersion.h" 1.17 +#include "nsReadableUtils.h" 1.18 +#include "nsNetUtil.h" 1.19 +#include "nsWindow.h" 1.20 +#include "nsILoadContext.h" 1.21 +#include "nsIServiceManager.h" 1.22 +#include "nsIURL.h" 1.23 +#include "nsIStringBundle.h" 1.24 +#include "nsEnumeratorUtils.h" 1.25 +#include "nsCRT.h" 1.26 +#include "nsString.h" 1.27 +#include "nsToolkit.h" 1.28 +#include "WinUtils.h" 1.29 +#include "nsPIDOMWindow.h" 1.30 + 1.31 +using mozilla::IsVistaOrLater; 1.32 +using namespace mozilla::widget; 1.33 + 1.34 +char16_t *nsFilePicker::mLastUsedUnicodeDirectory; 1.35 +char nsFilePicker::mLastUsedDirectory[MAX_PATH+1] = { 0 }; 1.36 + 1.37 +static const wchar_t kDialogPtrProp[] = L"DialogPtrProperty"; 1.38 +static const DWORD kDialogTimerID = 9999; 1.39 +static const unsigned long kDialogTimerTimeout = 300; 1.40 + 1.41 +#define MAX_EXTENSION_LENGTH 10 1.42 +#define FILE_BUFFER_SIZE 4096 1.43 + 1.44 +typedef DWORD FILEOPENDIALOGOPTIONS; 1.45 + 1.46 +/////////////////////////////////////////////////////////////////////////////// 1.47 +// Helper classes 1.48 + 1.49 +// Manages matching SuppressBlurEvents calls on the parent widget. 1.50 +class AutoSuppressEvents 1.51 +{ 1.52 +public: 1.53 + explicit AutoSuppressEvents(nsIWidget* aWidget) : 1.54 + mWindow(static_cast<nsWindow *>(aWidget)) { 1.55 + SuppressWidgetEvents(true); 1.56 + } 1.57 + 1.58 + ~AutoSuppressEvents() { 1.59 + SuppressWidgetEvents(false); 1.60 + } 1.61 +private: 1.62 + void SuppressWidgetEvents(bool aFlag) { 1.63 + if (mWindow) { 1.64 + mWindow->SuppressBlurEvents(aFlag); 1.65 + } 1.66 + } 1.67 + nsRefPtr<nsWindow> mWindow; 1.68 +}; 1.69 + 1.70 +// Manages the current working path. 1.71 +class AutoRestoreWorkingPath 1.72 +{ 1.73 +public: 1.74 + AutoRestoreWorkingPath() { 1.75 + DWORD bufferLength = GetCurrentDirectoryW(0, nullptr); 1.76 + mWorkingPath = new wchar_t[bufferLength]; 1.77 + if (GetCurrentDirectoryW(bufferLength, mWorkingPath) == 0) { 1.78 + mWorkingPath = nullptr; 1.79 + } 1.80 + } 1.81 + 1.82 + ~AutoRestoreWorkingPath() { 1.83 + if (HasWorkingPath()) { 1.84 + ::SetCurrentDirectoryW(mWorkingPath); 1.85 + } 1.86 + } 1.87 + 1.88 + inline bool HasWorkingPath() const { 1.89 + return mWorkingPath != nullptr; 1.90 + } 1.91 +private: 1.92 + nsAutoArrayPtr<wchar_t> mWorkingPath; 1.93 +}; 1.94 + 1.95 +// Manages NS_NATIVE_TMP_WINDOW child windows. NS_NATIVE_TMP_WINDOWs are 1.96 +// temporary child windows of mParentWidget created to address RTL issues 1.97 +// in picker dialogs. We are responsible for destroying these. 1.98 +class AutoDestroyTmpWindow 1.99 +{ 1.100 +public: 1.101 + explicit AutoDestroyTmpWindow(HWND aTmpWnd) : 1.102 + mWnd(aTmpWnd) { 1.103 + } 1.104 + 1.105 + ~AutoDestroyTmpWindow() { 1.106 + if (mWnd) 1.107 + DestroyWindow(mWnd); 1.108 + } 1.109 + 1.110 + inline HWND get() const { return mWnd; } 1.111 +private: 1.112 + HWND mWnd; 1.113 +}; 1.114 + 1.115 +// Manages matching PickerOpen/PickerClosed calls on the parent widget. 1.116 +class AutoWidgetPickerState 1.117 +{ 1.118 +public: 1.119 + explicit AutoWidgetPickerState(nsIWidget* aWidget) : 1.120 + mWindow(static_cast<nsWindow *>(aWidget)) { 1.121 + PickerState(true); 1.122 + } 1.123 + 1.124 + ~AutoWidgetPickerState() { 1.125 + PickerState(false); 1.126 + } 1.127 +private: 1.128 + void PickerState(bool aFlag) { 1.129 + if (mWindow) { 1.130 + if (aFlag) 1.131 + mWindow->PickerOpen(); 1.132 + else 1.133 + mWindow->PickerClosed(); 1.134 + } 1.135 + } 1.136 + nsRefPtr<nsWindow> mWindow; 1.137 +}; 1.138 + 1.139 +// Manages a simple callback timer 1.140 +class AutoTimerCallbackCancel 1.141 +{ 1.142 +public: 1.143 + AutoTimerCallbackCancel(nsFilePicker* aTarget, 1.144 + nsTimerCallbackFunc aCallbackFunc) { 1.145 + Init(aTarget, aCallbackFunc); 1.146 + } 1.147 + 1.148 + ~AutoTimerCallbackCancel() { 1.149 + if (mPickerCallbackTimer) { 1.150 + mPickerCallbackTimer->Cancel(); 1.151 + } 1.152 + } 1.153 + 1.154 +private: 1.155 + void Init(nsFilePicker* aTarget, 1.156 + nsTimerCallbackFunc aCallbackFunc) { 1.157 + mPickerCallbackTimer = do_CreateInstance("@mozilla.org/timer;1"); 1.158 + if (!mPickerCallbackTimer) { 1.159 + NS_WARNING("do_CreateInstance for timer failed??"); 1.160 + return; 1.161 + } 1.162 + mPickerCallbackTimer->InitWithFuncCallback(aCallbackFunc, 1.163 + aTarget, 1.164 + kDialogTimerTimeout, 1.165 + nsITimer::TYPE_REPEATING_SLACK); 1.166 + } 1.167 + nsCOMPtr<nsITimer> mPickerCallbackTimer; 1.168 + 1.169 +}; 1.170 + 1.171 +/////////////////////////////////////////////////////////////////////////////// 1.172 +// nsIFilePicker 1.173 + 1.174 +nsFilePicker::nsFilePicker() : 1.175 + mSelectedType(1) 1.176 + , mDlgWnd(nullptr) 1.177 + , mFDECookie(0) 1.178 +{ 1.179 + CoInitialize(nullptr); 1.180 +} 1.181 + 1.182 +nsFilePicker::~nsFilePicker() 1.183 +{ 1.184 + if (mLastUsedUnicodeDirectory) { 1.185 + NS_Free(mLastUsedUnicodeDirectory); 1.186 + mLastUsedUnicodeDirectory = nullptr; 1.187 + } 1.188 + CoUninitialize(); 1.189 +} 1.190 + 1.191 +NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker) 1.192 + 1.193 +NS_IMETHODIMP nsFilePicker::Init(nsIDOMWindow *aParent, const nsAString& aTitle, int16_t aMode) 1.194 +{ 1.195 + nsCOMPtr<nsPIDOMWindow> window = do_QueryInterface(aParent); 1.196 + nsIDocShell* docShell = window ? window->GetDocShell() : nullptr; 1.197 + mLoadContext = do_QueryInterface(docShell); 1.198 + 1.199 + return nsBaseFilePicker::Init(aParent, aTitle, aMode); 1.200 +} 1.201 + 1.202 +STDMETHODIMP nsFilePicker::QueryInterface(REFIID refiid, void** ppvResult) 1.203 +{ 1.204 + *ppvResult = nullptr; 1.205 + if (IID_IUnknown == refiid || 1.206 + refiid == IID_IFileDialogEvents) { 1.207 + *ppvResult = this; 1.208 + } 1.209 + 1.210 + if (nullptr != *ppvResult) { 1.211 + ((LPUNKNOWN)*ppvResult)->AddRef(); 1.212 + return S_OK; 1.213 + } 1.214 + 1.215 + return E_NOINTERFACE; 1.216 +} 1.217 + 1.218 +/* 1.219 + * XP picker callbacks 1.220 + */ 1.221 + 1.222 +// Show - Display the file dialog 1.223 +int CALLBACK 1.224 +BrowseCallbackProc(HWND hwnd, UINT uMsg, LPARAM lParam, LPARAM lpData) 1.225 +{ 1.226 + if (uMsg == BFFM_INITIALIZED) 1.227 + { 1.228 + char16_t * filePath = (char16_t *) lpData; 1.229 + if (filePath) 1.230 + ::SendMessageW(hwnd, BFFM_SETSELECTIONW, 1.231 + TRUE /* true because lpData is a path string */, 1.232 + lpData); 1.233 + } 1.234 + return 0; 1.235 +} 1.236 + 1.237 +static void 1.238 +EnsureWindowVisible(HWND hwnd) 1.239 +{ 1.240 + // Obtain the monitor which has the largest area of intersection 1.241 + // with the window, or nullptr if there is no intersection. 1.242 + HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONULL); 1.243 + if (!monitor) { 1.244 + // The window is not visible, we should reposition it to the same place as its parent 1.245 + HWND parentHwnd = GetParent(hwnd); 1.246 + RECT parentRect; 1.247 + GetWindowRect(parentHwnd, &parentRect); 1.248 + SetWindowPos(hwnd, nullptr, parentRect.left, parentRect.top, 0, 0, 1.249 + SWP_NOACTIVATE | SWP_NOSIZE | SWP_NOZORDER); 1.250 + } 1.251 +} 1.252 + 1.253 +// Callback hook which will ensure that the window is visible. Currently 1.254 +// only in use on os <= XP. 1.255 +UINT_PTR CALLBACK 1.256 +nsFilePicker::FilePickerHook(HWND hwnd, 1.257 + UINT msg, 1.258 + WPARAM wParam, 1.259 + LPARAM lParam) 1.260 +{ 1.261 + switch(msg) { 1.262 + case WM_NOTIFY: 1.263 + { 1.264 + LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; 1.265 + if (!lpofn || !lpofn->lpOFN) { 1.266 + return 0; 1.267 + } 1.268 + 1.269 + if (CDN_INITDONE == lpofn->hdr.code) { 1.270 + // The Window will be automatically moved to the last position after 1.271 + // CDN_INITDONE. We post a message to ensure the window will be visible 1.272 + // so it will be done after the automatic last position window move. 1.273 + PostMessage(hwnd, MOZ_WM_ENSUREVISIBLE, 0, 0); 1.274 + } 1.275 + } 1.276 + break; 1.277 + case MOZ_WM_ENSUREVISIBLE: 1.278 + EnsureWindowVisible(GetParent(hwnd)); 1.279 + break; 1.280 + case WM_INITDIALOG: 1.281 + { 1.282 + OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); 1.283 + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); 1.284 + nsFilePicker* picker = reinterpret_cast<nsFilePicker*>(pofn->lCustData); 1.285 + if (picker) { 1.286 + picker->SetDialogHandle(hwnd); 1.287 + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); 1.288 + } 1.289 + } 1.290 + break; 1.291 + case WM_TIMER: 1.292 + { 1.293 + // Check to see if our parent has been torn down, if so, we close too. 1.294 + if (wParam == kDialogTimerID) { 1.295 + nsFilePicker* picker = 1.296 + reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); 1.297 + if (picker && picker->ClosePickerIfNeeded(true)) { 1.298 + KillTimer(hwnd, kDialogTimerID); 1.299 + } 1.300 + } 1.301 + } 1.302 + break; 1.303 + } 1.304 + return 0; 1.305 +} 1.306 + 1.307 + 1.308 +// Callback hook which will dynamically allocate a buffer large enough 1.309 +// for the file picker dialog. Currently only in use on os <= XP. 1.310 +UINT_PTR CALLBACK 1.311 +nsFilePicker::MultiFilePickerHook(HWND hwnd, 1.312 + UINT msg, 1.313 + WPARAM wParam, 1.314 + LPARAM lParam) 1.315 +{ 1.316 + switch (msg) { 1.317 + case WM_INITDIALOG: 1.318 + { 1.319 + // Finds the child drop down of a File Picker dialog and sets the 1.320 + // maximum amount of text it can hold when typed in manually. 1.321 + // A wParam of 0 mean 0x7FFFFFFE characters. 1.322 + HWND comboBox = FindWindowEx(GetParent(hwnd), nullptr, 1.323 + L"ComboBoxEx32", nullptr ); 1.324 + if(comboBox) 1.325 + SendMessage(comboBox, CB_LIMITTEXT, 0, 0); 1.326 + // Store our nsFilePicker ptr for future use 1.327 + OPENFILENAMEW* pofn = reinterpret_cast<OPENFILENAMEW*>(lParam); 1.328 + SetProp(hwnd, kDialogPtrProp, (HANDLE)pofn->lCustData); 1.329 + nsFilePicker* picker = 1.330 + reinterpret_cast<nsFilePicker*>(pofn->lCustData); 1.331 + if (picker) { 1.332 + picker->SetDialogHandle(hwnd); 1.333 + SetTimer(hwnd, kDialogTimerID, kDialogTimerTimeout, nullptr); 1.334 + } 1.335 + } 1.336 + break; 1.337 + case WM_NOTIFY: 1.338 + { 1.339 + LPOFNOTIFYW lpofn = (LPOFNOTIFYW) lParam; 1.340 + if (!lpofn || !lpofn->lpOFN) { 1.341 + return 0; 1.342 + } 1.343 + // CDN_SELCHANGE is sent when the selection in the list box of the file 1.344 + // selection dialog changes 1.345 + if (lpofn->hdr.code == CDN_SELCHANGE) { 1.346 + HWND parentHWND = GetParent(hwnd); 1.347 + 1.348 + // Get the required size for the selected files buffer 1.349 + UINT newBufLength = 0; 1.350 + int requiredBufLength = CommDlg_OpenSave_GetSpecW(parentHWND, 1.351 + nullptr, 0); 1.352 + if(requiredBufLength >= 0) 1.353 + newBufLength += requiredBufLength; 1.354 + else 1.355 + newBufLength += MAX_PATH; 1.356 + 1.357 + // If the user selects multiple files, the buffer contains the 1.358 + // current directory followed by the file names of the selected 1.359 + // files. So make room for the directory path. If the user 1.360 + // selects a single file, it is no harm to add extra space. 1.361 + requiredBufLength = CommDlg_OpenSave_GetFolderPathW(parentHWND, 1.362 + nullptr, 0); 1.363 + if(requiredBufLength >= 0) 1.364 + newBufLength += requiredBufLength; 1.365 + else 1.366 + newBufLength += MAX_PATH; 1.367 + 1.368 + // Check if lpstrFile and nMaxFile are large enough 1.369 + if (newBufLength > lpofn->lpOFN->nMaxFile) { 1.370 + if (lpofn->lpOFN->lpstrFile) 1.371 + delete[] lpofn->lpOFN->lpstrFile; 1.372 + 1.373 + // We allocate FILE_BUFFER_SIZE more bytes than is needed so that 1.374 + // if the user selects a file and holds down shift and down to 1.375 + // select additional items, we will not continuously reallocate 1.376 + newBufLength += FILE_BUFFER_SIZE; 1.377 + 1.378 + wchar_t* filesBuffer = new wchar_t[newBufLength]; 1.379 + ZeroMemory(filesBuffer, newBufLength * sizeof(wchar_t)); 1.380 + 1.381 + lpofn->lpOFN->lpstrFile = filesBuffer; 1.382 + lpofn->lpOFN->nMaxFile = newBufLength; 1.383 + } 1.384 + } 1.385 + } 1.386 + break; 1.387 + case WM_TIMER: 1.388 + { 1.389 + // Check to see if our parent has been torn down, if so, we close too. 1.390 + if (wParam == kDialogTimerID) { 1.391 + nsFilePicker* picker = 1.392 + reinterpret_cast<nsFilePicker*>(GetProp(hwnd, kDialogPtrProp)); 1.393 + if (picker && picker->ClosePickerIfNeeded(true)) { 1.394 + KillTimer(hwnd, kDialogTimerID); 1.395 + } 1.396 + } 1.397 + } 1.398 + break; 1.399 + } 1.400 + 1.401 + return FilePickerHook(hwnd, msg, wParam, lParam); 1.402 +} 1.403 + 1.404 +/* 1.405 + * Vista+ callbacks 1.406 + */ 1.407 + 1.408 +HRESULT 1.409 +nsFilePicker::OnFileOk(IFileDialog *pfd) 1.410 +{ 1.411 + return S_OK; 1.412 +} 1.413 + 1.414 +HRESULT 1.415 +nsFilePicker::OnFolderChanging(IFileDialog *pfd, 1.416 + IShellItem *psiFolder) 1.417 +{ 1.418 + return S_OK; 1.419 +} 1.420 + 1.421 +HRESULT 1.422 +nsFilePicker::OnFolderChange(IFileDialog *pfd) 1.423 +{ 1.424 + return S_OK; 1.425 +} 1.426 + 1.427 +HRESULT 1.428 +nsFilePicker::OnSelectionChange(IFileDialog *pfd) 1.429 +{ 1.430 + return S_OK; 1.431 +} 1.432 + 1.433 +HRESULT 1.434 +nsFilePicker::OnShareViolation(IFileDialog *pfd, 1.435 + IShellItem *psi, 1.436 + FDE_SHAREVIOLATION_RESPONSE *pResponse) 1.437 +{ 1.438 + return S_OK; 1.439 +} 1.440 + 1.441 +HRESULT 1.442 +nsFilePicker::OnTypeChange(IFileDialog *pfd) 1.443 +{ 1.444 + // Failures here result in errors due to security concerns. 1.445 + nsRefPtr<IOleWindow> win; 1.446 + pfd->QueryInterface(IID_IOleWindow, getter_AddRefs(win)); 1.447 + if (!win) { 1.448 + NS_ERROR("Could not retrieve the IOleWindow interface for IFileDialog."); 1.449 + return S_OK; 1.450 + } 1.451 + HWND hwnd = nullptr; 1.452 + win->GetWindow(&hwnd); 1.453 + if (!hwnd) { 1.454 + NS_ERROR("Could not retrieve the HWND for IFileDialog."); 1.455 + return S_OK; 1.456 + } 1.457 + 1.458 + SetDialogHandle(hwnd); 1.459 + return S_OK; 1.460 +} 1.461 + 1.462 +HRESULT 1.463 +nsFilePicker::OnOverwrite(IFileDialog *pfd, 1.464 + IShellItem *psi, 1.465 + FDE_OVERWRITE_RESPONSE *pResponse) 1.466 +{ 1.467 + return S_OK; 1.468 +} 1.469 + 1.470 +/* 1.471 + * Close on parent close logic 1.472 + */ 1.473 + 1.474 +bool 1.475 +nsFilePicker::ClosePickerIfNeeded(bool aIsXPDialog) 1.476 +{ 1.477 + if (!mParentWidget || !mDlgWnd) 1.478 + return false; 1.479 + 1.480 + nsWindow *win = static_cast<nsWindow *>(mParentWidget.get()); 1.481 + // Note, the xp callbacks hand us an inner window, so we have to step up 1.482 + // one to get the actual dialog. 1.483 + HWND dlgWnd; 1.484 + if (aIsXPDialog) 1.485 + dlgWnd = GetParent(mDlgWnd); 1.486 + else 1.487 + dlgWnd = mDlgWnd; 1.488 + if (IsWindow(dlgWnd) && IsWindowVisible(dlgWnd) && win->DestroyCalled()) { 1.489 + wchar_t className[64]; 1.490 + // Make sure we have the right window 1.491 + if (GetClassNameW(dlgWnd, className, mozilla::ArrayLength(className)) && 1.492 + !wcscmp(className, L"#32770") && 1.493 + DestroyWindow(dlgWnd)) { 1.494 + mDlgWnd = nullptr; 1.495 + return true; 1.496 + } 1.497 + } 1.498 + return false; 1.499 +} 1.500 + 1.501 +void 1.502 +nsFilePicker::PickerCallbackTimerFunc(nsITimer *aTimer, void *aCtx) 1.503 +{ 1.504 + nsFilePicker* picker = (nsFilePicker*)aCtx; 1.505 + if (picker->ClosePickerIfNeeded(false)) { 1.506 + aTimer->Cancel(); 1.507 + } 1.508 +} 1.509 + 1.510 +void 1.511 +nsFilePicker::SetDialogHandle(HWND aWnd) 1.512 +{ 1.513 + if (!aWnd || mDlgWnd) 1.514 + return; 1.515 + mDlgWnd = aWnd; 1.516 +} 1.517 + 1.518 +/* 1.519 + * Folder picker invocation 1.520 + */ 1.521 + 1.522 +// Open the older XP style folder picker dialog. We end up in this call 1.523 +// on XP systems or when platform is built without the longhorn SDK. 1.524 +bool 1.525 +nsFilePicker::ShowXPFolderPicker(const nsString& aInitialDir) 1.526 +{ 1.527 + bool result = false; 1.528 + 1.529 + nsAutoArrayPtr<wchar_t> dirBuffer(new wchar_t[FILE_BUFFER_SIZE]); 1.530 + wcsncpy(dirBuffer, aInitialDir.get(), FILE_BUFFER_SIZE); 1.531 + dirBuffer[FILE_BUFFER_SIZE-1] = '\0'; 1.532 + 1.533 + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? 1.534 + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); 1.535 + 1.536 + BROWSEINFOW browserInfo = {0}; 1.537 + browserInfo.pidlRoot = nullptr; 1.538 + browserInfo.pszDisplayName = dirBuffer; 1.539 + browserInfo.lpszTitle = mTitle.get(); 1.540 + browserInfo.ulFlags = BIF_USENEWUI | BIF_RETURNONLYFSDIRS; 1.541 + browserInfo.hwndOwner = adtw.get(); 1.542 + browserInfo.iImage = 0; 1.543 + browserInfo.lParam = reinterpret_cast<LPARAM>(this); 1.544 + 1.545 + if (!aInitialDir.IsEmpty()) { 1.546 + // the dialog is modal so that |initialDir.get()| will be valid in 1.547 + // BrowserCallbackProc. Thus, we don't need to clone it. 1.548 + browserInfo.lParam = (LPARAM) aInitialDir.get(); 1.549 + browserInfo.lpfn = &BrowseCallbackProc; 1.550 + } else { 1.551 + browserInfo.lParam = 0; 1.552 + browserInfo.lpfn = nullptr; 1.553 + } 1.554 + 1.555 + LPITEMIDLIST list = ::SHBrowseForFolderW(&browserInfo); 1.556 + if (list) { 1.557 + result = ::SHGetPathFromIDListW(list, dirBuffer); 1.558 + if (result) 1.559 + mUnicodeFile.Assign(static_cast<const wchar_t*>(dirBuffer)); 1.560 + // free PIDL 1.561 + CoTaskMemFree(list); 1.562 + } 1.563 + 1.564 + return result; 1.565 +} 1.566 + 1.567 +/* 1.568 + * Show a folder picker post Windows XP 1.569 + * 1.570 + * @param aInitialDir The initial directory, the last used directory will be 1.571 + * used if left blank. 1.572 + * @param aWasInitError Out parameter will hold true if there was an error 1.573 + * before the folder picker is shown. 1.574 + * @return true if a file was selected successfully. 1.575 +*/ 1.576 +bool 1.577 +nsFilePicker::ShowFolderPicker(const nsString& aInitialDir, bool &aWasInitError) 1.578 +{ 1.579 + nsRefPtr<IFileOpenDialog> dialog; 1.580 + if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, 1.581 + IID_IFileOpenDialog, 1.582 + getter_AddRefs(dialog)))) { 1.583 + aWasInitError = true; 1.584 + return false; 1.585 + } 1.586 + aWasInitError = false; 1.587 + 1.588 + // hook up event callbacks 1.589 + dialog->Advise(this, &mFDECookie); 1.590 + 1.591 + // options 1.592 + FILEOPENDIALOGOPTIONS fos = FOS_PICKFOLDERS; 1.593 + dialog->SetOptions(fos); 1.594 + 1.595 + // initial strings 1.596 + dialog->SetTitle(mTitle.get()); 1.597 + if (!aInitialDir.IsEmpty()) { 1.598 + nsRefPtr<IShellItem> folder; 1.599 + if (SUCCEEDED( 1.600 + WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, 1.601 + IID_IShellItem, 1.602 + getter_AddRefs(folder)))) { 1.603 + dialog->SetFolder(folder); 1.604 + } 1.605 + } 1.606 + 1.607 + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? 1.608 + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); 1.609 + 1.610 + // display 1.611 + nsRefPtr<IShellItem> item; 1.612 + if (FAILED(dialog->Show(adtw.get())) || 1.613 + FAILED(dialog->GetResult(getter_AddRefs(item))) || 1.614 + !item) { 1.615 + dialog->Unadvise(mFDECookie); 1.616 + return false; 1.617 + } 1.618 + dialog->Unadvise(mFDECookie); 1.619 + 1.620 + // results 1.621 + 1.622 + // If the user chose a Win7 Library, resolve to the library's 1.623 + // default save folder. 1.624 + nsRefPtr<IShellItem> folderPath; 1.625 + nsRefPtr<IShellLibrary> shellLib; 1.626 + CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC, 1.627 + IID_IShellLibrary, getter_AddRefs(shellLib)); 1.628 + if (shellLib && 1.629 + SUCCEEDED(shellLib->LoadLibraryFromItem(item, STGM_READ)) && 1.630 + SUCCEEDED(shellLib->GetDefaultSaveFolder(DSFT_DETECT, IID_IShellItem, 1.631 + getter_AddRefs(folderPath)))) { 1.632 + item.swap(folderPath); 1.633 + } 1.634 + 1.635 + // get the folder's file system path 1.636 + return WinUtils::GetShellItemPath(item, mUnicodeFile); 1.637 +} 1.638 + 1.639 +/* 1.640 + * File open and save picker invocation 1.641 + */ 1.642 + 1.643 +bool 1.644 +nsFilePicker::FilePickerWrapper(OPENFILENAMEW* ofn, PickerType aType) 1.645 +{ 1.646 + if (!ofn) 1.647 + return false; 1.648 + 1.649 + bool result = false; 1.650 + AutoWidgetPickerState awps(mParentWidget); 1.651 + MOZ_SEH_TRY { 1.652 + if (aType == PICKER_TYPE_OPEN) 1.653 + result = ::GetOpenFileNameW(ofn); 1.654 + else if (aType == PICKER_TYPE_SAVE) 1.655 + result = ::GetSaveFileNameW(ofn); 1.656 + } MOZ_SEH_EXCEPT(true) { 1.657 + NS_ERROR("nsFilePicker GetFileName win32 call generated an exception! This is bad!"); 1.658 + } 1.659 + return result; 1.660 +} 1.661 + 1.662 +bool 1.663 +nsFilePicker::ShowXPFilePicker(const nsString& aInitialDir) 1.664 +{ 1.665 + OPENFILENAMEW ofn = {0}; 1.666 + ofn.lStructSize = sizeof(ofn); 1.667 + nsString filterBuffer = mFilterList; 1.668 + 1.669 + nsAutoArrayPtr<wchar_t> fileBuffer(new wchar_t[FILE_BUFFER_SIZE]); 1.670 + wcsncpy(fileBuffer, mDefaultFilePath.get(), FILE_BUFFER_SIZE); 1.671 + fileBuffer[FILE_BUFFER_SIZE-1] = '\0'; // null terminate in case copy truncated 1.672 + 1.673 + if (!aInitialDir.IsEmpty()) { 1.674 + ofn.lpstrInitialDir = aInitialDir.get(); 1.675 + } 1.676 + 1.677 + AutoDestroyTmpWindow adtw((HWND) (mParentWidget.get() ? 1.678 + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); 1.679 + 1.680 + ofn.lpstrTitle = (LPCWSTR)mTitle.get(); 1.681 + ofn.lpstrFilter = (LPCWSTR)filterBuffer.get(); 1.682 + ofn.nFilterIndex = mSelectedType; 1.683 + ofn.lpstrFile = fileBuffer; 1.684 + ofn.nMaxFile = FILE_BUFFER_SIZE; 1.685 + ofn.hwndOwner = adtw.get(); 1.686 + ofn.lCustData = reinterpret_cast<LPARAM>(this); 1.687 + ofn.Flags = OFN_SHAREAWARE | OFN_LONGNAMES | OFN_OVERWRITEPROMPT | 1.688 + OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_ENABLESIZING | 1.689 + OFN_EXPLORER; 1.690 + 1.691 + // Windows Vista and up won't allow you to use the new looking dialogs with 1.692 + // a hook procedure. The hook procedure fixes a problem on XP dialogs for 1.693 + // file picker visibility. Vista and up automatically ensures the file 1.694 + // picker is always visible. 1.695 + if (!IsVistaOrLater()) { 1.696 + ofn.lpfnHook = FilePickerHook; 1.697 + ofn.Flags |= OFN_ENABLEHOOK; 1.698 + } 1.699 + 1.700 + // Handle add to recent docs settings 1.701 + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { 1.702 + ofn.Flags |= OFN_DONTADDTORECENT; 1.703 + } 1.704 + 1.705 + NS_NAMED_LITERAL_STRING(htmExt, "html"); 1.706 + 1.707 + if (!mDefaultExtension.IsEmpty()) { 1.708 + ofn.lpstrDefExt = mDefaultExtension.get(); 1.709 + } else if (IsDefaultPathHtml()) { 1.710 + // Get file extension from suggested filename to detect if we are 1.711 + // saving an html file. 1.712 + // This is supposed to append ".htm" if user doesn't supply an 1.713 + // extension but the behavior is sort of weird: 1.714 + // - Often appends ".html" even if you have an extension 1.715 + // - It obeys your extension if you put quotes around name 1.716 + ofn.lpstrDefExt = htmExt.get(); 1.717 + } 1.718 + 1.719 + // When possible, instead of using OFN_NOCHANGEDIR to ensure the current 1.720 + // working directory will not change from this call, we will retrieve the 1.721 + // current working directory before the call and restore it after the 1.722 + // call. This flag causes problems on Windows XP for paths that are 1.723 + // selected like C:test.txt where the user is currently at C:\somepath 1.724 + // In which case expected result should be C:\somepath\test.txt 1.725 + AutoRestoreWorkingPath restoreWorkingPath; 1.726 + // If we can't get the current working directory, the best case is to 1.727 + // use the OFN_NOCHANGEDIR flag 1.728 + if (!restoreWorkingPath.HasWorkingPath()) { 1.729 + ofn.Flags |= OFN_NOCHANGEDIR; 1.730 + } 1.731 + 1.732 + bool result = false; 1.733 + 1.734 + switch(mMode) { 1.735 + case modeOpen: 1.736 + // FILE MUST EXIST! 1.737 + ofn.Flags |= OFN_FILEMUSTEXIST; 1.738 + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); 1.739 + break; 1.740 + 1.741 + case modeOpenMultiple: 1.742 + ofn.Flags |= OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT; 1.743 + 1.744 + // The hook set here ensures that the buffer returned will always be 1.745 + // large enough to hold all selected files. The hook may modify the 1.746 + // value of ofn.lpstrFile and deallocate the old buffer that it pointed 1.747 + // to (fileBuffer). The hook assumes that the passed in value is heap 1.748 + // allocated and that the returned value should be freed by the caller. 1.749 + // If the hook changes the buffer, it will deallocate the old buffer. 1.750 + // This fix would be nice to have in Vista and up, but it would force 1.751 + // the file picker to use the old style dialogs because hooks are not 1.752 + // allowed in the new file picker UI. We need to eventually move to 1.753 + // the new Common File Dialogs for Vista and up. 1.754 + if (!IsVistaOrLater()) { 1.755 + ofn.lpfnHook = MultiFilePickerHook; 1.756 + fileBuffer.forget(); 1.757 + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); 1.758 + fileBuffer = ofn.lpstrFile; 1.759 + } else { 1.760 + result = FilePickerWrapper(&ofn, PICKER_TYPE_OPEN); 1.761 + } 1.762 + break; 1.763 + 1.764 + case modeSave: 1.765 + { 1.766 + ofn.Flags |= OFN_NOREADONLYRETURN; 1.767 + 1.768 + // Don't follow shortcuts when saving a shortcut, this can be used 1.769 + // to trick users (bug 271732) 1.770 + if (IsDefaultPathLink()) 1.771 + ofn.Flags |= OFN_NODEREFERENCELINKS; 1.772 + 1.773 + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); 1.774 + if (!result) { 1.775 + // Error, find out what kind. 1.776 + if (GetLastError() == ERROR_INVALID_PARAMETER || 1.777 + CommDlgExtendedError() == FNERR_INVALIDFILENAME) { 1.778 + // Probably the default file name is too long or contains illegal 1.779 + // characters. Try again, without a starting file name. 1.780 + ofn.lpstrFile[0] = L'\0'; 1.781 + result = FilePickerWrapper(&ofn, PICKER_TYPE_SAVE); 1.782 + } 1.783 + } 1.784 + } 1.785 + break; 1.786 + 1.787 + default: 1.788 + NS_NOTREACHED("unsupported file picker mode"); 1.789 + return false; 1.790 + } 1.791 + 1.792 + if (!result) 1.793 + return false; 1.794 + 1.795 + // Remember what filter type the user selected 1.796 + mSelectedType = (int16_t)ofn.nFilterIndex; 1.797 + 1.798 + // Single file selection, we're done 1.799 + if (mMode != modeOpenMultiple) { 1.800 + GetQualifiedPath(fileBuffer, mUnicodeFile); 1.801 + return true; 1.802 + } 1.803 + 1.804 + // Set user-selected location of file or directory. From msdn's "Open and 1.805 + // Save As Dialog Boxes" section: 1.806 + // If you specify OFN_EXPLORER, the directory and file name strings are '\0' 1.807 + // separated, with an extra '\0' character after the last file name. This 1.808 + // format enables the Explorer-style dialog boxes to return long file names 1.809 + // that include spaces. 1.810 + wchar_t *current = fileBuffer; 1.811 + 1.812 + nsAutoString dirName(current); 1.813 + // Sometimes dirName contains a trailing slash and sometimes it doesn't: 1.814 + if (current[dirName.Length() - 1] != '\\') 1.815 + dirName.Append((char16_t)'\\'); 1.816 + 1.817 + while (current && *current && *(current + wcslen(current) + 1)) { 1.818 + current = current + wcslen(current) + 1; 1.819 + 1.820 + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); 1.821 + NS_ENSURE_TRUE(file, false); 1.822 + 1.823 + // Only prepend the directory if the path specified is a relative path 1.824 + nsAutoString path; 1.825 + if (PathIsRelativeW(current)) { 1.826 + path = dirName + nsDependentString(current); 1.827 + } else { 1.828 + path = current; 1.829 + } 1.830 + 1.831 + nsAutoString canonicalizedPath; 1.832 + GetQualifiedPath(path.get(), canonicalizedPath); 1.833 + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || 1.834 + !mFiles.AppendObject(file)) 1.835 + return false; 1.836 + } 1.837 + 1.838 + // Handle the case where the user selected just one file. From msdn: If you 1.839 + // specify OFN_ALLOWMULTISELECT and the user selects only one file the 1.840 + // lpstrFile string does not have a separator between the path and file name. 1.841 + if (current && *current && (current == fileBuffer)) { 1.842 + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); 1.843 + NS_ENSURE_TRUE(file, false); 1.844 + 1.845 + nsAutoString canonicalizedPath; 1.846 + GetQualifiedPath(current, canonicalizedPath); 1.847 + if (NS_FAILED(file->InitWithPath(canonicalizedPath)) || 1.848 + !mFiles.AppendObject(file)) 1.849 + return false; 1.850 + } 1.851 + 1.852 + return true; 1.853 +} 1.854 + 1.855 +/* 1.856 + * Show a file picker post Windows XP 1.857 + * 1.858 + * @param aInitialDir The initial directory, the last used directory will be 1.859 + * used if left blank. 1.860 + * @param aWasInitError Out parameter will hold true if there was an error 1.861 + * before the file picker is shown. 1.862 + * @return true if a file was selected successfully. 1.863 +*/ 1.864 +bool 1.865 +nsFilePicker::ShowFilePicker(const nsString& aInitialDir, bool &aWasInitError) 1.866 +{ 1.867 + nsRefPtr<IFileDialog> dialog; 1.868 + if (mMode != modeSave) { 1.869 + if (FAILED(CoCreateInstance(CLSID_FileOpenDialog, nullptr, CLSCTX_INPROC, 1.870 + IID_IFileOpenDialog, 1.871 + getter_AddRefs(dialog)))) { 1.872 + aWasInitError = true; 1.873 + return false; 1.874 + } 1.875 + } else { 1.876 + if (FAILED(CoCreateInstance(CLSID_FileSaveDialog, nullptr, CLSCTX_INPROC, 1.877 + IID_IFileSaveDialog, 1.878 + getter_AddRefs(dialog)))) { 1.879 + aWasInitError = true; 1.880 + return false; 1.881 + } 1.882 + } 1.883 + aWasInitError = false; 1.884 + 1.885 + // hook up event callbacks 1.886 + dialog->Advise(this, &mFDECookie); 1.887 + 1.888 + // options 1.889 + 1.890 + FILEOPENDIALOGOPTIONS fos = 0; 1.891 + fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | 1.892 + FOS_FORCEFILESYSTEM; 1.893 + 1.894 + // Handle add to recent docs settings 1.895 + if (IsPrivacyModeEnabled() || !mAddToRecentDocs) { 1.896 + fos |= FOS_DONTADDTORECENT; 1.897 + } 1.898 + 1.899 + // Msdn claims FOS_NOCHANGEDIR is not needed. We'll add this 1.900 + // just in case. 1.901 + AutoRestoreWorkingPath arw; 1.902 + 1.903 + // mode specific 1.904 + switch(mMode) { 1.905 + case modeOpen: 1.906 + fos |= FOS_FILEMUSTEXIST; 1.907 + break; 1.908 + 1.909 + case modeOpenMultiple: 1.910 + fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT; 1.911 + break; 1.912 + 1.913 + case modeSave: 1.914 + fos |= FOS_NOREADONLYRETURN; 1.915 + // Don't follow shortcuts when saving a shortcut, this can be used 1.916 + // to trick users (bug 271732) 1.917 + if (IsDefaultPathLink()) 1.918 + fos |= FOS_NODEREFERENCELINKS; 1.919 + break; 1.920 + } 1.921 + 1.922 + dialog->SetOptions(fos); 1.923 + 1.924 + // initial strings 1.925 + 1.926 + // title 1.927 + dialog->SetTitle(mTitle.get()); 1.928 + 1.929 + // default filename 1.930 + if (!mDefaultFilename.IsEmpty()) { 1.931 + dialog->SetFileName(mDefaultFilename.get()); 1.932 + } 1.933 + 1.934 + NS_NAMED_LITERAL_STRING(htmExt, "html"); 1.935 + 1.936 + // default extension to append to new files 1.937 + if (!mDefaultExtension.IsEmpty()) { 1.938 + dialog->SetDefaultExtension(mDefaultExtension.get()); 1.939 + } else if (IsDefaultPathHtml()) { 1.940 + dialog->SetDefaultExtension(htmExt.get()); 1.941 + } 1.942 + 1.943 + // initial location 1.944 + if (!aInitialDir.IsEmpty()) { 1.945 + nsRefPtr<IShellItem> folder; 1.946 + if (SUCCEEDED( 1.947 + WinUtils::SHCreateItemFromParsingName(aInitialDir.get(), nullptr, 1.948 + IID_IShellItem, 1.949 + getter_AddRefs(folder)))) { 1.950 + dialog->SetFolder(folder); 1.951 + } 1.952 + } 1.953 + 1.954 + // filter types and the default index 1.955 + if (!mComFilterList.IsEmpty()) { 1.956 + dialog->SetFileTypes(mComFilterList.Length(), mComFilterList.get()); 1.957 + dialog->SetFileTypeIndex(mSelectedType); 1.958 + } 1.959 + 1.960 + // display 1.961 + 1.962 + { 1.963 + AutoDestroyTmpWindow adtw((HWND)(mParentWidget.get() ? 1.964 + mParentWidget->GetNativeData(NS_NATIVE_TMP_WINDOW) : nullptr)); 1.965 + AutoTimerCallbackCancel atcc(this, PickerCallbackTimerFunc); 1.966 + AutoWidgetPickerState awps(mParentWidget); 1.967 + 1.968 + if (FAILED(dialog->Show(adtw.get()))) { 1.969 + dialog->Unadvise(mFDECookie); 1.970 + return false; 1.971 + } 1.972 + dialog->Unadvise(mFDECookie); 1.973 + } 1.974 + 1.975 + // results 1.976 + 1.977 + // Remember what filter type the user selected 1.978 + UINT filterIdxResult; 1.979 + if (SUCCEEDED(dialog->GetFileTypeIndex(&filterIdxResult))) { 1.980 + mSelectedType = (int16_t)filterIdxResult; 1.981 + } 1.982 + 1.983 + // single selection 1.984 + if (mMode != modeOpenMultiple) { 1.985 + nsRefPtr<IShellItem> item; 1.986 + if (FAILED(dialog->GetResult(getter_AddRefs(item))) || !item) 1.987 + return false; 1.988 + return WinUtils::GetShellItemPath(item, mUnicodeFile); 1.989 + } 1.990 + 1.991 + // multiple selection 1.992 + nsRefPtr<IFileOpenDialog> openDlg; 1.993 + dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg)); 1.994 + if (!openDlg) { 1.995 + // should not happen 1.996 + return false; 1.997 + } 1.998 + 1.999 + nsRefPtr<IShellItemArray> items; 1.1000 + if (FAILED(openDlg->GetResults(getter_AddRefs(items))) || !items) { 1.1001 + return false; 1.1002 + } 1.1003 + 1.1004 + DWORD count = 0; 1.1005 + items->GetCount(&count); 1.1006 + for (unsigned int idx = 0; idx < count; idx++) { 1.1007 + nsRefPtr<IShellItem> item; 1.1008 + nsAutoString str; 1.1009 + if (SUCCEEDED(items->GetItemAt(idx, getter_AddRefs(item)))) { 1.1010 + if (!WinUtils::GetShellItemPath(item, str)) 1.1011 + continue; 1.1012 + nsCOMPtr<nsIFile> file = do_CreateInstance("@mozilla.org/file/local;1"); 1.1013 + if (file && NS_SUCCEEDED(file->InitWithPath(str))) 1.1014 + mFiles.AppendObject(file); 1.1015 + } 1.1016 + } 1.1017 + return true; 1.1018 +} 1.1019 + 1.1020 +/////////////////////////////////////////////////////////////////////////////// 1.1021 +// nsIFilePicker impl. 1.1022 + 1.1023 +NS_IMETHODIMP 1.1024 +nsFilePicker::ShowW(int16_t *aReturnVal) 1.1025 +{ 1.1026 + NS_ENSURE_ARG_POINTER(aReturnVal); 1.1027 + 1.1028 + *aReturnVal = returnCancel; 1.1029 + 1.1030 + AutoSuppressEvents supress(mParentWidget); 1.1031 + 1.1032 + nsAutoString initialDir; 1.1033 + if (mDisplayDirectory) 1.1034 + mDisplayDirectory->GetPath(initialDir); 1.1035 + 1.1036 + // If no display directory, re-use the last one. 1.1037 + if(initialDir.IsEmpty()) { 1.1038 + // Allocate copy of last used dir. 1.1039 + initialDir = mLastUsedUnicodeDirectory; 1.1040 + } 1.1041 + 1.1042 + // Clear previous file selections 1.1043 + mUnicodeFile.Truncate(); 1.1044 + mFiles.Clear(); 1.1045 + 1.1046 + // Launch the XP file/folder picker on XP and as a fallback on Vista+. 1.1047 + // The CoCreateInstance call to CLSID_FileOpenDialog fails with "(0x80040111) 1.1048 + // ClassFactory cannot supply requested class" when the checkbox for 1.1049 + // Disable Visual Themes is on in the compatability tab within the shortcut 1.1050 + // properties. 1.1051 + bool result = false, wasInitError = true; 1.1052 + if (mMode == modeGetFolder) { 1.1053 + if (IsVistaOrLater()) 1.1054 + result = ShowFolderPicker(initialDir, wasInitError); 1.1055 + if (!result && wasInitError) 1.1056 + result = ShowXPFolderPicker(initialDir); 1.1057 + } else { 1.1058 + if (IsVistaOrLater()) 1.1059 + result = ShowFilePicker(initialDir, wasInitError); 1.1060 + if (!result && wasInitError) 1.1061 + result = ShowXPFilePicker(initialDir); 1.1062 + } 1.1063 + 1.1064 + // exit, and return returnCancel in aReturnVal 1.1065 + if (!result) 1.1066 + return NS_OK; 1.1067 + 1.1068 + RememberLastUsedDirectory(); 1.1069 + 1.1070 + int16_t retValue = returnOK; 1.1071 + if (mMode == modeSave) { 1.1072 + // Windows does not return resultReplace, we must check if file 1.1073 + // already exists. 1.1074 + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); 1.1075 + bool flag = false; 1.1076 + if (file && NS_SUCCEEDED(file->InitWithPath(mUnicodeFile)) && 1.1077 + NS_SUCCEEDED(file->Exists(&flag)) && flag) { 1.1078 + retValue = returnReplace; 1.1079 + } 1.1080 + } 1.1081 + 1.1082 + *aReturnVal = retValue; 1.1083 + return NS_OK; 1.1084 +} 1.1085 + 1.1086 +NS_IMETHODIMP 1.1087 +nsFilePicker::Show(int16_t *aReturnVal) 1.1088 +{ 1.1089 + return ShowW(aReturnVal); 1.1090 +} 1.1091 + 1.1092 +NS_IMETHODIMP 1.1093 +nsFilePicker::GetFile(nsIFile **aFile) 1.1094 +{ 1.1095 + NS_ENSURE_ARG_POINTER(aFile); 1.1096 + *aFile = nullptr; 1.1097 + 1.1098 + if (mUnicodeFile.IsEmpty()) 1.1099 + return NS_OK; 1.1100 + 1.1101 + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); 1.1102 + 1.1103 + NS_ENSURE_TRUE(file, NS_ERROR_FAILURE); 1.1104 + 1.1105 + file->InitWithPath(mUnicodeFile); 1.1106 + 1.1107 + NS_ADDREF(*aFile = file); 1.1108 + 1.1109 + return NS_OK; 1.1110 +} 1.1111 + 1.1112 +NS_IMETHODIMP 1.1113 +nsFilePicker::GetFileURL(nsIURI **aFileURL) 1.1114 +{ 1.1115 + *aFileURL = nullptr; 1.1116 + nsCOMPtr<nsIFile> file; 1.1117 + nsresult rv = GetFile(getter_AddRefs(file)); 1.1118 + if (!file) 1.1119 + return rv; 1.1120 + 1.1121 + return NS_NewFileURI(aFileURL, file); 1.1122 +} 1.1123 + 1.1124 +NS_IMETHODIMP 1.1125 +nsFilePicker::GetFiles(nsISimpleEnumerator **aFiles) 1.1126 +{ 1.1127 + NS_ENSURE_ARG_POINTER(aFiles); 1.1128 + return NS_NewArrayEnumerator(aFiles, mFiles); 1.1129 +} 1.1130 + 1.1131 +// Get the file + path 1.1132 +NS_IMETHODIMP 1.1133 +nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) 1.1134 +{ 1.1135 + mDefaultFilePath = aString; 1.1136 + 1.1137 + // First, make sure the file name is not too long. 1.1138 + int32_t nameLength; 1.1139 + int32_t nameIndex = mDefaultFilePath.RFind("\\"); 1.1140 + if (nameIndex == kNotFound) 1.1141 + nameIndex = 0; 1.1142 + else 1.1143 + nameIndex ++; 1.1144 + nameLength = mDefaultFilePath.Length() - nameIndex; 1.1145 + mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex)); 1.1146 + 1.1147 + if (nameLength > MAX_PATH) { 1.1148 + int32_t extIndex = mDefaultFilePath.RFind("."); 1.1149 + if (extIndex == kNotFound) 1.1150 + extIndex = mDefaultFilePath.Length(); 1.1151 + 1.1152 + // Let's try to shave the needed characters from the name part. 1.1153 + int32_t charsToRemove = nameLength - MAX_PATH; 1.1154 + if (extIndex - nameIndex >= charsToRemove) { 1.1155 + mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove); 1.1156 + } 1.1157 + } 1.1158 + 1.1159 + // Then, we need to replace illegal characters. At this stage, we cannot 1.1160 + // replace the backslash as the string might represent a file path. 1.1161 + mDefaultFilePath.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); 1.1162 + mDefaultFilename.ReplaceChar(FILE_ILLEGAL_CHARACTERS, '-'); 1.1163 + 1.1164 + return NS_OK; 1.1165 +} 1.1166 + 1.1167 +NS_IMETHODIMP 1.1168 +nsBaseWinFilePicker::GetDefaultString(nsAString& aString) 1.1169 +{ 1.1170 + return NS_ERROR_FAILURE; 1.1171 +} 1.1172 + 1.1173 +// The default extension to use for files 1.1174 +NS_IMETHODIMP 1.1175 +nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) 1.1176 +{ 1.1177 + aExtension = mDefaultExtension; 1.1178 + return NS_OK; 1.1179 +} 1.1180 + 1.1181 +NS_IMETHODIMP 1.1182 +nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) 1.1183 +{ 1.1184 + mDefaultExtension = aExtension; 1.1185 + return NS_OK; 1.1186 +} 1.1187 + 1.1188 +// Set the filter index 1.1189 +NS_IMETHODIMP 1.1190 +nsFilePicker::GetFilterIndex(int32_t *aFilterIndex) 1.1191 +{ 1.1192 + // Windows' filter index is 1-based, we use a 0-based system. 1.1193 + *aFilterIndex = mSelectedType - 1; 1.1194 + return NS_OK; 1.1195 +} 1.1196 + 1.1197 +NS_IMETHODIMP 1.1198 +nsFilePicker::SetFilterIndex(int32_t aFilterIndex) 1.1199 +{ 1.1200 + // Windows' filter index is 1-based, we use a 0-based system. 1.1201 + mSelectedType = aFilterIndex + 1; 1.1202 + return NS_OK; 1.1203 +} 1.1204 + 1.1205 +void 1.1206 +nsFilePicker::InitNative(nsIWidget *aParent, 1.1207 + const nsAString& aTitle) 1.1208 +{ 1.1209 + mParentWidget = aParent; 1.1210 + mTitle.Assign(aTitle); 1.1211 +} 1.1212 + 1.1213 +void 1.1214 +nsFilePicker::GetQualifiedPath(const wchar_t *aInPath, nsString &aOutPath) 1.1215 +{ 1.1216 + // Prefer a qualified path over a non qualified path. 1.1217 + // Things like c:file.txt would be accepted in Win XP but would later 1.1218 + // fail to open from the download manager. 1.1219 + wchar_t qualifiedFileBuffer[MAX_PATH]; 1.1220 + if (PathSearchAndQualifyW(aInPath, qualifiedFileBuffer, MAX_PATH)) { 1.1221 + aOutPath.Assign(qualifiedFileBuffer); 1.1222 + } else { 1.1223 + aOutPath.Assign(aInPath); 1.1224 + } 1.1225 +} 1.1226 + 1.1227 +void 1.1228 +nsFilePicker::AppendXPFilter(const nsAString& aTitle, const nsAString& aFilter) 1.1229 +{ 1.1230 + mFilterList.Append(aTitle); 1.1231 + mFilterList.Append(char16_t('\0')); 1.1232 + 1.1233 + if (aFilter.EqualsLiteral("..apps")) 1.1234 + mFilterList.AppendLiteral("*.exe;*.com"); 1.1235 + else 1.1236 + { 1.1237 + nsAutoString filter(aFilter); 1.1238 + filter.StripWhitespace(); 1.1239 + if (filter.EqualsLiteral("*")) 1.1240 + filter.AppendLiteral(".*"); 1.1241 + mFilterList.Append(filter); 1.1242 + } 1.1243 + 1.1244 + mFilterList.Append(char16_t('\0')); 1.1245 +} 1.1246 + 1.1247 +NS_IMETHODIMP 1.1248 +nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) 1.1249 +{ 1.1250 + if (IsVistaOrLater()) { 1.1251 + mComFilterList.Append(aTitle, aFilter); 1.1252 + } else { 1.1253 + AppendXPFilter(aTitle, aFilter); 1.1254 + } 1.1255 + return NS_OK; 1.1256 +} 1.1257 + 1.1258 +void 1.1259 +nsFilePicker::RememberLastUsedDirectory() 1.1260 +{ 1.1261 + nsCOMPtr<nsIFile> file(do_CreateInstance("@mozilla.org/file/local;1")); 1.1262 + if (!file || NS_FAILED(file->InitWithPath(mUnicodeFile))) { 1.1263 + NS_WARNING("RememberLastUsedDirectory failed to init file path."); 1.1264 + return; 1.1265 + } 1.1266 + 1.1267 + nsCOMPtr<nsIFile> dir; 1.1268 + nsAutoString newDir; 1.1269 + if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) || 1.1270 + !(mDisplayDirectory = do_QueryInterface(dir)) || 1.1271 + NS_FAILED(mDisplayDirectory->GetPath(newDir)) || 1.1272 + newDir.IsEmpty()) { 1.1273 + NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); 1.1274 + return; 1.1275 + } 1.1276 + 1.1277 + if (mLastUsedUnicodeDirectory) { 1.1278 + NS_Free(mLastUsedUnicodeDirectory); 1.1279 + mLastUsedUnicodeDirectory = nullptr; 1.1280 + } 1.1281 + mLastUsedUnicodeDirectory = ToNewUnicode(newDir); 1.1282 +} 1.1283 + 1.1284 +bool 1.1285 +nsFilePicker::IsPrivacyModeEnabled() 1.1286 +{ 1.1287 + return mLoadContext && mLoadContext->UsePrivateBrowsing(); 1.1288 +} 1.1289 + 1.1290 +bool 1.1291 +nsFilePicker::IsDefaultPathLink() 1.1292 +{ 1.1293 + NS_ConvertUTF16toUTF8 ext(mDefaultFilePath); 1.1294 + ext.Trim(" .", false, true); // watch out for trailing space and dots 1.1295 + ToLowerCase(ext); 1.1296 + if (StringEndsWith(ext, NS_LITERAL_CSTRING(".lnk")) || 1.1297 + StringEndsWith(ext, NS_LITERAL_CSTRING(".pif")) || 1.1298 + StringEndsWith(ext, NS_LITERAL_CSTRING(".url"))) 1.1299 + return true; 1.1300 + return false; 1.1301 +} 1.1302 + 1.1303 +bool 1.1304 +nsFilePicker::IsDefaultPathHtml() 1.1305 +{ 1.1306 + int32_t extIndex = mDefaultFilePath.RFind("."); 1.1307 + if (extIndex >= 0) { 1.1308 + nsAutoString ext; 1.1309 + mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex); 1.1310 + if (ext.LowerCaseEqualsLiteral(".htm") || 1.1311 + ext.LowerCaseEqualsLiteral(".html") || 1.1312 + ext.LowerCaseEqualsLiteral(".shtml")) 1.1313 + return true; 1.1314 + } 1.1315 + return false; 1.1316 +} 1.1317 + 1.1318 +void 1.1319 +nsFilePicker::ComDlgFilterSpec::Append(const nsAString& aTitle, const nsAString& aFilter) 1.1320 +{ 1.1321 + COMDLG_FILTERSPEC* pSpecForward = mSpecList.AppendElement(); 1.1322 + if (!pSpecForward) { 1.1323 + NS_WARNING("mSpecList realloc failed."); 1.1324 + return; 1.1325 + } 1.1326 + memset(pSpecForward, 0, sizeof(*pSpecForward)); 1.1327 + nsString* pStr = mStrings.AppendElement(aTitle); 1.1328 + if (!pStr) { 1.1329 + NS_WARNING("mStrings.AppendElement failed."); 1.1330 + return; 1.1331 + } 1.1332 + pSpecForward->pszName = pStr->get(); 1.1333 + pStr = mStrings.AppendElement(aFilter); 1.1334 + if (!pStr) { 1.1335 + NS_WARNING("mStrings.AppendElement failed."); 1.1336 + return; 1.1337 + } 1.1338 + if (aFilter.EqualsLiteral("..apps")) 1.1339 + pStr->AssignLiteral("*.exe;*.com"); 1.1340 + else { 1.1341 + pStr->StripWhitespace(); 1.1342 + if (pStr->EqualsLiteral("*")) 1.1343 + pStr->AppendLiteral(".*"); 1.1344 + } 1.1345 + pSpecForward->pszSpec = pStr->get(); 1.1346 +}