summaryrefslogtreecommitdiffstats
path: root/widget/windows/filedialog
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-06-12 05:35:29 +0000
commit59203c63bb777a3bacec32fb8830fba33540e809 (patch)
tree58298e711c0ff0575818c30485b44a2f21bf28a0 /widget/windows/filedialog
parentAdding upstream version 126.0.1. (diff)
downloadfirefox-59203c63bb777a3bacec32fb8830fba33540e809.tar.xz
firefox-59203c63bb777a3bacec32fb8830fba33540e809.zip
Adding upstream version 127.0.upstream/127.0
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'widget/windows/filedialog')
-rw-r--r--widget/windows/filedialog/PWinFileDialog.ipdl17
-rw-r--r--widget/windows/filedialog/WinFileDialogChild.cpp60
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.cpp111
-rw-r--r--widget/windows/filedialog/WinFileDialogCommands.h186
-rw-r--r--widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh13
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.cpp80
-rw-r--r--widget/windows/filedialog/WinFileDialogParent.h15
7 files changed, 395 insertions, 87 deletions
diff --git a/widget/windows/filedialog/PWinFileDialog.ipdl b/widget/windows/filedialog/PWinFileDialog.ipdl
index 812db7e103..b61bfc0432 100644
--- a/widget/windows/filedialog/PWinFileDialog.ipdl
+++ b/widget/windows/filedialog/PWinFileDialog.ipdl
@@ -12,6 +12,16 @@ namespace mozilla {
namespace widget {
namespace filedialog {
+union FileResult {
+ Results?;
+ RemoteError;
+};
+
+union FolderResult {
+ nsString?;
+ RemoteError;
+};
+
[ChildProc=Utility]
protocol PWinFileDialog {
@@ -19,12 +29,13 @@ child:
// Exactly one Show function should be called per instance. Further calls will
// result in IPC failure.
//
- // Each will return `Nothing` iff the operation was canceled by the user.
+ // Each will return `Ok(Nothing)` if the operation was canceled by the user,
+ // or `Err(...)` if the operation actually failed.
async ShowFileDialog(WindowsHandle parentHwnd, FileDialogType type, Command[] commands)
- returns (Results? results);
+ returns (FileResult result);
async ShowFolderDialog(WindowsHandle parentHwnd, Command[] commands)
- returns (nsString? path);
+ returns (FolderResult result);
};
} // namespace filedialog
diff --git a/widget/windows/filedialog/WinFileDialogChild.cpp b/widget/windows/filedialog/WinFileDialogChild.cpp
index a41018ff0e..ddb972534a 100644
--- a/widget/windows/filedialog/WinFileDialogChild.cpp
+++ b/widget/windows/filedialog/WinFileDialogChild.cpp
@@ -46,34 +46,23 @@ WinFileDialogChild::IPCResult WinFileDialogChild::MakeIpcFailure(
return IPC_FAIL(this, what);
}
-#define MOZ_IPC_ENSURE_HRESULT_OK(hr, what) \
- do { \
- MOZ_LOG(sLogFileDialog, LogLevel::Verbose, \
- ("checking HRESULT for %s", what)); \
- HRESULT const _hr_ = (hr); \
- if (FAILED(_hr_)) { \
- MOZ_LOG(sLogFileDialog, LogLevel::Error, \
- ("HRESULT %8lX while %s", (hr), (what))); \
- return MakeIpcFailure(_hr_, (what)); \
- } \
- } while (0)
-
WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFileDialog(
uintptr_t parentHwnd, FileDialogType type, nsTArray<Command> commands,
FileResolver&& resolver) {
MOZ_ABORT_IF_ALREADY_USED();
- SpawnFilePicker(HWND(parentHwnd), type, std::move(commands))
- ->Then(
- GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
- [resolver = std::move(resolver)](Maybe<Results> const& res) {
- resolver(res);
- },
- [self = RefPtr(this)](HRESULT hr) {
- // this doesn't need to be returned anywhere; it'll crash the
- // process as a side effect of construction
- self->MakeIpcFailure(hr, "SpawnFilePicker");
- });
+ auto promise = SpawnFilePicker(HWND(parentHwnd), type, std::move(commands));
+ using RRV = std::decay_t<decltype(*promise)>::ResolveOrRejectValue;
+
+ promise->Then(GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [resolver = std::move(resolver)](RRV&& val) -> void {
+ if (val.IsResolve()) {
+ resolver(val.ResolveValue());
+ } else {
+ auto err = val.RejectValue();
+ resolver(RemoteError(err.where.Serialize(), err.why));
+ }
+ });
return IPC_OK();
}
@@ -83,23 +72,22 @@ WinFileDialogChild::IPCResult WinFileDialogChild::RecvShowFolderDialog(
FolderResolver&& resolver) {
MOZ_ABORT_IF_ALREADY_USED();
- SpawnFolderPicker(HWND(parentHwnd), std::move(commands))
- ->Then(
- GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
- [resolver = std::move(resolver)](Maybe<nsString> const& res) {
- resolver(res);
- },
- [self = RefPtr(this), resolver](HRESULT hr) {
- // this doesn't need to be returned anywhere; it'll crash the
- // process as a side effect of construction
- self->MakeIpcFailure(hr, "SpawnFolderPicker");
- });
+ auto promise = SpawnFolderPicker(HWND(parentHwnd), std::move(commands));
+ using RRV = std::decay_t<decltype(*promise)>::ResolveOrRejectValue;
+
+ promise->Then(GetMainThreadSerialEventTarget(), __PRETTY_FUNCTION__,
+ [resolver = std::move(resolver)](RRV&& val) -> void {
+ if (val.IsResolve()) {
+ resolver(val.ResolveValue());
+ } else {
+ auto err = val.RejectValue();
+ resolver(RemoteError(err.where.Serialize(), err.why));
+ }
+ });
return IPC_OK();
}
-#undef MOZ_IPC_ENSURE_HRESULT_OK
-
void WinFileDialogChild::ProcessingError(Result aCode, const char* aReason) {
detail::LogProcessingError(sLogFileDialog, this, aCode, aReason);
}
diff --git a/widget/windows/filedialog/WinFileDialogCommands.cpp b/widget/windows/filedialog/WinFileDialogCommands.cpp
index 838856893d..75abbad343 100644
--- a/widget/windows/filedialog/WinFileDialogCommands.cpp
+++ b/widget/windows/filedialog/WinFileDialogCommands.cpp
@@ -22,6 +22,20 @@
namespace mozilla::widget::filedialog {
+const char* Error::KindName(Error::Kind kind) {
+ switch (kind) {
+ case LocalError:
+ return "LocalError";
+ case RemoteError:
+ return "RemoteError";
+ case IPCError:
+ return "IPCError";
+ default:
+ MOZ_ASSERT(false);
+ return "<bad value>";
+ }
+}
+
// Visitor to apply commands to the dialog.
struct Applicator {
IFileDialog* dialog = nullptr;
@@ -98,13 +112,15 @@ static HRESULT GetShellItemPath(IShellItem* aItem, nsString& aResultString) {
}
} // namespace
-#define MOZ_ENSURE_HRESULT_OK(call_) \
- do { \
- HRESULT const _tmp_hr_ = (call_); \
- if (FAILED(_tmp_hr_)) return Err(_tmp_hr_); \
+#define MOZ_ENSURE_HRESULT_OK(where, call_) \
+ do { \
+ HRESULT const _tmp_hr_ = (call_); \
+ if (FAILED(_tmp_hr_)) { \
+ return mozilla::Err(MOZ_FD_LOCAL_ERROR(where, _tmp_hr_)); \
+ } \
} while (0)
-mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(
+mozilla::Result<RefPtr<IFileDialog>, Error> MakeFileDialog(
FileDialogType type) {
RefPtr<IFileDialog> dialog;
@@ -112,43 +128,45 @@ mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(
: CLSID_FileSaveDialog;
HRESULT const hr = CoCreateInstance(clsid, nullptr, CLSCTX_INPROC_SERVER,
IID_IFileDialog, getter_AddRefs(dialog));
- MOZ_ENSURE_HRESULT_OK(hr);
+ // more properly: "CoCreateInstance(CLSID_...)", but this suffices
+ MOZ_ENSURE_HRESULT_OK("MakeFileDialog", hr);
return std::move(dialog);
}
-HRESULT ApplyCommands(::IFileDialog* dialog,
- nsTArray<Command> const& commands) {
+mozilla::Result<Ok, Error> ApplyCommands(::IFileDialog* dialog,
+ nsTArray<Command> const& commands) {
Applicator applicator{.dialog = dialog};
for (auto const& cmd : commands) {
HRESULT const hr = applicator.Visit(cmd);
- if (FAILED(hr)) {
- return hr;
- }
+ MOZ_ENSURE_HRESULT_OK("ApplyCommands", hr);
}
- return S_OK;
+ return Ok{};
}
-mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) {
+mozilla::Result<Results, Error> GetFileResults(::IFileDialog* dialog) {
FILEOPENDIALOGOPTIONS fos;
- MOZ_ENSURE_HRESULT_OK(dialog->GetOptions(&fos));
+ MOZ_ENSURE_HRESULT_OK("IFileDialog::GetOptions", dialog->GetOptions(&fos));
using widget::WinUtils;
// Extract which filter type the user selected
UINT index;
- MOZ_ENSURE_HRESULT_OK(dialog->GetFileTypeIndex(&index));
+ MOZ_ENSURE_HRESULT_OK("IFileDialog::GetFileTypeIndex",
+ dialog->GetFileTypeIndex(&index));
// single selection
if ((fos & FOS_ALLOWMULTISELECT) == 0) {
RefPtr<IShellItem> item;
- MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item)));
+ MOZ_ENSURE_HRESULT_OK("IFileDialog::GetResult",
+ dialog->GetResult(getter_AddRefs(item)));
if (!item) {
- return Err(E_FAIL);
+ return Err(MOZ_FD_LOCAL_ERROR("IFileDialog::GetResult: item", E_POINTER));
}
nsAutoString path;
- MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, path));
+ MOZ_ENSURE_HRESULT_OK("GetFileResults: GetShellItemPath (1)",
+ GetShellItemPath(item, path));
return Results({path}, index);
}
@@ -158,25 +176,29 @@ mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) {
dialog->QueryInterface(IID_IFileOpenDialog, getter_AddRefs(openDlg));
if (!openDlg) {
MOZ_ASSERT(false, "a file-save dialog was given FOS_ALLOWMULTISELECT?");
- return Err(E_UNEXPECTED);
+ return Err(MOZ_FD_LOCAL_ERROR("Save + FOS_ALLOWMULTISELECT", E_UNEXPECTED));
}
RefPtr<IShellItemArray> items;
- MOZ_ENSURE_HRESULT_OK(openDlg->GetResults(getter_AddRefs(items)));
+ MOZ_ENSURE_HRESULT_OK("IFileOpenDialog::GetResults",
+ openDlg->GetResults(getter_AddRefs(items)));
if (!items) {
- return Err(E_FAIL);
+ return Err(
+ MOZ_FD_LOCAL_ERROR("IFileOpenDialog::GetResults: items", E_POINTER));
}
nsTArray<nsString> paths;
DWORD count = 0;
- MOZ_ENSURE_HRESULT_OK(items->GetCount(&count));
+ MOZ_ENSURE_HRESULT_OK("IShellItemArray::GetCount", items->GetCount(&count));
for (DWORD idx = 0; idx < count; idx++) {
RefPtr<IShellItem> item;
- MOZ_ENSURE_HRESULT_OK(items->GetItemAt(idx, getter_AddRefs(item)));
+ MOZ_ENSURE_HRESULT_OK("IShellItemArray::GetItemAt",
+ items->GetItemAt(idx, getter_AddRefs(item)));
nsAutoString str;
- MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str));
+ MOZ_ENSURE_HRESULT_OK("GetFileResults: GetShellItemPath (2)",
+ GetShellItemPath(item, str));
paths.EmplaceBack(str);
}
@@ -184,15 +206,17 @@ mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog* dialog) {
return Results(std::move(paths), std::move(index));
}
-mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) {
+mozilla::Result<nsString, Error> GetFolderResults(::IFileDialog* dialog) {
RefPtr<IShellItem> item;
- MOZ_ENSURE_HRESULT_OK(dialog->GetResult(getter_AddRefs(item)));
+ MOZ_ENSURE_HRESULT_OK("IFileDialog::GetResult",
+ dialog->GetResult(getter_AddRefs(item)));
+
if (!item) {
// shouldn't happen -- probably a precondition failure on our part, but
// might be due to misbehaving shell extensions?
MOZ_ASSERT(false,
"unexpected lack of item: was `Show`'s return value checked?");
- return Err(E_FAIL);
+ return Err(MOZ_FD_LOCAL_ERROR("IFileDialog::GetResult: item", E_POINTER));
}
// If the user chose a Win7 Library, resolve to the library's
@@ -200,6 +224,7 @@ mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) {
RefPtr<IShellLibrary> shellLib;
RefPtr<IShellItem> folderPath;
MOZ_ENSURE_HRESULT_OK(
+ "CoCreateInstance(CLSID_ShellLibrary)",
CoCreateInstance(CLSID_ShellLibrary, nullptr, CLSCTX_INPROC_SERVER,
IID_IShellLibrary, getter_AddRefs(shellLib)));
@@ -211,7 +236,7 @@ mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog* dialog) {
// get the folder's file system path
nsAutoString str;
- MOZ_ENSURE_HRESULT_OK(GetShellItemPath(item, str));
+ MOZ_ENSURE_HRESULT_OK("GetShellItemPath", GetShellItemPath(item, str));
return str;
}
@@ -312,12 +337,22 @@ void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller,
template <typename Res, typename Action, size_t N>
RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N],
Action action) {
+ {
+ using ActionRetT = std::invoke_result_t<Action>;
+ using Info = detail::DestructureResult<ActionRetT>;
+
+ MOZ_ASSERT_SAME_TYPE(
+ typename Info::ErrorT, Error,
+ "supplied Action must return Result<T, filedialog::Err>");
+ }
+
RefPtr<nsIThread> thread;
{
nsresult rv = NS_NewNamedThread("File Dialog", getter_AddRefs(thread),
nullptr, {.isUiThread = true});
if (NS_FAILED(rv)) {
- return Promise<Res>::CreateAndReject((HRESULT)rv, where);
+ return Promise<Res>::CreateAndReject(
+ MOZ_FD_LOCAL_ERROR("NS_NewNamedThread", (HRESULT)rv), where);
}
}
// `thread` is single-purpose, and should not perform any additional work
@@ -393,7 +428,7 @@ RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N],
}
// Actually invoke the action and report the result.
- Result<Res, HRESULT> val = action();
+ Result<Res, Error> val = action();
if (val.isErr()) {
promise->Reject(val.unwrapErr(), where);
} else {
@@ -407,16 +442,16 @@ RefPtr<Promise<Res>> SpawnFileDialogThread(const char (&where)[N],
// For F returning `Result<T, E>`, yields the type `T`.
template <typename F, typename... Args>
using inner_result_of =
- typename std::remove_reference_t<decltype(std::declval<F>()(
- std::declval<Args>()...))>::ok_type;
+ typename detail::DestructureResult<std::invoke_result_t<F, Args...>>::OkT;
template <typename ExtractorF,
typename RetT = inner_result_of<ExtractorF, IFileDialog*>>
auto SpawnPickerT(HWND parent, FileDialogType type, ExtractorF&& extractor,
nsTArray<Command> commands) -> RefPtr<Promise<Maybe<RetT>>> {
+ using ActionRetT = Result<Maybe<RetT>, Error>;
+
return detail::SpawnFileDialogThread<Maybe<RetT>>(
- __PRETTY_FUNCTION__,
- [=, commands = std::move(commands)]() -> Result<Maybe<RetT>, HRESULT> {
+ __PRETTY_FUNCTION__, [=, commands = std::move(commands)]() -> ActionRetT {
// On Win10, the picker doesn't support per-monitor DPI, so we create it
// with our context set temporarily to system-dpi-aware.
WinUtils::AutoSystemDpiAware dpiAwareness;
@@ -424,15 +459,13 @@ auto SpawnPickerT(HWND parent, FileDialogType type, ExtractorF&& extractor,
RefPtr<IFileDialog> dialog;
MOZ_TRY_VAR(dialog, MakeFileDialog(type));
- if (HRESULT const rv = ApplyCommands(dialog, commands); FAILED(rv)) {
- return mozilla::Err(rv);
- }
+ MOZ_TRY(ApplyCommands(dialog, commands));
if (HRESULT const rv = dialog->Show(parent); FAILED(rv)) {
if (rv == HRESULT_FROM_WIN32(ERROR_CANCELLED)) {
- return Result<Maybe<RetT>, HRESULT>(Nothing());
+ return ActionRetT{Nothing()};
}
- return mozilla::Err(rv);
+ return mozilla::Err(MOZ_FD_LOCAL_ERROR("IFileDialog::Show", rv));
}
RetT res;
diff --git a/widget/windows/filedialog/WinFileDialogCommands.h b/widget/windows/filedialog/WinFileDialogCommands.h
index ca4561a8f2..800ce832d5 100644
--- a/widget/windows/filedialog/WinFileDialogCommands.h
+++ b/widget/windows/filedialog/WinFileDialogCommands.h
@@ -19,27 +19,200 @@ struct IFileOpenDialog;
namespace mozilla::widget::filedialog {
+namespace detail {
+
+template <typename T, typename E, bool B>
+struct PromiseInfo {
+ using ResolveT = T;
+ using RejectT = E;
+ constexpr static const bool IsExclusive = B;
+ using Promise = MozPromise<T, E, B>;
+};
+
+template <typename P>
+auto DestructurePromiseImpl(P&&) {
+ // Debugging hint: A type in the instantiation chain (here named `P`) was
+ // expected to be a `RefPtr<MozPromise<...>>`, but was some other type.
+ static_assert(false, "expected P = RefPtr<MozPromise< ... >>");
+}
+
+template <typename T, typename E, bool B>
+auto DestructurePromiseImpl(RefPtr<MozPromise<T, E, B>>&&)
+ -> PromiseInfo<T, E, B>;
+
+template <typename P>
+using DestructurePromise =
+ std::decay_t<decltype(DestructurePromiseImpl(std::declval<P>()))>;
+
+template <typename T, typename E>
+struct ResultInfo {
+ using OkT = T;
+ using ErrorT = E;
+};
+
+template <typename R>
+auto DestructureResultImpl(R&&) {
+ // Debugging hint: A type in the instantiation chain (here named `R`) was
+ // expected to be a `mozilla::Result<...>`, but was some other type.
+ static_assert(false, "expected R = mozilla::Result< ... >");
+}
+
+template <typename T, typename E>
+auto DestructureResultImpl(mozilla::Result<T, E>&&) -> ResultInfo<T, E>;
+
+template <typename R>
+using DestructureResult =
+ std::decay_t<decltype(DestructureResultImpl(std::declval<R>()))>;
+
+#define MOZ_ASSERT_SAME_TYPE(T1, T2, ...) \
+ static_assert(std::is_same_v<T1, T2>, ##__VA_ARGS__)
+} // namespace detail
+
extern LazyLogModule sLogFileDialog;
+// Simple struct for reporting errors.
+struct Error {
+ enum Kind {
+ // This error's source was within the local (current) process.
+ LocalError,
+ // This error's source was within the remote host process, and it was
+ // reported via noncatastrophic channels.
+ RemoteError,
+ // This error was reported via the IPC subsystem. (This includes unexpected
+ // remote host-process crashes.)
+ IPCError,
+ };
+
+ // "Enum" denoting error-location. Members are in `VALID_STRINGS`, and have no
+ // name other than their string.
+ //
+ // (Note: under C++20, this could reasonably be replaced with an `nsString`
+ // alongside a check that all constructors are either a) consteval or b) from
+ // IPC.)
+ class Location {
+ uint32_t value;
+ constexpr explicit Location(uint32_t value) : value(value) {}
+
+ // Valid locations for errors. (Indices do not need to remain stable between
+ // releases; but -- where meaningful -- string values themselves should, for
+ // ease of telemetry-aggregation.)
+ constexpr static std::string_view const VALID_STRINGS[] = {
+ "ApplyCommands",
+ "CoCreateInstance(CLSID_ShellLibrary)",
+ "GetFileResults: GetShellItemPath (1)",
+ "GetFileResults: GetShellItemPath (2)",
+ "GetShellItemPath",
+ "IFileDialog::GetFileTypeIndex",
+ "IFileDialog::GetOptions",
+ "IFileDialog::GetResult",
+ "IFileDialog::GetResult: item",
+ "IFileDialog::Show",
+ "IFileOpenDialog::GetResults",
+ "IFileOpenDialog::GetResults: items",
+ "IPC",
+ "IShellItemArray::GetCount",
+ "IShellItemArray::GetItemAt",
+ "MakeFileDialog",
+ "NS_NewNamedThread",
+ "Save + FOS_ALLOWMULTISELECT",
+ "ShowFilePicker",
+ "ShowFolderPicker",
+ "ShowRemote: UtilityProcessManager::GetSingleton",
+ "ShowRemote: invocation of CreateWinFileDialogActor",
+ "UtilityProcessManager::CreateWinFileDialogActor",
+ "internal IPC failure?",
+ };
+ constexpr static size_t VALID_STRINGS_COUNT =
+ std::extent_v<decltype(VALID_STRINGS)>;
+
+ // Prevent duplicates from occurring in VALID_STRINGS by forcing it to be
+ // sorted. (Note that std::is_sorted is not constexpr until C++20.)
+ static_assert(
+ []() {
+ for (size_t i = 0; i + 1 < VALID_STRINGS_COUNT; ++i) {
+ if (!(VALID_STRINGS[i] < VALID_STRINGS[i + 1])) {
+ return false;
+ }
+ }
+ return true;
+ }(),
+ "VALID_STRINGS should be ASCIIbetically sorted");
+
+ public:
+ constexpr uint32_t Serialize() const { return value; }
+ constexpr static Location Deserialize(uint32_t val) {
+ return Location{val};
+ }
+
+ public:
+ constexpr static Location npos() { return Location{~uint32_t(0)}; }
+
+ constexpr bool IsValid() const { return value < VALID_STRINGS_COUNT; }
+
+ constexpr std::string_view ToString() const {
+ return value < VALID_STRINGS_COUNT ? VALID_STRINGS[value]
+ : "<bad filedialog::Error::Location?>";
+ }
+ constexpr static Location FromString(std::string_view str) {
+ for (uint32_t i = 0; i < VALID_STRINGS_COUNT; ++i) {
+ if (str == VALID_STRINGS[i]) return Location{i};
+ }
+ return npos();
+ }
+
+ constexpr char const* c_str() const { return ToString().data(); }
+ };
+
+ // Where and how (run-time) this error occurred.
+ Kind kind;
+ // Where (compile-time) this error occurred.
+ Location where;
+ // Why (run-time) this error occurred. Probably an HRESULT.
+ uint32_t why;
+
+ // `impl Debug for Kind`
+ static const char* KindName(Kind);
+};
+
+// Create a filedialog::Error, confirming at compile-time that the supplied
+// where-string is valid.
+#define MOZ_FD_ERROR(kind_, where_, why_) \
+ ([](HRESULT why_arg_) -> ::mozilla::widget::filedialog::Error { \
+ using Error = ::mozilla::widget::filedialog::Error; \
+ constexpr static const Error::Location loc = \
+ Error::Location::FromString(where_); \
+ static_assert( \
+ loc.IsValid(), \
+ "filedialog::Error: location not found in Error::VALID_STRINGS"); \
+ return Error{ \
+ .kind = Error::kind_, .where = loc, .why = (uint32_t)why_arg_}; \
+ }(why_))
+
+// Create a filedialog::Error of kind LocalError (the usual case).
+#define MOZ_FD_LOCAL_ERROR(where_, why_) MOZ_FD_ERROR(LocalError, where_, why_)
+
+template <typename R>
+using Promise = MozPromise<R, Error, true>;
+
enum class FileDialogType : uint8_t { Open, Save };
// Create a file-dialog of the relevant type. Requires MSCOM to be initialized.
-mozilla::Result<RefPtr<IFileDialog>, HRESULT> MakeFileDialog(FileDialogType);
+mozilla::Result<RefPtr<IFileDialog>, Error> MakeFileDialog(FileDialogType);
// Apply the selected commands to the IFileDialog, in preparation for showing
// it. (The actual showing step is left to the caller.)
-[[nodiscard]] HRESULT ApplyCommands(::IFileDialog*,
- nsTArray<Command> const& commands);
+mozilla::Result<Ok, Error> ApplyCommands(::IFileDialog*,
+ nsTArray<Command> const& commands);
// Extract one or more results from the file-picker dialog.
//
// Requires that Show() has been called and has returned S_OK.
-mozilla::Result<Results, HRESULT> GetFileResults(::IFileDialog*);
+mozilla::Result<Results, Error> GetFileResults(::IFileDialog*);
// Extract the chosen folder from the folder-picker dialog.
//
// Requires that Show() has been called and has returned S_OK.
-mozilla::Result<nsString, HRESULT> GetFolderResults(::IFileDialog*);
+mozilla::Result<nsString, Error> GetFolderResults(::IFileDialog*);
namespace detail {
// Log the error. If it's a notable error, kill the child process.
@@ -48,9 +221,6 @@ void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller,
} // namespace detail
-template <typename R>
-using Promise = MozPromise<R, HRESULT, true>;
-
// Show a file-picker on another thread in the current process.
RefPtr<Promise<Maybe<Results>>> SpawnFilePicker(HWND parent,
FileDialogType type,
diff --git a/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh
index dd85942f24..352b46df17 100644
--- a/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh
+++ b/widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh
@@ -44,6 +44,19 @@ struct Results {
uint32_t selectedFileTypeIndex;
};
+// Homolog of filedialog::Err. (Always Err::Kind::RemoteError, by definition.)
+struct RemoteError {
+ // An enum (`filedialog::Error::Location`) describing the compile-time location
+ // where the error was detected.
+ //
+ // (This value is validated at use-sites; if the child process sends a bad
+ // value, nothing of import will happen.)
+ uint32_t where;
+ // An error code describing the error itself more precisely. Its semantics
+ // depend on the context provided by `where`, but it's probably an HRESULT.
+ uint32_t why;
+};
+
} // namespace filedialog
} // namespace widget
} // namespace mozilla
diff --git a/widget/windows/filedialog/WinFileDialogParent.cpp b/widget/windows/filedialog/WinFileDialogParent.cpp
index 2c256a1506..329c72cc94 100644
--- a/widget/windows/filedialog/WinFileDialogParent.cpp
+++ b/widget/windows/filedialog/WinFileDialogParent.cpp
@@ -54,6 +54,86 @@ PWinFileDialogParent::nsresult WinFileDialogParent::BindToUtilityProcess(
return NS_OK;
}
+// Convert the raw IPC promise-type to a filedialog::Promise.
+template <typename T, typename Ex, size_t N>
+static auto ConvertToFDPromise(
+ const char (&aMethod)[N], // __func__
+ Ex&& extractor,
+ RefPtr<MozPromise<T, mozilla::ipc::ResponseRejectReason, true>>
+ aSrcPromise) {
+ // The extractor must produce a `mozilla::Result<..., Error>` from `T`.
+ using SrcResultInfo = detail::DestructureResult<std::invoke_result_t<Ex, T>>;
+ using ResolveT = typename SrcResultInfo::OkT;
+ static_assert(std::is_same_v<typename SrcResultInfo::ErrorT, Error>,
+ "expected T to be a Result<..., Error>");
+
+ using SrcPromiseT = MozPromise<T, mozilla::ipc::ResponseRejectReason, true>;
+ using DstPromiseT = MozPromise<ResolveT, Error, true>;
+
+ RefPtr<DstPromiseT> ret = aSrcPromise->Then(
+ mozilla::GetCurrentSerialEventTarget(), aMethod,
+
+ [extractor, aMethod](T&& val) {
+ mozilla::Result<ResolveT, Error> result = extractor(std::move(val));
+ if (result.isOk()) {
+ return DstPromiseT::CreateAndResolve(result.unwrap(), aMethod);
+ }
+ return DstPromiseT::CreateAndReject(result.unwrapErr(), aMethod);
+ },
+ [aMethod](typename mozilla::ipc::ResponseRejectReason&& val) {
+ return DstPromiseT::CreateAndReject(
+ MOZ_FD_ERROR(IPCError, "IPC", (uint32_t)val), aMethod);
+ });
+
+ return ret;
+}
+
+template <typename Input, typename Output>
+struct Extractor {
+ template <typename Input::Type tag_, Output const& (Input::*getter_)() const>
+ static auto get() {
+ return [](Input&& res) -> Result<Output, Error> {
+ if (res.type() == tag_) {
+ return (res.*getter_)();
+ }
+ if (res.type() == Input::TRemoteError) {
+ RemoteError err = res.get_RemoteError();
+ return Err(Error{.kind = Error::RemoteError,
+ .where = Error::Location::Deserialize(err.where()),
+ .why = err.why()});
+ }
+ MOZ_ASSERT_UNREACHABLE("internal IPC failure?");
+ return Err(MOZ_FD_ERROR(IPCError, "internal IPC failure?", E_FAIL));
+ };
+ }
+};
+
+[[nodiscard]] RefPtr<WinFileDialogParent::ShowFileDialogPromise>
+WinFileDialogParent::ShowFileDialogImpl(HWND parent, const FileDialogType& type,
+ mozilla::Span<Command const> commands) {
+ auto inner_promise = PWinFileDialogParent::SendShowFileDialog(
+ reinterpret_cast<WindowsHandle>(parent), type, std::move(commands));
+
+ return ConvertToFDPromise(
+ __func__,
+ Extractor<FileResult, Maybe<Results>>::get<
+ FileResult::TMaybeResults, &FileResult::get_MaybeResults>(),
+ std::move(inner_promise));
+}
+
+[[nodiscard]] RefPtr<WinFileDialogParent::ShowFolderDialogPromise>
+WinFileDialogParent::ShowFolderDialogImpl(
+ HWND parent, mozilla::Span<Command const> commands) {
+ auto inner_promise = PWinFileDialogParent::SendShowFolderDialog(
+ reinterpret_cast<WindowsHandle>(parent), std::move(commands));
+
+ return ConvertToFDPromise(
+ __func__,
+ Extractor<FolderResult, Maybe<nsString>>::get<
+ FolderResult::TMaybensString, &FolderResult::get_MaybensString>(),
+ std::move(inner_promise));
+}
+
void WinFileDialogParent::ProcessingError(Result aCode, const char* aReason) {
detail::LogProcessingError(sLogFileDialog, this, aCode, aReason);
}
diff --git a/widget/windows/filedialog/WinFileDialogParent.h b/widget/windows/filedialog/WinFileDialogParent.h
index a2c1197c55..a1eb547444 100644
--- a/widget/windows/filedialog/WinFileDialogParent.h
+++ b/widget/windows/filedialog/WinFileDialogParent.h
@@ -21,11 +21,14 @@
namespace mozilla::widget::filedialog {
-class WinFileDialogParent : public PWinFileDialogParent {
+class WinFileDialogParent final : private PWinFileDialogParent {
public:
using UtilityActorName = ::mozilla::UtilityActorName;
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinFileDialogParent, override);
+ using ShowFileDialogPromise = Promise<Maybe<Results>>;
+ using ShowFolderDialogPromise = Promise<Maybe<nsString>>;
+
public:
WinFileDialogParent();
nsresult BindToUtilityProcess(
@@ -35,6 +38,16 @@ class WinFileDialogParent : public PWinFileDialogParent {
return UtilityActorName::WindowsFileDialog;
}
+ bool CanSend() const { return PWinFileDialogParent::CanSend(); }
+ void Close() { return PWinFileDialogParent::Close(); }
+
+ [[nodiscard]] RefPtr<ShowFileDialogPromise> ShowFileDialogImpl(
+ HWND parent, const FileDialogType& type,
+ mozilla::Span<Command const> commands);
+
+ [[nodiscard]] RefPtr<ShowFolderDialogPromise> ShowFolderDialogImpl(
+ HWND parent, mozilla::Span<Command const> commands);
+
private:
~WinFileDialogParent();