/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- * * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
NS_IMETHODIMP nsFilePicker::Init(
mozilla::dom::BrowsingContext* aBrowsingContext, const nsAString& aTitle,
nsIFilePicker::Mode aMode) { // Don't attempt to open a real file-picker in headless mode. if (gfxPlatform::IsHeadless()) { return nsresult::NS_ERROR_NOT_AVAILABLE;
}
// fd_async // // Wrapper-namespace for the AsyncExecute() and AsyncAll() functions. namespace fd_async {
// Implementation details of, specifically, the AsyncExecute() and AsyncAll() // functions. namespace details { // Helper for generically copying ordinary types and nsTArray (which lacks a // copy constructor) in the same breath. template <typename T> static T Copy(T const& val) { return val;
} template <typename T> static nsTArray<T> Copy(nsTArray<T> const& arr) { return arr.Clone();
}
// The possible execution strategies of AsyncExecute. enum Strategy { // Always and only open the dialog in-process. This is effectively the // only behavior in older versions of Gecko.
LocalOnly,
// Always and only open the dialog out-of-process.
RemoteOnly,
// Open the dialog out-of-process. If that fails in any way, try to recover by // opening it in-process.
RemoteWithFallback,
// Try to open the dialog out-of-process. If and only if the process can't // even be created, fall back to in-process. // // This heuristic is crafted to avoid the out-of-process file-dialog causing // user-experience regressions compared to the previous "LocalOnly" behavior: // * If the file-dialog actually crashes, then it would have brought down the // entire browser. In this case just surfacing an error is a strict // improvement. // * If the utility process simply fails to start, there's usually nothing // preventing the dialog from being opened in-process instead. Producing an // error would be a degradation.
FallbackUnlessCrash,
};
// Decode the relevant preference to determine the desired execution- // strategy. static Strategy GetStrategy() {
int32_t const pref =
mozilla::StaticPrefs::widget_windows_utility_process_file_picker(); switch (pref) { case -1: return LocalOnly; case 3: return FallbackUnlessCrash; case 2: return RemoteOnly; case 1: return RemoteWithFallback;
default: // by default, fall back to local only on non-crash failures return FallbackUnlessCrash;
}
};
/* N.B.: L and R stand for Local and Remote, not just Left and Right */ template <typename FnL, typename FnR, typename... Args> struct AsyncExecuteInfo { template <typename T> using DestructurePromise = widget::filedialog::detail::DestructurePromise<T>;
using Unit = ::mozilla::Ok;
using RetL = std::invoke_result_t<FnL, Args...>; using RetR = std::invoke_result_t<FnR, Args...>;
using InfoL = DestructurePromise<RetL>; using InfoR = DestructurePromise<RetR>;
MOZ_ASSERT_SAME_TYPE( typename InfoL::ResolveT, typename InfoR::ResolveT, "local and remote promises must have identical resolve-types");
// At present, the local and remote promises have the same type, but this // isn't logically necessary. (In particular, a future refactor may remove the // redundant `.kind` from the local promises' return types.)
MOZ_ASSERT_SAME_TYPE(typename InfoL::RejectT, filedialog::Error, "local promise must reject with a filedialog::Error");
MOZ_ASSERT_SAME_TYPE(typename InfoR::RejectT, filedialog::Error, "remote promise must reject with a filedialog::Error");
using ResolveT = typename InfoL::ResolveT; using PromiseT = MozPromise<ResolveT, filedialog::Error, true>;
using RetT = RefPtr<PromiseT>;
};
} // namespace details
// Invoke either or both of a promise-returning "do locally" and "do remotely" // function with the provided arguments, depending on the relevant preference's // value and on whether or not the remote version fails (returns a rejection- // promise). // // Both provided functions must return a `RefPtr<filedialog::MozPromise<T>>`. As // `AsyncExecute` reports failures itself, its rejection-type is `()`. template <typename Fn1, typename Fn2, typename... Args> staticauto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args) -> typename details::AsyncExecuteInfo<Fn1, Fn2, Args...>::RetT { usingnamespace details; using Info = AsyncExecuteInfo<Fn1, Fn2, Args...>;
using ResolveT = typename Info::ResolveT; using PromiseT = typename Info::PromiseT; using LPromiseT = typename Info::InfoL::Promise; using RPromiseT = typename Info::InfoR::Promise;
case RemoteOnly:
useLocalFallback = [](Error const&) { returnfalse; }; break;
case RemoteWithFallback:
useLocalFallback = [](Error const&) { returntrue; }; break;
case FallbackUnlessCrash:
useLocalFallback = [](Error const& err) { // All remote crashes are reported as IPCError. The converse isn't // necessarily true in theory, but (per telemetry) appears to be true in // practice. return err.kind != Error::IPCError;
}; break;
}
return remote(args...)->Then(
NS_GetCurrentThread(), kFunctionName,
[](typename RPromiseT::ResolveValueType result) -> RefPtr<PromiseT> { // success; stop here return PromiseT::CreateAndResolve(std::move(result), kFunctionName);
}, // initialized lambda pack captures are C++20 (clang 9, gcc 9); // `make_tuple` is just a C++17 workaround
[=, tuple = std::make_tuple(Copy(args)...)]( typename RPromiseT::RejectValueType err) mutable -> RefPtr<PromiseT> { // failure; record time
// should we fall back to a local implementation? if (!useLocalFallback(err)) { // if not, log this failure immediately...
MOZ_LOG(filedialog::sLogFileDialog, LogLevel::Info,
("remote file-dialog failed: kind=%s, where=%s, " "why=%08" PRIX32,
Error::KindName(err.kind), err.where.c_str(), err.why)); // ... and stop here return PromiseT::CreateAndReject(err, kFunctionName);
}
// Asynchronously invokes `aPredicate` on each member of `aItems`. // Yields `false` (and stops immediately) if any invocation of // `predicate` yielded `false`; otherwise yields `true`. template <typename T> static RefPtr<mozilla::MozPromise<bool, nsresult, true>> AsyncAll(
nsTArray<T> aItems,
std::function<
RefPtr<mozilla::MozPromise<bool, nsresult, true>>(const T& item)>
aPredicate) { auto promise =
mozilla::MakeRefPtr<mozilla::MozPromise<bool, nsresult, true>::Private>(
__func__); auto iterator = mozilla::MakeRefPtr<details::AsyncAllIterator<T>>(
std::move(aItems), aPredicate, promise);
iterator->StartIterating(); return promise;
}
} // namespace fd_async
using fd_async::AsyncAll; using fd_async::AsyncExecute;
} // namespace mozilla::detail
/* * Folder picker invocation
*/
/* * Show a folder picker. * * @param aInitialDir The initial directory. The last-used directory will be * used if left blank. * @return A promise which: * - resolves to true if a file was selected successfully (in which * case mUnicodeFile will be updated); * - resolves to false if the dialog was cancelled by the user; * - is rejected with the associated HRESULT if some error occurred.
*/
RefPtr<mozilla::MozPromise<bool, nsFilePicker::Error, true>>
nsFilePicker::ShowFolderPicker(const nsString& aInitialDir) { namespace fd = ::mozilla::widget::filedialog;
nsTArray<fd::Command> commands = {
fd::SetOptions(FOS_PICKFOLDERS),
fd::SetTitle(mTitle),
};
if (!mOkButtonLabel.IsEmpty()) {
commands.AppendElement(fd::SetOkButtonLabel(mOkButtonLabel));
}
if (!aInitialDir.IsEmpty()) {
commands.AppendElement(fd::SetFolder(aInitialDir));
}
/* * Show a file picker. * * @param aInitialDir The initial directory. The last-used directory will be * used if left blank. * @return A promise which: * - resolves to true if one or more files were selected successfully * (in which case mUnicodeFile and/or mFiles will be updated); * - resolves to false if the dialog was cancelled by the user; * - is rejected with the associated HRESULT if some error occurred.
*/
RefPtr<mozilla::MozPromise<bool, nsFilePicker::Error, true>>
nsFilePicker::ShowFilePicker(const nsString& aInitialDir) {
AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER);
// FOS_OVERWRITEPROMPT: always confirm on overwrite in Save dialogs // FOS_FORCEFILESYSTEM: provide only filesystem-objects, not more exotic // entities like libraries
fos |= FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM;
// Handle add to recent docs settings if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
fos |= FOS_DONTADDTORECENT;
}
case modeOpenMultiple:
fos |= FOS_FILEMUSTEXIST | FOS_ALLOWMULTISELECT; break;
case modeSave:
fos |= FOS_NOREADONLYRETURN; // Don't follow shortcuts when saving a shortcut, this can be used // to trick users (bug 271732) if (IsDefaultPathLink()) {
fos |= FOS_NODEREFERENCELINKS;
} break;
case modeGetFolder:
MOZ_ASSERT(false, "file-picker opened in directory-picker mode"); return NotOk(MOZ_FD_LOCAL_ERROR( "file-picker opened in directory-picker mode", E_INVALIDARG));
}
commands.AppendElement(fd::SetOptions(fos));
}
// initial strings
// title
commands.AppendElement(fd::SetTitle(mTitle));
// default filename if (!mDefaultFilename.IsEmpty()) { // Prevent the shell from expanding environment variables by removing the % // characters that are used to delimit them. // // Note that we do _not_ need to preserve this sanitization for the fallback // case where the file dialog fails. Variable-expansion only occurs in the // file dialog specifically, and not when creating a file directly via other // means.
nsAutoString sanitizedFilename(mDefaultFilename);
sanitizedFilename.ReplaceChar('%', '_');
// default extension to append to new files if (!mDefaultExtension.IsEmpty()) { // We don't want environment variables expanded in the extension either.
nsAutoString sanitizedExtension(mDefaultExtension);
sanitizedExtension.ReplaceChar('%', '_');
// initial location if (!aInitialDir.IsEmpty()) {
commands.AppendElement(fd::SetFolder(aInitialDir));
}
// filter types and the default index if (!mFilterList.IsEmpty()) {
nsTArray<fd::ComDlgFilterSpec> fileTypes; for (autoconst& filter : mFilterList) {
fileTypes.EmplaceBack(filter.title, filter.filter);
}
commands.AppendElement(fd::SetFileTypes(std::move(fileTypes)));
commands.AppendElement(fd::SetFileTypeIndex(mSelectedType));
}
if (MaybeBlockFilePicker(aCallback)) { return NS_OK;
}
// Don't attempt to open a real file-picker in headless mode. if (gfxPlatform::IsHeadless()) { return nsresult::NS_ERROR_NOT_AVAILABLE;
}
nsAutoString initialDir; if (mDisplayDirectory) {
mDisplayDirectory->GetPath(initialDir);
}
// If no display directory, re-use the last one. if (initialDir.IsEmpty()) { // Allocate copy of last used dir.
initialDir = sLastUsedUnicodeDirectory.get();
}
// Clear previous file selections
ClearFiles();
auto promise = mMode == modeGetFolder ? ShowFolderPicker(initialDir)
: ShowFilePicker(initialDir);
if (self->mMode == modeSave) { // Windows does not return resultReplace; we must check whether the // file already exists.
nsCOMPtr<nsIFile> file;
nsresult rv =
NS_NewLocalFile(self->mUnicodeFile, getter_AddRefs(file));
bool flag = false; if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(file->Exists(&flag)) && flag) {
retValue = ResultCode::returnReplace;
}
}
callback->Done(retValue);
},
[callback = RefPtr(aCallback), self = RefPtr{this}](Error const& err) { // The file-dialog process (probably) crashed. Report this fact to the // user, and try to recover with a fallback rather than discarding the // file. // // (Note that at this point, logging of the crash -- and possibly also a // telemetry ping -- has already occurred.)
ResultCode resultCode = ResultCode::returnCancel;
// This does not describe the original error, just the error when trying // to select a fallback location -- no such attempt means no such error.
FallbackResult fallback{nullptr};
if (self->mMode == Mode::modeSave) {
fallback = self->ComputeFallbackSavePath(); // don't set sLastUsedUnicodeDirectory here: the user didn't // actually select anything
}
// Get the file + path
NS_IMETHODIMP
nsBaseWinFilePicker::SetDefaultString(const nsAString& aString) {
mDefaultFilePath = aString;
// First, make sure the file name is not too long.
int32_t nameLength;
int32_t nameIndex = mDefaultFilePath.RFind(u"\\"); if (nameIndex == kNotFound) {
nameIndex = 0;
} else {
nameIndex++;
}
nameLength = mDefaultFilePath.Length() - nameIndex;
mDefaultFilename.Assign(Substring(mDefaultFilePath, nameIndex));
if (nameLength > MAX_PATH) {
int32_t extIndex = mDefaultFilePath.RFind(u"."); if (extIndex == kNotFound) {
extIndex = mDefaultFilePath.Length();
}
// Let's try to shave the needed characters from the name part.
int32_t charsToRemove = nameLength - MAX_PATH; if (extIndex - nameIndex >= charsToRemove) {
mDefaultFilePath.Cut(extIndex - charsToRemove, charsToRemove);
}
}
// Then, we need to replace illegal characters. At this stage, we cannot // replace the backslash as the string might represent a file path.
mDefaultFilePath.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
mDefaultFilename.ReplaceChar(u"" FILE_ILLEGAL_CHARACTERS, u'-');
// The default extension to use for files
NS_IMETHODIMP
nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) {
aExtension = mDefaultExtension; return NS_OK;
}
// Set the filter index
NS_IMETHODIMP
nsFilePicker::GetFilterIndex(int32_t* aFilterIndex) { // Windows' filter index is 1-based, we use a 0-based system.
*aFilterIndex = mSelectedType - 1; return NS_OK;
}
NS_IMETHODIMP
nsFilePicker::SetFilterIndex(int32_t aFilterIndex) { // Windows' filter index is 1-based, we use a 0-based system.
mSelectedType = aFilterIndex + 1; return NS_OK;
}
void nsFilePicker::RememberLastUsedDirectory() { if (IsPrivacyModeEnabled()) { // Don't remember the directory if private browsing was in effect return;
}
nsCOMPtr<nsIFile> file; if (NS_FAILED(NS_NewLocalFile(mUnicodeFile, getter_AddRefs(file)))) {
NS_WARNING("RememberLastUsedDirectory failed to init file path."); return;
}
nsCOMPtr<nsIFile> dir;
nsAutoString newDir; if (NS_FAILED(file->GetParent(getter_AddRefs(dir))) ||
!(mDisplayDirectory = dir) ||
NS_FAILED(mDisplayDirectory->GetPath(newDir)) || newDir.IsEmpty()) {
NS_WARNING("RememberLastUsedDirectory failed to get parent directory."); return;
}
auto nsFilePicker::ComputeFallbackSavePath() const -> FallbackResult { using mozilla::Err;
// we shouldn't even be here if we're not trying to save if (mMode != Mode::modeSave) { return Err(NS_ERROR_FAILURE);
}
// get a fallback download-location
RefPtr<nsIFile> location;
{ // try to query the helper service for the preferred downloads directory
nsresult rv;
nsCOMPtr<nsIExternalHelperAppService> svc =
do_GetService(NS_EXTERNALHELPERAPPSERVICE_CONTRACTID, &rv);
MOZ_TRY(rv);
constexpr staticconstauto EndsWithExtension =
[](nsAString const& path, nsAString const& extension) -> bool {
size_t const len = path.Length();
size_t const extLen = extension.Length(); if (extLen + 2 > len) { // `path` is too short and can't possibly end with `extension`. (Note that // we consider, _e.g._, ".jpg" not to end with the extension "jpg".) returnfalse;
} if (path[len - extLen - 1] == L'.' &&
StringTail(path, extLen) == extension) { returntrue;
} returnfalse;
};
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.