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 "ContentAnalysis.h"
18 #include "mozilla/Assertions.h"
19 #include "mozilla/BackgroundHangMonitor.h"
20 #include "mozilla/Components.h"
21 #include "mozilla/dom/BrowsingContext.h"
22 #include "mozilla/dom/Directory.h"
23 #include "mozilla/Logging.h"
24 #include "mozilla/ipc/UtilityProcessManager.h"
25 #include "mozilla/ProfilerLabels.h"
26 #include "mozilla/StaticPrefs_widget.h"
27 #include "mozilla/UniquePtr.h"
28 #include "mozilla/WindowsVersion.h"
30 #include "nsEnumeratorUtils.h"
31 #include "nsIContentAnalysis.h"
32 #include "nsNetUtil.h"
33 #include "nsPIDOMWindow.h"
34 #include "nsPrintfCString.h"
35 #include "nsReadableUtils.h"
37 #include "nsToolkit.h"
41 #include "mozilla/glean/GleanMetrics.h"
43 #include "mozilla/widget/filedialog/WinFileDialogCommands.h"
44 #include "mozilla/widget/filedialog/WinFileDialogParent.h"
46 using mozilla::UniquePtr
;
48 using namespace mozilla::widget
;
50 UniquePtr
<char16_t
[], nsFilePicker::FreeDeleter
>
51 nsFilePicker::sLastUsedUnicodeDirectory
;
53 using mozilla::LogLevel
;
55 #define MAX_EXTENSION_LENGTH 10
57 ///////////////////////////////////////////////////////////////////////////////
60 // Manages matching PickerOpen/PickerClosed calls on the parent widget.
61 class AutoWidgetPickerState
{
62 static RefPtr
<nsWindow
> GetWindowForWidget(nsIWidget
* aWidget
) {
63 MOZ_ASSERT(NS_IsMainThread());
67 HWND hwnd
= (HWND
)aWidget
->GetNativeData(NS_NATIVE_WINDOW
);
68 return RefPtr(WinUtils::GetNSWindowPtr(hwnd
));
72 explicit AutoWidgetPickerState(nsIWidget
* aWidget
)
73 : mWindow(GetWindowForWidget(aWidget
)) {
75 if (mWindow
) mWindow
->PickerOpen();
77 ~AutoWidgetPickerState() {
78 // may be null if moved-from
79 if (mWindow
) mWindow
->PickerClosed();
82 AutoWidgetPickerState(AutoWidgetPickerState
const&) = delete;
83 AutoWidgetPickerState(AutoWidgetPickerState
&& that
) noexcept
= default;
86 RefPtr
<nsWindow
> mWindow
;
89 ///////////////////////////////////////////////////////////////////////////////
92 nsFilePicker::nsFilePicker() : mSelectedType(1) {}
94 NS_IMPL_ISUPPORTS(nsFilePicker
, nsIFilePicker
)
96 NS_IMETHODIMP
nsFilePicker::Init(
97 mozIDOMWindowProxy
* aParent
, const nsAString
& aTitle
,
98 nsIFilePicker::Mode aMode
,
99 mozilla::dom::BrowsingContext
* aBrowsingContext
) {
100 // Don't attempt to open a real file-picker in headless mode.
101 if (gfxPlatform::IsHeadless()) {
102 return nsresult::NS_ERROR_NOT_AVAILABLE
;
105 nsCOMPtr
<nsPIDOMWindowOuter
> window
= do_QueryInterface(aParent
);
106 nsIDocShell
* docShell
= window
? window
->GetDocShell() : nullptr;
107 mLoadContext
= do_QueryInterface(docShell
);
109 return nsBaseFilePicker::Init(aParent
, aTitle
, aMode
, aBrowsingContext
);
112 namespace mozilla::detail
{
113 // Boilerplate for remotely showing a file dialog.
114 template <typename ActionType
,
115 typename ReturnType
= typename
decltype(std::declval
<ActionType
>()(
116 nullptr))::element_type::ResolveValueType
>
117 static auto ShowRemote(ActionType
&& action
)
118 -> RefPtr
<MozPromise
<ReturnType
, HRESULT
, true>> {
119 using RetPromise
= MozPromise
<ReturnType
, HRESULT
, true>;
121 constexpr static const auto fail
= []() {
122 return RetPromise::CreateAndReject(E_FAIL
, __PRETTY_FUNCTION__
);
125 auto mgr
= mozilla::ipc::UtilityProcessManager::GetSingleton();
131 auto wfda
= mgr
->CreateWinFileDialogActor();
136 using mozilla::widget::filedialog::sLogFileDialog
;
139 mozilla::GetMainThreadSerialEventTarget(),
140 "nsFilePicker ShowRemote acquire",
141 [action
= std::forward
<ActionType
>(action
)](
142 filedialog::ProcessProxy
const& p
) -> RefPtr
<RetPromise
> {
143 MOZ_LOG(sLogFileDialog
, LogLevel::Info
,
144 ("nsFilePicker ShowRemote first callback: p = [%p]", p
.get()));
146 // false positive: not actually redundant
147 // NOLINTNEXTLINE(readability-redundant-smartptr-get)
148 return action(p
.get())->Then(
149 mozilla::GetMainThreadSerialEventTarget(),
150 "nsFilePicker ShowRemote call",
151 [p
](ReturnType ret
) {
152 return RetPromise::CreateAndResolve(std::move(ret
),
153 __PRETTY_FUNCTION__
);
155 [](mozilla::ipc::ResponseRejectReason error
) {
156 MOZ_LOG(sLogFileDialog
, LogLevel::Error
,
157 ("IPC call rejected: %zu", size_t(error
)));
161 [](nsresult error
) -> RefPtr
<RetPromise
> {
162 MOZ_LOG(sLogFileDialog
, LogLevel::Error
,
163 ("could not acquire WinFileDialog: %zu", size_t(error
)));
170 // Wrapper-namespace for the AsyncExecute() and AsyncAll() functions.
173 // Implementation details of, specifically, the AsyncExecute() and AsyncAll()
176 // Helper for generically copying ordinary types and nsTArray (which lacks a
177 // copy constructor) in the same breath.
178 template <typename T
>
179 static T
Copy(T
const& val
) {
182 template <typename T
>
183 static nsTArray
<T
> Copy(nsTArray
<T
> const& arr
) {
187 // The possible execution strategies of AsyncExecute.
188 enum Strategy
{ Local
, Remote
, RemoteWithFallback
};
190 // Decode the relevant preference to determine the desired execution-
192 static Strategy
GetStrategy() {
194 mozilla::StaticPrefs::widget_windows_utility_process_file_picker();
201 return RemoteWithFallback
;
205 // on Nightly builds, fall back to local on failure
206 return RemoteWithFallback
;
208 // on release and beta, remain local-only for now
214 template <typename T
>
215 class AsyncAllIterator final
{
217 NS_INLINE_DECL_REFCOUNTING(AsyncAllIterator
)
221 RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>>(const T
& item
)>
223 RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>::Private
> aPromise
)
224 : mItems(std::move(aItems
)),
226 mPredicate(std::move(aPredicate
)),
227 mPromise(std::move(aPromise
)) {}
229 void StartIterating() { ContinueIterating(); }
232 ~AsyncAllIterator() = default;
233 void ContinueIterating() {
234 if (mNextIndex
>= mItems
.Length()) {
235 mPromise
->Resolve(true, __func__
);
238 mPredicate(mItems
.ElementAt(mNextIndex
))
240 mozilla::GetMainThreadSerialEventTarget(), __func__
,
241 [self
= RefPtr
{this}](bool aResult
) {
243 self
->mPromise
->Resolve(false, __func__
);
247 self
->ContinueIterating();
249 [self
= RefPtr
{this}](nsresult aError
) {
250 self
->mPromise
->Reject(aError
, __func__
);
255 std::function
<RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>>(
258 RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>::Private
> mPromise
;
261 namespace telemetry
{
262 static uint32_t Delta(uint64_t tb
, uint64_t ta
) {
263 // FILETIMEs are 100ns intervals; we reduce that to 1ms.
264 // (`u32::max()` milliseconds is roughly 47.91 days.)
265 return uint32_t((tb
- ta
) / 10'000);
267 static nsCString
HexString(HRESULT val
) {
268 return nsPrintfCString("%08lX", val
);
271 static void RecordSuccess(uint64_t (&&time
)[2]) {
272 auto [t0
, t1
] = time
;
274 namespace glean_fd
= mozilla::glean::file_dialog
;
275 glean_fd::FallbackExtra extra
{
276 .hresultLocal
= Nothing(),
277 .hresultRemote
= Nothing(),
278 .succeeded
= Some(true),
279 .timeLocal
= Nothing(),
280 .timeRemote
= Some(Delta(t1
, t0
)),
282 glean_fd::fallback
.Record(Some(extra
));
285 static void RecordFailure(uint64_t (&&time
)[3], HRESULT hrRemote
,
287 auto [t0
, t1
, t2
] = time
;
290 namespace glean_fd
= mozilla::glean::file_dialog
;
291 glean_fd::FallbackExtra extra
{
292 .hresultLocal
= Some(HexString(hrLocal
)),
293 .hresultRemote
= Some(HexString(hrRemote
)),
294 .succeeded
= Some(false),
295 .timeLocal
= Some(Delta(t2
, t1
)),
296 .timeRemote
= Some(Delta(t1
, t0
)),
298 glean_fd::fallback
.Record(Some(extra
));
302 } // namespace telemetry
303 } // namespace details
305 // Invoke either or both of a "do locally" and "do remotely" function with the
306 // provided arguments, depending on the relevant preference-value and whether
307 // or not the remote version fails.
309 // Both functions must be asynchronous, returning a `RefPtr<MozPromise<...>>`.
310 // "Failure" is defined as the promise being rejected.
311 template <typename Fn1
, typename Fn2
, typename
... Args
>
312 static auto AsyncExecute(Fn1 local
, Fn2 remote
, Args
const&... args
)
313 -> std::invoke_result_t
<Fn1
, Args
...> {
314 using namespace details
;
316 static_assert(std::is_same_v
<std::invoke_result_t
<Fn1
, Args
...>,
317 std::invoke_result_t
<Fn2
, Args
...>>);
318 using PromiseT
= typename
std::invoke_result_t
<Fn1
, Args
...>::element_type
;
320 constexpr static char kFunctionName
[] = "LocalAndOrRemote::AsyncExecute";
322 switch (GetStrategy()) {
324 return local(args
...);
327 return remote(args
...);
329 case RemoteWithFallback
:
330 // more complicated; continue below
334 // capture time for telemetry
335 constexpr static const auto GetTime
= []() -> uint64_t {
337 ::GetSystemTimeAsFileTime(&t
);
338 return (uint64_t(t
.dwHighDateTime
) << 32) | t
.dwLowDateTime
;
340 uint64_t const t0
= GetTime();
342 return remote(args
...)->Then(
343 NS_GetCurrentThread(), kFunctionName
,
344 [t0
](typename
PromiseT::ResolveValueType result
) -> RefPtr
<PromiseT
> {
345 // success; stop here
346 auto const t1
= GetTime();
348 telemetry::RecordSuccess({t0
, t1
});
349 return PromiseT::CreateAndResolve(result
, kFunctionName
);
351 // initialized lambda pack captures are C++20 (clang 9, gcc 9);
352 // `make_tuple` is just a C++17 workaround
353 [=, tuple
= std::make_tuple(Copy(args
)...)](
354 typename
PromiseT::RejectValueType err
) mutable -> RefPtr
<PromiseT
> {
355 // failure; record time
356 auto const t1
= GetTime();
357 HRESULT
const hrRemote
= err
;
360 auto p0
= std::apply(local
, std::move(tuple
));
361 // ...then record the telemetry event
363 NS_GetCurrentThread(), kFunctionName
,
365 hrRemote
](typename
PromiseT::ResolveOrRejectValue
const& val
)
366 -> RefPtr
<PromiseT
> {
367 auto const t2
= GetTime();
368 HRESULT
const hrLocal
= val
.IsReject() ? val
.RejectValue() : S_OK
;
369 telemetry::RecordFailure({t0
, t1
, t2
}, hrRemote
, hrLocal
);
371 return PromiseT::CreateAndResolveOrReject(val
, kFunctionName
);
376 // Asynchronously invokes `aPredicate` on each member of `aItems`.
377 // Yields `false` (and stops immediately) if any invocation of
378 // `predicate` yielded `false`; otherwise yields `true`.
379 template <typename T
>
380 static RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>> AsyncAll(
383 RefPtr
<mozilla::MozPromise
<bool, nsresult
, true>>(const T
& item
)>
386 mozilla::MakeRefPtr
<mozilla::MozPromise
<bool, nsresult
, true>::Private
>(
388 auto iterator
= mozilla::MakeRefPtr
<details::AsyncAllIterator
<T
>>(
389 std::move(aItems
), aPredicate
, promise
);
390 iterator
->StartIterating();
393 } // namespace fd_async
395 using fd_async::AsyncAll
;
396 using fd_async::AsyncExecute
;
398 } // namespace mozilla::detail
401 nsFilePicker::FPPromise
<filedialog::Results
> nsFilePicker::ShowFilePickerRemote(
402 HWND parent
, filedialog::FileDialogType type
,
403 nsTArray
<filedialog::Command
> const& commands
) {
404 using mozilla::widget::filedialog::sLogFileDialog
;
405 return mozilla::detail::ShowRemote(
407 commands
= commands
.Clone()](filedialog::WinFileDialogParent
* p
) {
408 MOZ_LOG(sLogFileDialog
, LogLevel::Info
,
409 ("%s: p = [%p]", __PRETTY_FUNCTION__
, p
));
410 return p
->SendShowFileDialog((uintptr_t)parent
, type
, commands
);
415 nsFilePicker::FPPromise
<nsString
> nsFilePicker::ShowFolderPickerRemote(
416 HWND parent
, nsTArray
<filedialog::Command
> const& commands
) {
417 using mozilla::widget::filedialog::sLogFileDialog
;
418 return mozilla::detail::ShowRemote([parent
, commands
= commands
.Clone()](
419 filedialog::WinFileDialogParent
* p
) {
420 MOZ_LOG(sLogFileDialog
, LogLevel::Info
,
421 ("%s: p = [%p]", __PRETTY_FUNCTION__
, p
));
422 return p
->SendShowFolderDialog((uintptr_t)parent
, commands
);
427 nsFilePicker::FPPromise
<filedialog::Results
> nsFilePicker::ShowFilePickerLocal(
428 HWND parent
, filedialog::FileDialogType type
,
429 nsTArray
<filedialog::Command
> const& commands
) {
430 return filedialog::SpawnFilePicker(parent
, type
, commands
.Clone());
434 nsFilePicker::FPPromise
<nsString
> nsFilePicker::ShowFolderPickerLocal(
435 HWND parent
, nsTArray
<filedialog::Command
> const& commands
) {
436 return filedialog::SpawnFolderPicker(parent
, commands
.Clone());
440 * Folder picker invocation
444 * Show a folder picker.
446 * @param aInitialDir The initial directory. The last-used directory will be
447 * used if left blank.
448 * @return A promise which:
449 * - resolves to true if a file was selected successfully (in which
450 * case mUnicodeFile will be updated);
451 * - resolves to false if the dialog was cancelled by the user;
452 * - is rejected with the associated HRESULT if some error occurred.
454 RefPtr
<mozilla::MozPromise
<bool, HRESULT
, true>> nsFilePicker::ShowFolderPicker(
455 const nsString
& aInitialDir
) {
456 using Promise
= mozilla::MozPromise
<bool, HRESULT
, true>;
457 constexpr static auto Ok
= [](bool val
) {
458 return Promise::CreateAndResolve(val
, "nsFilePicker::ShowFolderPicker");
460 constexpr static auto NotOk
= [](HRESULT val
= E_FAIL
) {
461 return Promise::CreateAndReject(val
, "nsFilePicker::ShowFolderPicker");
464 namespace fd
= ::mozilla::widget::filedialog
;
465 nsTArray
<fd::Command
> commands
= {
466 fd::SetOptions(FOS_PICKFOLDERS
),
467 fd::SetTitle(mTitle
),
470 if (!mOkButtonLabel
.IsEmpty()) {
471 commands
.AppendElement(fd::SetOkButtonLabel(mOkButtonLabel
));
474 if (!aInitialDir
.IsEmpty()) {
475 commands
.AppendElement(fd::SetFolder(aInitialDir
));
478 ScopedRtlShimWindow
shim(mParentWidget
.get());
479 AutoWidgetPickerState
awps(mParentWidget
);
481 return mozilla::detail::AsyncExecute(&ShowFolderPickerLocal
,
482 &ShowFolderPickerRemote
, shim
.get(),
485 NS_GetCurrentThread(), __PRETTY_FUNCTION__
,
486 [self
= RefPtr(this), shim
= std::move(shim
),
487 awps
= std::move(awps
)](Maybe
<nsString
> val
) {
489 self
->mUnicodeFile
= val
.extract();
495 NS_WARNING("ShowFolderPicker failed");
501 * File open and save picker invocation
505 * Show a file picker.
507 * @param aInitialDir The initial directory. The last-used directory will be
508 * used if left blank.
509 * @return A promise which:
510 * - resolves to true if one or more files were selected successfully
511 * (in which case mUnicodeFile and/or mFiles will be updated);
512 * - resolves to false if the dialog was cancelled by the user;
513 * - is rejected with the associated HRESULT if some error occurred.
515 RefPtr
<mozilla::MozPromise
<bool, HRESULT
, true>> nsFilePicker::ShowFilePicker(
516 const nsString
& aInitialDir
) {
517 AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER
);
519 using Promise
= mozilla::MozPromise
<bool, HRESULT
, true>;
520 constexpr static auto Ok
= [](bool val
) {
521 return Promise::CreateAndResolve(val
, "nsFilePicker::ShowFilePicker");
523 constexpr static auto NotOk
= [](HRESULT val
= E_FAIL
) {
524 return Promise::CreateAndReject(val
, "nsFilePicker::ShowFilePicker");
527 namespace fd
= ::mozilla::widget::filedialog
;
528 nsTArray
<fd::Command
> commands
;
531 FILEOPENDIALOGOPTIONS fos
= 0;
532 fos
|= FOS_SHAREAWARE
| FOS_OVERWRITEPROMPT
| FOS_FORCEFILESYSTEM
;
534 // Handle add to recent docs settings
535 if (IsPrivacyModeEnabled() || !mAddToRecentDocs
) {
536 fos
|= FOS_DONTADDTORECENT
;
542 fos
|= FOS_FILEMUSTEXIST
;
545 case modeOpenMultiple
:
546 fos
|= FOS_FILEMUSTEXIST
| FOS_ALLOWMULTISELECT
;
550 fos
|= FOS_NOREADONLYRETURN
;
551 // Don't follow shortcuts when saving a shortcut, this can be used
552 // to trick users (bug 271732)
553 if (IsDefaultPathLink()) fos
|= FOS_NODEREFERENCELINKS
;
557 MOZ_ASSERT(false, "file-picker opened in directory-picker mode");
558 return NotOk(E_FAIL
);
561 commands
.AppendElement(fd::SetOptions(fos
));
567 commands
.AppendElement(fd::SetTitle(mTitle
));
570 if (!mDefaultFilename
.IsEmpty()) {
571 // Prevent the shell from expanding environment variables by removing
572 // the % characters that are used to delimit them.
573 nsAutoString
sanitizedFilename(mDefaultFilename
);
574 sanitizedFilename
.ReplaceChar('%', '_');
576 commands
.AppendElement(fd::SetFileName(sanitizedFilename
));
579 // default extension to append to new files
580 if (!mDefaultExtension
.IsEmpty()) {
581 // We don't want environment variables expanded in the extension either.
582 nsAutoString
sanitizedExtension(mDefaultExtension
);
583 sanitizedExtension
.ReplaceChar('%', '_');
585 commands
.AppendElement(fd::SetDefaultExtension(sanitizedExtension
));
586 } else if (IsDefaultPathHtml()) {
587 commands
.AppendElement(fd::SetDefaultExtension(u
"html"_ns
));
591 if (!aInitialDir
.IsEmpty()) {
592 commands
.AppendElement(fd::SetFolder(aInitialDir
));
595 // filter types and the default index
596 if (!mFilterList
.IsEmpty()) {
597 nsTArray
<fd::ComDlgFilterSpec
> fileTypes
;
598 for (auto const& filter
: mFilterList
) {
599 fileTypes
.EmplaceBack(filter
.title
, filter
.filter
);
601 commands
.AppendElement(fd::SetFileTypes(std::move(fileTypes
)));
602 commands
.AppendElement(fd::SetFileTypeIndex(mSelectedType
));
605 ScopedRtlShimWindow
shim(mParentWidget
.get());
606 AutoWidgetPickerState
awps(mParentWidget
);
608 mozilla::BackgroundHangMonitor().NotifyWait();
609 auto type
= mMode
== modeSave
? FileDialogType::Save
: FileDialogType::Open
;
611 auto promise
= mozilla::detail::AsyncExecute(
612 &ShowFilePickerLocal
, &ShowFilePickerRemote
, shim
.get(), type
, commands
);
614 return promise
->Then(
615 mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__
,
616 [self
= RefPtr(this), mode
= mMode
, shim
= std::move(shim
),
617 awps
= std::move(awps
)](Maybe
<Results
> res_opt
) {
621 auto result
= res_opt
.extract();
623 // Remember what filter type the user selected
624 self
->mSelectedType
= int32_t(result
.selectedFileTypeIndex());
626 auto const& paths
= result
.paths();
629 if (mode
!= modeOpenMultiple
) {
630 if (!paths
.IsEmpty()) {
631 MOZ_ASSERT(paths
.Length() == 1);
632 self
->mUnicodeFile
= paths
[0];
638 // multiple selection
639 for (auto const& str
: paths
) {
640 nsCOMPtr
<nsIFile
> file
;
641 if (NS_SUCCEEDED(NS_NewLocalFile(str
, false, getter_AddRefs(file
)))) {
642 self
->mFiles
.AppendObject(file
);
649 NS_WARNING("ShowFilePicker failed");
654 void nsFilePicker::ClearFiles() {
655 mUnicodeFile
.Truncate();
660 class GetFilesInDirectoryCallback final
661 : public mozilla::dom::GetFilesCallback
{
663 explicit GetFilesInDirectoryCallback(
664 RefPtr
<mozilla::MozPromise
<nsTArray
<mozilla::PathString
>, nsresult
,
667 : mPromise(std::move(aPromise
)) {}
670 const FallibleTArray
<RefPtr
<mozilla::dom::BlobImpl
>>& aBlobImpls
) {
671 if (NS_FAILED(aStatus
)) {
672 mPromise
->Reject(aStatus
, __func__
);
675 nsTArray
<mozilla::PathString
> filePaths
;
676 filePaths
.SetCapacity(aBlobImpls
.Length());
677 for (const auto& blob
: aBlobImpls
) {
678 if (blob
->IsFile()) {
679 mozilla::PathString pathString
;
680 mozilla::ErrorResult error
;
681 blob
->GetMozFullPathInternal(pathString
, error
);
682 nsresult rv
= error
.StealNSResult();
683 if (NS_WARN_IF(NS_FAILED(rv
))) {
684 mPromise
->Reject(rv
, __func__
);
687 filePaths
.AppendElement(pathString
);
689 NS_WARNING("Got a non-file blob, can't do content analysis on it");
692 mPromise
->Resolve(std::move(filePaths
), __func__
);
696 RefPtr
<mozilla::MozPromise
<nsTArray
<mozilla::PathString
>, nsresult
,
700 } // anonymous namespace
702 RefPtr
<nsFilePicker::ContentAnalysisResponse
>
703 nsFilePicker::CheckContentAnalysisService() {
705 nsCOMPtr
<nsIContentAnalysis
> contentAnalysis
=
706 mozilla::components::nsIContentAnalysis::Service(&rv
);
707 if (NS_WARN_IF(NS_FAILED(rv
))) {
708 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv
, __func__
);
710 bool contentAnalysisIsActive
= false;
711 rv
= contentAnalysis
->GetIsActive(&contentAnalysisIsActive
);
712 if (NS_WARN_IF(NS_FAILED(rv
))) {
713 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv
, __func__
);
715 if (!contentAnalysisIsActive
) {
716 return nsFilePicker::ContentAnalysisResponse::CreateAndResolve(true,
720 nsCOMPtr
<nsIURI
> uri
= mBrowsingContext
->Canonical()->GetCurrentURI();
722 auto processOneItem
= [self
= RefPtr
{this},
723 contentAnalysis
= std::move(contentAnalysis
),
725 std::move(uri
)](const mozilla::PathString
& aItem
) {
726 nsCString emptyDigestString
;
728 self
->mBrowsingContext
->Canonical()->GetCurrentWindowGlobal();
729 nsCOMPtr
<nsIContentAnalysisRequest
> contentAnalysisRequest(
730 new mozilla::contentanalysis::ContentAnalysisRequest(
731 nsIContentAnalysisRequest::AnalysisType::eFileAttached
, aItem
, true,
732 std::move(emptyDigestString
), uri
,
733 nsIContentAnalysisRequest::OperationType::eCustomDisplayString
,
737 mozilla::MakeRefPtr
<nsFilePicker::ContentAnalysisResponse::Private
>(
739 auto contentAnalysisCallback
=
740 mozilla::MakeRefPtr
<mozilla::contentanalysis::ContentAnalysisCallback
>(
741 [promise
](nsIContentAnalysisResponse
* aResponse
) {
742 promise
->Resolve(aResponse
->GetShouldAllowContent(), __func__
);
744 [promise
](nsresult aError
) { promise
->Reject(aError
, __func__
); });
746 nsresult rv
= contentAnalysis
->AnalyzeContentRequestCallback(
747 contentAnalysisRequest
, /* aAutoAcknowledge */ true,
748 contentAnalysisCallback
);
749 if (NS_WARN_IF(NS_FAILED(rv
))) {
750 promise
->Reject(rv
, __func__
);
755 // Since getting the files to analyze might be asynchronous, use a MozPromise
756 // to unify the logic below.
757 auto getFilesToAnalyzePromise
= mozilla::MakeRefPtr
<mozilla::MozPromise
<
758 nsTArray
<mozilla::PathString
>, nsresult
, true>::Private
>(__func__
);
759 if (mMode
== modeGetFolder
) {
760 nsCOMPtr
<nsISupports
> tmp
;
761 nsresult rv
= GetDomFileOrDirectory(getter_AddRefs(tmp
));
762 if (NS_WARN_IF(NS_FAILED(rv
))) {
763 getFilesToAnalyzePromise
->Reject(rv
, __func__
);
764 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv
,
767 auto* directory
= static_cast<mozilla::dom::Directory
*>(tmp
.get());
768 mozilla::dom::OwningFileOrDirectory owningDirectory
;
769 owningDirectory
.SetAsDirectory() = directory
;
770 nsTArray
<mozilla::dom::OwningFileOrDirectory
> directoryArray
{
771 std::move(owningDirectory
)};
773 mozilla::ErrorResult error
;
774 RefPtr
<mozilla::dom::GetFilesHelper
> helper
=
775 mozilla::dom::GetFilesHelper::Create(directoryArray
, true, error
);
776 rv
= error
.StealNSResult();
777 if (NS_WARN_IF(NS_FAILED(rv
))) {
778 getFilesToAnalyzePromise
->Reject(rv
, __func__
);
779 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv
,
782 auto getFilesCallback
= mozilla::MakeRefPtr
<GetFilesInDirectoryCallback
>(
783 getFilesToAnalyzePromise
);
784 helper
->AddCallback(getFilesCallback
);
786 nsCOMArray
<nsIFile
> files
;
787 if (!mUnicodeFile
.IsEmpty()) {
788 nsCOMPtr
<nsIFile
> file
;
789 rv
= GetFile(getter_AddRefs(file
));
790 if (NS_WARN_IF(NS_FAILED(rv
))) {
791 getFilesToAnalyzePromise
->Reject(rv
, __func__
);
792 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(rv
,
795 files
.AppendElement(file
);
797 files
.AppendElements(mFiles
);
799 nsTArray
<mozilla::PathString
> paths(files
.Length());
800 std::transform(files
.begin(), files
.end(), MakeBackInserter(paths
),
801 [](auto* entry
) { return entry
->NativePath(); });
802 getFilesToAnalyzePromise
->Resolve(std::move(paths
), __func__
);
805 return getFilesToAnalyzePromise
->Then(
806 mozilla::GetMainThreadSerialEventTarget(), __func__
,
807 [processOneItem
](nsTArray
<mozilla::PathString
> aPaths
) mutable {
808 return mozilla::detail::AsyncAll
<mozilla::PathString
>(std::move(aPaths
),
811 [](nsresult aError
) {
812 return nsFilePicker::ContentAnalysisResponse::CreateAndReject(aError
,
817 ///////////////////////////////////////////////////////////////////////////////
818 // nsIFilePicker impl.
820 nsresult
nsFilePicker::Open(nsIFilePickerShownCallback
* aCallback
) {
821 NS_ENSURE_ARG_POINTER(aCallback
);
823 if (MaybeBlockFilePicker(aCallback
)) {
827 // Don't attempt to open a real file-picker in headless mode.
828 if (gfxPlatform::IsHeadless()) {
829 return nsresult::NS_ERROR_NOT_AVAILABLE
;
832 nsAutoString initialDir
;
833 if (mDisplayDirectory
) mDisplayDirectory
->GetPath(initialDir
);
835 // If no display directory, re-use the last one.
836 if (initialDir
.IsEmpty()) {
837 // Allocate copy of last used dir.
838 initialDir
= sLastUsedUnicodeDirectory
.get();
841 // Clear previous file selections
844 auto promise
= mMode
== modeGetFolder
? ShowFolderPicker(initialDir
)
845 : ShowFilePicker(initialDir
);
847 auto p2
= promise
->Then(
848 mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__
,
849 [self
= RefPtr(this),
850 callback
= RefPtr(aCallback
)](bool selectionMade
) -> void {
851 if (!selectionMade
) {
852 callback
->Done(ResultCode::returnCancel
);
856 self
->RememberLastUsedDirectory();
858 nsIFilePicker::ResultCode retValue
= ResultCode::returnOK
;
860 if (self
->mMode
== modeSave
) {
861 // Windows does not return resultReplace; we must check whether the
862 // file already exists.
863 nsCOMPtr
<nsIFile
> file
;
865 NS_NewLocalFile(self
->mUnicodeFile
, false, getter_AddRefs(file
));
868 if (NS_SUCCEEDED(rv
) && NS_SUCCEEDED(file
->Exists(&flag
)) && flag
) {
869 retValue
= ResultCode::returnReplace
;
873 if (self
->mBrowsingContext
&& !self
->mBrowsingContext
->IsChrome() &&
874 self
->mMode
!= modeSave
&& retValue
!= ResultCode::returnCancel
) {
875 self
->CheckContentAnalysisService()->Then(
876 mozilla::GetMainThreadSerialEventTarget(), __func__
,
877 [retValue
, callback
, self
= RefPtr
{self
}](bool aAllowContent
) {
879 callback
->Done(retValue
);
882 callback
->Done(ResultCode::returnCancel
);
885 [callback
, self
= RefPtr
{self
}](nsresult aError
) {
887 callback
->Done(ResultCode::returnCancel
);
892 callback
->Done(retValue
);
894 [callback
= RefPtr(aCallback
)](HRESULT err
) {
895 using mozilla::widget::filedialog::sLogFileDialog
;
896 MOZ_LOG(sLogFileDialog
, LogLevel::Error
,
897 ("nsFilePicker: Show failed with hr=0x%08lX", err
));
898 callback
->Done(ResultCode::returnCancel
);
904 nsresult
nsFilePicker::Show(nsIFilePicker::ResultCode
* aReturnVal
) {
905 return NS_ERROR_NOT_IMPLEMENTED
;
909 nsFilePicker::GetFile(nsIFile
** aFile
) {
910 NS_ENSURE_ARG_POINTER(aFile
);
913 if (mUnicodeFile
.IsEmpty()) return NS_OK
;
915 nsCOMPtr
<nsIFile
> file
;
916 nsresult rv
= NS_NewLocalFile(mUnicodeFile
, false, getter_AddRefs(file
));
926 nsFilePicker::GetFileURL(nsIURI
** aFileURL
) {
928 nsCOMPtr
<nsIFile
> file
;
929 nsresult rv
= GetFile(getter_AddRefs(file
));
930 if (!file
) return rv
;
932 return NS_NewFileURI(aFileURL
, file
);
936 nsFilePicker::GetFiles(nsISimpleEnumerator
** aFiles
) {
937 NS_ENSURE_ARG_POINTER(aFiles
);
938 return NS_NewArrayEnumerator(aFiles
, mFiles
, NS_GET_IID(nsIFile
));
941 // Get the file + path
943 nsBaseWinFilePicker::SetDefaultString(const nsAString
& aString
) {
944 mDefaultFilePath
= aString
;
946 // First, make sure the file name is not too long.
948 int32_t nameIndex
= mDefaultFilePath
.RFind(u
"\\");
949 if (nameIndex
== kNotFound
)
953 nameLength
= mDefaultFilePath
.Length() - nameIndex
;
954 mDefaultFilename
.Assign(Substring(mDefaultFilePath
, nameIndex
));
956 if (nameLength
> MAX_PATH
) {
957 int32_t extIndex
= mDefaultFilePath
.RFind(u
".");
958 if (extIndex
== kNotFound
) extIndex
= mDefaultFilePath
.Length();
960 // Let's try to shave the needed characters from the name part.
961 int32_t charsToRemove
= nameLength
- MAX_PATH
;
962 if (extIndex
- nameIndex
>= charsToRemove
) {
963 mDefaultFilePath
.Cut(extIndex
- charsToRemove
, charsToRemove
);
967 // Then, we need to replace illegal characters. At this stage, we cannot
968 // replace the backslash as the string might represent a file path.
969 mDefaultFilePath
.ReplaceChar(u
"" FILE_ILLEGAL_CHARACTERS
, u
'-');
970 mDefaultFilename
.ReplaceChar(u
"" FILE_ILLEGAL_CHARACTERS
, u
'-');
976 nsBaseWinFilePicker::GetDefaultString(nsAString
& aString
) {
977 return NS_ERROR_FAILURE
;
980 // The default extension to use for files
982 nsBaseWinFilePicker::GetDefaultExtension(nsAString
& aExtension
) {
983 aExtension
= mDefaultExtension
;
988 nsBaseWinFilePicker::SetDefaultExtension(const nsAString
& aExtension
) {
989 mDefaultExtension
= aExtension
;
993 // Set the filter index
995 nsFilePicker::GetFilterIndex(int32_t* aFilterIndex
) {
996 // Windows' filter index is 1-based, we use a 0-based system.
997 *aFilterIndex
= mSelectedType
- 1;
1002 nsFilePicker::SetFilterIndex(int32_t aFilterIndex
) {
1003 // Windows' filter index is 1-based, we use a 0-based system.
1004 mSelectedType
= aFilterIndex
+ 1;
1008 void nsFilePicker::InitNative(nsIWidget
* aParent
, const nsAString
& aTitle
) {
1009 mParentWidget
= aParent
;
1010 mTitle
.Assign(aTitle
);
1014 nsFilePicker::AppendFilter(const nsAString
& aTitle
, const nsAString
& aFilter
) {
1015 nsString
sanitizedFilter(aFilter
);
1016 sanitizedFilter
.ReplaceChar('%', '_');
1018 if (sanitizedFilter
== u
"..apps"_ns
) {
1019 sanitizedFilter
= u
"*.exe;*.com"_ns
;
1021 sanitizedFilter
.StripWhitespace();
1022 if (sanitizedFilter
== u
"*"_ns
) {
1023 sanitizedFilter
= u
"*.*"_ns
;
1026 mFilterList
.AppendElement(
1027 Filter
{.title
= nsString(aTitle
), .filter
= std::move(sanitizedFilter
)});
1031 void nsFilePicker::RememberLastUsedDirectory() {
1032 if (IsPrivacyModeEnabled()) {
1033 // Don't remember the directory if private browsing was in effect
1037 nsCOMPtr
<nsIFile
> file
;
1038 if (NS_FAILED(NS_NewLocalFile(mUnicodeFile
, false, getter_AddRefs(file
)))) {
1039 NS_WARNING("RememberLastUsedDirectory failed to init file path.");
1043 nsCOMPtr
<nsIFile
> dir
;
1044 nsAutoString newDir
;
1045 if (NS_FAILED(file
->GetParent(getter_AddRefs(dir
))) ||
1046 !(mDisplayDirectory
= dir
) ||
1047 NS_FAILED(mDisplayDirectory
->GetPath(newDir
)) || newDir
.IsEmpty()) {
1048 NS_WARNING("RememberLastUsedDirectory failed to get parent directory.");
1052 sLastUsedUnicodeDirectory
.reset(ToNewUnicode(newDir
));
1055 bool nsFilePicker::IsPrivacyModeEnabled() {
1056 return mLoadContext
&& mLoadContext
->UsePrivateBrowsing();
1059 bool nsFilePicker::IsDefaultPathLink() {
1060 NS_ConvertUTF16toUTF8
ext(mDefaultFilePath
);
1061 ext
.Trim(" .", false, true); // watch out for trailing space and dots
1063 return StringEndsWith(ext
, ".lnk"_ns
) || StringEndsWith(ext
, ".pif"_ns
) ||
1064 StringEndsWith(ext
, ".url"_ns
);
1067 bool nsFilePicker::IsDefaultPathHtml() {
1068 int32_t extIndex
= mDefaultFilePath
.RFind(u
".");
1069 if (extIndex
>= 0) {
1071 mDefaultFilePath
.Right(ext
, mDefaultFilePath
.Length() - extIndex
);
1072 if (ext
.LowerCaseEqualsLiteral(".htm") ||
1073 ext
.LowerCaseEqualsLiteral(".html") ||
1074 ext
.LowerCaseEqualsLiteral(".shtml"))