Source code
Revision control
Copy as Markdown
Other Tools
/* -*- 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
#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 <shobjidl.h>
struct IFileDialog;
struct IFileOpenDialog;
namespace mozilla::ipc {
class LaunchError;
}
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,
};
constexpr static const StaticString kInvalidValue{
"<bad filedialog::Error::Location?>"};
// "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<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 (!(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 <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>, 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<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, 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, Error> 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<Promise<Maybe<Results>>> SpawnFilePicker(HWND parent,
FileDialogType type,
nsTArray<Command> commands);
// Show a folder-picker on another thread in the current process.
RefPtr<Promise<Maybe<nsString>>> SpawnFolderPicker(HWND parent,
nsTArray<Command> commands);
} // namespace mozilla::widget::filedialog
namespace IPC {
template <>
struct ParamTraits<mozilla::widget::filedialog::FileDialogType>
: public ContiguousEnumSerializerInclusive<
mozilla::widget::filedialog::FileDialogType,
mozilla::widget::filedialog::FileDialogType::Open,
mozilla::widget::filedialog::FileDialogType::Save> {};
} // namespace IPC
#endif // widget_windows_filedialog_WinFileDialogCommands_h__