/* -*- 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/. */
constchar* Error::KindName(Error::Kind kind) { switch (kind) { case LocalError: return"LocalError"; case RemoteError: return"RemoteError"; case IPCError: return"IPCError"; default:
MOZ_ASSERT(false); return"";
}
}
// Location from LaunchError. "text" is not guaranteed to be in VALID_STRINGS. // There is no significance to the value of `value`, except that it's not less // than Location::VALID_STRINGS_COUNT.
constexpr Error::Location::Location(mozilla::ipc::LaunchError const& err)
: text(err.FunctionName()), value(0x867'5309) {}
// Visitor to apply commands to the dialog. struct Applicator {
IFileDialog* dialog = nullptr;
HRESULT Visit(Command const& c) { switch (c.type()) { default: case Command::T__None: return E_INVALIDARG;
case Command::TSetOptions: return Apply(c.get_SetOptions()); case Command::TSetTitle: return Apply(c.get_SetTitle()); case Command::TSetOkButtonLabel: return Apply(c.get_SetOkButtonLabel()); case Command::TSetFolder: return Apply(c.get_SetFolder()); case Command::TSetFileName: return Apply(c.get_SetFileName()); case Command::TSetDefaultExtension: return Apply(c.get_SetDefaultExtension()); case Command::TSetFileTypes: return Apply(c.get_SetFileTypes()); case Command::TSetFileTypeIndex: return Apply(c.get_SetFileTypeIndex());
}
}
HRESULT Apply(SetOptions const& c) { return dialog->SetOptions(c.options()); }
HRESULT Apply(SetTitle const& c) { return dialog->SetTitle(c.title().get()); }
HRESULT Apply(SetOkButtonLabel const& c) { return dialog->SetOkButtonLabel(c.label().get());
}
HRESULT Apply(SetFolder const& c) {
RefPtr<IShellItem> folder; if (SUCCEEDED(SHCreateItemFromParsingName(
c.path().get(), nullptr, IID_IShellItem, getter_AddRefs(folder)))) { return dialog->SetFolder(folder);
} // graciously accept that the provided path may have been nonsense return S_OK;
}
HRESULT Apply(SetFileName const& c) { return dialog->SetFileName(c.filename().get());
}
HRESULT Apply(SetDefaultExtension const& c) { return dialog->SetDefaultExtension(c.extension().get());
}
HRESULT Apply(SetFileTypes const& c) {
std::vector<COMDLG_FILTERSPEC> vec; for (autoconst& filter : c.filterList()) {
vec.push_back(
{.pszName = filter.name().get(), .pszSpec = filter.spec().get()});
} return dialog->SetFileTypes(vec.size(), vec.data());
}
HRESULT Apply(SetFileTypeIndex const& c) { return dialog->SetFileTypeIndex(c.index());
}
};
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(MOZ_FD_LOCAL_ERROR("IFileDialog::GetResult: item", E_POINTER));
}
// If the user chose a Win7 Library, resolve to the library's // default save folder.
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)));
// get the folder's file system path
nsAutoString str;
MOZ_ENSURE_HRESULT_OK("GetShellItemPath", GetShellItemPath(item, str)); return str;
}
#undef MOZ_ENSURE_HRESULT_OK
namespace detail { void LogProcessingError(LogModule* aModule, ipc::IProtocol* aCaller,
ipc::HasResultCodes::Result aCode, constchar* aReason) {
LogLevel const level = [&]() { switch (aCode) { case ipc::HasResultCodes::MsgProcessed: // Normal operation. (We probably never actually get this code.) return LogLevel::Verbose;
case ipc::HasResultCodes::MsgDropped: return LogLevel::Verbose;
default: return LogLevel::Error;
}
}();
// Processing errors are sometimes unhelpfully formatted. We can't fix that // directly because the unhelpful formatting has made its way to telemetry // (table `telemetry.socorro_crash`, column `ipc_channel_error`) and is being // aggregated on. :(
nsCString reason(aReason); if (reason.Last() == '\n') {
reason.Truncate(reason.Length() - 1);
}
if (MOZ_LOG_TEST(aModule, level)) { constchar* const side = [&]() { switch (aCaller->GetSide()) { case ipc::ParentSide: return"parent"; case ipc::ChildSide: return"child"; case ipc::UnknownSide: return"unknown side"; default: return"";
}
}();
constchar* const errorStr = [&]() { switch (aCode) { case ipc::HasResultCodes::MsgProcessed: return"Processed"; case ipc::HasResultCodes::MsgDropped: return"Dropped"; case ipc::HasResultCodes::MsgNotKnown: return"NotKnown"; case ipc::HasResultCodes::MsgNotAllowed: return"NotAllowed"; case ipc::HasResultCodes::MsgPayloadError: return"PayloadError"; case ipc::HasResultCodes::MsgProcessingError: return"ProcessingError"; case ipc::HasResultCodes::MsgValueError: return"ValueError"; default: return"";
}
}();
if (level == LogLevel::Error) { // kill the child process... if (aCaller->GetSide() == ipc::ParentSide) { // ... which isn't us
ipc::UtilityProcessManager::GetSingleton()->CleanShutdown(
ipc::SandboxingKind::WINDOWS_FILE_DIALOG);
} else { // ... which (presumably) is us
CrashReporter::AutoRecordAnnotation(
CrashReporter::Annotation::ipc_channel_error, reason);
MOZ_CRASH("IPC error");
}
}
}
// Given a (synchronous) Action returning a Result<T, HRESULT>, perform that // action on a new single-purpose "File Dialog" thread, with COM initialized as // STA. (The thread will be destroyed afterwards.) // // Returns a Promise which will resolve to T (if the action returns Ok) or // reject with an HRESULT (if the action either returns Err or couldn't be // performed). template <typename Res, typename Action, size_t N>
RefPtr<Promise<Res>> SpawnFileDialogThread(constchar (&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");
}
RefPtr<nsIThread> thread;
{
nsresult rv = NS_NewNamedThread("File Dialog", getter_AddRefs(thread),
nullptr, {.isUiThread = true}); if (NS_FAILED(rv)) { return Promise<Res>::CreateAndReject(
MOZ_FD_LOCAL_ERROR("NS_NewNamedThread", (HRESULT)rv), where);
}
} // `thread` is single-purpose, and should not perform any additional work // after `action`. Shut it down after we've dispatched that. auto close_thread_ = MakeScopeExit([&]() { autoconst res = thread->AsyncShutdown();
static_assert(
std::is_same_v<uint32_t, std::underlying_type_t<decltype(res)>>); if (NS_FAILED(res)) {
MOZ_LOG(sLogFileDialog, LogLevel::Warning,
("thread->AsyncShutdown() failed: res=0x%08" PRIX32, static_cast<uint32_t>(res)));
}
});
// our eventual return value
RefPtr promise = MakeRefPtr<typename Promise<Res>::Private>(where);
// alias to reduce indentation depth autoconst dispatch = [&](auto closure) { return thread->DispatchToQueue(
NS_NewRunnableFunction(where, std::move(closure)),
mozilla::EventQueuePriority::Normal);
};
dispatch([thread, promise, where, action = std::move(action)]() { // Like essentially all COM UI components, the file dialog is STA: it must // be associated with a specific thread to create its HWNDs and receive // messages for them. If it's launched from a thread in the multithreaded // apartment (including via implicit MTA), COM will proxy out to the // process's main STA thread, and the file-dialog's modal loop will run // there. // // This of course would completely negate any point in using a separate // thread, since behind the scenes the dialog would still be running on the // process's main thread. In particular, under that arrangement, file // dialogs (and other nested modal loops, like those performed by // `SpinEventLoopUntil`) will resolve in strictly LIFO order, effectively // remaining suspended until all later modal loops resolve. // // To avoid this, we initialize COM as STA, so that it (rather than the main // STA thread) is the file dialog's "home" thread and the IFileDialog's home // apartment.
mozilla::mscom::STARegion staRegion; if (!staRegion) {
MOZ_LOG(sLogFileDialog, LogLevel::Error,
("COM init failed on file dialog thread: hr = %08lx",
staRegion.GetHResult()));
// If this happens in the utility process, crash so we learn about it. // (TODO: replace this with a telemetry ping.) if (!XRE_IsParentProcess()) { // Preserve relevant data on the stack for later analysis.
std::tuple volatile info{staRegion.GetHResult(), hr, at, atq};
MOZ_CRASH("Could not initialize COM STA in utility process");
}
// If this happens in the parent process, don't crash; just fall back to a // nested modal loop. This isn't ideal, but it will probably still work // well enough for the common case, wherein no other modal loops are // active. // // (TODO: replace this with a telemetry ping, too.)
}
// Actually invoke the action and report the result.
Result<Res, Error> val = action(); if (val.isErr()) {
promise->Reject(val.unwrapErr(), where);
} else {
promise->Resolve(val.unwrap(), where);
}
});
return promise;
}
// For F returning `Result<T, E>`, yields the type `T`. template <typename F, typename... Args> using inner_result_of = 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)]() -> 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;
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.