Bug 1855360 - Fix the skip-if syntax. a=bustage-fix
[gecko.git] / widget / windows / nsFilePicker.cpp
blob9b12b4c4e244a1e6a63f8368e8a8e3a6cd30b1f8
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"
9 #include <cderr.h>
10 #include <shlobj.h>
11 #include <shlwapi.h>
12 #include <sysinfoapi.h>
13 #include <winerror.h>
14 #include <winuser.h>
15 #include <utility>
17 #include "mozilla/Assertions.h"
18 #include "mozilla/BackgroundHangMonitor.h"
19 #include "mozilla/Logging.h"
20 #include "mozilla/ipc/UtilityProcessManager.h"
21 #include "mozilla/ProfilerLabels.h"
22 #include "mozilla/StaticPrefs_widget.h"
23 #include "mozilla/UniquePtr.h"
24 #include "mozilla/WindowsVersion.h"
25 #include "nsCRT.h"
26 #include "nsEnumeratorUtils.h"
27 #include "nsNetUtil.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsPrintfCString.h"
30 #include "nsReadableUtils.h"
31 #include "nsString.h"
32 #include "nsToolkit.h"
33 #include "nsWindow.h"
34 #include "WinUtils.h"
36 #include "mozilla/glean/GleanMetrics.h"
38 #include "mozilla/widget/filedialog/WinFileDialogCommands.h"
39 #include "mozilla/widget/filedialog/WinFileDialogParent.h"
41 using mozilla::UniquePtr;
43 using namespace mozilla::widget;
45 UniquePtr<char16_t[], nsFilePicker::FreeDeleter>
46 nsFilePicker::sLastUsedUnicodeDirectory;
48 using mozilla::LogLevel;
50 #define MAX_EXTENSION_LENGTH 10
52 ///////////////////////////////////////////////////////////////////////////////
53 // Helper classes
55 // Manages matching PickerOpen/PickerClosed calls on the parent widget.
56 class AutoWidgetPickerState {
57 public:
58 explicit AutoWidgetPickerState(nsIWidget* aWidget)
59 : mWindow(static_cast<nsWindow*>(aWidget)) {
60 PickerState(true);
63 AutoWidgetPickerState(AutoWidgetPickerState const&) = delete;
64 AutoWidgetPickerState(AutoWidgetPickerState&& that) noexcept = default;
66 ~AutoWidgetPickerState() { PickerState(false); }
68 private:
69 void PickerState(bool aFlag) {
70 if (mWindow) {
71 if (aFlag)
72 mWindow->PickerOpen();
73 else
74 mWindow->PickerClosed();
77 RefPtr<nsWindow> mWindow;
80 ///////////////////////////////////////////////////////////////////////////////
81 // nsIFilePicker
83 nsFilePicker::nsFilePicker() : mSelectedType(1) {}
85 NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
87 NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy* aParent,
88 const nsAString& aTitle,
89 nsIFilePicker::Mode aMode) {
90 // Don't attempt to open a real file-picker in headless mode.
91 if (gfxPlatform::IsHeadless()) {
92 return nsresult::NS_ERROR_NOT_AVAILABLE;
95 nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent);
96 nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
97 mLoadContext = do_QueryInterface(docShell);
99 return nsBaseFilePicker::Init(aParent, aTitle, aMode);
102 namespace mozilla::detail {
103 // Boilerplate for remotely showing a file dialog.
104 template <typename ActionType,
105 typename ReturnType = typename decltype(std::declval<ActionType>()(
106 nullptr))::element_type::ResolveValueType>
107 static auto ShowRemote(HWND parent, ActionType&& action)
108 -> RefPtr<MozPromise<ReturnType, HRESULT, true>> {
109 using RetPromise = MozPromise<ReturnType, HRESULT, true>;
111 constexpr static const auto fail = []() {
112 return RetPromise::CreateAndReject(E_FAIL, __PRETTY_FUNCTION__);
115 auto mgr = mozilla::ipc::UtilityProcessManager::GetSingleton();
116 if (!mgr) {
117 MOZ_ASSERT(false);
118 return fail();
121 auto wfda = mgr->CreateWinFileDialogActor();
122 if (!wfda) {
123 return fail();
126 using mozilla::widget::filedialog::sLogFileDialog;
128 // WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
129 // top-level ancestor of its provided owner-window. If the modal window's
130 // container process crashes, it will never get a chance to undo that, so
131 // we have to do it manually.
132 struct WindowEnabler {
133 HWND hwnd;
134 BOOL enabled;
135 explicit WindowEnabler(HWND hwnd)
136 : hwnd(hwnd), enabled(::IsWindowEnabled(hwnd)) {
137 MOZ_LOG(sLogFileDialog, LogLevel::Info,
138 ("ShowRemote: HWND %08zX enabled=%u", uintptr_t(hwnd), enabled));
140 WindowEnabler(WindowEnabler const&) = default;
142 void restore() const {
143 MOZ_LOG(sLogFileDialog, LogLevel::Info,
144 ("ShowRemote: HWND %08zX enabled=%u (restoring to %u)",
145 uintptr_t(hwnd), ::IsWindowEnabled(hwnd), enabled));
146 ::EnableWindow(hwnd, enabled);
149 HWND const rootWindow = ::GetAncestor(parent, GA_ROOT);
151 return wfda->Then(
152 mozilla::GetMainThreadSerialEventTarget(),
153 "nsFilePicker ShowRemote acquire",
154 [action = std::forward<ActionType>(action),
155 rootWindow](filedialog::ProcessProxy const& p) -> RefPtr<RetPromise> {
156 MOZ_LOG(sLogFileDialog, LogLevel::Info,
157 ("nsFilePicker ShowRemote first callback: p = [%p]", p.get()));
158 WindowEnabler enabledState(rootWindow);
160 // false positive: not actually redundant
161 // NOLINTNEXTLINE(readability-redundant-smartptr-get)
162 return action(p.get())
163 ->Then(
164 mozilla::GetMainThreadSerialEventTarget(),
165 "nsFilePicker ShowRemote call",
166 [p](ReturnType ret) {
167 return RetPromise::CreateAndResolve(std::move(ret),
168 __PRETTY_FUNCTION__);
170 [](mozilla::ipc::ResponseRejectReason error) {
171 MOZ_LOG(sLogFileDialog, LogLevel::Error,
172 ("IPC call rejected: %zu", size_t(error)));
173 return fail();
175 // Unconditionally restore the disabled state.
176 ->Then(mozilla::GetMainThreadSerialEventTarget(),
177 "nsFilePicker ShowRemote cleanup",
178 [enabledState](
179 typename RetPromise::ResolveOrRejectValue const& val) {
180 enabledState.restore();
181 return RetPromise::CreateAndResolveOrReject(
182 val, "nsFilePicker ShowRemote cleanup fmap");
185 [](nsresult error) -> RefPtr<RetPromise> {
186 MOZ_LOG(sLogFileDialog, LogLevel::Error,
187 ("could not acquire WinFileDialog: %zu", size_t(error)));
188 return fail();
192 // fd_async
194 // Wrapper-namespace for the AsyncExecute() function.
195 namespace fd_async {
197 // Implementation details of, specifically, the AsyncExecute() function.
198 namespace details {
199 // Helper for generically copying ordinary types and nsTArray (which lacks a
200 // copy constructor) in the same breath.
201 template <typename T>
202 static T Copy(T const& val) {
203 return val;
205 template <typename T>
206 static nsTArray<T> Copy(nsTArray<T> const& arr) {
207 return arr.Clone();
210 // The possible execution strategies of AsyncExecute.
211 enum Strategy { Local, Remote, RemoteWithFallback };
213 // Decode the relevant preference to determine the desired execution-
214 // strategy.
215 static Strategy GetStrategy() {
216 int32_t const pref =
217 mozilla::StaticPrefs::widget_windows_utility_process_file_picker();
218 switch (pref) {
219 case -1:
220 return Local;
221 case 2:
222 return Remote;
223 case 1:
224 return RemoteWithFallback;
226 default:
227 #ifdef NIGHTLY_BUILD
228 // on Nightly builds, fall back to local on failure
229 return RemoteWithFallback;
230 #else
231 // on release and beta, remain local-only for now
232 return Local;
233 #endif
237 namespace telemetry {
238 static uint32_t Delta(uint64_t tb, uint64_t ta) {
239 // FILETIMEs are 100ns intervals; we reduce that to 1ms.
240 // (`u32::max()` milliseconds is roughly 47.91 days.)
241 return uint32_t((tb - ta) / 10'000);
243 static nsCString HexString(HRESULT val) {
244 return nsPrintfCString("%08lX", val);
247 static void RecordSuccess(uint64_t (&&time)[2]) {
248 auto [t0, t1] = time;
250 namespace glean_fd = mozilla::glean::file_dialog;
251 glean_fd::FallbackExtra extra{
252 .hresultLocal = Nothing(),
253 .hresultRemote = Nothing(),
254 .succeeded = Some(true),
255 .timeLocal = Nothing(),
256 .timeRemote = Some(Delta(t1, t0)),
258 glean_fd::fallback.Record(Some(extra));
261 static void RecordFailure(uint64_t (&&time)[3], HRESULT hrRemote,
262 HRESULT hrLocal) {
263 auto [t0, t1, t2] = time;
266 namespace glean_fd = mozilla::glean::file_dialog;
267 glean_fd::FallbackExtra extra{
268 .hresultLocal = Some(HexString(hrLocal)),
269 .hresultRemote = Some(HexString(hrRemote)),
270 .succeeded = Some(false),
271 .timeLocal = Some(Delta(t2, t1)),
272 .timeRemote = Some(Delta(t1, t0)),
274 glean_fd::fallback.Record(Some(extra));
278 } // namespace telemetry
279 } // namespace details
281 // Invoke either or both of a "do locally" and "do remotely" function with the
282 // provided arguments, depending on the relevant preference-value and whether
283 // or not the remote version fails.
285 // Both functions must be asynchronous, returning a `RefPtr<MozPromise<...>>`.
286 // "Failure" is defined as the promise being rejected.
287 template <typename Fn1, typename Fn2, typename... Args>
288 static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args)
289 -> std::invoke_result_t<Fn1, Args...> {
290 using namespace details;
292 static_assert(std::is_same_v<std::invoke_result_t<Fn1, Args...>,
293 std::invoke_result_t<Fn2, Args...>>);
294 using PromiseT = typename std::invoke_result_t<Fn1, Args...>::element_type;
296 constexpr static char kFunctionName[] = "LocalAndOrRemote::AsyncExecute";
298 switch (GetStrategy()) {
299 case Local:
300 return local(args...);
302 case Remote:
303 return remote(args...);
305 case RemoteWithFallback:
306 // more complicated; continue below
307 break;
310 // capture time for telemetry
311 constexpr static const auto GetTime = []() -> uint64_t {
312 FILETIME t;
313 ::GetSystemTimeAsFileTime(&t);
314 return (uint64_t(t.dwHighDateTime) << 32) | t.dwLowDateTime;
316 uint64_t const t0 = GetTime();
318 return remote(args...)->Then(
319 NS_GetCurrentThread(), kFunctionName,
320 [t0](typename PromiseT::ResolveValueType result) -> RefPtr<PromiseT> {
321 // success; stop here
322 auto const t1 = GetTime();
323 // record success
324 telemetry::RecordSuccess({t0, t1});
325 return PromiseT::CreateAndResolve(result, kFunctionName);
327 // initialized lambda pack captures are C++20 (clang 9, gcc 9);
328 // `make_tuple` is just a C++17 workaround
329 [=, tuple = std::make_tuple(Copy(args)...)](
330 typename PromiseT::RejectValueType err) mutable -> RefPtr<PromiseT> {
331 // failure; record time
332 auto const t1 = GetTime();
333 HRESULT const hrRemote = err;
335 // retry locally...
336 auto p0 = std::apply(local, std::move(tuple));
337 // ...then record the telemetry event
338 return p0->Then(
339 NS_GetCurrentThread(), kFunctionName,
340 [t0, t1,
341 hrRemote](typename PromiseT::ResolveOrRejectValue const& val)
342 -> RefPtr<PromiseT> {
343 auto const t2 = GetTime();
344 HRESULT const hrLocal = val.IsReject() ? val.RejectValue() : S_OK;
345 telemetry::RecordFailure({t0, t1, t2}, hrRemote, hrLocal);
347 return PromiseT::CreateAndResolveOrReject(val, kFunctionName);
351 } // namespace fd_async
353 using fd_async::AsyncExecute;
355 } // namespace mozilla::detail
357 /* static */
358 nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerRemote(
359 HWND parent, filedialog::FileDialogType type,
360 nsTArray<filedialog::Command> const& commands) {
361 using mozilla::widget::filedialog::sLogFileDialog;
362 return mozilla::detail::ShowRemote(
363 parent, [parent, type, commands = commands.Clone()](
364 filedialog::WinFileDialogParent* p) {
365 MOZ_LOG(sLogFileDialog, LogLevel::Info,
366 ("%s: p = [%p]", __PRETTY_FUNCTION__, p));
367 return p->SendShowFileDialog((uintptr_t)parent, type, commands);
371 /* static */
372 nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerRemote(
373 HWND parent, nsTArray<filedialog::Command> const& commands) {
374 using mozilla::widget::filedialog::sLogFileDialog;
375 return mozilla::detail::ShowRemote(
376 parent, [parent, commands = commands.Clone()](
377 filedialog::WinFileDialogParent* p) {
378 MOZ_LOG(sLogFileDialog, LogLevel::Info,
379 ("%s: p = [%p]", __PRETTY_FUNCTION__, p));
380 return p->SendShowFolderDialog((uintptr_t)parent, commands);
384 /* static */
385 nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerLocal(
386 HWND parent, filedialog::FileDialogType type,
387 nsTArray<filedialog::Command> const& commands) {
388 return filedialog::SpawnFilePicker(parent, type, commands.Clone());
391 /* static */
392 nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerLocal(
393 HWND parent, nsTArray<filedialog::Command> const& commands) {
394 return filedialog::SpawnFolderPicker(parent, commands.Clone());
398 * Folder picker invocation
402 * Show a folder picker.
404 * @param aInitialDir The initial directory. The last-used directory will be
405 * used if left blank.
406 * @return A promise which:
407 * - resolves to true if a file was selected successfully (in which
408 * case mUnicodeFile will be updated);
409 * - resolves to false if the dialog was cancelled by the user;
410 * - is rejected with the associated HRESULT if some error occurred.
412 RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFolderPicker(
413 const nsString& aInitialDir) {
414 using Promise = mozilla::MozPromise<bool, HRESULT, true>;
415 constexpr static auto Ok = [](bool val) {
416 return Promise::CreateAndResolve(val, "nsFilePicker::ShowFolderPicker");
418 constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
419 return Promise::CreateAndReject(val, "nsFilePicker::ShowFolderPicker");
422 namespace fd = ::mozilla::widget::filedialog;
423 nsTArray<fd::Command> commands = {
424 fd::SetOptions(FOS_PICKFOLDERS),
425 fd::SetTitle(mTitle),
428 if (!mOkButtonLabel.IsEmpty()) {
429 commands.AppendElement(fd::SetOkButtonLabel(mOkButtonLabel));
432 if (!aInitialDir.IsEmpty()) {
433 commands.AppendElement(fd::SetFolder(aInitialDir));
436 ScopedRtlShimWindow shim(mParentWidget.get());
437 AutoWidgetPickerState awps(mParentWidget);
439 return mozilla::detail::AsyncExecute(&ShowFolderPickerLocal,
440 &ShowFolderPickerRemote, shim.get(),
441 commands)
442 ->Then(
443 NS_GetCurrentThread(), __PRETTY_FUNCTION__,
444 [self = RefPtr(this), shim = std::move(shim),
445 awps = std::move(awps)](Maybe<nsString> val) {
446 if (val) {
447 self->mUnicodeFile = val.extract();
448 return Ok(true);
450 return Ok(false);
452 [](HRESULT err) {
453 NS_WARNING("ShowFolderPicker failed");
454 return NotOk(err);
459 * File open and save picker invocation
463 * Show a file picker.
465 * @param aInitialDir The initial directory. The last-used directory will be
466 * used if left blank.
467 * @return A promise which:
468 * - resolves to true if one or more files were selected successfully
469 * (in which case mUnicodeFile and/or mFiles will be updated);
470 * - resolves to false if the dialog was cancelled by the user;
471 * - is rejected with the associated HRESULT if some error occurred.
473 RefPtr<mozilla::MozPromise<bool, HRESULT, true>> nsFilePicker::ShowFilePicker(
474 const nsString& aInitialDir) {
475 AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER);
477 using Promise = mozilla::MozPromise<bool, HRESULT, true>;
478 constexpr static auto Ok = [](bool val) {
479 return Promise::CreateAndResolve(val, "nsFilePicker::ShowFilePicker");
481 constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
482 return Promise::CreateAndReject(val, "nsFilePicker::ShowFilePicker");
485 namespace fd = ::mozilla::widget::filedialog;
486 nsTArray<fd::Command> commands;
487 // options
489 FILEOPENDIALOGOPTIONS fos = 0;
490 fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM;
492 // Handle add to recent docs settings
493 if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
494 fos |= FOS_DONTADDTORECENT;
497 // mode specific
498 switch (mMode) {
499 case modeOpen:
500 fos |= FOS_FILEMUSTEXIST;
501 break;
503 case modeOpenMultiple:
504 fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT;
505 break;
507 case modeSave:
508 fos |= FOS_NOREADONLYRETURN;
509 // Don't follow shortcuts when saving a shortcut, this can be used
510 // to trick users (bug 271732)
511 if (IsDefaultPathLink()) fos |= FOS_NODEREFERENCELINKS;
512 break;
514 case modeGetFolder:
515 MOZ_ASSERT(false, "file-picker opened in directory-picker mode");
516 return NotOk(E_FAIL);
519 commands.AppendElement(fd::SetOptions(fos));
522 // initial strings
524 // title
525 commands.AppendElement(fd::SetTitle(mTitle));
527 // default filename
528 if (!mDefaultFilename.IsEmpty()) {
529 // Prevent the shell from expanding environment variables by removing
530 // the % characters that are used to delimit them.
531 nsAutoString sanitizedFilename(mDefaultFilename);
532 sanitizedFilename.ReplaceChar('%', '_');
534 commands.AppendElement(fd::SetFileName(sanitizedFilename));
537 // default extension to append to new files
538 if (!mDefaultExtension.IsEmpty()) {
539 // We don't want environment variables expanded in the extension either.
540 nsAutoString sanitizedExtension(mDefaultExtension);
541 sanitizedExtension.ReplaceChar('%', '_');
543 commands.AppendElement(fd::SetDefaultExtension(sanitizedExtension));
544 } else if (IsDefaultPathHtml()) {
545 commands.AppendElement(fd::SetDefaultExtension(u"html"_ns));
548 // initial location
549 if (!aInitialDir.IsEmpty()) {
550 commands.AppendElement(fd::SetFolder(aInitialDir));
553 // filter types and the default index
554 if (!mFilterList.IsEmpty()) {
555 nsTArray<fd::ComDlgFilterSpec> fileTypes;
556 for (auto const& filter : mFilterList) {
557 fileTypes.EmplaceBack(filter.title, filter.filter);
559 commands.AppendElement(fd::SetFileTypes(std::move(fileTypes)));
560 commands.AppendElement(fd::SetFileTypeIndex(mSelectedType));
563 ScopedRtlShimWindow shim(mParentWidget.get());
564 AutoWidgetPickerState awps(mParentWidget);
566 mozilla::BackgroundHangMonitor().NotifyWait();
567 auto type = mMode == modeSave ? FileDialogType::Save : FileDialogType::Open;
569 auto promise = mozilla::detail::AsyncExecute(
570 &ShowFilePickerLocal, &ShowFilePickerRemote, shim.get(), type, commands);
572 return promise->Then(
573 mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
574 [self = RefPtr(this), mode = mMode, shim = std::move(shim),
575 awps = std::move(awps)](Maybe<Results> res_opt) {
576 if (!res_opt) {
577 return Ok(false);
579 auto result = res_opt.extract();
581 // Remember what filter type the user selected
582 self->mSelectedType = int32_t(result.selectedFileTypeIndex());
584 auto const& paths = result.paths();
586 // single selection
587 if (mode != modeOpenMultiple) {
588 if (!paths.IsEmpty()) {
589 MOZ_ASSERT(paths.Length() == 1);
590 self->mUnicodeFile = paths[0];
591 return Ok(true);
593 return Ok(false);
596 // multiple selection
597 for (auto const& str : paths) {
598 nsCOMPtr<nsIFile> file;
599 if (NS_SUCCEEDED(NS_NewLocalFile(str, false, getter_AddRefs(file)))) {
600 self->mFiles.AppendObject(file);
604 return Ok(true);
606 [](HRESULT err) {
607 NS_WARNING("ShowFilePicker failed");
608 return NotOk(err);
612 ///////////////////////////////////////////////////////////////////////////////
613 // nsIFilePicker impl.
615 nsresult nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
616 NS_ENSURE_ARG_POINTER(aCallback);
618 // Don't attempt to open a real file-picker in headless mode.
619 if (gfxPlatform::IsHeadless()) {
620 return nsresult::NS_ERROR_NOT_AVAILABLE;
623 nsAutoString initialDir;
624 if (mDisplayDirectory) mDisplayDirectory->GetPath(initialDir);
626 // If no display directory, re-use the last one.
627 if (initialDir.IsEmpty()) {
628 // Allocate copy of last used dir.
629 initialDir = sLastUsedUnicodeDirectory.get();
632 // Clear previous file selections
633 mUnicodeFile.Truncate();
634 mFiles.Clear();
636 auto promise = mMode == modeGetFolder ? ShowFolderPicker(initialDir)
637 : ShowFilePicker(initialDir);
639 auto p2 = promise->Then(
640 mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
641 [self = RefPtr(this),
642 callback = RefPtr(aCallback)](bool selectionMade) -> void {
643 if (!selectionMade) {
644 callback->Done(ResultCode::returnCancel);
645 return;
648 self->RememberLastUsedDirectory();
650 nsIFilePicker::ResultCode retValue = ResultCode::returnOK;
652 if (self->mMode == modeSave) {
653 // Windows does not return resultReplace; we must check whether the
654 // file already exists.
655 nsCOMPtr<nsIFile> file;
656 nsresult rv =
657 NS_NewLocalFile(self->mUnicodeFile, false, getter_AddRefs(file));
659 bool flag = false;
660 if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(file->Exists(&flag)) && flag) {
661 retValue = ResultCode::returnReplace;
665 callback->Done(retValue);
667 [callback = RefPtr(aCallback)](HRESULT err) {
668 using mozilla::widget::filedialog::sLogFileDialog;
669 MOZ_LOG(sLogFileDialog, LogLevel::Error,
670 ("nsFilePicker: Show failed with hr=0x%08lX", err));
671 callback->Done(ResultCode::returnCancel);
674 return NS_OK;
677 nsresult nsFilePicker::Show(nsIFilePicker::ResultCode* aReturnVal) {
678 return NS_ERROR_NOT_IMPLEMENTED;
681 NS_IMETHODIMP
682 nsFilePicker::GetFile(nsIFile** aFile) {
683 NS_ENSURE_ARG_POINTER(aFile);
684 *aFile = nullptr;
686 if (mUnicodeFile.IsEmpty()) return NS_OK;
688 nsCOMPtr<nsIFile> file;
689 nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file));
690 if (NS_FAILED(rv)) {
691 return rv;
694 file.forget(aFile);
695 return NS_OK;
698 NS_IMETHODIMP
699 nsFilePicker::GetFileURL(nsIURI** aFileURL) {
700 *aFileURL = nullptr;
701 nsCOMPtr<nsIFile> file;
702 nsresult rv = GetFile(getter_AddRefs(file));
703 if (!file) return rv;
705 return NS_NewFileURI(aFileURL, file);
708 NS_IMETHODIMP
709 nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
710 NS_ENSURE_ARG_POINTER(aFiles);
711 return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
714 // Get the file + path
715 NS_IMETHODIMP
716 nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) {
717 mDefaultFilePath = aString;
719 // First, make sure the file name is not too long.
720 int32_t nameLength;
721 int32_t nameIndex = mDefaultFilePath.RFind(u"\\");
722 if (nameIndex == kNotFound)
723 nameIndex = 0;
724 else
725 nameIndex++;
726 nameLength = mDefaultFilePath.Length() - nameIndex;
727 mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex));
729 if (nameLength > MAX_PATH) {
730 int32_t extIndex = mDefaultFilePath.RFind(u".");
731 if (extIndex == kNotFound) extIndex = mDefaultFilePath.Length();
733 // Let's try to shave the needed characters from the name part.
734 int32_t charsToRemove = nameLength - MAX_PATH;
735 if (extIndex - nameIndex >= charsToRemove) {
736 mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove);
740 // Then, we need to replace illegal characters. At this stage, we cannot
741 // replace the backslash as the string might represent a file path.
742 mDefaultFilePath.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
743 mDefaultFilename.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
745 return NS_OK;
748 NS_IMETHODIMP
749 nsBaseWinFilePicker::GetDefaultString(nsAString& aString) {
750 return NS_ERROR_FAILURE;
753 // The default extension to use for files
754 NS_IMETHODIMP
755 nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) {
756 aExtension = mDefaultExtension;
757 return NS_OK;
760 NS_IMETHODIMP
761 nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) {
762 mDefaultExtension = aExtension;
763 return NS_OK;
766 // Set the filter index
767 NS_IMETHODIMP
768 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) {
769 // Windows' filter index is 1-based, we use a 0-based system.
770 *aFilterIndex = mSelectedType - 1;
771 return NS_OK;
774 NS_IMETHODIMP
775 nsFilePicker::SetFilterIndex(int32_t aFilterIndex) {
776 // Windows' filter index is 1-based, we use a 0-based system.
777 mSelectedType = aFilterIndex + 1;
778 return NS_OK;
781 void nsFilePicker::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
782 mParentWidget = aParent;
783 mTitle.Assign(aTitle);
786 NS_IMETHODIMP
787 nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
788 nsString sanitizedFilter(aFilter);
789 sanitizedFilter.ReplaceChar('%', '_');
791 if (sanitizedFilter == u"..apps"_ns) {
792 sanitizedFilter = u"*.exe;*.com"_ns;
793 } else {
794 sanitizedFilter.StripWhitespace();
795 if (sanitizedFilter == u"*"_ns) {
796 sanitizedFilter = u"*.*"_ns;
799 mFilterList.AppendElement(
800 Filter{.title = nsString(aTitle), .filter = std::move(sanitizedFilter)});
801 return NS_OK;
804 void nsFilePicker::RememberLastUsedDirectory() {
805 if (IsPrivacyModeEnabled()) {
806 // Don't remember the directory if private browsing was in effect
807 return;
810 nsCOMPtr<nsIFile> file;
811 if (NS_FAILED(NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file)))) {
812 NS_WARNING("RememberLastUsedDirectory failed to init file path.");
813 return;
816 nsCOMPtr<nsIFile> dir;
817 nsAutoString newDir;
818 if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
819 !(mDisplayDirectory = dir) ||
820 NS_FAILED(mDisplayDirectory->GetPath(newDir)) || newDir.IsEmpty()) {
821 NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
822 return;
825 sLastUsedUnicodeDirectory.reset(ToNewUnicode(newDir));
828 bool nsFilePicker::IsPrivacyModeEnabled() {
829 return mLoadContext && mLoadContext->UsePrivateBrowsing();
832 bool nsFilePicker::IsDefaultPathLink() {
833 NS_ConvertUTF16toUTF8 ext(mDefaultFilePath);
834 ext.Trim(" .", false, true); // watch out for trailing space and dots
835 ToLowerCase(ext);
836 return StringEndsWith(ext, ".lnk"_ns) || StringEndsWith(ext, ".pif"_ns) ||
837 StringEndsWith(ext, ".url"_ns);
840 bool nsFilePicker::IsDefaultPathHtml() {
841 int32_t extIndex = mDefaultFilePath.RFind(u".");
842 if (extIndex >= 0) {
843 nsAutoString ext;
844 mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex);
845 if (ext.LowerCaseEqualsLiteral(".htm") ||
846 ext.LowerCaseEqualsLiteral(".html") ||
847 ext.LowerCaseEqualsLiteral(".shtml"))
848 return true;
850 return false;