зеркало из https://github.com/mozilla/gecko-dev.git
781 строка
25 KiB
C++
781 строка
25 KiB
C++
/* -*- 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/. */
|
|
|
|
#include "nsFilePicker.h"
|
|
|
|
#include <shlobj.h>
|
|
#include <shlwapi.h>
|
|
#include <cderr.h>
|
|
#include <winerror.h>
|
|
#include <winuser.h>
|
|
#include <utility>
|
|
|
|
#include "mozilla/Assertions.h"
|
|
#include "mozilla/BackgroundHangMonitor.h"
|
|
#include "mozilla/Logging.h"
|
|
#include "mozilla/ipc/UtilityProcessManager.h"
|
|
#include "mozilla/ProfilerLabels.h"
|
|
#include "mozilla/StaticPrefs_widget.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
#include "mozilla/WindowsVersion.h"
|
|
#include "nsCRT.h"
|
|
#include "nsEnumeratorUtils.h"
|
|
#include "nsNetUtil.h"
|
|
#include "nsPIDOMWindow.h"
|
|
#include "nsReadableUtils.h"
|
|
#include "nsString.h"
|
|
#include "nsToolkit.h"
|
|
#include "nsWindow.h"
|
|
#include "WinUtils.h"
|
|
|
|
#include "mozilla/widget/filedialog/WinFileDialogCommands.h"
|
|
#include "mozilla/widget/filedialog/WinFileDialogParent.h"
|
|
|
|
using mozilla::Maybe;
|
|
using mozilla::Result;
|
|
using mozilla::UniquePtr;
|
|
|
|
using namespace mozilla::widget;
|
|
|
|
UniquePtr<char16_t[], nsFilePicker::FreeDeleter>
|
|
nsFilePicker::sLastUsedUnicodeDirectory;
|
|
|
|
using mozilla::LogLevel;
|
|
|
|
#define MAX_EXTENSION_LENGTH 10
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// Helper classes
|
|
|
|
// Manages matching PickerOpen/PickerClosed calls on the parent widget.
|
|
class AutoWidgetPickerState {
|
|
public:
|
|
explicit AutoWidgetPickerState(nsIWidget* aWidget)
|
|
: mWindow(static_cast<nsWindow*>(aWidget)) {
|
|
PickerState(true);
|
|
}
|
|
|
|
AutoWidgetPickerState(AutoWidgetPickerState const&) = delete;
|
|
AutoWidgetPickerState(AutoWidgetPickerState&& that) noexcept = default;
|
|
|
|
~AutoWidgetPickerState() { PickerState(false); }
|
|
|
|
private:
|
|
void PickerState(bool aFlag) {
|
|
if (mWindow) {
|
|
if (aFlag)
|
|
mWindow->PickerOpen();
|
|
else
|
|
mWindow->PickerClosed();
|
|
}
|
|
}
|
|
RefPtr<nsWindow> mWindow;
|
|
};
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// nsIFilePicker
|
|
|
|
nsFilePicker::nsFilePicker() : mSelectedType(1) {}
|
|
|
|
NS_IMPL_ISUPPORTS(nsFilePicker, nsIFilePicker)
|
|
|
|
NS_IMETHODIMP nsFilePicker::Init(mozIDOMWindowProxy* aParent,
|
|
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;
|
|
}
|
|
|
|
nsCOMPtr<nsPIDOMWindowOuter> window = do_QueryInterface(aParent);
|
|
nsIDocShell* docShell = window ? window->GetDocShell() : nullptr;
|
|
mLoadContext = do_QueryInterface(docShell);
|
|
|
|
return nsBaseFilePicker::Init(aParent, aTitle, aMode);
|
|
}
|
|
|
|
namespace mozilla::detail {
|
|
// Boilerplate for remotely showing a file dialog.
|
|
template <typename ActionType,
|
|
typename ReturnType = typename decltype(std::declval<ActionType>()(
|
|
nullptr))::element_type::ResolveValueType>
|
|
static auto ShowRemote(HWND parent, ActionType&& action)
|
|
-> RefPtr<MozPromise<ReturnType, HRESULT, true>> {
|
|
using RetPromise = MozPromise<ReturnType, HRESULT, true>;
|
|
|
|
constexpr static const auto fail = []() {
|
|
return RetPromise::CreateAndReject(E_FAIL, __PRETTY_FUNCTION__);
|
|
};
|
|
|
|
auto mgr = mozilla::ipc::UtilityProcessManager::GetSingleton();
|
|
if (!mgr) {
|
|
MOZ_ASSERT(false);
|
|
return fail();
|
|
}
|
|
|
|
auto wfda = mgr->CreateWinFileDialogActor();
|
|
if (!wfda) {
|
|
return fail();
|
|
}
|
|
|
|
using mozilla::widget::filedialog::sLogFileDialog;
|
|
|
|
// WORKAROUND FOR UNDOCUMENTED BEHAVIOR: `IFileDialog::Show` disables the
|
|
// top-level ancestor of its provided owner-window. If the modal window's
|
|
// container process crashes, it will never get a chance to undo that, so
|
|
// we have to do it manually.
|
|
struct WindowEnabler {
|
|
HWND hwnd;
|
|
BOOL enabled;
|
|
explicit WindowEnabler(HWND hwnd)
|
|
: hwnd(hwnd), enabled(::IsWindowEnabled(hwnd)) {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Info,
|
|
("ShowRemote: HWND %08zX enabled=%u", uintptr_t(hwnd), enabled));
|
|
}
|
|
WindowEnabler(WindowEnabler const&) = default;
|
|
|
|
void restore() const {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Info,
|
|
("ShowRemote: HWND %08zX enabled=%u (restoring to %u)",
|
|
uintptr_t(hwnd), ::IsWindowEnabled(hwnd), enabled));
|
|
::EnableWindow(hwnd, enabled);
|
|
}
|
|
};
|
|
HWND const rootWindow = ::GetAncestor(parent, GA_ROOT);
|
|
|
|
return wfda->Then(
|
|
mozilla::GetMainThreadSerialEventTarget(),
|
|
"nsFilePicker ShowRemote acquire",
|
|
[action = std::forward<ActionType>(action),
|
|
rootWindow](filedialog::ProcessProxy const& p) -> RefPtr<RetPromise> {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Info,
|
|
("nsFilePicker ShowRemote first callback: p = [%p]", p.get()));
|
|
WindowEnabler enabledState(rootWindow);
|
|
|
|
// false positive: not actually redundant
|
|
// NOLINTNEXTLINE(readability-redundant-smartptr-get)
|
|
return action(p.get())
|
|
->Then(
|
|
mozilla::GetMainThreadSerialEventTarget(),
|
|
"nsFilePicker ShowRemote call",
|
|
[p](ReturnType ret) {
|
|
return RetPromise::CreateAndResolve(std::move(ret),
|
|
__PRETTY_FUNCTION__);
|
|
},
|
|
[](mozilla::ipc::ResponseRejectReason error) {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Error,
|
|
("IPC call rejected: %zu", size_t(error)));
|
|
return fail();
|
|
})
|
|
// Unconditionally restore the disabled state.
|
|
->Then(mozilla::GetMainThreadSerialEventTarget(),
|
|
"nsFilePicker ShowRemote cleanup",
|
|
[enabledState](
|
|
typename RetPromise::ResolveOrRejectValue const& val) {
|
|
enabledState.restore();
|
|
return RetPromise::CreateAndResolveOrReject(
|
|
val, "nsFilePicker ShowRemote cleanup fmap");
|
|
});
|
|
},
|
|
[](nsresult error) -> RefPtr<RetPromise> {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Error,
|
|
("could not acquire WinFileDialog: %zu", size_t(error)));
|
|
return fail();
|
|
});
|
|
}
|
|
|
|
// LocalAndOrRemote
|
|
//
|
|
// Pseudo-namespace for the private implementation details of its AsyncExecute()
|
|
// function. Not intended to be instantiated.
|
|
struct LocalAndOrRemote {
|
|
LocalAndOrRemote() = delete;
|
|
|
|
private:
|
|
// Helper for generically copying ordinary types and nsTArray (which lacks a
|
|
// copy constructor) in the same breath.
|
|
//
|
|
// N.B.: This function is ultimately the reason for LocalAndOrRemote existing
|
|
// (rather than AsyncExecute() being a free function at namespace scope).
|
|
// While this is notionally an implementation detail of AsyncExecute(), it
|
|
// can't be confined to a local struct therein because local structs can't
|
|
// have template member functions [temp.mem/2].
|
|
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 { Local, Remote, RemoteWithFallback };
|
|
|
|
// 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 Local;
|
|
case 2:
|
|
return Remote;
|
|
case 1:
|
|
return RemoteWithFallback;
|
|
|
|
default:
|
|
#ifdef NIGHTLY_BUILD
|
|
// on Nightly builds, fall back to local on failure
|
|
return RemoteWithFallback;
|
|
#else
|
|
// on release and beta, remain local-only for now
|
|
return Local;
|
|
#endif
|
|
}
|
|
};
|
|
|
|
public:
|
|
// Invoke either or both of a "do locally" and "do remotely" function with the
|
|
// provided arguments, depending on the relevant preference-value and whether
|
|
// or not the remote version fails.
|
|
//
|
|
// Both functions must be asynchronous, returning a `RefPtr<MozPromise<...>>`.
|
|
// "Failure" is defined as the promise being rejected.
|
|
template <typename Fn1, typename Fn2, typename... Args>
|
|
static auto AsyncExecute(Fn1 local, Fn2 remote, Args const&... args)
|
|
-> std::invoke_result_t<Fn1, Args...> {
|
|
static_assert(std::is_same_v<std::invoke_result_t<Fn1, Args...>,
|
|
std::invoke_result_t<Fn2, Args...>>);
|
|
using PromiseT = typename std::invoke_result_t<Fn1, Args...>::element_type;
|
|
|
|
constexpr static char kFunctionName[] = "LocalAndOrRemote::AsyncExecute";
|
|
|
|
switch (GetStrategy()) {
|
|
case Local:
|
|
return local(args...);
|
|
|
|
case Remote:
|
|
return remote(args...);
|
|
|
|
case RemoteWithFallback:
|
|
return remote(args...)->Then(
|
|
NS_GetCurrentThread(), kFunctionName,
|
|
[](typename PromiseT::ResolveValueType result) -> RefPtr<PromiseT> {
|
|
// success; stop here
|
|
return PromiseT::CreateAndResolve(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 PromiseT::RejectValueType _err) mutable
|
|
-> RefPtr<PromiseT> {
|
|
// failure; retry locally
|
|
return std::apply(local, std::move(tuple));
|
|
});
|
|
}
|
|
}
|
|
};
|
|
|
|
} // namespace mozilla::detail
|
|
|
|
/* static */
|
|
nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerRemote(
|
|
HWND parent, filedialog::FileDialogType type,
|
|
nsTArray<filedialog::Command> const& commands) {
|
|
using mozilla::widget::filedialog::sLogFileDialog;
|
|
return mozilla::detail::ShowRemote(
|
|
parent, [parent, type, commands = commands.Clone()](
|
|
filedialog::WinFileDialogParent* p) {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Info,
|
|
("%s: p = [%p]", __PRETTY_FUNCTION__, p));
|
|
return p->SendShowFileDialog((uintptr_t)parent, type, commands);
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerRemote(
|
|
HWND parent, nsTArray<filedialog::Command> const& commands) {
|
|
using mozilla::widget::filedialog::sLogFileDialog;
|
|
return mozilla::detail::ShowRemote(
|
|
parent, [parent, commands = commands.Clone()](
|
|
filedialog::WinFileDialogParent* p) {
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Info,
|
|
("%s: p = [%p]", __PRETTY_FUNCTION__, p));
|
|
return p->SendShowFolderDialog((uintptr_t)parent, commands);
|
|
});
|
|
}
|
|
|
|
/* static */
|
|
nsFilePicker::FPPromise<filedialog::Results> nsFilePicker::ShowFilePickerLocal(
|
|
HWND parent, filedialog::FileDialogType type,
|
|
nsTArray<filedialog::Command> const& commands) {
|
|
return filedialog::SpawnFilePicker(parent, type, commands.Clone());
|
|
}
|
|
|
|
/* static */
|
|
nsFilePicker::FPPromise<nsString> nsFilePicker::ShowFolderPickerLocal(
|
|
HWND parent, nsTArray<filedialog::Command> const& commands) {
|
|
return filedialog::SpawnFolderPicker(parent, commands.Clone());
|
|
}
|
|
|
|
/*
|
|
* 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, HRESULT, true>> nsFilePicker::ShowFolderPicker(
|
|
const nsString& aInitialDir) {
|
|
using Promise = mozilla::MozPromise<bool, HRESULT, true>;
|
|
constexpr static auto Ok = [](bool val) {
|
|
return Promise::CreateAndResolve(val, "nsFilePicker::ShowFolderPicker");
|
|
};
|
|
constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
|
|
return Promise::CreateAndReject(val, "nsFilePicker::ShowFolderPicker");
|
|
};
|
|
|
|
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));
|
|
}
|
|
|
|
ScopedRtlShimWindow shim(mParentWidget.get());
|
|
AutoWidgetPickerState awps(mParentWidget);
|
|
|
|
return mozilla::detail::LocalAndOrRemote::AsyncExecute(
|
|
&ShowFolderPickerLocal, &ShowFolderPickerRemote, shim.get(),
|
|
commands)
|
|
->Then(
|
|
NS_GetCurrentThread(), __PRETTY_FUNCTION__,
|
|
[self = RefPtr(this), shim = std::move(shim),
|
|
awps = std::move(awps)](Maybe<nsString> val) {
|
|
if (val) {
|
|
self->mUnicodeFile = val.extract();
|
|
return Ok(true);
|
|
}
|
|
return Ok(false);
|
|
},
|
|
[](HRESULT err) {
|
|
NS_WARNING("ShowFolderPicker failed");
|
|
return NotOk(err);
|
|
});
|
|
}
|
|
|
|
/*
|
|
* File open and save picker invocation
|
|
*/
|
|
|
|
/*
|
|
* 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, HRESULT, true>> nsFilePicker::ShowFilePicker(
|
|
const nsString& aInitialDir) {
|
|
AUTO_PROFILER_LABEL("nsFilePicker::ShowFilePicker", OTHER);
|
|
|
|
using Promise = mozilla::MozPromise<bool, HRESULT, true>;
|
|
constexpr static auto Ok = [](bool val) {
|
|
return Promise::CreateAndResolve(val, "nsFilePicker::ShowFilePicker");
|
|
};
|
|
constexpr static auto NotOk = [](HRESULT val = E_FAIL) {
|
|
return Promise::CreateAndReject(val, "nsFilePicker::ShowFilePicker");
|
|
};
|
|
|
|
namespace fd = ::mozilla::widget::filedialog;
|
|
nsTArray<fd::Command> commands;
|
|
// options
|
|
{
|
|
FILEOPENDIALOGOPTIONS fos = 0;
|
|
fos |= FOS_SHAREAWARE | FOS_OVERWRITEPROMPT | FOS_FORCEFILESYSTEM;
|
|
|
|
// Handle add to recent docs settings
|
|
if (IsPrivacyModeEnabled() || !mAddToRecentDocs) {
|
|
fos |= FOS_DONTADDTORECENT;
|
|
}
|
|
|
|
// mode specific
|
|
switch (mMode) {
|
|
case modeOpen:
|
|
fos |= FOS_FILEMUSTEXIST;
|
|
break;
|
|
|
|
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(E_FAIL);
|
|
}
|
|
|
|
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.
|
|
nsAutoString sanitizedFilename(mDefaultFilename);
|
|
sanitizedFilename.ReplaceChar('%', '_');
|
|
|
|
commands.AppendElement(fd::SetFileName(sanitizedFilename));
|
|
}
|
|
|
|
// 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('%', '_');
|
|
|
|
commands.AppendElement(fd::SetDefaultExtension(sanitizedExtension));
|
|
} else if (IsDefaultPathHtml()) {
|
|
commands.AppendElement(fd::SetDefaultExtension(u"html"_ns));
|
|
}
|
|
|
|
// initial location
|
|
if (!aInitialDir.IsEmpty()) {
|
|
commands.AppendElement(fd::SetFolder(aInitialDir));
|
|
}
|
|
|
|
// filter types and the default index
|
|
if (!mFilterList.IsEmpty()) {
|
|
nsTArray<fd::ComDlgFilterSpec> fileTypes;
|
|
for (auto const& filter : mFilterList) {
|
|
fileTypes.EmplaceBack(filter.title, filter.filter);
|
|
}
|
|
commands.AppendElement(fd::SetFileTypes(std::move(fileTypes)));
|
|
commands.AppendElement(fd::SetFileTypeIndex(mSelectedType));
|
|
}
|
|
|
|
ScopedRtlShimWindow shim(mParentWidget.get());
|
|
AutoWidgetPickerState awps(mParentWidget);
|
|
|
|
mozilla::BackgroundHangMonitor().NotifyWait();
|
|
auto type = mMode == modeSave ? FileDialogType::Save : FileDialogType::Open;
|
|
|
|
auto promise = mozilla::detail::LocalAndOrRemote::AsyncExecute(
|
|
&ShowFilePickerLocal, &ShowFilePickerRemote, shim.get(), type, commands);
|
|
|
|
return promise->Then(
|
|
mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
|
|
[self = RefPtr(this), mode = mMode, shim = std::move(shim),
|
|
awps = std::move(awps)](Maybe<Results> res_opt) {
|
|
if (!res_opt) {
|
|
return Ok(false);
|
|
}
|
|
auto result = res_opt.extract();
|
|
|
|
// Remember what filter type the user selected
|
|
self->mSelectedType = int32_t(result.selectedFileTypeIndex());
|
|
|
|
auto const& paths = result.paths();
|
|
|
|
// single selection
|
|
if (mode != modeOpenMultiple) {
|
|
if (!paths.IsEmpty()) {
|
|
MOZ_ASSERT(paths.Length() == 1);
|
|
self->mUnicodeFile = paths[0];
|
|
return Ok(true);
|
|
}
|
|
return Ok(false);
|
|
}
|
|
|
|
// multiple selection
|
|
for (auto const& str : paths) {
|
|
nsCOMPtr<nsIFile> file;
|
|
if (NS_SUCCEEDED(NS_NewLocalFile(str, false, getter_AddRefs(file)))) {
|
|
self->mFiles.AppendObject(file);
|
|
}
|
|
}
|
|
|
|
return Ok(true);
|
|
},
|
|
[](HRESULT err) {
|
|
NS_WARNING("ShowFilePicker failed");
|
|
return NotOk(err);
|
|
});
|
|
}
|
|
|
|
///////////////////////////////////////////////////////////////////////////////
|
|
// nsIFilePicker impl.
|
|
|
|
nsresult nsFilePicker::Open(nsIFilePickerShownCallback* aCallback) {
|
|
NS_ENSURE_ARG_POINTER(aCallback);
|
|
|
|
// 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
|
|
mUnicodeFile.Truncate();
|
|
mFiles.Clear();
|
|
|
|
auto promise = mMode == modeGetFolder ? ShowFolderPicker(initialDir)
|
|
: ShowFilePicker(initialDir);
|
|
|
|
auto p2 = promise->Then(
|
|
mozilla::GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
|
|
[self = RefPtr(this),
|
|
callback = RefPtr(aCallback)](bool selectionMade) -> void {
|
|
if (!selectionMade) {
|
|
callback->Done(ResultCode::returnCancel);
|
|
return;
|
|
}
|
|
|
|
self->RememberLastUsedDirectory();
|
|
|
|
nsIFilePicker::ResultCode retValue = ResultCode::returnOK;
|
|
|
|
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, false, 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)](HRESULT err) {
|
|
using mozilla::widget::filedialog::sLogFileDialog;
|
|
MOZ_LOG(sLogFileDialog, LogLevel::Error,
|
|
("nsFilePicker: Show failed with hr=0x%08lX", err));
|
|
callback->Done(ResultCode::returnCancel);
|
|
});
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult nsFilePicker::Show(nsIFilePicker::ResultCode* aReturnVal) {
|
|
return NS_ERROR_NOT_IMPLEMENTED;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFilePicker::GetFile(nsIFile** aFile) {
|
|
NS_ENSURE_ARG_POINTER(aFile);
|
|
*aFile = nullptr;
|
|
|
|
if (mUnicodeFile.IsEmpty()) return NS_OK;
|
|
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = NS_NewLocalFile(mUnicodeFile, false, getter_AddRefs(file));
|
|
if (NS_FAILED(rv)) {
|
|
return rv;
|
|
}
|
|
|
|
file.forget(aFile);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFilePicker::GetFileURL(nsIURI** aFileURL) {
|
|
*aFileURL = nullptr;
|
|
nsCOMPtr<nsIFile> file;
|
|
nsresult rv = GetFile(getter_AddRefs(file));
|
|
if (!file) return rv;
|
|
|
|
return NS_NewFileURI(aFileURL, file);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFilePicker::GetFiles(nsISimpleEnumerator** aFiles) {
|
|
NS_ENSURE_ARG_POINTER(aFiles);
|
|
return NS_NewArrayEnumerator(aFiles, mFiles, NS_GET_IID(nsIFile));
|
|
}
|
|
|
|
// 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'-');
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseWinFilePicker::GetDefaultString(nsAString& aString) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
|
|
// The default extension to use for files
|
|
NS_IMETHODIMP
|
|
nsBaseWinFilePicker::GetDefaultExtension(nsAString& aExtension) {
|
|
aExtension = mDefaultExtension;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsBaseWinFilePicker::SetDefaultExtension(const nsAString& aExtension) {
|
|
mDefaultExtension = aExtension;
|
|
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::InitNative(nsIWidget* aParent, const nsAString& aTitle) {
|
|
mParentWidget = aParent;
|
|
mTitle.Assign(aTitle);
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsFilePicker::AppendFilter(const nsAString& aTitle, const nsAString& aFilter) {
|
|
nsString sanitizedFilter(aFilter);
|
|
sanitizedFilter.ReplaceChar('%', '_');
|
|
|
|
if (sanitizedFilter == u"..apps"_ns) {
|
|
sanitizedFilter = u"*.exe;*.com"_ns;
|
|
} else {
|
|
sanitizedFilter.StripWhitespace();
|
|
if (sanitizedFilter == u"*"_ns) {
|
|
sanitizedFilter = u"*.*"_ns;
|
|
}
|
|
}
|
|
mFilterList.AppendElement(
|
|
Filter{.title = nsString(aTitle), .filter = std::move(sanitizedFilter)});
|
|
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, false, 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;
|
|
}
|
|
|
|
sLastUsedUnicodeDirectory.reset(ToNewUnicode(newDir));
|
|
}
|
|
|
|
bool nsFilePicker::IsPrivacyModeEnabled() {
|
|
return mLoadContext && mLoadContext->UsePrivateBrowsing();
|
|
}
|
|
|
|
bool nsFilePicker::IsDefaultPathLink() {
|
|
NS_ConvertUTF16toUTF8 ext(mDefaultFilePath);
|
|
ext.Trim(" .", false, true); // watch out for trailing space and dots
|
|
ToLowerCase(ext);
|
|
return StringEndsWith(ext, ".lnk"_ns) || StringEndsWith(ext, ".pif"_ns) ||
|
|
StringEndsWith(ext, ".url"_ns);
|
|
}
|
|
|
|
bool nsFilePicker::IsDefaultPathHtml() {
|
|
int32_t extIndex = mDefaultFilePath.RFind(u".");
|
|
if (extIndex >= 0) {
|
|
nsAutoString ext;
|
|
mDefaultFilePath.Right(ext, mDefaultFilePath.Length() - extIndex);
|
|
if (ext.LowerCaseEqualsLiteral(".htm") ||
|
|
ext.LowerCaseEqualsLiteral(".html") ||
|
|
ext.LowerCaseEqualsLiteral(".shtml"))
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|