diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-06-12 05:35:29 +0000 |
commit | 59203c63bb777a3bacec32fb8830fba33540e809 (patch) | |
tree | 58298e711c0ff0575818c30485b44a2f21bf28a0 /widget/windows/filedialog | |
parent | Adding upstream version 126.0.1. (diff) | |
download | firefox-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.ipdl | 17 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogChild.cpp | 60 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogCommands.cpp | 111 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogCommands.h | 186 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogCommandsDefn.ipdlh | 13 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogParent.cpp | 80 | ||||
-rw-r--r-- | widget/windows/filedialog/WinFileDialogParent.h | 15 |
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(); |