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"
12 #include <sysinfoapi.h>
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"
26 #include "nsEnumeratorUtils.h"
27 #include "nsNetUtil.h"
28 #include "nsPIDOMWindow.h"
29 #include "nsPrintfCString.h"
30 #include "nsReadableUtils.h"
32 #include "nsToolkit.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 ///////////////////////////////////////////////////////////////////////////////
55 // Manages matching PickerOpen/PickerClosed calls on the parent widget.
56 class AutoWidgetPickerState
{
58 explicit AutoWidgetPickerState(nsIWidget
* aWidget
)
59 : mWindow(static_cast<nsWindow
*>(aWidget
)) {
63 AutoWidgetPickerState(AutoWidgetPickerState
const&) = delete;
64 AutoWidgetPickerState(AutoWidgetPickerState
&& that
) noexcept
= default;
66 ~AutoWidgetPickerState() { PickerState(false); }
69 void PickerState(bool aFlag
) {
72 mWindow
->PickerOpen();
74 mWindow
->PickerClosed();
77 RefPtr
<nsWindow
> mWindow
;
80 ///////////////////////////////////////////////////////////////////////////////
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();
121 auto wfda
= mgr
->CreateWinFileDialogActor();
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
{
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
);
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())
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
)));
175 // Unconditionally restore the disabled state.
176 ->Then(mozilla::GetMainThreadSerialEventTarget(),
177 "nsFilePicker ShowRemote cleanup",
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
)));
194 // Wrapper-namespace for the AsyncExecute() function.
197 // Implementation details of, specifically, the AsyncExecute() function.
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
) {
205 template <typename T
>
206 static nsTArray
<T
> Copy(nsTArray
<T
> const& arr
) {
210 // The possible execution strategies of AsyncExecute.
211 enum Strategy
{ Local
, Remote
, RemoteWithFallback
};
213 // Decode the relevant preference to determine the desired execution-
215 static Strategy
GetStrategy() {
217 mozilla::StaticPrefs::widget_windows_utility_process_file_picker();
224 return RemoteWithFallback
;
228 // on Nightly builds, fall back to local on failure
229 return RemoteWithFallback
;
231 // on release and beta, remain local-only for now
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
,
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()) {
300 return local(args
...);
303 return remote(args
...);
305 case RemoteWithFallback
:
306 // more complicated; continue below
310 // capture time for telemetry
311 constexpr static const auto GetTime
= []() -> uint64_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();
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
;
336 auto p0
= std::apply(local
, std::move(tuple
));
337 // ...then record the telemetry event
339 NS_GetCurrentThread(), kFunctionName
,
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
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
);
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
);
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());
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(),
443 NS_GetCurrentThread(), __PRETTY_FUNCTION__
,
444 [self
= RefPtr(this), shim
= std::move(shim
),
445 awps
= std::move(awps
)](Maybe
<nsString
> val
) {
447 self
->mUnicodeFile
= val
.extract();
453 NS_WARNING("ShowFolderPicker failed");
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
;
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
;
500 fos
|= FOS_FILEMUSTEXIST
;
503 case modeOpenMultiple
:
504 fos
|= FOS_FILEMUSTEXIST
| FOS_ALLOWMULTISELECT
;
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
;
515 MOZ_ASSERT(false, "file-picker opened in directory-picker mode");
516 return NotOk(E_FAIL
);
519 commands
.AppendElement(fd::SetOptions(fos
));
525 commands
.AppendElement(fd::SetTitle(mTitle
));
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
));
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
) {
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();
587 if (mode
!= modeOpenMultiple
) {
588 if (!paths
.IsEmpty()) {
589 MOZ_ASSERT(paths
.Length() == 1);
590 self
->mUnicodeFile
= paths
[0];
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
);
607 NS_WARNING("ShowFilePicker failed");
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();
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
);
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
;
657 NS_NewLocalFile(self
->mUnicodeFile
, false, getter_AddRefs(file
));
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
);
677 nsresult
nsFilePicker::Show(nsIFilePicker::ResultCode
* aReturnVal
) {
678 return NS_ERROR_NOT_IMPLEMENTED
;
682 nsFilePicker::GetFile(nsIFile
** aFile
) {
683 NS_ENSURE_ARG_POINTER(aFile
);
686 if (mUnicodeFile
.IsEmpty()) return NS_OK
;
688 nsCOMPtr
<nsIFile
> file
;
689 nsresult rv
= NS_NewLocalFile(mUnicodeFile
, false, getter_AddRefs(file
));
699 nsFilePicker::GetFileURL(nsIURI
** aFileURL
) {
701 nsCOMPtr
<nsIFile
> file
;
702 nsresult rv
= GetFile(getter_AddRefs(file
));
703 if (!file
) return rv
;
705 return NS_NewFileURI(aFileURL
, file
);
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
716 nsBaseWinFilePicker::SetDefaultString(const nsAString
& aString
) {
717 mDefaultFilePath
= aString
;
719 // First, make sure the file name is not too long.
721 int32_t nameIndex
= mDefaultFilePath
.RFind(u
"\\");
722 if (nameIndex
== kNotFound
)
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
'-');
749 nsBaseWinFilePicker::GetDefaultString(nsAString
& aString
) {
750 return NS_ERROR_FAILURE
;
753 // The default extension to use for files
755 nsBaseWinFilePicker::GetDefaultExtension(nsAString
& aExtension
) {
756 aExtension
= mDefaultExtension
;
761 nsBaseWinFilePicker::SetDefaultExtension(const nsAString
& aExtension
) {
762 mDefaultExtension
= aExtension
;
766 // Set the filter index
768 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
769 // Windows' filter index is 1-based, we use a 0-based system.
770 *aFilterIndex
= mSelectedType
- 1;
775 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
776 // Windows' filter index is 1-based, we use a 0-based system.
777 mSelectedType
= aFilterIndex
+ 1;
781 void nsFilePicker::InitNative(nsIWidget
* aParent
, const nsAString
& aTitle
) {
782 mParentWidget
= aParent
;
783 mTitle
.Assign(aTitle
);
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
;
794 sanitizedFilter
.StripWhitespace();
795 if (sanitizedFilter
== u
"*"_ns
) {
796 sanitizedFilter
= u
"*.*"_ns
;
799 mFilterList
.AppendElement(
800 Filter
{.title
= nsString(aTitle
), .filter
= std::move(sanitizedFilter
)});
804 void nsFilePicker::RememberLastUsedDirectory() {
805 if (IsPrivacyModeEnabled()) {
806 // Don't remember the directory if private browsing was in effect
810 nsCOMPtr
<nsIFile
> file
;
811 if (NS_FAILED(NS_NewLocalFile(mUnicodeFile
, false, getter_AddRefs(file
)))) {
812 NS_WARNING("RememberLastUsedDirectory failed to init file path.");
816 nsCOMPtr
<nsIFile
> dir
;
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.");
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
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
".");
844 mDefaultFilePath
.Right(ext
, mDefaultFilePath
.Length() - extIndex
);
845 if (ext
.LowerCaseEqualsLiteral(".htm") ||
846 ext
.LowerCaseEqualsLiteral(".html") ||
847 ext
.LowerCaseEqualsLiteral(".shtml"))