/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ /* vim: set ts=8 sts=2 et sw=2 tw=80: */ /* 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/. */ #ifndef widget_windows_filedialog_WinFileDialogCommands_h__ #define widget_windows_filedialog_WinFileDialogCommands_h__ #include "ipc/EnumSerializer.h" #include "mozilla/Logging.h" #include "mozilla/MozPromise.h" #include "mozilla/ipc/MessageLink.h" #include "mozilla/widget/filedialog/WinFileDialogCommandsDefn.h" // Windows interface types, defined in struct IFileDialog; struct IFileOpenDialog; namespace mozilla::ipc { class LaunchError; } namespace mozilla::widget::filedialog { namespace detail { template struct PromiseInfo { using ResolveT = T; using RejectT = E; constexpr static const bool IsExclusive = B; using Promise = MozPromise; }; template auto DestructurePromiseImpl(P&&) { // Debugging hint: A type in the instantiation chain (here named `P`) was // expected to be a `RefPtr>`, but was some other type. static_assert(false, "expected P = RefPtr>"); } template auto DestructurePromiseImpl(RefPtr>&&) -> PromiseInfo; template using DestructurePromise = std::decay_t()))>; template struct ResultInfo { using OkT = T; using ErrorT = E; }; template 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 auto DestructureResultImpl(mozilla::Result&&) -> ResultInfo; template using DestructureResult = std::decay_t()))>; #define MOZ_ASSERT_SAME_TYPE(T1, T2, ...) \ static_assert(std::is_same_v, ##__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, }; constexpr static const StaticString kInvalidValue{ ""}; // "Enum" denoting error-location. Members are in `VALID_STRINGS`, and have no // name other than their string. // // The "enum" also supports importing "dynamic" strings from ipc::LaunchError. // These strings will not be present in the VALID_STRINGS set and so cannot be // faithfully serialized for IPC -- which is fine, since definitionally if you // get one you won't have an IPC connection to worry about passing it over // anyway. class Location { // The printable form of the location. StaticString text; // The numeric value for the location. // // This will be less than `VALID_STRINGS_COUNT` iff the text of `text` is // equal to that of some string in `VALID_STRINGS`; other values have no // individual significance. uint32_t value; constexpr explicit Location(uint32_t value) : text(value < VALID_STRINGS_COUNT ? StaticString(VALID_STRINGS[value]) : kInvalidValue), 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 nsLiteralCString const VALID_STRINGS[] = { "ApplyCommands"_ns, "CoCreateInstance(CLSID_ShellLibrary)"_ns, "GetFileResults: GetShellItemPath (1)"_ns, "GetFileResults: GetShellItemPath (2)"_ns, "GetShellItemPath"_ns, "IFileDialog::GetFileTypeIndex"_ns, "IFileDialog::GetOptions"_ns, "IFileDialog::GetResult"_ns, "IFileDialog::GetResult: item"_ns, "IFileDialog::Show"_ns, "IFileOpenDialog::GetResults"_ns, "IFileOpenDialog::GetResults: items"_ns, "IPC"_ns, "IShellItemArray::GetCount"_ns, "IShellItemArray::GetItemAt"_ns, "MakeFileDialog"_ns, "NS_NewNamedThread"_ns, "Save + FOS_ALLOWMULTISELECT"_ns, "ShowFilePicker"_ns, "ShowFolderPicker"_ns, "ShowRemote: UtilityProcessManager::GetSingleton"_ns, "ShowRemote: invocation of CreateWinFileDialogActor"_ns, "UtilityProcessManager::CreateWinFileDialogActor"_ns, "file-picker opened in directory-picker mode"_ns, "internal IPC failure?"_ns, }; constexpr static size_t VALID_STRINGS_COUNT = std::extent_v; // 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 (!(std::string_view{VALID_STRINGS[i]} < std::string_view{VALID_STRINGS[i + 1]})) { return false; } } return true; }(), "VALID_STRINGS should be ASCIIbetically sorted"); public: // A LaunchError's carried location won't be in the list and so is not // IPDL-serializable -- but it's still telemetry-safe. constexpr explicit Location(mozilla::ipc::LaunchError const& err); constexpr uint32_t Serialize() const { return value; } constexpr static Location Deserialize(uint32_t val) { MOZ_ASSERT(val < VALID_STRINGS_COUNT); return Location(val); } public: constexpr static Location npos() { return Location(~uint32_t(0)); } // True iff this Location was formed from a string in VALID_STRINGS. constexpr bool IsSerializable() const { return value < VALID_STRINGS_COUNT; } constexpr StaticString ToString() const { return text; } constexpr static Location FromString(StaticString str) { std::string_view val(str.get()); for (uint32_t i = 0; i < VALID_STRINGS_COUNT; ++i) { if (val == VALID_STRINGS[i]) { return Location(i); } } // TODO(C++20): make this `consteval` and fail here, eliminating any need // for a return value for the fallthrough case. return npos(); } constexpr char const* c_str() const { return ToString().get(); } }; // class Error::Location // Where and how (run-time) this error occurred. Kind kind; // Where (compile-time) this error occurred. Location where; // Why (run-time) this error occurred. Usually an HRESULT, but its // interpretation is necessarily dependent on the value of `where`. uint32_t why; // `impl Debug for Kind` static const char* KindName(Kind); // Constructs an error of Kind "LocalError" (since launch-errors are always // considered local). static Error From(mozilla::ipc::LaunchError const&); }; // Create a filedialog::Error from a where-string, confirming at compile-time // that the supplied where-string is validly serializable. #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.IsSerializable(), \ "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 using Promise = MozPromise; enum class FileDialogType : uint8_t { Open, Save }; // Create a file-dialog of the relevant type. Requires MSCOM to be initialized. mozilla::Result, Error> MakeFileDialog(FileDialogType); // Apply the selected commands to the IFileDialog, in preparation for showing // it. (The actual showing step is left to the caller.) mozilla::Result ApplyCommands(::IFileDialog*, nsTArray 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 GetFileResults(::IFileDialog*); // Extract the chosen folder from the folder-picker dialog. // // Requires that Show() has been called and has returned S_OK. mozilla::Result GetFolderResults(::IFileDialog*); namespace detail { // Log the error. If it's a notable error, kill the child process. void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller, ipc::HasResultCodes::Result aCode, const char* aReason); } // namespace detail // Show a file-picker on another thread in the current process. RefPtr>> SpawnFilePicker(HWND parent, FileDialogType type, nsTArray commands); // Show a folder-picker on another thread in the current process. RefPtr>> SpawnFolderPicker(HWND parent, nsTArray commands); } // namespace mozilla::widget::filedialog namespace IPC { template <> struct ParamTraits : public ContiguousEnumSerializerInclusive< mozilla::widget::filedialog::FileDialogType, mozilla::widget::filedialog::FileDialogType::Open, mozilla::widget::filedialog::FileDialogType::Save> {}; } // namespace IPC #endif // widget_windows_filedialog_WinFileDialogCommands_h__