diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-28 14:29:10 +0000 |
commit | 2aa4a82499d4becd2284cdb482213d541b8804dd (patch) | |
tree | b80bf8bf13c3766139fbacc530efd0dd9d54394c /toolkit/xre | |
parent | Initial commit. (diff) | |
download | firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.tar.xz firefox-2aa4a82499d4becd2284cdb482213d541b8804dd.zip |
Adding upstream version 86.0.1.upstream/86.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/xre')
107 files changed, 23830 insertions, 0 deletions
diff --git a/toolkit/xre/AssembleCmdLine.h b/toolkit/xre/AssembleCmdLine.h new file mode 100644 index 0000000000..85ff9a7d48 --- /dev/null +++ b/toolkit/xre/AssembleCmdLine.h @@ -0,0 +1,136 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_AssembleCmdLine_h +#define mozilla_AssembleCmdLine_h + +#if defined(XP_WIN) + +# include "mozilla/UniquePtr.h" + +# include <stdlib.h> +# include <windows.h> + +# ifdef MOZILLA_INTERNAL_API +# include "nsString.h" +# endif // MOZILLA_INTERNAL_API + +namespace mozilla { + +// Out param `aWideCmdLine` must be free()d by the caller. +inline int assembleCmdLine(const char* const* aArgv, wchar_t** aWideCmdLine, + UINT aCodePage) { + const char* const* arg; + char* p; + const char* q; + char* cmdLine; + int cmdLineSize; + int numBackslashes; + int i; + int argNeedQuotes; + + /* + * Find out how large the command line buffer should be. + */ + cmdLineSize = 0; + for (arg = aArgv; *arg; ++arg) { + /* + * \ and " need to be escaped by a \. In the worst case, + * every character is a \ or ", so the string of length + * may double. If we quote an argument, that needs two ". + * Finally, we need a space between arguments, and + * a null byte at the end of command line. + */ + cmdLineSize += 2 * strlen(*arg) /* \ and " need to be escaped */ + + 2 /* we quote every argument */ + + 1; /* space in between, or final null */ + } + p = cmdLine = (char*)malloc(cmdLineSize * sizeof(char)); + if (!p) { + return -1; + } + + for (arg = aArgv; *arg; ++arg) { + /* Add a space to separates the arguments */ + if (arg != aArgv) { + *p++ = ' '; + } + q = *arg; + numBackslashes = 0; + argNeedQuotes = 0; + + /* If the argument contains white space, it needs to be quoted. */ + if (strpbrk(*arg, " \f\n\r\t\v")) { + argNeedQuotes = 1; + } + + if (argNeedQuotes) { + *p++ = '"'; + } + while (*q) { + if (*q == '\\') { + numBackslashes++; + q++; + } else if (*q == '"') { + if (numBackslashes) { + /* + * Double the backslashes since they are followed + * by a quote + */ + for (i = 0; i < 2 * numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; + } + /* To escape the quote */ + *p++ = '\\'; + *p++ = *q++; + } else { + if (numBackslashes) { + /* + * Backslashes are not followed by a quote, so + * don't need to double the backslashes. + */ + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + numBackslashes = 0; + } + *p++ = *q++; + } + } + + /* Now we are at the end of this argument */ + if (numBackslashes) { + /* + * Double the backslashes if we have a quote string + * delimiter at the end. + */ + if (argNeedQuotes) { + numBackslashes *= 2; + } + for (i = 0; i < numBackslashes; i++) { + *p++ = '\\'; + } + } + if (argNeedQuotes) { + *p++ = '"'; + } + } + + *p = '\0'; + int numChars = MultiByteToWideChar(aCodePage, 0, cmdLine, -1, nullptr, 0); + *aWideCmdLine = (wchar_t*)malloc(numChars * sizeof(wchar_t)); + MultiByteToWideChar(aCodePage, 0, cmdLine, -1, *aWideCmdLine, numChars); + free(cmdLine); + return 0; +} + +} // namespace mozilla + +#endif // defined(XP_WIN) + +#endif // mozilla_AssembleCmdLine_h diff --git a/toolkit/xre/AutoSQLiteLifetime.cpp b/toolkit/xre/AutoSQLiteLifetime.cpp new file mode 100644 index 0000000000..4500ec1bfe --- /dev/null +++ b/toolkit/xre/AutoSQLiteLifetime.cpp @@ -0,0 +1,156 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsDebug.h" +#include "AutoSQLiteLifetime.h" +#include "sqlite3.h" + +#ifdef MOZ_MEMORY +# include "mozmemory.h" +# ifdef MOZ_DMD +# include "nsIMemoryReporter.h" +# include "DMD.h" + +namespace mozilla { +namespace storage { +extern mozilla::Atomic<size_t> gSqliteMemoryUsed; +} +} // namespace mozilla + +using mozilla::storage::gSqliteMemoryUsed; + +# endif + +namespace { + +// By default, SQLite tracks the size of all its heap blocks by adding an extra +// 8 bytes at the start of the block to hold the size. Unfortunately, this +// causes a lot of 2^N-sized allocations to be rounded up by jemalloc +// allocator, wasting memory. For example, a request for 1024 bytes has 8 +// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up +// to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.) +// +// So we register jemalloc as the malloc implementation, which avoids this +// 8-byte overhead, and thus a lot of waste. This requires us to provide a +// function, sqliteMemRoundup(), which computes the actual size that will be +// allocated for a given request. SQLite uses this function before all +// allocations, and may be able to use any excess bytes caused by the rounding. +// +// Note: the wrappers for malloc, realloc and moz_malloc_usable_size are +// necessary because the sqlite_mem_methods type signatures differ slightly +// from the standard ones -- they use int instead of size_t. But we don't need +// a wrapper for free. + +# ifdef MOZ_DMD + +// sqlite does its own memory accounting, and we use its numbers in our memory +// reporters. But we don't want sqlite's heap blocks to show up in DMD's +// output as unreported, so we mark them as reported when they're allocated and +// mark them as unreported when they are freed. +// +// In other words, we are marking all sqlite heap blocks as reported even +// though we're not reporting them ourselves. Instead we're trusting that +// sqlite is fully and correctly accounting for all of its heap blocks via its +// own memory accounting. Well, we don't have to trust it entirely, because +// it's easy to keep track (while doing this DMD-specific marking) of exactly +// how much memory SQLite is using. And we can compare that against what +// SQLite reports it is using. + +MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc) +MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree) + +# endif + +static void* sqliteMemMalloc(int n) { + void* p = ::malloc(n); +# ifdef MOZ_DMD + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); +# endif + return p; +} + +static void sqliteMemFree(void* p) { +# ifdef MOZ_DMD + gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); +# endif + ::free(p); +} + +static void* sqliteMemRealloc(void* p, int n) { +# ifdef MOZ_DMD + gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); + void* pnew = ::realloc(p, n); + if (pnew) { + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew); + } else { + // realloc failed; undo the SqliteMallocSizeOfOnFree from above + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); + } + return pnew; +# else + return ::realloc(p, n); +# endif +} + +static int sqliteMemSize(void* p) { return ::moz_malloc_usable_size(p); } + +static int sqliteMemRoundup(int n) { + n = malloc_good_size(n); + + // jemalloc can return blocks of size 2 and 4, but SQLite requires that all + // allocations be 8-aligned. So we round up sub-8 requests to 8. This + // wastes a small amount of memory but is obviously safe. + return n <= 8 ? 8 : n; +} + +static int sqliteMemInit(void* p) { return 0; } + +static void sqliteMemShutdown(void* p) {} + +const sqlite3_mem_methods memMethods = { + &sqliteMemMalloc, &sqliteMemFree, &sqliteMemRealloc, &sqliteMemSize, + &sqliteMemRoundup, &sqliteMemInit, &sqliteMemShutdown, nullptr}; + +} // namespace + +#endif // MOZ_MEMORY + +namespace mozilla { + +AutoSQLiteLifetime::AutoSQLiteLifetime() { + if (++AutoSQLiteLifetime::sSingletonEnforcer != 1) { + MOZ_CRASH("multiple instances of AutoSQLiteLifetime constructed!"); + } + +#ifdef MOZ_MEMORY + sResult = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods); +#else + sResult = SQLITE_OK; +#endif + + if (sResult == SQLITE_OK) { + // TODO (bug 1191405): do not preallocate the connections caches until we + // have figured the impact on our consumers and memory. + sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0); + + // Explicitly initialize sqlite3. Although this is implicitly called by + // various sqlite3 functions (and the sqlite3_open calls in our case), + // the documentation suggests calling this directly. So we do. + sResult = ::sqlite3_initialize(); + } +} + +AutoSQLiteLifetime::~AutoSQLiteLifetime() { + // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but + // there is nothing actionable we can do in that case. + sResult = ::sqlite3_shutdown(); + NS_WARNING_ASSERTION(sResult == SQLITE_OK, + "sqlite3 did not shutdown cleanly."); +} + +int AutoSQLiteLifetime::sSingletonEnforcer = 0; +int AutoSQLiteLifetime::sResult = SQLITE_MISUSE; + +} // namespace mozilla diff --git a/toolkit/xre/AutoSQLiteLifetime.h b/toolkit/xre/AutoSQLiteLifetime.h new file mode 100644 index 0000000000..26f8d976b2 --- /dev/null +++ b/toolkit/xre/AutoSQLiteLifetime.h @@ -0,0 +1,24 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 mozilla_AutoSQLiteLifetime_h +#define mozilla_AutoSQLiteLifetime_h + +namespace mozilla { + +class AutoSQLiteLifetime final { + private: + static int sSingletonEnforcer; + static int sResult; + + public: + AutoSQLiteLifetime(); + ~AutoSQLiteLifetime(); + static int getInitResult() { return AutoSQLiteLifetime::sResult; } +}; + +} // namespace mozilla + +#endif diff --git a/toolkit/xre/Bootstrap.cpp b/toolkit/xre/Bootstrap.cpp new file mode 100644 index 0000000000..9f38cba823 --- /dev/null +++ b/toolkit/xre/Bootstrap.cpp @@ -0,0 +1,122 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/Bootstrap.h" +#include "nsXPCOM.h" + +#include "AutoSQLiteLifetime.h" + +#ifdef MOZ_WIDGET_ANDROID +# ifdef MOZ_PROFILE_GENERATE +extern "C" int __llvm_profile_dump(void); +# endif +#endif + +namespace mozilla { + +class BootstrapImpl final : public Bootstrap { + protected: + AutoSQLiteLifetime mSQLLT; + + virtual void Dispose() override { delete this; } + + public: + BootstrapImpl() = default; + + ~BootstrapImpl() = default; + + virtual void NS_LogInit() override { ::NS_LogInit(); } + + virtual void NS_LogTerm() override { ::NS_LogTerm(); } + + virtual void XRE_TelemetryAccumulate(int aID, uint32_t aSample) override { + ::XRE_TelemetryAccumulate(aID, aSample); + } + + virtual void XRE_StartupTimelineRecord(int aEvent, + mozilla::TimeStamp aWhen) override { + ::XRE_StartupTimelineRecord(aEvent, aWhen); + } + + virtual int XRE_main(int argc, char* argv[], + const BootstrapConfig& aConfig) override { + return ::XRE_main(argc, argv, aConfig); + } + + virtual void XRE_StopLateWriteChecks() override { + ::XRE_StopLateWriteChecks(); + } + + virtual int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData) override { + return ::XRE_XPCShellMain(argc, argv, envp, aShellData); + } + + virtual GeckoProcessType XRE_GetProcessType() override { + return ::XRE_GetProcessType(); + } + + virtual void XRE_SetProcessType(const char* aProcessTypeString) override { + ::XRE_SetProcessType(aProcessTypeString); + } + + virtual nsresult XRE_InitChildProcess( + int argc, char* argv[], const XREChildData* aChildData) override { + return ::XRE_InitChildProcess(argc, argv, aChildData); + } + + virtual void XRE_EnableSameExecutableForContentProc() override { + ::XRE_EnableSameExecutableForContentProc(); + } + +#ifdef MOZ_WIDGET_ANDROID + virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, + const StaticXREAppData& aAppData) override { + ::GeckoStart(aEnv, argv, argc, aAppData); + } + + virtual void XRE_SetAndroidChildFds( + JNIEnv* aEnv, const XRE_AndroidChildFds& aFds) override { + ::XRE_SetAndroidChildFds(aEnv, aFds); + } + +# ifdef MOZ_PROFILE_GENERATE + virtual void XRE_WriteLLVMProfData() override { + __android_log_print(ANDROID_LOG_INFO, "GeckoLibLoad", + "Calling __llvm_profile_dump()"); + __llvm_profile_dump(); + } +# endif +#endif + +#ifdef LIBFUZZER + virtual void XRE_LibFuzzerSetDriver(LibFuzzerDriver aDriver) override { + ::XRE_LibFuzzerSetDriver(aDriver); + } +#endif + +#ifdef MOZ_IPDL_TESTS + virtual int XRE_RunIPDLTest(int argc, char** argv) override { + return ::XRE_RunIPDLTest(argc, argv); + } +#endif + +#ifdef MOZ_ENABLE_FORKSERVER + virtual int XRE_ForkServer(int* argc, char*** argv) override { + return ::XRE_ForkServer(argc, argv); + } +#endif +}; + +extern "C" NS_EXPORT void NS_FROZENCALL +XRE_GetBootstrap(Bootstrap::UniquePtr& b) { + static bool sBootstrapInitialized = false; + MOZ_RELEASE_ASSERT(!sBootstrapInitialized); + + sBootstrapInitialized = true; + b.reset(new BootstrapImpl()); +} + +} // namespace mozilla diff --git a/toolkit/xre/Bootstrap.h b/toolkit/xre/Bootstrap.h new file mode 100644 index 0000000000..85fd868c94 --- /dev/null +++ b/toolkit/xre/Bootstrap.h @@ -0,0 +1,171 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/** + * This file represents the only external interface exposed from libxul. It + * is used by the various stub binaries (nsBrowserApp, xpcshell, + * plugin-container) to initialize XPCOM and start their main loop. + */ + +#ifndef mozilla_Bootstrap_h +#define mozilla_Bootstrap_h + +#include "mozilla/UniquePtr.h" +#include "nscore.h" +#include "nsXULAppAPI.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "jni.h" + +namespace mozilla { +struct StaticXREAppData; +} + +extern "C" NS_EXPORT void GeckoStart(JNIEnv* aEnv, char** argv, int argc, + const mozilla::StaticXREAppData& aAppData); +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandbox { +class BrokerServices; +} +#endif + +namespace mozilla { + +struct StaticXREAppData; + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +namespace sandboxing { +class PermissionsService; +} +#endif + +struct BootstrapConfig { +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + /* Chromium sandbox BrokerServices. */ + sandbox::BrokerServices* sandboxBrokerServices; + sandboxing::PermissionsService* sandboxPermissionsService; +#endif + /* Pointer to static XRE AppData from application.ini.h */ + const StaticXREAppData* appData; + /* When the pointer above is null, points to the (string) path of an + * application.ini file to open and parse. + * When the pointer above is non-null, may indicate the directory where + * application files are, relative to the XRE. */ + const char* appDataPath; +}; + +/** + * This class is virtual abstract so that using it does not require linking + * any symbols. The singleton instance of this class is obtained from the + * exported method XRE_GetBootstrap. + */ +class Bootstrap { + protected: + Bootstrap() {} + + // Because of allocator mismatches, code outside libxul shouldn't delete a + // Bootstrap instance. Use Dispose(). + virtual ~Bootstrap() {} + + /** + * Destroy and deallocate this Bootstrap instance. + */ + virtual void Dispose() = 0; + + /** + * Helper class to use with UniquePtr. + */ + class BootstrapDelete { + public: + constexpr BootstrapDelete() {} + void operator()(Bootstrap* aPtr) const { aPtr->Dispose(); } + }; + + public: + typedef mozilla::UniquePtr<Bootstrap, BootstrapDelete> UniquePtr; + + virtual void NS_LogInit() = 0; + + virtual void NS_LogTerm() = 0; + + virtual void XRE_TelemetryAccumulate(int aID, uint32_t aSample) = 0; + + virtual void XRE_StartupTimelineRecord(int aEvent, + mozilla::TimeStamp aWhen) = 0; + + virtual int XRE_main(int argc, char* argv[], + const BootstrapConfig& aConfig) = 0; + + virtual void XRE_StopLateWriteChecks() = 0; + + virtual int XRE_XPCShellMain(int argc, char** argv, char** envp, + const XREShellData* aShellData) = 0; + + virtual GeckoProcessType XRE_GetProcessType() = 0; + + virtual void XRE_SetProcessType(const char* aProcessTypeString) = 0; + + virtual nsresult XRE_InitChildProcess(int argc, char* argv[], + const XREChildData* aChildData) = 0; + + virtual void XRE_EnableSameExecutableForContentProc() = 0; + +#ifdef MOZ_WIDGET_ANDROID + virtual void GeckoStart(JNIEnv* aEnv, char** argv, int argc, + const StaticXREAppData& aAppData) = 0; + + virtual void XRE_SetAndroidChildFds(JNIEnv* aEnv, + const XRE_AndroidChildFds& fds) = 0; +# ifdef MOZ_PROFILE_GENERATE + virtual void XRE_WriteLLVMProfData() = 0; +# endif +#endif + +#ifdef LIBFUZZER + virtual void XRE_LibFuzzerSetDriver(LibFuzzerDriver aDriver) = 0; +#endif + +#ifdef MOZ_IPDL_TESTS + virtual int XRE_RunIPDLTest(int argc, char** argv) = 0; +#endif + +#ifdef MOZ_ENABLE_FORKSERVER + virtual int XRE_ForkServer(int* argc, char*** argv) = 0; +#endif +}; + +enum class LibLoadingStrategy { + NoReadAhead, + ReadAhead, +}; + +/** + * Creates and returns the singleton instnace of the bootstrap object. + * @param `b` is an outparam. We use a parameter and not a return value + * because MSVC doesn't let us return a c++ class from a function with + * "C" linkage. On failure this will be null. + * @note This function may only be called once and will crash if called again. + */ +#ifdef XPCOM_GLUE +typedef void (*GetBootstrapType)(Bootstrap::UniquePtr&); +Bootstrap::UniquePtr GetBootstrap( + const char* aXPCOMFile = nullptr, + LibLoadingStrategy aLibLoadingStrategy = LibLoadingStrategy::NoReadAhead); +#else +extern "C" NS_EXPORT void NS_FROZENCALL +XRE_GetBootstrap(Bootstrap::UniquePtr& b); + +inline Bootstrap::UniquePtr GetBootstrap(const char* aXPCOMFile = nullptr) { + Bootstrap::UniquePtr bootstrap; + XRE_GetBootstrap(bootstrap); + return bootstrap; +} +#endif + +} // namespace mozilla + +#endif // mozilla_Bootstrap_h diff --git a/toolkit/xre/CmdLineAndEnvUtils.cpp b/toolkit/xre/CmdLineAndEnvUtils.cpp new file mode 100644 index 0000000000..1c0c9dd68a --- /dev/null +++ b/toolkit/xre/CmdLineAndEnvUtils.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsCOMPtr.h" +#include "nsDependentString.h" +#include "prenv.h" + +namespace mozilla { + +// Load the path of a file saved with SaveFileToEnv +already_AddRefed<nsIFile> GetFileFromEnv(const char* name) { + nsresult rv; + nsCOMPtr<nsIFile> file; + +#ifdef XP_WIN + WCHAR path[_MAX_PATH]; + if (!GetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path, + _MAX_PATH)) + return nullptr; + + rv = NS_NewLocalFile(nsDependentString(path), true, getter_AddRefs(file)); + if (NS_FAILED(rv)) return nullptr; + + return file.forget(); +#else + const char* arg = PR_GetEnv(name); + if (!arg || !*arg) { + return nullptr; + } + + rv = NS_NewNativeLocalFile(nsDependentCString(arg), true, + getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return nullptr; + } + + return file.forget(); +#endif +} + +} // namespace mozilla diff --git a/toolkit/xre/CmdLineAndEnvUtils.h b/toolkit/xre/CmdLineAndEnvUtils.h new file mode 100644 index 0000000000..e4120da93a --- /dev/null +++ b/toolkit/xre/CmdLineAndEnvUtils.h @@ -0,0 +1,657 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_CmdLineAndEnvUtils_h +#define mozilla_CmdLineAndEnvUtils_h + +// NB: This code may be used outside of xul and thus must not depend on XPCOM + +#if defined(MOZILLA_INTERNAL_API) +# include "prenv.h" +# include "prprf.h" +# include <string.h> +#elif defined(XP_WIN) +# include <stdlib.h> +#endif + +#if defined(XP_WIN) +# include "mozilla/UniquePtr.h" +# include "mozilla/Vector.h" +# include "mozilla/WinHeaderOnlyUtils.h" + +# include <wchar.h> +# include <windows.h> +#endif // defined(XP_WIN) + +#include "mozilla/MemoryChecking.h" +#include "mozilla/TypedEnumBits.h" + +#include <ctype.h> +#include <stdint.h> + +#ifndef NS_NO_XPCOM +# include "nsIFile.h" +# include "mozilla/AlreadyAddRefed.h" +#endif + +// Undo X11/X.h's definition of None +#undef None + +namespace mozilla { + +enum ArgResult { + ARG_NONE = 0, + ARG_FOUND = 1, + ARG_BAD = 2 // you wanted a param, but there isn't one +}; + +template <typename CharT> +inline void RemoveArg(int& argc, CharT** argv) { + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --argc; +} + +namespace internal { + +template <typename FuncT, typename CharT> +static inline bool strimatch(FuncT aToLowerFn, const CharT* lowerstr, + const CharT* mixedstr) { + while (*lowerstr) { + if (!*mixedstr) return false; // mixedstr is shorter + if (static_cast<CharT>(aToLowerFn(*mixedstr)) != *lowerstr) + return false; // no match + + ++lowerstr; + ++mixedstr; + } + + if (*mixedstr) return false; // lowerstr is shorter + + return true; +} + +} // namespace internal + +inline bool strimatch(const char* lowerstr, const char* mixedstr) { + return internal::strimatch(&tolower, lowerstr, mixedstr); +} + +inline bool strimatch(const wchar_t* lowerstr, const wchar_t* mixedstr) { + return internal::strimatch(&towlower, lowerstr, mixedstr); +} + +const wchar_t kCommandLineDelimiter[] = L" \t"; + +enum class FlagLiteral { osint, safemode }; + +template <typename CharT, FlagLiteral Literal> +inline const CharT* GetLiteral(); + +#define DECLARE_FLAG_LITERAL(enum_name, literal) \ + template <> \ + inline const char* GetLiteral<char, FlagLiteral::enum_name>() { \ + return literal; \ + } \ + \ + template <> \ + inline const wchar_t* GetLiteral<wchar_t, FlagLiteral::enum_name>() { \ + return L##literal; \ + } + +DECLARE_FLAG_LITERAL(osint, "osint") +DECLARE_FLAG_LITERAL(safemode, "safe-mode") + +enum class CheckArgFlag : uint32_t { + None = 0, + // (1 << 0) Used to be CheckOSInt + RemoveArg = (1 << 1) // Remove the argument from the argv array. +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(CheckArgFlag) + +/** + * Check for a commandline flag. If the flag takes a parameter, the + * parameter is returned in aParam. Flags may be in the form -arg or + * --arg (or /arg on win32). + * + * @param aArgc The argc value. + * @param aArgv The original argv. + * @param aArg the parameter to check. Must be lowercase. + * @param aParam if non-null, the -arg <data> will be stored in this pointer. + * This is *not* allocated, but rather a pointer to the argv data. + * @param aFlags Flags @see CheckArgFlag + */ +template <typename CharT> +inline ArgResult CheckArg(int& aArgc, CharT** aArgv, const CharT* aArg, + const CharT** aParam = nullptr, + CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { + MOZ_ASSERT(aArgv && aArg); + + CharT** curarg = aArgv + 1; // skip argv[0] + ArgResult ar = ARG_NONE; + + while (*curarg) { + CharT* arg = curarg[0]; + + if (arg[0] == '-' +#if defined(XP_WIN) + || *arg == '/' +#endif + ) { + ++arg; + + if (*arg == '-') { + ++arg; + } + + if (strimatch(aArg, arg)) { + if (aFlags & CheckArgFlag::RemoveArg) { + RemoveArg(aArgc, curarg); + } else { + ++curarg; + } + + if (!aParam) { + ar = ARG_FOUND; + break; + } + + if (*curarg) { + if (**curarg == '-' +#if defined(XP_WIN) + || **curarg == '/' +#endif + ) { + return ARG_BAD; + } + + *aParam = *curarg; + + if (aFlags & CheckArgFlag::RemoveArg) { + RemoveArg(aArgc, curarg); + } + + ar = ARG_FOUND; + break; + } + + return ARG_BAD; + } + } + + ++curarg; + } + + return ar; +} + +template <typename CharT> +inline void EnsureCommandlineSafe(int& aArgc, CharT** aArgv, + const CharT** aAcceptableArgs) { + // We expect either no -osint, or the full commandline to be: + // app -osint + // followed by one of the arguments listed in aAcceptableArgs, + // followed by one parameter for that arg. + // If this varies, we abort to avoid abuse of other commandline handlers + // from apps that do a poor job escaping links they give to the OS. + + const CharT* osintLit = GetLiteral<CharT, FlagLiteral::osint>(); + + if (CheckArg(aArgc, aArgv, osintLit, static_cast<const CharT**>(nullptr), + CheckArgFlag::None) == ARG_FOUND) { + // There should be 4 items left (app name + -osint + (acceptable arg) + + // param) + if (aArgc != 4) { + exit(127); + } + + // The first should be osint. + CharT* arg = aArgv[1]; + if (*arg != '-' +#ifdef XP_WIN + && *arg != '/' +#endif + ) { + exit(127); + } + ++arg; + if (*arg == '-') { + ++arg; + } + if (!strimatch(osintLit, arg)) { + exit(127); + } + // Strip it: + RemoveArg(aArgc, aArgv + 1); + + // Now only an acceptable argument and a parameter for it should be left: + arg = aArgv[1]; + if (*arg != '-' +#ifdef XP_WIN + && *arg != '/' +#endif + ) { + exit(127); + } + ++arg; + if (*arg == '-') { + ++arg; + } + bool haveAcceptableArg = false; + const CharT** acceptableArg = aAcceptableArgs; + while (*acceptableArg) { + if (strimatch(*acceptableArg, arg)) { + haveAcceptableArg = true; + break; + } + acceptableArg++; + } + if (!haveAcceptableArg) { + exit(127); + } + // The param that is passed afterwards shouldn't be another switch: + arg = aArgv[2]; + if (*arg == '-' +#ifdef XP_WIN + || *arg == '/' +#endif + ) { + exit(127); + } + } + // Either no osint, so nothing to do, or we ensured nothing nefarious was + // passed. +} + +#if defined(XP_WIN) + +namespace internal { + +/** + * Get the length that the string will take and takes into account the + * additional length if the string needs to be quoted and if characters need to + * be escaped. + */ +inline int ArgStrLen(const wchar_t* s) { + int backslashes = 0; + int i = wcslen(s); + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, kCommandLineDelimiter) != nullptr; + + if (addDoubleQuotes) { + i += 2; // initial and final duoblequote + } + + if (hasDoubleQuote) { + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the + // doublequote + i += backslashes + 1; + } + + backslashes = 0; + } + + ++s; + } + } + + return i; +} + +/** + * Copy string "s" to string "d", quoting the argument as appropriate and + * escaping doublequotes along with any backslashes that immediately precede + * doublequotes. + * The CRT parses this to retrieve the original argc/argv that we meant, + * see STDARGV.C in the MSVC CRT sources. + * + * @return the end of the string + */ +inline wchar_t* ArgToString(wchar_t* d, const wchar_t* s) { + int backslashes = 0; + bool hasDoubleQuote = wcschr(s, L'"') != nullptr; + // Only add doublequotes if the string contains a space or a tab + bool addDoubleQuotes = wcspbrk(s, kCommandLineDelimiter) != nullptr; + + if (addDoubleQuotes) { + *d = '"'; // initial doublequote + ++d; + } + + if (hasDoubleQuote) { + int i; + while (*s) { + if (*s == '\\') { + ++backslashes; + } else { + if (*s == '"') { + // Escape the doublequote and all backslashes preceding the + // doublequote + for (i = 0; i <= backslashes; ++i) { + *d = '\\'; + ++d; + } + } + + backslashes = 0; + } + + *d = *s; + ++d; + ++s; + } + } else { + wcscpy(d, s); + d += wcslen(s); + } + + if (addDoubleQuotes) { + *d = '"'; // final doublequote + ++d; + } + + return d; +} + +} // namespace internal + +/** + * Creates a command line from a list of arguments. + * + * @param argc Number of elements in |argv| + * @param argv Array of arguments + * @param aArgcExtra Number of elements in |aArgvExtra| + * @param aArgvExtra Optional array of arguments to be appended to the resulting + * command line after those provided by |argv|. + */ +inline UniquePtr<wchar_t[]> MakeCommandLine( + int argc, const wchar_t* const* argv, int aArgcExtra = 0, + const wchar_t* const* aArgvExtra = nullptr) { + int i; + int len = 0; + + // The + 1 for each argument reserves space for either a ' ' or the null + // terminator, depending on the position of the argument. + for (i = 0; i < argc; ++i) { + len += internal::ArgStrLen(argv[i]) + 1; + } + + for (i = 0; i < aArgcExtra; ++i) { + len += internal::ArgStrLen(aArgvExtra[i]) + 1; + } + + // Protect against callers that pass 0 arguments + if (len == 0) { + len = 1; + } + + auto s = MakeUnique<wchar_t[]>(len); + if (!s) { + return s; + } + + int totalArgc = argc + aArgcExtra; + + wchar_t* c = s.get(); + for (i = 0; i < argc; ++i) { + c = internal::ArgToString(c, argv[i]); + if (i + 1 != totalArgc) { + *c = ' '; + ++c; + } + } + + for (i = 0; i < aArgcExtra; ++i) { + c = internal::ArgToString(c, aArgvExtra[i]); + if (i + 1 != aArgcExtra) { + *c = ' '; + ++c; + } + } + + *c = '\0'; + + return s; +} + +inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) { + if (!aArgv) { + return false; + } + + UniquePtr<wchar_t[]> newArgv_0(GetFullBinaryPath()); + if (!newArgv_0) { + return false; + } + + // We intentionally leak newArgv_0 into argv[0] + aArgv[0] = newArgv_0.release(); + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(aArgv[0]); + return true; +} + +# if defined(MOZILLA_INTERNAL_API) +// This class converts a command line string into an array of the arguments. +// It's basically the opposite of MakeCommandLine. However, the behavior is +// different from ::CommandLineToArgvW in several ways, such as escaping a +// backslash or quoting an argument containing whitespaces. +template <typename T> +class CommandLineParserWin final { + int mArgc; + T** mArgv; + + void Release() { + if (mArgv) { + while (mArgc) { + delete[] mArgv[--mArgc]; + } + delete[] mArgv; + mArgv = nullptr; + } + } + + public: + CommandLineParserWin() : mArgc(0), mArgv(nullptr) {} + ~CommandLineParserWin() { Release(); } + + CommandLineParserWin(const CommandLineParserWin&) = delete; + CommandLineParserWin(CommandLineParserWin&&) = delete; + CommandLineParserWin& operator=(const CommandLineParserWin&) = delete; + CommandLineParserWin& operator=(CommandLineParserWin&&) = delete; + + int Argc() const { return mArgc; } + const T* const* Argv() const { return mArgv; } + + void HandleCommandLine(const T* aCmdLineString) { + Release(); + + int justCounting = 1; + // Flags, etc. + int init = 1; + int between, quoted, bSlashCount; + const T* p; + nsTAutoString<T> arg; + + // Parse command line args according to MS spec + // (see "Parsing C++ Command-Line Arguments" at + // http://msdn.microsoft.com/library/devprods/vs6/visualc/vclang/_pluslang_parsing_c.2b2b_.command.2d.line_arguments.htm). + // We loop if we've not finished the second pass through. + while (1) { + // Initialize if required. + if (init) { + p = aCmdLineString; + between = 1; + mArgc = quoted = bSlashCount = 0; + + init = 0; + } + if (between) { + // We are traversing whitespace between args. + // Check for start of next arg. + if (*p != 0 && !wcschr(kCommandLineDelimiter, *p)) { + // Start of another arg. + between = 0; + arg.Truncate(); + switch (*p) { + case '\\': + // Count the backslash. + bSlashCount = 1; + break; + case '"': + // Remember we're inside quotes. + quoted = 1; + break; + default: + // Add character to arg. + arg += *p; + break; + } + } else { + // Another space between args, ignore it. + } + } else { + // We are processing the contents of an argument. + // Check for whitespace or end. + if (*p == 0 || (!quoted && wcschr(kCommandLineDelimiter, *p))) { + // Process pending backslashes (interpret them + // literally since they're not followed by a "). + while (bSlashCount) { + arg += '\\'; + bSlashCount--; + } + // End current arg. + if (!justCounting) { + mArgv[mArgc] = new T[arg.Length() + 1]; + memcpy(mArgv[mArgc], arg.get(), (arg.Length() + 1) * sizeof(T)); + } + mArgc++; + // We're now between args. + between = 1; + } else { + // Still inside argument, process the character. + switch (*p) { + case '"': + // First, digest preceding backslashes (if any). + while (bSlashCount > 1) { + // Put one backsplash in arg for each pair. + arg += '\\'; + bSlashCount -= 2; + } + if (bSlashCount) { + // Quote is literal. + arg += '"'; + bSlashCount = 0; + } else { + // Quote starts or ends a quoted section. + if (quoted) { + // Check for special case of consecutive double + // quotes inside a quoted section. + if (*(p + 1) == '"') { + // This implies a literal double-quote. Fake that + // out by causing next double-quote to look as + // if it was preceded by a backslash. + bSlashCount = 1; + } else { + quoted = 0; + } + } else { + quoted = 1; + } + } + break; + case '\\': + // Add to count. + bSlashCount++; + break; + default: + // Accept any preceding backslashes literally. + while (bSlashCount) { + arg += '\\'; + bSlashCount--; + } + // Just add next char to the current arg. + arg += *p; + break; + } + } + } + // Check for end of input. + if (*p) { + // Go to next character. + p++; + } else { + // If on first pass, go on to second. + if (justCounting) { + // Allocate argv array. + mArgv = new T*[mArgc]; + + // Start second pass + justCounting = 0; + init = 1; + } else { + // Quit. + break; + } + } + } + } +}; +# endif // defined(MOZILLA_INTERNAL_API) + +#endif // defined(XP_WIN) + +// SaveToEnv and EnvHasValue are only available on Windows or when +// MOZILLA_INTERNAL_API is defined +#if defined(MOZILLA_INTERNAL_API) || defined(XP_WIN) + +// Save literal putenv string to environment variable. +MOZ_NEVER_INLINE inline void SaveToEnv(const char* aEnvString) { +# if defined(MOZILLA_INTERNAL_API) + char* expr = strdup(aEnvString); + if (expr) { + PR_SetEnv(expr); + } + + // We intentionally leak |expr| here since it is required by PR_SetEnv. + MOZ_LSAN_INTENTIONALLY_LEAK_OBJECT(expr); +# elif defined(XP_WIN) + // This is the same as the NSPR implementation + // (Note that we don't need to do a strdup for this case; the CRT makes a + // copy) + _putenv(aEnvString); +# endif +} + +inline bool EnvHasValue(const char* aVarName) { +# if defined(MOZILLA_INTERNAL_API) + const char* val = PR_GetEnv(aVarName); + return val && *val; +# elif defined(XP_WIN) + // This is the same as the NSPR implementation + const char* val = getenv(aVarName); + return val && *val; +# endif +} + +#endif // end windows/internal_api-only definitions + +#ifndef NS_NO_XPCOM +already_AddRefed<nsIFile> GetFileFromEnv(const char* name); +#endif + +} // namespace mozilla + +#endif // mozilla_CmdLineAndEnvUtils_h diff --git a/toolkit/xre/CreateAppData.cpp b/toolkit/xre/CreateAppData.cpp new file mode 100644 index 0000000000..dda5b37873 --- /dev/null +++ b/toolkit/xre/CreateAppData.cpp @@ -0,0 +1,77 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsXULAppAPI.h" +#include "nsINIParser.h" +#include "nsIFile.h" +#include "mozilla/XREAppData.h" + +// This include must appear early in the unified cpp file for toolkit/xre to +// make sure OSX APIs make use of the OSX TextRange before mozilla::TextRange is +// declared and made a global symbol by a "using namespace mozilla" declaration. +#ifdef XP_MACOSX +# include <Carbon/Carbon.h> +#endif + +using namespace mozilla; + +static void ReadString(nsINIParser& parser, const char* section, + const char* key, XREAppData::CharPtr& result) { + nsCString str; + nsresult rv = parser.GetString(section, key, str); + if (NS_SUCCEEDED(rv)) { + result = str.get(); + } +} + +struct ReadFlag { + const char* section; + const char* key; + uint32_t flag; +}; + +static void ReadFlag(nsINIParser& parser, const char* section, const char* key, + uint32_t flag, uint32_t& result) { + char buf[6]; // large enough to hold "false" + nsresult rv = parser.GetString(section, key, buf, sizeof(buf)); + if (NS_SUCCEEDED(rv) || rv == NS_ERROR_LOSS_OF_SIGNIFICANT_DATA) { + if (buf[0] == '1' || buf[0] == 't' || buf[0] == 'T') { + result |= flag; + } + if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') { + result &= ~flag; + } + } +} + +nsresult XRE_ParseAppData(nsIFile* aINIFile, XREAppData& aAppData) { + NS_ENSURE_ARG(aINIFile); + + nsresult rv; + + nsINIParser parser; + rv = parser.Init(aINIFile); + if (NS_FAILED(rv)) return rv; + + ReadString(parser, "App", "Vendor", aAppData.vendor); + ReadString(parser, "App", "Name", aAppData.name); + ReadString(parser, "App", "RemotingName", aAppData.remotingName); + ReadString(parser, "App", "Version", aAppData.version); + ReadString(parser, "App", "BuildID", aAppData.buildID); + ReadString(parser, "App", "ID", aAppData.ID); + ReadString(parser, "App", "Copyright", aAppData.copyright); + ReadString(parser, "App", "Profile", aAppData.profile); + ReadString(parser, "Gecko", "MinVersion", aAppData.minVersion); + ReadString(parser, "Gecko", "MaxVersion", aAppData.maxVersion); + ReadString(parser, "Crash Reporter", "ServerURL", aAppData.crashReporterURL); + ReadString(parser, "App", "UAName", aAppData.UAName); + ReadString(parser, "AppUpdate", "URL", aAppData.updateURL); + ReadFlag(parser, "XRE", "EnableProfileMigrator", + NS_XRE_ENABLE_PROFILE_MIGRATOR, aAppData.flags); + ReadFlag(parser, "Crash Reporter", "Enabled", NS_XRE_ENABLE_CRASH_REPORTER, + aAppData.flags); + + return NS_OK; +} diff --git a/toolkit/xre/DllPrefetchExperimentRegistryInfo.cpp b/toolkit/xre/DllPrefetchExperimentRegistryInfo.cpp new file mode 100644 index 0000000000..829999ac45 --- /dev/null +++ b/toolkit/xre/DllPrefetchExperimentRegistryInfo.cpp @@ -0,0 +1,112 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "DllPrefetchExperimentRegistryInfo.h" + +#include "mozilla/Assertions.h" +#include "mozilla/NativeNt.h" +#include "mozilla/ResultExtensions.h" + +namespace mozilla { + +const wchar_t DllPrefetchExperimentRegistryInfo::kExperimentSubKeyPath[] = + L"SOFTWARE" + L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\DllPrefetchExperiment"; + +Result<Ok, nsresult> DllPrefetchExperimentRegistryInfo::Open() { + if (!!mRegKey) { + return Ok(); + } + + DWORD disposition; + HKEY rawKey; + LSTATUS result = ::RegCreateKeyExW( + HKEY_CURRENT_USER, kExperimentSubKeyPath, 0, nullptr, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, &disposition); + + if (result != ERROR_SUCCESS) { + return Err(NS_ERROR_UNEXPECTED); + } + + mRegKey.own(rawKey); + + if (disposition == REG_CREATED_NEW_KEY || + disposition == REG_OPENED_EXISTING_KEY) { + return Ok(); + } + + MOZ_ASSERT_UNREACHABLE("Invalid disposition from RegCreateKeyExW"); + return Err(NS_ERROR_UNEXPECTED); +} + +Result<Ok, nsresult> DllPrefetchExperimentRegistryInfo::ReflectPrefToRegistry( + int32_t aVal) { + MOZ_TRY(Open()); + + mPrefetchMode = aVal; + + LSTATUS status = + ::RegSetValueExW(mRegKey.get(), mBinPath.get(), 0, REG_DWORD, + reinterpret_cast<PBYTE>(&aVal), sizeof(aVal)); + + if (status != ERROR_SUCCESS) { + return Err(NS_ERROR_UNEXPECTED); + } + + return Ok(); +} + +Result<Ok, nsresult> DllPrefetchExperimentRegistryInfo::ReadRegistryValueData( + DWORD expectedType) { + MOZ_TRY(Open()); + + int32_t data; + DWORD dataLen = sizeof((ULONG)data); + DWORD type; + LSTATUS status = + ::RegQueryValueExW(mRegKey.get(), mBinPath.get(), nullptr, &type, + reinterpret_cast<PBYTE>(&data), &dataLen); + + if (status == ERROR_FILE_NOT_FOUND) { + // The registry key has not been created, set to default 0 + mPrefetchMode = 0; + return Ok(); + } + + if (status != ERROR_SUCCESS) { + return Err(NS_ERROR_UNEXPECTED); + } + + if (type != expectedType) { + return Err(NS_ERROR_UNEXPECTED); + } + + mPrefetchMode = data; + return Ok(); +} + +AlteredDllPrefetchMode +DllPrefetchExperimentRegistryInfo::GetAlteredDllPrefetchMode() { + Result<Ok, nsresult> result = ReadRegistryValueData(REG_DWORD); + if (!result.isOk()) { + MOZ_ASSERT(false); + return AlteredDllPrefetchMode::CurrentPrefetch; + } + + switch (mPrefetchMode) { + case 0: + return AlteredDllPrefetchMode::CurrentPrefetch; + case 1: + return AlteredDllPrefetchMode::NoPrefetch; + case 2: + return AlteredDllPrefetchMode::OptimizedPrefetch; + default: + MOZ_ASSERT(false); + return AlteredDllPrefetchMode::CurrentPrefetch; + } +} + +} // namespace mozilla diff --git a/toolkit/xre/DllPrefetchExperimentRegistryInfo.h b/toolkit/xre/DllPrefetchExperimentRegistryInfo.h new file mode 100644 index 0000000000..26cba51dcf --- /dev/null +++ b/toolkit/xre/DllPrefetchExperimentRegistryInfo.h @@ -0,0 +1,50 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_DllPrefetchExperimentRegistryInfo_h +#define mozilla_DllPrefetchExperimentRegistryInfo_h + +#include "mozilla/Maybe.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsWindowsHelpers.h" + +/** + * This is a temporary file in order to conduct an experiment on the impact of + * startup time on retention (see Bug 1640087). + * This was generally adapted from LauncherRegistryInfo.h + */ + +namespace mozilla { + +enum class AlteredDllPrefetchMode { + CurrentPrefetch, + NoPrefetch, + OptimizedPrefetch +}; + +class DllPrefetchExperimentRegistryInfo final { + public: + DllPrefetchExperimentRegistryInfo() : mBinPath(GetFullBinaryPath().get()) {} + ~DllPrefetchExperimentRegistryInfo() {} + + Result<Ok, nsresult> ReflectPrefToRegistry(int32_t aVal); + Result<Ok, nsresult> ReadRegistryValueData(DWORD expectedType); + + AlteredDllPrefetchMode GetAlteredDllPrefetchMode(); + + private: + Result<Ok, nsresult> Open(); + + nsAutoRegKey mRegKey; + nsString mBinPath; + int32_t mPrefetchMode; + + static const wchar_t kExperimentSubKeyPath[]; +}; + +} // namespace mozilla + +#endif // mozilla_DllPrefetchExperimentRegistryInfo_h diff --git a/toolkit/xre/EventTracer.cpp b/toolkit/xre/EventTracer.cpp new file mode 100644 index 0000000000..aae3fee8e3 --- /dev/null +++ b/toolkit/xre/EventTracer.cpp @@ -0,0 +1,219 @@ +/* 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/. */ + +/* + * Event loop instrumentation. This code attempts to measure the + * latency of the UI-thread event loop by firing native events at it from + * a background thread, and measuring how long it takes for them + * to be serviced. The measurement interval (kMeasureInterval, below) + * is also used as the upper bound of acceptable response time. + * When an event takes longer than that interval to be serviced, + * a sample will be written to the log. + * + * Usage: + * + * Set MOZ_INSTRUMENT_EVENT_LOOP=1 in the environment to enable + * this instrumentation. Currently only the UI process is instrumented. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT in the environment to a + * file path to contain the log output, the default is to log to stdout. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD in the environment to an + * integer number of milliseconds to change the threshold for reporting. + * The default is 20 milliseconds. Unresponsive periods shorter than this + * threshold will not be reported. + * + * Set MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL in the environment to an + * integer number of milliseconds to change the maximum sampling frequency. + * This variable controls how often events will be sent to the main + * thread's event loop to sample responsiveness. The sampler will not + * send events twice within LOOP_INTERVAL milliseconds. + * The default is 10 milliseconds. + * + * All logged output lines start with MOZ_EVENT_TRACE. All timestamps + * output are milliseconds since the epoch (PRTime / 1000). + * + * On startup, a line of the form: + * MOZ_EVENT_TRACE start <timestamp> + * will be output. + * + * On shutdown, a line of the form: + * MOZ_EVENT_TRACE stop <timestamp> + * will be output. + * + * When an event servicing time exceeds the threshold, a line of the form: + * MOZ_EVENT_TRACE sample <timestamp> <duration> + * will be output, where <duration> is the number of milliseconds that + * it took for the event to be serviced. Duration may contain a fractional + * component. + */ + +#include "GeckoProfiler.h" + +#include "EventTracer.h" + +#include <stdio.h> + +#include "mozilla/Preferences.h" +#include "mozilla/TimeStamp.h" +#include "mozilla/WidgetTraceEvent.h" +#include "nsDebug.h" +#include <limits.h> +#include <prenv.h> +#include <prinrval.h> +#include <prthread.h> +#include <prtime.h> + +#include "nsThreadUtils.h" + +using mozilla::FireAndWaitForTracerEvent; +using mozilla::TimeDuration; +using mozilla::TimeStamp; + +namespace { + +PRThread* sTracerThread = nullptr; +bool sExit = false; + +struct TracerStartClosure { + bool mLogTracing; + int32_t mThresholdInterval; +}; + +/* + * The tracer thread fires events at the native event loop roughly + * every kMeasureInterval. It will sleep to attempt not to send them + * more quickly, but if the response time is longer than kMeasureInterval + * it will not send another event until the previous response is received. + * + * The output defaults to stdout, but can be redirected to a file by + * settting the environment variable MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT + * to the name of a file to use. + */ +void TracerThread(void* arg) { + AUTO_PROFILER_REGISTER_THREAD("Event Tracer"); + NS_SetCurrentThreadName("Event Tracer"); + + TracerStartClosure* threadArgs = static_cast<TracerStartClosure*>(arg); + + // These are the defaults. They can be overridden by environment vars. + // This should be set to the maximum latency we'd like to allow + // for responsiveness. + int32_t thresholdInterval = threadArgs->mThresholdInterval; + PRIntervalTime threshold = PR_MillisecondsToInterval(thresholdInterval); + // This is the sampling interval. + PRIntervalTime interval = PR_MillisecondsToInterval(thresholdInterval / 2); + + sExit = false; + FILE* log = nullptr; + char* envfile = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_OUTPUT"); + if (envfile) { + log = fopen(envfile, "w"); + } + if (log == nullptr) log = stdout; + + char* thresholdenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_THRESHOLD"); + if (thresholdenv && *thresholdenv) { + int val = atoi(thresholdenv); + if (val != 0 && val != INT_MAX && val != INT_MIN) { + threshold = PR_MillisecondsToInterval(val); + } + } + + char* intervalenv = PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP_INTERVAL"); + if (intervalenv && *intervalenv) { + int val = atoi(intervalenv); + if (val != 0 && val != INT_MAX && val != INT_MIN) { + interval = PR_MillisecondsToInterval(val); + } + } + + if (threadArgs->mLogTracing) { + long long now = PR_Now() / PR_USEC_PER_MSEC; + fprintf(log, "MOZ_EVENT_TRACE start %llu\n", now); + } + + while (!sExit) { + TimeStamp start(TimeStamp::Now()); + PRIntervalTime next_sleep = interval; + + // TODO: only wait up to a maximum of interval; return + // early if that threshold is exceeded and dump a stack trace + // or do something else useful. + if (FireAndWaitForTracerEvent()) { + TimeDuration duration = TimeStamp::Now() - start; + // Only report samples that exceed our measurement threshold. + long long now = PR_Now() / PR_USEC_PER_MSEC; + if (threadArgs->mLogTracing && duration.ToMilliseconds() > threshold) { + fprintf(log, "MOZ_EVENT_TRACE sample %llu %lf\n", now, + duration.ToMilliseconds()); + } + + if (next_sleep > duration.ToMilliseconds()) { + next_sleep -= int(duration.ToMilliseconds()); + } else { + // Don't sleep at all if this event took longer than the measure + // interval to deliver. + next_sleep = 0; + } + } + + if (next_sleep != 0 && !sExit) { + PR_Sleep(next_sleep); + } + } + + if (threadArgs->mLogTracing) { + long long now = PR_Now() / PR_USEC_PER_MSEC; + fprintf(log, "MOZ_EVENT_TRACE stop %llu\n", now); + } + + if (log != stdout) fclose(log); + + delete threadArgs; +} + +} // namespace + +namespace mozilla { + +bool InitEventTracing(bool aLog) { + if (sTracerThread) return true; + + // Initialize the widget backend. + if (!InitWidgetTracing()) return false; + + // The tracer thread owns the object and will delete it. + TracerStartClosure* args = new TracerStartClosure(); + args->mLogTracing = aLog; + + // Pass the default threshold interval. + int32_t thresholdInterval = 20; + Preferences::GetInt("devtools.eventlooplag.threshold", &thresholdInterval); + args->mThresholdInterval = thresholdInterval; + + // Create a thread that will fire events back at the + // main thread to measure responsiveness. + MOZ_ASSERT(!sTracerThread, "Event tracing already initialized!"); + sTracerThread = + PR_CreateThread(PR_USER_THREAD, TracerThread, args, PR_PRIORITY_NORMAL, + PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0); + return sTracerThread != nullptr; +} + +void ShutdownEventTracing() { + if (!sTracerThread) return; + + sExit = true; + // Ensure that the tracer thread doesn't hang. + SignalTracerThread(); + + if (sTracerThread) PR_JoinThread(sTracerThread); + sTracerThread = nullptr; + + // Allow the widget backend to clean up. + CleanUpWidgetTracing(); +} + +} // namespace mozilla diff --git a/toolkit/xre/EventTracer.h b/toolkit/xre/EventTracer.h new file mode 100644 index 0000000000..67fa6661dd --- /dev/null +++ b/toolkit/xre/EventTracer.h @@ -0,0 +1,23 @@ +/* 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 XRE_EVENTTRACER_H_ +#define XRE_EVENTTRACER_H_ + +namespace mozilla { + +// Create a thread that will fire events back at the +// main thread to measure responsiveness. Return true +// if the thread was created successfully. +// aLog If the tracing results should be printed to +// the console. +bool InitEventTracing(bool aLog); + +// Signal the background thread to stop, and join it. +// Must be called from the same thread that called InitEventTracing. +void ShutdownEventTracing(); + +} // namespace mozilla + +#endif /* XRE_EVENTTRACER_H_ */ diff --git a/toolkit/xre/LauncherRegistryInfo.cpp b/toolkit/xre/LauncherRegistryInfo.cpp new file mode 100644 index 0000000000..6778827fec --- /dev/null +++ b/toolkit/xre/LauncherRegistryInfo.cpp @@ -0,0 +1,533 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "LauncherRegistryInfo.h" + +#include "mozilla/UniquePtr.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/NativeNt.h" + +#include <string> + +#define EXPAND_STRING_MACRO2(t) t +#define EXPAND_STRING_MACRO(t) EXPAND_STRING_MACRO2(t) + +// This function is copied from Chromium base/time/time_win.cc +// Returns the current value of the performance counter. +static uint64_t QPCNowRaw() { + LARGE_INTEGER perf_counter_now = {}; + // According to the MSDN documentation for QueryPerformanceCounter(), this + // will never fail on systems that run XP or later. + // https://docs.microsoft.com/en-us/windows/win32/api/profileapi/nf-profileapi-queryperformancecounter + ::QueryPerformanceCounter(&perf_counter_now); + return perf_counter_now.QuadPart; +} + +static mozilla::LauncherResult<DWORD> GetCurrentImageTimestamp() { + mozilla::nt::PEHeaders headers(::GetModuleHandleW(nullptr)); + if (!headers) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_BAD_EXE_FORMAT); + } + + DWORD timestamp; + if (!headers.GetTimeStamp(timestamp)) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA); + } + + return timestamp; +} + +template <typename T> +static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData( + const nsAutoRegKey& key, const std::wstring& name, DWORD expectedType) { + static_assert(mozilla::IsPod<T>::value, + "Registry value type must be primitive."); + T data; + DWORD dataLen = sizeof(data); + DWORD type; + LSTATUS status = ::RegQueryValueExW(key.get(), name.c_str(), nullptr, &type, + reinterpret_cast<PBYTE>(&data), &dataLen); + if (status == ERROR_FILE_NOT_FOUND) { + return mozilla::Maybe<T>(); + } + + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + if (type != expectedType) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + } + + return mozilla::Some(data); +} + +template <typename T> +static mozilla::LauncherVoidResult WriteRegistryValueData( + const nsAutoRegKey& key, const std::wstring& name, DWORD type, T data) { + static_assert(mozilla::IsPod<T>::value, + "Registry value type must be primitive."); + LSTATUS status = + ::RegSetValueExW(key.get(), name.c_str(), 0, type, + reinterpret_cast<PBYTE>(&data), sizeof(data)); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return mozilla::Ok(); +} + +static mozilla::LauncherResult<bool> DeleteRegistryValueData( + const nsAutoRegKey& key, const std::wstring& name) { + LSTATUS status = ::RegDeleteValueW(key, name.c_str()); + if (status == ERROR_FILE_NOT_FOUND) { + return false; + } + + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return true; +} + +namespace mozilla { + +const wchar_t LauncherRegistryInfo::kLauncherSubKeyPath[] = + L"SOFTWARE\\" EXPAND_STRING_MACRO(MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO( + MOZ_APP_BASENAME) L"\\Launcher"; +const wchar_t LauncherRegistryInfo::kLauncherSuffix[] = L"|Launcher"; +const wchar_t LauncherRegistryInfo::kBrowserSuffix[] = L"|Browser"; +const wchar_t LauncherRegistryInfo::kImageTimestampSuffix[] = L"|Image"; +const wchar_t LauncherRegistryInfo::kTelemetrySuffix[] = L"|Telemetry"; + +bool LauncherRegistryInfo::sAllowCommit = true; + +LauncherResult<LauncherRegistryInfo::Disposition> LauncherRegistryInfo::Open() { + if (!!mRegKey) { + return Disposition::OpenedExisting; + } + + DWORD disposition; + HKEY rawKey; + LSTATUS result = ::RegCreateKeyExW( + HKEY_CURRENT_USER, kLauncherSubKeyPath, 0, nullptr, + REG_OPTION_NON_VOLATILE, KEY_ALL_ACCESS, nullptr, &rawKey, &disposition); + if (result != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(result); + } + + mRegKey.own(rawKey); + + switch (disposition) { + case REG_CREATED_NEW_KEY: + return Disposition::CreatedNew; + case REG_OPENED_EXISTING_KEY: + return Disposition::OpenedExisting; + default: + break; + } + + MOZ_ASSERT_UNREACHABLE("Invalid disposition from RegCreateKeyExW"); + return LAUNCHER_ERROR_GENERIC(); +} + +LauncherVoidResult LauncherRegistryInfo::ReflectPrefToRegistry( + const bool aEnable) { + LauncherResult<EnabledState> curEnabledState = IsEnabled(); + if (curEnabledState.isErr()) { + return curEnabledState.propagateErr(); + } + + bool isCurrentlyEnabled = + curEnabledState.inspect() != EnabledState::ForceDisabled; + if (isCurrentlyEnabled == aEnable) { + // Don't reflect to the registry unless the new enabled state is actually + // changing with respect to the current enabled state. + return Ok(); + } + + // Always delete the launcher timestamp + LauncherResult<bool> clearedLauncherTimestamp = ClearLauncherStartTimestamp(); + MOZ_ASSERT(clearedLauncherTimestamp.isOk()); + if (clearedLauncherTimestamp.isErr()) { + return clearedLauncherTimestamp.propagateErr(); + } + + // Allow commit when we enable the launcher, otherwise block. + sAllowCommit = aEnable; + + if (!aEnable) { + // Set the browser timestamp to 0 to indicate force-disabled + return WriteBrowserStartTimestamp(0ULL); + } + + // Otherwise we delete the browser timestamp to start over fresh + LauncherResult<bool> clearedBrowserTimestamp = ClearBrowserStartTimestamp(); + MOZ_ASSERT(clearedBrowserTimestamp.isOk()); + if (clearedBrowserTimestamp.isErr()) { + return clearedBrowserTimestamp.propagateErr(); + } + + return Ok(); +} + +LauncherVoidResult LauncherRegistryInfo::ReflectTelemetryPrefToRegistry( + const bool aEnable) { + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + return WriteRegistryValueData(mRegKey, ResolveTelemetryValueName(), REG_DWORD, + aEnable ? 1UL : 0UL); +} + +LauncherResult<LauncherRegistryInfo::ProcessType> LauncherRegistryInfo::Check( + const ProcessType aDesiredType, const CheckOption aOption) { + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult<DWORD> ourImageTimestamp = GetCurrentImageTimestamp(); + if (ourImageTimestamp.isErr()) { + return ourImageTimestamp.propagateErr(); + } + + LauncherResult<Maybe<DWORD>> savedImageTimestamp = GetSavedImageTimestamp(); + if (savedImageTimestamp.isErr()) { + return savedImageTimestamp.propagateErr(); + } + + // If we don't have a saved timestamp, or we do but it doesn't match with + // our current timestamp, clear previous values unless we're force-disabled. + if (savedImageTimestamp.inspect().isNothing() || + savedImageTimestamp.inspect().value() != ourImageTimestamp.inspect()) { + LauncherVoidResult clearResult = ClearStartTimestamps(); + if (clearResult.isErr()) { + return clearResult.propagateErr(); + } + + LauncherVoidResult writeResult = + WriteImageTimestamp(ourImageTimestamp.inspect()); + if (writeResult.isErr()) { + return writeResult.propagateErr(); + } + } + + // If we're going to be running as the browser process, or there is no + // existing values to check, just write our timestamp and return. + if (aDesiredType == ProcessType::Browser) { + mBrowserTimestampToWrite = Some(QPCNowRaw()); + return ProcessType::Browser; + } + + if (disposition.inspect() == Disposition::CreatedNew) { + mLauncherTimestampToWrite = Some(QPCNowRaw()); + return ProcessType::Launcher; + } + + if (disposition.inspect() != Disposition::OpenedExisting) { + MOZ_ASSERT_UNREACHABLE("Invalid |disposition|"); + return LAUNCHER_ERROR_GENERIC(); + } + + LauncherResult<Maybe<uint64_t>> lastLauncherTimestampResult = + GetLauncherStartTimestamp(); + if (lastLauncherTimestampResult.isErr()) { + return lastLauncherTimestampResult.propagateErr(); + } + + LauncherResult<Maybe<uint64_t>> lastBrowserTimestampResult = + GetBrowserStartTimestamp(); + if (lastBrowserTimestampResult.isErr()) { + return lastBrowserTimestampResult.propagateErr(); + } + + const Maybe<uint64_t>& lastLauncherTimestamp = + lastLauncherTimestampResult.inspect(); + const Maybe<uint64_t>& lastBrowserTimestamp = + lastBrowserTimestampResult.inspect(); + + ProcessType typeToRunAs = aDesiredType; + + if (lastLauncherTimestamp.isSome() != lastBrowserTimestamp.isSome()) { + // If we have a launcher timestamp but no browser timestamp (or vice versa), + // that's bad because it is indicating that the browser can't run with + // the launcher process. + typeToRunAs = ProcessType::Browser; + } else if (lastLauncherTimestamp.isSome()) { + // if we have both timestamps, we want to ensure that the launcher timestamp + // is earlier than the browser timestamp. + if (aDesiredType == ProcessType::Launcher) { + bool areTimestampsOk = + lastLauncherTimestamp.value() < lastBrowserTimestamp.value(); + if (!areTimestampsOk) { + typeToRunAs = ProcessType::Browser; + } + } + } else { + // If we have neither timestamp, then we should try running as suggested + // by |aDesiredType|. + // We shouldn't really have this scenario unless we're going to be running + // as the launcher process. + MOZ_ASSERT(typeToRunAs == ProcessType::Launcher); + // No change to typeToRunAs + } + + // Debugging setting that forces the desired type regardless of the various + // tests that have been performed. + if (aOption == CheckOption::Force) { + typeToRunAs = aDesiredType; + } + + switch (typeToRunAs) { + case ProcessType::Browser: + if (aDesiredType != typeToRunAs) { + // We were hoping to run as the launcher, but some failure has caused + // us to run as the browser. Set the browser timestamp to zero as an + // indicator. + mBrowserTimestampToWrite = Some(0ULL); + } else { + mBrowserTimestampToWrite = Some(QPCNowRaw()); + } + break; + case ProcessType::Launcher: + mLauncherTimestampToWrite = Some(QPCNowRaw()); + break; + default: + MOZ_ASSERT_UNREACHABLE("Invalid |typeToRunAs|"); + return LAUNCHER_ERROR_GENERIC(); + } + + return typeToRunAs; +} + +LauncherVoidResult LauncherRegistryInfo::DisableDueToFailure() { + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + LauncherVoidResult result = WriteBrowserStartTimestamp(0ULL); + if (result.isOk()) { + // Block commit when we disable the launcher. It could be allowed + // when the image timestamp is updated. + sAllowCommit = false; + } + return result; +} + +LauncherVoidResult LauncherRegistryInfo::Commit() { + if (!sAllowCommit) { + Abort(); + return Ok(); + } + + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + if (mLauncherTimestampToWrite.isSome()) { + LauncherVoidResult writeResult = + WriteLauncherStartTimestamp(mLauncherTimestampToWrite.value()); + if (writeResult.isErr()) { + return writeResult.propagateErr(); + } + mLauncherTimestampToWrite = Nothing(); + } + + if (mBrowserTimestampToWrite.isSome()) { + LauncherVoidResult writeResult = + WriteBrowserStartTimestamp(mBrowserTimestampToWrite.value()); + if (writeResult.isErr()) { + return writeResult.propagateErr(); + } + mBrowserTimestampToWrite = Nothing(); + } + + return Ok(); +} + +void LauncherRegistryInfo::Abort() { + mLauncherTimestampToWrite = mBrowserTimestampToWrite = Nothing(); +} + +LauncherRegistryInfo::EnabledState LauncherRegistryInfo::GetEnabledState( + const Maybe<uint64_t>& aLauncherTs, const Maybe<uint64_t>& aBrowserTs) { + if (aBrowserTs.isSome()) { + if (aLauncherTs.isSome()) { + if (aLauncherTs.value() < aBrowserTs.value()) { + // Both timestamps exist and the browser's timestamp is later. + return EnabledState::Enabled; + } + } else if (aBrowserTs.value() == 0ULL) { + // Only browser's timestamp exists and its value is 0. + return EnabledState::ForceDisabled; + } + } else if (aLauncherTs.isNothing()) { + // Neither timestamps exist. + return EnabledState::Enabled; + } + + // Everything else is FailDisabled. + return EnabledState::FailDisabled; +} + +LauncherResult<LauncherRegistryInfo::EnabledState> +LauncherRegistryInfo::IsEnabled() { + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult<Maybe<uint64_t>> lastLauncherTimestamp = + GetLauncherStartTimestamp(); + if (lastLauncherTimestamp.isErr()) { + return lastLauncherTimestamp.propagateErr(); + } + + LauncherResult<Maybe<uint64_t>> lastBrowserTimestamp = + GetBrowserStartTimestamp(); + if (lastBrowserTimestamp.isErr()) { + return lastBrowserTimestamp.propagateErr(); + } + + return GetEnabledState(lastLauncherTimestamp.inspect(), + lastBrowserTimestamp.inspect()); +} + +LauncherResult<bool> LauncherRegistryInfo::IsTelemetryEnabled() { + LauncherResult<Disposition> disposition = Open(); + if (disposition.isErr()) { + return disposition.propagateErr(); + } + + LauncherResult<Maybe<DWORD>> result = ReadRegistryValueData<DWORD>( + mRegKey, ResolveTelemetryValueName(), REG_DWORD); + if (result.isErr()) { + return result.propagateErr(); + } + + if (result.inspect().isNothing()) { + // Value does not exist, treat as false + return false; + } + + return result.inspect().value() != 0; +} + +const std::wstring& LauncherRegistryInfo::ResolveLauncherValueName() { + if (mLauncherValueName.empty()) { + mLauncherValueName.assign(mBinPath); + mLauncherValueName.append(kLauncherSuffix, + ArrayLength(kLauncherSuffix) - 1); + } + + return mLauncherValueName; +} + +const std::wstring& LauncherRegistryInfo::ResolveBrowserValueName() { + if (mBrowserValueName.empty()) { + mBrowserValueName.assign(mBinPath); + mBrowserValueName.append(kBrowserSuffix, ArrayLength(kBrowserSuffix) - 1); + } + + return mBrowserValueName; +} + +const std::wstring& LauncherRegistryInfo::ResolveImageTimestampValueName() { + if (mImageValueName.empty()) { + mImageValueName.assign(mBinPath); + mImageValueName.append(kImageTimestampSuffix, + ArrayLength(kImageTimestampSuffix) - 1); + } + + return mImageValueName; +} + +const std::wstring& LauncherRegistryInfo::ResolveTelemetryValueName() { + if (mTelemetryValueName.empty()) { + mTelemetryValueName.assign(mBinPath); + mTelemetryValueName.append(kTelemetrySuffix, + ArrayLength(kTelemetrySuffix) - 1); + } + + return mTelemetryValueName; +} + +LauncherVoidResult LauncherRegistryInfo::WriteLauncherStartTimestamp( + uint64_t aValue) { + return WriteRegistryValueData(mRegKey, ResolveLauncherValueName(), REG_QWORD, + aValue); +} + +LauncherVoidResult LauncherRegistryInfo::WriteBrowserStartTimestamp( + uint64_t aValue) { + return WriteRegistryValueData(mRegKey, ResolveBrowserValueName(), REG_QWORD, + aValue); +} + +LauncherVoidResult LauncherRegistryInfo::WriteImageTimestamp(DWORD aTimestamp) { + return WriteRegistryValueData(mRegKey, ResolveImageTimestampValueName(), + REG_DWORD, aTimestamp); +} + +LauncherResult<bool> LauncherRegistryInfo::ClearLauncherStartTimestamp() { + return DeleteRegistryValueData(mRegKey, ResolveLauncherValueName()); +} + +LauncherResult<bool> LauncherRegistryInfo::ClearBrowserStartTimestamp() { + return DeleteRegistryValueData(mRegKey, ResolveBrowserValueName()); +} + +LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() { + LauncherResult<EnabledState> enabled = IsEnabled(); + if (enabled.isOk() && enabled.inspect() == EnabledState::ForceDisabled) { + // We don't clear anything when we're force disabled - we need to maintain + // the current registry state in this case. + return Ok(); + } + + LauncherResult<bool> clearedLauncherTimestamp = ClearLauncherStartTimestamp(); + if (clearedLauncherTimestamp.isErr()) { + return clearedLauncherTimestamp.propagateErr(); + } + + LauncherResult<bool> clearedBrowserTimestamp = ClearBrowserStartTimestamp(); + if (clearedBrowserTimestamp.isErr()) { + return clearedBrowserTimestamp.propagateErr(); + } + + // Reset both timestamps to align with registry deletion + mLauncherTimestampToWrite = mBrowserTimestampToWrite = Nothing(); + + // Disablement is gone. Let's allow commit. + sAllowCommit = true; + + return Ok(); +} + +LauncherResult<Maybe<DWORD>> LauncherRegistryInfo::GetSavedImageTimestamp() { + return ReadRegistryValueData<DWORD>(mRegKey, ResolveImageTimestampValueName(), + REG_DWORD); +} + +LauncherResult<Maybe<uint64_t>> +LauncherRegistryInfo::GetLauncherStartTimestamp() { + return ReadRegistryValueData<uint64_t>(mRegKey, ResolveLauncherValueName(), + REG_QWORD); +} + +LauncherResult<Maybe<uint64_t>> +LauncherRegistryInfo::GetBrowserStartTimestamp() { + return ReadRegistryValueData<uint64_t>(mRegKey, ResolveBrowserValueName(), + REG_QWORD); +} + +} // namespace mozilla diff --git a/toolkit/xre/LauncherRegistryInfo.h b/toolkit/xre/LauncherRegistryInfo.h new file mode 100644 index 0000000000..c8d015a4ce --- /dev/null +++ b/toolkit/xre/LauncherRegistryInfo.h @@ -0,0 +1,99 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_LauncherRegistryInfo_h +#define mozilla_LauncherRegistryInfo_h + +#include "mozilla/Maybe.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsWindowsHelpers.h" + +#include <string> + +/** + * We use std::wstring here because this code must be usable within both the + * launcher process and Gecko itself. + */ + +namespace mozilla { + +class LauncherRegistryInfo final { + public: + enum class ProcessType { Launcher, Browser }; + + enum class EnabledState { + Enabled, + FailDisabled, + ForceDisabled, + }; + + enum class CheckOption { + Default, + Force, + }; + + LauncherRegistryInfo() : mBinPath(GetFullBinaryPath().get()) {} + ~LauncherRegistryInfo() { Abort(); } + + LauncherVoidResult ReflectPrefToRegistry(const bool aEnable); + LauncherResult<EnabledState> IsEnabled(); + LauncherResult<bool> IsTelemetryEnabled(); + LauncherVoidResult ReflectTelemetryPrefToRegistry(const bool aEnable); + LauncherResult<ProcessType> Check( + const ProcessType aDesiredType, + const CheckOption aOption = CheckOption::Default); + LauncherVoidResult DisableDueToFailure(); + LauncherVoidResult Commit(); + void Abort(); + + private: + enum class Disposition { CreatedNew, OpenedExisting }; + + private: + // This flag is to prevent the disabled state from being accidentally + // re-enabled by another instance. + static bool sAllowCommit; + + static EnabledState GetEnabledState(const Maybe<uint64_t>& aLauncherTs, + const Maybe<uint64_t>& aBrowserTs); + + LauncherResult<Disposition> Open(); + LauncherVoidResult WriteLauncherStartTimestamp(uint64_t aValue); + LauncherVoidResult WriteBrowserStartTimestamp(uint64_t aValue); + LauncherVoidResult WriteImageTimestamp(DWORD aTimestamp); + LauncherResult<bool> ClearLauncherStartTimestamp(); + LauncherResult<bool> ClearBrowserStartTimestamp(); + LauncherVoidResult ClearStartTimestamps(); + LauncherResult<Maybe<DWORD>> GetSavedImageTimestamp(); + LauncherResult<Maybe<uint64_t>> GetLauncherStartTimestamp(); + LauncherResult<Maybe<uint64_t>> GetBrowserStartTimestamp(); + + const std::wstring& ResolveLauncherValueName(); + const std::wstring& ResolveBrowserValueName(); + const std::wstring& ResolveImageTimestampValueName(); + const std::wstring& ResolveTelemetryValueName(); + + private: + Maybe<uint64_t> mLauncherTimestampToWrite; + Maybe<uint64_t> mBrowserTimestampToWrite; + + nsAutoRegKey mRegKey; + std::wstring mBinPath; + std::wstring mImageValueName; + std::wstring mBrowserValueName; + std::wstring mLauncherValueName; + std::wstring mTelemetryValueName; + + static const wchar_t kLauncherSubKeyPath[]; + static const wchar_t kLauncherSuffix[]; + static const wchar_t kBrowserSuffix[]; + static const wchar_t kImageTimestampSuffix[]; + static const wchar_t kTelemetrySuffix[]; +}; + +} // namespace mozilla + +#endif // mozilla_LauncherRegistryInfo_h diff --git a/toolkit/xre/MacApplicationDelegate.h b/toolkit/xre/MacApplicationDelegate.h new file mode 100644 index 0000000000..72cab8b86c --- /dev/null +++ b/toolkit/xre/MacApplicationDelegate.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// This file defines the interface between Cocoa-specific Obj-C++ and generic +// C++, so it itself cannot have any Obj-C bits in it. + +#ifndef MacApplicationDelegate_h_ +#define MacApplicationDelegate_h_ + +void EnsureUseCocoaDockAPI(void); +void SetupMacApplicationDelegate(void); +void ProcessPendingGetURLAppleEvents(void); +void DisableAppNap(void); + +#endif diff --git a/toolkit/xre/MacApplicationDelegate.mm b/toolkit/xre/MacApplicationDelegate.mm new file mode 100644 index 0000000000..ec841ab904 --- /dev/null +++ b/toolkit/xre/MacApplicationDelegate.mm @@ -0,0 +1,406 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +// NSApplication delegate for Mac OS X Cocoa API. + +// As of 10.4 Tiger, the system can send six kinds of Apple Events to an application; +// a well-behaved XUL app should have some kind of handling for all of them. +// +// See +// http://developer.apple.com/documentation/Cocoa/Conceptual/ScriptableCocoaApplications/SApps_handle_AEs/chapter_11_section_3.html +// for details. + +#import <Cocoa/Cocoa.h> +#import <Carbon/Carbon.h> + +#include "nsCOMPtr.h" +#include "nsINativeAppSupport.h" +#include "nsAppRunner.h" +#include "nsAppShell.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" +#include "nsIAppStartup.h" +#include "nsIObserverService.h" +#include "nsISupportsPrimitives.h" +#include "nsObjCExceptions.h" +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsCommandLine.h" +#include "nsIMacDockSupport.h" +#include "nsIStandaloneNativeMenu.h" +#include "nsILocalFileMac.h" +#include "nsString.h" +#include "nsCommandLineServiceMac.h" +#include "nsCommandLine.h" + +class AutoAutoreleasePool { + public: + AutoAutoreleasePool() { mLocalPool = [[NSAutoreleasePool alloc] init]; } + ~AutoAutoreleasePool() { [mLocalPool release]; } + + private: + NSAutoreleasePool* mLocalPool; +}; + +@interface MacApplicationDelegate : NSObject <NSApplicationDelegate> { +} + +@end + +static bool sProcessedGetURLEvent = false; + +// Methods that can be called from non-Objective-C code. + +// This is needed, on relaunch, to force the OS to use the "Cocoa Dock API" +// instead of the "Carbon Dock API". For more info see bmo bug 377166. +void EnsureUseCocoaDockAPI() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + [GeckoNSApplication sharedApplication]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +void DisableAppNap() { + // Prevent the parent process from entering App Nap. macOS does not put our + // child processes into App Nap and, as a result, when the parent is in + // App Nap, child processes continue to run normally generating IPC messages + // for the parent which can end up being queued. This can cause the browser + // to be unresponsive for a period of time after the App Nap until the parent + // process "catches up." NSAppSleepDisabled has to be set early during + // startup before the OS reads the value for the process. + [[NSUserDefaults standardUserDefaults] registerDefaults:@{ + @"NSAppSleepDisabled" : @YES, + }]; +} + +void SetupMacApplicationDelegate() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + // this is called during startup, outside an event loop, and therefore + // needs an autorelease pool to avoid cocoa object leakage (bug 559075) + AutoAutoreleasePool pool; + + // Ensure that ProcessPendingGetURLAppleEvents() doesn't regress bug 377166. + [GeckoNSApplication sharedApplication]; + + // This call makes it so that application:openFile: doesn't get bogus calls + // from Cocoa doing its own parsing of the argument string. And yes, we need + // to use a string with a boolean value in it. That's just how it works. + [[NSUserDefaults standardUserDefaults] setObject:@"NO" forKey:@"NSTreatUnknownArgumentsAsOpen"]; + + // Create the delegate. This should be around for the lifetime of the app. + id<NSApplicationDelegate> delegate = [[MacApplicationDelegate alloc] init]; + [[GeckoNSApplication sharedApplication] setDelegate:delegate]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +// Indirectly make the OS process any pending GetURL Apple events. This is +// done via _DPSNextEvent() (an undocumented AppKit function called from +// [NSApplication nextEventMatchingMask:untilDate:inMode:dequeue:]). Apple +// events are only processed if 'dequeue' is 'YES' -- so we need to call +// [NSApplication sendEvent:] on any event that gets returned. 'event' will +// never itself be an Apple event, and it may be 'nil' even when Apple events +// are processed. +void ProcessPendingGetURLAppleEvents() { + AutoAutoreleasePool pool; + bool keepSpinning = true; + while (keepSpinning) { + sProcessedGetURLEvent = false; + NSEvent* event = [NSApp nextEventMatchingMask:NSEventMaskAny + untilDate:nil + inMode:NSDefaultRunLoopMode + dequeue:YES]; + if (event) [NSApp sendEvent:event]; + keepSpinning = sProcessedGetURLEvent; + } +} + +@implementation MacApplicationDelegate + +- (id)init { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + if ((self = [super init])) { + NSAppleEventManager* aeMgr = [NSAppleEventManager sharedAppleEventManager]; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:kInternetEventClass + andEventID:kAEGetURL]; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:'WWW!' + andEventID:'OURL']; + + [aeMgr setEventHandler:self + andSelector:@selector(handleAppleEvent:withReplyEvent:) + forEventClass:kCoreEventClass + andEventID:kAEOpenDocuments]; + + if (![NSApp windowsMenu]) { + // If the application has a windows menu, it will keep it up to date and + // prepend the window list to the Dock menu automatically. + NSMenu* windowsMenu = [[NSMenu alloc] initWithTitle:@"Window"]; + [NSApp setWindowsMenu:windowsMenu]; + [windowsMenu release]; + } + } + return self; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(nil); +} + +- (void)dealloc { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK; + + NSAppleEventManager* aeMgr = [NSAppleEventManager sharedAppleEventManager]; + [aeMgr removeEventHandlerForEventClass:kInternetEventClass andEventID:kAEGetURL]; + [aeMgr removeEventHandlerForEventClass:'WWW!' andEventID:'OURL']; + [aeMgr removeEventHandlerForEventClass:kCoreEventClass andEventID:kAEOpenDocuments]; + [super dealloc]; + + NS_OBJC_END_TRY_ABORT_BLOCK; +} + +// The method that NSApplication calls upon a request to reopen, such as when +// the Dock icon is clicked and no windows are open. A "visible" window may be +// miniaturized, so we can't skip nsCocoaNativeReOpen() if 'flag' is 'true'. +- (BOOL)applicationShouldHandleReopen:(NSApplication*)theApp hasVisibleWindows:(BOOL)flag { + nsCOMPtr<nsINativeAppSupport> nas = NS_GetNativeAppSupport(); + NS_ENSURE_TRUE(nas, NO); + + // Go to the common Carbon/Cocoa reopen method. + nsresult rv = nas->ReOpen(); + NS_ENSURE_SUCCESS(rv, NO); + + // NO says we don't want NSApplication to do anything else for us. + return NO; +} + +// The method that NSApplication calls when documents are requested to be opened. +// It will be called once for each selected document. +- (BOOL)application:(NSApplication*)theApplication openFile:(NSString*)filename { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_RETURN; + + NSURL* url = [NSURL fileURLWithPath:filename]; + if (!url) return NO; + + NSString* urlString = [url absoluteString]; + if (!urlString) return NO; + + // Add the URL to any command line we're currently setting up. + if (CommandLineServiceMac::AddURLToCurrentCommandLine([urlString UTF8String])) return YES; + + nsCOMPtr<nsILocalFileMac> inFile; + nsresult rv = NS_NewLocalFileWithCFURL((CFURLRef)url, true, getter_AddRefs(inFile)); + if (NS_FAILED(rv)) return NO; + + nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine()); + + nsCString filePath; + rv = inFile->GetNativePath(filePath); + if (NS_FAILED(rv)) return NO; + + nsCOMPtr<nsIFile> workingDir; + rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); + if (NS_FAILED(rv)) { + // Couldn't find a working dir. Uh oh. Good job cmdline::Init can cope. + workingDir = nullptr; + } + + const char* argv[3] = {nullptr, "-file", filePath.get()}; + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); + if (NS_FAILED(rv)) return NO; + + if (NS_SUCCEEDED(cmdLine->Run())) return YES; + + return NO; + + NS_OBJC_END_TRY_ABORT_BLOCK_RETURN(NO); +} + +// The method that NSApplication calls when documents are requested to be printed +// from the Finder (under the "File" menu). +// It will be called once for each selected document. +- (BOOL)application:(NSApplication*)theApplication printFile:(NSString*)filename { + return NO; +} + +// Create the menu that shows up in the Dock. +- (NSMenu*)applicationDockMenu:(NSApplication*)sender { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NIL; + + // Create the NSMenu that will contain the dock menu items. + NSMenu* menu = [[[NSMenu alloc] initWithTitle:@""] autorelease]; + [menu setAutoenablesItems:NO]; + + // Add application-specific dock menu items. On error, do not insert the + // dock menu items. + nsresult rv; + nsCOMPtr<nsIMacDockSupport> dockSupport = + do_GetService("@mozilla.org/widget/macdocksupport;1", &rv); + if (NS_FAILED(rv) || !dockSupport) return menu; + + nsCOMPtr<nsIStandaloneNativeMenu> dockMenu; + rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenu)); + if (NS_FAILED(rv) || !dockMenu) return menu; + + // Determine if the dock menu items should be displayed. This also gives + // the menu the opportunity to update itself before display. + bool shouldShowItems; + rv = dockMenu->MenuWillOpen(&shouldShowItems); + if (NS_FAILED(rv) || !shouldShowItems) return menu; + + // Obtain a copy of the native menu. + NSMenu* nativeDockMenu; + rv = dockMenu->GetNativeMenu(reinterpret_cast<void**>(&nativeDockMenu)); + if (NS_FAILED(rv) || !nativeDockMenu) return menu; + + // Loop through the application-specific dock menu and insert its + // contents into the dock menu that we are building for Cocoa. + int numDockMenuItems = [nativeDockMenu numberOfItems]; + if (numDockMenuItems > 0) { + if ([menu numberOfItems] > 0) [menu addItem:[NSMenuItem separatorItem]]; + + for (int i = 0; i < numDockMenuItems; i++) { + NSMenuItem* itemCopy = [[nativeDockMenu itemAtIndex:i] copy]; + [menu addItem:itemCopy]; + [itemCopy release]; + } + } + + return menu; + + NS_OBJC_END_TRY_ABORT_BLOCK_NIL; +} + +- (void)applicationWillFinishLaunching:(NSNotification*)notification { + // We provide our own full screen menu item, so we don't want the OS providing + // one as well. + [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"NSFullScreenMenuItemEverywhere"]; +} + +// If we don't handle applicationShouldTerminate:, a call to [NSApp terminate:] +// (from the browser or from the OS) can result in an unclean shutdown. +- (NSApplicationTerminateReply)applicationShouldTerminate:(NSApplication*)sender { + nsCOMPtr<nsIObserverService> obsServ = do_GetService("@mozilla.org/observer-service;1"); + if (!obsServ) return NSTerminateNow; + + nsCOMPtr<nsISupportsPRBool> cancelQuit = do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + if (!cancelQuit) return NSTerminateNow; + + cancelQuit->SetData(false); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); + + bool abortQuit; + cancelQuit->GetData(&abortQuit); + if (abortQuit) return NSTerminateCancel; + + nsCOMPtr<nsIAppStartup> appService = do_GetService("@mozilla.org/toolkit/app-startup;1"); + if (appService) { + bool userAllowedQuit = true; + appService->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit); + if (!userAllowedQuit) { + return NSTerminateCancel; + } + } + + return NSTerminateNow; +} + +- (void)handleAppleEvent:(NSAppleEventDescriptor*)event + withReplyEvent:(NSAppleEventDescriptor*)replyEvent { + if (!event) return; + + AutoAutoreleasePool pool; + + bool isGetURLEvent = ([event eventClass] == kInternetEventClass && [event eventID] == kAEGetURL); + if (isGetURLEvent) sProcessedGetURLEvent = true; + + if (isGetURLEvent || ([event eventClass] == 'WWW!' && [event eventID] == 'OURL')) { + NSString* urlString = [[event paramDescriptorForKeyword:keyDirectObject] stringValue]; + NSURL* url = [NSURL URLWithString:urlString]; + + [self openURL:url]; + } else if ([event eventClass] == kCoreEventClass && [event eventID] == kAEOpenDocuments) { + NSAppleEventDescriptor* fileListDescriptor = [event paramDescriptorForKeyword:keyDirectObject]; + if (!fileListDescriptor) return; + + // Descriptor list indexing is one-based... + NSInteger numberOfFiles = [fileListDescriptor numberOfItems]; + for (NSInteger i = 1; i <= numberOfFiles; i++) { + NSString* urlString = [[fileListDescriptor descriptorAtIndex:i] stringValue]; + if (!urlString) continue; + + // We need a path, not a URL + NSURL* url = [NSURL URLWithString:urlString]; + if (!url) continue; + + [self application:NSApp openFile:[url path]]; + } + } +} + +- (BOOL)application:(NSApplication*)application + willContinueUserActivityWithType:(NSString*)userActivityType { + return [userActivityType isEqualToString:NSUserActivityTypeBrowsingWeb]; +} + +- (BOOL)application:(NSApplication*)application + continueUserActivity:(NSUserActivity*)userActivity +#if defined(MAC_OS_X_VERSION_10_14) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_14 + restorationHandler:(void (^)(NSArray<id<NSUserActivityRestoring>>*))restorationHandler { +#else + restorationHandler:(void (^)(NSArray*))restorationHandler { +#endif + if (![userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) { + return NO; + } + + return [self openURL:userActivity.webpageURL]; +} + +- (void)application:(NSApplication*)application + didFailToContinueUserActivityWithType:(NSString*)userActivityType + error:(NSError*)error { + NSLog(@"Failed to continue user activity %@: %@", userActivityType, error); +} + +- (BOOL)openURL:(NSURL*)url { + if (!url || !url.scheme || [url.scheme caseInsensitiveCompare:@"chrome"] == NSOrderedSame) { + return NO; + } + + const char* const urlString = [[url absoluteString] UTF8String]; + // Add the URL to any command line we're currently setting up. + if (CommandLineServiceMac::AddURLToCurrentCommandLine(urlString)) { + return NO; + } + + nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine()); + nsCOMPtr<nsIFile> workingDir; + nsresult rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(workingDir)); + if (NS_FAILED(rv)) { + // Couldn't find a working dir. Uh oh. Good job cmdline::Init can cope. + workingDir = nullptr; + } + + const char* argv[3] = {nullptr, "-url", urlString}; + rv = cmdLine->Init(3, argv, workingDir, nsICommandLine::STATE_REMOTE_EXPLICIT); + if (NS_FAILED(rv)) { + return NO; + } + rv = cmdLine->Run(); + if (NS_FAILED(rv)) { + return NO; + } + + return YES; +} + +@end diff --git a/toolkit/xre/MacAutoreleasePool.h b/toolkit/xre/MacAutoreleasePool.h new file mode 100644 index 0000000000..6c574beef2 --- /dev/null +++ b/toolkit/xre/MacAutoreleasePool.h @@ -0,0 +1,31 @@ +/* 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 MacAutoreleasePool_h_ +#define MacAutoreleasePool_h_ + +// This needs to be #include-able from non-ObjC code in nsAppRunner.cpp +#ifdef __OBJC__ +@class NSAutoreleasePool; +#else +class NSAutoreleasePool; +#endif + +namespace mozilla { + +class MacAutoreleasePool { + public: + MacAutoreleasePool(); + ~MacAutoreleasePool(); + + private: + NSAutoreleasePool* mPool; + + MacAutoreleasePool(const MacAutoreleasePool&); + void operator=(const MacAutoreleasePool&); +}; + +} // namespace mozilla + +#endif // MacAutoreleasePool_h_ diff --git a/toolkit/xre/MacAutoreleasePool.mm b/toolkit/xre/MacAutoreleasePool.mm new file mode 100644 index 0000000000..0d2a47b3d8 --- /dev/null +++ b/toolkit/xre/MacAutoreleasePool.mm @@ -0,0 +1,17 @@ +/* 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/. */ + +#include "MacAutoreleasePool.h" +#include "nsDebug.h" + +#import <Foundation/Foundation.h> + +using mozilla::MacAutoreleasePool; + +MacAutoreleasePool::MacAutoreleasePool() { + mPool = [[NSAutoreleasePool alloc] init]; + NS_ASSERTION(mPool != nullptr, "failed to create pool, objects will leak"); +} + +MacAutoreleasePool::~MacAutoreleasePool() { [mPool release]; } diff --git a/toolkit/xre/MacLaunchHelper.h b/toolkit/xre/MacLaunchHelper.h new file mode 100644 index 0000000000..f8dc75ee4d --- /dev/null +++ b/toolkit/xre/MacLaunchHelper.h @@ -0,0 +1,23 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 MacLaunchHelper_h_ +#define MacLaunchHelper_h_ + +#include <stdint.h> + +#include <unistd.h> + +extern "C" { +/** + * Passing an aPid parameter to LaunchChildMac will wait for the launched + * process to terminate. When the process terminates, aPid will be set to the + * pid of the terminated process to confirm that it executed successfully. + */ +void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid = 0); +bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid = 0); +} + +#endif diff --git a/toolkit/xre/MacLaunchHelper.mm b/toolkit/xre/MacLaunchHelper.mm new file mode 100644 index 0000000000..ec570ffab1 --- /dev/null +++ b/toolkit/xre/MacLaunchHelper.mm @@ -0,0 +1,118 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "MacLaunchHelper.h" + +#include "MacAutoreleasePool.h" +#include "mozilla/UniquePtr.h" +#include "nsMemory.h" + +#include <Cocoa/Cocoa.h> +#include <crt_externs.h> +#include <ServiceManagement/ServiceManagement.h> +#include <Security/Authorization.h> +#include <spawn.h> +#include <stdio.h> + +using namespace mozilla; + +void LaunchChildMac(int aArgc, char** aArgv, pid_t* aPid) { + MacAutoreleasePool pool; + + @try { + NSString* launchPath = [NSString stringWithUTF8String:aArgv[0]]; + NSMutableArray* arguments = [NSMutableArray arrayWithCapacity:aArgc - 1]; + for (int i = 1; i < aArgc; i++) { + [arguments addObject:[NSString stringWithUTF8String:aArgv[i]]]; + } + NSTask* child = [NSTask launchedTaskWithLaunchPath:launchPath arguments:arguments]; + if (aPid) { + *aPid = [child processIdentifier]; + // We used to use waitpid to wait for the process to terminate. This is + // incompatible with NSTask and we wait for the process to exit here + // instead. + [child waitUntilExit]; + } + } @catch (NSException* e) { + NSLog(@"%@: %@", e.name, e.reason); + } +} + +BOOL InstallPrivilegedHelper() { + AuthorizationRef authRef = NULL; + OSStatus status = AuthorizationCreate( + NULL, kAuthorizationEmptyEnvironment, + kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed, &authRef); + if (status != errAuthorizationSuccess) { + // AuthorizationCreate really shouldn't fail. + NSLog(@"AuthorizationCreate failed! NSOSStatusErrorDomain / %d", (int)status); + return NO; + } + + BOOL result = NO; + AuthorizationItem authItem = {kSMRightBlessPrivilegedHelper, 0, NULL, 0}; + AuthorizationRights authRights = {1, &authItem}; + AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | + kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights; + + // Obtain the right to install our privileged helper tool. + status = + AuthorizationCopyRights(authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL); + if (status != errAuthorizationSuccess) { + NSLog(@"AuthorizationCopyRights failed! NSOSStatusErrorDomain / %d", (int)status); + } else { + CFErrorRef cfError; + // This does all the work of verifying the helper tool against the + // application and vice-versa. Once verification has passed, the embedded + // launchd.plist is extracted and placed in /Library/LaunchDaemons and then + // loaded. The executable is placed in /Library/PrivilegedHelperTools. + result = (BOOL)SMJobBless(kSMDomainSystemLaunchd, (CFStringRef) @"org.mozilla.updater", authRef, + &cfError); + if (!result) { + NSLog(@"Unable to install helper!"); + CFRelease(cfError); + } + } + + return result; +} + +void AbortElevatedUpdate() { + mozilla::MacAutoreleasePool pool; + + id updateServer = nil; + int currTry = 0; + const int numRetries = 10; // Number of IPC connection retries before + // giving up. + while (currTry < numRetries) { + @try { + updateServer = (id)[NSConnection + rootProxyForConnectionWithRegisteredName:@"org.mozilla.updater.server" + host:nil + usingNameServer:[NSSocketPortNameServer sharedInstance]]; + if (updateServer && [updateServer respondsToSelector:@selector(abort)]) { + [updateServer performSelector:@selector(abort)]; + return; + } + NSLog(@"Server doesn't exist or doesn't provide correct selectors."); + sleep(1); // Wait 1 second. + currTry++; + } @catch (NSException* e) { + NSLog(@"Encountered exception, retrying: %@: %@", e.name, e.reason); + sleep(1); // Wait 1 second. + currTry++; + } + } + NSLog(@"Unable to clean up updater."); +} + +bool LaunchElevatedUpdate(int aArgc, char** aArgv, pid_t* aPid) { + LaunchChildMac(aArgc, aArgv, aPid); + bool didSucceed = InstallPrivilegedHelper(); + if (!didSucceed) { + AbortElevatedUpdate(); + } + return didSucceed; +} diff --git a/toolkit/xre/ModuleEvaluator.cpp b/toolkit/xre/ModuleEvaluator.cpp new file mode 100644 index 0000000000..ccc4fba686 --- /dev/null +++ b/toolkit/xre/ModuleEvaluator.cpp @@ -0,0 +1,253 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "ModuleEvaluator.h" + +#include <algorithm> // For std::find() +#include <type_traits> + +#include <windows.h> +#include <shlobj.h> + +#include "mozilla/ArrayUtils.h" +#include "mozilla/ModuleVersionInfo.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WinDllServices.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsReadableUtils.h" +#include "nsWindowsHelpers.h" +#include "nsXULAppAPI.h" + +// Fills a Vector with keyboard layout DLLs found in the registry. +// These are leaf names only, not full paths. Here we will convert them to +// lowercase before returning, to facilitate case-insensitive searches. +// On error, this may return partial results. +static Vector<nsString> GetKeyboardLayoutDlls() { + Vector<nsString> result; + + HKEY rawKey; + if (::RegOpenKeyExW(HKEY_LOCAL_MACHINE, + L"SYSTEM\\CurrentControlSet\\Control\\Keyboard Layouts", + 0, KEY_ENUMERATE_SUB_KEYS, &rawKey) != ERROR_SUCCESS) { + return result; + } + nsAutoRegKey key(rawKey); + + DWORD iKey = 0; + wchar_t strTemp[MAX_PATH] = {}; + while (true) { + DWORD strTempSize = ArrayLength(strTemp); + if (RegEnumKeyExW(rawKey, iKey, strTemp, &strTempSize, nullptr, nullptr, + nullptr, nullptr) != ERROR_SUCCESS) { + // ERROR_NO_MORE_ITEMS or a real error: bail with what we have. + return result; + } + iKey++; + + strTempSize = sizeof(strTemp); + if (::RegGetValueW(rawKey, strTemp, L"Layout File", RRF_RT_REG_SZ, nullptr, + strTemp, &strTempSize) == ERROR_SUCCESS && + strTempSize) { + nsString ws(strTemp, ((strTempSize + 1) / sizeof(wchar_t)) - 1); + ToLowerCase(ws); // To facilitate case-insensitive searches + Unused << result.emplaceBack(std::move(ws)); + } + } + + return result; +} + +namespace mozilla { + +/* static */ +bool ModuleEvaluator::ResolveKnownFolder(REFKNOWNFOLDERID aFolderId, + nsIFile** aOutFile) { + if (!aOutFile) { + return false; + } + + *aOutFile = nullptr; + + // Since we're running off main thread, we can't use NS_GetSpecialDirectory + PWSTR rawPath = nullptr; + HRESULT hr = + ::SHGetKnownFolderPath(aFolderId, KF_FLAG_DEFAULT, nullptr, &rawPath); + if (FAILED(hr)) { + return false; + } + + using ShellStringUniquePtr = + UniquePtr<std::remove_pointer_t<PWSTR>, CoTaskMemFreeDeleter>; + + ShellStringUniquePtr path(rawPath); + + nsresult rv = NS_NewLocalFile(nsDependentString(path.get()), false, aOutFile); + return NS_SUCCEEDED(rv); +} + +ModuleEvaluator::ModuleEvaluator() + : mKeyboardLayoutDlls(GetKeyboardLayoutDlls()) { + MOZ_ASSERT(XRE_IsParentProcess()); + +#if defined(_M_IX86) + // We want to resolve to SYSWOW64 when applicable + REFKNOWNFOLDERID systemFolderId = FOLDERID_SystemX86; +#else + REFKNOWNFOLDERID systemFolderId = FOLDERID_System; +#endif // defined(_M_IX86) + + bool resolveOk = + ResolveKnownFolder(systemFolderId, getter_AddRefs(mSysDirectory)); + MOZ_ASSERT(resolveOk); + if (!resolveOk) { + return; + } + + nsCOMPtr<nsIFile> winSxSDir; + resolveOk = ResolveKnownFolder(FOLDERID_Windows, getter_AddRefs(winSxSDir)); + MOZ_ASSERT(resolveOk); + if (!resolveOk) { + return; + } + + nsresult rv = winSxSDir->Append(u"WinSxS"_ns); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + mWinSxSDirectory = std::move(winSxSDir); + + nsCOMPtr<nsIFile> exeFile; + rv = XRE_GetBinaryPath(getter_AddRefs(exeFile)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + rv = exeFile->GetParent(getter_AddRefs(mExeDirectory)); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + nsAutoString exePath; + rv = exeFile->GetPath(exePath); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + return; + } + + ModuleVersionInfo exeVi; + if (!exeVi.GetFromImage(exePath)) { + return; + } + + mExeVersion = Some(ModuleVersion(exeVi.mFileVersion.Version64())); +} + +ModuleEvaluator::operator bool() const { + return mExeVersion.isSome() && mExeDirectory && mSysDirectory && + mWinSxSDirectory; +} + +Maybe<ModuleTrustFlags> ModuleEvaluator::GetTrust( + const ModuleRecord& aModuleRecord) const { + MOZ_ASSERT(XRE_IsParentProcess()); + + // We start by checking authenticode signatures, as the presence of any + // signature will produce an immediate pass/fail. + if (aModuleRecord.mVendorInfo.isSome() && + aModuleRecord.mVendorInfo.ref().mSource == + VendorInfo::Source::Signature) { + const nsString& signedBy = aModuleRecord.mVendorInfo.ref().mVendor; + + if (signedBy.EqualsLiteral("Microsoft Windows")) { + return Some(ModuleTrustFlags::MicrosoftWindowsSignature); + } else if (signedBy.EqualsLiteral("Microsoft Corporation")) { + return Some(ModuleTrustFlags::MicrosoftWindowsSignature); + } else if (signedBy.EqualsLiteral("Mozilla Corporation")) { + return Some(ModuleTrustFlags::MozillaSignature); + } else { + // Being signed by somebody who is neither Microsoft nor us is an + // automatic and immediate disqualification. + return Some(ModuleTrustFlags::None); + } + } + + const nsCOMPtr<nsIFile>& dllFile = aModuleRecord.mResolvedDosName; + MOZ_ASSERT(!!dllFile); + if (!dllFile) { + return Nothing(); + } + + nsAutoString dllLeafLower; + if (NS_FAILED(dllFile->GetLeafName(dllLeafLower))) { + return Nothing(); + } + + ToLowerCase(dllLeafLower); // To facilitate case-insensitive searching + + // The JIT profiling module doesn't really have any other practical way to + // match; hard-code it as being trusted. + if (dllLeafLower.EqualsLiteral("jitpi.dll")) { + return Some(ModuleTrustFlags::JitPI); + } + + ModuleTrustFlags result = ModuleTrustFlags::None; + + nsresult rv; + bool contained; + + // Is the DLL in the system directory? + rv = mSysDirectory->Contains(dllFile, &contained); + if (NS_SUCCEEDED(rv) && contained) { + result |= ModuleTrustFlags::SystemDirectory; + } + + // Is the DLL in the WinSxS directory? Some Microsoft DLLs (e.g. comctl32) are + // loaded from here and don't have digital signatures. So while this is not a + // guarantee of trustworthiness, but is at least as valid as system32. + rv = mWinSxSDirectory->Contains(dllFile, &contained); + if (NS_SUCCEEDED(rv) && contained) { + result |= ModuleTrustFlags::WinSxSDirectory; + } + + // Is it a keyboard layout DLL? + if (std::find(mKeyboardLayoutDlls.begin(), mKeyboardLayoutDlls.end(), + dllLeafLower) != mKeyboardLayoutDlls.end()) { + result |= ModuleTrustFlags::KeyboardLayout; + // This doesn't guarantee trustworthiness by itself. Keyboard layouts also + // must be in the system directory. + } + + if (aModuleRecord.mVendorInfo.isSome() && + aModuleRecord.mVendorInfo.ref().mSource == + VendorInfo::Source::VersionInfo) { + const nsString& companyName = aModuleRecord.mVendorInfo.ref().mVendor; + + if (companyName.EqualsLiteral("Microsoft Corporation")) { + result |= ModuleTrustFlags::MicrosoftVersion; + } + } + + rv = mExeDirectory->Contains(dllFile, &contained); + if (NS_SUCCEEDED(rv) && contained) { + result |= ModuleTrustFlags::FirefoxDirectory; + + // If the DLL is in the Firefox directory, does it also share the Firefox + // version info? + if (mExeVersion.isSome() && aModuleRecord.mVersion.isSome() && + mExeVersion.value() == aModuleRecord.mVersion.value()) { + result |= ModuleTrustFlags::FirefoxDirectoryAndVersion; + } + } + + return Some(result); +} + +} // namespace mozilla diff --git a/toolkit/xre/ModuleEvaluator.h b/toolkit/xre/ModuleEvaluator.h new file mode 100644 index 0000000000..e1f623cb9e --- /dev/null +++ b/toolkit/xre/ModuleEvaluator.h @@ -0,0 +1,48 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ModuleEvaluator_h +#define mozilla_ModuleEvaluator_h + +#include "mozilla/Attributes.h" +#include "mozilla/Maybe.h" +#include "mozilla/UntrustedModulesData.h" +#include "mozilla/Vector.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsCOMPtr.h" +#include "nsIFile.h" +#include "nsString.h" + +namespace mozilla { + +class ModuleRecord; + +/** + * This class performs trustworthiness evaluation for incoming DLLs. + */ +class MOZ_RAII ModuleEvaluator final { + public: + ModuleEvaluator(); + + explicit operator bool() const; + + Maybe<ModuleTrustFlags> GetTrust(const ModuleRecord& aModuleRecord) const; + + private: + static bool ResolveKnownFolder(REFKNOWNFOLDERID aFolderId, + nsIFile** aOutFile); + + private: + Maybe<ModuleVersion> mExeVersion; // Version number of the running EXE image + nsCOMPtr<nsIFile> mExeDirectory; + nsCOMPtr<nsIFile> mSysDirectory; + nsCOMPtr<nsIFile> mWinSxSDirectory; + Vector<nsString> mKeyboardLayoutDlls; +}; + +} // namespace mozilla + +#endif // mozilla_ModuleEvaluator_h diff --git a/toolkit/xre/ModuleVersionInfo.cpp b/toolkit/xre/ModuleVersionInfo.cpp new file mode 100644 index 0000000000..b6f7b0925d --- /dev/null +++ b/toolkit/xre/ModuleVersionInfo.cpp @@ -0,0 +1,111 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "ModuleVersionInfo.h" + +#include "mozilla/UniquePtr.h" + +namespace mozilla { + +/** + * Gets a string value from a version info block with the specified translation + * and field name. + * + * @param aBlock [in] The binary version resource block + * @param aTranslation [in] The translation ID as obtained in the version + * translation list. + * @param aFieldName [in] Null-terminated name of the desired field + * @param aResult [out] Receives the string value, if successful. + * @return true if successful. aResult is unchanged upon failure. + */ +static bool QueryStringValue(const void* aBlock, DWORD aTranslation, + const wchar_t* aFieldName, nsAString& aResult) { + nsAutoString path; + path.AppendPrintf("\\StringFileInfo\\%02X%02X%02X%02X\\%S", + (aTranslation & 0x0000ff00) >> 8, + (aTranslation & 0x000000ff), + (aTranslation & 0xff000000) >> 24, + (aTranslation & 0x00ff0000) >> 16, aFieldName); + + wchar_t* lpBuffer = nullptr; + UINT len = 0; + if (!::VerQueryValueW(aBlock, path.get(), (PVOID*)&lpBuffer, &len)) { + return false; + } + aResult.Assign(lpBuffer, (size_t)len - 1); + return true; +} + +/** + * Searches through translations in the version resource for the requested + * field. English(US) is preferred, otherwise we take the first translation + * that succeeds. + * + * @param aBlock [in] The binary version resource block + * @param aTranslations [in] The list of translation IDs available + * @param aNumTrans [in] Number of items in aTranslations + * @param aFieldName [in] Null-terminated name of the desired field + * @param aResult [in] Receives the string value, if successful. + * @return true if successful. aResult is unchanged upon failure. + */ +static bool QueryStringValue(const void* aBlock, const DWORD* aTranslations, + size_t aNumTrans, const wchar_t* aFieldName, + nsAString& aResult) { + static const DWORD kPreferredTranslation = + 0x04b00409; // English (US), Unicode + if (QueryStringValue(aBlock, kPreferredTranslation, aFieldName, aResult)) { + return true; + } + for (size_t i = 0; i < aNumTrans; ++i) { + if (QueryStringValue(aBlock, aTranslations[i], aFieldName, aResult)) { + return true; + } + } + return false; +} + +bool ModuleVersionInfo::GetFromImage(const nsAString& aPath) { + nsString path(aPath); + DWORD infoSize = GetFileVersionInfoSizeW(path.get(), nullptr); + if (!infoSize) { + return false; + } + + auto verInfo = MakeUnique<BYTE[]>(infoSize); + if (!::GetFileVersionInfoW(path.get(), 0, infoSize, verInfo.get())) { + return false; + } + + VS_FIXEDFILEINFO* vInfo = nullptr; + UINT vInfoLen = 0; + if (::VerQueryValueW(verInfo.get(), L"\\", (LPVOID*)&vInfo, &vInfoLen)) { + mFileVersion = + VersionNumber(vInfo->dwFileVersionMS, vInfo->dwFileVersionLS); + mProductVersion = + VersionNumber(vInfo->dwProductVersionMS, vInfo->dwProductVersionLS); + } + + // Note that regardless the character set indicated, strings are always + // returned as Unicode by the Windows APIs. + DWORD* pTrans = nullptr; + UINT cbTrans = 0; + if (::VerQueryValueW(verInfo.get(), L"\\VarFileInfo\\Translation", + (PVOID*)&pTrans, &cbTrans)) { + size_t numTrans = cbTrans / sizeof(DWORD); + QueryStringValue(verInfo.get(), pTrans, numTrans, L"CompanyName", + mCompanyName); + QueryStringValue(verInfo.get(), pTrans, numTrans, L"ProductName", + mProductName); + QueryStringValue(verInfo.get(), pTrans, numTrans, L"LegalCopyright", + mLegalCopyright); + QueryStringValue(verInfo.get(), pTrans, numTrans, L"FileDescription", + mFileDescription); + } + + return true; +} + +} // namespace mozilla diff --git a/toolkit/xre/ModuleVersionInfo.h b/toolkit/xre/ModuleVersionInfo.h new file mode 100644 index 0000000000..2b46817417 --- /dev/null +++ b/toolkit/xre/ModuleVersionInfo.h @@ -0,0 +1,71 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_ModuleVersionInfo_h +#define mozilla_ModuleVersionInfo_h + +#include <windows.h> +#include "nsString.h" + +namespace mozilla { + +// Obtains basic version info from a module image's version info resource. +class ModuleVersionInfo { + public: + // We favor English(US) for these fields, otherwise we take the first + // translation provided in the version resource. + nsString mCompanyName; + nsString mProductName; + nsString mLegalCopyright; + nsString mFileDescription; + + // Represents an A.B.C.D style version number, internally stored as a uint64_t + class VersionNumber { + uint64_t mVersion64 = 0; + + public: + VersionNumber() = default; + + VersionNumber(DWORD aMostSig, DWORD aLeastSig) + : mVersion64((uint64_t)aMostSig << 32 | aLeastSig) {} + + uint16_t A() const { + return (uint16_t)((mVersion64 & 0xffff000000000000) >> 48); + } + + uint16_t B() const { + return (uint16_t)((mVersion64 & 0x0000ffff00000000) >> 32); + } + + uint16_t C() const { + return (uint16_t)((mVersion64 & 0x00000000ffff0000) >> 16); + } + + uint16_t D() const { return (uint16_t)(mVersion64 & 0x000000000000ffff); } + + uint64_t Version64() const { return mVersion64; } + + bool operator==(const VersionNumber& aOther) const { + return mVersion64 == aOther.mVersion64; + } + + nsCString ToString() const { + nsCString ret; + ret.AppendPrintf("%d.%d.%d.%d", (int)A(), (int)B(), (int)C(), (int)D()); + return ret; + } + }; + + VersionNumber mFileVersion; + VersionNumber mProductVersion; + + // Returns false if it has no version resource or has no fixed version info. + bool GetFromImage(const nsAString& aPath); +}; + +} // namespace mozilla + +#endif // mozilla_ModuleVersionInfo_h diff --git a/toolkit/xre/MultiInstanceLock.cpp b/toolkit/xre/MultiInstanceLock.cpp new file mode 100644 index 0000000000..eb1cd01ce6 --- /dev/null +++ b/toolkit/xre/MultiInstanceLock.cpp @@ -0,0 +1,197 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include "MultiInstanceLock.h" + +#include "commonupdatedir.h" // for GetInstallHash +#include "mozilla/UniquePtr.h" +#include "nsPrintfCString.h" +#include "nsPromiseFlatString.h" +#include "updatedefines.h" // for NS_t* definitions + +#ifndef XP_WIN +# include <fcntl.h> +# include <sys/stat.h> +# include <sys/types.h> +#endif + +namespace mozilla { + +static bool GetLockFileName(const char* nameToken, const char16_t* installPath, + nsCString& filePath) { + mozilla::UniquePtr<NS_tchar[]> pathHash; + if (!GetInstallHash(installPath, MOZ_APP_VENDOR, pathHash)) { + return false; + } + +#ifdef XP_WIN + // On Windows, the lock file is placed at the path + // ProgramData\[vendor]\[nameToken]-[pathHash], so first we need to get the + // ProgramData path and then append our directory and the file name. + PWSTR programDataPath; + HRESULT hr = SHGetKnownFolderPath(FOLDERID_ProgramData, KF_FLAG_CREATE, + nullptr, &programDataPath); + if (FAILED(hr)) { + return false; + } + mozilla::UniquePtr<wchar_t, CoTaskMemFreeDeleter> programDataPathUnique( + programDataPath); + + filePath = nsPrintfCString("%S\\%s\\%s-%S", programDataPath, MOZ_APP_VENDOR, + nameToken, pathHash.get()); + +#else + // On POSIX platforms the base path is /tmp/[vendor][nameToken]-[pathHash]. + filePath = nsPrintfCString("/tmp/%s%s-%s", MOZ_APP_VENDOR, nameToken, + pathHash.get()); + +#endif + + return true; +} + +MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken, + const char16_t* installPath) { + nsCString filePath; + GetLockFileName(nameToken, installPath, filePath); + + // Open a file handle with full privileges and sharing, and then attempt to + // take a shared (nonexclusive, read-only) lock on it. +#ifdef XP_WIN + HANDLE h = + ::CreateFileW(PromiseFlatString(NS_ConvertUTF8toUTF16(filePath)).get(), + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, + nullptr, OPEN_ALWAYS, FILE_FLAG_DELETE_ON_CLOSE, nullptr); + if (h != INVALID_HANDLE_VALUE) { + // The LockFileEx functions always require an OVERLAPPED structure even + // though we did not open the lock file for overlapped I/O. + OVERLAPPED o = {0}; + if (!::LockFileEx(h, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) { + CloseHandle(h); + h = INVALID_HANDLE_VALUE; + } + } + return h; + +#else + int fd = ::open(PromiseFlatCString(filePath).get(), + O_CLOEXEC | O_CREAT | O_NOFOLLOW, + S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH); + if (fd != -1) { + // We would like to ensure that the lock file is deleted when we are done + // with it. The normal way to do that would be to call unlink on it right + // now, but that would immediately delete the name from the file system, and + // we need other instances to be able to open that name and get the same + // inode, so we can't unlink the file before we're done with it. This means + // we accept some unreliability in getting the file deleted, but it's a zero + // byte file in the tmp directory, so having it stay around isn't the worst. + struct flock l = {0}; + l.l_start = 0; + l.l_len = 0; + l.l_type = F_RDLCK; + if (::fcntl(fd, F_SETLK, &l)) { + ::close(fd); + fd = -1; + } + } + return fd; + +#endif +} + +void ReleaseMultiInstanceLock(MultiInstLockHandle lock) { + if (lock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) { +#ifdef XP_WIN + OVERLAPPED o = {0}; + ::UnlockFileEx(lock, 0, 1, 0, &o); + // We've used FILE_FLAG_DELETE_ON_CLOSE, so if we are the last instance + // with a handle on the lock file, closing it here will delete it. + ::CloseHandle(lock); + +#else + // If we're the last instance, then unlink the lock file. There is a race + // condition here that may cause an instance to fail to open the same inode + // as another even though they use the same path, but there's no reasonable + // way to avoid that without skipping deleting the file at all, so we accept + // that risk. + bool otherInstance = true; + if (IsOtherInstanceRunning(lock, &otherInstance) && !otherInstance) { + // Recover the file's path so we can unlink it. + // There's no error checking in here because we're content to let the file + // hang around if any of this fails (which can happen if for example we're + // on a system where /proc/self/fd does not exist); this is a zero-byte + // file in the tmp directory after all. + UniquePtr<NS_tchar[]> linkPath = MakeUnique<NS_tchar[]>(MAXPATHLEN + 1); + NS_tsnprintf(linkPath.get(), MAXPATHLEN + 1, "/proc/self/fd/%d", lock); + UniquePtr<NS_tchar[]> lockFilePath = + MakeUnique<NS_tchar[]>(MAXPATHLEN + 1); + if (::readlink(linkPath.get(), lockFilePath.get(), MAXPATHLEN + 1) != + -1) { + ::unlink(lockFilePath.get()); + } + } + // Now close the lock file, which will release the lock. + ::close(lock); +#endif + } +} + +bool IsOtherInstanceRunning(MultiInstLockHandle lock, bool* aResult) { + // Every running instance has opened a readonly lock, and read locks prevent + // write locks from being opened, so to see if we are the only instance, we + // attempt to take a write lock, and if it succeeds then that must mean there + // are no other read locks open and therefore no other instances. + if (lock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) { + return false; + } + +#ifdef XP_WIN + // We need to release the lock we're holding before we would be allowed to + // take an exclusive lock, and if that succeeds we need to release it too + // in order to get our shared lock back. This procedure is not atomic, so we + // accept the risk of the scheduler deciding to ruin our day between these + // operations; we'd get a false negative in a different instance's check. + OVERLAPPED o = {0}; + // Release our current shared lock. + if (!::UnlockFileEx(lock, 0, 1, 0, &o)) { + return false; + } + // Attempt to take an exclusive lock. + bool rv = false; + if (::LockFileEx(lock, LOCKFILE_EXCLUSIVE_LOCK | LOCKFILE_FAIL_IMMEDIATELY, 0, + 1, 0, &o)) { + // We got the exclusive lock, so now release it. + ::UnlockFileEx(lock, 0, 1, 0, &o); + *aResult = false; + rv = true; + } else if (::GetLastError() == ERROR_LOCK_VIOLATION) { + // We didn't get the exclusive lock because of outstanding shared locks. + *aResult = true; + rv = true; + } + // Attempt to reclaim the shared lock we released at the beginning. + if (!::LockFileEx(lock, LOCKFILE_FAIL_IMMEDIATELY, 0, 1, 0, &o)) { + rv = false; + } + return rv; + +#else + // See if we would be allowed to set a write lock (no need to actually do so). + struct flock l = {0}; + l.l_start = 0; + l.l_len = 0; + l.l_type = F_WRLCK; + if (::fcntl(lock, F_GETLK, &l)) { + return false; + } + *aResult = l.l_type != F_UNLCK; + return true; + +#endif +} + +}; // namespace mozilla diff --git a/toolkit/xre/MultiInstanceLock.h b/toolkit/xre/MultiInstanceLock.h new file mode 100644 index 0000000000..ccc4702c1c --- /dev/null +++ b/toolkit/xre/MultiInstanceLock.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 MULTIINSTANCELOCK_H +#define MULTIINSTANCELOCK_H + +#ifdef XP_WIN +# include <windows.h> +#endif + +// These functions manage "multi-instance locks", which are a type of lock +// specifically designed to allow instances of an application, process, or other +// task to detect when other instances relevant to them are running. Each +// instance opens a lock and holds it for the duration of the task of interest +// (which may be the lifetime of the process, or a shorter period). Then while +// the lock is open, it can be used to check whether any other instances of the +// same task are currently running out of the same copy of the binary, in the +// context of any OS user. A process can open any number of locks, so long as +// they use different names. It is necessary for the process to have permission +// to create files in /tmp/ on POSIX systems or ProgramData\[vendor]\ on +// Windows, so this mechanism may not work for sandboxed processes. + +// The implementation is based on file locking. An empty file is created in a +// systemwide (not per-user) location, and a shared (read) lock is taken on that +// file; the value that OpenMultiInstanceLock() returns is the file +// handle/descriptor. When you call IsOtherInstanceRunning(), it will attempt to +// convert that shared lock into an exclusive (write) lock. If that operation +// would succeed, it means that there must not be any other shared locks +// currently taken on that file, so we know there are no other instances +// running. This is a more complex design than most file locks or most other +// concurrency mechanisms, but it is necessary for this use case because of the +// requirement that an instance must be able to detect other instances that were +// started later than it was. If, say, a mutex were used, or another kind of +// exclusive lock, then the first instance that tried to take it would succeed, +// and be unable to tell that another instance had tried to take it later and +// failed. This mechanism allows any number of instances started at any time in +// relation to one another to always be able to detect that the others exist +// (although it does not allow you to know how many others exist). The lock is +// guaranteed to be released if the process holding it crashes or is exec'd into +// something else, because the file is closed when that happens. The file itself +// is not necessarily always deleted on POSIX, because it isn't possible (within +// reason) to guarantee that unlink() is called, but the file is empty and +// created in the /tmp directory, so should not be a serious problem. + +namespace mozilla { + +#ifdef XP_WIN +using MultiInstLockHandle = HANDLE; +# define MULTI_INSTANCE_LOCK_HANDLE_ERROR INVALID_HANDLE_VALUE +#else +using MultiInstLockHandle = int; +# define MULTI_INSTANCE_LOCK_HANDLE_ERROR -1 +#endif + +/* + * nameToken should be a string very briefly naming the lock you are creating + * creating, and it should be unique except for across multiple instances of the + * same application. The vendor name is included in the generated path, so it + * doesn't need to be present in your supplied name. Try to keep this name sort + * of short, ideally under about 64 characters, because creating the lock will + * fail if the final path string (the token + the path hash + the vendor name) + * is longer than the platform's maximum path and/or path component length. + * + * installPath should be the path to the directory containing the application, + * which will be used to form a path specific to that installation. + * + * Returns MULTI_INSTANCE_LOCK_HANDLE_ERROR upon failure, or a handle which can + * later be passed to the other functions declared here upon success. + */ +MultiInstLockHandle OpenMultiInstanceLock(const char* nameToken, + const char16_t* installPath); + +void ReleaseMultiInstanceLock(MultiInstLockHandle lock); + +// aResult will be set to true if another instance *was* found, false if not. +// Return value is true on success, false on error (and aResult won't be set). +bool IsOtherInstanceRunning(MultiInstLockHandle lock, bool* aResult); + +}; // namespace mozilla + +#endif // MULTIINSTANCELOCK_H diff --git a/toolkit/xre/PolicyChecks.h b/toolkit/xre/PolicyChecks.h new file mode 100644 index 0000000000..7760b30cbd --- /dev/null +++ b/toolkit/xre/PolicyChecks.h @@ -0,0 +1,42 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_PolicyChecks_h +#define mozilla_PolicyChecks_h + +#if defined(XP_WIN) + +# include <windows.h> + +// NB: This code must be able to run apart from XPCOM + +namespace mozilla { + +inline bool PolicyHasRegValue(HKEY aKey, LPCWSTR aName, DWORD* aValue) { + DWORD len = sizeof(DWORD); + LONG ret = ::RegGetValueW(aKey, L"SOFTWARE\\Policies\\Mozilla\\Firefox", + aName, RRF_RT_DWORD, nullptr, aValue, &len); + return ret == ERROR_SUCCESS; +} + +inline bool PolicyCheckBoolean(LPCWSTR aPolicyName) { + DWORD value; + if (PolicyHasRegValue(HKEY_LOCAL_MACHINE, aPolicyName, &value)) { + return value == 1; + } + + if (PolicyHasRegValue(HKEY_CURRENT_USER, aPolicyName, &value)) { + return value == 1; + } + + return false; +} + +} // namespace mozilla + +#endif // defined(XP_WIN) + +#endif // mozilla_PolicyChecks_h diff --git a/toolkit/xre/ProfileReset.cpp b/toolkit/xre/ProfileReset.cpp new file mode 100644 index 0000000000..f181e34788 --- /dev/null +++ b/toolkit/xre/ProfileReset.cpp @@ -0,0 +1,149 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsIAppStartup.h" +#include "nsIFile.h" +#include "nsIStringBundle.h" +#include "nsIToolkitProfile.h" +#include "nsIWindowWatcher.h" + +#include "ProfileReset.h" + +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsPIDOMWindow.h" +#include "nsString.h" +#include "nsXPCOMCIDInternal.h" +#include "mozilla/Components.h" +#include "mozilla/XREAppData.h" + +#include "mozilla/Services.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/Unused.h" +#include "prtime.h" + +using namespace mozilla; + +extern const XREAppData* gAppData; + +static const char kProfileProperties[] = + "chrome://mozapps/locale/profile/profileSelection.properties"; + +/** + * Spin up a thread to backup the old profile's main directory and delete the + * profile's local directory. Once complete have the profile service remove the + * old profile and if necessary make the new profile the default. + */ +nsresult ProfileResetCleanup(nsToolkitProfileService* aService, + nsIToolkitProfile* aOldProfile) { + nsresult rv; + nsCOMPtr<nsIFile> profileDir; + rv = aOldProfile->GetRootDir(getter_AddRefs(profileDir)); + if (NS_FAILED(rv)) return rv; + + nsCOMPtr<nsIFile> profileLocalDir; + rv = aOldProfile->GetLocalDir(getter_AddRefs(profileLocalDir)); + if (NS_FAILED(rv)) return rv; + + // Get the friendly name for the backup directory. + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + if (!sbs) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIStringBundle> sb; + Unused << sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + if (!sb) return NS_ERROR_FAILURE; + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + AutoTArray<nsString, 2> params = {appName, appName}; + + nsAutoString resetBackupDirectoryName; + + static const char* kResetBackupDirectory = "resetBackupDirectory"; + rv = sb->FormatStringFromName(kResetBackupDirectory, params, + resetBackupDirectoryName); + if (NS_FAILED(rv)) return rv; + + // Get info to copy the old root profile dir to the desktop as a backup. + nsCOMPtr<nsIFile> backupDest, containerDest, profileDest; + rv = NS_GetSpecialDirectory(NS_OS_DESKTOP_DIR, getter_AddRefs(backupDest)); + if (NS_FAILED(rv)) { + // Fall back to the home directory if the desktop is not available. + rv = NS_GetSpecialDirectory(NS_OS_HOME_DIR, getter_AddRefs(backupDest)); + if (NS_FAILED(rv)) return rv; + } + + // Try to create a directory for all the backups + backupDest->Clone(getter_AddRefs(containerDest)); + containerDest->Append(resetBackupDirectoryName); + rv = containerDest->Create(nsIFile::DIRECTORY_TYPE, 0700); + // It's OK if it already exists, if and only if it is a directory + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { + bool containerIsDir; + rv = containerDest->IsDirectory(&containerIsDir); + if (NS_FAILED(rv) || !containerIsDir) { + return rv; + } + } else if (NS_FAILED(rv)) { + return rv; + } + + // Get the name of the profile + nsAutoString leafName; + rv = profileDir->GetLeafName(leafName); + if (NS_FAILED(rv)) return rv; + + // Try to create a unique directory for the profile: + containerDest->Clone(getter_AddRefs(profileDest)); + profileDest->Append(leafName); + rv = profileDest->CreateUnique(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) return rv; + + // Get the unique profile name + rv = profileDest->GetLeafName(leafName); + if (NS_FAILED(rv)) return rv; + + // Delete the empty directory that CreateUnique just created. + rv = profileDest->Remove(false); + if (NS_FAILED(rv)) return rv; + + // Show a progress window while the cleanup happens since the disk I/O can + // take time. + nsCOMPtr<nsIWindowWatcher> windowWatcher( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + if (!windowWatcher) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service()); + if (!appStartup) return NS_ERROR_FAILURE; + + nsCOMPtr<mozIDOMWindowProxy> progressWindow; + rv = windowWatcher->OpenWindow(nullptr, nsDependentCString(kResetProgressURL), + "_blank"_ns, "centerscreen,chrome,titlebar"_ns, + nullptr, getter_AddRefs(progressWindow)); + if (NS_FAILED(rv)) return rv; + + // Create a new thread to do the bulk of profile cleanup to stay responsive. + nsCOMPtr<nsIThreadManager> tm = do_GetService(NS_THREADMANAGER_CONTRACTID); + nsCOMPtr<nsIThread> cleanupThread; + rv = tm->NewThread(0, 0, getter_AddRefs(cleanupThread)); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIRunnable> runnable = new ProfileResetCleanupAsyncTask( + profileDir, profileLocalDir, containerDest, leafName); + cleanupThread->Dispatch(runnable, nsIThread::DISPATCH_NORMAL); + // The result callback will shut down the worker thread. + + // Wait for the cleanup thread to complete. + SpinEventLoopUntil([&]() { return gProfileResetCleanupCompleted; }); + } else { + gProfileResetCleanupCompleted = true; + NS_WARNING("Cleanup thread creation failed"); + return rv; + } + // Close the progress window now that the cleanup thread is done. + auto* piWindow = nsPIDOMWindowOuter::From(progressWindow); + piWindow->Close(); + + return aService->ApplyResetProfile(aOldProfile); +} diff --git a/toolkit/xre/ProfileReset.h b/toolkit/xre/ProfileReset.h new file mode 100644 index 0000000000..71a52e7252 --- /dev/null +++ b/toolkit/xre/ProfileReset.h @@ -0,0 +1,84 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsToolkitProfileService.h" +#include "nsIFile.h" +#include "nsIThread.h" +#include "nsThreadUtils.h" + +static bool gProfileResetCleanupCompleted = false; +static const char kResetProgressURL[] = + "chrome://global/content/resetProfileProgress.xhtml"; + +nsresult ProfileResetCleanup(nsToolkitProfileService* aService, + nsIToolkitProfile* aOldProfile); + +class ProfileResetCleanupResultTask : public mozilla::Runnable { + public: + ProfileResetCleanupResultTask() + : mozilla::Runnable("ProfileResetCleanupResultTask"), + mWorkerThread(do_GetCurrentThread()) { + MOZ_ASSERT(!NS_IsMainThread()); + } + + NS_IMETHOD Run() override { + MOZ_ASSERT(NS_IsMainThread()); + mWorkerThread->Shutdown(); + return NS_OK; + } + + private: + nsCOMPtr<nsIThread> mWorkerThread; +}; + +class ProfileResetCleanupAsyncTask : public mozilla::Runnable { + public: + ProfileResetCleanupAsyncTask(nsIFile* aProfileDir, nsIFile* aProfileLocalDir, + nsIFile* aTargetDir, const nsAString& aLeafName) + : mozilla::Runnable("ProfileResetCleanupAsyncTask"), + mProfileDir(aProfileDir), + mProfileLocalDir(aProfileLocalDir), + mTargetDir(aTargetDir), + mLeafName(aLeafName) {} + + /** + * Copy a root profile to a backup folder before deleting it. Then delete the + * local profile dir. + */ + NS_IMETHOD Run() override { + // Copy profile's files to the destination. The profile folder will be + // removed after the changes to the known profiles have been flushed to disk + // in nsToolkitProfileService::ApplyResetProfile which isn't called until + // after this thread finishes copying the files. + nsresult rv = mProfileDir->CopyToFollowingLinks(mTargetDir, mLeafName); + // I guess we just warn if we fail to make the backup? + if (NS_WARN_IF(NS_FAILED(rv))) { + NS_WARNING("Could not backup the root profile directory"); + } + + // If we have a separate local cache profile directory, just delete it. + // Don't return an error if this fails so that reset can proceed if it can't + // be deleted. + bool sameDir; + nsresult rvLocal = mProfileDir->Equals(mProfileLocalDir, &sameDir); + if (NS_SUCCEEDED(rvLocal) && !sameDir) { + rvLocal = mProfileLocalDir->Remove(true); + if (NS_FAILED(rvLocal)) { + NS_WARNING("Could not remove the old local profile directory (cache)"); + } + } + gProfileResetCleanupCompleted = true; + + nsCOMPtr<nsIRunnable> resultRunnable = new ProfileResetCleanupResultTask(); + NS_DispatchToMainThread(resultRunnable); + return NS_OK; + } + + private: + nsCOMPtr<nsIFile> mProfileDir; + nsCOMPtr<nsIFile> mProfileLocalDir; + nsCOMPtr<nsIFile> mTargetDir; + nsString mLeafName; +}; diff --git a/toolkit/xre/SafeMode.h b/toolkit/xre/SafeMode.h new file mode 100644 index 0000000000..a6cc86f334 --- /dev/null +++ b/toolkit/xre/SafeMode.h @@ -0,0 +1,92 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_SafeMode_h +#define mozilla_SafeMode_h + +// NB: This code must be able to run apart from XPCOM + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/Maybe.h" + +#if defined(XP_WIN) +# include "mozilla/PolicyChecks.h" +# include <windows.h> +#endif // defined(XP_WIN) + +// Undo X11/X.h's definition of None +#undef None + +namespace mozilla { + +enum class SafeModeFlag : uint32_t { + None = 0, + Unset = (1 << 0), + NoKeyPressCheck = (1 << 1), +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(SafeModeFlag) + +template <typename CharT> +inline Maybe<bool> IsSafeModeRequested( + int& aArgc, CharT* aArgv[], + const SafeModeFlag aFlags = SafeModeFlag::Unset) { + CheckArgFlag checkArgFlags = CheckArgFlag::None; + if (aFlags & SafeModeFlag::Unset) { + checkArgFlags |= CheckArgFlag::RemoveArg; + } + + ArgResult ar = + CheckArg(aArgc, aArgv, GetLiteral<CharT, FlagLiteral::safemode>(), + static_cast<const CharT**>(nullptr), checkArgFlags); + if (ar == ARG_BAD) { + return Nothing(); + } + + bool result = ar == ARG_FOUND; + +#if defined(XP_WIN) + // If the shift key is pressed and the ctrl and / or alt keys are not pressed + // during startup, start in safe mode. GetKeyState returns a short and the + // high order bit will be 1 if the key is pressed. By masking the returned + // short with 0x8000 the result will be 0 if the key is not pressed and + // non-zero otherwise. + if (!(aFlags & SafeModeFlag::NoKeyPressCheck) && + (GetKeyState(VK_SHIFT) & 0x8000) && !(GetKeyState(VK_CONTROL) & 0x8000) && + !(GetKeyState(VK_MENU) & 0x8000) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { + result = true; + } + + if (result && PolicyCheckBoolean(L"DisableSafeMode")) { + result = false; + } +#endif // defined(XP_WIN) + +#if defined(XP_MACOSX) + if (!(aFlags & SafeModeFlag::NoKeyPressCheck) && + (GetCurrentEventKeyModifiers() & optionKey) && + !EnvHasValue("MOZ_DISABLE_SAFE_MODE_KEY")) { + result = true; + } +#endif // defined(XP_MACOSX) + + // The Safe Mode Policy should not be enforced for the env var case + // (used by updater and crash-recovery). + if (EnvHasValue("MOZ_SAFE_MODE_RESTART")) { + result = true; + if (aFlags & SafeModeFlag::Unset) { + // unset the env variable + SaveToEnv("MOZ_SAFE_MODE_RESTART="); + } + } + + return Some(result); +} + +} // namespace mozilla + +#endif // mozilla_SafeMode_h diff --git a/toolkit/xre/UIKitDirProvider.h b/toolkit/xre/UIKitDirProvider.h new file mode 100644 index 0000000000..ff7ad6b3b9 --- /dev/null +++ b/toolkit/xre/UIKitDirProvider.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 TOOLKIT_XRE_UIKITDIRPROVIDER_H_ +#define TOOLKIT_XRE_UIKITDIRPROVIDER_H_ + +#include "nsString.h" + +bool GetUIKitDirectory(bool aLocal, nsACString& aUserDir); + +#endif // TOOLKIT_XRE_UIKITDIRPROVIDER_H_ diff --git a/toolkit/xre/UIKitDirProvider.mm b/toolkit/xre/UIKitDirProvider.mm new file mode 100644 index 0000000000..043ecbe2ec --- /dev/null +++ b/toolkit/xre/UIKitDirProvider.mm @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include <Foundation/Foundation.h> + +#include "UIKitDirProvider.h" + +bool GetUIKitDirectory(bool aLocal, nsACString& aUserDir) { + NSSearchPathDirectory directory = aLocal ? NSCachesDirectory : NSApplicationSupportDirectory; + NSArray* paths = NSSearchPathForDirectoriesInDomains(directory, NSUserDomainMask, YES); + if ([paths count] == 0) { + return false; + } + aUserDir = [[paths objectAtIndex:0] UTF8String]; + return true; +} diff --git a/toolkit/xre/UntrustedModulesData.cpp b/toolkit/xre/UntrustedModulesData.cpp new file mode 100644 index 0000000000..9f0bf030e8 --- /dev/null +++ b/toolkit/xre/UntrustedModulesData.cpp @@ -0,0 +1,422 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "UntrustedModulesData.h" + +#include <windows.h> + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/DynamicallyLinkedFunctionPtr.h" +#include "mozilla/FileUtilsWin.h" +#include "mozilla/Likely.h" +#include "mozilla/MathAlgorithms.h" +#include "mozilla/UniquePtr.h" +#include "mozilla/Unused.h" +#include "mozilla/WinDllServices.h" +#include "ModuleEvaluator.h" +#include "nsCOMPtr.h" +#include "nsDebug.h" +#include "nsXULAppAPI.h" +#include "WinUtils.h" + +// Some utility functions + +static LONGLONG GetQPCFreq() { + static const LONGLONG sFreq = []() -> LONGLONG { + LARGE_INTEGER freq; + ::QueryPerformanceFrequency(&freq); + return freq.QuadPart; + }(); + + return sFreq; +} + +template <typename ReturnT> +static ReturnT QPCToTimeUnits(const LONGLONG aTimeStamp, + const LONGLONG aUnitsPerSec) { + return ReturnT(aTimeStamp * aUnitsPerSec) / ReturnT(GetQPCFreq()); +} + +template <typename ReturnT> +static ReturnT QPCToMilliseconds(const LONGLONG aTimeStamp) { + const LONGLONG kMillisecondsPerSec = 1000; + return QPCToTimeUnits<ReturnT>(aTimeStamp, kMillisecondsPerSec); +} + +template <typename ReturnT> +static ReturnT QPCToMicroseconds(const LONGLONG aTimeStamp) { + const LONGLONG kMicrosecondsPerSec = 1000000; + return QPCToTimeUnits<ReturnT>(aTimeStamp, kMicrosecondsPerSec); +} + +static LONGLONG TimeUnitsToQPC(const LONGLONG aTimeStamp, + const LONGLONG aUnitsPerSec) { + MOZ_ASSERT(aUnitsPerSec != 0); + + LONGLONG result = aTimeStamp; + result *= GetQPCFreq(); + result /= aUnitsPerSec; + return result; +} + +static Maybe<double> QPCLoadDurationToMilliseconds( + const ModuleLoadInfo& aNtInfo) { + if (aNtInfo.IsBare()) { + return Nothing(); + } + + return Some(QPCToMilliseconds<double>(aNtInfo.mLoadTimeInfo.QuadPart)); +} + +namespace mozilla { + +ModuleRecord::ModuleRecord() : mTrustFlags(ModuleTrustFlags::None) {} + +ModuleRecord::ModuleRecord(const nsAString& aResolvedNtPath) + : mResolvedNtName(aResolvedNtPath), mTrustFlags(ModuleTrustFlags::None) { + if (aResolvedNtPath.IsEmpty()) { + return; + } + + MOZ_ASSERT(XRE_IsParentProcess()); + + nsAutoString resolvedDosPath; + if (!NtPathToDosPath(aResolvedNtPath, resolvedDosPath)) { +#if defined(DEBUG) + nsAutoCString msg; + msg.AppendLiteral("NtPathToDosPath failed for path \""); + msg.Append(NS_ConvertUTF16toUTF8(aResolvedNtPath)); + msg.AppendLiteral("\""); + NS_WARNING(msg.get()); +#endif // defined(DEBUG) + return; + } + + nsresult rv = + NS_NewLocalFile(resolvedDosPath, false, getter_AddRefs(mResolvedDosName)); + if (NS_FAILED(rv) || !mResolvedDosName) { + return; + } + + GetVersionAndVendorInfo(resolvedDosPath); + + // Now sanitize the resolved DLL name. If we cannot sanitize this then this + // record must not be considered valid. + nsAutoString strSanitizedPath(resolvedDosPath); + if (!widget::WinUtils::PreparePathForTelemetry(strSanitizedPath)) { + return; + } + + mSanitizedDllName = strSanitizedPath; +} + +void ModuleRecord::GetVersionAndVendorInfo(const nsAString& aPath) { + RefPtr<DllServices> dllSvc(DllServices::Get()); + + // WinVerifyTrust is too slow and of limited utility for our purposes, so + // we pass SkipTrustVerification here to avoid it. + UniquePtr<wchar_t[]> signedBy( + dllSvc->GetBinaryOrgName(PromiseFlatString(aPath).get(), + AuthenticodeFlags::SkipTrustVerification)); + if (signedBy) { + mVendorInfo = Some(VendorInfo(VendorInfo::Source::Signature, + nsDependentString(signedBy.get()))); + } + + ModuleVersionInfo verInfo; + if (!verInfo.GetFromImage(aPath)) { + return; + } + + if (verInfo.mFileVersion.Version64()) { + mVersion = Some(ModuleVersion(verInfo.mFileVersion.Version64())); + } + + if (!mVendorInfo && !verInfo.mCompanyName.IsEmpty()) { + mVendorInfo = + Some(VendorInfo(VendorInfo::Source::VersionInfo, verInfo.mCompanyName)); + } +} + +bool ModuleRecord::IsXUL() const { + if (!mResolvedDosName) { + return false; + } + + nsAutoString leafName; + nsresult rv = mResolvedDosName->GetLeafName(leafName); + if (NS_FAILED(rv)) { + return false; + } + + return leafName.EqualsIgnoreCase("xul.dll"); +} + +int32_t ModuleRecord::GetScoreThreshold() const { +#ifdef ENABLE_TESTS + // Check whether we are running as an xpcshell test. + if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) { + nsAutoString dllLeaf; + if (NS_SUCCEEDED(mResolvedDosName->GetLeafName(dllLeaf))) { + // During xpcshell tests, this DLL is hard-coded to pass through all + // criteria checks and still result in "untrusted" status, so it shows up + // in the untrusted modules ping for the test to examine. + // Setting the threshold very high ensures the test will cover all + // criteria. + if (dllLeaf.EqualsIgnoreCase("modules-test.dll")) { + return 99999; + } + } + } +#endif + + return 100; +} + +bool ModuleRecord::IsTrusted() const { + if (mTrustFlags == ModuleTrustFlags::None) { + return false; + } + + // These flags are immediate passes + if (mTrustFlags & + (ModuleTrustFlags::MicrosoftWindowsSignature | + ModuleTrustFlags::MozillaSignature | ModuleTrustFlags::JitPI)) { + return true; + } + + // The remaining flags, when set, each count for 50 points toward a + // trustworthiness score. + int32_t score = static_cast<int32_t>( + CountPopulation32(static_cast<uint32_t>(mTrustFlags))) * + 50; + return score >= GetScoreThreshold(); +} + +ProcessedModuleLoadEvent::ProcessedModuleLoadEvent() + : mProcessUptimeMS(0ULL), + mThreadId(0UL), + mBaseAddress(0U), + mIsDependent(false), + mLoadStatus(0) {} + +ProcessedModuleLoadEvent::ProcessedModuleLoadEvent( + glue::EnhancedModuleLoadInfo&& aModLoadInfo, + RefPtr<ModuleRecord>&& aModuleRecord, bool aIsDependent) + : mProcessUptimeMS(QPCTimeStampToProcessUptimeMilliseconds( + aModLoadInfo.mNtLoadInfo.mBeginTimestamp)), + mLoadDurationMS(QPCLoadDurationToMilliseconds(aModLoadInfo.mNtLoadInfo)), + mThreadId(aModLoadInfo.mNtLoadInfo.mThreadId), + mThreadName(std::move(aModLoadInfo.mThreadName)), + mBaseAddress( + reinterpret_cast<uintptr_t>(aModLoadInfo.mNtLoadInfo.mBaseAddr)), + mModule(std::move(aModuleRecord)), + mIsDependent(aIsDependent), + mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) { + if (!mModule || !(*mModule)) { + return; + } + + // Sanitize the requested DLL name. It is not a critical failure if we + // cannot do so; we simply do not provide that field to Telemetry. + nsAutoString strRequested( + aModLoadInfo.mNtLoadInfo.mRequestedDllName.AsString()); + if (!strRequested.IsEmpty() && + widget::WinUtils::PreparePathForTelemetry(strRequested)) { + mRequestedDllName = strRequested; + } +} + +/* static */ +Maybe<LONGLONG> +ProcessedModuleLoadEvent::ComputeQPCTimeStampForProcessCreation() { + // This is similar to the algorithm used by TimeStamp::ProcessCreation: + + // 1. Get the process creation timestamp as FILETIME; + FILETIME creationTime, exitTime, kernelTime, userTime; + if (!::GetProcessTimes(::GetCurrentProcess(), &creationTime, &exitTime, + &kernelTime, &userTime)) { + return Nothing(); + } + + // 2. Get current timestamps as both QPC and FILETIME; + LARGE_INTEGER nowQPC; + ::QueryPerformanceCounter(&nowQPC); + + static const StaticDynamicallyLinkedFunctionPtr<void(WINAPI*)(LPFILETIME)> + pGetSystemTimePreciseAsFileTime(L"kernel32.dll", + "GetSystemTimePreciseAsFileTime"); + + FILETIME nowFile; + if (pGetSystemTimePreciseAsFileTime) { + pGetSystemTimePreciseAsFileTime(&nowFile); + } else { + ::GetSystemTimeAsFileTime(&nowFile); + } + + // 3. Take the difference between the FILETIMEs from (1) and (2), + // respectively, yielding the elapsed process uptime in microseconds. + ULARGE_INTEGER ulCreation = { + {creationTime.dwLowDateTime, creationTime.dwHighDateTime}}; + ULARGE_INTEGER ulNow = {{nowFile.dwLowDateTime, nowFile.dwHighDateTime}}; + + ULONGLONG timeSinceCreationMicroSec = + (ulNow.QuadPart - ulCreation.QuadPart) / 10ULL; + + // 4. Convert the QPC timestamp from (1) to microseconds. + LONGLONG nowQPCMicroSec = QPCToMicroseconds<LONGLONG>(nowQPC.QuadPart); + + // 5. Convert the elapsed uptime to an absolute timestamp by subtracting + // from (4), which yields the absolute timestamp for process creation. + // We convert back to QPC units before returning. + const LONGLONG kMicrosecondsPerSec = 1000000; + return Some(TimeUnitsToQPC(nowQPCMicroSec - timeSinceCreationMicroSec, + kMicrosecondsPerSec)); +} + +/* static */ +uint64_t ProcessedModuleLoadEvent::QPCTimeStampToProcessUptimeMilliseconds( + const LARGE_INTEGER& aTimeStamp) { + static const Maybe<LONGLONG> sProcessCreationTimeStamp = + ComputeQPCTimeStampForProcessCreation(); + + if (!sProcessCreationTimeStamp) { + return 0ULL; + } + + LONGLONG diff = aTimeStamp.QuadPart - sProcessCreationTimeStamp.value(); + return QPCToMilliseconds<uint64_t>(diff); +} + +bool ProcessedModuleLoadEvent::IsXULLoad() const { + if (!mModule) { + return false; + } + + return mModule->IsXUL(); +} + +bool ProcessedModuleLoadEvent::IsTrusted() const { + if (!mModule) { + return false; + } + + return mModule->IsTrusted(); +} + +void UntrustedModulesData::AddNewLoads( + const ModulesMap& aModules, Vector<ProcessedModuleLoadEvent>&& aEvents, + Vector<Telemetry::ProcessedStack>&& aStacks) { + MOZ_ASSERT(aEvents.length() == aStacks.length()); + + for (auto iter = aModules.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data()->IsTrusted()) { + // Filter out trusted module records + continue; + } + + auto addPtr = mModules.LookupForAdd(iter.Key()); + if (addPtr) { + // |mModules| already contains this record + continue; + } + + RefPtr<ModuleRecord> rec(iter.Data()); + addPtr.OrInsert([rec = std::move(rec)]() { return rec; }); + } + + // This constant matches the maximum in Telemetry::CombinedStacks + const size_t kMaxEvents = 50; + MOZ_ASSERT(mEvents.length() <= kMaxEvents); + + if (mEvents.length() + aEvents.length() > kMaxEvents) { + // Ensure that we will never retain more tha kMaxEvents events + size_t newLength = kMaxEvents - mEvents.length(); + if (!newLength) { + return; + } + + aEvents.shrinkTo(newLength); + aStacks.shrinkTo(newLength); + } + + if (mEvents.empty()) { + mEvents = std::move(aEvents); + } else { + Unused << mEvents.reserve(mEvents.length() + aEvents.length()); + for (auto&& event : aEvents) { + Unused << mEvents.emplaceBack(std::move(event)); + } + } + + for (auto&& stack : aStacks) { + mStacks.AddStack(stack); + } +} + +void UntrustedModulesData::Merge(UntrustedModulesData&& aNewData) { + // Don't merge loading events of a different process + MOZ_ASSERT((mProcessType == aNewData.mProcessType) && + (mPid == aNewData.mPid)); + + UntrustedModulesData newData(std::move(aNewData)); + + if (mEvents.empty()) { + mModules = std::move(newData.mModules); + mEvents = std::move(newData.mEvents); + mStacks = std::move(newData.mStacks); + return; + } + + Unused << mEvents.reserve(mEvents.length() + newData.mEvents.length()); + for (auto&& event : newData.mEvents) { + auto addPtr = mModules.LookupForAdd(event.mModule->mResolvedNtName); + if (addPtr) { + // Even though the path of a ModuleRecord matches, the object of + // ModuleRecord can be different. + // Make sure the event's mModule points to an object in mModules. + event.mModule = addPtr.Data(); + } else { + addPtr.OrInsert([modRef = event.mModule]() { return modRef; }); + } + + Unused << mEvents.emplaceBack(std::move(event)); + } + + mStacks.AddStacks(newData.mStacks); +} + +void UntrustedModulesData::Swap(UntrustedModulesData& aOther) { + GeckoProcessType tmpProcessType = mProcessType; + mProcessType = aOther.mProcessType; + aOther.mProcessType = tmpProcessType; + + DWORD tmpPid = mPid; + mPid = aOther.mPid; + aOther.mPid = tmpPid; + + TimeDuration tmpElapsed = mElapsed; + mElapsed = aOther.mElapsed; + aOther.mElapsed = tmpElapsed; + + mModules.SwapElements(aOther.mModules); + mEvents.swap(aOther.mEvents); + mStacks.Swap(aOther.mStacks); + + Maybe<double> tmpXULLoadDurationMS = mXULLoadDurationMS; + mXULLoadDurationMS = aOther.mXULLoadDurationMS; + aOther.mXULLoadDurationMS = tmpXULLoadDurationMS; + + uint32_t tmpSanitizationFailures = mSanitizationFailures; + mSanitizationFailures = aOther.mSanitizationFailures; + aOther.mSanitizationFailures = tmpSanitizationFailures; + + uint32_t tmpTrustTestFailures = mTrustTestFailures; + mTrustTestFailures = aOther.mTrustTestFailures; + aOther.mTrustTestFailures = tmpTrustTestFailures; +} + +} // namespace mozilla diff --git a/toolkit/xre/UntrustedModulesData.h b/toolkit/xre/UntrustedModulesData.h new file mode 100644 index 0000000000..da37ab1939 --- /dev/null +++ b/toolkit/xre/UntrustedModulesData.h @@ -0,0 +1,618 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_UntrustedModulesData_h +#define mozilla_UntrustedModulesData_h + +#if defined(XP_WIN) + +# include "ipc/IPCMessageUtils.h" +# include "mozilla/CombinedStacks.h" +# include "mozilla/DebugOnly.h" +# include "mozilla/Maybe.h" +# include "mozilla/RefPtr.h" +# include "mozilla/TypedEnumBits.h" +# include "mozilla/Unused.h" +# include "mozilla/Variant.h" +# include "mozilla/Vector.h" +# include "mozilla/WinHeaderOnlyUtils.h" +# include "nsCOMPtr.h" +# include "nsHashKeys.h" +# include "nsIFile.h" +# include "nsISupportsImpl.h" +# include "nsRefPtrHashtable.h" +# include "nsString.h" +# include "nsXULAppAPI.h" + +namespace mozilla { +namespace glue { +struct EnhancedModuleLoadInfo; +} // namespace glue + +enum class ModuleTrustFlags : uint32_t { + None = 0, + MozillaSignature = 1, + MicrosoftWindowsSignature = 2, + MicrosoftVersion = 4, + FirefoxDirectory = 8, + FirefoxDirectoryAndVersion = 0x10, + SystemDirectory = 0x20, + KeyboardLayout = 0x40, + JitPI = 0x80, + WinSxSDirectory = 0x100, +}; + +MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(ModuleTrustFlags); + +class VendorInfo final { + public: + enum class Source : uint32_t { + None, + Signature, + VersionInfo, + }; + + VendorInfo() : mSource(Source::None) {} + VendorInfo(const Source aSource, const nsAString& aVendor) + : mSource(aSource), mVendor(aVendor) { + MOZ_ASSERT(aSource != Source::None && !aVendor.IsEmpty()); + } + + Source mSource; + nsString mVendor; +}; + +class ModulesMap; + +class ModuleRecord final { + public: + explicit ModuleRecord(const nsAString& aResolvedNtPath); + + NS_INLINE_DECL_THREADSAFE_REFCOUNTING(ModuleRecord) + + nsString mResolvedNtName; + nsCOMPtr<nsIFile> mResolvedDosName; + nsString mSanitizedDllName; + Maybe<ModuleVersion> mVersion; + Maybe<VendorInfo> mVendorInfo; + ModuleTrustFlags mTrustFlags; + + explicit operator bool() const { return !mSanitizedDllName.IsEmpty(); } + bool IsXUL() const; + bool IsTrusted() const; + + ModuleRecord(const ModuleRecord&) = delete; + ModuleRecord(ModuleRecord&&) = delete; + + ModuleRecord& operator=(const ModuleRecord&) = delete; + ModuleRecord& operator=(ModuleRecord&&) = delete; + + private: + ModuleRecord(); + ~ModuleRecord() = default; + void GetVersionAndVendorInfo(const nsAString& aPath); + int32_t GetScoreThreshold() const; + + friend struct ::IPC::ParamTraits<ModulesMap>; +}; + +/** + * This type holds module path data using one of two internal representations. + * It may be created from either a nsTHashtable or a Vector, and may be + * serialized from either representation into a common format over the wire. + * Deserialization always uses the Vector representation. + */ +struct ModulePaths final { + using SetType = nsTHashtable<nsStringCaseInsensitiveHashKey>; + using VecType = Vector<nsString>; + + Variant<SetType, VecType> mModuleNtPaths; + + template <typename T> + explicit ModulePaths(T&& aPaths) + : mModuleNtPaths(AsVariant(std::forward<T>(aPaths))) {} + + ModulePaths() : mModuleNtPaths(VecType()) {} + + ModulePaths(const ModulePaths& aOther) = delete; + ModulePaths(ModulePaths&& aOther) = default; + ModulePaths& operator=(const ModulePaths&) = delete; + ModulePaths& operator=(ModulePaths&&) = default; +}; + +class ProcessedModuleLoadEvent final { + public: + ProcessedModuleLoadEvent(); + ProcessedModuleLoadEvent(glue::EnhancedModuleLoadInfo&& aModLoadInfo, + RefPtr<ModuleRecord>&& aModuleRecord, + bool aIsDependent); + + explicit operator bool() const { return mModule && *mModule; } + bool IsXULLoad() const; + bool IsTrusted() const; + + uint64_t mProcessUptimeMS; + Maybe<double> mLoadDurationMS; + DWORD mThreadId; + nsCString mThreadName; + nsString mRequestedDllName; + // We intentionally store mBaseAddress as part of the event and not the + // module, as relocation may cause it to change between loads. If so, we want + // to know about it. + uintptr_t mBaseAddress; + RefPtr<ModuleRecord> mModule; + bool mIsDependent; + uint32_t mLoadStatus; // corresponding to enum ModuleLoadInfo::Status + + ProcessedModuleLoadEvent(const ProcessedModuleLoadEvent&) = delete; + ProcessedModuleLoadEvent& operator=(const ProcessedModuleLoadEvent&) = delete; + + ProcessedModuleLoadEvent(ProcessedModuleLoadEvent&&) = default; + ProcessedModuleLoadEvent& operator=(ProcessedModuleLoadEvent&&) = default; + + private: + static Maybe<LONGLONG> ComputeQPCTimeStampForProcessCreation(); + static uint64_t QPCTimeStampToProcessUptimeMilliseconds( + const LARGE_INTEGER& aTimeStamp); +}; + +// Declaring ModulesMap this way makes it much easier to forward declare than +// if we had used |using| or |typedef|. +class ModulesMap final + : public nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord> { + public: + ModulesMap() + : nsRefPtrHashtable<nsStringCaseInsensitiveHashKey, ModuleRecord>() {} +}; + +class UntrustedModulesData final { + public: + UntrustedModulesData() + : mProcessType(XRE_GetProcessType()), + mPid(::GetCurrentProcessId()), + mSanitizationFailures(0), + mTrustTestFailures(0) {} + + UntrustedModulesData(UntrustedModulesData&&) = default; + UntrustedModulesData& operator=(UntrustedModulesData&&) = default; + + UntrustedModulesData(const UntrustedModulesData&) = delete; + UntrustedModulesData& operator=(const UntrustedModulesData&) = delete; + + explicit operator bool() const { + return !mEvents.empty() || mSanitizationFailures || mTrustTestFailures || + mXULLoadDurationMS.isSome(); + } + + void AddNewLoads(const ModulesMap& aModulesMap, + Vector<ProcessedModuleLoadEvent>&& aEvents, + Vector<Telemetry::ProcessedStack>&& aStacks); + void Merge(UntrustedModulesData&& aNewData); + + void Swap(UntrustedModulesData& aOther); + + GeckoProcessType mProcessType; + DWORD mPid; + TimeDuration mElapsed; + ModulesMap mModules; + Vector<ProcessedModuleLoadEvent> mEvents; + Telemetry::CombinedStacks mStacks; + Maybe<double> mXULLoadDurationMS; + uint32_t mSanitizationFailures; + uint32_t mTrustTestFailures; +}; + +class ModulesMapResult final { + public: + ModulesMapResult() : mTrustTestFailures(0) {} + + ModulesMapResult(const ModulesMapResult& aOther) = delete; + ModulesMapResult(ModulesMapResult&& aOther) = default; + ModulesMapResult& operator=(const ModulesMapResult& aOther) = delete; + ModulesMapResult& operator=(ModulesMapResult&& aOther) = default; + + ModulesMap mModules; + uint32_t mTrustTestFailures; +}; + +} // namespace mozilla + +namespace IPC { + +template <> +struct ParamTraits<mozilla::ModuleVersion> { + typedef mozilla::ModuleVersion paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt64(aParam.AsInteger()); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint64_t ver; + if (!aMsg->ReadUInt64(aIter, &ver)) { + return false; + } + + *aResult = ver; + return true; + } +}; + +template <> +struct ParamTraits<mozilla::VendorInfo> { + typedef mozilla::VendorInfo paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mSource)); + WriteParam(aMsg, aParam.mVendor); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t source; + if (!aMsg->ReadUInt32(aIter, &source)) { + return false; + } + + aResult->mSource = static_cast<mozilla::VendorInfo::Source>(source); + + if (!ReadParam(aMsg, aIter, &aResult->mVendor)) { + return false; + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::ModuleRecord> { + typedef mozilla::ModuleRecord paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mResolvedNtName); + + nsAutoString resolvedDosName; + if (aParam.mResolvedDosName) { + mozilla::DebugOnly<nsresult> rv = + aParam.mResolvedDosName->GetPath(resolvedDosName); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + } + + WriteParam(aMsg, resolvedDosName); + WriteParam(aMsg, aParam.mSanitizedDllName); + WriteParam(aMsg, aParam.mVersion); + WriteParam(aMsg, aParam.mVendorInfo); + aMsg->WriteUInt32(static_cast<uint32_t>(aParam.mTrustFlags)); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &aResult->mResolvedNtName)) { + return false; + } + + nsAutoString resolvedDosName; + if (!ReadParam(aMsg, aIter, &resolvedDosName)) { + return false; + } + + if (resolvedDosName.IsEmpty()) { + aResult->mResolvedDosName = nullptr; + } else if (NS_FAILED(NS_NewLocalFile( + resolvedDosName, false, + getter_AddRefs(aResult->mResolvedDosName)))) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mSanitizedDllName)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mVersion)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mVendorInfo)) { + return false; + } + + uint32_t trustFlags; + if (!aMsg->ReadUInt32(aIter, &trustFlags)) { + return false; + } + + aResult->mTrustFlags = static_cast<mozilla::ModuleTrustFlags>(trustFlags); + return true; + } +}; + +template <> +struct ParamTraits<mozilla::ModulesMap> { + typedef mozilla::ModulesMap paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt32(aParam.Count()); + + for (auto iter = aParam.ConstIter(); !iter.Done(); iter.Next()) { + MOZ_RELEASE_ASSERT(iter.Data()); + WriteParam(aMsg, iter.Key()); + WriteParam(aMsg, *(iter.Data())); + } + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t count; + if (!ReadParam(aMsg, aIter, &count)) { + return false; + } + + for (uint32_t current = 0; current < count; ++current) { + nsAutoString key; + if (!ReadParam(aMsg, aIter, &key) || key.IsEmpty()) { + return false; + } + + RefPtr<mozilla::ModuleRecord> rec(new mozilla::ModuleRecord()); + if (!ReadParam(aMsg, aIter, rec.get())) { + return false; + } + + aResult->Put(key, std::move(rec)); + } + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::ModulePaths> { + typedef mozilla::ModulePaths paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aParam.mModuleNtPaths.match( + [aMsg](const paramType::SetType& aSet) { WriteSet(aMsg, aSet); }, + [aMsg](const paramType::VecType& aVec) { WriteVector(aMsg, aVec); }); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t len; + if (!aMsg->ReadUInt32(aIter, &len)) { + return false; + } + + // As noted in the comments for ModulePaths, we only deserialize using the + // Vector representation. + auto& vec = aResult->mModuleNtPaths.as<paramType::VecType>(); + if (!vec.reserve(len)) { + return false; + } + + for (uint32_t idx = 0; idx < len; ++idx) { + nsString str; + if (!ReadParam(aMsg, aIter, &str)) { + return false; + } + + if (!vec.emplaceBack(std::move(str))) { + return false; + } + } + + return true; + } + + private: + // NB: This function must write out the set in the same format as WriteVector + static void WriteSet(Message* aMsg, const paramType::SetType& aSet) { + aMsg->WriteUInt32(aSet.Count()); + for (auto iter = aSet.ConstIter(); !iter.Done(); iter.Next()) { + WriteParam(aMsg, iter.Get()->GetKey()); + } + } + + // NB: This function must write out the vector in the same format as WriteSet + static void WriteVector(Message* aMsg, const paramType::VecType& aVec) { + aMsg->WriteUInt32(aVec.length()); + for (auto const& item : aVec) { + WriteParam(aMsg, item); + } + } +}; + +template <> +struct ParamTraits<mozilla::UntrustedModulesData> { + typedef mozilla::UntrustedModulesData paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + aMsg->WriteUInt32(aParam.mProcessType); + aMsg->WriteULong(aParam.mPid); + WriteParam(aMsg, aParam.mElapsed); + WriteParam(aMsg, aParam.mModules); + + aMsg->WriteUInt32(aParam.mEvents.length()); + for (auto& evt : aParam.mEvents) { + WriteEvent(aMsg, evt); + } + + WriteParam(aMsg, aParam.mStacks); + WriteParam(aMsg, aParam.mXULLoadDurationMS); + aMsg->WriteUInt32(aParam.mSanitizationFailures); + aMsg->WriteUInt32(aParam.mTrustTestFailures); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + uint32_t processType; + if (!aMsg->ReadUInt32(aIter, &processType)) { + return false; + } + + aResult->mProcessType = static_cast<GeckoProcessType>(processType); + + if (!aMsg->ReadULong(aIter, &aResult->mPid)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mElapsed)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mModules)) { + return false; + } + + // We read mEvents manually so that we can use ReadEvent defined below. + uint32_t eventsLen; + if (!ReadParam(aMsg, aIter, &eventsLen)) { + return false; + } + + if (!aResult->mEvents.resize(eventsLen)) { + return false; + } + + for (uint32_t curEventIdx = 0; curEventIdx < eventsLen; ++curEventIdx) { + if (!ReadEvent(aMsg, aIter, &(aResult->mEvents[curEventIdx]), + aResult->mModules)) { + return false; + } + } + + if (!ReadParam(aMsg, aIter, &aResult->mStacks)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mXULLoadDurationMS)) { + return false; + } + + if (!aMsg->ReadUInt32(aIter, &aResult->mSanitizationFailures)) { + return false; + } + + if (!aMsg->ReadUInt32(aIter, &aResult->mTrustTestFailures)) { + return false; + } + + return true; + } + + private: + // Because ProcessedModuleLoadEvent depends on a hash table from + // UntrustedModulesData, we do its serialization as part of this + // specialization. + static void WriteEvent(Message* aMsg, + const mozilla::ProcessedModuleLoadEvent& aParam) { + aMsg->WriteUInt64(aParam.mProcessUptimeMS); + WriteParam(aMsg, aParam.mLoadDurationMS); + aMsg->WriteULong(aParam.mThreadId); + WriteParam(aMsg, aParam.mThreadName); + WriteParam(aMsg, aParam.mRequestedDllName); + WriteParam(aMsg, aParam.mBaseAddress); + WriteParam(aMsg, aParam.mIsDependent); + WriteParam(aMsg, aParam.mLoadStatus); + + // We don't write the ModuleRecord directly; we write its key into the + // UntrustedModulesData::mModules hash table. + MOZ_ASSERT(aParam.mModule && !aParam.mModule->mResolvedNtName.IsEmpty()); + WriteParam(aMsg, aParam.mModule->mResolvedNtName); + } + + // Because ProcessedModuleLoadEvent depends on a hash table from + // UntrustedModulesData, we do its deserialization as part of this + // specialization. + static bool ReadEvent(const Message* aMsg, PickleIterator* aIter, + mozilla::ProcessedModuleLoadEvent* aResult, + const mozilla::ModulesMap& aModulesMap) { + if (!aMsg->ReadUInt64(aIter, &aResult->mProcessUptimeMS)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mLoadDurationMS)) { + return false; + } + + if (!aMsg->ReadULong(aIter, &aResult->mThreadId)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mThreadName)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mRequestedDllName)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mBaseAddress)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mIsDependent)) { + return false; + } + + if (!ReadParam(aMsg, aIter, &aResult->mLoadStatus)) { + return false; + } + + nsAutoString resolvedNtName; + if (!ReadParam(aMsg, aIter, &resolvedNtName)) { + return false; + } + + // NB: While bad data integrity might for some reason result in a null + // mModule, we do not fail the deserialization; this is a data error, + // rather than an IPC error. The error is detected and dealt with in + // telemetry. + aResult->mModule = aModulesMap.Get(resolvedNtName); + + return true; + } +}; + +template <> +struct ParamTraits<mozilla::ModulesMapResult> { + typedef mozilla::ModulesMapResult paramType; + + static void Write(Message* aMsg, const paramType& aParam) { + WriteParam(aMsg, aParam.mModules); + aMsg->WriteUInt32(aParam.mTrustTestFailures); + } + + static bool Read(const Message* aMsg, PickleIterator* aIter, + paramType* aResult) { + if (!ReadParam(aMsg, aIter, &aResult->mModules)) { + return false; + } + + if (!aMsg->ReadUInt32(aIter, &aResult->mTrustTestFailures)) { + return false; + } + + return true; + } +}; + +} // namespace IPC + +#else // defined(XP_WIN) + +namespace mozilla { + +// For compiling IPDL on non-Windows platforms +using UntrustedModulesData = uint32_t; +using ModulePaths = uint32_t; +using ModulesMapResult = uint32_t; + +} // namespace mozilla + +#endif // defined(XP_WIN) + +#endif // mozilla_UntrustedModulesData_h diff --git a/toolkit/xre/UntrustedModulesProcessor.cpp b/toolkit/xre/UntrustedModulesProcessor.cpp new file mode 100644 index 0000000000..96ce5b4f1d --- /dev/null +++ b/toolkit/xre/UntrustedModulesProcessor.cpp @@ -0,0 +1,1042 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "UntrustedModulesProcessor.h" + +#include <windows.h> + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/Likely.h" +#include "mozilla/RDDParent.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Unused.h" +#include "ModuleEvaluator.h" +#include "nsCOMPtr.h" +#include "nsHashKeys.h" +#include "nsIObserverService.h" +#include "nsTHashtable.h" +#include "nsThreadUtils.h" +#include "nsXULAppAPI.h" +#include "private/prpriv.h" // For PR_GetThreadID + +static DWORD ToWin32ThreadId(nsIThread* aThread) { + if (!aThread) { + return 0UL; + } + + PRThread* prThread; + nsresult rv = aThread->GetPRThread(&prThread); + if (NS_FAILED(rv)) { + // Possible when a LazyInitThread's underlying nsThread is not present + return 0UL; + } + + return DWORD(::PR_GetThreadID(prThread)); +} + +namespace mozilla { + +class MOZ_RAII BackgroundPriorityRegion final { + public: + BackgroundPriorityRegion() + : mIsBackground( + ::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_IDLE)) {} + + ~BackgroundPriorityRegion() { + if (!mIsBackground) { + return; + } + + Clear(::GetCurrentThread()); + } + + static void Clear(nsIThread* aThread) { + DWORD tid = ToWin32ThreadId(aThread); + if (!tid) { + return; + } + + nsAutoHandle thread( + ::OpenThread(THREAD_SET_LIMITED_INFORMATION, FALSE, tid)); + if (!thread) { + return; + } + + Clear(thread); + } + + BackgroundPriorityRegion(const BackgroundPriorityRegion&) = delete; + BackgroundPriorityRegion(BackgroundPriorityRegion&&) = delete; + BackgroundPriorityRegion& operator=(const BackgroundPriorityRegion&) = delete; + BackgroundPriorityRegion& operator=(BackgroundPriorityRegion&&) = delete; + + private: + static void Clear(HANDLE aThread) { + DebugOnly<BOOL> ok = ::SetThreadPriority(aThread, THREAD_PRIORITY_NORMAL); + MOZ_ASSERT(ok); + } + + private: + const BOOL mIsBackground; +}; + +// This class wraps a set of the executables's dependent modules +// that is delay-initialized the first time Lookup() is called. +class DependentModules final { + Maybe<nsTHashtable<nsStringCaseInsensitiveHashKey>> mDependentModules; + + public: + bool Lookup(const nsString& aModulePath) { + if (aModulePath.IsEmpty()) { + return false; + } + + if (mDependentModules.isNothing()) { + nt::PEHeaders executable(::GetModuleHandleW(nullptr)); + + // We generate a hash table only when the executable's import table is + // tampered. If the import table is intact, all dependent modules are + // legit and we're not interested in any of them. In such a case, we + // set an empty table so that this function returns false. + mDependentModules = + Some(executable.IsImportDirectoryTampered() + ? executable.GenerateDependentModuleSet() + : nsTHashtable<nsStringCaseInsensitiveHashKey>()); + } + + return !!mDependentModules.ref().GetEntry(nt::GetLeafName(aModulePath)); + } +}; + +/* static */ +bool UntrustedModulesProcessor::IsSupportedProcessType() { + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + case GeckoProcessType_Content: + return Telemetry::CanRecordReleaseData(); + case GeckoProcessType_RDD: + // For RDD process, we check the telemetry settings in RDDChild::Init() + // running in the browser process because CanRecordReleaseData() always + // returns false here. + return true; + default: + return false; + } +} + +/* static */ +RefPtr<UntrustedModulesProcessor> UntrustedModulesProcessor::Create() { + if (!IsSupportedProcessType()) { + return nullptr; + } + + RefPtr<UntrustedModulesProcessor> result(new UntrustedModulesProcessor()); + return result.forget(); +} + +NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver) + +static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes + +UntrustedModulesProcessor::UntrustedModulesProcessor() + : mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules"_ns, + LazyIdleThread::ManualShutdown)), + mUnprocessedMutex( + "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"), + mAllowProcessing(true), + mIsFirstBatchProcessed(false) { + AddObservers(); +} + +void UntrustedModulesProcessor::AddObservers() { + nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService()); + obsServ->AddObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID, false); + obsServ->AddObserver(this, "xpcom-shutdown-threads", false); + if (XRE_IsContentProcess()) { + obsServ->AddObserver(this, "content-child-will-shutdown", false); + } +} + +void UntrustedModulesProcessor::Disable() { + // Ensure that mThread cannot run at low priority anymore + BackgroundPriorityRegion::Clear(mThread); + + // No more background processing allowed beyond this point + if (!mAllowProcessing.exchange(false)) { + return; + } + + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); +} + +NS_IMETHODIMP UntrustedModulesProcessor::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + if (!strcmp(aTopic, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID) || + !strcmp(aTopic, "content-child-will-shutdown")) { + Disable(); + return NS_OK; + } + + if (!strcmp(aTopic, "xpcom-shutdown-threads")) { + Disable(); + mThread->Shutdown(); + + RemoveObservers(); + + mThread = nullptr; + return NS_OK; + } + + MOZ_ASSERT_UNREACHABLE("Not reachable"); + + return NS_OK; +} + +void UntrustedModulesProcessor::RemoveObservers() { + nsCOMPtr<nsIObserverService> obsServ(services::GetObserverService()); + obsServ->RemoveObserver(this, NS_XPCOM_WILL_SHUTDOWN_OBSERVER_ID); + obsServ->RemoveObserver(this, "xpcom-shutdown-threads"); + if (XRE_IsContentProcess()) { + obsServ->RemoveObserver(this, "content-child-will-shutdown"); + } +} + +void UntrustedModulesProcessor::ScheduleNonEmptyQueueProcessing( + const MutexAutoLock& aProofOfLock) { + // In case something tried to load a DLL during shutdown + if (!mThread) { + return; + } + +#if defined(ENABLE_TESTS) + // Don't bother scheduling background processing in short-lived xpcshell + // processes; it makes the test suites take too long. + if (MOZ_UNLIKELY(mozilla::EnvHasValue("XPCSHELL_TEST_PROFILE_DIR"))) { + return; + } +#endif // defined(ENABLE_TESTS) + + if (mIdleRunnable) { + return; + } + + if (!mAllowProcessing) { + return; + } + + // Schedule a runnable to trigger background processing once the main thread + // has gone idle. We do it this way to ensure that we don't start doing a + // bunch of processing during periods of heavy main thread activity. + nsCOMPtr<nsIRunnable> idleRunnable(NewCancelableRunnableMethod( + "UntrustedModulesProcessor::DispatchBackgroundProcessing", this, + &UntrustedModulesProcessor::DispatchBackgroundProcessing)); + + if (NS_FAILED(NS_DispatchToMainThreadQueue(do_AddRef(idleRunnable), + EventQueuePriority::Idle))) { + return; + } + + mIdleRunnable = std::move(idleRunnable); +} + +void UntrustedModulesProcessor::CancelScheduledProcessing( + const MutexAutoLock& aProofOfLock) { + if (!mIdleRunnable) { + return; + } + + nsCOMPtr<nsICancelableRunnable> cancelable(do_QueryInterface(mIdleRunnable)); + if (cancelable) { + // Stop the pending idle runnable from doing anything + cancelable->Cancel(); + } + + mIdleRunnable = nullptr; +} + +void UntrustedModulesProcessor::DispatchBackgroundProcessing() { + MOZ_ASSERT(NS_IsMainThread()); + + if (!mAllowProcessing) { + return; + } + + nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod( + "UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this, + &UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue)); + + mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL); +} + +void UntrustedModulesProcessor::Enqueue( + glue::EnhancedModuleLoadInfo&& aModLoadInfo) { + if (!mAllowProcessing) { + return; + } + + DWORD bgThreadId = ToWin32ThreadId(mThread); + if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) { + // Exclude loads that were caused by our own background thread + return; + } + + MutexAutoLock lock(mUnprocessedMutex); + + Unused << mUnprocessedModuleLoads.emplaceBack(std::move(aModLoadInfo)); + + ScheduleNonEmptyQueueProcessing(lock); +} + +void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) { + if (!mAllowProcessing) { + return; + } + + // We do not need to attempt to exclude our background thread in this case + // because |aEvents| was accumulated prior to |mThread|'s existence. + + MutexAutoLock lock(mUnprocessedMutex); + + for (auto& event : aEvents) { + Unused << mUnprocessedModuleLoads.emplaceBack(std::move(event)); + } + + ScheduleNonEmptyQueueProcessing(lock); +} + +void UntrustedModulesProcessor::AssertRunningOnLazyIdleThread() { +#if defined(DEBUG) + PRThread* curThread; + PRThread* lazyIdleThread; + + MOZ_ASSERT(NS_SUCCEEDED(NS_GetCurrentThread()->GetPRThread(&curThread)) && + NS_SUCCEEDED(mThread->GetPRThread(&lazyIdleThread)) && + curThread == lazyIdleThread); +#endif // defined(DEBUG) +} + +RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetProcessedData() { + MOZ_ASSERT(NS_IsMainThread()); + + // Clear any background priority in case background processing is running. + BackgroundPriorityRegion::Clear(mThread); + + RefPtr<UntrustedModulesProcessor> self(this); + return InvokeAsync( + mThread->SerialEventTarget(), __func__, + [self = std::move(self)]() { return self->GetProcessedDataInternal(); }); +} + +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(XRE_IsParentProcess() && NS_IsMainThread()); + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + RefPtr<UntrustedModulesProcessor> self(this); + auto run = [self = std::move(self), modPaths = std::move(aModPaths), + runNormal = aRunAtNormalPriority]() mutable { + return self->GetModulesTrustInternal(std::move(modPaths), runNormal); + }; + + if (aRunAtNormalPriority) { + // Clear any background priority in case background processing is running. + BackgroundPriorityRegion::Clear(mThread); + + return InvokeAsync(mThread->SerialEventTarget(), __func__, std::move(run)); + } + + RefPtr<ModulesTrustPromise::Private> p( + new ModulesTrustPromise::Private(__func__)); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + const char* source = __func__; + + auto runWrap = [evtTarget = std::move(evtTarget), p, source, + run = std::move(run)]() mutable -> void { + InvokeAsync(evtTarget, source, std::move(run))->ChainTo(p.forget(), source); + }; + + nsCOMPtr<nsIRunnable> idleRunnable( + NS_NewRunnableFunction(source, std::move(runWrap))); + + nsresult rv = NS_DispatchToMainThreadQueue(idleRunnable.forget(), + EventQueuePriority::Idle); + if (NS_FAILED(rv)) { + p->Reject(rv, source); + } + + return p; +} + +RefPtr<UntrustedModulesPromise> +UntrustedModulesProcessor::GetProcessedDataInternal() { + AssertRunningOnLazyIdleThread(); + if (!XRE_IsParentProcess()) { + return GetProcessedDataInternalChildProcess(); + } + + ProcessModuleLoadQueue(); + + return GetAllProcessedData(__func__); +} + +RefPtr<UntrustedModulesPromise> UntrustedModulesProcessor::GetAllProcessedData( + const char* aSource) { + AssertRunningOnLazyIdleThread(); + + UntrustedModulesData result; + + if (!mProcessedModuleLoads) { + return UntrustedModulesPromise::CreateAndResolve(Nothing(), aSource); + } + + result.Swap(mProcessedModuleLoads); + + result.mElapsed = TimeStamp::Now() - TimeStamp::ProcessCreation(); + + return UntrustedModulesPromise::CreateAndResolve( + Some(UntrustedModulesData(std::move(result))), aSource); +} + +RefPtr<UntrustedModulesPromise> +UntrustedModulesProcessor::GetProcessedDataInternalChildProcess() { + AssertRunningOnLazyIdleThread(); + MOZ_ASSERT(!XRE_IsParentProcess()); + + RefPtr<GetModulesTrustPromise> whenProcessed( + ProcessModuleLoadQueueChildProcess(Priority::Default)); + + RefPtr<UntrustedModulesProcessor> self(this); + RefPtr<UntrustedModulesPromise::Private> p( + new UntrustedModulesPromise::Private(__func__)); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + + const char* source = __func__; + auto completionRoutine = [evtTarget = std::move(evtTarget), p, + self = std::move(self), source, + whenProcessed = std::move(whenProcessed)]() { + MOZ_ASSERT(NS_IsMainThread()); + if (!self->mAllowProcessing) { + // We can't do any more work, just reject all the things + whenProcessed->Then( + GetMainThreadSerialEventTarget(), source, + [p, source](Maybe<ModulesMapResultWithLoads>&& aResult) { + p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, source); + }, + [p, source](nsresult aRv) { p->Reject(aRv, source); }); + return; + } + + whenProcessed->Then( + evtTarget, source, + [p, self = std::move(self), + source](Maybe<ModulesMapResultWithLoads>&& aResult) mutable { + if (aResult.isSome()) { + self->CompleteProcessing(std::move(aResult.ref())); + } + self->GetAllProcessedData(source)->ChainTo(p.forget(), source); + }, + [p, source](nsresult aRv) { p->Reject(aRv, source); }); + }; + + // We always send |completionRoutine| on a trip through the main thread + // due to some subtlety with |mThread| being a LazyIdleThread: we can only + // Dispatch or Then to |mThread| from its creating thread, which is the + // main thread. Hopefully we can get rid of this in the future and just + // invoke whenProcessed->Then() directly. + nsresult rv = NS_DispatchToMainThread( + NS_NewRunnableFunction(__func__, std::move(completionRoutine))); + MOZ_ASSERT(NS_SUCCEEDED(rv)); + if (NS_FAILED(rv)) { + p->Reject(rv, __func__); + } + + return p; +} + +void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue() { + if (!mAllowProcessing) { + return; + } + + BackgroundPriorityRegion bgRgn; + + if (!mAllowProcessing) { + return; + } + + ProcessModuleLoadQueue(); +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord( + ModulesMap& aModules, const ModuleEvaluator& aModEval, + const glue::EnhancedModuleLoadInfo& aModLoadInfo) { + return GetOrAddModuleRecord(aModules, aModEval, + aModLoadInfo.mNtLoadInfo.mSectionName.AsString()); +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord( + ModulesMap& aModules, const ModuleEvaluator& aModEval, + const nsAString& aResolvedNtPath) { + MOZ_ASSERT(XRE_IsParentProcess()); + + auto addPtr = aModules.LookupForAdd(aResolvedNtPath); + if (addPtr) { + return addPtr.Data(); + } + + RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath)); + if (!(*newMod)) { + addPtr.OrRemove(); + return nullptr; + } + + Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod); + if (maybeTrust.isNothing()) { + addPtr.OrRemove(); + return nullptr; + } + + newMod->mTrustFlags = maybeTrust.value(); + + addPtr.OrInsert([newMod]() { return newMod; }); + + return newMod; +} + +RefPtr<ModuleRecord> UntrustedModulesProcessor::GetModuleRecord( + const ModulesMap& aModules, + const glue::EnhancedModuleLoadInfo& aModuleLoadInfo) { + MOZ_ASSERT(!XRE_IsParentProcess()); + + return aModules.Get(aModuleLoadInfo.mNtLoadInfo.mSectionName.AsString()); +} + +void UntrustedModulesProcessor::BackgroundProcessModuleLoadQueueChildProcess() { + RefPtr<GetModulesTrustPromise> whenProcessed( + ProcessModuleLoadQueueChildProcess(Priority::Background)); + + RefPtr<UntrustedModulesProcessor> self(this); + nsCOMPtr<nsISerialEventTarget> evtTarget(mThread->SerialEventTarget()); + + const char* source = __func__; + auto completionRoutine = [evtTarget = std::move(evtTarget), + self = std::move(self), source, + whenProcessed = std::move(whenProcessed)]() { + MOZ_ASSERT(NS_IsMainThread()); + if (!self->mAllowProcessing) { + // We can't do any more work, just no-op + whenProcessed->Then( + GetMainThreadSerialEventTarget(), source, + [](Maybe<ModulesMapResultWithLoads>&& aResult) {}, + [](nsresult aRv) {}); + return; + } + + whenProcessed->Then( + evtTarget, source, + [self = std::move(self)](Maybe<ModulesMapResultWithLoads>&& aResult) { + if (aResult.isNothing() || !self->mAllowProcessing) { + // Nothing to do + return; + } + + BackgroundPriorityRegion bgRgn; + self->CompleteProcessing(std::move(aResult.ref())); + }, + [](nsresult aRv) {}); + }; + + // We always send |completionRoutine| on a trip through the main thread + // due to some subtlety with |mThread| being a LazyIdleThread: we can only + // Dispatch or Then to |mThread| from its creating thread, which is the + // main thread. Hopefully we can get rid of this in the future and just + // invoke whenProcessed->Then() directly. + DebugOnly<nsresult> rv = NS_DispatchToMainThread( + NS_NewRunnableFunction(__func__, std::move(completionRoutine))); + MOZ_ASSERT(NS_SUCCEEDED(rv)); +} + +// This function contains multiple |mAllowProcessing| checks so that we can +// quickly bail out at the first sign of shutdown. This may be important when +// the current thread is running under background priority. +void UntrustedModulesProcessor::ProcessModuleLoadQueue() { + AssertRunningOnLazyIdleThread(); + if (!XRE_IsParentProcess()) { + BackgroundProcessModuleLoadQueueChildProcess(); + return; + } + + Vector<glue::EnhancedModuleLoadInfo> loadsToProcess; + + { // Scope for lock + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); + loadsToProcess.swap(mUnprocessedModuleLoads); + } + + if (!mAllowProcessing || loadsToProcess.empty()) { + return; + } + + ModuleEvaluator modEval; + MOZ_ASSERT(!!modEval); + if (!modEval) { + return; + } + + auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; }); + + Telemetry::BatchProcessedStackGenerator stackProcessor; + ModulesMap modules; + DependentModules dependentModules; + + Maybe<double> maybeXulLoadDuration; + Vector<Telemetry::ProcessedStack> processedStacks; + Vector<ProcessedModuleLoadEvent> processedEvents; + uint32_t sanitizationFailures = 0; + uint32_t trustTestFailures = 0; + + for (glue::EnhancedModuleLoadInfo& entry : loadsToProcess) { + if (!mAllowProcessing) { + return; + } + + RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modules, modEval, entry)); + if (!module) { + // We failed to obtain trust information about the module. + // Don't include test failures in the ping to avoid flooding it. + ++trustTestFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + bool isDependent = mIsFirstBatchProcessed + ? false + : dependentModules.Lookup(module->mSanitizedDllName); + + if (!mAllowProcessing) { + return; + } + + glue::EnhancedModuleLoadInfo::BacktraceType backtrace = + std::move(entry.mNtLoadInfo.mBacktrace); + ProcessedModuleLoadEvent event(std::move(entry), std::move(module), + isDependent); + + if (!event) { + // We don't have a sanitized DLL path, so we cannot include this event + // for privacy reasons. + ++sanitizationFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + if (event.IsTrusted()) { + if (event.IsXULLoad()) { + maybeXulLoadDuration = event.mLoadDurationMS; + } + + // Trusted modules are not included in the ping + continue; + } + + if (!mAllowProcessing) { + return; + } + + Telemetry::ProcessedStack processedStack = + stackProcessor.GetStackAndModules(backtrace); + + if (!mAllowProcessing) { + return; + } + + Unused << processedStacks.emplaceBack(std::move(processedStack)); + Unused << processedEvents.emplaceBack(std::move(event)); + } + + if (processedStacks.empty() && processedEvents.empty() && + !sanitizationFailures && !trustTestFailures) { + // Nothing to save + return; + } + + if (!mAllowProcessing) { + return; + } + + mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents), + std::move(processedStacks)); + if (maybeXulLoadDuration) { + MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS); + mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration; + } + + mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures; + mProcessedModuleLoads.mTrustTestFailures += trustTestFailures; +} + +template <typename ActorT> +static RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust( + ActorT* aActor, ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(NS_IsMainThread()); + return aActor->SendGetModulesTrust(std::move(aModPaths), + aRunAtNormalPriority); +} + +RefPtr<GetModulesTrustIpcPromise> +UntrustedModulesProcessor::SendGetModulesTrust(ModulePaths&& aModules, + Priority aPriority) { + MOZ_ASSERT(NS_IsMainThread()); + bool runNormal = aPriority == Priority::Default; + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: { + return ::SendGetModulesTrust(dom::ContentChild::GetSingleton(), + std::move(aModules), runNormal); + } + case GeckoProcessType_RDD: { + return ::SendGetModulesTrust(RDDParent::GetSingleton(), + std::move(aModules), runNormal); + } + default: { + MOZ_ASSERT_UNREACHABLE("Unsupported process type"); + return GetModulesTrustIpcPromise::CreateAndReject( + ipc::ResponseRejectReason::SendError, __func__); + } + } +} + +/** + * This method works very similarly to ProcessModuleLoadQueue, with the + * exception that a sandboxed child process does not have sufficient rights to + * be able to evaluate a module's trustworthiness. Instead, we accumulate the + * resolved paths for all of the modules in this batch and send them to the + * parent to determine trustworthiness. + * + * The parent process returns a list of untrusted modules and invokes + * CompleteProcessing to handle the remainder of the process. + * + * By doing it this way, we minimize the amount of data that needs to be sent + * over IPC and avoid the need to process every load's metadata only + * to throw most of it away (since most modules will be trusted). + */ +RefPtr<UntrustedModulesProcessor::GetModulesTrustPromise> +UntrustedModulesProcessor::ProcessModuleLoadQueueChildProcess( + UntrustedModulesProcessor::Priority aPriority) { + AssertRunningOnLazyIdleThread(); + MOZ_ASSERT(!XRE_IsParentProcess()); + + Vector<glue::EnhancedModuleLoadInfo> loadsToProcess; + + { // Scope for lock + MutexAutoLock lock(mUnprocessedMutex); + CancelScheduledProcessing(lock); + loadsToProcess.swap(mUnprocessedModuleLoads); + } + + if (loadsToProcess.empty()) { + // Nothing to process + return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__); + } + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet; + + // Build a set of modules to be processed by the parent + for (glue::EnhancedModuleLoadInfo& entry : loadsToProcess) { + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString()); + } + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + MOZ_ASSERT(!moduleNtPathSet.IsEmpty()); + if (moduleNtPathSet.IsEmpty()) { + // Nothing to process + return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__); + } + + ModulePaths moduleNtPaths(std::move(moduleNtPathSet)); + + if (!mAllowProcessing) { + return GetModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + RefPtr<UntrustedModulesProcessor> self(this); + + auto invoker = [self = std::move(self), + moduleNtPaths = std::move(moduleNtPaths), + priority = aPriority]() mutable { + return self->SendGetModulesTrust(std::move(moduleNtPaths), priority); + }; + + RefPtr<GetModulesTrustPromise::Private> p( + new GetModulesTrustPromise::Private(__func__)); + + if (!mAllowProcessing) { + p->Reject(NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + return p; + } + + // Send the IPC request via the main thread + InvokeAsync(GetMainThreadSerialEventTarget(), __func__, std::move(invoker)) + ->Then( + GetMainThreadSerialEventTarget(), __func__, + [p, loads = std::move(loadsToProcess)]( + Maybe<ModulesMapResult>&& aResult) mutable { + ModulesMapResultWithLoads result(std::move(aResult), + std::move(loads)); + p->Resolve(Some(ModulesMapResultWithLoads(std::move(result))), + __func__); + }, + [p](ipc::ResponseRejectReason aReason) { + p->Reject(NS_ERROR_FAILURE, __func__); + }); + + return p; +} + +void UntrustedModulesProcessor::CompleteProcessing( + UntrustedModulesProcessor::ModulesMapResultWithLoads&& aModulesAndLoads) { + MOZ_ASSERT(!XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + if (!mAllowProcessing) { + return; + } + + if (aModulesAndLoads.mModMapResult.isNothing()) { + // No untrusted modules in this batch, nothing to save. + return; + } + + // This map only contains information about modules deemed to be untrusted, + // plus xul.dll. Any module referenced by load requests that is *not* in the + // map is deemed to be trusted. + ModulesMap& modules = aModulesAndLoads.mModMapResult.ref().mModules; + const uint32_t& trustTestFailures = + aModulesAndLoads.mModMapResult.ref().mTrustTestFailures; + LoadsVec& loads = aModulesAndLoads.mLoads; + + if (modules.IsEmpty() && !trustTestFailures) { + // No data, nothing to save. + return; + } + + if (!mAllowProcessing) { + return; + } + + auto cleanup = MakeScopeExit([&]() { mIsFirstBatchProcessed = true; }); + + Telemetry::BatchProcessedStackGenerator stackProcessor; + DependentModules dependentModules; + + Maybe<double> maybeXulLoadDuration; + Vector<Telemetry::ProcessedStack> processedStacks; + Vector<ProcessedModuleLoadEvent> processedEvents; + uint32_t sanitizationFailures = 0; + + if (!modules.IsEmpty()) { + for (auto&& item : loads) { + if (!mAllowProcessing) { + return; + } + + RefPtr<ModuleRecord> module(GetModuleRecord(modules, item)); + if (!module) { + // If module is null then |item| is trusted + continue; + } + + if (!mAllowProcessing) { + return; + } + + bool isDependent = + mIsFirstBatchProcessed + ? false + : dependentModules.Lookup(module->mSanitizedDllName); + + if (!mAllowProcessing) { + return; + } + + glue::EnhancedModuleLoadInfo::BacktraceType backtrace = + std::move(item.mNtLoadInfo.mBacktrace); + ProcessedModuleLoadEvent event(std::move(item), std::move(module), + isDependent); + + if (!mAllowProcessing) { + return; + } + + if (!event) { + // We don't have a sanitized DLL path, so we cannot include this event + // for privacy reasons. + ++sanitizationFailures; + continue; + } + + if (!mAllowProcessing) { + return; + } + + if (event.IsXULLoad()) { + maybeXulLoadDuration = event.mLoadDurationMS; + // We saved the XUL load duration, but it is still trusted, so we + // continue. + continue; + } + + if (!mAllowProcessing) { + return; + } + + Telemetry::ProcessedStack processedStack = + stackProcessor.GetStackAndModules(backtrace); + + Unused << processedStacks.emplaceBack(std::move(processedStack)); + Unused << processedEvents.emplaceBack(std::move(event)); + } + } + + if (processedStacks.empty() && processedEvents.empty() && + !sanitizationFailures && !trustTestFailures) { + // Nothing to save + return; + } + + if (!mAllowProcessing) { + return; + } + + mProcessedModuleLoads.AddNewLoads(modules, std::move(processedEvents), + std::move(processedStacks)); + if (maybeXulLoadDuration) { + MOZ_ASSERT(!mProcessedModuleLoads.mXULLoadDurationMS); + mProcessedModuleLoads.mXULLoadDurationMS = maybeXulLoadDuration; + } + + mProcessedModuleLoads.mSanitizationFailures += sanitizationFailures; + mProcessedModuleLoads.mTrustTestFailures += trustTestFailures; +} + +// The thread priority of this job should match the priority that the child +// process is running with, as specified by |aRunAtNormalPriority|. +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal( + ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + if (aRunAtNormalPriority) { + return GetModulesTrustInternal(std::move(aModPaths)); + } + + BackgroundPriorityRegion bgRgn; + return GetModulesTrustInternal(std::move(aModPaths)); +} + +// For each module in |aModPaths|, evaluate its trustworthiness and only send +// ModuleRecords for untrusted modules back to the child process. We also save +// XUL's ModuleRecord so that the child process may report XUL's load time. +RefPtr<ModulesTrustPromise> UntrustedModulesProcessor::GetModulesTrustInternal( + ModulePaths&& aModPaths) { + MOZ_ASSERT(XRE_IsParentProcess()); + AssertRunningOnLazyIdleThread(); + + ModulesMapResult result; + + ModulesMap& modMap = result.mModules; + uint32_t& trustTestFailures = result.mTrustTestFailures; + + ModuleEvaluator modEval; + MOZ_ASSERT(!!modEval); + if (!modEval) { + return ModulesTrustPromise::CreateAndReject(NS_ERROR_FAILURE, __func__); + } + + // This map holds all modules regardless of trust status; we use this to + // filter any duplicates from the input. + ModulesMap modules; + + for (auto& resolvedNtPath : + aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) { + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + MOZ_ASSERT(!resolvedNtPath.IsEmpty()); + if (resolvedNtPath.IsEmpty()) { + continue; + } + + RefPtr<ModuleRecord> module( + GetOrAddModuleRecord(modules, modEval, resolvedNtPath)); + if (!module) { + // We failed to obtain trust information. + ++trustTestFailures; + continue; + } + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + if (module->IsTrusted() && !module->IsXUL()) { + // If the module is trusted we exclude it from results, unless it's XUL. + // (We save XUL so that the child process may report XUL's load time) + continue; + } + + if (!mAllowProcessing) { + return ModulesTrustPromise::CreateAndReject( + NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__); + } + + modMap.Put(resolvedNtPath, std::move(module)); + } + + return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__); +} + +} // namespace mozilla diff --git a/toolkit/xre/UntrustedModulesProcessor.h b/toolkit/xre/UntrustedModulesProcessor.h new file mode 100644 index 0000000000..546c05119a --- /dev/null +++ b/toolkit/xre/UntrustedModulesProcessor.h @@ -0,0 +1,157 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_UntrustedModulesProcessor_h +#define mozilla_UntrustedModulesProcessor_h + +#include "mozilla/Atomics.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/glue/WindowsDllServices.h" +#include "mozilla/LazyIdleThread.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" +#include "mozilla/UntrustedModulesData.h" +#include "mozilla/Vector.h" +#include "mozilla/WinHeaderOnlyUtils.h" +#include "nsCOMPtr.h" +#include "nsIObserver.h" +#include "nsIRunnable.h" +#include "nsISupportsImpl.h" +#include "nsString.h" + +namespace mozilla { + +class ModuleEvaluator; + +using UntrustedModulesPromise = + MozPromise<Maybe<UntrustedModulesData>, nsresult, true>; + +using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>; + +using GetModulesTrustIpcPromise = + MozPromise<Maybe<ModulesMapResult>, ipc::ResponseRejectReason, true>; + +class UntrustedModulesProcessor final : public nsIObserver { + public: + static RefPtr<UntrustedModulesProcessor> Create(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIOBSERVER + + // Called by DLL Services to explicitly begin shutting down + void Disable(); + + // Called by DLL Services to submit module load data to the processor + void Enqueue(glue::EnhancedModuleLoadInfo&& aModLoadInfo); + void Enqueue(ModuleLoadInfoVec&& aEvents); + + // Called by telemetry to retrieve the processed data + RefPtr<UntrustedModulesPromise> GetProcessedData(); + + // Called by IPC actors in the parent process to evaluate module trust + // on behalf of child processes + RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths, + bool aRunAtNormalPriority); + + UntrustedModulesProcessor(const UntrustedModulesProcessor&) = delete; + UntrustedModulesProcessor(UntrustedModulesProcessor&&) = delete; + UntrustedModulesProcessor& operator=(const UntrustedModulesProcessor&) = + delete; + UntrustedModulesProcessor& operator=(UntrustedModulesProcessor&&) = delete; + + private: + ~UntrustedModulesProcessor() = default; + UntrustedModulesProcessor(); + + static bool IsSupportedProcessType(); + + void AddObservers(); + void RemoveObservers(); + + void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock); + void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock); + void DispatchBackgroundProcessing(); + + void BackgroundProcessModuleLoadQueue(); + void ProcessModuleLoadQueue(); + + using LoadsVec = Vector<glue::EnhancedModuleLoadInfo>; + + class ModulesMapResultWithLoads final { + public: + ModulesMapResultWithLoads(Maybe<ModulesMapResult>&& aModMapResult, + LoadsVec&& aLoads) + : mModMapResult(std::move(aModMapResult)), mLoads(std::move(aLoads)) {} + Maybe<ModulesMapResult> mModMapResult; + LoadsVec mLoads; + }; + + using GetModulesTrustPromise = + MozPromise<Maybe<ModulesMapResultWithLoads>, nsresult, true>; + + enum class Priority { Default, Background }; + + RefPtr<GetModulesTrustPromise> ProcessModuleLoadQueueChildProcess( + Priority aPriority); + void BackgroundProcessModuleLoadQueueChildProcess(); + + void AssertRunningOnLazyIdleThread(); + + RefPtr<UntrustedModulesPromise> GetProcessedDataInternal(); + RefPtr<UntrustedModulesPromise> GetProcessedDataInternalChildProcess(); + + RefPtr<ModulesTrustPromise> GetModulesTrustInternal( + ModulePaths&& aModPaths, bool aRunAtNormalPriority); + RefPtr<ModulesTrustPromise> GetModulesTrustInternal(ModulePaths&& aModPaths); + + // These two functions are only called by the parent process + RefPtr<ModuleRecord> GetOrAddModuleRecord( + ModulesMap& aModules, const ModuleEvaluator& aModEval, + const glue::EnhancedModuleLoadInfo& aModLoadInfo); + RefPtr<ModuleRecord> GetOrAddModuleRecord(ModulesMap& aModules, + const ModuleEvaluator& aModEval, + const nsAString& aResolvedNtPath); + + // Only called by child processes + RefPtr<ModuleRecord> GetModuleRecord( + const ModulesMap& aModules, + const glue::EnhancedModuleLoadInfo& aModuleLoadInfo); + + RefPtr<GetModulesTrustIpcPromise> SendGetModulesTrust(ModulePaths&& aModules, + Priority aPriority); + + void CompleteProcessing(ModulesMapResultWithLoads&& aModulesAndLoads); + RefPtr<UntrustedModulesPromise> GetAllProcessedData(const char* aSource); + + private: + RefPtr<LazyIdleThread> mThread; + + Mutex mUnprocessedMutex; + + // The members in this group are protected by mUnprocessedMutex + Vector<glue::EnhancedModuleLoadInfo> mUnprocessedModuleLoads; + nsCOMPtr<nsIRunnable> mIdleRunnable; + + // This member must only be touched on mThread + UntrustedModulesData mProcessedModuleLoads; + + // This member may be touched by any thread + Atomic<bool> mAllowProcessing; + + // This member must only be touched on mThread, making sure a hash table of + // dependent modules is initialized once in a process when the first batch + // of queued events is processed. We don't need the hash table to process + // subsequent queues because we're interested in modules imported via the + // executable's Import Table which are all expected to be in the first queue. + // A boolean flag is enough to control initialization because tasks of + // processing queues are serialized. + bool mIsFirstBatchProcessed; +}; + +} // namespace mozilla + +#endif // mozilla_UntrustedModulesProcessor_h diff --git a/toolkit/xre/WinDllServices.cpp b/toolkit/xre/WinDllServices.cpp new file mode 100644 index 0000000000..eb82d6d647 --- /dev/null +++ b/toolkit/xre/WinDllServices.cpp @@ -0,0 +1,135 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "mozilla/WinDllServices.h" + +#include <windows.h> +#include <psapi.h> + +#include "mozilla/ClearOnShutdown.h" +#include "mozilla/SchedulerGroup.h" +#include "mozilla/Services.h" +#include "mozilla/StaticLocalPtr.h" +#include "mozilla/UntrustedModulesProcessor.h" +#include "nsCOMPtr.h" +#include "nsIObserverService.h" +#include "nsString.h" +#include "nsXULAppAPI.h" + +namespace mozilla { + +const char* DllServices::kTopicDllLoadedMainThread = "dll-loaded-main-thread"; +const char* DllServices::kTopicDllLoadedNonMainThread = + "dll-loaded-non-main-thread"; + +/* static */ +DllServices* DllServices::Get() { + static StaticLocalRefPtr<DllServices> sInstance( + []() -> already_AddRefed<DllServices> { + RefPtr<DllServices> dllSvc(new DllServices()); + // Full DLL services require XPCOM, which GMP doesn't have + if (XRE_IsGMPluginProcess()) { + dllSvc->EnableBasic(); + } else { + dllSvc->EnableFull(); + } + + auto setClearOnShutdown = [ptr = &sInstance]() -> void { + ClearOnShutdown(ptr); + }; + + if (NS_IsMainThread()) { + setClearOnShutdown(); + return dllSvc.forget(); + } + + SchedulerGroup::Dispatch( + TaskCategory::Other, + NS_NewRunnableFunction("mozilla::DllServices::Get", + std::move(setClearOnShutdown))); + + return dllSvc.forget(); + }()); + + return sInstance; +} + +void DllServices::StartUntrustedModulesProcessor() { + MOZ_ASSERT(NS_IsMainThread()); + MOZ_ASSERT(!mUntrustedModulesProcessor); + mUntrustedModulesProcessor = UntrustedModulesProcessor::Create(); +} + +RefPtr<UntrustedModulesPromise> DllServices::GetUntrustedModulesData() { + if (!mUntrustedModulesProcessor) { + return UntrustedModulesPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, + __func__); + } + + return mUntrustedModulesProcessor->GetProcessedData(); +} + +void DllServices::DisableFull() { + if (XRE_IsGMPluginProcess()) { + return; + } + + if (mUntrustedModulesProcessor) { + mUntrustedModulesProcessor->Disable(); + } + + glue::DllServices::DisableFull(); +} + +RefPtr<ModulesTrustPromise> DllServices::GetModulesTrust( + ModulePaths&& aModPaths, bool aRunAtNormalPriority) { + if (!mUntrustedModulesProcessor) { + return ModulesTrustPromise::CreateAndReject(NS_ERROR_NOT_IMPLEMENTED, + __func__); + } + + return mUntrustedModulesProcessor->GetModulesTrust(std::move(aModPaths), + aRunAtNormalPriority); +} + +void DllServices::NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) { + MOZ_ASSERT(NS_IsMainThread()); + + const char* topic; + + if (aModLoadInfo.mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) { + topic = kTopicDllLoadedMainThread; + } else { + topic = kTopicDllLoadedNonMainThread; + } + + // We save the path to a nsAutoString because once we have submitted + // aModLoadInfo for processing there is no guarantee that the original + // buffer will continue to be valid. + nsAutoString dllFilePath(aModLoadInfo.GetSectionName()); + + if (mUntrustedModulesProcessor) { + mUntrustedModulesProcessor->Enqueue(std::move(aModLoadInfo)); + } + + nsCOMPtr<nsIObserverService> obsServ(mozilla::services::GetObserverService()); + if (!obsServ) { + return; + } + + obsServ->NotifyObservers(nullptr, topic, dllFilePath.get()); +} + +void DllServices::NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) { + MOZ_ASSERT(NS_IsMainThread()); + if (!mUntrustedModulesProcessor) { + return; + } + + mUntrustedModulesProcessor->Enqueue(std::move(aEvents)); +} + +} // namespace mozilla diff --git a/toolkit/xre/WinDllServices.h b/toolkit/xre/WinDllServices.h new file mode 100644 index 0000000000..cb78dc5842 --- /dev/null +++ b/toolkit/xre/WinDllServices.h @@ -0,0 +1,56 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WinDllServices_h +#define mozilla_WinDllServices_h + +#include "mozilla/glue/WindowsDllServices.h" +#include "mozilla/Maybe.h" +#include "mozilla/MozPromise.h" +#include "mozilla/RefPtr.h" + +namespace mozilla { + +class UntrustedModulesData; +class UntrustedModulesProcessor; + +using UntrustedModulesPromise = + MozPromise<Maybe<UntrustedModulesData>, nsresult, true>; + +struct ModulePaths; +class ModulesMapResult; + +using ModulesTrustPromise = MozPromise<ModulesMapResult, nsresult, true>; + +class DllServices : public glue::DllServices { + public: + static DllServices* Get(); + + virtual void DisableFull() override; + + static const char* kTopicDllLoadedMainThread; + static const char* kTopicDllLoadedNonMainThread; + + void StartUntrustedModulesProcessor(); + + RefPtr<UntrustedModulesPromise> GetUntrustedModulesData(); + + RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths, + bool aRunAtNormalPriority); + + private: + DllServices() = default; + ~DllServices() = default; + + void NotifyDllLoad(glue::EnhancedModuleLoadInfo&& aModLoadInfo) override; + void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) override; + + RefPtr<UntrustedModulesProcessor> mUntrustedModulesProcessor; +}; + +} // namespace mozilla + +#endif // mozilla_WinDllServices_h diff --git a/toolkit/xre/WinTokenUtils.cpp b/toolkit/xre/WinTokenUtils.cpp new file mode 100644 index 0000000000..4774ec6dad --- /dev/null +++ b/toolkit/xre/WinTokenUtils.cpp @@ -0,0 +1,72 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#include "WinTokenUtils.h" +#include "nsWindowsHelpers.h" + +using namespace mozilla; + +// If |aToken| is nullptr, CheckTokenMembership uses the calling thread's +// primary token to check membership for. +static LauncherResult<bool> IsMemberOfAdministrators( + const nsAutoHandle& aToken) { + BYTE adminsGroupSid[SECURITY_MAX_SID_SIZE]; + DWORD adminsGroupSidSize = sizeof(adminsGroupSid); + if (!CreateWellKnownSid(WinBuiltinAdministratorsSid, nullptr, adminsGroupSid, + &adminsGroupSidSize)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + + BOOL isMember; + if (!CheckTokenMembership(aToken, adminsGroupSid, &isMember)) { + return LAUNCHER_ERROR_FROM_LAST(); + } + return !!isMember; +} + +static LauncherResult<bool> IsUacEnabled() { + DWORD len = sizeof(DWORD); + DWORD value; + LSTATUS status = RegGetValueW( + HKEY_LOCAL_MACHINE, + L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Policies\\System", + L"EnableLUA", RRF_RT_DWORD, nullptr, &value, &len); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + // UAC is disabled only when EnableLUA is 0. + return (value != 0); +} + +namespace mozilla { + +LauncherResult<bool> IsAdminWithoutUac() { + // To check whether the process was launched with Administrator priviledges + // or not, we cannot simply check the integrity level of the current process + // because the launcher process spawns the browser process with the medium + // integrity level even though the launcher process is high integrity level. + // We check whether the thread's token contains Administratos SID or not + // instead. + LauncherResult<bool> containsAdminGroup = + IsMemberOfAdministrators(nsAutoHandle()); + if (containsAdminGroup.isErr()) { + return containsAdminGroup.propagateErr(); + } + + if (!containsAdminGroup.unwrap()) { + return false; + } + + LauncherResult<bool> isUacEnabled = IsUacEnabled(); + if (isUacEnabled.isErr()) { + return isUacEnabled.propagateErr(); + } + + return !isUacEnabled.unwrap(); +} + +} // namespace mozilla diff --git a/toolkit/xre/WinTokenUtils.h b/toolkit/xre/WinTokenUtils.h new file mode 100644 index 0000000000..48b9ec5a8b --- /dev/null +++ b/toolkit/xre/WinTokenUtils.h @@ -0,0 +1,18 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_WinTokenUtils_h +#define mozilla_WinTokenUtils_h + +#include "mozilla/WinHeaderOnlyUtils.h" + +namespace mozilla { + +LauncherResult<bool> IsAdminWithoutUac(); + +} // namespace mozilla + +#endif // mozilla_WinTokenUtils_h diff --git a/toolkit/xre/components.conf b/toolkit/xre/components.conf new file mode 100644 index 0000000000..8c29cf27e1 --- /dev/null +++ b/toolkit/xre/components.conf @@ -0,0 +1,66 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +if defined('MOZ_CRASHREPORTER'): + crash_reporter = ['@mozilla.org/toolkit/crash-reporter;1'] +else: + crash_reporter = [] + +Classes = [ + { + 'js_name': 'appinfo', + 'cid': '{95d89e3e-a169-41a3-8e56-719978e15b12}', + 'contract_ids': [ + '@mozilla.org/xre/app-info;1', + '@mozilla.org/xre/runtime;1', + ] + crash_reporter, + 'interfaces': ['nsIXULRuntime', 'nsIXULAppInfo'], + 'legacy_constructor': 'mozilla::AppInfoConstructor', + 'headers': ['nsAppRunner.h'], + 'processes': ProcessSelector.ALLOW_IN_SOCKET_PROCESS, + 'overridable': True, + }, + { + 'cid': '{471f4944-1dd2-11b2-87ac-90be0a51d609}', + 'contract_ids': ['@mozilla.org/embedcomp/rangefind;1'], + 'type': 'nsFind', + 'headers': ['/toolkit/components/find/nsFind.h'], + }, + { + 'cid': '{7e677795-c582-4cd1-9e8d-8271b3474d2a}', + 'contract_ids': ['@mozilla.org/embedding/browser/nsWebBrowserPersist;1'], + 'type': 'nsWebBrowserPersist', + 'headers': ['/dom/webbrowserpersist/nsWebBrowserPersist.h'], + }, + { + 'js_name': 'ww', + 'cid': '{a21bfa01-f349-4394-a84c-8de5cf0737d0}', + 'contract_ids': ['@mozilla.org/embedcomp/window-watcher;1'], + 'interfaces': ['nsIWindowWatcher'], + 'type': 'nsWindowWatcher', + 'headers': ['nsWindowWatcher.h'], + 'init_method': 'Init', + 'overridable': True, + }, + { + 'cid': '{5573967d-f6cf-4c63-8e0e-9ac06e04d62b}', + 'contract_ids': ['@mozilla.org/xre/directory-provider;1'], + 'singleton': True, + 'type': 'nsXREDirProvider', + 'constructor': 'nsXREDirProvider::GetSingleton', + 'headers': ['/toolkit/xre/nsXREDirProvider.h'], + }, +] + +if defined('MOZ_XUL'): + Classes += [ + { + 'cid': '{4e4aae11-8901-46cc-8217-dad7c5415873}', + 'contract_ids': ['@mozilla.org/embedcomp/dialogparam;1'], + 'type': 'nsDialogParamBlock', + 'headers': ['/toolkit/components/windowwatcher/nsDialogParamBlock.h'], + }, + ] diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp new file mode 100644 index 0000000000..e533822a65 --- /dev/null +++ b/toolkit/xre/glxtest.cpp @@ -0,0 +1,1222 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * vim: sw=2 ts=8 et : + */ +/* 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/. */ + +////////////////////////////////////////////////////////////////////////////// +// +// Explanation: See bug 639842. Safely getting GL driver info on X11 is hard, +// because the only way to do that is to create a GL context and call +// glGetString(), but with bad drivers, just creating a GL context may crash. +// +// This file implements the idea to do that in a separate process. +// +// The only non-static function here is fire_glxtest_process(). It creates a +// pipe, publishes its 'read' end as the mozilla::widget::glxtest_pipe global +// variable, forks, and runs that GLX probe in the child process, which runs the +// childgltest() static function. This creates a X connection, a GLX context, +// calls glGetString, and writes that to the 'write' end of the pipe. + +#include <cstdio> +#include <cstdlib> +#include <dlfcn.h> +#include <fcntl.h> +#include <unistd.h> + +#include "mozilla/Unused.h" +#include "nsAppRunner.h" // for IsWaylandDisabled on IsX11EGLEnabled +#include "stdint.h" + +#ifdef __SUNPRO_CC +# include <stdio.h> +#endif + +#ifdef MOZ_X11 +# include "X11/Xlib.h" +# include "X11/Xutil.h" +#endif + +#ifdef MOZ_WAYLAND +# include "mozilla/widget/mozwayland.h" +# include "mozilla/widget/xdg-output-unstable-v1-client-protocol.h" +#endif + +#ifdef MOZ_X11 +// stuff from glx.h +typedef struct __GLXcontextRec* GLXContext; +typedef XID GLXPixmap; +typedef XID GLXDrawable; +/* GLX 1.3 and later */ +typedef struct __GLXFBConfigRec* GLXFBConfig; +typedef XID GLXFBConfigID; +typedef XID GLXContextID; +typedef XID GLXWindow; +typedef XID GLXPbuffer; +# define GLX_RGBA 4 +# define GLX_RED_SIZE 8 +# define GLX_GREEN_SIZE 9 +# define GLX_BLUE_SIZE 10 +#endif + +// stuff from gl.h +typedef uint8_t GLubyte; +typedef uint32_t GLenum; +#define GL_VENDOR 0x1F00 +#define GL_RENDERER 0x1F01 +#define GL_VERSION 0x1F02 + +// GLX_MESA_query_renderer +// clang-format off +#define GLX_RENDERER_VENDOR_ID_MESA 0x8183 +#define GLX_RENDERER_DEVICE_ID_MESA 0x8184 +#define GLX_RENDERER_VERSION_MESA 0x8185 +#define GLX_RENDERER_ACCELERATED_MESA 0x8186 +#define GLX_RENDERER_VIDEO_MEMORY_MESA 0x8187 +#define GLX_RENDERER_UNIFIED_MEMORY_ARCHITECTURE_MESA 0x8188 +#define GLX_RENDERER_PREFERRED_PROFILE_MESA 0x8189 +#define GLX_RENDERER_OPENGL_CORE_PROFILE_VERSION_MESA 0x818A +#define GLX_RENDERER_OPENGL_COMPATIBILITY_PROFILE_VERSION_MESA 0x818B +#define GLX_RENDERER_OPENGL_ES_PROFILE_VERSION_MESA 0x818C +#define GLX_RENDERER_OPENGL_ES2_PROFILE_VERSION_MESA 0x818D +#define GLX_RENDERER_ID_MESA 0x818E +// clang-format on + +// stuff from egl.h +typedef intptr_t EGLAttrib; +typedef int EGLBoolean; +typedef void* EGLConfig; +typedef void* EGLContext; +typedef void* EGLDeviceEXT; +typedef void* EGLDisplay; +typedef int EGLint; +typedef void* EGLNativeDisplayType; +typedef void* EGLSurface; +typedef void* (*PFNEGLGETPROCADDRESS)(const char*); + +#define EGL_NO_CONTEXT nullptr +#define EGL_FALSE 0 +#define EGL_TRUE 1 +#define EGL_BLUE_SIZE 0x3022 +#define EGL_GREEN_SIZE 0x3023 +#define EGL_RED_SIZE 0x3024 +#define EGL_NONE 0x3038 +#define EGL_VENDOR 0x3053 +#define EGL_CONTEXT_CLIENT_VERSION 0x3098 +#define EGL_DEVICE_EXT 0x322C +#define EGL_DRM_DEVICE_FILE_EXT 0x3233 + +// stuff from xf86drm.h +#define DRM_NODE_RENDER 2 +#define DRM_NODE_MAX 3 + +typedef struct _drmDevice { + char** nodes; + int available_nodes; + int bustype; + union { + void* pci; + void* usb; + void* platform; + void* host1x; + } businfo; + union { + void* pci; + void* usb; + void* platform; + void* host1x; + } deviceinfo; +} drmDevice, *drmDevicePtr; + +// Open libGL and load needed symbols +#if defined(__OpenBSD__) || defined(__NetBSD__) +# define LIBGL_FILENAME "libGL.so" +# define LIBGLES_FILENAME "libGLESv2.so" +# define LIBEGL_FILENAME "libEGL.so" +# define LIBDRM_FILENAME "libdrm.so" +#else +# define LIBGL_FILENAME "libGL.so.1" +# define LIBGLES_FILENAME "libGLESv2.so.2" +# define LIBEGL_FILENAME "libEGL.so.1" +# define LIBDRM_FILENAME "libdrm.so.2" +#endif + +#define EXIT_FAILURE_BUFFER_TOO_SMALL 2 + +namespace mozilla { +namespace widget { +// the read end of the pipe, which will be used by GfxInfo +extern int glxtest_pipe; +// the PID of the glxtest process, to pass to waitpid() +extern pid_t glxtest_pid; +} // namespace widget +} // namespace mozilla + +// the write end of the pipe, which we're going to write to +static int write_end_of_the_pipe = -1; + +// our buffer, size and used length +static char* glxtest_buf = nullptr; +static int glxtest_bufsize = 0; +static int glxtest_length = 0; + +// C++ standard collides with C standard in that it doesn't allow casting void* +// to function pointer types. So the work-around is to convert first to size_t. +// http://www.trilithium.com/johan/2004/12/problem-with-dlsym/ +template <typename func_ptr_type> +static func_ptr_type cast(void* ptr) { + return reinterpret_cast<func_ptr_type>(reinterpret_cast<size_t>(ptr)); +} + +static void record_value(const char* format, ...) { + // Don't add more if the buffer is full. + if (glxtest_bufsize <= glxtest_length) { + return; + } + + // Append the new values to the buffer, not to exceed the remaining space. + int remaining = glxtest_bufsize - glxtest_length; + va_list args; + va_start(args, format); + int max_added = + vsnprintf(glxtest_buf + glxtest_length, remaining, format, args); + va_end(args); + + // snprintf returns how many char it could have added, not how many it added. + // It is important to get this right since it will control how many chars we + // will attempt to write to the pipe fd. + if (max_added > remaining) { + glxtest_length += remaining; + } else { + glxtest_length += max_added; + } +} + +static void record_error(const char* str) { record_value("ERROR\n%s\n", str); } + +static void record_warning(const char* str) { + record_value("WARNING\n%s\n", str); +} + +static void record_flush() { + mozilla::Unused << write(write_end_of_the_pipe, glxtest_buf, glxtest_length); +} + +#ifdef MOZ_X11 +static int x_error_handler(Display*, XErrorEvent* ev) { + record_value( + "ERROR\nX error, error_code=%d, " + "request_code=%d, minor_code=%d\n", + ev->error_code, ev->request_code, ev->minor_code); + record_flush(); + _exit(EXIT_FAILURE); + return 0; +} +#endif + +// childgltest is declared inside extern "C" so that the name is not mangled. +// The name is used in build/valgrind/x86_64-pc-linux-gnu.sup to suppress +// memory leak errors because we run it inside a short lived fork and we don't +// care about leaking memory +extern "C" { + +static void close_logging() { + // we want to redirect to /dev/null stdout, stderr, and while we're at it, + // any PR logging file descriptors. To that effect, we redirect all positive + // file descriptors up to what open() returns here. In particular, 1 is stdout + // and 2 is stderr. + int fd = open("/dev/null", O_WRONLY); + for (int i = 1; i < fd; i++) { + dup2(fd, i); + } + close(fd); + + if (getenv("MOZ_AVOID_OPENGL_ALTOGETHER")) { + const char* msg = "ERROR\nMOZ_AVOID_OPENGL_ALTOGETHER envvar set"; + mozilla::Unused << write(write_end_of_the_pipe, msg, strlen(msg)); + exit(EXIT_FAILURE); + } +} + +#define PCI_FILL_IDENT 0x0001 +#define PCI_FILL_CLASS 0x0020 +#define PCI_BASE_CLASS_DISPLAY 0x03 + +static void get_pci_status() { + void* libpci = dlopen("libpci.so.3", RTLD_LAZY); + if (!libpci) { + libpci = dlopen("libpci.so", RTLD_LAZY); + } + if (!libpci) { + record_warning("libpci missing"); + return; + } + + typedef struct pci_dev { + struct pci_dev* next; + uint16_t domain_16; + uint8_t bus, dev, func; + unsigned int known_fields; + uint16_t vendor_id, device_id; + uint16_t device_class; + } pci_dev; + + typedef struct pci_access { + unsigned int method; + int writeable; + int buscentric; + char* id_file_name; + int free_id_name; + int numeric_ids; + unsigned int id_lookup_mode; + int debugging; + void* error; + void* warning; + void* debug; + pci_dev* devices; + } pci_access; + + typedef pci_access* (*PCIALLOC)(void); + PCIALLOC pci_alloc = cast<PCIALLOC>(dlsym(libpci, "pci_alloc")); + + typedef void (*PCIINIT)(pci_access*); + PCIINIT pci_init = cast<PCIINIT>(dlsym(libpci, "pci_init")); + + typedef void (*PCICLEANUP)(pci_access*); + PCICLEANUP pci_cleanup = cast<PCICLEANUP>(dlsym(libpci, "pci_cleanup")); + + typedef void (*PCISCANBUS)(pci_access*); + PCISCANBUS pci_scan_bus = cast<PCISCANBUS>(dlsym(libpci, "pci_scan_bus")); + + typedef void (*PCIFILLINFO)(pci_dev*, int); + PCIFILLINFO pci_fill_info = cast<PCIFILLINFO>(dlsym(libpci, "pci_fill_info")); + + if (!pci_alloc || !pci_cleanup || !pci_scan_bus || !pci_fill_info) { + dlclose(libpci); + record_warning("libpci missing methods"); + return; + } + + pci_access* pacc = pci_alloc(); + if (!pacc) { + dlclose(libpci); + record_warning("libpci alloc failed"); + return; + } + + pci_init(pacc); + pci_scan_bus(pacc); + + for (pci_dev* dev = pacc->devices; dev; dev = dev->next) { + pci_fill_info(dev, PCI_FILL_IDENT | PCI_FILL_CLASS); + if (dev->device_class >> 8 == PCI_BASE_CLASS_DISPLAY && dev->vendor_id && + dev->device_id) { + record_value("PCI_VENDOR_ID\n0x%04x\nPCI_DEVICE_ID\n0x%04x\n", + dev->vendor_id, dev->device_id); + } + } + + pci_cleanup(pacc); + dlclose(libpci); +} + +#ifdef MOZ_WAYLAND +static bool device_has_name(const drmDevice* device, const char* name) { + for (size_t i = 0; i < DRM_NODE_MAX; i++) { + if (!(device->available_nodes & (1 << i))) { + continue; + } + if (strcmp(device->nodes[i], name) == 0) { + return true; + } + } + return false; +} + +static char* get_render_name(const char* name) { + void* libdrm = dlopen(LIBDRM_FILENAME, RTLD_LAZY); + if (!libdrm) { + record_warning("Failed to open libdrm"); + return nullptr; + } + + typedef int (*DRMGETDEVICES2)(uint32_t, drmDevicePtr*, int); + DRMGETDEVICES2 drmGetDevices2 = + cast<DRMGETDEVICES2>(dlsym(libdrm, "drmGetDevices2")); + + typedef void (*DRMFREEDEVICE)(drmDevicePtr*); + DRMFREEDEVICE drmFreeDevice = + cast<DRMFREEDEVICE>(dlsym(libdrm, "drmFreeDevice")); + + if (!drmGetDevices2 || !drmFreeDevice) { + record_warning( + "libdrm missing methods for drmGetDevices2 or drmFreeDevice"); + dlclose(libdrm); + return nullptr; + } + + uint32_t flags = 0; + int devices_len = drmGetDevices2(flags, nullptr, 0); + if (devices_len < 0) { + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return nullptr; + } + drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*)); + if (!devices) { + record_warning("Allocation error"); + dlclose(libdrm); + return nullptr; + } + devices_len = drmGetDevices2(flags, devices, devices_len); + if (devices_len < 0) { + free(devices); + record_warning("drmGetDevices2 failed"); + dlclose(libdrm); + return nullptr; + } + + const drmDevice* match = nullptr; + for (int i = 0; i < devices_len; i++) { + if (device_has_name(devices[i], name)) { + match = devices[i]; + break; + } + } + + char* render_name = nullptr; + if (!match) { + record_warning("Cannot find DRM device"); + } else if (!(match->available_nodes & (1 << DRM_NODE_RENDER))) { + record_warning("DRM device has no render node"); + } else { + render_name = strdup(match->nodes[DRM_NODE_RENDER]); + } + + for (int i = 0; i < devices_len; i++) { + drmFreeDevice(&devices[i]); + } + free(devices); + + dlclose(libdrm); + return render_name; +} +#endif + +static void get_gles_status(EGLDisplay dpy, + PFNEGLGETPROCADDRESS eglGetProcAddress) { + typedef EGLBoolean (*PFNEGLCHOOSECONFIGPROC)( + EGLDisplay dpy, EGLint const* attrib_list, EGLConfig* configs, + EGLint config_size, EGLint* num_config); + PFNEGLCHOOSECONFIGPROC eglChooseConfig = + cast<PFNEGLCHOOSECONFIGPROC>(eglGetProcAddress("eglChooseConfig")); + + typedef EGLContext (*PFNEGLCREATECONTEXTPROC)( + EGLDisplay dpy, EGLConfig config, EGLContext share_context, + EGLint const* attrib_list); + PFNEGLCREATECONTEXTPROC eglCreateContext = + cast<PFNEGLCREATECONTEXTPROC>(eglGetProcAddress("eglCreateContext")); + + typedef EGLSurface (*PFNEGLCREATEPBUFFERSURFACEPROC)( + EGLDisplay dpy, EGLConfig config, EGLint const* attrib_list); + PFNEGLCREATEPBUFFERSURFACEPROC eglCreatePbufferSurface = + cast<PFNEGLCREATEPBUFFERSURFACEPROC>( + eglGetProcAddress("eglCreatePbufferSurface")); + + typedef EGLBoolean (*PFNEGLMAKECURRENTPROC)( + EGLDisplay dpy, EGLSurface draw, EGLSurface read, EGLContext context); + PFNEGLMAKECURRENTPROC eglMakeCurrent = + cast<PFNEGLMAKECURRENTPROC>(eglGetProcAddress("eglMakeCurrent")); + + typedef const char* (*PFNEGLQUERYDEVICESTRINGEXTPROC)(EGLDeviceEXT device, + EGLint name); + PFNEGLQUERYDEVICESTRINGEXTPROC eglQueryDeviceStringEXT = + cast<PFNEGLQUERYDEVICESTRINGEXTPROC>( + eglGetProcAddress("eglQueryDeviceStringEXT")); + + typedef EGLBoolean (*PFNEGLQUERYDISPLAYATTRIBEXTPROC)( + EGLDisplay dpy, EGLint name, EGLAttrib * value); + PFNEGLQUERYDISPLAYATTRIBEXTPROC eglQueryDisplayAttribEXT = + cast<PFNEGLQUERYDISPLAYATTRIBEXTPROC>( + eglGetProcAddress("eglQueryDisplayAttribEXT")); + + if (!eglChooseConfig || !eglCreateContext || !eglCreatePbufferSurface || + !eglMakeCurrent) { + record_error("libEGL missing methods for GLES test"); + return; + } + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString")); + + // Implementations disagree about whether eglGetProcAddress or dlsym + // should be used for getting functions from the actual API, see + // https://github.com/anholt/libepoxy/commit/14f24485e33816139398d1bd170d617703473738 + void* libgl = nullptr; + if (!glGetString) { + libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) { + libgl = dlopen(LIBGLES_FILENAME, RTLD_LAZY); + if (!libgl) { + record_error("Unable to load " LIBGL_FILENAME " or " LIBGLES_FILENAME); + return; + } + } + + glGetString = cast<PFNGLGETSTRING>(dlsym(libgl, "glGetString")); + if (!glGetString) { + dlclose(libgl); + record_error("libGL or libGLESv2 glGetString missing"); + return; + } + } + + EGLint config_attrs[] = {EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, + EGL_BLUE_SIZE, 8, EGL_NONE}; + EGLConfig config; + EGLint num_config; + eglChooseConfig(dpy, config_attrs, &config, 1, &num_config); + EGLint ctx_attrs[] = {EGL_CONTEXT_CLIENT_VERSION, 3, EGL_NONE}; + EGLContext ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs); + EGLSurface pbuf = eglCreatePbufferSurface(dpy, config, nullptr); + eglMakeCurrent(dpy, pbuf, pbuf, ectx); + + const GLubyte* versionString = glGetString(GL_VERSION); + const GLubyte* vendorString = glGetString(GL_VENDOR); + const GLubyte* rendererString = glGetString(GL_RENDERER); + + if (versionString && vendorString && rendererString) { + record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\nTRUE\n", + vendorString, rendererString, versionString); + } else { + record_error("libGLESv2 glGetString returned null"); + } + + if (eglQueryDeviceStringEXT) { + EGLDeviceEXT device = nullptr; + + if (eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, (EGLAttrib*)&device) == + EGL_TRUE) { + const char* deviceString = + eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT); + if (deviceString) { + record_value("MESA_ACCELERATED\nTRUE\n"); + +#ifdef MOZ_WAYLAND + char* renderDeviceName = get_render_name(deviceString); + if (renderDeviceName) { + record_value("DRM_RENDERDEVICE\n%s\n", renderDeviceName); + } else { + record_warning("Can't find render node name for DRM device"); + } +#endif + } + } + } + + if (libgl) { + dlclose(libgl); + } +} + +static void get_egl_status(EGLNativeDisplayType native_dpy, bool gles_test) { + void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY); + if (!libegl) { + record_warning("libEGL missing"); + return; + } + + PFNEGLGETPROCADDRESS eglGetProcAddress = + cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress")); + + if (!eglGetProcAddress) { + dlclose(libegl); + record_error("no eglGetProcAddress"); + return; + } + + typedef EGLDisplay (*PFNEGLGETDISPLAYPROC)(void* native_display); + PFNEGLGETDISPLAYPROC eglGetDisplay = + cast<PFNEGLGETDISPLAYPROC>(eglGetProcAddress("eglGetDisplay")); + + typedef EGLBoolean (*PFNEGLINITIALIZEPROC)(EGLDisplay dpy, EGLint * major, + EGLint * minor); + PFNEGLINITIALIZEPROC eglInitialize = + cast<PFNEGLINITIALIZEPROC>(eglGetProcAddress("eglInitialize")); + + typedef EGLBoolean (*PFNEGLTERMINATEPROC)(EGLDisplay dpy); + PFNEGLTERMINATEPROC eglTerminate = + cast<PFNEGLTERMINATEPROC>(eglGetProcAddress("eglTerminate")); + + if (!eglGetDisplay || !eglInitialize || !eglTerminate) { + dlclose(libegl); + record_error("libEGL missing methods"); + return; + } + + EGLDisplay dpy = eglGetDisplay(native_dpy); + if (!dpy) { + dlclose(libegl); + record_warning("libEGL no display"); + return; + } + + EGLint major, minor; + if (!eglInitialize(dpy, &major, &minor)) { + dlclose(libegl); + record_warning("libEGL initialize failed"); + return; + } + + if (gles_test) { + get_gles_status(dpy, eglGetProcAddress); + } + + typedef const char* (*PFNEGLGETDISPLAYDRIVERNAMEPROC)(EGLDisplay dpy); + PFNEGLGETDISPLAYDRIVERNAMEPROC eglGetDisplayDriverName = + cast<PFNEGLGETDISPLAYDRIVERNAMEPROC>( + eglGetProcAddress("eglGetDisplayDriverName")); + if (eglGetDisplayDriverName) { + const char* driDriver = eglGetDisplayDriverName(dpy); + if (driDriver) { + record_value("DRI_DRIVER\n%s\n", driDriver); + } + } + + eglTerminate(dpy); + dlclose(libegl); +} + +#ifdef MOZ_X11 +static void get_x11_screen_info(Display* dpy) { + int screenCount = ScreenCount(dpy); + int defaultScreen = DefaultScreen(dpy); + if (screenCount != 0) { + record_value("SCREEN_INFO\n"); + for (int idx = 0; idx < screenCount; idx++) { + Screen* scrn = ScreenOfDisplay(dpy, idx); + int current_height = scrn->height; + int current_width = scrn->width; + + record_value("%dx%d:%d%s", current_width, current_height, + idx == defaultScreen ? 1 : 0, + idx == screenCount - 1 ? ";\n" : ";"); + } + } +} + +static void get_glx_status(int* gotGlxInfo, int* gotDriDriver) { + void* libgl = dlopen(LIBGL_FILENAME, RTLD_LAZY); + if (!libgl) { + record_error(LIBGL_FILENAME " missing"); + return; + } + + typedef void* (*PFNGLXGETPROCADDRESS)(const char*); + PFNGLXGETPROCADDRESS glXGetProcAddress = + cast<PFNGLXGETPROCADDRESS>(dlsym(libgl, "glXGetProcAddress")); + + if (!glXGetProcAddress) { + record_error("no glXGetProcAddress"); + return; + } + + typedef GLXFBConfig* (*PFNGLXQUERYEXTENSION)(Display*, int*, int*); + PFNGLXQUERYEXTENSION glXQueryExtension = + cast<PFNGLXQUERYEXTENSION>(glXGetProcAddress("glXQueryExtension")); + + typedef GLXFBConfig* (*PFNGLXQUERYVERSION)(Display*, int*, int*); + PFNGLXQUERYVERSION glXQueryVersion = + cast<PFNGLXQUERYVERSION>(dlsym(libgl, "glXQueryVersion")); + + typedef XVisualInfo* (*PFNGLXCHOOSEVISUAL)(Display*, int, int*); + PFNGLXCHOOSEVISUAL glXChooseVisual = + cast<PFNGLXCHOOSEVISUAL>(glXGetProcAddress("glXChooseVisual")); + + typedef GLXContext (*PFNGLXCREATECONTEXT)(Display*, XVisualInfo*, GLXContext, + Bool); + PFNGLXCREATECONTEXT glXCreateContext = + cast<PFNGLXCREATECONTEXT>(glXGetProcAddress("glXCreateContext")); + + typedef Bool (*PFNGLXMAKECURRENT)(Display*, GLXDrawable, GLXContext); + PFNGLXMAKECURRENT glXMakeCurrent = + cast<PFNGLXMAKECURRENT>(glXGetProcAddress("glXMakeCurrent")); + + typedef void (*PFNGLXDESTROYCONTEXT)(Display*, GLXContext); + PFNGLXDESTROYCONTEXT glXDestroyContext = + cast<PFNGLXDESTROYCONTEXT>(glXGetProcAddress("glXDestroyContext")); + + typedef GLubyte* (*PFNGLGETSTRING)(GLenum); + PFNGLGETSTRING glGetString = + cast<PFNGLGETSTRING>(glXGetProcAddress("glGetString")); + + if (!glXQueryExtension || !glXQueryVersion || !glXChooseVisual || + !glXCreateContext || !glXMakeCurrent || !glXDestroyContext || + !glGetString) { + record_error(LIBGL_FILENAME " missing methods"); + return; + } + + ///// Open a connection to the X server ///// + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + record_error("Unable to open a connection to the X server"); + return; + } + + ///// Check that the GLX extension is present ///// + if (!glXQueryExtension(dpy, nullptr, nullptr)) { + record_error("GLX extension missing"); + return; + } + + XSetErrorHandler(x_error_handler); + + ///// Get a visual ///// + int attribs[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE, + 1, GLX_BLUE_SIZE, 1, None}; + XVisualInfo* vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs); + if (!vInfo) { + record_error("No visuals found"); + return; + } + + // using a X11 Window instead of a GLXPixmap does not crash + // fglrx in indirect rendering. bug 680644 + Window window; + XSetWindowAttributes swa; + swa.colormap = XCreateColormap(dpy, RootWindow(dpy, vInfo->screen), + vInfo->visual, AllocNone); + + swa.border_pixel = 0; + window = XCreateWindow(dpy, RootWindow(dpy, vInfo->screen), 0, 0, 16, 16, 0, + vInfo->depth, InputOutput, vInfo->visual, + CWBorderPixel | CWColormap, &swa); + + ///// Get a GL context and make it current ////// + GLXContext context = glXCreateContext(dpy, vInfo, nullptr, True); + glXMakeCurrent(dpy, window, context); + + ///// Look for this symbol to determine texture_from_pixmap support ///// + void* glXBindTexImageEXT = glXGetProcAddress("glXBindTexImageEXT"); + + ///// Get GL vendor/renderer/versions strings ///// + const GLubyte* versionString = glGetString(GL_VERSION); + const GLubyte* vendorString = glGetString(GL_VENDOR); + const GLubyte* rendererString = glGetString(GL_RENDERER); + + if (versionString && vendorString && rendererString) { + record_value("VENDOR\n%s\nRENDERER\n%s\nVERSION\n%s\nTFP\n%s\n", + vendorString, rendererString, versionString, + glXBindTexImageEXT ? "TRUE" : "FALSE"); + *gotGlxInfo = 1; + } else { + record_error("glGetString returned null"); + } + + // If GLX_MESA_query_renderer is available, populate additional data. + typedef Bool (*PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC)( + int attribute, unsigned int* value); + PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC + glXQueryCurrentRendererIntegerMESAProc = + cast<PFNGLXQUERYCURRENTRENDERERINTEGERMESAPROC>( + glXGetProcAddress("glXQueryCurrentRendererIntegerMESA")); + if (glXQueryCurrentRendererIntegerMESAProc) { + unsigned int vendorId, deviceId, accelerated, videoMemoryMB; + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VENDOR_ID_MESA, + &vendorId); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_DEVICE_ID_MESA, + &deviceId); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_ACCELERATED_MESA, + &accelerated); + glXQueryCurrentRendererIntegerMESAProc(GLX_RENDERER_VIDEO_MEMORY_MESA, + &videoMemoryMB); + + // Truncate IDs to 4 digits- that's all PCI IDs are. + vendorId &= 0xFFFF; + deviceId &= 0xFFFF; + + record_value( + "MESA_VENDOR_ID\n0x%04x\n" + "MESA_DEVICE_ID\n0x%04x\n" + "MESA_ACCELERATED\n%s\n" + "MESA_VRAM\n%dMB\n", + vendorId, deviceId, accelerated ? "TRUE" : "FALSE", videoMemoryMB); + } + + // From Mesa's GL/internal/dri_interface.h, to be used by DRI clients. + typedef const char* (*PFNGLXGETSCREENDRIVERPROC)(Display * dpy, int scrNum); + PFNGLXGETSCREENDRIVERPROC glXGetScreenDriverProc = + cast<PFNGLXGETSCREENDRIVERPROC>(glXGetProcAddress("glXGetScreenDriver")); + if (glXGetScreenDriverProc) { + const char* driDriver = glXGetScreenDriverProc(dpy, DefaultScreen(dpy)); + if (driDriver) { + *gotDriDriver = 1; + record_value("DRI_DRIVER\n%s\n", driDriver); + } + } + + // Get monitor information + get_x11_screen_info(dpy); + + ///// Clean up. Indeed, the parent process might fail to kill us (e.g. if it + ///// doesn't need to check GL info) so we might be staying alive for longer + ///// than expected, so it's important to consume as little memory as + ///// possible. Also we want to check that we're able to do that too without + ///// generating X errors. + glXMakeCurrent(dpy, None, + nullptr); // must release the GL context before destroying it + glXDestroyContext(dpy, context); + XDestroyWindow(dpy, window); + XFreeColormap(dpy, swa.colormap); + +# ifdef NS_FREE_PERMANENT_DATA // conditionally defined in nscore.h, don't + // forget to #include it above + XCloseDisplay(dpy); +# else + // This XSync call wanted to be instead: + // XCloseDisplay(dpy); + // but this can cause 1-minute stalls on certain setups using Nouveau, see bug + // 973192 + XSync(dpy, False); +# endif + + dlclose(libgl); +} + +static bool x11_egltest() { + get_egl_status(nullptr, true); + + Display* dpy = XOpenDisplay(nullptr); + if (!dpy) { + return false; + } + XSetErrorHandler(x_error_handler); + + get_x11_screen_info(dpy); + + XCloseDisplay(dpy); + return true; +} + +static void glxtest() { + int gotGlxInfo = 0; + int gotDriDriver = 0; + + get_glx_status(&gotGlxInfo, &gotDriDriver); + if (!gotGlxInfo) { + get_egl_status(nullptr, true); + } else if (!gotDriDriver) { + // If we failed to get the driver name from X, try via + // EGL_MESA_query_driver. We are probably using Wayland. + get_egl_status(nullptr, false); + } +} +#endif + +#ifdef MOZ_WAYLAND +typedef void (*print_info_t)(void* info); +typedef void (*destroy_info_t)(void* info); + +struct global_info { + struct wl_list link; + + uint32_t id; + uint32_t version; + char* interface; + + print_info_t print; + destroy_info_t destroy; +}; + +struct output_info { + struct global_info global; + struct wl_list global_link; + + struct wl_output* output; + + int32_t version; + + int32_t scale; +}; + +struct xdg_output_v1_info { + struct wl_list link; + + struct zxdg_output_v1* xdg_output; + struct output_info* output; + + struct { + int32_t width, height; + } logical; + + char *name, *description; +}; + +struct xdg_output_manager_v1_info { + struct global_info global; + struct zxdg_output_manager_v1* manager; + struct weston_info* info; + + struct wl_list outputs; +}; + +struct weston_info { + struct wl_display* display; + struct wl_registry* registry; + + struct wl_list infos; + bool roundtrip_needed; + + struct wl_list outputs; + struct xdg_output_manager_v1_info* xdg_output_manager_v1_info; +}; + +static void init_global_info(struct weston_info* info, + struct global_info* global, uint32_t id, + const char* interface, uint32_t version) { + global->id = id; + global->version = version; + global->interface = strdup(interface); + + wl_list_insert(info->infos.prev, &global->link); +} + +static void print_output_info(void* data) {} + +static void destroy_xdg_output_v1_info(struct xdg_output_v1_info* info) { + wl_list_remove(&info->link); + zxdg_output_v1_destroy(info->xdg_output); + free(info->name); + free(info->description); + free(info); +} + +static int cmpOutputIds(const void* a, const void* b) { + return (((struct xdg_output_v1_info*)a)->output->global.id - + ((struct xdg_output_v1_info*)b)->output->global.id); +} + +static void print_xdg_output_manager_v1_info(void* data) { + struct xdg_output_manager_v1_info* info = + (struct xdg_output_manager_v1_info*)data; + struct xdg_output_v1_info* output; + + int screen_count = wl_list_length(&info->outputs); + if (screen_count > 0) { + struct xdg_output_v1_info* infos = (struct xdg_output_v1_info*)malloc( + screen_count * sizeof(xdg_output_v1_info)); + + int pos = 0; + wl_list_for_each(output, &info->outputs, link) { + infos[pos] = *output; + pos++; + } + + if (screen_count > 1) { + qsort(infos, screen_count, sizeof(struct xdg_output_v1_info), + cmpOutputIds); + } + + record_value("SCREEN_INFO\n"); + for (int i = 0; i < screen_count; i++) { + record_value("%dx%d:0;", infos[i].logical.width, infos[i].logical.height); + } + record_value("\n"); + + free(infos); + } +} + +static void destroy_xdg_output_manager_v1_info(void* data) { + struct xdg_output_manager_v1_info* info = + (struct xdg_output_manager_v1_info*)data; + struct xdg_output_v1_info *output, *tmp; + + zxdg_output_manager_v1_destroy(info->manager); + + wl_list_for_each_safe(output, tmp, &info->outputs, link) + destroy_xdg_output_v1_info(output); +} + +static void handle_xdg_output_v1_logical_position(void* data, + struct zxdg_output_v1* output, + int32_t x, int32_t y) {} + +static void handle_xdg_output_v1_logical_size(void* data, + struct zxdg_output_v1* output, + int32_t width, int32_t height) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->logical.width = width; + xdg_output->logical.height = height; +} + +static void handle_xdg_output_v1_done(void* data, + struct zxdg_output_v1* output) {} + +static void handle_xdg_output_v1_name(void* data, struct zxdg_output_v1* output, + const char* name) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->name = strdup(name); +} + +static void handle_xdg_output_v1_description(void* data, + struct zxdg_output_v1* output, + const char* description) { + struct xdg_output_v1_info* xdg_output = (struct xdg_output_v1_info*)data; + xdg_output->description = strdup(description); +} + +static const struct zxdg_output_v1_listener xdg_output_v1_listener = { + .logical_position = handle_xdg_output_v1_logical_position, + .logical_size = handle_xdg_output_v1_logical_size, + .done = handle_xdg_output_v1_done, + .name = handle_xdg_output_v1_name, + .description = handle_xdg_output_v1_description, +}; + +static void add_xdg_output_v1_info( + struct xdg_output_manager_v1_info* manager_info, + struct output_info* output) { + struct xdg_output_v1_info* xdg_output = + (struct xdg_output_v1_info*)malloc(sizeof *xdg_output); + + wl_list_insert(&manager_info->outputs, &xdg_output->link); + xdg_output->xdg_output = zxdg_output_manager_v1_get_xdg_output( + manager_info->manager, output->output); + zxdg_output_v1_add_listener(xdg_output->xdg_output, &xdg_output_v1_listener, + xdg_output); + + xdg_output->output = output; + + manager_info->info->roundtrip_needed = true; +} + +static void add_xdg_output_manager_v1_info(struct weston_info* info, + uint32_t id, uint32_t version) { + struct output_info* output; + struct xdg_output_manager_v1_info* manager = + (struct xdg_output_manager_v1_info*)malloc(sizeof *manager); + + wl_list_init(&manager->outputs); + manager->info = info; + + init_global_info(info, &manager->global, id, + zxdg_output_manager_v1_interface.name, version); + manager->global.print = print_xdg_output_manager_v1_info; + manager->global.destroy = destroy_xdg_output_manager_v1_info; + + manager->manager = (struct zxdg_output_manager_v1*)wl_registry_bind( + info->registry, id, &zxdg_output_manager_v1_interface, + version > 2 ? 2 : version); + + wl_list_for_each(output, &info->outputs, global_link) + add_xdg_output_v1_info(manager, output); + + info->xdg_output_manager_v1_info = manager; +} + +static void output_handle_geometry(void* data, struct wl_output* wl_output, + int32_t x, int32_t y, int32_t physical_width, + int32_t physical_height, int32_t subpixel, + const char* make, const char* model, + int32_t output_transform) {} + +static void output_handle_mode(void* data, struct wl_output* wl_output, + uint32_t flags, int32_t width, int32_t height, + int32_t refresh) {} + +static void output_handle_done(void* data, struct wl_output* wl_output) {} + +static void output_handle_scale(void* data, struct wl_output* wl_output, + int32_t scale) { + struct output_info* output = (struct output_info*)data; + + output->scale = scale; +} + +static const struct wl_output_listener output_listener = { + output_handle_geometry, + output_handle_mode, + output_handle_done, + output_handle_scale, +}; + +static void destroy_output_info(void* data) { + struct output_info* output = (struct output_info*)data; + + wl_output_destroy(output->output); +} + +static void add_output_info(struct weston_info* info, uint32_t id, + uint32_t version) { + struct output_info* output = (struct output_info*)malloc(sizeof *output); + + init_global_info(info, &output->global, id, "wl_output", version); + output->global.print = print_output_info; + output->global.destroy = destroy_output_info; + + output->version = MIN(version, 2); + output->scale = 1; + + output->output = (struct wl_output*)wl_registry_bind( + info->registry, id, &wl_output_interface, output->version); + wl_output_add_listener(output->output, &output_listener, output); + + info->roundtrip_needed = true; + wl_list_insert(&info->outputs, &output->global_link); + + if (info->xdg_output_manager_v1_info) { + add_xdg_output_v1_info(info->xdg_output_manager_v1_info, output); + } +} + +static void global_handler(void* data, struct wl_registry* registry, + uint32_t id, const char* interface, + uint32_t version) { + struct weston_info* info = (struct weston_info*)data; + + if (!strcmp(interface, "wl_output")) { + add_output_info(info, id, version); + } else if (!strcmp(interface, zxdg_output_manager_v1_interface.name)) { + add_xdg_output_manager_v1_info(info, id, version); + } +} + +static void global_remove_handler(void* data, struct wl_registry* registry, + uint32_t name) {} + +static const struct wl_registry_listener registry_listener = { + global_handler, global_remove_handler}; + +static void print_infos(struct wl_list* infos) { + struct global_info* info; + + wl_list_for_each(info, infos, link) { info->print(info); } +} + +static void destroy_info(void* data) { + struct global_info* global = (struct global_info*)data; + + global->destroy(data); + wl_list_remove(&global->link); + free(global->interface); + free(data); +} + +static void destroy_infos(struct wl_list* infos) { + struct global_info *info, *tmp; + wl_list_for_each_safe(info, tmp, infos, link) { destroy_info(info); } +} + +static void get_wayland_screen_info(struct wl_display* dpy) { + struct weston_info info = {0}; + info.display = dpy; + + info.xdg_output_manager_v1_info = NULL; + wl_list_init(&info.infos); + wl_list_init(&info.outputs); + + info.registry = wl_display_get_registry(info.display); + wl_registry_add_listener(info.registry, ®istry_listener, &info); + + do { + info.roundtrip_needed = false; + wl_display_roundtrip(info.display); + } while (info.roundtrip_needed); + + print_infos(&info.infos); + destroy_infos(&info.infos); + + wl_registry_destroy(info.registry); +} + +static bool wayland_egltest() { + // NOTE: returns false to fall back to X11 when the Wayland socket doesn't + // exist but fails with record_error if something actually went wrong + struct wl_display* dpy = wl_display_connect(nullptr); + if (!dpy) { + return false; + } + + get_egl_status((EGLNativeDisplayType)dpy, true); + get_wayland_screen_info(dpy); + + wl_display_disconnect(dpy); + return true; +} +#endif + +int childgltest() { + enum { bufsize = 2048 }; + char buf[bufsize]; + + // We save it as a global so that the X error handler can flush the buffer + // before early exiting. + glxtest_buf = buf; + glxtest_bufsize = bufsize; + + // Get a list of all GPUs from the PCI bus. + get_pci_status(); + + bool result = false; +#ifdef MOZ_WAYLAND + if (!IsWaylandDisabled()) { + result = wayland_egltest(); + } +#endif +#ifdef MOZ_X11 + // TODO: --display command line argument is not properly handled + if (!result && IsX11EGLEnabled()) { + result = x11_egltest(); + } + if (!result) { + glxtest(); + } +#endif + + // Finally write buffered data to the pipe. + record_flush(); + + // If we completely filled the buffer, we need to tell the parent. + if (glxtest_length >= glxtest_bufsize) { + return EXIT_FAILURE_BUFFER_TOO_SMALL; + } + + return EXIT_SUCCESS; +} + +} // extern "C" + +/** \returns true in the child glxtest process, false in the parent process */ +bool fire_glxtest_process() { + int pfd[2]; + if (pipe(pfd) == -1) { + perror("pipe"); + return false; + } + pid_t pid = fork(); + if (pid < 0) { + perror("fork"); + close(pfd[0]); + close(pfd[1]); + return false; + } + // The child exits early to avoid running the full shutdown sequence and avoid + // conflicting with threads we have already spawned (like the profiler). + if (pid == 0) { + close(pfd[0]); + write_end_of_the_pipe = pfd[1]; + close_logging(); + int rv = childgltest(); + close(pfd[1]); + _exit(rv); + } + + close(pfd[1]); + mozilla::widget::glxtest_pipe = pfd[0]; + mozilla::widget::glxtest_pid = pid; + return false; +} diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build new file mode 100644 index 0000000000..edaabb958d --- /dev/null +++ b/toolkit/xre/moz.build @@ -0,0 +1,287 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +include("../components/telemetry/telemetry-constants.mozbuild") + +with Files("**"): + BUG_COMPONENT = ("Toolkit", "Startup and Profile System") + +if CONFIG["OS_ARCH"] == "WINNT": + TEST_DIRS += ["test/win"] + +MOCHITEST_MANIFESTS += ["test/mochitest.ini"] +BROWSER_CHROME_MANIFESTS += ["test/browser.ini"] +XPCSHELL_TESTS_MANIFESTS += ["test/xpcshell.ini"] +MARIONETTE_UNIT_MANIFESTS += ["test/marionette/marionette.ini"] + +XPIDL_SOURCES += [ + "nsINativeAppSupport.idl", + "nsIXREDirProvider.idl", +] + +if CONFIG["OS_ARCH"] == "WINNT": + XPIDL_SOURCES += [ + "nsIWinAppHelper.idl", + ] + +XPIDL_MODULE = "xulapp" + +EXPORTS += [ + "nsAppRunner.h", + "nsIAppStartupNotifier.h", +] + +EXPORTS.mozilla += [ + "AutoSQLiteLifetime.h", + "Bootstrap.h", + "CmdLineAndEnvUtils.h", + "MultiInstanceLock.h", + "SafeMode.h", + "UntrustedModulesData.h", +] + +if CONFIG["MOZ_INSTRUMENT_EVENT_LOOP"]: + EXPORTS += ["EventTracer.h"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + EXPORTS.mozilla += [ + "AssembleCmdLine.h", + "DllPrefetchExperimentRegistryInfo.h", + "ModuleVersionInfo.h", + "PolicyChecks.h", + "UntrustedModulesProcessor.h", + "WinDllServices.h", + "WinTokenUtils.h", + ] + UNIFIED_SOURCES += [ + "/toolkit/mozapps/update/common/updateutils_win.cpp", + "DllPrefetchExperimentRegistryInfo.cpp", + "ModuleEvaluator.cpp", + "ModuleVersionInfo.cpp", + "nsNativeAppSupportWin.cpp", + "UntrustedModulesData.cpp", + "UntrustedModulesProcessor.cpp", + "WinDllServices.cpp", + "WinTokenUtils.cpp", + ] + DEFINES["PROXY_PRINTING"] = 1 + LOCAL_INCLUDES += [ + "../components/printingui", + ] + if CONFIG["MOZ_LAUNCHER_PROCESS"]: + EXPORTS.mozilla += [ + "LauncherRegistryInfo.h", + ] + UNIFIED_SOURCES += [ + "LauncherRegistryInfo.cpp", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + UNIFIED_SOURCES += [ + "MacApplicationDelegate.mm", + "MacAutoreleasePool.mm", + "MacLaunchHelper.mm", + "nsCommandLineServiceMac.mm", + "nsNativeAppSupportCocoa.mm", + "updaterfileutils_osx.mm", + ] + DEFINES["PROXY_PRINTING"] = 1 + LOCAL_INCLUDES += [ + "../components/printingui", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "uikit": + UNIFIED_SOURCES += [ + "nsNativeAppSupportDefault.cpp", + "UIKitDirProvider.mm", + ] +elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + UNIFIED_SOURCES += [ + "nsNativeAppSupportUnix.cpp", + ] +else: + UNIFIED_SOURCES += [ + "nsNativeAppSupportDefault.cpp", + ] + +if CONFIG["MOZ_HAS_REMOTE"]: + LOCAL_INCLUDES += [ + "../components/remote", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + UNIFIED_SOURCES += [ + "nsGDKErrorHandler.cpp", + ] + +if CONFIG["MOZ_X11"]: + EXPORTS += ["nsX11ErrorHandler.h"] + UNIFIED_SOURCES += [ + "nsX11ErrorHandler.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + UNIFIED_SOURCES += [ + "nsAndroidStartup.cpp", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + UNIFIED_SOURCES += [ + "MultiInstanceLock.cpp", + ] + +UNIFIED_SOURCES += [ + "/toolkit/mozapps/update/common/commonupdatedir.cpp", + "AutoSQLiteLifetime.cpp", + "Bootstrap.cpp", + "CmdLineAndEnvUtils.cpp", + "CreateAppData.cpp", + "nsAppStartupNotifier.cpp", + "nsConsoleWriter.cpp", + "nsEmbeddingModule.cpp", + "nsNativeAppSupportBase.cpp", + "nsSigHandlers.cpp", + "nsXREDirProvider.cpp", +] + +# nsAppRunner.cpp and ProfileReset.cpp cannot be built in unified mode because +# they pull in OS X system headers. +# nsEmbedFunctions.cpp cannot be built in unified mode because it pulls in X11 headers. +SOURCES += [ + "../../other-licenses/nsis/Contrib/CityHash/cityhash/city.cpp", + "nsAppRunner.cpp", + "nsEmbedFunctions.cpp", + "ProfileReset.cpp", +] + +if CONFIG["MOZ_X11"]: + UNIFIED_SOURCES += [ + "glxtest.cpp", + ] + +if CONFIG["MOZ_INSTRUMENT_EVENT_LOOP"]: + UNIFIED_SOURCES += [ + "EventTracer.cpp", + ] + +if CONFIG["MOZ_UPDATER"]: + if CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + UNIFIED_SOURCES += [ + "nsUpdateDriver.cpp", + "nsUpdateSyncManager.cpp", + ] + +if CONFIG["MOZ_PDF_PRINTING"]: + DEFINES["PROXY_PRINTING"] = 1 + LOCAL_INCLUDES += [ + "../components/printingui", + ] + +XPCOM_MANIFESTS += [ + "components.conf", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +FINAL_LIBRARY = "xul" + +if CONFIG["MOZ_X11"]: + DEFINES["USE_GLX_TEST"] = True + +for var in ( + "MOZ_APP_NAME", + "MOZ_APP_BASENAME", + "MOZ_APP_DISPLAYNAME", + "MOZ_APP_VENDOR", + "MOZ_APP_VERSION", + "OS_TARGET", + "MOZ_WIDGET_TOOLKIT", +): + DEFINES[var] = '"%s"' % CONFIG[var] + +if CONFIG["MOZ_DEFAULT_BROWSER_AGENT"] and CONFIG["OS_ARCH"] == "WINNT": + DEFINES["MOZ_DEFAULT_BROWSER_AGENT"] = CONFIG["MOZ_DEFAULT_BROWSER_AGENT"] + +if CONFIG["MOZ_UPDATER"] and CONFIG["MOZ_WIDGET_TOOLKIT"] != "android": + DEFINES["MOZ_UPDATER"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows": + DEFINES["WIN32_LEAN_AND_MEAN"] = True + DEFINES["UNICODE"] = True + DEFINES["_UNICODE"] = True + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "android": + DEFINES["ANDROID_PACKAGE_NAME"] = '"%s"' % CONFIG["ANDROID_PACKAGE_NAME"] + +if CONFIG["TARGET_XPCOM_ABI"]: + DEFINES["TARGET_OS_ABI"] = '"%s_%s"' % ( + CONFIG["OS_TARGET"], + CONFIG["TARGET_XPCOM_ABI"], + ) + +if CONFIG["OS_ARCH"] == "Linux" and "lib64" in CONFIG["libdir"]: + DEFINES["HAVE_USR_LIB64_DIR"] = True + +DEFINES["GRE_MILESTONE"] = CONFIG["GRE_MILESTONE"] +DEFINES["MOZ_APP_VERSION_DISPLAY"] = CONFIG["MOZ_APP_VERSION_DISPLAY"] + +for var in ("APP_VERSION", "APP_ID"): + DEFINES[var] = CONFIG["MOZ_%s" % var] + +if CONFIG["MOZ_BUILD_APP"] == "browser": + DEFINES["MOZ_BUILD_APP_IS_BROWSER"] = True + +LOCAL_INCLUDES += [ + "../../other-licenses/nsis/Contrib/CityHash/cityhash", + "../components/find", + "../components/printingui/ipc", + "../components/windowwatcher", + "../mozapps/update/common", + "../profile", + "/config", + "/dom/base", + "/dom/commandhandler", + "/dom/ipc", + "/dom/webbrowserpersist", + "/testing/gtest/mozilla", + "/toolkit/crashreporter", + "/xpcom/build", +] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "WINNT": + LOCAL_INCLUDES += [ + "/security/sandbox/chromium", + "/security/sandbox/chromium-shim", + ] + +if CONFIG["MOZ_SANDBOX"] and CONFIG["OS_ARCH"] == "Linux": + USE_LIBS += [ + "mozsandbox", + ] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa": + LOCAL_INCLUDES += [ + "/widget", + "/widget/cocoa", + "/xpcom/base", + ] + +CXXFLAGS += CONFIG["TK_CFLAGS"] +CXXFLAGS += CONFIG["MOZ_DBUS_CFLAGS"] +CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"] + +if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk": + CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"] + +DEFINES["TOPOBJDIR"] = TOPOBJDIR +FINAL_TARGET_PP_FILES += ["platform.ini"] + +if CONFIG["CC_TYPE"] in ("clang", "gcc"): + CXXFLAGS += ["-Wno-error=shadow"] + +if CONFIG["MOZ_IPDL_TESTS"]: + DEFINES["MOZ_IPDL_TESTS"] = True + +if CONFIG["ENABLE_TESTS"]: + DIRS += ["test/gtest"] diff --git a/toolkit/xre/nsAndroidStartup.cpp b/toolkit/xre/nsAndroidStartup.cpp new file mode 100644 index 0000000000..8df017edc6 --- /dev/null +++ b/toolkit/xre/nsAndroidStartup.cpp @@ -0,0 +1,41 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * 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/. */ + +#include <android/log.h> + +#include <jni.h> + +#include <stdlib.h> +#include <string.h> +#include <pthread.h> + +#include "mozilla/jni/Utils.h" +#include "nsTArray.h" +#include "nsString.h" +#include "nsAppRunner.h" +#include "nsExceptionHandler.h" +#include "mozilla/Bootstrap.h" + +#define LOG(args...) __android_log_print(ANDROID_LOG_INFO, MOZ_APP_NAME, args) + +using namespace mozilla; + +extern "C" NS_EXPORT void GeckoStart(JNIEnv* env, char** argv, int argc, + const StaticXREAppData& aAppData) { + mozilla::jni::SetGeckoThreadEnv(env); + + if (!argv) { + LOG("Failed to get arguments for GeckoStart\n"); + return; + } + + BootstrapConfig config; + config.appData = &aAppData; + config.appDataPath = nullptr; + + int result = XRE_main(argc, argv, config); + + if (result) LOG("XRE_main returned %d", result); +} diff --git a/toolkit/xre/nsAppRunner.cpp b/toolkit/xre/nsAppRunner.cpp new file mode 100644 index 0000000000..9c7cb46079 --- /dev/null +++ b/toolkit/xre/nsAppRunner.cpp @@ -0,0 +1,5653 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/dom/ContentParent.h" +#include "mozilla/dom/ContentChild.h" +#include "mozilla/ipc/GeckoChildProcessHost.h" + +#include "mozilla/AppShutdown.h" +#include "mozilla/ArrayUtils.h" +#include "mozilla/Assertions.h" +#include "mozilla/Attributes.h" +#include "mozilla/Components.h" +#include "mozilla/FilePreferences.h" +#include "mozilla/ChaosMode.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/Likely.h" +#include "mozilla/MemoryChecking.h" +#include "mozilla/Poison.h" +#include "mozilla/Preferences.h" +#include "mozilla/Printf.h" +#include "mozilla/ResultExtensions.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Services.h" +#include "mozilla/StaticPrefs_fission.h" +#include "mozilla/Telemetry.h" +#include "mozilla/Utf8.h" +#include "mozilla/intl/LocaleService.h" +#include "mozilla/JSONWriter.h" +#include "BaseProfiler.h" + +#include "nsAppRunner.h" +#include "mozilla/XREAppData.h" +#include "mozilla/Bootstrap.h" +#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) +# include "nsUpdateDriver.h" +#endif +#include "ProfileReset.h" + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP +# include "EventTracer.h" +#endif + +#ifdef XP_MACOSX +# include "nsVersionComparator.h" +# include "MacLaunchHelper.h" +# include "MacApplicationDelegate.h" +# include "MacAutoreleasePool.h" +// these are needed for sysctl +# include <sys/types.h> +# include <sys/sysctl.h> +#endif + +#include "prnetdb.h" +#include "prprf.h" +#include "prproces.h" +#include "prenv.h" +#include "prtime.h" + +#include "nsIAppStartup.h" +#include "nsAppStartupNotifier.h" +#include "nsIMutableArray.h" +#include "nsCommandLine.h" +#include "nsIComponentRegistrar.h" +#include "nsIDialogParamBlock.h" +#include "mozilla/ModuleUtils.h" +#include "nsIIOService.h" +#include "nsIObserverService.h" +#include "nsINativeAppSupport.h" +#include "nsIPlatformInfo.h" +#include "nsIProcess.h" +#include "nsIProfileUnlocker.h" +#include "nsIPromptService.h" +#include "nsIPropertyBag2.h" +#include "nsIServiceManager.h" +#include "nsIStringBundle.h" +#include "nsISupportsPrimitives.h" +#include "nsIToolkitProfile.h" +#include "nsIUUIDGenerator.h" +#include "nsToolkitProfileService.h" +#include "nsIURI.h" +#include "nsIURL.h" +#include "nsIWindowCreator.h" +#include "nsIWindowWatcher.h" +#include "nsIXULAppInfo.h" +#include "nsIXULRuntime.h" +#include "nsPIDOMWindow.h" +#include "nsIWidget.h" +#include "nsAppShellCID.h" +#include "mozilla/dom/quota/QuotaManager.h" +#include "mozilla/scache/StartupCache.h" +#include "gfxPlatform.h" + +#include "mozilla/Unused.h" + +#ifdef XP_WIN +# include "nsIWinAppHelper.h" +# include <windows.h> +# include <intrin.h> +# include <math.h> +# include "cairo/cairo-features.h" +# include "mozilla/PreXULSkeletonUI.h" +# include "mozilla/DllPrefetchExperimentRegistryInfo.h" +# include "mozilla/WindowsDllBlocklist.h" +# include "mozilla/WindowsProcessMitigations.h" +# include "mozilla/WinHeaderOnlyUtils.h" +# include "mozilla/mscom/ProcessRuntime.h" +# if defined(MOZ_GECKO_PROFILER) +# include "mozilla/mscom/ProfilerMarkers.h" +# endif // defined(MOZ_GECKO_PROFILER) +# include "mozilla/widget/AudioSession.h" +# include "WinTokenUtils.h" + +# if defined(MOZ_LAUNCHER_PROCESS) +# include "mozilla/LauncherRegistryInfo.h" +# endif + +# if defined(MOZ_DEFAULT_BROWSER_AGENT) +# include "nsIWindowsRegKey.h" +# endif + +# ifndef PROCESS_DEP_ENABLE +# define PROCESS_DEP_ENABLE 0x1 +# endif +#endif + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# if (defined(XP_WIN) || defined(XP_MACOSX)) +# include "nsIUUIDGenerator.h" +# endif +#endif + +#ifdef ACCESSIBILITY +# include "nsAccessibilityService.h" +# if defined(XP_WIN) +# include "mozilla/a11y/Compatibility.h" +# include "mozilla/a11y/Platform.h" +# endif +#endif + +#include "nsCRT.h" +#include "nsCOMPtr.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsEmbedCID.h" +#include "nsNetUtil.h" +#include "nsReadableUtils.h" +#include "nsXPCOM.h" +#include "nsXPCOMCIDInternal.h" +#include "nsString.h" +#include "nsPrintfCString.h" +#include "nsVersionComparator.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsXULAppAPI.h" +#include "nsXREDirProvider.h" + +#include "nsINIParser.h" +#include "mozilla/Omnijar.h" +#include "mozilla/StartupTimeline.h" +#include "mozilla/LateWriteChecks.h" + +#include <stdlib.h> +#include <locale.h> + +#ifdef XP_UNIX +# include <errno.h> +# include <pwd.h> +# include <string.h> +# include <sys/resource.h> +# include <sys/stat.h> +# include <unistd.h> +#endif + +#ifdef XP_WIN +# include <process.h> +# include <shlobj.h> +# include "mozilla/WinDllServices.h" +# include "nsThreadUtils.h" +# include <comdef.h> +# include <wbemidl.h> +# include "WinUtils.h" +#endif + +#ifdef XP_MACOSX +# include "nsILocalFileMac.h" +# include "nsCommandLineServiceMac.h" +#endif + +// for X remote support +#if defined(MOZ_HAS_REMOTE) +# include "nsRemoteService.h" +#endif + +#if defined(DEBUG) && defined(XP_WIN) +# include <malloc.h> +#endif + +#if defined(XP_MACOSX) +# include <Carbon/Carbon.h> +#endif + +#ifdef DEBUG +# include "mozilla/Logging.h" +#endif + +#ifdef MOZ_JPROF +# include "jprof.h" +#endif + +#include "nsExceptionHandler.h" +#include "nsICrashReporter.h" +#define NS_CRASHREPORTER_CONTRACTID "@mozilla.org/toolkit/crash-reporter;1" +#include "nsIPrefService.h" +#include "nsIMemoryInfoDumper.h" +#if defined(XP_LINUX) && !defined(ANDROID) +# include "mozilla/widget/LSBUtils.h" +#endif + +#include "base/command_line.h" +#include "GTestRunner.h" + +#ifdef MOZ_WIDGET_ANDROID +# include "mozilla/java/GeckoAppShellWrappers.h" +#endif + +#if defined(MOZ_SANDBOX) +# if defined(XP_LINUX) && !defined(ANDROID) +# include "mozilla/SandboxInfo.h" +# elif defined(XP_WIN) +# include "sandboxBroker.h" +# include "sandboxPermissions.h" +# endif +#endif + +#ifdef MOZ_CODE_COVERAGE +# include "mozilla/CodeCoverageHandler.h" +#endif + +#include "mozilla/mozalloc_oom.h" +#include "SafeMode.h" + +#ifdef MOZ_THUNDERBIRD +# include "nsIPK11TokenDB.h" +# include "nsIPK11Token.h" +#endif + +extern uint32_t gRestartMode; +extern void InstallSignalHandlers(const char* ProgramName); + +#define FILE_COMPATIBILITY_INFO "compatibility.ini"_ns +#define FILE_INVALIDATE_CACHES ".purgecaches"_ns +#define FILE_STARTUP_INCOMPLETE u".startup-incomplete"_ns + +#if defined(MOZ_BLOCK_PROFILE_DOWNGRADE) || defined(MOZ_LAUNCHER_PROCESS) || \ + defined(MOZ_DEFAULT_BROWSER_AGENT) +static const char kPrefHealthReportUploadEnabled[] = + "datareporting.healthreport.uploadEnabled"; +#endif // defined(MOZ_BLOCK_PROFILE_DOWNGRADE) || defined(MOZ_LAUNCHER_PROCESS) + // || defined(MOZ_DEFAULT_BROWSER_AGENT) +#if defined(MOZ_DEFAULT_BROWSER_AGENT) +static const char kPrefDefaultAgentEnabled[] = "default-browser-agent.enabled"; + +static const char kPrefServicesSettingsServer[] = "services.settings.server"; +static const char kPrefSecurityContentSignatureRootHash[] = + "security.content.signature.root_hash"; +#endif // defined(MOZ_DEFAULT_BROWSER_AGENT) + +#if defined(XP_WIN) +static const char kPrefThemeId[] = "extensions.activeThemeID"; +static const char kPrefBrowserStartupBlankWindow[] = + "browser.startup.blankWindow"; +static const char kPrefPreXulSkeletonUI[] = "browser.startup.preXulSkeletonUI"; +static const char kPrefDrawTabsInTitlebar[] = "browser.tabs.drawInTitlebar"; +#endif // defined(XP_WIN) + +int gArgc; +char** gArgv; + +static const char gToolkitVersion[] = MOZ_STRINGIFY(GRE_MILESTONE); +// The gToolkitBuildID global is defined to MOZ_BUILDID via gen_buildid.py +// in toolkit/library. See related comment in toolkit/library/moz.build. +extern const char gToolkitBuildID[]; + +static nsIProfileLock* gProfileLock; + +int gRestartArgc; +char** gRestartArgv; + +// If gRestartedByOS is set, we were automatically restarted by the OS. +bool gRestartedByOS = false; + +bool gIsGtest = false; + +nsString gAbsoluteArgv0Path; + +#if defined(MOZ_WIDGET_GTK) +# include <glib.h> +# if defined(DEBUG) || defined(NS_BUILD_REFCNT_LOGGING) +# define CLEANUP_MEMORY 1 +# define PANGO_ENABLE_BACKEND +# include <pango/pangofc-fontmap.h> +# endif +# include <gtk/gtk.h> +# ifdef MOZ_WAYLAND +# include <gdk/gdkwayland.h> +# include "mozilla/widget/nsWaylandDisplay.h" +# endif +# ifdef MOZ_X11 +# include <gdk/gdkx.h> +# endif /* MOZ_X11 */ +# include <fontconfig/fontconfig.h> +#endif +#include "BinaryPath.h" + +#ifdef MOZ_LINKER +extern "C" MFBT_API bool IsSignalHandlingBroken(); +#endif + +#ifdef FUZZING +# include "FuzzerRunner.h" + +namespace mozilla { +FuzzerRunner* fuzzerRunner = 0; +} // namespace mozilla + +# ifdef LIBFUZZER +void XRE_LibFuzzerSetDriver(LibFuzzerDriver aDriver) { + mozilla::fuzzerRunner->setParams(aDriver); +} +# endif +#endif // FUZZING + +// Undo X11/X.h's definition of None +#undef None + +namespace mozilla { +int (*RunGTest)(int*, char**) = 0; + +bool RunningGTest() { return RunGTest; } +} // namespace mozilla + +using namespace mozilla; +using namespace mozilla::widget; +using namespace mozilla::startup; +using mozilla::Unused; +using mozilla::dom::ContentChild; +using mozilla::dom::ContentParent; +using mozilla::dom::quota::QuotaManager; +using mozilla::intl::LocaleService; +using mozilla::scache::StartupCache; + +// Save the given word to the specified environment variable. +static void MOZ_NEVER_INLINE SaveWordToEnv(const char* name, + const nsACString& word) { + char* expr = + Smprintf("%s=%s", name, PromiseFlatCString(word).get()).release(); + if (expr) PR_SetEnv(expr); + // We intentionally leak |expr| here since it is required by PR_SetEnv. +} + +// Save the path of the given file to the specified environment variable. +static void SaveFileToEnv(const char* name, nsIFile* file) { +#ifdef XP_WIN + nsAutoString path; + file->GetPath(path); + SetEnvironmentVariableW(NS_ConvertASCIItoUTF16(name).get(), path.get()); +#else + nsAutoCString path; + file->GetNativePath(path); + SaveWordToEnv(name, path); +#endif +} + +static bool gIsExpectedExit = false; + +void MozExpectedExit() { gIsExpectedExit = true; } + +/** + * Runs atexit() to catch unexpected exit from 3rd party libraries like the + * Intel graphics driver calling exit in an error condition. When they + * call exit() to report an error we won't shutdown correctly and wont catch + * the issue with our crash reporter. + */ +static void UnexpectedExit() { + if (!gIsExpectedExit) { + gIsExpectedExit = true; // Don't risk re-entrency issues when crashing. + MOZ_CRASH("Exit called by third party code."); + } +} + +/** + * Output a string to the user. This method is really only meant to be used to + * output last-ditch error messages designed for developers NOT END USERS. + * + * @param isError + * Pass true to indicate severe errors. + * @param fmt + * printf-style format string followed by arguments. + */ +static MOZ_FORMAT_PRINTF(2, 3) void Output(bool isError, const char* fmt, ...) { + va_list ap; + va_start(ap, fmt); + +#if defined(XP_WIN) && !MOZ_WINCONSOLE + SmprintfPointer msg = mozilla::Vsmprintf(fmt, ap); + if (msg) { + UINT flags = MB_OK; + if (isError) + flags |= MB_ICONERROR; + else + flags |= MB_ICONINFORMATION; + + wchar_t wide_msg[1024]; + MultiByteToWideChar(CP_ACP, 0, msg.get(), -1, wide_msg, + sizeof(wide_msg) / sizeof(wchar_t)); + + MessageBoxW(nullptr, wide_msg, L"XULRunner", flags); + } +#else + vfprintf(stderr, fmt, ap); +#endif + + va_end(ap); +} + +/** + * Check for a commandline flag. If the flag takes a parameter, the + * parameter is returned in aParam. Flags may be in the form -arg or + * --arg (or /arg on win32). + * + * @param aArg the parameter to check. Must be lowercase. + * @param aParam if non-null, the -arg <data> will be stored in this pointer. + * This is *not* allocated, but rather a pointer to the argv data. + * @param aFlags flags @see CheckArgFlag + */ +static ArgResult CheckArg(const char* aArg, const char** aParam = nullptr, + CheckArgFlag aFlags = CheckArgFlag::RemoveArg) { + MOZ_ASSERT(gArgv, "gArgv must be initialized before CheckArg()"); + return CheckArg(gArgc, gArgv, aArg, aParam, aFlags); +} + +/** + * Check for a commandline flag. Ignore data that's passed in with the flag. + * Flags may be in the form -arg or --arg (or /arg on win32). + * Will not remove flag if found. + * + * @param aArg the parameter to check. Must be lowercase. + */ +static ArgResult CheckArgExists(const char* aArg) { + return CheckArg(aArg, nullptr, CheckArgFlag::None); +} + +bool gSafeMode = false; +bool gFxREmbedded = false; + +// Fission enablement for the current session is determined once, at startup, +// and then remains the same for the duration of the session. +// +// The following factors determine whether or not Fission is enabled for a +// session, in order of precedence: +// +// - Safe mode: In safe mode, Fission is never enabled. +// +// - The MOZ_FORCE_ENABLE_FISSION environment variable: If set to any value, +// Fission will be enabled. +// +// - The 'fission.autostart' preference, if it has been configured by the user. +static const char kPrefFissionAutostart[] = "fission.autostart"; +// +// - The fission experiment enrollment status set during the previous run, which +// is controlled by the following preferences: +// +// The current enrollment status as controlled by Normandy. This value is only +// stored in the default preference branch, and is not persisted across +// sessions by the preference service. It therefore isn't available early +// enough at startup, and needs to be synced to a preference in the user +// branch which is persisted across sessions. +static const char kPrefFissionExperimentEnrollmentStatus[] = + "fission.experiment.enrollmentStatus"; +// +// The enrollment status to be used at browser startup. This automatically +// synced from the above enrollmentStatus preference whenever the latter is +// changed. It can have any of the values defined in the +// `nsIXULRuntime_ExperimentStatus` enum. Meanings are documented in +// the declaration of `nsIXULRuntime.fissionExperimentStatus` +static const char kPrefFissionExperimentStartupEnrollmentStatus[] = + "fission.experiment.startupEnrollmentStatus"; + +// The computed FissionAutostart value for the session, read by content +// processes to initialize gFissionAutostart. +// +// This pref is locked, and only configured on the default branch, so should +// never be persisted in a profile. +static const char kPrefFissionAutostartSession[] = "fission.autostart.session"; + +static nsIXULRuntime::ExperimentStatus gFissionExperimentStatus = + nsIXULRuntime::eExperimentStatusUnenrolled; +static bool gFissionAutostart = false; +static bool gFissionAutostartInitialized = false; +static nsIXULRuntime::FissionDecisionStatus gFissionDecisionStatus; + +enum E10sStatus { + kE10sEnabledByDefault, + kE10sDisabledByUser, + kE10sForceDisabled, +}; + +static bool gBrowserTabsRemoteAutostart = false; +static E10sStatus gBrowserTabsRemoteStatus; +static bool gBrowserTabsRemoteAutostartInitialized = false; + +namespace mozilla { + +bool BrowserTabsRemoteAutostart() { + if (gBrowserTabsRemoteAutostartInitialized) { + return gBrowserTabsRemoteAutostart; + } + gBrowserTabsRemoteAutostartInitialized = true; + + // If we're not in the parent process, we are running E10s. + if (!XRE_IsParentProcess()) { + gBrowserTabsRemoteAutostart = true; + return gBrowserTabsRemoteAutostart; + } + +#if defined(MOZILLA_OFFICIAL) && MOZ_BUILD_APP_IS_BROWSER + bool allowSingleProcessOutsideAutomation = false; +#else + bool allowSingleProcessOutsideAutomation = true; +#endif + + E10sStatus status = kE10sEnabledByDefault; + // We use "are non-local connections disabled" as a proxy for + // "are we running some kind of automated test". It would be nicer to use + // xpc::IsInAutomation(), but that depends on some prefs being set, which + // they are not in (at least) gtests (where we can't) and xpcshell. + // Long-term, hopefully we can make tests switch to environment variables + // to disable e10s and then we can get rid of this. + if (allowSingleProcessOutsideAutomation || + xpc::AreNonLocalConnectionsDisabled()) { + bool optInPref = + Preferences::GetBool("browser.tabs.remote.autostart", true); + + if (optInPref) { + gBrowserTabsRemoteAutostart = true; + } else { + status = kE10sDisabledByUser; + } + } else { + gBrowserTabsRemoteAutostart = true; + } + + // Uber override pref for emergency blocking + if (gBrowserTabsRemoteAutostart) { + const char* forceDisable = PR_GetEnv("MOZ_FORCE_DISABLE_E10S"); +#if defined(MOZ_WIDGET_ANDROID) + // We need this for xpcshell on Android + if (forceDisable && *forceDisable) { +#else + // The environment variable must match the application version to apply. + if (forceDisable && gAppData && !strcmp(forceDisable, gAppData->version)) { +#endif + gBrowserTabsRemoteAutostart = false; + status = kE10sForceDisabled; + } + } + + gBrowserTabsRemoteStatus = status; + + return gBrowserTabsRemoteAutostart; +} + +} // namespace mozilla + +static bool FissionExperimentEnrolled() { + MOZ_ASSERT(XRE_IsParentProcess()); + return gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusControl || + gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusTreatment; +} + +static void FissionExperimentDisqualify() { + MOZ_ASSERT(XRE_IsParentProcess()); + // Setting this pref's user value will be detected by Normandy, causing the + // client to be unenrolled from the experiment. + Preferences::SetUint(kPrefFissionExperimentEnrollmentStatus, + nsIXULRuntime::eExperimentStatusDisqualified); +} + +static void OnFissionEnrollmentStatusChanged(const char* aPref, void* aData) { + Preferences::SetUint( + kPrefFissionExperimentStartupEnrollmentStatus, + Preferences::GetUint(kPrefFissionExperimentEnrollmentStatus, + nsIXULRuntime::eExperimentStatusUnenrolled)); +} + +namespace { +// This observer is notified during `profile-before-change`, and ensures that +// the experiment enrollment status is synced over before the browser shuts +// down, even if it was not modified since FissionAutostart was initialized. +class FissionEnrollmentStatusShutdownObserver final : public nsIObserver { + public: + NS_DECL_ISUPPORTS + + NS_IMETHOD Observe(nsISupports* aSubject, const char* aTopic, + const char16_t* aData) override { + MOZ_ASSERT(!strcmp("profile-before-change", aTopic)); + OnFissionEnrollmentStatusChanged(kPrefFissionExperimentEnrollmentStatus, + nullptr); + return NS_OK; + } + + private: + ~FissionEnrollmentStatusShutdownObserver() = default; +}; +NS_IMPL_ISUPPORTS(FissionEnrollmentStatusShutdownObserver, nsIObserver) +} // namespace + +static void OnFissionAutostartChanged(const char* aPref, void* aData) { + MOZ_ASSERT(FissionExperimentEnrolled()); + if (Preferences::HasUserValue(kPrefFissionAutostart)) { + FissionExperimentDisqualify(); + } +} + +static void EnsureFissionAutostartInitialized() { + if (gFissionAutostartInitialized) { + return; + } + gFissionAutostartInitialized = true; + + if (!XRE_IsParentProcess()) { + // This pref is configured for the current session by the parent process. + gFissionAutostart = Preferences::GetBool(kPrefFissionAutostartSession, + false, PrefValueKind::Default); + return; + } + + // Initialize the fission experiment, configuring fission.autostart's + // default, before checking other overrides. This allows opting-out of a + // fission experiment through about:preferences or about:config from a + // safemode session. + uint32_t experimentRaw = + Preferences::GetUint(kPrefFissionExperimentStartupEnrollmentStatus, + nsIXULRuntime::eExperimentStatusUnenrolled); + gFissionExperimentStatus = + experimentRaw < nsIXULRuntime::eExperimentStatusCount + ? nsIXULRuntime::ExperimentStatus(experimentRaw) + : nsIXULRuntime::eExperimentStatusDisqualified; + + // Watch the experiment enrollment status pref to detect experiment + // disqualification, and ensure it is propagated for the next restart. + Preferences::RegisterCallback(&OnFissionEnrollmentStatusChanged, + kPrefFissionExperimentEnrollmentStatus); + if (nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService()) { + nsCOMPtr<nsIObserver> shutdownObserver = + new FissionEnrollmentStatusShutdownObserver(); + observerService->AddObserver(shutdownObserver, "profile-before-change", + false); + } + + // If the user has overridden an active experiment by setting a user value for + // "fission.autostart", disqualify the user from the experiment. + if (Preferences::HasUserValue(kPrefFissionAutostart) && + FissionExperimentEnrolled()) { + FissionExperimentDisqualify(); + gFissionExperimentStatus = nsIXULRuntime::eExperimentStatusDisqualified; + } + + // Configure the default branch for "fission.autostart" based on experiment + // enrollment status. + if (FissionExperimentEnrolled()) { + bool isTreatment = + gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusTreatment; + Preferences::SetBool(kPrefFissionAutostart, isTreatment, + PrefValueKind::Default); + } + + if (!BrowserTabsRemoteAutostart()) { + gFissionAutostart = false; + if (gBrowserTabsRemoteStatus == kE10sForceDisabled) { + gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledByE10sEnv; + } else { + gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledByE10sOther; + } + } else if (gSafeMode) { + gFissionAutostart = false; + gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledBySafeMode; + } else if (EnvHasValue("MOZ_FORCE_ENABLE_FISSION")) { + gFissionAutostart = true; + gFissionDecisionStatus = nsIXULRuntime::eFissionEnabledByEnv; + } else { + // NOTE: This will take into account changes to the default due to + // `InitializeFissionExperimentStatus`. + gFissionAutostart = Preferences::GetBool(kPrefFissionAutostart, false); + if (gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusControl) { + gFissionDecisionStatus = nsIXULRuntime::eFissionExperimentControl; + } else if (gFissionExperimentStatus == + nsIXULRuntime::eExperimentStatusTreatment) { + gFissionDecisionStatus = nsIXULRuntime::eFissionExperimentTreatment; + } else if (Preferences::HasUserValue(kPrefFissionAutostart)) { + gFissionDecisionStatus = gFissionAutostart + ? nsIXULRuntime::eFissionEnabledByUserPref + : nsIXULRuntime::eFissionDisabledByUserPref; + } else { + gFissionDecisionStatus = gFissionAutostart + ? nsIXULRuntime::eFissionEnabledByDefault + : nsIXULRuntime::eFissionDisabledByDefault; + } + } + + // Content processes cannot run the same logic as we're running in the parent + // process, as the current value of various preferences may have changed + // between launches. Instead, the content process will read the default branch + // of the locked `fission.autostart.session` preference to determine the value + // determined by this method. + Preferences::Unlock(kPrefFissionAutostartSession); + Preferences::ClearUser(kPrefFissionAutostartSession); + Preferences::SetBool(kPrefFissionAutostartSession, gFissionAutostart, + PrefValueKind::Default); + Preferences::Lock(kPrefFissionAutostartSession); + + // If we're actively enrolled in the fission experiment, disqualify the user + // from the experiment if the fission pref is modified. + if (FissionExperimentEnrolled()) { + Preferences::RegisterCallback(&OnFissionAutostartChanged, + kPrefFissionAutostart); + } +} + +namespace mozilla { + +bool FissionAutostart() { + EnsureFissionAutostartInitialized(); + return gFissionAutostart; +} + +bool SessionHistoryInParent() { + return FissionAutostart() || + StaticPrefs:: + fission_sessionHistoryInParent_AtStartup_DoNotUseDirectly(); +} + +} // namespace mozilla + +/** + * The nsXULAppInfo object implements nsIFactory so that it can be its own + * singleton. + */ +class nsXULAppInfo : public nsIXULAppInfo, +#ifdef XP_WIN + public nsIWinAppHelper, +#endif + public nsICrashReporter, + public nsIFinishDumpingCallback, + public nsIXULRuntime + +{ + public: + constexpr nsXULAppInfo() = default; + NS_DECL_ISUPPORTS_INHERITED + NS_DECL_NSIPLATFORMINFO + NS_DECL_NSIXULAPPINFO + NS_DECL_NSIXULRUNTIME + NS_DECL_NSICRASHREPORTER + NS_DECL_NSIFINISHDUMPINGCALLBACK +#ifdef XP_WIN + NS_DECL_NSIWINAPPHELPER +#endif +}; + +NS_INTERFACE_MAP_BEGIN(nsXULAppInfo) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIXULRuntime) + NS_INTERFACE_MAP_ENTRY(nsIXULRuntime) +#ifdef XP_WIN + NS_INTERFACE_MAP_ENTRY(nsIWinAppHelper) +#endif + NS_INTERFACE_MAP_ENTRY(nsICrashReporter) + NS_INTERFACE_MAP_ENTRY(nsIFinishDumpingCallback) + NS_INTERFACE_MAP_ENTRY(nsIPlatformInfo) + NS_INTERFACE_MAP_ENTRY_CONDITIONAL(nsIXULAppInfo, + gAppData || XRE_IsContentProcess()) +NS_INTERFACE_MAP_END + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXULAppInfo::AddRef() { return 1; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXULAppInfo::Release() { return 1; } + +NS_IMETHODIMP +nsXULAppInfo::GetVendor(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().vendor; + return NS_OK; + } + aResult.Assign(gAppData->vendor); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetName(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().name; + return NS_OK; + } + +#ifdef MOZ_WIDGET_ANDROID + nsCString name = java::GeckoAppShell::GetAppName()->ToCString(); + aResult.Assign(std::move(name)); +#else + aResult.Assign(gAppData->name); +#endif + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetID(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().ID; + return NS_OK; + } + aResult.Assign(gAppData->ID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetVersion(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().version; + return NS_OK; + } + aResult.Assign(gAppData->version); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetPlatformVersion(nsACString& aResult) { + aResult.Assign(gToolkitVersion); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAppBuildID(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().buildID; + return NS_OK; + } + aResult.Assign(gAppData->buildID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetPlatformBuildID(nsACString& aResult) { + aResult.Assign(gToolkitBuildID); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetUAName(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().UAName; + return NS_OK; + } + aResult.Assign(gAppData->UAName); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetSourceURL(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().sourceURL; + return NS_OK; + } + aResult.Assign(gAppData->sourceURL); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetUpdateURL(nsACString& aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + aResult = cc->GetAppInfo().updateURL; + return NS_OK; + } + aResult.Assign(gAppData->updateURL); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetLogConsoleErrors(bool* aResult) { + *aResult = gLogConsoleErrors; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetLogConsoleErrors(bool aValue) { + gLogConsoleErrors = aValue; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetInSafeMode(bool* aResult) { + *aResult = gSafeMode; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetOS(nsACString& aResult) { + aResult.AssignLiteral(OS_TARGET); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetXPCOMABI(nsACString& aResult) { +#ifdef TARGET_XPCOM_ABI + aResult.AssignLiteral(TARGET_XPCOM_ABI); + return NS_OK; +#else + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::GetWidgetToolkit(nsACString& aResult) { + aResult.AssignLiteral(MOZ_WIDGET_TOOLKIT); + return NS_OK; +} + +// Ensure that the GeckoProcessType enum, defined in xpcom/build/nsXULAppAPI.h, +// is synchronized with the const unsigned longs defined in +// xpcom/system/nsIXULRuntime.idl. +#define SYNC_ENUMS(a, b) \ + static_assert(nsIXULRuntime::PROCESS_TYPE_##a == \ + static_cast<int>(GeckoProcessType_##b), \ + "GeckoProcessType in nsXULAppAPI.h not synchronized with " \ + "nsIXULRuntime.idl"); + +SYNC_ENUMS(DEFAULT, Default) +SYNC_ENUMS(PLUGIN, Plugin) +SYNC_ENUMS(CONTENT, Content) +SYNC_ENUMS(IPDLUNITTEST, IPDLUnitTest) +SYNC_ENUMS(GMPLUGIN, GMPlugin) +SYNC_ENUMS(GPU, GPU) +SYNC_ENUMS(VR, VR) +SYNC_ENUMS(RDD, RDD) +SYNC_ENUMS(SOCKET, Socket) +SYNC_ENUMS(SANDBOX_BROKER, RemoteSandboxBroker) +SYNC_ENUMS(FORKSERVER, ForkServer) + +// .. and ensure that that is all of them: +static_assert(GeckoProcessType_ForkServer + 1 == GeckoProcessType_End, + "Did not find the final GeckoProcessType"); + +NS_IMETHODIMP +nsXULAppInfo::GetProcessType(uint32_t* aResult) { + NS_ENSURE_ARG_POINTER(aResult); + *aResult = XRE_GetProcessType(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetProcessID(uint32_t* aResult) { +#ifdef XP_WIN + *aResult = GetCurrentProcessId(); +#else + *aResult = getpid(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetUniqueProcessID(uint64_t* aResult) { + if (XRE_IsContentProcess()) { + ContentChild* cc = ContentChild::GetSingleton(); + *aResult = cc->GetID(); + } else { + *aResult = 0; + } + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetRemoteType(nsACString& aRemoteType) { + if (XRE_IsContentProcess()) { + aRemoteType = ContentChild::GetSingleton()->GetRemoteType(); + } else { + aRemoteType = NOT_REMOTE_TYPE; + } + + return NS_OK; +} + +static nsCString gLastAppVersion; +static nsCString gLastAppBuildID; + +NS_IMETHODIMP +nsXULAppInfo::GetLastAppVersion(nsACString& aResult) { + if (XRE_IsContentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!gLastAppVersion.IsVoid() && gLastAppVersion.IsEmpty()) { + NS_WARNING("Attempt to retrieve lastAppVersion before it has been set."); + return NS_ERROR_NOT_AVAILABLE; + } + + aResult.Assign(gLastAppVersion); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetLastAppBuildID(nsACString& aResult) { + if (XRE_IsContentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + if (!gLastAppBuildID.IsVoid() && gLastAppBuildID.IsEmpty()) { + NS_WARNING("Attempt to retrieve lastAppBuildID before it has been set."); + return NS_ERROR_NOT_AVAILABLE; + } + + aResult.Assign(gLastAppBuildID); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetFissionAutostart(bool* aResult) { + *aResult = FissionAutostart(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetFissionExperimentStatus(ExperimentStatus* aResult) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + EnsureFissionAutostartInitialized(); + *aResult = gFissionExperimentStatus; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetFissionDecisionStatus(FissionDecisionStatus* aResult) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + EnsureFissionAutostartInitialized(); + + MOZ_ASSERT(gFissionDecisionStatus != eFissionStatusUnknown); + *aResult = gFissionDecisionStatus; + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetFissionDecisionStatusString(nsACString& aResult) { + if (!XRE_IsParentProcess()) { + return NS_ERROR_NOT_AVAILABLE; + } + + EnsureFissionAutostartInitialized(); + switch (gFissionDecisionStatus) { + case eFissionExperimentControl: + aResult = "experimentControl"; + break; + case eFissionExperimentTreatment: + aResult = "experimentTreatment"; + break; + case eFissionDisabledByE10sEnv: + aResult = "disabledByE10sEnv"; + break; + case eFissionEnabledByEnv: + aResult = "enabledByEnv"; + break; + case eFissionDisabledBySafeMode: + aResult = "disabledBySafeMode"; + break; + case eFissionEnabledByDefault: + aResult = "enabledByDefault"; + break; + case eFissionDisabledByDefault: + aResult = "disabledByDefault"; + break; + case eFissionEnabledByUserPref: + aResult = "enabledByUserPref"; + break; + case eFissionDisabledByUserPref: + aResult = "disabledByUserPref"; + break; + case eFissionDisabledByE10sOther: + aResult = "disabledByE10sOther"; + break; + default: + MOZ_ASSERT_UNREACHABLE("Unexpected enum value"); + } + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetSessionHistoryInParent(bool* aResult) { + *aResult = SessionHistoryInParent(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetBrowserTabsRemoteAutostart(bool* aResult) { + *aResult = BrowserTabsRemoteAutostart(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetMaxWebProcessCount(uint32_t* aResult) { + *aResult = mozilla::GetMaxWebProcessCount(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAccessibilityEnabled(bool* aResult) { +#ifdef ACCESSIBILITY + *aResult = GetAccService() != nullptr; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAccessibleHandlerUsed(bool* aResult) { +#if defined(ACCESSIBILITY) && defined(XP_WIN) + *aResult = Preferences::GetBool("accessibility.handler.enabled", false) && + a11y::IsHandlerRegistered(); +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetAccessibilityInstantiator(nsAString& aInstantiator) { +#if defined(ACCESSIBILITY) && defined(XP_WIN) + if (!GetAccService()) { + aInstantiator.Truncate(); + return NS_OK; + } + nsAutoString ipClientInfo; + a11y::Compatibility::GetHumanReadableConsumersStr(ipClientInfo); + aInstantiator.Append(ipClientInfo); + aInstantiator.AppendLiteral("|"); + + nsCOMPtr<nsIFile> oopClientExe; + if (a11y::GetInstantiator(getter_AddRefs(oopClientExe))) { + nsAutoString oopClientInfo; + if (NS_SUCCEEDED(oopClientExe->GetPath(oopClientInfo))) { + aInstantiator.Append(oopClientInfo); + } + } +#else + aInstantiator.Truncate(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetShouldBlockIncompatJaws(bool* aResult) { + *aResult = false; +#if defined(ACCESSIBILITY) && defined(XP_WIN) + *aResult = mozilla::a11y::Compatibility::IsOldJAWS(); +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIs64Bit(bool* aResult) { +#ifdef HAVE_64BIT_BUILD + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::EnsureContentProcess() { + if (!XRE_IsParentProcess()) return NS_ERROR_NOT_AVAILABLE; + + RefPtr<ContentParent> unused = + ContentParent::GetNewOrUsedBrowserProcess(DEFAULT_REMOTE_TYPE); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::InvalidateCachesOnRestart() { + nsCOMPtr<nsIFile> file; + nsresult rv = + NS_GetSpecialDirectory(NS_APP_PROFILE_DIR_STARTUP, getter_AddRefs(file)); + if (NS_FAILED(rv)) return rv; + if (!file) return NS_ERROR_NOT_AVAILABLE; + + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsINIParser parser; + rv = parser.Init(file); + if (NS_FAILED(rv)) { + // This fails if compatibility.ini is not there, so we'll + // flush the caches on the next restart anyways. + return NS_OK; + } + + nsAutoCString buf; + rv = parser.GetString("Compatibility", "InvalidateCaches", buf); + + if (NS_FAILED(rv)) { + PRFileDesc* fd; + rv = file->OpenNSPRFileDesc(PR_RDWR | PR_APPEND, 0600, &fd); + if (NS_FAILED(rv)) { + NS_ERROR("could not create output stream"); + return NS_ERROR_NOT_AVAILABLE; + } + static const char kInvalidationHeader[] = + NS_LINEBREAK "InvalidateCaches=1" NS_LINEBREAK; + PR_Write(fd, kInvalidationHeader, sizeof(kInvalidationHeader) - 1); + PR_Close(fd); + } + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetReplacedLockTime(PRTime* aReplacedLockTime) { + if (!gProfileLock) return NS_ERROR_NOT_AVAILABLE; + gProfileLock->GetReplacedLockTime(aReplacedLockTime); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIsReleaseOrBeta(bool* aResult) { +#ifdef RELEASE_OR_BETA + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetIsOfficialBranding(bool* aResult) { +#ifdef MOZ_OFFICIAL_BRANDING + *aResult = true; +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetDefaultUpdateChannel(nsACString& aResult) { + aResult.AssignLiteral(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetDistributionID(nsACString& aResult) { + aResult.AssignLiteral(MOZ_DISTRIBUTION_ID); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetWindowsDLLBlocklistStatus(bool* aResult) { +#if defined(HAS_DLL_BLOCKLIST) + *aResult = DllBlocklist_CheckStatus(); +#else + *aResult = false; +#endif + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetRestartedByOS(bool* aResult) { + *aResult = gRestartedByOS; + return NS_OK; +} + +#if defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS) +// Forward declaration +void SetupLauncherProcessPref(); + +static Maybe<LauncherRegistryInfo::EnabledState> gLauncherProcessState; +#endif // defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS) + +NS_IMETHODIMP +nsXULAppInfo::GetLauncherProcessState(uint32_t* aResult) { +#if defined(XP_WIN) && defined(MOZ_LAUNCHER_PROCESS) + SetupLauncherProcessPref(); + + if (!gLauncherProcessState) { + return NS_ERROR_UNEXPECTED; + } + + *aResult = static_cast<uint32_t>(gLauncherProcessState.value()); + return NS_OK; +#else + return NS_ERROR_NOT_AVAILABLE; +#endif +} + +#ifdef XP_WIN +NS_IMETHODIMP +nsXULAppInfo::GetUserCanElevate(bool* aUserCanElevate) { + HANDLE rawToken; + if (!::OpenProcessToken(::GetCurrentProcess(), TOKEN_QUERY, &rawToken)) { + *aUserCanElevate = false; + return NS_OK; + } + + nsAutoHandle token(rawToken); + LauncherResult<TOKEN_ELEVATION_TYPE> elevationType = GetElevationType(token); + if (elevationType.isErr()) { + *aUserCanElevate = false; + return NS_OK; + } + + // The possible values returned for elevationType and their meanings are: + // TokenElevationTypeDefault: The token does not have a linked token + // (e.g. UAC disabled or a standard user, so they can't be elevated) + // TokenElevationTypeFull: The token is linked to an elevated token + // (e.g. UAC is enabled and the user is already elevated so they can't + // be elevated again) + // TokenElevationTypeLimited: The token is linked to a limited token + // (e.g. UAC is enabled and the user is not elevated, so they can be + // elevated) + *aUserCanElevate = (elevationType.inspect() == TokenElevationTypeLimited); + return NS_OK; +} +#endif + +NS_IMETHODIMP +nsXULAppInfo::GetEnabled(bool* aEnabled) { + *aEnabled = CrashReporter::GetEnabled(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetEnabled(bool aEnabled) { + if (aEnabled) { + if (CrashReporter::GetEnabled()) { + // no point in erroring for double-enabling + return NS_OK; + } + + nsCOMPtr<nsIFile> greBinDir; + NS_GetSpecialDirectory(NS_GRE_BIN_DIR, getter_AddRefs(greBinDir)); + if (!greBinDir) { + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIFile> xreBinDirectory = greBinDir; + if (!xreBinDirectory) { + return NS_ERROR_FAILURE; + } + + return CrashReporter::SetExceptionHandler(xreBinDirectory, true); + } + + if (!CrashReporter::GetEnabled()) { + // no point in erroring for double-disabling + return NS_OK; + } + + return CrashReporter::UnsetExceptionHandler(); +} + +NS_IMETHODIMP +nsXULAppInfo::GetServerURL(nsIURL** aServerURL) { + NS_ENSURE_ARG_POINTER(aServerURL); + if (!CrashReporter::GetEnabled()) return NS_ERROR_NOT_INITIALIZED; + + nsAutoCString data; + if (!CrashReporter::GetServerURL(data)) { + return NS_ERROR_FAILURE; + } + nsCOMPtr<nsIURI> uri; + NS_NewURI(getter_AddRefs(uri), data); + if (!uri) return NS_ERROR_FAILURE; + + nsCOMPtr<nsIURL> url; + url = do_QueryInterface(uri); + NS_ADDREF(*aServerURL = url); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetServerURL(nsIURL* aServerURL) { + // Only allow https or http URLs + if (!aServerURL->SchemeIs("http") && !aServerURL->SchemeIs("https")) { + return NS_ERROR_INVALID_ARG; + } + + nsAutoCString spec; + nsresult rv = aServerURL->GetSpec(spec); + NS_ENSURE_SUCCESS(rv, rv); + + return CrashReporter::SetServerURL(spec); +} + +NS_IMETHODIMP +nsXULAppInfo::GetMinidumpPath(nsIFile** aMinidumpPath) { + if (!CrashReporter::GetEnabled()) return NS_ERROR_NOT_INITIALIZED; + + nsAutoString path; + if (!CrashReporter::GetMinidumpPath(path)) return NS_ERROR_FAILURE; + + nsresult rv = NS_NewLocalFile(path, false, aMinidumpPath); + NS_ENSURE_SUCCESS(rv, rv); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SetMinidumpPath(nsIFile* aMinidumpPath) { + nsAutoString path; + nsresult rv = aMinidumpPath->GetPath(path); + NS_ENSURE_SUCCESS(rv, rv); + return CrashReporter::SetMinidumpPath(path); +} + +NS_IMETHODIMP +nsXULAppInfo::GetMinidumpForID(const nsAString& aId, nsIFile** aMinidump) { + if (!CrashReporter::GetMinidumpForID(aId, aMinidump)) { + return NS_ERROR_FILE_NOT_FOUND; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::GetExtraFileForID(const nsAString& aId, nsIFile** aExtraFile) { + if (!CrashReporter::GetExtraFileForID(aId, aExtraFile)) { + return NS_ERROR_FILE_NOT_FOUND; + } + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::AnnotateCrashReport(const nsACString& key, + const nsACString& data) { + CrashReporter::Annotation annotation; + + if (!AnnotationFromString(annotation, PromiseFlatCString(key).get())) { + return NS_ERROR_INVALID_ARG; + } + + return CrashReporter::AnnotateCrashReport(annotation, data); +} + +NS_IMETHODIMP +nsXULAppInfo::RemoveCrashReportAnnotation(const nsACString& key) { + CrashReporter::Annotation annotation; + + if (!AnnotationFromString(annotation, PromiseFlatCString(key).get())) { + return NS_ERROR_INVALID_ARG; + } + + return CrashReporter::RemoveCrashReportAnnotation(annotation); +} + +NS_IMETHODIMP +nsXULAppInfo::IsAnnotationWhitelistedForPing(const nsACString& aValue, + bool* aIsWhitelisted) { + CrashReporter::Annotation annotation; + + if (!AnnotationFromString(annotation, PromiseFlatCString(aValue).get())) { + return NS_ERROR_INVALID_ARG; + } + + *aIsWhitelisted = CrashReporter::IsAnnotationWhitelistedForPing(annotation); + + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::AppendAppNotesToCrashReport(const nsACString& data) { + return CrashReporter::AppendAppNotesToCrashReport(data); +} + +NS_IMETHODIMP +nsXULAppInfo::RegisterAppMemory(uint64_t pointer, uint64_t len) { + return CrashReporter::RegisterAppMemory((void*)pointer, len); +} + +NS_IMETHODIMP +nsXULAppInfo::WriteMinidumpForException(void* aExceptionInfo) { +#ifdef XP_WIN + return CrashReporter::WriteMinidumpForException( + static_cast<EXCEPTION_POINTERS*>(aExceptionInfo)); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::AppendObjCExceptionInfoToAppNotes(void* aException) { +#ifdef XP_MACOSX + return CrashReporter::AppendObjCExceptionInfoToAppNotes(aException); +#else + return NS_ERROR_NOT_IMPLEMENTED; +#endif +} + +NS_IMETHODIMP +nsXULAppInfo::GetSubmitReports(bool* aEnabled) { + return CrashReporter::GetSubmitReports(aEnabled); +} + +NS_IMETHODIMP +nsXULAppInfo::SetSubmitReports(bool aEnabled) { + return CrashReporter::SetSubmitReports(aEnabled); +} + +NS_IMETHODIMP +nsXULAppInfo::UpdateCrashEventsDir() { + CrashReporter::UpdateCrashEventsDir(); + return NS_OK; +} + +NS_IMETHODIMP +nsXULAppInfo::SaveMemoryReport() { + if (!CrashReporter::GetEnabled()) { + return NS_ERROR_NOT_INITIALIZED; + } + nsCOMPtr<nsIFile> file; + nsresult rv = CrashReporter::GetDefaultMemoryReportFile(getter_AddRefs(file)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + nsString path; + file->GetPath(path); + + nsCOMPtr<nsIMemoryInfoDumper> dumper = + do_GetService("@mozilla.org/memory-info-dumper;1"); + if (NS_WARN_IF(!dumper)) { + return NS_ERROR_UNEXPECTED; + } + + rv = dumper->DumpMemoryReportsToNamedFile( + path, this, file, true /* anonymize */, false /* minimizeMemoryUsage */); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + return NS_OK; +} + +// This method is from nsIFInishDumpingCallback. +NS_IMETHODIMP +nsXULAppInfo::Callback(nsISupports* aData) { + nsCOMPtr<nsIFile> file = do_QueryInterface(aData); + MOZ_ASSERT(file); + + CrashReporter::SetMemoryReportFile(file); + return NS_OK; +} + +static const nsXULAppInfo kAppInfo; +namespace mozilla { +nsresult AppInfoConstructor(nsISupports* aOuter, REFNSIID aIID, + void** aResult) { + NS_ENSURE_NO_AGGREGATION(aOuter); + + return const_cast<nsXULAppInfo*>(&kAppInfo)->QueryInterface(aIID, aResult); +} +} // namespace mozilla + +bool gLogConsoleErrors = false; + +#define NS_ENSURE_TRUE_LOG(x, ret) \ + PR_BEGIN_MACRO \ + if (MOZ_UNLIKELY(!(x))) { \ + NS_WARNING("NS_ENSURE_TRUE(" #x ") failed"); \ + gLogConsoleErrors = true; \ + return ret; \ + } \ + PR_END_MACRO + +#define NS_ENSURE_SUCCESS_LOG(res, ret) \ + NS_ENSURE_TRUE_LOG(NS_SUCCEEDED(res), ret) + +/** + * Because we're starting/stopping XPCOM several times in different scenarios, + * this class is a stack-based critter that makes sure that XPCOM is shut down + * during early returns. + */ + +class ScopedXPCOMStartup { + public: + ScopedXPCOMStartup() : mServiceManager(nullptr) {} + ~ScopedXPCOMStartup(); + + nsresult Initialize(bool aInitJSContext = true); + nsresult SetWindowCreator(nsINativeAppSupport* native); + + private: + nsIServiceManager* mServiceManager; + static nsINativeAppSupport* gNativeAppSupport; + + friend already_AddRefed<nsINativeAppSupport> NS_GetNativeAppSupport(); +}; + +ScopedXPCOMStartup::~ScopedXPCOMStartup() { + NS_IF_RELEASE(gNativeAppSupport); + + if (mServiceManager) { +#ifdef XP_MACOSX + // On OS X, we need a pool to catch cocoa objects that are autoreleased + // during teardown. + mozilla::MacAutoreleasePool pool; +#endif + + nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service()); + if (appStartup) appStartup->DestroyHiddenWindow(); + + gDirServiceProvider->DoShutdown(); + PROFILER_MARKER_UNTYPED("Shutdown early", OTHER); + + WriteConsoleLog(); + + NS_ShutdownXPCOM(mServiceManager); + mServiceManager = nullptr; + } +} + +// {5F5E59CE-27BC-47eb-9D1F-B09CA9049836} +static const nsCID kProfileServiceCID = { + 0x5f5e59ce, + 0x27bc, + 0x47eb, + {0x9d, 0x1f, 0xb0, 0x9c, 0xa9, 0x4, 0x98, 0x36}}; + +static already_AddRefed<nsIFactory> ProfileServiceFactoryConstructor( + const mozilla::Module& module, const mozilla::Module::CIDEntry& entry) { + nsCOMPtr<nsIFactory> factory; + NS_NewToolkitProfileFactory(getter_AddRefs(factory)); + return factory.forget(); +} + +static const mozilla::Module::CIDEntry kXRECIDs[] = { + {&kProfileServiceCID, false, ProfileServiceFactoryConstructor, nullptr}, + {nullptr}}; + +static const mozilla::Module::ContractIDEntry kXREContracts[] = { + {NS_PROFILESERVICE_CONTRACTID, &kProfileServiceCID}, {nullptr}}; + +extern const mozilla::Module kXREModule = {mozilla::Module::kVersion, kXRECIDs, + kXREContracts}; + +nsresult ScopedXPCOMStartup::Initialize(bool aInitJSContext) { + NS_ASSERTION(gDirServiceProvider, "Should not get here!"); + + nsresult rv; + + rv = NS_InitXPCOM(&mServiceManager, gDirServiceProvider->GetAppDir(), + gDirServiceProvider, aInitJSContext); + if (NS_FAILED(rv)) { + NS_ERROR("Couldn't start xpcom!"); + mServiceManager = nullptr; + } else { +#ifdef DEBUG + nsCOMPtr<nsIComponentRegistrar> reg = do_QueryInterface(mServiceManager); + NS_ASSERTION(reg, "Service Manager doesn't QI to Registrar."); +#endif + } + + return rv; +} + +/** + * This is a little factory class that serves as a singleton-service-factory + * for the nativeappsupport object. + */ +class nsSingletonFactory final : public nsIFactory { + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIFACTORY + + explicit nsSingletonFactory(nsISupports* aSingleton); + + private: + ~nsSingletonFactory() = default; + nsCOMPtr<nsISupports> mSingleton; +}; + +nsSingletonFactory::nsSingletonFactory(nsISupports* aSingleton) + : mSingleton(aSingleton) { + NS_ASSERTION(mSingleton, "Singleton was null!"); +} + +NS_IMPL_ISUPPORTS(nsSingletonFactory, nsIFactory) + +NS_IMETHODIMP +nsSingletonFactory::CreateInstance(nsISupports* aOuter, const nsIID& aIID, + void** aResult) { + NS_ENSURE_NO_AGGREGATION(aOuter); + + return mSingleton->QueryInterface(aIID, aResult); +} + +NS_IMETHODIMP +nsSingletonFactory::LockFactory(bool) { return NS_OK; } + +/** + * Set our windowcreator on the WindowWatcher service. + */ +nsresult ScopedXPCOMStartup::SetWindowCreator(nsINativeAppSupport* native) { + nsresult rv; + + NS_IF_ADDREF(gNativeAppSupport = native); + + nsCOMPtr<nsIWindowCreator> creator(components::AppStartup::Service()); + if (!creator) return NS_ERROR_UNEXPECTED; + + nsCOMPtr<nsIWindowWatcher> wwatch( + do_GetService(NS_WINDOWWATCHER_CONTRACTID, &rv)); + NS_ENSURE_SUCCESS(rv, rv); + + return wwatch->SetWindowCreator(creator); +} + +/* static */ already_AddRefed<nsINativeAppSupport> NS_GetNativeAppSupport() { + if (!ScopedXPCOMStartup::gNativeAppSupport) { + return nullptr; + } + + return do_AddRef(ScopedXPCOMStartup::gNativeAppSupport); +} + +nsINativeAppSupport* ScopedXPCOMStartup::gNativeAppSupport; + +static void DumpArbitraryHelp() { + nsresult rv; + + ScopedLogging log; + + { + ScopedXPCOMStartup xpcom; + xpcom.Initialize(); + + nsCOMPtr<nsICommandLineRunner> cmdline(new nsCommandLine()); + + nsCString text; + rv = cmdline->GetHelpText(text); + if (NS_SUCCEEDED(rv)) printf("%s", text.get()); + } +} + +// English text needs to go into a dtd file. +// But when this is called we have no components etc. These strings must either +// be here, or in a native resource file. +static void DumpHelp() { + printf( + "Usage: %s [ options ... ] [URL]\n" + " where options include:\n\n", + gArgv[0]); + +#ifdef MOZ_X11 + printf( + "X11 options\n" + " --display=DISPLAY X display to use\n" + " --sync Make X calls synchronous\n"); +#endif +#ifdef XP_UNIX + printf( + " --g-fatal-warnings Make all warnings fatal\n" + "\n%s options\n", + (const char*)gAppData->name); +#endif + + printf( + " -h or --help Print this message.\n" + " -v or --version Print %s version.\n" + " --full-version Print %s version, build and platform build ids.\n" + " -P <profile> Start with <profile>.\n" + " --profile <path> Start with profile at <path>.\n" + " --migration Start with migration wizard.\n" + " --ProfileManager Start with ProfileManager.\n" +#ifdef MOZ_HAS_REMOTE + " --no-remote Do not accept or send remote commands; implies\n" + " --new-instance.\n" + " --new-instance Open new instance, not a new window in running " + "instance.\n" +#endif + " --safe-mode Disables extensions and themes for this session.\n" +#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE + " --allow-downgrade Allows downgrading a profile.\n" +#endif + " --MOZ_LOG=<modules> Treated as MOZ_LOG=<modules> environment " + "variable,\n" + " overrides it.\n" + " --MOZ_LOG_FILE=<file> Treated as MOZ_LOG_FILE=<file> environment " + "variable,\n" + " overrides it. If MOZ_LOG_FILE is not specified as " + "an\n" + " argument or as an environment variable, logging " + "will be\n" + " written to stdout.\n", + (const char*)gAppData->name, (const char*)gAppData->name); + +#if defined(XP_WIN) + printf(" --console Start %s with a debugging console.\n", + (const char*)gAppData->name); +#endif + +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX) + printf(" --headless Run without a GUI.\n"); +#endif + + // this works, but only after the components have registered. so if you drop + // in a new command line handler, --help won't not until the second run. out + // of the bug, because we ship a component.reg file, it works correctly. + DumpArbitraryHelp(); +} + +static inline void DumpVersion() { + if (gAppData->vendor) { + printf("%s ", (const char*)gAppData->vendor); + } + printf("%s ", (const char*)gAppData->name); + + // Use the displayed version + // For example, for beta, we would display 42.0b2 instead of 42.0 + printf("%s", MOZ_STRINGIFY(MOZ_APP_VERSION_DISPLAY)); + + if (gAppData->copyright) { + printf(", %s", (const char*)gAppData->copyright); + } + printf("\n"); +} + +static inline void DumpFullVersion() { + if (gAppData->vendor) { + printf("%s ", (const char*)gAppData->vendor); + } + printf("%s ", (const char*)gAppData->name); + + // Use the displayed version + // For example, for beta, we would display 42.0b2 instead of 42.0 + printf("%s ", MOZ_STRINGIFY(MOZ_APP_VERSION_DISPLAY)); + + printf("%s ", (const char*)gAppData->buildID); + printf("%s ", (const char*)PlatformBuildID()); + if (gAppData->copyright) { + printf(", %s", (const char*)gAppData->copyright); + } + printf("\n"); +} + +void XRE_InitOmnijar(nsIFile* greOmni, nsIFile* appOmni) { + mozilla::Omnijar::Init(greOmni, appOmni); +} + +nsresult XRE_GetBinaryPath(nsIFile** aResult) { + return mozilla::BinaryPath::GetFile(aResult); +} + +#ifdef XP_WIN +# include "nsWindowsRestart.cpp" +# include <shellapi.h> + +typedef BOOL(WINAPI* SetProcessDEPPolicyFunc)(DWORD dwFlags); + +static void RegisterApplicationRestartChanged(const char* aPref, void* aData) { + DWORD cchCmdLine = 0; + HRESULT rc = ::GetApplicationRestartSettings(::GetCurrentProcess(), nullptr, + &cchCmdLine, nullptr); + bool wasRegistered = false; + if (rc == S_OK) { + wasRegistered = true; + } + + if (Preferences::GetBool(PREF_WIN_REGISTER_APPLICATION_RESTART, false) && + !wasRegistered) { + // Make the command line to use when restarting. + // Excludes argv[0] because RegisterApplicationRestart adds the + // executable name, replace that temporarily with -os-restarted + char* exeName = gRestartArgv[0]; + gRestartArgv[0] = const_cast<char*>("-os-restarted"); + wchar_t** restartArgvConverted = + AllocConvertUTF8toUTF16Strings(gRestartArgc, gRestartArgv); + gRestartArgv[0] = exeName; + + mozilla::UniquePtr<wchar_t[]> restartCommandLine; + if (restartArgvConverted) { + restartCommandLine = + mozilla::MakeCommandLine(gRestartArgc, restartArgvConverted); + FreeAllocStrings(gRestartArgc, restartArgvConverted); + } + + if (restartCommandLine) { + // Flags RESTART_NO_PATCH and RESTART_NO_REBOOT are not set, so we + // should be restarted if terminated by an update or restart. + ::RegisterApplicationRestart(restartCommandLine.get(), + RESTART_NO_CRASH | RESTART_NO_HANG); + } + } else if (wasRegistered) { + ::UnregisterApplicationRestart(); + } +} + +static void OnAlteredPrefetchPrefChanged(const char* aPref, void* aData) { + int32_t prefVal = Preferences::GetInt(PREF_WIN_ALTERED_DLL_PREFETCH, 0); + + mozilla::DllPrefetchExperimentRegistryInfo prefetchRegInfo; + mozilla::DebugOnly<mozilla::Result<Ok, nsresult>> reflectResult = + prefetchRegInfo.ReflectPrefToRegistry(prefVal); + + MOZ_ASSERT(reflectResult.value.isOk()); +} + +static void SetupAlteredPrefetchPref() { + mozilla::DllPrefetchExperimentRegistryInfo prefetchRegInfo; + + mozilla::DebugOnly<mozilla::Result<Ok, nsresult>> reflectResult = + prefetchRegInfo.ReflectPrefToRegistry( + Preferences::GetInt(PREF_WIN_ALTERED_DLL_PREFETCH, 0)); + MOZ_ASSERT(reflectResult.value.isOk()); + + Preferences::RegisterCallback(&OnAlteredPrefetchPrefChanged, + PREF_WIN_ALTERED_DLL_PREFETCH); +} + +static void ReflectSkeletonUIPrefToRegistry(const char* aPref, void* aData) { + Unused << aPref; + Unused << aData; + + bool shouldBeEnabled = + Preferences::GetBool(kPrefPreXulSkeletonUI, false) && + Preferences::GetBool(kPrefBrowserStartupBlankWindow, false) && + Preferences::GetBool(kPrefDrawTabsInTitlebar, false); + if (shouldBeEnabled && Preferences::HasUserValue(kPrefThemeId)) { + nsCString themeId; + Preferences::GetCString(kPrefThemeId, themeId); + if (themeId.EqualsLiteral("default-theme@mozilla.org")) { + SetPreXULSkeletonUIThemeId(ThemeMode::Default); + } else if (themeId.EqualsLiteral("firefox-compact-dark@mozilla.org")) { + SetPreXULSkeletonUIThemeId(ThemeMode::Dark); + } else if (themeId.EqualsLiteral("firefox-compact-light@mozilla.org")) { + SetPreXULSkeletonUIThemeId(ThemeMode::Light); + } else { + shouldBeEnabled = false; + } + } else if (shouldBeEnabled) { + SetPreXULSkeletonUIThemeId(ThemeMode::Default); + } + + if (GetPreXULSkeletonUIEnabled() != shouldBeEnabled) { + SetPreXULSkeletonUIEnabledIfAllowed(shouldBeEnabled); + } +} + +static void SetupSkeletonUIPrefs() { + ReflectSkeletonUIPrefToRegistry(nullptr, nullptr); + Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry, + kPrefPreXulSkeletonUI); + Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry, + kPrefBrowserStartupBlankWindow); + Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry, kPrefThemeId); + Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry, + kPrefDrawTabsInTitlebar); +} + +# if defined(MOZ_LAUNCHER_PROCESS) + +static void OnLauncherPrefChanged(const char* aPref, void* aData) { + bool prefVal = Preferences::GetBool(PREF_WIN_LAUNCHER_PROCESS_ENABLED, true); + + mozilla::LauncherRegistryInfo launcherRegInfo; + mozilla::DebugOnly<mozilla::LauncherVoidResult> reflectResult = + launcherRegInfo.ReflectPrefToRegistry(prefVal); + MOZ_ASSERT(reflectResult.inspect().isOk()); +} + +static void OnLauncherTelemetryPrefChanged(const char* aPref, void* aData) { + bool prefVal = Preferences::GetBool(kPrefHealthReportUploadEnabled, true); + + mozilla::LauncherRegistryInfo launcherRegInfo; + mozilla::DebugOnly<mozilla::LauncherVoidResult> reflectResult = + launcherRegInfo.ReflectTelemetryPrefToRegistry(prefVal); + MOZ_ASSERT(reflectResult.inspect().isOk()); +} + +static void SetupLauncherProcessPref() { + if (gLauncherProcessState) { + // We've already successfully run + return; + } + + mozilla::LauncherRegistryInfo launcherRegInfo; + + mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> + enabledState = launcherRegInfo.IsEnabled(); + + if (enabledState.isOk()) { + gLauncherProcessState = Some(enabledState.unwrap()); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::LauncherProcessState, + static_cast<uint32_t>(enabledState.unwrap())); + + // Reflect the launcher process registry state into user prefs + Preferences::SetBool( + PREF_WIN_LAUNCHER_PROCESS_ENABLED, + enabledState.unwrap() != + mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled); + } + + mozilla::DebugOnly<mozilla::LauncherVoidResult> reflectResult = + launcherRegInfo.ReflectTelemetryPrefToRegistry( + Preferences::GetBool(kPrefHealthReportUploadEnabled, true)); + MOZ_ASSERT(reflectResult.inspect().isOk()); + + Preferences::RegisterCallback(&OnLauncherPrefChanged, + PREF_WIN_LAUNCHER_PROCESS_ENABLED); + Preferences::RegisterCallback(&OnLauncherTelemetryPrefChanged, + kPrefHealthReportUploadEnabled); +} + +# endif // defined(MOZ_LAUNCHER_PROCESS) + +# if defined(MOZ_DEFAULT_BROWSER_AGENT) + +# define DEFAULT_BROWSER_AGENT_KEY_NAME \ + "SOFTWARE\\" MOZ_APP_VENDOR "\\" MOZ_APP_NAME "\\Default Browser Agent" + +static nsresult PrependRegistryValueName(nsAutoString& aValueName) { + nsresult rv; + + nsCOMPtr<nsIFile> binaryPath; + rv = XRE_GetBinaryPath(getter_AddRefs(binaryPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> binaryDir; + rv = binaryPath->GetParent(getter_AddRefs(binaryDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString prefix; + rv = binaryDir->GetPath(prefix); + NS_ENSURE_SUCCESS(rv, rv); + + prefix.AppendLiteral("|"); + aValueName.Insert(prefix, 0); + + return NS_OK; +} + +static void OnDefaultAgentTelemetryPrefChanged(const char* aPref, void* aData) { + nsresult rv; + nsAutoString valueName; + if (strcmp(aPref, kPrefHealthReportUploadEnabled) == 0) { + valueName.AssignLiteral("DisableTelemetry"); + } else if (strcmp(aPref, kPrefDefaultAgentEnabled) == 0) { + valueName.AssignLiteral("DisableDefaultBrowserAgent"); + } else { + return; + } + rv = PrependRegistryValueName(valueName); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoString keyName; + keyName.AppendLiteral(DEFAULT_BROWSER_AGENT_KEY_NAME); + rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, keyName, + nsIWindowsRegKey::ACCESS_WRITE); + + bool prefVal = Preferences::GetBool(aPref, true); + + // We're recording whether the pref is *disabled*, so invert the value. + rv = regKey->WriteIntValue(valueName, prefVal ? 0 : 1); + NS_ENSURE_SUCCESS_VOID(rv); +} + +static void OnDefaultAgentRemoteSettingsPrefChanged(const char* aPref, + void* aData) { + nsresult rv; + nsAutoString valueName; + if (strcmp(aPref, kPrefServicesSettingsServer) == 0) { + valueName.AssignLiteral("ServicesSettingsServer"); + } else if (strcmp(aPref, kPrefSecurityContentSignatureRootHash) == 0) { + valueName.AssignLiteral("SecurityContentSignatureRootHash"); + } else { + return; + } + rv = PrependRegistryValueName(valueName); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoString keyName; + keyName.AppendLiteral(DEFAULT_BROWSER_AGENT_KEY_NAME); + rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, keyName, + nsIWindowsRegKey::ACCESS_WRITE); + + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoString prefVal; + rv = Preferences::GetString(aPref, prefVal); + NS_ENSURE_SUCCESS_VOID(rv); + + if (prefVal.IsEmpty()) { + rv = regKey->RemoveValue(valueName); + } else { + rv = regKey->WriteStringValue(valueName, prefVal); + } + NS_ENSURE_SUCCESS_VOID(rv); +} + +static void SetDefaultAgentLastRunTime() { + nsresult rv; + nsAutoString valueName; + valueName.AppendLiteral("AppLastRunTime"); + rv = PrependRegistryValueName(valueName); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIWindowsRegKey> regKey = + do_CreateInstance("@mozilla.org/windows-registry-key;1", &rv); + NS_ENSURE_SUCCESS_VOID(rv); + + nsAutoString keyName; + keyName.AppendLiteral(DEFAULT_BROWSER_AGENT_KEY_NAME); + rv = regKey->Create(nsIWindowsRegKey::ROOT_KEY_CURRENT_USER, keyName, + nsIWindowsRegKey::ACCESS_WRITE); + NS_ENSURE_SUCCESS_VOID(rv); + + FILETIME fileTime; + GetSystemTimeAsFileTime(&fileTime); + + ULARGE_INTEGER integerTime; + integerTime.u.LowPart = fileTime.dwLowDateTime; + integerTime.u.HighPart = fileTime.dwHighDateTime; + + rv = regKey->WriteInt64Value(valueName, integerTime.QuadPart); + NS_ENSURE_SUCCESS_VOID(rv); +} + +# endif // defined(MOZ_DEFAULT_BROWSER_AGENT) + +#endif // XP_WIN + +void UnlockProfile() { + if (gProfileLock) { + gProfileLock->Unlock(); + } +} + +// If aBlankCommandLine is true, then the application will be launched with a +// blank command line instead of being launched with the same command line that +// it was initially started with. +nsresult LaunchChild(bool aBlankCommandLine) { + // Restart this process by exec'ing it into the current process + // if supported by the platform. Otherwise, use NSPR. + +#ifdef MOZ_JPROF + // make sure JPROF doesn't think we're E10s + unsetenv("JPROF_ISCHILD"); +#endif + + if (aBlankCommandLine) { + gRestartArgc = 1; + gRestartArgv[gRestartArgc] = nullptr; + } + + SaveToEnv("MOZ_LAUNCHED_CHILD=1"); +#if defined(MOZ_LAUNCHER_PROCESS) + SaveToEnv("MOZ_LAUNCHER_PROCESS=1"); +#endif // defined(MOZ_LAUNCHER_PROCESS) + +#if !defined(MOZ_WIDGET_ANDROID) // Android has separate restart code. +# if defined(XP_MACOSX) + CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, true); + LaunchChildMac(gRestartArgc, gRestartArgv); +# else + nsCOMPtr<nsIFile> lf; + nsresult rv = XRE_GetBinaryPath(getter_AddRefs(lf)); + if (NS_FAILED(rv)) return rv; + +# if defined(XP_WIN) + nsAutoString exePath; + rv = lf->GetPath(exePath); + if (NS_FAILED(rv)) return rv; + + HANDLE hProcess; + if (!WinLaunchChild(exePath.get(), gRestartArgc, gRestartArgv, nullptr, + &hProcess)) + return NS_ERROR_FAILURE; + // Keep the current process around until the restarted process has created + // its message queue, to avoid the launched process's windows being forced + // into the background. + mozilla::WaitForInputIdle(hProcess); + ::CloseHandle(hProcess); + +# else + nsAutoCString exePath; + rv = lf->GetNativePath(exePath); + if (NS_FAILED(rv)) return rv; + +# if defined(XP_UNIX) + if (execv(exePath.get(), gRestartArgv) == -1) return NS_ERROR_FAILURE; +# else + PRProcess* process = + PR_CreateProcess(exePath.get(), gRestartArgv, nullptr, nullptr); + if (!process) return NS_ERROR_FAILURE; + + int32_t exitCode; + PRStatus failed = PR_WaitProcess(process, &exitCode); + if (failed || exitCode) return NS_ERROR_FAILURE; +# endif // XP_UNIX +# endif // WP_WIN +# endif // WP_MACOSX +#endif // MOZ_WIDGET_ANDROID + + return NS_ERROR_LAUNCHED_CHILD_PROCESS; +} + +static const char kProfileProperties[] = + "chrome://mozapps/locale/profile/profileSelection.properties"; + +namespace { + +/** + * This class, instead of a raw nsresult, should be the return type of any + * function called by SelectProfile that initializes XPCOM. + */ +class ReturnAbortOnError { + public: + MOZ_IMPLICIT ReturnAbortOnError(nsresult aRv) { mRv = ConvertRv(aRv); } + + operator nsresult() { return mRv; } + + private: + inline nsresult ConvertRv(nsresult aRv) { + if (NS_SUCCEEDED(aRv) || aRv == NS_ERROR_LAUNCHED_CHILD_PROCESS) { + return aRv; + } + return NS_ERROR_ABORT; + } + + nsresult mRv; +}; + +} // namespace + +static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) { + nsresult rv; + + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + { // extra scoping is needed so we release these components before xpcom + // shutdown + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> sb; + sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE); + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + AutoTArray<nsString, 2> params = {appName, appName}; + + // profileMissing + nsAutoString missingMessage; + rv = sb->FormatStringFromName("profileMissing", params, missingMessage); + NS_ENSURE_SUCCESS(rv, NS_ERROR_ABORT); + + nsAutoString missingTitle; + params.SetLength(1); + rv = sb->FormatStringFromName("profileMissingTitle", params, missingTitle); + NS_ENSURE_SUCCESS(rv, NS_ERROR_ABORT); + + nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(ps, NS_ERROR_FAILURE); + + ps->Alert(nullptr, missingTitle.get(), missingMessage.get()); + + return NS_ERROR_ABORT; + } +} + +static ReturnAbortOnError ProfileLockedDialog(nsIFile* aProfileDir, + nsIFile* aProfileLocalDir, + nsIProfileUnlocker* aUnlocker, + nsINativeAppSupport* aNative, + nsIProfileLock** aResult) { + nsresult rv; + + bool exists; + aProfileDir->Exists(&exists); + if (!exists) { + return ProfileMissingDialog(aNative); + } + + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::Telemetry::WriteFailedProfileLock(aProfileDir); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + { // extra scoping is needed so we release these components before xpcom + // shutdown + nsCOMPtr<nsIStringBundleService> sbs = + mozilla::services::GetStringBundleService(); + NS_ENSURE_TRUE(sbs, NS_ERROR_FAILURE); + + nsCOMPtr<nsIStringBundle> sb; + sbs->CreateBundle(kProfileProperties, getter_AddRefs(sb)); + NS_ENSURE_TRUE_LOG(sbs, NS_ERROR_FAILURE); + + NS_ConvertUTF8toUTF16 appName(gAppData->name); + AutoTArray<nsString, 3> params = {appName, appName, appName}; + + nsAutoString killMessage; +#ifndef XP_MACOSX + rv = sb->FormatStringFromName( + aUnlocker ? "restartMessageUnlocker" : "restartMessageNoUnlocker2", + params, killMessage); +#else + rv = sb->FormatStringFromName( + aUnlocker ? "restartMessageUnlockerMac" : "restartMessageNoUnlockerMac", + params, killMessage); +#endif + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + params.SetLength(1); + nsAutoString killTitle; + rv = sb->FormatStringFromName("restartTitle", params, killTitle); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + if (gfxPlatform::IsHeadless()) { + // TODO: make a way to turn off all dialogs when headless. + Output(true, "%s\n", NS_LossyConvertUTF16toASCII(killMessage).get()); + return NS_ERROR_FAILURE; + } + + nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); + NS_ENSURE_TRUE(ps, NS_ERROR_FAILURE); + + if (aUnlocker) { + int32_t button; +#ifdef MOZ_WIDGET_ANDROID + java::GeckoAppShell::KillAnyZombies(); + button = 0; +#else + const uint32_t flags = (nsIPromptService::BUTTON_TITLE_IS_STRING * + nsIPromptService::BUTTON_POS_0) + + (nsIPromptService::BUTTON_TITLE_CANCEL * + nsIPromptService::BUTTON_POS_1); + + bool checkState = false; + rv = ps->ConfirmEx(nullptr, killTitle.get(), killMessage.get(), flags, + killTitle.get(), nullptr, nullptr, nullptr, + &checkState, &button); + NS_ENSURE_SUCCESS_LOG(rv, rv); +#endif + + if (button == 0) { + rv = aUnlocker->Unlock(nsIProfileUnlocker::FORCE_QUIT); + if (NS_FAILED(rv)) { + return rv; + } + + SaveFileToEnv("XRE_PROFILE_PATH", aProfileDir); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", aProfileLocalDir); + + return LaunchChild(false); + } + } else { +#ifdef MOZ_WIDGET_ANDROID + if (java::GeckoAppShell::UnlockProfile()) { + return NS_LockProfilePath(aProfileDir, aProfileLocalDir, nullptr, + aResult); + } +#else + rv = ps->Alert(nullptr, killTitle.get(), killMessage.get()); + NS_ENSURE_SUCCESS_LOG(rv, rv); +#endif + } + + return NS_ERROR_ABORT; + } +} + +static const char kProfileManagerURL[] = + "chrome://mozapps/content/profile/profileSelection.xhtml"; + +static ReturnAbortOnError ShowProfileManager( + nsIToolkitProfileService* aProfileSvc, nsINativeAppSupport* aNative) { + nsresult rv; + + nsCOMPtr<nsIFile> profD, profLD; + bool offline = false; + int32_t dialogReturn; + + { + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + +#ifdef XP_MACOSX + CommandLineServiceMac::SetupMacCommandLine(gRestartArgc, gRestartArgv, + true); +#endif + + { // extra scoping is needed so we release these components before xpcom + // shutdown + nsCOMPtr<nsIWindowWatcher> windowWatcher( + do_GetService(NS_WINDOWWATCHER_CONTRACTID)); + nsCOMPtr<nsIDialogParamBlock> ioParamBlock( + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID)); + nsCOMPtr<nsIMutableArray> dlgArray( + do_CreateInstance(NS_ARRAY_CONTRACTID)); + NS_ENSURE_TRUE(windowWatcher && ioParamBlock && dlgArray, + NS_ERROR_FAILURE); + + ioParamBlock->SetObjects(dlgArray); + + nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service()); + NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = windowWatcher->OpenWindow( + nullptr, nsDependentCString(kProfileManagerURL), "_blank"_ns, + "centerscreen,chrome,modal,titlebar"_ns, ioParamBlock, + getter_AddRefs(newWindow)); + + NS_ENSURE_SUCCESS_LOG(rv, rv); + + rv = ioParamBlock->GetInt(0, &dialogReturn); + if (NS_FAILED(rv) || dialogReturn == nsIToolkitProfileService::exit) { + return NS_ERROR_ABORT; + } + + int32_t startOffline; + rv = ioParamBlock->GetInt(1, &startOffline); + offline = NS_SUCCEEDED(rv) && startOffline == 1; + + rv = dlgArray->QueryElementAt(0, NS_GET_IID(nsIFile), + getter_AddRefs(profD)); + NS_ENSURE_SUCCESS_LOG(rv, rv); + + rv = dlgArray->QueryElementAt(1, NS_GET_IID(nsIFile), + getter_AddRefs(profLD)); + NS_ENSURE_SUCCESS_LOG(rv, rv); + } + } + + if (offline) { + SaveToEnv("XRE_START_OFFLINE=1"); + } + + // User requested that we restart back into the profile manager. + if (dialogReturn == nsIToolkitProfileService::restart) { + SaveToEnv("XRE_RESTART_TO_PROFILE_MANAGER=1"); + SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1"); + } else { + MOZ_ASSERT(dialogReturn == nsIToolkitProfileService::launchWithProfile); + SaveFileToEnv("XRE_PROFILE_PATH", profD); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD); + SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER=1"); + } + + if (gRestartedByOS) { + // Re-add this argument when actually starting the application. + char** newArgv = + (char**)realloc(gRestartArgv, sizeof(char*) * (gRestartArgc + 2)); + NS_ENSURE_TRUE(newArgv, NS_ERROR_OUT_OF_MEMORY); + gRestartArgv = newArgv; + gRestartArgv[gRestartArgc++] = const_cast<char*>("-os-restarted"); + gRestartArgv[gRestartArgc] = nullptr; + } + + return LaunchChild(false); +} + +static bool gDoMigration = false; +static bool gDoProfileReset = false; +static nsCOMPtr<nsIToolkitProfile> gResetOldProfile; + +static nsresult LockProfile(nsINativeAppSupport* aNative, nsIFile* aRootDir, + nsIFile* aLocalDir, nsIToolkitProfile* aProfile, + nsIProfileLock** aResult) { + // If you close Firefox and very quickly reopen it, the old Firefox may + // still be closing down. Rather than immediately showing the + // "Firefox is running but is not responding" message, we spend a few + // seconds retrying first. + + static const int kLockRetrySeconds = 5; + static const int kLockRetrySleepMS = 100; + + nsresult rv; + nsCOMPtr<nsIProfileUnlocker> unlocker; + const TimeStamp start = TimeStamp::Now(); + do { + if (aProfile) { + rv = aProfile->Lock(getter_AddRefs(unlocker), aResult); + } else { + rv = NS_LockProfilePath(aRootDir, aLocalDir, getter_AddRefs(unlocker), + aResult); + } + if (NS_SUCCEEDED(rv)) { + StartupTimeline::Record(StartupTimeline::AFTER_PROFILE_LOCKED); + return NS_OK; + } + PR_Sleep(kLockRetrySleepMS); + } while (TimeStamp::Now() - start < + TimeDuration::FromSeconds(kLockRetrySeconds)); + + return ProfileLockedDialog(aRootDir, aLocalDir, unlocker, aNative, aResult); +} + +// Pick a profile. We need to end up with a profile root dir, local dir and +// potentially an nsIToolkitProfile instance. +// +// 1) check for --profile <path> +// 2) check for -P <name> +// 3) check for --ProfileManager +// 4) use the default profile, if there is one +// 5) if there are *no* profiles, set up profile-migration +// 6) display the profile-manager UI +static nsresult SelectProfile(nsToolkitProfileService* aProfileSvc, + nsINativeAppSupport* aNative, nsIFile** aRootDir, + nsIFile** aLocalDir, nsIToolkitProfile** aProfile, + bool* aWasDefaultSelection) { + StartupTimeline::Record(StartupTimeline::SELECT_PROFILE); + + nsresult rv; + + if (EnvHasValue("MOZ_RESET_PROFILE_RESTART")) { + gDoProfileReset = true; + gDoMigration = true; + SaveToEnv("MOZ_RESET_PROFILE_RESTART="); + // We only want to restore the previous session if the profile refresh was + // triggered by user. And if it was a user-triggered profile refresh + // through, say, the safeMode dialog or the troubleshooting page, the + // MOZ_RESET_PROFILE_RESTART env variable would be set. Hence we set + // MOZ_RESET_PROFILE_MIGRATE_SESSION here so that Firefox profile migrator + // would migrate old session data later. + SaveToEnv("MOZ_RESET_PROFILE_MIGRATE_SESSION=1"); + } + + // reset-profile and migration args need to be checked before any profiles are + // chosen below. + ArgResult ar = CheckArg("reset-profile"); + if (ar == ARG_FOUND) { + gDoProfileReset = true; + } + + ar = CheckArg("migration"); + if (ar == ARG_FOUND) { + gDoMigration = true; + } + +#if defined(XP_WIN) + // This arg is only used to indicate to telemetry that a profile refresh + // (reset+migration) was requested from the uninstaller, pass this along + // via an environment variable for simplicity. + ar = CheckArg("uninstaller-profile-refresh"); + if (ar == ARG_FOUND) { + SaveToEnv("MOZ_UNINSTALLER_PROFILE_REFRESH=1"); + } +#endif + + if (EnvHasValue("XRE_RESTART_TO_PROFILE_MANAGER")) { + return ShowProfileManager(aProfileSvc, aNative); + } + + // Ask the profile manager to select the profile directories to use. + bool didCreate = false; + rv = aProfileSvc->SelectStartupProfile(&gArgc, gArgv, gDoProfileReset, + aRootDir, aLocalDir, aProfile, + &didCreate, aWasDefaultSelection); + + if (rv == NS_ERROR_SHOW_PROFILE_MANAGER) { + return ShowProfileManager(aProfileSvc, aNative); + } + + NS_ENSURE_SUCCESS(rv, rv); + + if (didCreate) { + // For a fresh install, we would like to let users decide + // to do profile migration on their own later after using. + gDoProfileReset = false; + gDoMigration = false; + } + + if (gDoProfileReset && !*aProfile) { + NS_WARNING("Profile reset is only supported for named profiles."); + return NS_ERROR_ABORT; + } + + // No profile could be found. This generally shouldn't happen, a new profile + // should be created in all cases except for profile reset which is covered + // above, but just in case... + if (!*aRootDir) { + NS_WARNING("Failed to select or create profile."); + return NS_ERROR_ABORT; + } + + return NS_OK; +} + +#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE +struct FileWriteFunc : public JSONWriteFunc { + FILE* mFile; + explicit FileWriteFunc(FILE* aFile) : mFile(aFile) {} + + void Write(const Span<const char>& aStr) override { + fprintf(mFile, "%.*s", int(aStr.size()), aStr.data()); + } +}; + +static void SubmitDowngradeTelemetry(const nsCString& aLastVersion, + bool aHasSync, int32_t aButton) { + nsCOMPtr<nsIPrefService> prefSvc = + do_GetService("@mozilla.org/preferences-service;1"); + NS_ENSURE_TRUE_VOID(prefSvc); + + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefSvc); + NS_ENSURE_TRUE_VOID(prefBranch); + + bool enabled; + nsresult rv = + prefBranch->GetBoolPref(kPrefHealthReportUploadEnabled, &enabled); + NS_ENSURE_SUCCESS_VOID(rv); + if (!enabled) { + return; + } + + nsCString server; + rv = prefBranch->GetCharPref("toolkit.telemetry.server", server); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString clientId; + rv = prefBranch->GetCharPref("toolkit.telemetry.cachedClientID", clientId); + NS_ENSURE_SUCCESS_VOID(rv); + + rv = prefSvc->GetDefaultBranch(nullptr, getter_AddRefs(prefBranch)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCString channel("default"); + rv = prefBranch->GetCharPref("app.update.channel", channel); + NS_ENSURE_SUCCESS_VOID(rv); + + nsID uuid; + nsCOMPtr<nsIUUIDGenerator> uuidGen = + do_GetService("@mozilla.org/uuid-generator;1"); + NS_ENSURE_TRUE_VOID(uuidGen); + rv = uuidGen->GenerateUUIDInPlace(&uuid); + NS_ENSURE_SUCCESS_VOID(rv); + + char strid[NSID_LENGTH]; + uuid.ToProvidedString(strid); + + nsCString arch("null"); + nsCOMPtr<nsIPropertyBag2> sysInfo = + do_GetService("@mozilla.org/system-info;1"); + NS_ENSURE_TRUE_VOID(sysInfo); + sysInfo->GetPropertyAsACString(u"arch"_ns, arch); + + time_t now; + time(&now); + char date[sizeof "YYYY-MM-DDThh:mm:ss.000Z"]; + strftime(date, sizeof date, "%FT%T.000Z", gmtime(&now)); + + // NSID_LENGTH includes the trailing \0 and we also want to strip off the + // surrounding braces so the length becomes NSID_LENGTH - 3. + nsDependentCSubstring pingId(strid + 1, NSID_LENGTH - 3); + constexpr auto pingType = "downgrade"_ns; + + int32_t pos = aLastVersion.Find("_"); + if (pos == kNotFound) { + return; + } + + const nsDependentCSubstring lastVersion = Substring(aLastVersion, 0, pos); + const nsDependentCSubstring lastBuildId = + Substring(aLastVersion, pos + 1, 14); + + nsPrintfCString url("%s/submit/telemetry/%s/%s/%s/%s/%s/%s?v=%d", + server.get(), PromiseFlatCString(pingId).get(), + pingType.get(), (const char*)gAppData->name, + (const char*)gAppData->version, channel.get(), + (const char*)gAppData->buildID, + TELEMETRY_PING_FORMAT_VERSION); + + nsCOMPtr<nsIFile> pingFile; + rv = NS_GetSpecialDirectory(XRE_USER_APP_DATA_DIR, getter_AddRefs(pingFile)); + NS_ENSURE_SUCCESS_VOID(rv); + rv = pingFile->Append(u"Pending Pings"_ns); + NS_ENSURE_SUCCESS_VOID(rv); + rv = pingFile->Create(nsIFile::DIRECTORY_TYPE, 0755); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS) { + return; + } + rv = pingFile->Append(NS_ConvertUTF8toUTF16(pingId)); + NS_ENSURE_SUCCESS_VOID(rv); + + nsCOMPtr<nsIFile> pingSender; + rv = NS_GetSpecialDirectory(NS_GRE_BIN_DIR, getter_AddRefs(pingSender)); + NS_ENSURE_SUCCESS_VOID(rv); +# ifdef XP_WIN + pingSender->Append(u"pingsender.exe"_ns); +# else + pingSender->Append(u"pingsender"_ns); +# endif + + bool exists; + rv = pingSender->Exists(&exists); + NS_ENSURE_SUCCESS_VOID(rv); + if (!exists) { + return; + } + + FILE* file; + rv = pingFile->OpenANSIFileDesc("w", &file); + NS_ENSURE_SUCCESS_VOID(rv); + + JSONWriter w(MakeUnique<FileWriteFunc>(file)); + w.Start(); + { + w.StringProperty("type", + Span<const char>(pingType.Data(), pingType.Length())); + w.StringProperty("id", PromiseFlatCString(pingId)); + w.StringProperty("creationDate", MakeStringSpan(date)); + w.IntProperty("version", TELEMETRY_PING_FORMAT_VERSION); + w.StringProperty("clientId", clientId); + w.StartObjectProperty("application"); + { + w.StringProperty("architecture", arch); + w.StringProperty( + "buildId", + MakeStringSpan(static_cast<const char*>(gAppData->buildID))); + w.StringProperty( + "name", MakeStringSpan(static_cast<const char*>(gAppData->name))); + w.StringProperty( + "version", + MakeStringSpan(static_cast<const char*>(gAppData->version))); + w.StringProperty("displayVersion", + MOZ_STRINGIFY(MOZ_APP_VERSION_DISPLAY)); + w.StringProperty( + "vendor", MakeStringSpan(static_cast<const char*>(gAppData->vendor))); + w.StringProperty("platformVersion", gToolkitVersion); +# ifdef TARGET_XPCOM_ABI + w.StringProperty("xpcomAbi", TARGET_XPCOM_ABI); +# else + w.StringProperty("xpcomAbi", "unknown"); +# endif + w.StringProperty("channel", channel); + } + w.EndObject(); + w.StartObjectProperty("payload"); + { + w.StringProperty("lastVersion", PromiseFlatCString(lastVersion)); + w.StringProperty("lastBuildId", PromiseFlatCString(lastBuildId)); + w.BoolProperty("hasSync", aHasSync); + w.IntProperty("button", aButton); + } + w.EndObject(); + } + w.End(); + + fclose(file); + + PathString filePath = pingFile->NativePath(); + const filesystem::Path::value_type* args[2]; +# ifdef XP_WIN + nsString urlw = NS_ConvertUTF8toUTF16(url); + args[0] = urlw.get(); +# else + args[0] = url.get(); +# endif + args[1] = filePath.get(); + + nsCOMPtr<nsIProcess> process = + do_CreateInstance("@mozilla.org/process/util;1"); + NS_ENSURE_TRUE_VOID(process); + process->Init(pingSender); + process->SetStartHidden(true); + process->SetNoShell(true); + +# ifdef XP_WIN + process->Runw(false, args, 2); +# else + process->Run(false, args, 2); +# endif +} + +static const char kProfileDowngradeURL[] = + "chrome://mozapps/content/profile/profileDowngrade.xhtml"; + +static ReturnAbortOnError CheckDowngrade(nsIFile* aProfileDir, + nsINativeAppSupport* aNative, + nsIToolkitProfileService* aProfileSvc, + const nsCString& aLastVersion) { + int32_t result = 0; + nsresult rv; + + { + if (gfxPlatform::IsHeadless()) { + // TODO: make a way to turn off all dialogs when headless. + Output(true, + "This profile was last used with a newer version of this " + "application. Please create a new profile.\n"); + return NS_ERROR_ABORT; + } + + ScopedXPCOMStartup xpcom; + rv = xpcom.Initialize(); + NS_ENSURE_SUCCESS(rv, rv); + + rv = xpcom.SetWindowCreator(aNative); + NS_ENSURE_SUCCESS(rv, rv); + + { // extra scoping is needed so we release these components before xpcom + // shutdown + bool hasSync = false; + nsCOMPtr<nsIPrefService> prefSvc = + do_GetService("@mozilla.org/preferences-service;1"); + NS_ENSURE_TRUE(prefSvc, rv); + + nsCOMPtr<nsIFile> prefsFile; + rv = aProfileDir->Clone(getter_AddRefs(prefsFile)); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prefsFile->Append(u"prefs.js"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + rv = prefSvc->ReadUserPrefsFromFile(prefsFile); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> prefBranch = do_QueryInterface(prefSvc); + + rv = prefBranch->PrefHasUserValue("services.sync.username", &hasSync); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIWindowWatcher> windowWatcher = + do_GetService(NS_WINDOWWATCHER_CONTRACTID); + NS_ENSURE_TRUE(windowWatcher, NS_ERROR_ABORT); + + nsCOMPtr<nsIAppStartup> appStartup(components::AppStartup::Service()); + NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE); + + nsCOMPtr<nsIDialogParamBlock> paramBlock = + do_CreateInstance(NS_DIALOGPARAMBLOCK_CONTRACTID); + NS_ENSURE_TRUE(paramBlock, NS_ERROR_ABORT); + + uint8_t flags = 0; + if (hasSync) { + flags |= nsIToolkitProfileService::hasSync; + } + + paramBlock->SetInt(0, flags); + + nsCOMPtr<mozIDOMWindowProxy> newWindow; + rv = windowWatcher->OpenWindow( + nullptr, nsDependentCString(kProfileDowngradeURL), "_blank"_ns, + "centerscreen,chrome,modal,titlebar"_ns, paramBlock, + getter_AddRefs(newWindow)); + NS_ENSURE_SUCCESS(rv, rv); + + paramBlock->GetInt(1, &result); + + SubmitDowngradeTelemetry(aLastVersion, hasSync, result); + } + } + + if (result == nsIToolkitProfileService::createNewProfile) { + // Create a new profile and start it. + nsCString profileName; + profileName.AssignLiteral("default"); +# ifdef MOZ_DEDICATED_PROFILES + profileName.Append("-" MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)); +# endif + nsCOMPtr<nsIToolkitProfile> newProfile; + rv = aProfileSvc->CreateUniqueProfile(nullptr, profileName, + getter_AddRefs(newProfile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = aProfileSvc->SetDefaultProfile(newProfile); + NS_ENSURE_SUCCESS(rv, rv); + rv = aProfileSvc->Flush(); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> profD, profLD; + rv = newProfile->GetRootDir(getter_AddRefs(profD)); + NS_ENSURE_SUCCESS(rv, rv); + rv = newProfile->GetLocalDir(getter_AddRefs(profLD)); + NS_ENSURE_SUCCESS(rv, rv); + + SaveFileToEnv("XRE_PROFILE_PATH", profD); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", profLD); + + return LaunchChild(false); + } + + // Cancel + return NS_ERROR_ABORT; +} +#endif + +/** + * Extracts the various parts of a compatibility version string. + * + * Compatibility versions are of the form + * "<appversion>_<appbuildid>/<platformbuildid>". The toolkit version comparator + * can only handle 32-bit numbers and in the normal case build IDs are larger + * than this. So if the build ID is numeric we split it into two version parts. + */ +static void ExtractCompatVersionInfo(const nsACString& aCompatVersion, + nsACString& aAppVersion, + nsACString& aAppBuildID) { + int32_t underscorePos = aCompatVersion.FindChar('_'); + int32_t slashPos = aCompatVersion.FindChar('/'); + + if (underscorePos == kNotFound || slashPos == kNotFound || + slashPos < underscorePos) { + NS_WARNING( + "compatibility.ini Version string does not match the expected format."); + + // Fall back to just using the entire string as the version. + aAppVersion = aCompatVersion; + aAppBuildID.Truncate(0); + return; + } + + aAppVersion = Substring(aCompatVersion, 0, underscorePos); + aAppBuildID = Substring(aCompatVersion, underscorePos + 1, + slashPos - (underscorePos + 1)); +} + +/** + * Compares the provided compatibility versions. Returns 0 if they match, + * < 0 if the new version is considered an upgrade from the old version and + * > 0 if the new version is considered a downgrade from the old version. + */ +int32_t CompareCompatVersions(const nsACString& aOldCompatVersion, + const nsACString& aNewCompatVersion) { + // Hardcode the case where the last run was in safe mode (Bug 1556612). We + // cannot tell if this is a downgrade or not so just assume it isn't and let + // the user proceed. + if (aOldCompatVersion.EqualsLiteral("Safe Mode")) { + return -1; + } + + // Extract the major version part from the version string and only use that + // for version comparison. + int32_t index = aOldCompatVersion.FindChar('.'); + const nsACString& oldMajorVersion = Substring( + aOldCompatVersion, 0, index < 0 ? aOldCompatVersion.Length() : index); + index = aNewCompatVersion.FindChar('.'); + const nsACString& newMajorVersion = Substring( + aNewCompatVersion, 0, index < 0 ? aNewCompatVersion.Length() : index); + + return CompareVersions(PromiseFlatCString(oldMajorVersion).get(), + PromiseFlatCString(newMajorVersion).get()); +} + +/** + * Checks the compatibility.ini file to see if we have updated our application + * or otherwise invalidated our caches. If the application has been updated, + * we return false; otherwise, we return true. + * + * We also write the status of the caches (valid/invalid) into the return param + * aCachesOK. The aCachesOK is always invalid if the application has been + * updated. + * + * Finally, aIsDowngrade is set to true if the current application is older + * than that previously used by the profile. + */ +static bool CheckCompatibility(nsIFile* aProfileDir, const nsCString& aVersion, + const nsCString& aOSABI, nsIFile* aXULRunnerDir, + nsIFile* aAppDir, nsIFile* aFlagFile, + bool* aCachesOK, bool* aIsDowngrade, + nsCString& aLastVersion) { + *aCachesOK = false; + *aIsDowngrade = false; + gLastAppVersion.SetIsVoid(true); + gLastAppBuildID.SetIsVoid(true); + + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) return false; + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsINIParser parser; + nsresult rv = parser.Init(file); + if (NS_FAILED(rv)) return false; + + rv = parser.GetString("Compatibility", "LastVersion", aLastVersion); + if (NS_FAILED(rv)) { + return false; + } + + if (!aLastVersion.Equals(aVersion)) { + // The version is not the same. Whether it's a downgrade depends on an + // actual comparison: + *aIsDowngrade = 0 < CompareCompatVersions(aLastVersion, aVersion); + ExtractCompatVersionInfo(aLastVersion, gLastAppVersion, gLastAppBuildID); + return false; + } + + // If we get here, the version matched, but there may still be other + // differences between us and the build that the profile last ran under. + + gLastAppVersion.Assign(gAppData->version); + gLastAppBuildID.Assign(gAppData->buildID); + + nsAutoCString buf; + rv = parser.GetString("Compatibility", "LastOSABI", buf); + if (NS_FAILED(rv) || !aOSABI.Equals(buf)) return false; + + rv = parser.GetString("Compatibility", "LastPlatformDir", buf); + if (NS_FAILED(rv)) return false; + + nsCOMPtr<nsIFile> lf; + rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lf)); + if (NS_FAILED(rv)) return false; + + rv = lf->SetPersistentDescriptor(buf); + if (NS_FAILED(rv)) return false; + + bool eq; + rv = lf->Equals(aXULRunnerDir, &eq); + if (NS_FAILED(rv) || !eq) return false; + + if (aAppDir) { + rv = parser.GetString("Compatibility", "LastAppDir", buf); + if (NS_FAILED(rv)) return false; + + rv = NS_NewNativeLocalFile(""_ns, false, getter_AddRefs(lf)); + if (NS_FAILED(rv)) return false; + + rv = lf->SetPersistentDescriptor(buf); + if (NS_FAILED(rv)) return false; + + rv = lf->Equals(aAppDir, &eq); + if (NS_FAILED(rv) || !eq) return false; + } + + // If we see this flag, caches are invalid. + rv = parser.GetString("Compatibility", "InvalidateCaches", buf); + *aCachesOK = (NS_FAILED(rv) || !buf.EqualsLiteral("1")); + + bool purgeCaches = false; + if (aFlagFile && NS_SUCCEEDED(aFlagFile->Exists(&purgeCaches)) && + purgeCaches) { + *aCachesOK = false; + } + + return true; +} + +void BuildCompatVersion(const char* aAppVersion, const char* aAppBuildID, + const char* aToolkitBuildID, nsACString& aBuf) { + aBuf.Assign(aAppVersion); + aBuf.Append('_'); + aBuf.Append(aAppBuildID); + aBuf.Append('/'); + aBuf.Append(aToolkitBuildID); +} + +static void BuildVersion(nsCString& aBuf) { + BuildCompatVersion(gAppData->version, gAppData->buildID, gToolkitBuildID, + aBuf); +} + +static void WriteVersion(nsIFile* aProfileDir, const nsCString& aVersion, + const nsCString& aOSABI, nsIFile* aXULRunnerDir, + nsIFile* aAppDir, bool invalidateCache) { + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) return; + file->AppendNative(FILE_COMPATIBILITY_INFO); + + nsAutoCString platformDir; + Unused << aXULRunnerDir->GetPersistentDescriptor(platformDir); + + nsAutoCString appDir; + if (aAppDir) Unused << aAppDir->GetPersistentDescriptor(appDir); + + PRFileDesc* fd; + nsresult rv = file->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_TRUNCATE, + 0600, &fd); + if (NS_FAILED(rv)) { + NS_ERROR("could not create output stream"); + return; + } + + static const char kHeader[] = "[Compatibility]" NS_LINEBREAK "LastVersion="; + + PR_Write(fd, kHeader, sizeof(kHeader) - 1); + PR_Write(fd, aVersion.get(), aVersion.Length()); + + static const char kOSABIHeader[] = NS_LINEBREAK "LastOSABI="; + PR_Write(fd, kOSABIHeader, sizeof(kOSABIHeader) - 1); + PR_Write(fd, aOSABI.get(), aOSABI.Length()); + + static const char kPlatformDirHeader[] = NS_LINEBREAK "LastPlatformDir="; + + PR_Write(fd, kPlatformDirHeader, sizeof(kPlatformDirHeader) - 1); + PR_Write(fd, platformDir.get(), platformDir.Length()); + + static const char kAppDirHeader[] = NS_LINEBREAK "LastAppDir="; + if (aAppDir) { + PR_Write(fd, kAppDirHeader, sizeof(kAppDirHeader) - 1); + PR_Write(fd, appDir.get(), appDir.Length()); + } + + static const char kInvalidationHeader[] = NS_LINEBREAK "InvalidateCaches=1"; + if (invalidateCache) + PR_Write(fd, kInvalidationHeader, sizeof(kInvalidationHeader) - 1); + + static const char kNL[] = NS_LINEBREAK; + PR_Write(fd, kNL, sizeof(kNL) - 1); + + PR_Close(fd); +} + +/** + * Returns true if the startup cache file was successfully removed. + * Returns false if file->Clone fails at any point (OOM) or if unable + * to remove the startup cache file. Note in particular the return value + * is unaffected by a failure to remove extensions.ini + */ +static bool RemoveComponentRegistries(nsIFile* aProfileDir, + nsIFile* aLocalProfileDir, + bool aRemoveEMFiles) { + nsCOMPtr<nsIFile> file; + aProfileDir->Clone(getter_AddRefs(file)); + if (!file) return false; + + if (aRemoveEMFiles) { + file->SetNativeLeafName("extensions.ini"_ns); + file->Remove(false); + } + + aLocalProfileDir->Clone(getter_AddRefs(file)); + if (!file) return false; + +#if defined(XP_UNIX) || defined(XP_BEOS) +# define PLATFORM_FASL_SUFFIX ".mfasl" +#elif defined(XP_WIN) +# define PLATFORM_FASL_SUFFIX ".mfl" +#endif + + file->AppendNative(nsLiteralCString("XUL" PLATFORM_FASL_SUFFIX)); + file->Remove(false); + + file->SetNativeLeafName(nsLiteralCString("XPC" PLATFORM_FASL_SUFFIX)); + file->Remove(false); + + file->SetNativeLeafName("startupCache"_ns); + nsresult rv = file->Remove(true); + return NS_SUCCEEDED(rv) || rv == NS_ERROR_FILE_TARGET_DOES_NOT_EXIST || + rv == NS_ERROR_FILE_NOT_FOUND; +} + +// When we first initialize the crash reporter we don't have a profile, +// so we set the minidump path to $TEMP. Once we have a profile, +// we set it to $PROFILE/minidumps, creating the directory +// if needed. +static void MakeOrSetMinidumpPath(nsIFile* profD) { + nsCOMPtr<nsIFile> dumpD; + profD->Clone(getter_AddRefs(dumpD)); + + if (dumpD) { + bool fileExists; + // XXX: do some more error checking here + dumpD->Append(u"minidumps"_ns); + dumpD->Exists(&fileExists); + if (!fileExists) { + nsresult rv = dumpD->Create(nsIFile::DIRECTORY_TYPE, 0700); + NS_ENSURE_SUCCESS_VOID(rv); + } + + nsAutoString pathStr; + if (NS_SUCCEEDED(dumpD->GetPath(pathStr))) + CrashReporter::SetMinidumpPath(pathStr); + } +} + +const XREAppData* gAppData = nullptr; + +/** + * NSPR will search for the "nspr_use_zone_allocator" symbol throughout + * the process and use it to determine whether the application defines its own + * memory allocator or not. + * + * Since most applications (e.g. Firefox and Thunderbird) don't use any special + * allocators and therefore don't define this symbol, NSPR must search the + * entire process, which reduces startup performance. + * + * By defining the symbol here, we can avoid the wasted lookup and hopefully + * improve startup performance. + */ +NS_VISIBILITY_DEFAULT PRBool nspr_use_zone_allocator = PR_FALSE; + +#ifdef CAIRO_HAS_DWRITE_FONT + +# include <dwrite.h> +# include "nsWindowsHelpers.h" + +# ifdef DEBUG_DWRITE_STARTUP + +# define LOGREGISTRY(msg) LogRegistryEvent(msg) + +// for use when monitoring process +static void LogRegistryEvent(const wchar_t* msg) { + HKEY dummyKey; + HRESULT hr; + wchar_t buf[512]; + + wsprintf(buf, L" log %s", msg); + hr = RegOpenKeyEx(HKEY_LOCAL_MACHINE, buf, 0, KEY_READ, &dummyKey); + if (SUCCEEDED(hr)) { + RegCloseKey(dummyKey); + } +} +# else + +# define LOGREGISTRY(msg) + +# endif + +static DWORD WINAPI InitDwriteBG(LPVOID lpdwThreadParam) { + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_BEGIN); + LOGREGISTRY(L"loading dwrite.dll"); + HMODULE dwdll = LoadLibrarySystem32(L"dwrite.dll"); + if (dwdll) { + decltype(DWriteCreateFactory)* createDWriteFactory = + (decltype(DWriteCreateFactory)*)GetProcAddress(dwdll, + "DWriteCreateFactory"); + if (createDWriteFactory) { + LOGREGISTRY(L"creating dwrite factory"); + IDWriteFactory* factory; + HRESULT hr = createDWriteFactory(DWRITE_FACTORY_TYPE_SHARED, + __uuidof(IDWriteFactory), + reinterpret_cast<IUnknown**>(&factory)); + if (SUCCEEDED(hr)) { + LOGREGISTRY(L"dwrite factory done"); + factory->Release(); + LOGREGISTRY(L"freed factory"); + } else { + LOGREGISTRY(L"failed to create factory"); + } + } + } + SetThreadPriority(GetCurrentThread(), THREAD_MODE_BACKGROUND_END); + return 0; +} +#endif + +#ifdef USE_GLX_TEST +bool fire_glxtest_process(); +#endif + +#include "GeckoProfiler.h" + +// Encapsulates startup and shutdown state for XRE_main +class XREMain { + public: + XREMain() + : mStartOffline(false), + mShuttingDown(false) +#ifdef MOZ_HAS_REMOTE + , + mDisableRemoteClient(false), + mDisableRemoteServer(false) +#endif +#if defined(MOZ_WIDGET_GTK) + , + mGdkDisplay(nullptr) +#endif + {}; + + ~XREMain() { + mScopedXPCOM = nullptr; + mAppData = nullptr; + } + + int XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig); + int XRE_mainInit(bool* aExitFlag); + int XRE_mainStartup(bool* aExitFlag); + nsresult XRE_mainRun(); + + Result<bool, nsresult> CheckLastStartupWasCrash(); + + nsCOMPtr<nsINativeAppSupport> mNativeApp; + RefPtr<nsToolkitProfileService> mProfileSvc; + nsCOMPtr<nsIFile> mProfD; + nsCOMPtr<nsIFile> mProfLD; + nsCOMPtr<nsIProfileLock> mProfileLock; +#if defined(MOZ_HAS_REMOTE) + RefPtr<nsRemoteService> mRemoteService; +#endif + + UniquePtr<ScopedXPCOMStartup> mScopedXPCOM; + UniquePtr<XREAppData> mAppData; + + nsXREDirProvider mDirProvider; + nsAutoCString mDesktopStartupID; + + bool mStartOffline; + bool mShuttingDown; +#if defined(MOZ_HAS_REMOTE) + bool mDisableRemoteClient; + bool mDisableRemoteServer; +#endif + +#if defined(MOZ_WIDGET_GTK) + GdkDisplay* mGdkDisplay; +#endif +}; + +#if defined(XP_UNIX) && !defined(ANDROID) +static SmprintfPointer FormatUid(uid_t aId) { + if (const auto pw = getpwuid(aId)) { + return mozilla::Smprintf("%s", pw->pw_name); + } + return mozilla::Smprintf("uid %d", static_cast<int>(aId)); +} + +// Bug 1323302: refuse to run under sudo or similar. +static bool CheckForUserMismatch() { + static char const* const kVars[] = { + "HOME", +# ifdef MOZ_WIDGET_GTK + "XDG_RUNTIME_DIR", +# endif +# ifdef MOZ_X11 + "XAUTHORITY", +# endif + }; + + const uid_t euid = geteuid(); + if (euid != 0) { + // On Linux it's possible to have superuser capabilities with a + // nonzero uid, but anyone who knows enough to make that happen + // probably knows enough to debug the resulting problems. + // Otherwise, a non-root user can't cause the problems we're + // concerned about. + return false; + } + + for (const auto var : kVars) { + if (const auto path = PR_GetEnv(var)) { + struct stat st; + if (stat(path, &st) == 0) { + if (st.st_uid != euid) { + const auto owner = FormatUid(st.st_uid); + Output(true, + "Running " MOZ_APP_DISPLAYNAME + " as root in a regular" + " user's session is not supported. ($%s is %s which is" + " owned by %s.)\n", + var, path, owner.get()); + return true; + } + } + } + } + return false; +} +#else // !XP_UNIX || ANDROID +static bool CheckForUserMismatch() { return false; } +#endif + +static void IncreaseDescriptorLimits() { +#ifdef XP_UNIX + // Increase the fd limit to accomodate IPC resources like shared memory. + // See also the Darwin case in config/external/nspr/pr/moz.build + static const rlim_t kFDs = 4096; + struct rlimit rlim; + + if (getrlimit(RLIMIT_NOFILE, &rlim) != 0) { + Output(false, "getrlimit: %s\n", strerror(errno)); + return; + } + // Don't decrease the limit if it's already high enough, but don't + // try to go over the hard limit. (RLIM_INFINITY isn't required to + // be the numerically largest rlim_t, so don't assume that.) + if (rlim.rlim_cur != RLIM_INFINITY && rlim.rlim_cur < kFDs && + rlim.rlim_cur < rlim.rlim_max) { + if (rlim.rlim_max != RLIM_INFINITY && rlim.rlim_max < kFDs) { + rlim.rlim_cur = rlim.rlim_max; + } else { + rlim.rlim_cur = kFDs; + } + if (setrlimit(RLIMIT_NOFILE, &rlim) != 0) { + Output(false, "setrlimit: %s\n", strerror(errno)); + } + } +#endif +} + +/* + * XRE_mainInit - Initial setup and command line parameter processing. + * Main() will exit early if either return value != 0 or if aExitFlag is + * true. + */ +int XREMain::XRE_mainInit(bool* aExitFlag) { + if (!aExitFlag) return 1; + *aExitFlag = false; + + atexit(UnexpectedExit); + auto expectedShutdown = mozilla::MakeScopeExit([&] { MozExpectedExit(); }); + + StartupTimeline::Record(StartupTimeline::MAIN); + + if (CheckForUserMismatch()) { + return 1; + } + +#ifdef XP_MACOSX + mozilla::MacAutoreleasePool pool; + + DisableAppNap(); +#endif + +#ifndef ANDROID + if (PR_GetEnv("MOZ_RUN_GTEST") +# ifdef FUZZING + || PR_GetEnv("FUZZER") +# endif + ) { + // Enable headless mode and assert that it worked, since gfxPlatform + // uses a static bool set after the first call to `IsHeadless`. + // Note: Android gtests seem to require an Activity and fail to start + // with headless mode enabled. + PR_SetEnv("MOZ_HEADLESS=1"); + MOZ_ASSERT(gfxPlatform::IsHeadless()); + } +#endif // ANDROID + + if (PR_GetEnv("MOZ_CHAOSMODE")) { + ChaosFeature feature = ChaosFeature::Any; + long featureInt = strtol(PR_GetEnv("MOZ_CHAOSMODE"), nullptr, 16); + if (featureInt) { + // NOTE: MOZ_CHAOSMODE=0 or a non-hex value maps to Any feature. + feature = static_cast<ChaosFeature>(featureInt); + } + ChaosMode::SetChaosFeature(feature); + } + + if (CheckArgExists("fxr")) { + gFxREmbedded = true; + } + + if (ChaosMode::isActive(ChaosFeature::Any)) { + printf_stderr( + "*** You are running in chaos test mode. See ChaosMode.h. ***\n"); + } + + if (CheckArg("headless") || CheckArgExists("screenshot")) { + PR_SetEnv("MOZ_HEADLESS=1"); + } + + if (gfxPlatform::IsHeadless()) { +#if defined(XP_WIN) || defined(MOZ_WIDGET_GTK) || defined(XP_MACOSX) + printf_stderr("*** You are running in headless mode.\n"); +#else + Output( + true, + "Error: headless mode is not currently supported on this platform.\n"); + return 1; +#endif + +#ifdef XP_MACOSX + // To avoid taking focus when running in headless mode immediately + // transition Firefox to a background application. + ProcessSerialNumber psn = {0, kCurrentProcess}; + OSStatus transformStatus = + TransformProcessType(&psn, kProcessTransformToBackgroundApplication); + if (transformStatus != noErr) { + NS_ERROR("Failed to make process a background application."); + return 1; + } +#endif + } + + nsresult rv; + ArgResult ar; + +#ifdef DEBUG + if (PR_GetEnv("XRE_MAIN_BREAK")) NS_BREAK(); +#endif + + IncreaseDescriptorLimits(); + +#ifdef USE_GLX_TEST + // bug 639842 - it's very important to fire this process BEFORE we set up + // error handling. indeed, this process is expected to be crashy, and we + // don't want the user to see its crashes. That's the whole reason for + // doing this in a separate process. + // + // This call will cause a fork and the fork will terminate itself separately + // from the usual shutdown sequence + fire_glxtest_process(); +#endif + + SetupErrorHandling(gArgv[0]); + +#ifdef CAIRO_HAS_DWRITE_FONT + { + // Bug 602792 - when DWriteCreateFactory is called the dwrite client dll + // starts the FntCache service if it isn't already running (it's set + // to manual startup by default in Windows 7 RTM). Subsequent DirectWrite + // calls cause the IDWriteFactory object to communicate with the FntCache + // service with a timeout; if there's no response after the timeout, the + // DirectWrite client library will assume the service isn't around and do + // manual font file I/O on _all_ system fonts. To avoid this, load the + // dwrite library and create a factory as early as possible so that the + // FntCache service is ready by the time it's needed. + + CreateThread(nullptr, 0, &InitDwriteBG, nullptr, 0, nullptr); + } +#endif + +#ifdef XP_UNIX + const char* home = PR_GetEnv("HOME"); + if (!home || !*home) { + struct passwd* pw = getpwuid(geteuid()); + if (!pw || !pw->pw_dir) { + Output(true, "Could not determine HOME directory"); + return 1; + } + SaveWordToEnv("HOME", nsDependentCString(pw->pw_dir)); + } +#endif + +#ifdef MOZ_ACCESSIBILITY_ATK + // Suppress atk-bridge init at startup, until mozilla accessibility is + // initialized. This works after gnome 2.24.2. + SaveToEnv("NO_AT_BRIDGE=1"); +#endif + + // Check for application.ini overrides + const char* override = nullptr; + ar = CheckArg("override", &override); + if (ar == ARG_BAD) { + Output(true, "Incorrect number of arguments passed to --override"); + return 1; + } + if (ar == ARG_FOUND) { + nsCOMPtr<nsIFile> overrideLF; + rv = XRE_GetFileFromPath(override, getter_AddRefs(overrideLF)); + if (NS_FAILED(rv)) { + Output(true, "Error: unrecognized override.ini path.\n"); + return 1; + } + + rv = XRE_ParseAppData(overrideLF, *mAppData); + if (NS_FAILED(rv)) { + Output(true, "Couldn't read override.ini"); + return 1; + } + } + + // Check sanity and correctness of app data. + + if (!mAppData->name) { + Output(true, "Error: App:Name not specified in application.ini\n"); + return 1; + } + if (!mAppData->buildID) { + Output(true, "Error: App:BuildID not specified in application.ini\n"); + return 1; + } + + // XXX Originally ScopedLogging was here? Now it's in XRE_main above + // XRE_mainInit. + + if (!mAppData->minVersion) { + Output(true, "Error: Gecko:MinVersion not specified in application.ini\n"); + return 1; + } + + if (!mAppData->maxVersion) { + // If no maxVersion is specified, we assume the app is only compatible + // with the initial preview release. Do not increment this number ever! + mAppData->maxVersion = "1.*"; + } + + if (mozilla::Version(mAppData->minVersion) > gToolkitVersion || + mozilla::Version(mAppData->maxVersion) < gToolkitVersion) { + Output(true, + "Error: Platform version '%s' is not compatible with\n" + "minVersion >= %s\nmaxVersion <= %s\n", + (const char*)gToolkitVersion, (const char*)mAppData->minVersion, + (const char*)mAppData->maxVersion); + return 1; + } + + rv = mDirProvider.Initialize(mAppData->directory, mAppData->xreDirectory); + if (NS_FAILED(rv)) return 1; + + if (EnvHasValue("MOZ_CRASHREPORTER")) { + mAppData->flags |= NS_XRE_ENABLE_CRASH_REPORTER; + } + + nsCOMPtr<nsIFile> xreBinDirectory; + xreBinDirectory = mDirProvider.GetGREBinDir(); + + if ((mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) && + NS_SUCCEEDED(CrashReporter::SetExceptionHandler(xreBinDirectory))) { + nsCOMPtr<nsIFile> file; + rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + CrashReporter::SetUserAppDataDirectory(file); + } + if (mAppData->crashReporterURL) + CrashReporter::SetServerURL( + nsDependentCString(mAppData->crashReporterURL)); + + // We overwrite this once we finish starting up. + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::StartupCrash, + true); + + // pass some basic info from the app data + if (mAppData->vendor) + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Vendor, + nsDependentCString(mAppData->vendor)); + if (mAppData->name) + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::ProductName, + nsDependentCString(mAppData->name)); + if (mAppData->ID) + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::ProductID, + nsDependentCString(mAppData->ID)); + if (mAppData->version) + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::Version, + nsDependentCString(mAppData->version)); + if (mAppData->buildID) + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::BuildID, + nsDependentCString(mAppData->buildID)); + + nsDependentCString releaseChannel(MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL)); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ReleaseChannel, releaseChannel); +#ifdef MOZ_LINKER + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::CrashAddressLikelyWrong, + IsSignalHandlingBroken()); +#endif + +#ifdef XP_WIN + nsAutoString appInitDLLs; + if (widget::WinUtils::GetAppInitDLLs(appInitDLLs)) { + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::AppInitDLLs, + NS_ConvertUTF16toUTF8(appInitDLLs)); + } +#endif + + CrashReporter::SetRestartArgs(gArgc, gArgv); + + // annotate other data (user id etc) + nsCOMPtr<nsIFile> userAppDataDir; + if (NS_SUCCEEDED(mDirProvider.GetUserAppDataDirectory( + getter_AddRefs(userAppDataDir)))) { + CrashReporter::SetupExtraData(userAppDataDir, + nsDependentCString(mAppData->buildID)); + + // see if we have a crashreporter-override.ini in the application + // directory + nsCOMPtr<nsIFile> overrideini; + if (NS_SUCCEEDED( + mDirProvider.GetAppDir()->Clone(getter_AddRefs(overrideini))) && + NS_SUCCEEDED( + overrideini->AppendNative("crashreporter-override.ini"_ns))) { +#ifdef XP_WIN + nsAutoString overridePathW; + overrideini->GetPath(overridePathW); + NS_ConvertUTF16toUTF8 overridePath(overridePathW); +#else + nsAutoCString overridePath; + overrideini->GetNativePath(overridePath); +#endif + + SaveWordToEnv("MOZ_CRASHREPORTER_STRINGS_OVERRIDE", overridePath); + } + } + } + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + if (mAppData->sandboxBrokerServices) { + SandboxBroker::Initialize(mAppData->sandboxBrokerServices); + } else { +# if defined(MOZ_SANDBOX) + // If we're sandboxing content and we fail to initialize, then crashing here + // seems like the sensible option. + if (BrowserTabsRemoteAutostart()) { + MOZ_CRASH("Failed to initialize broker services, can't continue."); + } +# endif + // Otherwise just warn for the moment, as most things will work. + NS_WARNING( + "Failed to initialize broker services, sandboxed processes will " + "fail to start."); + } + if (mAppData->sandboxPermissionsService) { + SandboxPermissions::Initialize(mAppData->sandboxPermissionsService, + nullptr); + } +#endif + +#ifdef XP_MACOSX + // Set up ability to respond to system (Apple) events. This must occur before + // ProcessUpdates to ensure that links clicked in external applications aren't + // lost when updates are pending. + SetupMacApplicationDelegate(); + + if (EnvHasValue("MOZ_LAUNCHED_CHILD")) { + // This is needed, on relaunch, to force the OS to use the "Cocoa Dock + // API". Otherwise the call to ReceiveNextEvent() below will make it + // use the "Carbon Dock API". For more info see bmo bug 377166. + EnsureUseCocoaDockAPI(); + + // When the app relaunches, the original process exits. This causes + // the dock tile to stop bouncing, lose the "running" triangle, and + // if the tile does not permanently reside in the Dock, even disappear. + // This can be confusing to the user, who is expecting the app to launch. + // Calling ReceiveNextEvent without requesting any event is enough to + // cause a dock tile for the child process to appear. + const EventTypeSpec kFakeEventList[] = {{INT_MAX, INT_MAX}}; + EventRef event; + ::ReceiveNextEvent(GetEventTypeCount(kFakeEventList), kFakeEventList, + kEventDurationNoWait, false, &event); + } + + if (CheckArg("foreground")) { + // The original process communicates that it was in the foreground by + // adding this argument. This new process, which is taking over for + // the old one, should make itself the active application. + ProcessSerialNumber psn; + if (::GetCurrentProcess(&psn) == noErr) ::SetFrontProcess(&psn); + } +#endif + + SaveToEnv("MOZ_LAUNCHED_CHILD="); + + // On Windows, the -os-restarted command line switch lets us know when we are + // restarted via RegisterApplicationRestart. May be used for other OSes later. + if (CheckArg("os-restarted", nullptr, CheckArgFlag::RemoveArg) == ARG_FOUND) { + gRestartedByOS = true; + } + + gRestartArgc = gArgc; + gRestartArgv = + (char**)malloc(sizeof(char*) * (gArgc + 1 + (override ? 2 : 0))); + if (!gRestartArgv) { + return 1; + } + + int i; + for (i = 0; i < gArgc; ++i) { + gRestartArgv[i] = gArgv[i]; + } + + // Add the -override argument back (it is removed automatically be CheckArg) + // if there is one + if (override) { + gRestartArgv[gRestartArgc++] = const_cast<char*>("-override"); + gRestartArgv[gRestartArgc++] = const_cast<char*>(override); + } + + gRestartArgv[gRestartArgc] = nullptr; + + Maybe<bool> safeModeRequested = IsSafeModeRequested(gArgc, gArgv); + if (!safeModeRequested) { + return 1; + } + + gSafeMode = safeModeRequested.value(); + +#ifdef XP_WIN + { + // Add CPU microcode version to the crash report as "CPUMicrocodeVersion". + // It feels like this code may belong in nsSystemInfo instead. + int cpuUpdateRevision = -1; + HKEY key; + static const WCHAR keyName[] = + L"HARDWARE\\DESCRIPTION\\System\\CentralProcessor\\0"; + + if (RegOpenKeyEx(HKEY_LOCAL_MACHINE, keyName, 0, KEY_QUERY_VALUE, &key) == + ERROR_SUCCESS) { + DWORD updateRevision[2]; + DWORD len = sizeof(updateRevision); + DWORD vtype; + + // Windows 7 uses "Update Signature", 8 uses "Update Revision". + // For AMD CPUs, "CurrentPatchLevel" is sometimes used. + // Take the first one we find. + LPCWSTR choices[] = {L"Update Signature", L"Update Revision", + L"CurrentPatchLevel"}; + for (size_t oneChoice = 0; oneChoice < ArrayLength(choices); + oneChoice++) { + if (RegQueryValueExW(key, choices[oneChoice], 0, &vtype, + reinterpret_cast<LPBYTE>(updateRevision), + &len) == ERROR_SUCCESS) { + if (vtype == REG_BINARY && len == sizeof(updateRevision)) { + // The first word is unused + cpuUpdateRevision = static_cast<int>(updateRevision[1]); + break; + } else if (vtype == REG_DWORD && len == sizeof(updateRevision[0])) { + cpuUpdateRevision = static_cast<int>(updateRevision[0]); + break; + } + } + } + } + + if (cpuUpdateRevision > 0) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::CPUMicrocodeVersion, + nsPrintfCString("0x%x", cpuUpdateRevision)); + } + } +#endif + + CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::SafeMode, + gSafeMode); + +#if defined(MOZ_HAS_REMOTE) + // Handle --no-remote and --new-instance command line arguments. Setup + // the environment to better accommodate other components and various + // restart scenarios. + ar = CheckArg("no-remote"); + if (ar == ARG_FOUND || EnvHasValue("MOZ_NO_REMOTE")) { + mDisableRemoteClient = true; + mDisableRemoteServer = true; + if (!EnvHasValue("MOZ_NO_REMOTE")) { + SaveToEnv("MOZ_NO_REMOTE=1"); + } + } + + ar = CheckArg("new-instance"); + if (ar == ARG_FOUND || EnvHasValue("MOZ_NEW_INSTANCE")) { + mDisableRemoteClient = true; + } +#else + // These arguments do nothing in platforms with no remoting support but we + // should remove them from the command line anyway. + CheckArg("no-remote"); + CheckArg("new-instance"); +#endif + + ar = CheckArg("offline"); + if (ar || EnvHasValue("XRE_START_OFFLINE")) { + mStartOffline = true; + } + + // Handle --help, --full-version and --version command line arguments. + // They should return quickly, so we deal with them here. + if (CheckArg("h") || CheckArg("help") || CheckArg("?")) { + DumpHelp(); + *aExitFlag = true; + return 0; + } + + if (CheckArg("v") || CheckArg("version")) { + DumpVersion(); + *aExitFlag = true; + return 0; + } + + if (CheckArg("full-version")) { + DumpFullVersion(); + *aExitFlag = true; + return 0; + } + + rv = XRE_InitCommandLine(gArgc, gArgv); + NS_ENSURE_SUCCESS(rv, 1); + + return 0; +} + +#ifdef XP_WIN +static bool QueryOneWMIProperty(IWbemServices* aServices, + const wchar_t* aWMIClass, + const wchar_t* aProperty, VARIANT* aResult) { + RefPtr<IEnumWbemClassObject> enumerator; + + _bstr_t query(L"SELECT * FROM "); + query += _bstr_t(aWMIClass); + + HRESULT hr = aServices->ExecQuery( + _bstr_t(L"WQL"), query, + WBEM_FLAG_FORWARD_ONLY | WBEM_FLAG_RETURN_IMMEDIATELY, nullptr, + getter_AddRefs(enumerator)); + + if (FAILED(hr) || !enumerator) { + return false; + } + + RefPtr<IWbemClassObject> classObject; + ULONG results; + + hr = + enumerator->Next(WBEM_INFINITE, 1, getter_AddRefs(classObject), &results); + + if (FAILED(hr) || results == 0) { + return false; + } + + hr = classObject->Get(aProperty, 0, aResult, 0, 0); + + return SUCCEEDED(hr); +} + +/** + * Uses WMI to read some information that may be useful for diagnosing + * crashes. This function is best-effort; failures shouldn't burden the + * caller. COM must be initialized before calling. + */ + +static const char kMemoryErrorCorrectionValues[][15] = { + "Reserved", // 0 + "Other", // 1 + "Unknown", // 2 + "None", // 3 + "Parity", // 4 + "Single-bit ECC", // 5 + "Multi-bit ECC", // 6 + "CRC" // 7 +}; + +static void AnnotateWMIData() { + RefPtr<IWbemLocator> locator; + + HRESULT hr = + CoCreateInstance(CLSID_WbemLocator, nullptr, CLSCTX_INPROC_SERVER, + IID_IWbemLocator, getter_AddRefs(locator)); + + if (FAILED(hr)) { + return; + } + + RefPtr<IWbemServices> services; + + hr = + locator->ConnectServer(_bstr_t(L"ROOT\\CIMV2"), nullptr, nullptr, nullptr, + 0, nullptr, nullptr, getter_AddRefs(services)); + + if (FAILED(hr)) { + return; + } + + hr = CoSetProxyBlanket(services, RPC_C_AUTHN_WINNT, RPC_C_AUTHZ_NONE, nullptr, + RPC_C_AUTHN_LEVEL_CALL, RPC_C_IMP_LEVEL_IMPERSONATE, + nullptr, EOAC_NONE); + + if (FAILED(hr)) { + return; + } + + VARIANT value; + VariantInit(&value); + + // Annotate information about the system manufacturer. + if (QueryOneWMIProperty(services, L"Win32_BIOS", L"Manufacturer", &value) && + V_VT(&value) == VT_BSTR) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::BIOS_Manufacturer, + NS_ConvertUTF16toUTF8(V_BSTR(&value))); + } + + VariantClear(&value); + + // Annotate information about type of memory error correction. + if (QueryOneWMIProperty(services, L"Win32_PhysicalMemoryArray", + L"MemoryErrorCorrection", &value) && + V_VT(&value) == VT_I4) { + long valueInt = V_I4(&value); + nsCString valueString; + if (valueInt < 0 || + valueInt >= long(ArrayLength(kMemoryErrorCorrectionValues))) { + valueString.AssignLiteral("Unexpected value"); + } else { + valueString.AssignASCII(kMemoryErrorCorrectionValues[valueInt]); + } + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::MemoryErrorCorrection, valueString); + } + + VariantClear(&value); +} + +static void PR_CALLBACK AnnotateWMIData_ThreadStart(void*) { + mscom::MTARegion mta; + if (!mta.IsValid()) { + return; + } + + AnnotateWMIData(); +} +#endif // XP_WIN + +#if defined(XP_LINUX) && !defined(ANDROID) + +static void AnnotateLSBRelease(void*) { + nsCString dist, desc, release, codename; + if (widget::lsb::GetLSBRelease(dist, desc, release, codename)) { + CrashReporter::AppendAppNotesToCrashReport(desc); + } +} + +#endif // defined(XP_LINUX) && !defined(ANDROID) + +#ifdef XP_WIN +static void ReadAheadSystemDll(const wchar_t* dllName) { + wchar_t dllPath[MAX_PATH]; + if (ConstructSystem32Path(dllName, dllPath, MAX_PATH)) { + ReadAheadLib(dllPath); + } +} + +static void ReadAheadPackagedDll(const wchar_t* dllName, + const wchar_t* aGREDir) { + wchar_t dllPath[MAX_PATH]; + swprintf(dllPath, MAX_PATH, L"%s\\%s", aGREDir, dllName); + ReadAheadLib(dllPath); +} + +static void PR_CALLBACK ReadAheadDlls_ThreadStart(void* arg) { + UniquePtr<wchar_t[]> greDir(static_cast<wchar_t*>(arg)); + + // In Bug 1628903, we investigated which DLLs we should prefetch in + // order to reduce disk I/O and improve startup on Windows machines. + // Our ultimate goal is to measure the impact of these improvements on + // retention (see Bug 1640087). Before we place this within a pref, + // we should ensure this feature only ships to the nightly channel + // and monitor results from that subset. + if (greDir) { + // Prefetch the DLLs shipped with firefox + ReadAheadPackagedDll(L"libegl.dll", greDir.get()); + ReadAheadPackagedDll(L"libGLESv2.dll", greDir.get()); + ReadAheadPackagedDll(L"nssckbi.dll", greDir.get()); + ReadAheadPackagedDll(L"freebl3.dll", greDir.get()); + ReadAheadPackagedDll(L"softokn3.dll", greDir.get()); + + // Prefetch the system DLLs + ReadAheadSystemDll(L"DWrite.dll"); + ReadAheadSystemDll(L"D3DCompiler_47.dll"); + } else { + // Load DataExchange.dll and twinapi.appcore.dll for + // nsWindow::EnableDragDrop + ReadAheadSystemDll(L"DataExchange.dll"); + ReadAheadSystemDll(L"twinapi.appcore.dll"); + + // Load twinapi.dll for WindowsUIUtils::UpdateTabletModeState + ReadAheadSystemDll(L"twinapi.dll"); + + // Load explorerframe.dll for WinTaskbar::Initialize + ReadAheadSystemDll(L"ExplorerFrame.dll"); + + // Load WinTypes.dll for nsOSHelperAppService::GetApplicationDescription + ReadAheadSystemDll(L"WinTypes.dll"); + } +} +#endif + +#if defined(MOZ_WAYLAND) +bool IsWaylandDisabled() { + // MOZ_ENABLE_WAYLAND is our primary Wayland on/off switch. + const char* waylandPref = PR_GetEnv("MOZ_ENABLE_WAYLAND"); + bool enableWayland = (waylandPref && *waylandPref); + if (!enableWayland) { + const char* backendPref = PR_GetEnv("GDK_BACKEND"); + enableWayland = (backendPref && strncmp(backendPref, "wayland", 7) == 0); + if (enableWayland) { + NS_WARNING( + "Wayland backend should be enabled by MOZ_ENABLE_WAYLAND=1." + "GDK_BACKEND is a Gtk3 debug variable and may cause various issues."); + } + } + if (enableWayland && gtk_check_version(3, 22, 0) != nullptr) { + NS_WARNING("Running Wayland backen on Gtk3 < 3.22. Expect issues/glitches"); + } + return !enableWayland; +} +#endif + +#if defined(MOZ_X11) +bool IsX11EGLEnabled() { + const char* eglPref = PR_GetEnv("MOZ_X11_EGL"); + return (eglPref && *eglPref); +} +#endif + +namespace mozilla::startup { +Result<nsCOMPtr<nsIFile>, nsresult> GetIncompleteStartupFile(nsIFile* aProfLD) { + nsCOMPtr<nsIFile> crashFile; + MOZ_TRY(aProfLD->Clone(getter_AddRefs(crashFile))); + MOZ_TRY(crashFile->Append(FILE_STARTUP_INCOMPLETE)); + return std::move(crashFile); +} +} // namespace mozilla::startup + +// Check whether the last startup attempt resulted in a crash within the +// last 6 hours. +// Note that this duplicates the logic in nsAppStartup::TrackStartupCrashBegin, +// which runs too late for our purposes. +Result<bool, nsresult> XREMain::CheckLastStartupWasCrash() { + constexpr int32_t MAX_TIME_SINCE_STARTUP = 6 * 60 * 60 * 1000; + + nsCOMPtr<nsIFile> crashFile; + MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD)); + + // Attempt to create the incomplete startup canary file. If the file already + // exists, this fails, and we know the last startup was a success. If it + // doesn't already exist, it is created, and will be removed at the end of + // the startup crash detection window. + AutoFDClose fd; + Unused << crashFile->OpenNSPRFileDesc(PR_WRONLY | PR_CREATE_FILE | PR_EXCL, + 0666, &fd.rwget()); + if (fd) { + return false; + } + + PRTime lastModifiedTime; + MOZ_TRY(crashFile->GetLastModifiedTime(&lastModifiedTime)); + + // If the file exists, and was created within the appropriate time window, + // the last startup was recent and resulted in a crash. + PRTime now = PR_Now() / PR_USEC_PER_MSEC; + return now - lastModifiedTime <= MAX_TIME_SINCE_STARTUP; +} + +/* + * XRE_mainStartup - Initializes the profile and various other services. + * Main() will exit early if either return value != 0 or if aExitFlag is + * true. + */ +int XREMain::XRE_mainStartup(bool* aExitFlag) { + nsresult rv; + + if (!aExitFlag) return 1; + *aExitFlag = false; + +#ifdef XP_MACOSX + mozilla::MacAutoreleasePool pool; +#endif + + // Enable Telemetry IO Reporting on DEBUG, nightly and local builds, + // but disable it on FUZZING builds. +#ifndef FUZZING +# ifdef DEBUG + mozilla::Telemetry::InitIOReporting(gAppData->xreDirectory); +# else + { + const char* releaseChannel = MOZ_STRINGIFY(MOZ_UPDATE_CHANNEL); + if (strcmp(releaseChannel, "nightly") == 0 || + strcmp(releaseChannel, "default") == 0) { + mozilla::Telemetry::InitIOReporting(gAppData->xreDirectory); + } + } +# endif /* DEBUG */ +#endif /* FUZZING */ + +#if defined(XP_WIN) + // Enable the HeapEnableTerminationOnCorruption exploit mitigation. We ignore + // the return code because it always returns success, although it has no + // effect on Windows older than XP SP3. + HeapSetInformation(NULL, HeapEnableTerminationOnCorruption, NULL, 0); +#endif /* XP_WIN */ + +#if defined(MOZ_WIDGET_GTK) + // Stash DESKTOP_STARTUP_ID in malloc'ed memory because gtk_init will clear + // it. +# define HAVE_DESKTOP_STARTUP_ID + const char* desktopStartupIDEnv = PR_GetEnv("DESKTOP_STARTUP_ID"); + if (desktopStartupIDEnv) { + mDesktopStartupID.Assign(desktopStartupIDEnv); + } +#endif + +#if defined(MOZ_WIDGET_GTK) + // setup for private colormap. Ideally we'd like to do this + // in nsAppShell::Create, but we need to get in before gtk + // has been initialized to make sure everything is running + // consistently. + + // Set program name to the one defined in application.ini. + { + nsAutoCString program(gAppData->name); + ToLowerCase(program); + g_set_prgname(program.get()); + } + + // Initialize GTK here for splash. + +# if defined(MOZ_WIDGET_GTK) && defined(MOZ_X11) + // Disable XInput2 multidevice support due to focus bugginess. + // See bugs 1182700, 1170342. + // gdk_disable_multidevice() affects Gdk X11 backend only, + // the multidevice support is always enabled on Wayland backend. + const char* useXI2 = PR_GetEnv("MOZ_USE_XINPUT2"); + if (!useXI2 || (*useXI2 == '0')) gdk_disable_multidevice(); +# endif + + // Open the display ourselves instead of using gtk_init, so that we can + // close it without fear that one day gtk might clean up the display it + // opens. + if (!gtk_parse_args(&gArgc, &gArgv)) return 1; +#endif /* MOZ_WIDGET_GTK */ + +#ifdef FUZZING + if (PR_GetEnv("FUZZER")) { + *aExitFlag = true; + return mozilla::fuzzerRunner->Run(&gArgc, &gArgv); + } +#endif + + if (PR_GetEnv("MOZ_RUN_GTEST")) { + int result; +#ifdef XP_WIN + UseParentConsole(); +#endif + // RunGTest will only be set if we're in xul-unit + if (mozilla::RunGTest) { + gIsGtest = true; + result = mozilla::RunGTest(&gArgc, gArgv); + gIsGtest = false; + } else { + result = 1; + printf("TEST-UNEXPECTED-FAIL | gtest | Not compiled with enable-tests\n"); + } + *aExitFlag = true; + return result; + } + +#ifdef MOZ_HAS_REMOTE + if (gfxPlatform::IsHeadless()) { + mDisableRemoteClient = true; + mDisableRemoteServer = true; + } +#endif + +#ifdef MOZ_X11 + // Init X11 in thread-safe mode. Must be called prior to the first call to + // XOpenDisplay (called inside gdk_display_open). This is a requirement for + // off main tread compositing. + if (!gfxPlatform::IsHeadless()) { + XInitThreads(); + } +#endif +#if defined(MOZ_WIDGET_GTK) + if (!gfxPlatform::IsHeadless()) { + const char* display_name = nullptr; + bool saveDisplayArg = false; + + // display_name is owned by gdk. + display_name = gdk_get_display_arg_name(); + // if --display argument is given make sure it's + // also passed to ContentChild::Init() by MOZ_GDK_DISPLAY. + if (display_name) { + SaveWordToEnv("MOZ_GDK_DISPLAY", nsDependentCString(display_name)); + saveDisplayArg = true; + } + + bool disableWayland = true; +# if defined(MOZ_WAYLAND) + disableWayland = IsWaylandDisabled(); +# endif + // On Wayland disabled builds read X11 DISPLAY env exclusively + // and don't care about different displays. + if (disableWayland && !display_name) { + display_name = PR_GetEnv("DISPLAY"); + if (!display_name) { + PR_fprintf(PR_STDERR, + "Error: no DISPLAY environment variable specified\n"); + return 1; + } + } + + if (display_name) { + mGdkDisplay = gdk_display_open(display_name); + if (!mGdkDisplay) { + PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name); + return 1; + } + gdk_display_manager_set_default_display(gdk_display_manager_get(), + mGdkDisplay); + if (saveDisplayArg) { + if (GDK_IS_X11_DISPLAY(mGdkDisplay)) { + SaveWordToEnv("DISPLAY", nsDependentCString(display_name)); + } +# ifdef MOZ_WAYLAND + else if (!GDK_IS_X11_DISPLAY(mGdkDisplay)) { + SaveWordToEnv("WAYLAND_DISPLAY", nsDependentCString(display_name)); + } +# endif + } + } +# ifdef MOZ_WIDGET_GTK + else { + mGdkDisplay = + gdk_display_manager_open_display(gdk_display_manager_get(), nullptr); + } +# endif + } +#endif +#if defined(MOZ_HAS_REMOTE) + // handle --remote now that xpcom is fired up + mRemoteService = new nsRemoteService(gAppData->remotingName); + if (mRemoteService && !mDisableRemoteServer) { + mRemoteService->LockStartup(); + } +#endif +#if defined(MOZ_WIDGET_GTK) + g_set_application_name(mAppData->name); + +#endif /* defined(MOZ_WIDGET_GTK) */ +#ifdef MOZ_X11 + // Do this after initializing GDK, or GDK will install its own handler. + XRE_InstallX11ErrorHandler(); +#endif + + // Call the code to install our handler +#ifdef MOZ_JPROF + setupProfilingStuff(); +#endif + + rv = NS_CreateNativeAppSupport(getter_AddRefs(mNativeApp)); + if (NS_FAILED(rv)) return 1; + + bool canRun = false; + rv = mNativeApp->Start(&canRun); + if (NS_FAILED(rv) || !canRun) { + return 1; + } + +#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK) + // DESKTOP_STARTUP_ID is cleared now, + // we recover it in case we need a restart. + if (!mDesktopStartupID.IsEmpty()) { + nsAutoCString desktopStartupEnv; + desktopStartupEnv.AssignLiteral("DESKTOP_STARTUP_ID="); + desktopStartupEnv.Append(mDesktopStartupID); + // Leak it with extreme prejudice! + PR_SetEnv(ToNewCString(desktopStartupEnv)); + } +#endif + + // Support exiting early for testing startup sequence. Bug 1360493 + if (CheckArg("test-launch-without-hang")) { + *aExitFlag = true; + return 0; + } + + rv = NS_NewToolkitProfileService(getter_AddRefs(mProfileSvc)); + if (rv == NS_ERROR_FILE_ACCESS_DENIED) { + PR_fprintf(PR_STDERR, + "Error: Access was denied while trying to open files in " + "your profile directory.\n"); + } + if (NS_FAILED(rv)) { + // We failed to choose or create profile - notify user and quit + ProfileMissingDialog(mNativeApp); + return 1; + } + + bool wasDefaultSelection; + nsCOMPtr<nsIToolkitProfile> profile; + rv = SelectProfile(mProfileSvc, mNativeApp, getter_AddRefs(mProfD), + getter_AddRefs(mProfLD), getter_AddRefs(profile), + &wasDefaultSelection); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } + + if (NS_FAILED(rv)) { + // We failed to choose or create profile - notify user and quit + ProfileMissingDialog(mNativeApp); + return 1; + } + +#if defined(MOZ_HAS_REMOTE) + if (mRemoteService) { + // We want a unique profile name to identify the remote instance. + nsCString profileName; + if (profile) { + rv = profile->GetName(profileName); + } + if (!profile || NS_FAILED(rv) || profileName.IsEmpty()) { + // Couldn't get a name from the profile. Use the directory name? + nsString leafName; + rv = mProfD->GetLeafName(leafName); + if (NS_SUCCEEDED(rv)) { + CopyUTF16toUTF8(leafName, profileName); + } + } + + mRemoteService->SetProfile(profileName); + + if (!mDisableRemoteClient) { + // Try to remote the entire command line. If this fails, start up + // normally. + const char* desktopStartupIDPtr = + mDesktopStartupID.IsEmpty() ? nullptr : mDesktopStartupID.get(); + + RemoteResult rr = mRemoteService->StartClient(desktopStartupIDPtr); + if (rr == REMOTE_FOUND) { + *aExitFlag = true; + mRemoteService->UnlockStartup(); + return 0; + } + if (rr == REMOTE_ARG_BAD) { + mRemoteService->UnlockStartup(); + return 1; + } + } + } +#endif + +#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID) + // Check for and process any available updates + nsCOMPtr<nsIFile> updRoot; + bool persistent; + rv = mDirProvider.GetFile(XRE_UPDATE_ROOT_DIR, &persistent, + getter_AddRefs(updRoot)); + // XRE_UPDATE_ROOT_DIR may fail. Fallback to appDir if failed + if (NS_FAILED(rv)) { + updRoot = mDirProvider.GetAppDir(); + } + + // If the MOZ_TEST_PROCESS_UPDATES environment variable already exists, then + // we are being called from the callback application. + if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { + // If the caller has asked us to log our arguments, do so. This is used + // to make sure that the maintenance service successfully launches the + // callback application. + const char* logFile = nullptr; + if (ARG_FOUND == CheckArg("dump-args", &logFile)) { + FILE* logFP = fopen(logFile, "wb"); + if (logFP) { + for (int i = 1; i < gRestartArgc; ++i) { + fprintf(logFP, "%s\n", gRestartArgv[i]); + } + fclose(logFP); + } + } + *aExitFlag = true; + return 0; + } + + // Support for processing an update and exiting. The MOZ_TEST_PROCESS_UPDATES + // environment variable will be part of the updater's environment and the + // application that is relaunched by the updater. When the application is + // relaunched by the updater it will be removed below and the application + // will exit. + if (CheckArg("test-process-updates")) { + SaveToEnv("MOZ_TEST_PROCESS_UPDATES=1"); + } + nsCOMPtr<nsIFile> exeFile, exeDir; + rv = mDirProvider.GetFile(XRE_EXECUTABLE_FILE, &persistent, + getter_AddRefs(exeFile)); + NS_ENSURE_SUCCESS(rv, 1); + rv = exeFile->GetParent(getter_AddRefs(exeDir)); + NS_ENSURE_SUCCESS(rv, 1); + ProcessUpdates(mDirProvider.GetGREDir(), exeDir, updRoot, gRestartArgc, + gRestartArgv, mAppData->version); + if (EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) { + SaveToEnv("MOZ_TEST_PROCESS_UPDATES="); + *aExitFlag = true; + return 0; + } +#endif + + // We now know there is no existing instance using the selected profile. If + // the profile wasn't selected by specific command line arguments and the + // user has chosen to show the profile manager on startup then do that. + if (wasDefaultSelection) { + bool useSelectedProfile; + rv = mProfileSvc->GetStartWithLastProfile(&useSelectedProfile); + NS_ENSURE_SUCCESS(rv, 1); + + if (!useSelectedProfile) { + rv = ShowProfileManager(mProfileSvc, mNativeApp); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } + if (NS_FAILED(rv)) { + return 1; + } + } + } + + // We always want to lock the profile even if we're actually going to reset + // it later. + rv = LockProfile(mNativeApp, mProfD, mProfLD, profile, + getter_AddRefs(mProfileLock)); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } else if (NS_FAILED(rv)) { + return 1; + } + + if (gDoProfileReset) { + // Unlock the source profile. + mProfileLock->Unlock(); + + // If we're resetting a profile, create a new one and use it to startup. + gResetOldProfile = profile; + rv = mProfileSvc->CreateResetProfile(getter_AddRefs(profile)); + if (NS_SUCCEEDED(rv)) { + rv = profile->GetRootDir(getter_AddRefs(mProfD)); + NS_ENSURE_SUCCESS(rv, 1); + SaveFileToEnv("XRE_PROFILE_PATH", mProfD); + + rv = profile->GetLocalDir(getter_AddRefs(mProfLD)); + NS_ENSURE_SUCCESS(rv, 1); + SaveFileToEnv("XRE_PROFILE_LOCAL_PATH", mProfLD); + + // Lock the new profile + rv = LockProfile(mNativeApp, mProfD, mProfLD, profile, + getter_AddRefs(mProfileLock)); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } else if (NS_FAILED(rv)) { + return 1; + } + } else { + NS_WARNING("Profile reset failed."); + return 1; + } + } + + gProfileLock = mProfileLock; + + nsAutoCString version; + BuildVersion(version); + +#ifdef TARGET_OS_ABI + constexpr auto osABI = nsLiteralCString{TARGET_OS_ABI}; +#else + // No TARGET_XPCOM_ABI, but at least the OS is known + constexpr auto osABI = nsLiteralCString{OS_TARGET "_UNKNOWN"}; +#endif + + // Check for version compatibility with the last version of the app this + // profile was started with. The format of the version stamp is defined + // by the BuildVersion function. + // Also check to see if something has happened to invalidate our + // fastload caches, like an app upgrade. + + // If we see .purgecaches, that means someone did a make. + // Re-register components to catch potential changes. + nsCOMPtr<nsIFile> flagFile; + if (mAppData->directory) { + Unused << mAppData->directory->Clone(getter_AddRefs(flagFile)); + } + if (flagFile) { + flagFile->AppendNative(FILE_INVALIDATE_CACHES); + } + + bool cachesOK; + bool isDowngrade; + nsCString lastVersion; + bool versionOK = CheckCompatibility( + mProfD, version, osABI, mDirProvider.GetGREDir(), mAppData->directory, + flagFile, &cachesOK, &isDowngrade, lastVersion); + + MOZ_RELEASE_ASSERT(!cachesOK || lastVersion.Equals(version), + "Caches cannot be good if the version has changed."); + +#ifdef MOZ_BLOCK_PROFILE_DOWNGRADE + // The argument check must come first so the argument is always removed from + // the command line regardless of whether this is a downgrade or not. + if (!CheckArg("allow-downgrade") && isDowngrade && + !EnvHasValue("MOZ_ALLOW_DOWNGRADE")) { + rv = CheckDowngrade(mProfD, mNativeApp, mProfileSvc, lastVersion); + if (rv == NS_ERROR_LAUNCHED_CHILD_PROCESS || rv == NS_ERROR_ABORT) { + *aExitFlag = true; + return 0; + } + } +#endif + + rv = mDirProvider.SetProfile(mProfD, mProfLD); + NS_ENSURE_SUCCESS(rv, 1); + + //////////////////////// NOW WE HAVE A PROFILE //////////////////////// + + mozilla::Telemetry::SetProfileDir(mProfD); + + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) { + MakeOrSetMinidumpPath(mProfD); + } + + CrashReporter::SetProfileDirectory(mProfD); + +#ifdef MOZ_ASAN_REPORTER + // In ASan reporter builds, we need to set ASan's log_path as early as + // possible, so it dumps its errors into files there instead of using + // the default stderr location. Since this is crucial for ASan reporter + // to work at all (and we don't want people to use a non-functional + // ASan reporter build), all failures while setting log_path are fatal. + setASanReporterPath(mProfD); + + // Export to env for child processes + SaveFileToEnv("ASAN_REPORTER_PATH", mProfD); +#endif + + bool lastStartupWasCrash = CheckLastStartupWasCrash().unwrapOr(false); + + if (CheckArg("purgecaches") || PR_GetEnv("MOZ_PURGE_CACHES") || + lastStartupWasCrash || gSafeMode) { + cachesOK = false; + } + + // Every time a profile is loaded by a build with a different version, + // it updates the compatibility.ini file saying what version last wrote + // the fastload caches. On subsequent launches if the version matches, + // there is no need for re-registration. If the user loads the same + // profile in different builds the component registry must be + // re-generated to prevent mysterious component loading failures. + // + bool startupCacheValid = true; + + if (!cachesOK || !versionOK) { + QuotaManager::InvalidateQuotaCache(); + + startupCacheValid = RemoveComponentRegistries(mProfD, mProfLD, false); + + // Rewrite compatibility.ini to match the current build. The next run + // should attempt to invalidate the caches if either this run is safe mode + // or the attempt to invalidate the caches this time failed. + WriteVersion(mProfD, version, osABI, mDirProvider.GetGREDir(), + mAppData->directory, gSafeMode || !startupCacheValid); + } + + if (!startupCacheValid) StartupCache::IgnoreDiskCache(); + + if (flagFile) { + flagFile->Remove(true); + } + + return 0; +} + +#if defined(MOZ_SANDBOX) +void AddSandboxAnnotations() { + // Include the sandbox content level, regardless of platform + int level = GetEffectiveContentSandboxLevel(); + + nsAutoCString levelString; + levelString.AppendInt(level); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxLevel, levelString); + + // Include whether or not this instance is capable of content sandboxing + bool sandboxCapable = false; + +# if defined(XP_WIN) + // All supported Windows versions support some level of content sandboxing + sandboxCapable = true; +# elif defined(XP_MACOSX) + // All supported OS X versions are capable + sandboxCapable = true; +# elif defined(XP_LINUX) + sandboxCapable = SandboxInfo::Get().CanSandboxContent(); +# elif defined(__OpenBSD__) + sandboxCapable = true; + StartOpenBSDSandbox(GeckoProcessType_Default); +# endif + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxCapable, sandboxCapable); +} +#endif /* MOZ_SANDBOX */ + +/* + * XRE_mainRun - Command line startup, profile migration, and + * the calling of appStartup->Run(). + */ +nsresult XREMain::XRE_mainRun() { + nsresult rv = NS_OK; + NS_ASSERTION(mScopedXPCOM, "Scoped xpcom not initialized."); + +#if defined(XP_WIN) + RefPtr<mozilla::DllServices> dllServices(mozilla::DllServices::Get()); + dllServices->StartUntrustedModulesProcessor(); + auto dllServicesDisable = + MakeScopeExit([&dllServices]() { dllServices->DisableFull(); }); + +# if defined(MOZ_GECKO_PROFILER) + mozilla::mscom::InitProfilerMarkers(); +# endif // defined(MOZ_GECKO_PROFILER) +#endif // defined(XP_WIN) + + // We need the appStartup pointer to span multiple scopes, so we declare + // it here. + nsCOMPtr<nsIAppStartup> appStartup; + { +#ifdef XP_MACOSX + // In this scope, create an autorelease pool that will leave scope with + // it just before entering our event loop. + mozilla::MacAutoreleasePool pool; +#endif + + rv = mScopedXPCOM->SetWindowCreator(mNativeApp); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + // tell the crash reporter to also send the release channel + nsCOMPtr<nsIPrefService> prefs = + do_GetService("@mozilla.org/preferences-service;1", &rv); + if (NS_SUCCEEDED(rv)) { + nsCOMPtr<nsIPrefBranch> defaultPrefBranch; + rv = prefs->GetDefaultBranch(nullptr, getter_AddRefs(defaultPrefBranch)); + + if (NS_SUCCEEDED(rv)) { + nsAutoCString sval; + rv = defaultPrefBranch->GetCharPref("app.update.channel", sval); + if (NS_SUCCEEDED(rv)) { + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ReleaseChannel, sval); + } + } + } + // Needs to be set after xpcom initialization. + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::FramePoisonBase, + nsPrintfCString("%.16" PRIu64, uint64_t(gMozillaPoisonBase))); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::FramePoisonSize, + uint32_t(gMozillaPoisonSize)); + + bool includeContextHeap = Preferences::GetBool( + "toolkit.crashreporter.include_context_heap", false); + CrashReporter::SetIncludeContextHeap(includeContextHeap); + +#ifdef XP_WIN + PR_CreateThread(PR_USER_THREAD, AnnotateWMIData_ThreadStart, 0, + PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); +#endif + +#if defined(XP_LINUX) && !defined(ANDROID) + PR_CreateThread(PR_USER_THREAD, AnnotateLSBRelease, 0, PR_PRIORITY_LOW, + PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); +#endif + + if (mStartOffline) { + nsCOMPtr<nsIIOService> io( + do_GetService("@mozilla.org/network/io-service;1")); + NS_ENSURE_TRUE(io, NS_ERROR_FAILURE); + io->SetManageOfflineStatus(false); + io->SetOffline(true); + } + +#ifdef XP_WIN + mozilla::DllPrefetchExperimentRegistryInfo prefetchRegInfo; + mozilla::AlteredDllPrefetchMode dllPrefetchMode = + prefetchRegInfo.GetAlteredDllPrefetchMode(); + + if (!PR_GetEnv("XRE_NO_DLL_READAHEAD") && + dllPrefetchMode != mozilla::AlteredDllPrefetchMode::NoPrefetch) { + nsCOMPtr<nsIFile> greDir = mDirProvider.GetGREDir(); + nsAutoString path; + rv = greDir->GetPath(path); + if (NS_SUCCEEDED(rv)) { + PRThread* readAheadThread; + wchar_t* pathRaw; + + // We use the presence of a path argument inside the thread to determine + // which list of Dlls to use. The old list does not need access to the + // GRE dir, so the path argument is set to a null pointer. + if (dllPrefetchMode == + mozilla::AlteredDllPrefetchMode::OptimizedPrefetch) { + pathRaw = new wchar_t[MAX_PATH]; + wcscpy_s(pathRaw, MAX_PATH, path.get()); + } else { + pathRaw = nullptr; + } + readAheadThread = PR_CreateThread( + PR_USER_THREAD, ReadAheadDlls_ThreadStart, (void*)pathRaw, + PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_UNJOINABLE_THREAD, 0); + if (readAheadThread == NULL) { + delete[] pathRaw; + } + } + } +#endif + + if (gDoMigration) { + nsCOMPtr<nsIFile> file; + mDirProvider.GetAppDir()->Clone(getter_AddRefs(file)); + file->AppendNative("override.ini"_ns); + nsINIParser parser; + nsresult rv = parser.Init(file); + // if override.ini doesn't exist, also check for distribution.ini + if (NS_FAILED(rv)) { + bool persistent; + mDirProvider.GetFile(XRE_APP_DISTRIBUTION_DIR, &persistent, + getter_AddRefs(file)); + file->AppendNative("distribution.ini"_ns); + rv = parser.Init(file); + } + if (NS_SUCCEEDED(rv)) { + nsAutoCString buf; + rv = parser.GetString("XRE", "EnableProfileMigrator", buf); + if (NS_SUCCEEDED(rv)) { + if (buf[0] == '0' || buf[0] == 'f' || buf[0] == 'F') { + gDoMigration = false; + } + } + } + } + + // We'd like to initialize the JSContext *after* reading the user prefs. + // Unfortunately that's not possible if we have to do profile migration + // because that requires us to execute JS before reading user prefs. + // Restarting the browser after profile migration would fix this. See + // bug 1592523. + bool initializedJSContext = false; + + { + // Profile Migration + if (mAppData->flags & NS_XRE_ENABLE_PROFILE_MIGRATOR && gDoMigration) { + gDoMigration = false; + + xpc::InitializeJSContext(); + initializedJSContext = true; + + nsCOMPtr<nsIProfileMigrator> pm( + do_CreateInstance(NS_PROFILEMIGRATOR_CONTRACTID)); + if (pm) { + nsAutoCString aKey; + nsAutoCString aName; + if (gDoProfileReset) { + // Automatically migrate from the current application if we just + // reset the profile. + aKey = MOZ_APP_NAME; + gResetOldProfile->GetName(aName); + } + pm->Migrate(&mDirProvider, aKey, aName); + } + } + + if (gDoProfileReset) { + if (!initializedJSContext) { + xpc::InitializeJSContext(); + initializedJSContext = true; + } + + nsresult backupCreated = + ProfileResetCleanup(mProfileSvc, gResetOldProfile); + if (NS_FAILED(backupCreated)) { + NS_WARNING("Could not cleanup the profile that was reset"); + } + } + } + +#ifndef XP_WIN + nsCOMPtr<nsIFile> profileDir; + nsAutoCString path; + rv = mDirProvider.GetProfileStartupDir(getter_AddRefs(profileDir)); + if (NS_SUCCEEDED(rv) && NS_SUCCEEDED(profileDir->GetNativePath(path)) && + !IsUtf8(path)) { + PR_fprintf( + PR_STDERR, + "Error: The profile path is not valid UTF-8. Unable to continue.\n"); + return NS_ERROR_FAILURE; + } +#endif + + // Initialize user preferences before notifying startup observers so they're + // ready in time for early consumers, such as the component loader. + mDirProvider.InitializeUserPrefs(); + + // Now that all (user) prefs have been loaded we can initialize the main + // thread's JSContext. + if (!initializedJSContext) { + xpc::InitializeJSContext(); + } + + // Finally, now that JS has been initialized, we can finish pref loading. + // This needs to happen after JS and XPConnect initialization because + // AutoConfig files require JS execution. Note that this means AutoConfig + // files can't override JS engine start-up prefs. + mDirProvider.FinishInitializingUserPrefs(); + + nsAppStartupNotifier::NotifyObservers(APPSTARTUP_CATEGORY); + + appStartup = components::AppStartup::Service(); + NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE); + + mDirProvider.DoStartup(); + +#ifdef MOZ_THUNDERBIRD + if (Preferences::GetBool("security.prompt_for_master_password_on_startup", + false)) { + // Prompt for the master password prior to opening application windows, + // to avoid the race that triggers multiple prompts (see bug 177175). + // We use this code until we have a better solution, possibly as + // described in bug 177175 comment 384. + nsCOMPtr<nsIPK11TokenDB> db = + do_GetService("@mozilla.org/security/pk11tokendb;1"); + nsCOMPtr<nsIPK11Token> token; + if (NS_SUCCEEDED(db->GetInternalKeyToken(getter_AddRefs(token)))) { + Unused << token->Login(false); + } + } +#endif + + // As FilePreferences need the profile directory, we must initialize right + // here. + mozilla::FilePreferences::InitDirectoriesWhitelist(); + mozilla::FilePreferences::InitPrefs(); + + OverrideDefaultLocaleIfNeeded(); + + nsCString userAgentLocale; + LocaleService::GetInstance()->GetAppLocaleAsBCP47(userAgentLocale); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::useragent_locale, userAgentLocale); + + appStartup->GetShuttingDown(&mShuttingDown); + + nsCOMPtr<nsICommandLineRunner> cmdLine; + + nsCOMPtr<nsIFile> workingDir; + rv = NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, + getter_AddRefs(workingDir)); + if (NS_FAILED(rv)) { + // No working dir? This can happen if it gets deleted before we start. + workingDir = nullptr; + } + + if (!mShuttingDown) { + cmdLine = new nsCommandLine(); + + rv = cmdLine->Init(gArgc, gArgv, workingDir, + nsICommandLine::STATE_INITIAL_LAUNCH); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + + /* Special-case services that need early access to the command + line. */ + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) { + obsService->NotifyObservers(cmdLine, "command-line-startup", nullptr); + } + } + +#ifdef XP_WIN + // Hack to sync up the various environment storages. XUL_APP_FILE is special + // in that it comes from a different CRT (firefox.exe's static-linked copy). + // Ugly details in http://bugzil.la/1175039#c27 + char appFile[MAX_PATH]; + if (GetEnvironmentVariableA("XUL_APP_FILE", appFile, sizeof(appFile))) { + SmprintfPointer saved = mozilla::Smprintf("XUL_APP_FILE=%s", appFile); + // We intentionally leak the string here since it is required by + // PR_SetEnv. + PR_SetEnv(saved.release()); + } +#endif + + mozilla::AppShutdown::SaveEnvVarsForPotentialRestart(); + + // clear out any environment variables which may have been set + // during the relaunch process now that we know we won't be relaunching. + SaveToEnv("XRE_PROFILE_PATH="); + SaveToEnv("XRE_PROFILE_LOCAL_PATH="); + SaveToEnv("XRE_START_OFFLINE="); + SaveToEnv("XUL_APP_FILE="); + SaveToEnv("XRE_BINARY_PATH="); + SaveToEnv("XRE_RESTARTED_BY_PROFILE_MANAGER="); + + if (!mShuttingDown) { +#ifdef XP_MACOSX + bool lazyHiddenWindow = false; +#else + bool lazyHiddenWindow = + Preferences::GetBool("toolkit.lazyHiddenWindow", false); +#endif + + if (!lazyHiddenWindow) { + rv = appStartup->CreateHiddenWindow(); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + +#ifdef XP_WIN + Preferences::RegisterCallbackAndCall( + RegisterApplicationRestartChanged, + PREF_WIN_REGISTER_APPLICATION_RESTART); + SetupAlteredPrefetchPref(); + SetupSkeletonUIPrefs(); +# if defined(MOZ_LAUNCHER_PROCESS) + SetupLauncherProcessPref(); +# endif // defined(MOZ_LAUNCHER_PROCESS) +# if defined(MOZ_DEFAULT_BROWSER_AGENT) + Preferences::RegisterCallbackAndCall(&OnDefaultAgentTelemetryPrefChanged, + kPrefHealthReportUploadEnabled); + Preferences::RegisterCallbackAndCall(&OnDefaultAgentTelemetryPrefChanged, + kPrefDefaultAgentEnabled); + + Preferences::RegisterCallbackAndCall( + &OnDefaultAgentRemoteSettingsPrefChanged, + kPrefServicesSettingsServer); + Preferences::RegisterCallbackAndCall( + &OnDefaultAgentRemoteSettingsPrefChanged, + kPrefSecurityContentSignatureRootHash); + SetDefaultAgentLastRunTime(); +# endif // defined(MOZ_DEFAULT_BROWSER_AGENT) +#endif + +#if defined(HAVE_DESKTOP_STARTUP_ID) && defined(MOZ_WIDGET_GTK) + // Clear the environment variable so it won't be inherited by + // child processes and confuse things. + g_unsetenv("DESKTOP_STARTUP_ID"); +#endif + +#ifdef XP_MACOSX + // we re-initialize the command-line service and do appleevents munging + // after we are sure that we're not restarting + cmdLine = new nsCommandLine(); + + char** tempArgv = static_cast<char**>(malloc(gArgc * sizeof(char*))); + for (int i = 0; i < gArgc; i++) { + tempArgv[i] = strdup(gArgv[i]); + } + CommandLineServiceMac::SetupMacCommandLine(gArgc, tempArgv, false); + rv = cmdLine->Init(gArgc, tempArgv, workingDir, + nsICommandLine::STATE_INITIAL_LAUNCH); + free(tempArgv); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); +#endif + + nsCOMPtr<nsIObserverService> obsService = + mozilla::services::GetObserverService(); + if (obsService) + obsService->NotifyObservers(nullptr, "final-ui-startup", nullptr); + + (void)appStartup->DoneStartingUp(); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::StartupCrash, false); + + appStartup->GetShuttingDown(&mShuttingDown); + } + + if (!mShuttingDown) { + rv = cmdLine->Run(); + NS_ENSURE_SUCCESS_LOG(rv, NS_ERROR_FAILURE); + + appStartup->GetShuttingDown(&mShuttingDown); + } + + if (!mShuttingDown) { +#if defined(MOZ_HAS_REMOTE) + // if we have X remote support, start listening for requests on the + // proxy window. + if (mRemoteService && !mDisableRemoteServer) { + mRemoteService->StartupServer(); + mRemoteService->UnlockStartup(); + } +#endif /* MOZ_WIDGET_GTK */ + + mNativeApp->Enable(); + } + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + if (PR_GetEnv("MOZ_INSTRUMENT_EVENT_LOOP")) { + bool logToConsole = true; + mozilla::InitEventTracing(logToConsole); + } +#endif /* MOZ_INSTRUMENT_EVENT_LOOP */ + + // Send Telemetry about Gecko version and buildid + Telemetry::ScalarSet(Telemetry::ScalarID::GECKO_VERSION, + NS_ConvertASCIItoUTF16(gAppData->version)); + Telemetry::ScalarSet(Telemetry::ScalarID::GECKO_BUILD_ID, + NS_ConvertASCIItoUTF16(gAppData->buildID)); + +#if defined(MOZ_SANDBOX) && defined(XP_LINUX) + // If we're on Linux, we now have information about the OS capabilities + // available to us. + SandboxInfo sandboxInfo = SandboxInfo::Get(); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_SECCOMP_BPF, + sandboxInfo.Test(SandboxInfo::kHasSeccompBPF)); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_SECCOMP_TSYNC, + sandboxInfo.Test(SandboxInfo::kHasSeccompTSync)); + Telemetry::Accumulate( + Telemetry::SANDBOX_HAS_USER_NAMESPACES_PRIVILEGED, + sandboxInfo.Test(SandboxInfo::kHasPrivilegedUserNamespaces)); + Telemetry::Accumulate(Telemetry::SANDBOX_HAS_USER_NAMESPACES, + sandboxInfo.Test(SandboxInfo::kHasUserNamespaces)); + Telemetry::Accumulate(Telemetry::SANDBOX_CONTENT_ENABLED, + sandboxInfo.Test(SandboxInfo::kEnabledForContent)); + Telemetry::Accumulate(Telemetry::SANDBOX_MEDIA_ENABLED, + sandboxInfo.Test(SandboxInfo::kEnabledForMedia)); + nsAutoCString flagsString; + flagsString.AppendInt(sandboxInfo.AsInteger()); + + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxCapabilities, flagsString); +#endif /* MOZ_SANDBOX && XP_LINUX */ + +#if defined(XP_WIN) + LauncherResult<bool> isAdminWithoutUac = IsAdminWithoutUac(); + if (isAdminWithoutUac.isOk()) { + Telemetry::ScalarSet( + Telemetry::ScalarID::OS_ENVIRONMENT_IS_ADMIN_WITHOUT_UAC, + isAdminWithoutUac.unwrap()); + } +#endif /* XP_WIN */ + +#if defined(MOZ_SANDBOX) + AddSandboxAnnotations(); +#endif /* MOZ_SANDBOX */ + + mProfileSvc->CompleteStartup(); + } + + { + rv = appStartup->Run(); + if (NS_FAILED(rv)) { + NS_ERROR("failed to run appstartup"); + gLogConsoleErrors = true; + } + } + + return rv; +} + +#if defined(MOZ_WIDGET_ANDROID) +static already_AddRefed<nsIFile> GreOmniPath() { + nsresult rv; + + const char* path = nullptr; + ArgResult ar = CheckArg("greomni", &path); + if (ar == ARG_BAD) { + PR_fprintf(PR_STDERR, + "Error: argument --greomni requires a path argument\n"); + return nullptr; + } + + if (!path) return nullptr; + + nsCOMPtr<nsIFile> greOmni; + rv = XRE_GetFileFromPath(path, getter_AddRefs(greOmni)); + if (NS_FAILED(rv)) { + PR_fprintf(PR_STDERR, "Error: argument --greomni requires a valid path\n"); + return nullptr; + } + + return greOmni.forget(); +} +#endif + +/* + * XRE_main - A class based main entry point used by most platforms. + * Note that on OSX, aAppData->xreDirectory will point to + * .app/Contents/Resources. + */ +int XREMain::XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) { + gArgc = argc; + gArgv = argv; + + ScopedLogging log; + + mozilla::LogModule::Init(gArgc, gArgv); + +#ifndef XP_LINUX + NS_SetCurrentThreadName("MainThread"); +#endif + + AUTO_BASE_PROFILER_LABEL("XREMain::XRE_main (around Gecko Profiler)", OTHER); + AUTO_PROFILER_INIT; + AUTO_PROFILER_LABEL("XREMain::XRE_main", OTHER); + +#ifdef MOZ_CODE_COVERAGE + CodeCoverageHandler::Init(); +#endif + + nsresult rv = NS_OK; + + if (aConfig.appData) { + mAppData = MakeUnique<XREAppData>(*aConfig.appData); + } else { + MOZ_RELEASE_ASSERT(aConfig.appDataPath); + nsCOMPtr<nsIFile> appini; + rv = XRE_GetFileFromPath(aConfig.appDataPath, getter_AddRefs(appini)); + if (NS_FAILED(rv)) { + Output(true, "Error: unrecognized path: %s\n", aConfig.appDataPath); + return 1; + } + + mAppData = MakeUnique<XREAppData>(); + rv = XRE_ParseAppData(appini, *mAppData); + if (NS_FAILED(rv)) { + Output(true, "Couldn't read application.ini"); + return 1; + } + + appini->GetParent(getter_AddRefs(mAppData->directory)); + } + + if (!mAppData->remotingName) { + mAppData->remotingName = mAppData->name; + } + // used throughout this file + gAppData = mAppData.get(); + + nsCOMPtr<nsIFile> binFile; + rv = XRE_GetBinaryPath(getter_AddRefs(binFile)); + NS_ENSURE_SUCCESS(rv, 1); + + rv = binFile->GetPath(gAbsoluteArgv0Path); + NS_ENSURE_SUCCESS(rv, 1); + + if (!mAppData->xreDirectory) { + nsCOMPtr<nsIFile> greDir; + +#if defined(MOZ_WIDGET_ANDROID) + greDir = GreOmniPath(); + if (!greDir) { + return 2; + } +#else + rv = binFile->GetParent(getter_AddRefs(greDir)); + if (NS_FAILED(rv)) return 2; +#endif + +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> parent; + greDir->GetParent(getter_AddRefs(parent)); + greDir = parent.forget(); + greDir->AppendNative("Resources"_ns); +#endif + + mAppData->xreDirectory = greDir; + } + +#if defined(MOZ_WIDGET_ANDROID) + nsCOMPtr<nsIFile> dataDir; + rv = binFile->GetParent(getter_AddRefs(dataDir)); + if (NS_FAILED(rv)) return 2; + + mAppData->directory = dataDir; +#else + if (aConfig.appData && aConfig.appDataPath) { + mAppData->xreDirectory->Clone(getter_AddRefs(mAppData->directory)); + mAppData->directory->AppendNative(nsDependentCString(aConfig.appDataPath)); + } +#endif + + if (!mAppData->directory) { + mAppData->directory = mAppData->xreDirectory; + } + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + mAppData->sandboxBrokerServices = aConfig.sandboxBrokerServices; + mAppData->sandboxPermissionsService = aConfig.sandboxPermissionsService; +#endif + + mozilla::IOInterposerInit ioInterposerGuard; + +#if defined(XP_WIN) + // Initializing COM below may load modules via SetWindowHookEx, some of + // which may modify the executable's IAT for ntdll.dll. If that happens, + // this browser process fails to launch sandbox processes because we cannot + // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp). + // To prevent that, we cache the intact IAT before COM initialization. + // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will + // also prevent an injected module from parsing the PE headers and modifying + // the IAT. Therefore, we can skip CacheNtDllThunk(). + if (!mozilla::IsEafPlusEnabled()) { + mozilla::ipc::GeckoChildProcessHost::CacheNtDllThunk(); + } + + // Some COM settings are global to the process and must be set before any non- + // trivial COM is run in the application. Since these settings may affect + // stability, we should instantiate COM ASAP so that we can ensure that these + // global settings are configured before anything can interfere. + mozilla::mscom::ProcessRuntime msCOMRuntime; +#endif + + // init + bool exit = false; + int result = XRE_mainInit(&exit); + if (result != 0 || exit) return result; + + // If we exit gracefully, remove the startup crash canary file. + auto cleanup = MakeScopeExit([&]() -> nsresult { + if (mProfLD) { + nsCOMPtr<nsIFile> crashFile; + MOZ_TRY_VAR(crashFile, GetIncompleteStartupFile(mProfLD)); + crashFile->Remove(false); + } + return NS_OK; + }); + + // startup + result = XRE_mainStartup(&exit); + if (result != 0 || exit) return result; + + // Start the real application. We use |aInitJSContext = false| because + // XRE_mainRun wants to initialize the JSContext after reading user prefs. + + mScopedXPCOM = MakeUnique<ScopedXPCOMStartup>(); + if (!mScopedXPCOM) return 1; + + rv = mScopedXPCOM->Initialize(/* aInitJSContext = */ false); + NS_ENSURE_SUCCESS(rv, 1); + + // run! + rv = XRE_mainRun(); + +#ifdef MOZ_INSTRUMENT_EVENT_LOOP + mozilla::ShutdownEventTracing(); +#endif + + gAbsoluteArgv0Path.Truncate(); + +#if defined(MOZ_HAS_REMOTE) + // Shut down the remote service. We must do this before calling LaunchChild + // if we're restarting because otherwise the new instance will attempt to + // remote to this instance. + if (mRemoteService && !mDisableRemoteServer) { + mRemoteService->ShutdownServer(); + } +#endif /* MOZ_WIDGET_GTK */ + + mScopedXPCOM = nullptr; + +#if defined(XP_WIN) + mozilla::widget::StopAudioSession(); +#endif + + // unlock the profile after ScopedXPCOMStartup object (xpcom) + // has gone out of scope. see bug #386739 for more details + mProfileLock->Unlock(); + gProfileLock = nullptr; + + gLastAppVersion.Truncate(); + gLastAppBuildID.Truncate(); + + mozilla::AppShutdown::MaybeDoRestart(); + +#ifdef MOZ_WIDGET_GTK + // gdk_display_close also calls gdk_display_manager_set_default_display + // appropriately when necessary. + if (!gfxPlatform::IsHeadless()) { +# ifdef MOZ_WAYLAND + WaylandDisplayRelease(); +# endif + } +#endif + + if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER) + CrashReporter::UnsetExceptionHandler(); + + XRE_DeinitCommandLine(); + + if (NS_FAILED(rv)) { + return 1; + } + return mozilla::AppShutdown::GetExitCode(); +} + +void XRE_StopLateWriteChecks(void) { mozilla::StopLateWriteChecks(); } + +int XRE_main(int argc, char* argv[], const BootstrapConfig& aConfig) { + XREMain main; + + int result = main.XRE_main(argc, argv, aConfig); + mozilla::RecordShutdownEndTimeStamp(); + return result; +} + +nsresult XRE_InitCommandLine(int aArgc, char* aArgv[]) { + nsresult rv = NS_OK; + +#if defined(OS_WIN) + CommandLine::Init(aArgc, aArgv); +#else + + // these leak on error, but that's OK: we'll just exit() + char** canonArgs = new char*[aArgc]; + + // get the canonical version of the binary's path + nsCOMPtr<nsIFile> binFile; + rv = XRE_GetBinaryPath(getter_AddRefs(binFile)); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + nsAutoCString canonBinPath; + rv = binFile->GetNativePath(canonBinPath); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + canonArgs[0] = strdup(canonBinPath.get()); + + for (int i = 1; i < aArgc; ++i) { + if (aArgv[i]) { + canonArgs[i] = strdup(aArgv[i]); + } + } + + NS_ASSERTION(!CommandLine::IsInitialized(), "Bad news!"); + CommandLine::Init(aArgc, canonArgs); + + for (int i = 0; i < aArgc; ++i) free(canonArgs[i]); + delete[] canonArgs; +#endif + +#if defined(MOZ_WIDGET_ANDROID) + nsCOMPtr<nsIFile> greOmni = gAppData ? gAppData->xreDirectory : GreOmniPath(); + if (!greOmni) { + return NS_ERROR_FAILURE; + } + mozilla::Omnijar::Init(greOmni, greOmni); +#endif + + return rv; +} + +nsresult XRE_DeinitCommandLine() { + nsresult rv = NS_OK; + + CommandLine::Terminate(); + + return rv; +} + +GeckoProcessType XRE_GetProcessType() { + return mozilla::startup::sChildProcessType; +} + +const char* XRE_GetProcessTypeString() { + return XRE_GeckoProcessTypeToString(XRE_GetProcessType()); +} + +bool XRE_IsE10sParentProcess() { +#ifdef MOZ_WIDGET_ANDROID + return XRE_IsParentProcess() && BrowserTabsRemoteAutostart() && + mozilla::jni::IsAvailable(); +#else + return XRE_IsParentProcess() && BrowserTabsRemoteAutostart(); +#endif +} + +#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name, bin_type) \ + bool XRE_Is##xre_name##Process() { \ + return XRE_GetProcessType() == GeckoProcessType_##enum_name; \ + } +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + +bool XRE_UseNativeEventProcessing() { +#if defined(XP_MACOSX) || defined(XP_WIN) + if (XRE_IsRDDProcess() || XRE_IsSocketProcess()) { + return false; + } +#endif + if (XRE_IsContentProcess()) { + return StaticPrefs::dom_ipc_useNativeEventProcessing_content(); + } + + return true; +} + +namespace mozilla { + +uint32_t GetMaxWebProcessCount() { + // multiOptOut is in int to allow us to run multiple experiments without + // introducing multiple prefs a la the autostart.N prefs. + if (Preferences::GetInt("dom.ipc.multiOptOut", 0) >= + nsIXULRuntime::E10S_MULTI_EXPERIMENT) { + return 1; + } + + const char* optInPref = "dom.ipc.processCount"; + uint32_t optInPrefValue = Preferences::GetInt(optInPref, 1); + return std::max(1u, optInPrefValue); +} + +const char* PlatformBuildID() { return gToolkitBuildID; } + +} // namespace mozilla + +void SetupErrorHandling(const char* progname) { +#ifdef XP_WIN + /* On Windows XPSP3 and Windows Vista if DEP is configured off-by-default + we still want DEP protection: enable it explicitly and programmatically. + + This function is not available on WinXPSP2 so we dynamically load it. + */ + + HMODULE kernel32 = GetModuleHandleW(L"kernel32.dll"); + SetProcessDEPPolicyFunc _SetProcessDEPPolicy = + (SetProcessDEPPolicyFunc)GetProcAddress(kernel32, "SetProcessDEPPolicy"); + if (_SetProcessDEPPolicy) _SetProcessDEPPolicy(PROCESS_DEP_ENABLE); +#endif + +#ifdef XP_WIN + // Suppress the "DLL Foo could not be found" dialog, such that if dependent + // libraries (such as GDI+) are not preset, we gracefully fail to load those + // XPCOM components, instead of being ungraceful. + UINT realMode = SetErrorMode(0); + realMode |= SEM_FAILCRITICALERRORS; + // If XRE_NO_WINDOWS_CRASH_DIALOG is set, suppress displaying the "This + // application has crashed" dialog box. This is mainly useful for + // automated testing environments, e.g. tinderbox, where there's no need + // for a dozen of the dialog boxes to litter the console + if (getenv("XRE_NO_WINDOWS_CRASH_DIALOG")) + realMode |= SEM_NOGPFAULTERRORBOX | SEM_NOOPENFILEERRORBOX; + + SetErrorMode(realMode); + +#endif + + InstallSignalHandlers(progname); + + // Unbuffer stdout, needed for tinderbox tests. + setbuf(stdout, 0); +} + +// Note: This function should not be needed anymore. See Bug 818634 for details. +void OverrideDefaultLocaleIfNeeded() { + // Read pref to decide whether to override default locale with US English. + if (mozilla::Preferences::GetBool("javascript.use_us_english_locale", + false)) { + // Set the application-wide C-locale. Needed to resist fingerprinting + // of Date.toLocaleFormat(). We use the locale to "C.UTF-8" if possible, + // to avoid interfering with non-ASCII keyboard input on some Linux + // desktops. Otherwise fall back to the "C" locale, which is available on + // all platforms. + setlocale(LC_ALL, "C.UTF-8") || setlocale(LC_ALL, "C"); + } +} + +static bool gRunSelfAsContentProc = false; + +void XRE_EnableSameExecutableForContentProc() { + if (!PR_GetEnv("MOZ_SEPARATE_CHILD_PROCESS")) { + gRunSelfAsContentProc = true; + } +} + +mozilla::BinPathType XRE_GetChildProcBinPathType( + GeckoProcessType aProcessType) { + MOZ_ASSERT(aProcessType != GeckoProcessType_Default); + + if (!gRunSelfAsContentProc) { + return BinPathType::PluginContainer; + } + + switch (aProcessType) { +#define GECKO_PROCESS_TYPE(enum_name, string_name, xre_name, bin_type) \ + case GeckoProcessType_##enum_name: \ + return BinPathType::bin_type; +#include "mozilla/GeckoProcessTypes.h" +#undef GECKO_PROCESS_TYPE + default: + return BinPathType::PluginContainer; + } +} + +// Because rust doesn't handle weak symbols, this function wraps the weak +// malloc_handle_oom for it. +extern "C" void GeckoHandleOOM(size_t size) { mozalloc_handle_oom(size); } + +// From toolkit/library/rust/shared/lib.rs +extern "C" void install_rust_panic_hook(); +extern "C" void install_rust_oom_hook(); + +struct InstallRustHooks { + InstallRustHooks() { + install_rust_panic_hook(); + install_rust_oom_hook(); + } +}; + +InstallRustHooks sInstallRustHooks; + +#ifdef MOZ_ASAN_REPORTER +void setASanReporterPath(nsIFile* aDir) { + nsCOMPtr<nsIFile> dir; + aDir->Clone(getter_AddRefs(dir)); + + dir->Append(u"asan"_ns); + nsresult rv = dir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_WARN_IF(NS_FAILED(rv) && rv != NS_ERROR_FILE_ALREADY_EXISTS)) { + MOZ_CRASH("[ASan Reporter] Unable to create crash directory."); + } + + dir->Append(u"ff_asan_log"_ns); + +# ifdef XP_WIN + nsAutoString nspathW; + rv = dir->GetPath(nspathW); + NS_ConvertUTF16toUTF8 nspath(nspathW); +# else + nsAutoCString nspath; + rv = dir->GetNativePath(nspath); +# endif + if (NS_FAILED(rv)) { + MOZ_CRASH("[ASan Reporter] Unable to get native path for crash directory."); + } + + __sanitizer_set_report_path(nspath.get()); +} +#endif diff --git a/toolkit/xre/nsAppRunner.h b/toolkit/xre/nsAppRunner.h new file mode 100644 index 0000000000..d0c8218bac --- /dev/null +++ b/toolkit/xre/nsAppRunner.h @@ -0,0 +1,169 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsAppRunner_h__ +#define nsAppRunner_h__ + +#ifdef XP_WIN +# include <windows.h> +# include "mozilla/WindowsConsole.h" +#else +# include <limits.h> +#endif + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#include "nsCOMPtr.h" +#include "nsStringFwd.h" +#include "nsXULAppAPI.h" + +class nsINativeAppSupport; +class nsXREDirProvider; +class nsIToolkitProfileService; +class nsIFile; +class nsIProfileLock; +class nsIProfileUnlocker; +class nsIFactory; + +extern nsXREDirProvider* gDirServiceProvider; + +// NOTE: gAppData will be null in embedded contexts. +extern const mozilla::XREAppData* gAppData; +extern bool gSafeMode; +extern bool gFxREmbedded; + +extern int gArgc; +extern char** gArgv; +extern int gRestartArgc; +extern char** gRestartArgv; +extern bool gRestartedByOS; +extern bool gLogConsoleErrors; +extern nsString gAbsoluteArgv0Path; + +extern bool gIsGtest; + +namespace mozilla { +nsresult AppInfoConstructor(nsISupports* aOuter, const nsID& aIID, + void** aResult); +} // namespace mozilla + +// Exported for gtests. +void BuildCompatVersion(const char* aAppVersion, const char* aAppBuildID, + const char* aToolkitBuildID, nsACString& aBuf); + +/** + * Compares the provided compatibility versions. Returns 0 if they match, + * < 0 if the new version is considered an upgrade from the old version and + * > 0 if the new version is considered a downgrade from the old version. + */ +int32_t CompareCompatVersions(const nsACString& aOldCompatVersion, + const nsACString& aNewCompatVersion); + +/** + * Create the nativeappsupport implementation. + * + * @note XPCOMInit has not happened yet. + */ +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport** aResult); +already_AddRefed<nsINativeAppSupport> NS_GetNativeAppSupport(); + +nsresult NS_NewToolkitProfileService(nsIToolkitProfileService** aResult); + +nsresult NS_NewToolkitProfileFactory(nsIFactory** aResult); + +/** + * Try to acquire exclusive access to the specified profile directory. + * + * @param aPath + * The profile directory to lock. + * @param aTempPath + * The corresponding profile temporary directory. + * @param aUnlocker + * A callback interface used to attempt to unlock a profile that + * appears to be locked. + * @param aResult + * The resulting profile lock object (or null if the profile could + * not be locked). + * + * @return NS_ERROR_FILE_ACCESS_DENIED to indicate that the profile + * directory cannot be unlocked. + */ +nsresult NS_LockProfilePath(nsIFile* aPath, nsIFile* aTempPath, + nsIProfileUnlocker** aUnlocker, + nsIProfileLock** aResult); + +void WriteConsoleLog(); + +void OverrideDefaultLocaleIfNeeded(); + +/** + * Allow exit() calls to complete. This should be done from a proper Gecko + * shutdown path. Otherwise we aim to catch improper shutdowns. + */ +void MozExpectedExit(); + +class nsINativeAppSupport; +nsresult LaunchChild(bool aBlankCommandLine); +void UnlockProfile(); + +#ifdef XP_WIN + +BOOL WinLaunchChild(const wchar_t* exePath, int argc, char** argv, + HANDLE userToken = nullptr, HANDLE* hProcess = nullptr); + +# define PREF_WIN_REGISTER_APPLICATION_RESTART \ + "toolkit.winRegisterApplicationRestart" + +# define PREF_WIN_ALTERED_DLL_PREFETCH "startup.experiments.alteredDllPrefetch" + +# if defined(MOZ_LAUNCHER_PROCESS) +# define PREF_WIN_LAUNCHER_PROCESS_ENABLED "browser.launcherProcess.enabled" +# endif // defined(MOZ_LAUNCHER_PROCESS) +#endif + +namespace mozilla { +namespace startup { +Result<nsCOMPtr<nsIFile>, nsresult> GetIncompleteStartupFile(nsIFile* aProfLD); + +extern GeckoProcessType sChildProcessType; +} // namespace startup + +const char* PlatformBuildID(); + +bool RunningGTest(); + +} // namespace mozilla + +/** + * Set up platform specific error handling such as suppressing DLL load dialog + * and the JIT debugger on Windows, and install unix signal handlers. + */ +void SetupErrorHandling(const char* progname); + +#ifdef MOZ_ASAN_REPORTER +extern "C" { +void MOZ_EXPORT __sanitizer_set_report_path(const char* path); +} +void setASanReporterPath(nsIFile* aDir); +#endif + +#ifdef MOZ_WAYLAND +bool IsWaylandDisabled(); +#endif +#ifdef MOZ_X11 +bool IsX11EGLEnabled(); +#endif + +#endif // nsAppRunner_h__ diff --git a/toolkit/xre/nsAppStartupNotifier.cpp b/toolkit/xre/nsAppStartupNotifier.cpp new file mode 100644 index 0000000000..8a00ff88a8 --- /dev/null +++ b/toolkit/xre/nsAppStartupNotifier.cpp @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAppStartupNotifier.h" +#include "nsServiceManagerUtils.h" +#include "nsComponentManagerUtils.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsICategoryManager.h" +#include "nsIObserver.h" +#include "nsXPCOM.h" +#include "mozilla/SimpleEnumerator.h" + +using namespace mozilla; + +/* static */ +nsresult nsAppStartupNotifier::NotifyObservers(const char* aCategory) { + NS_ENSURE_ARG(aCategory); + nsresult rv; + + // now initialize all startup listeners + nsCOMPtr<nsICategoryManager> categoryManager = + do_GetService(NS_CATEGORYMANAGER_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsDependentCString category(aCategory); + + nsCOMPtr<nsISimpleEnumerator> enumerator; + rv = categoryManager->EnumerateCategory(category, getter_AddRefs(enumerator)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + for (auto& categoryEntry : SimpleEnumerator<nsICategoryEntry>(enumerator)) { + nsAutoCString contractId; + categoryEntry->GetValue(contractId); + + nsCOMPtr<nsISupports> startupInstance; + + // If we see the word "service," in the beginning + // of the contractId then we create it as a service + // if not we do a createInstance + if (StringBeginsWith(contractId, "service,"_ns)) { + startupInstance = do_GetService(contractId.get() + 8, &rv); + } else { + startupInstance = do_CreateInstance(contractId.get(), &rv); + } + + if (NS_SUCCEEDED(rv)) { + // Try to QI to nsIObserver + nsCOMPtr<nsIObserver> startupObserver = + do_QueryInterface(startupInstance, &rv); + if (NS_SUCCEEDED(rv)) { + rv = startupObserver->Observe(nullptr, aCategory, nullptr); + + // mainly for debugging if you want to know if your observer worked. + NS_ASSERTION(NS_SUCCEEDED(rv), "Startup Observer failed!\n"); + } + } else { +#ifdef DEBUG + nsAutoCString warnStr("Cannot create startup observer : "); + warnStr += contractId.get(); + NS_WARNING(warnStr.get()); +#endif + } + } + + return NS_OK; +} diff --git a/toolkit/xre/nsAppStartupNotifier.h b/toolkit/xre/nsAppStartupNotifier.h new file mode 100644 index 0000000000..64b9da4f23 --- /dev/null +++ b/toolkit/xre/nsAppStartupNotifier.h @@ -0,0 +1,17 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsAppStartupNotifier_h___ +#define nsAppStartupNotifier_h___ + +#include "nsIAppStartupNotifier.h" +#include "nsError.h" + +class nsAppStartupNotifier final { + public: + static nsresult NotifyObservers(const char* aTopic); +}; + +#endif /* nsAppStartupNotifier_h___ */ diff --git a/toolkit/xre/nsCommandLineServiceMac.h b/toolkit/xre/nsCommandLineServiceMac.h new file mode 100644 index 0000000000..caa37fdc52 --- /dev/null +++ b/toolkit/xre/nsCommandLineServiceMac.h @@ -0,0 +1,20 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsCommandLineServiceMac_h_ +#define nsCommandLineServiceMac_h_ + +#include "nscore.h" + +namespace CommandLineServiceMac { +void SetupMacCommandLine(int& argc, char**& argv, bool forRestart); + +// Add a URL to the command line currently being set up via +// SetupMacCommandLine. Returns false if no command line is +// being set up or the addition fails for any other reason. +bool AddURLToCurrentCommandLine(const char* aURL); +} // namespace CommandLineServiceMac + +#endif // nsCommandLineServiceMac_h_ diff --git a/toolkit/xre/nsCommandLineServiceMac.mm b/toolkit/xre/nsCommandLineServiceMac.mm new file mode 100644 index 0000000000..3406bea62d --- /dev/null +++ b/toolkit/xre/nsCommandLineServiceMac.mm @@ -0,0 +1,92 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsCommandLineServiceMac.h" +#include "MacApplicationDelegate.h" + +namespace CommandLineServiceMac { + +static const int kArgsGrowSize = 20; + +static char** sArgs = nullptr; +static int sArgsAllocated = 0; +static int sArgsUsed = 0; + +static bool sBuildingCommandLine = false; + +void AddToCommandLine(const char* inArgText) { + if (sArgsUsed >= sArgsAllocated - 1) { + // realloc does not free the given pointer if allocation fails + char** temp = + static_cast<char**>(realloc(sArgs, (sArgsAllocated + kArgsGrowSize) * sizeof(char*))); + if (!temp) return; + sArgs = temp; + sArgsAllocated += kArgsGrowSize; + } + + char* temp2 = strdup(inArgText); + if (!temp2) return; + + sArgs[sArgsUsed++] = temp2; + sArgs[sArgsUsed] = nullptr; + + return; +} + +// Caller has ownership of argv and is responsible for freeing the allocated +// memory. +void SetupMacCommandLine(int& argc, char**& argv, bool forRestart) { + sArgs = static_cast<char**>(malloc(kArgsGrowSize * sizeof(char*))); + if (!sArgs) return; + sArgsAllocated = kArgsGrowSize; + sArgs[0] = nullptr; + sArgsUsed = 0; + + sBuildingCommandLine = true; + + // Copy args, stripping anything we don't want. + for (int arg = 0; arg < argc; arg++) { + char* flag = argv[arg]; + // Don't pass on the psn (Process Serial Number) flag from the OS, or + // the "-foreground" flag since it will be set below if necessary. + if (strncmp(flag, "-psn_", 5) != 0 && strncmp(flag, "-foreground", 11) != 0) + AddToCommandLine(flag); + } + + // Force processing of any pending Apple GetURL Events while we're building + // the command line. The handlers will append to the command line rather than + // act directly so there is no chance we'll process them during a XUL window + // load and accidentally open unnecessary windows and home pages. + ProcessPendingGetURLAppleEvents(); + + // If the process will be relaunched, the child should be in the foreground + // if the parent is in the foreground. This will be communicated in a + // command-line argument to the child. + if (forRestart) { + NSRunningApplication* frontApp = [[NSWorkspace sharedWorkspace] frontmostApplication]; + if ([frontApp isEqual:[NSRunningApplication currentApplication]]) { + AddToCommandLine("-foreground"); + } + } + + sBuildingCommandLine = false; + + free(argv); + argc = sArgsUsed; + argv = sArgs; +} + +bool AddURLToCurrentCommandLine(const char* aURL) { + if (!sBuildingCommandLine) { + return false; + } + + AddToCommandLine("-url"); + AddToCommandLine(aURL); + + return true; +} + +} // namespace CommandLineServiceMac diff --git a/toolkit/xre/nsConsoleWriter.cpp b/toolkit/xre/nsConsoleWriter.cpp new file mode 100644 index 0000000000..d89ea3bde3 --- /dev/null +++ b/toolkit/xre/nsConsoleWriter.cpp @@ -0,0 +1,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/. */ + +#include "nsAppRunner.h" + +#include "prio.h" +#include "prprf.h" +#include "prenv.h" + +#include "nsCRT.h" +#include "nsNativeCharsetUtils.h" +#include "nsString.h" +#include "nsXREDirProvider.h" +#include "nsXULAppAPI.h" + +#include "nsIConsoleService.h" +#include "nsIConsoleMessage.h" + +void WriteConsoleLog() { + nsresult rv; + + nsCOMPtr<nsIFile> lfile; + + char* logFileEnv = PR_GetEnv("XRE_CONSOLE_LOG"); + if (logFileEnv && *logFileEnv) { + rv = XRE_GetFileFromPath(logFileEnv, getter_AddRefs(lfile)); + if (NS_FAILED(rv)) return; + } else { + if (!gLogConsoleErrors) return; + + rv = nsXREDirProvider::GetUserAppDataDirectory(getter_AddRefs(lfile)); + if (NS_FAILED(rv)) return; + + lfile->AppendNative("console.log"_ns); + } + + PRFileDesc* file; + rv = lfile->OpenNSPRFileDesc(PR_WRONLY | PR_APPEND | PR_CREATE_FILE, 0660, + &file); + if (NS_FAILED(rv)) return; + + nsCOMPtr<nsIConsoleService> csrv(do_GetService(NS_CONSOLESERVICE_CONTRACTID)); + if (!csrv) { + PR_Close(file); + return; + } + + nsTArray<RefPtr<nsIConsoleMessage>> messages; + + rv = csrv->GetMessageArray(messages); + if (NS_FAILED(rv)) { + PR_Close(file); + return; + } + + if (!messages.IsEmpty()) { + PRExplodedTime etime; + PR_ExplodeTime(PR_Now(), PR_LocalTimeParameters, &etime); + char datetime[512]; + PR_FormatTimeUSEnglish(datetime, sizeof(datetime), "%Y-%m-%d %H:%M:%S", + &etime); + + PR_fprintf(file, NS_LINEBREAK "*** Console log: %s ***" NS_LINEBREAK, + datetime); + } + + nsString msg; + nsAutoCString nativemsg; + + for (auto& message : messages) { + rv = message->GetMessageMoz(msg); + if (NS_SUCCEEDED(rv)) { + NS_CopyUnicodeToNative(msg, nativemsg); + PR_fprintf(file, "%s" NS_LINEBREAK, nativemsg.get()); + } + } + + PR_Close(file); +} diff --git a/toolkit/xre/nsEmbedFunctions.cpp b/toolkit/xre/nsEmbedFunctions.cpp new file mode 100644 index 0000000000..046e9105eb --- /dev/null +++ b/toolkit/xre/nsEmbedFunctions.cpp @@ -0,0 +1,1003 @@ +/* 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/. */ + +#include "mozilla/DebugOnly.h" + +#include "nsXULAppAPI.h" + +#include <stdlib.h> +#if defined(MOZ_WIDGET_GTK) +# include <glib.h> +#endif + +#include "prenv.h" + +#include "nsIAppShell.h" +#include "nsAppStartupNotifier.h" +#include "nsIToolkitProfile.h" + +#ifdef XP_WIN +# include <process.h> +# include <shobjidl.h> +# include "mozilla/ipc/WindowsMessageLoop.h" +# include "mozilla/ScopeExit.h" +# include "mozilla/WinDllServices.h" +#endif + +#include "nsAppRunner.h" +#include "nsExceptionHandler.h" +#include "nsString.h" +#include "nsThreadUtils.h" +#include "nsJSUtils.h" +#include "nsWidgetsCID.h" +#include "nsXREDirProvider.h" +#ifdef MOZ_ASAN_REPORTER +# include "CmdLineAndEnvUtils.h" +# include "nsIFile.h" +#endif + +#include "mozilla/Omnijar.h" +#if defined(XP_MACOSX) +# include "nsVersionComparator.h" +# include "chrome/common/mach_ipc_mac.h" +#endif +#include "nsGDKErrorHandler.h" +#include "base/at_exit.h" +#include "base/message_loop.h" +#include "base/process_util.h" +#if defined(MOZ_WIDGET_ANDROID) +# include "chrome/common/ipc_channel.h" +# include "mozilla/jni/Utils.h" +# include "ProcessUtils.h" +#endif // defined(MOZ_WIDGET_ANDROID) + +#include "mozilla/AbstractThread.h" +#include "mozilla/FilePreferences.h" +#include "mozilla/IOInterposer.h" +#include "mozilla/RDDProcessImpl.h" +#include "mozilla/UniquePtr.h" + +#include "mozilla/ipc/BrowserProcessSubThread.h" +#include "mozilla/ipc/IOThreadChild.h" +#include "mozilla/ipc/ProcessChild.h" + +#include "mozilla/plugins/PluginProcessChild.h" +#include "mozilla/dom/ContentProcess.h" +#include "mozilla/dom/ContentParent.h" + +#include "mozilla/ipc/TestShellParent.h" +#if defined(XP_WIN) +# include "mozilla/WindowsConsole.h" +# include "mozilla/WindowsDllBlocklist.h" +#endif + +#include "GMPProcessChild.h" +#include "mozilla/gfx/GPUProcessImpl.h" +#include "mozilla/net/SocketProcessImpl.h" + +#include "GeckoProfiler.h" +#include "BaseProfiler.h" + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) +# include "mozilla/sandboxTarget.h" +# include "mozilla/sandboxing/loggingCallbacks.h" +# include "mozilla/RemoteSandboxBrokerProcessChild.h" +#endif + +#if defined(MOZ_SANDBOX) +# include "XREChildData.h" +# include "mozilla/SandboxSettings.h" +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) +# include "mozilla/Sandbox.h" +#endif + +#if defined(XP_LINUX) +# include <sys/prctl.h> +# ifndef PR_SET_PTRACER +# define PR_SET_PTRACER 0x59616d61 +# endif +# ifndef PR_SET_PTRACER_ANY +# define PR_SET_PTRACER_ANY ((unsigned long)-1) +# endif +#endif + +#ifdef MOZ_IPDL_TESTS +# include "mozilla/_ipdltest/IPDLUnitTests.h" +# include "mozilla/_ipdltest/IPDLUnitTestProcessChild.h" + +using mozilla::_ipdltest::IPDLUnitTestProcessChild; +#endif // ifdef MOZ_IPDL_TESTS + +#ifdef MOZ_JPROF +# include "jprof.h" +#endif + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) +# include "mozilla/sandboxing/SandboxInitialization.h" +# include "mozilla/sandboxing/sandboxLogging.h" +#endif + +#if defined(MOZ_ENABLE_FORKSERVER) +# include "mozilla/ipc/ForkServer.h" +#endif + +#include "VRProcessChild.h" + +using namespace mozilla; + +using mozilla::ipc::BrowserProcessSubThread; +using mozilla::ipc::GeckoChildProcessHost; +using mozilla::ipc::IOThreadChild; +using mozilla::ipc::ProcessChild; +using mozilla::ipc::ScopedXREEmbed; + +using mozilla::dom::ContentParent; +using mozilla::dom::ContentProcess; +using mozilla::plugins::PluginProcessChild; + +using mozilla::gmp::GMPProcessChild; + +using mozilla::ipc::TestShellCommandParent; +using mozilla::ipc::TestShellParent; + +using mozilla::startup::sChildProcessType; + +static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID); + +nsresult XRE_LockProfileDirectory(nsIFile* aDirectory, + nsISupports** aLockObject) { + nsCOMPtr<nsIProfileLock> lock; + + nsresult rv = + NS_LockProfilePath(aDirectory, nullptr, nullptr, getter_AddRefs(lock)); + if (NS_SUCCEEDED(rv)) NS_ADDREF(*aLockObject = lock); + + return rv; +} + +static int32_t sInitCounter; + +nsresult XRE_InitEmbedding2(nsIFile* aLibXULDirectory, nsIFile* aAppDirectory, + nsIDirectoryServiceProvider* aAppDirProvider) { + // Initialize some globals to make nsXREDirProvider happy + static char* kNullCommandLine[] = {nullptr}; + gArgv = kNullCommandLine; + gArgc = 0; + + NS_ENSURE_ARG(aLibXULDirectory); + + if (++sInitCounter > 1) // XXXbsmedberg is this really the right solution? + return NS_OK; + + if (!aAppDirectory) aAppDirectory = aLibXULDirectory; + + nsresult rv; + + new nsXREDirProvider; // This sets gDirServiceProvider + if (!gDirServiceProvider) return NS_ERROR_OUT_OF_MEMORY; + + rv = gDirServiceProvider->Initialize(aAppDirectory, aLibXULDirectory, + aAppDirProvider); + if (NS_FAILED(rv)) return rv; + + rv = NS_InitXPCOM(nullptr, aAppDirectory, gDirServiceProvider); + if (NS_FAILED(rv)) return rv; + + // We do not need to autoregister components here. The CheckCompatibility() + // bits in nsAppRunner.cpp check for an invalidation flag in + // compatibility.ini. + // If the app wants to autoregister every time (for instance, if it's debug), + // it can do so after we return from this function. + + nsAppStartupNotifier::NotifyObservers(APPSTARTUP_CATEGORY); + + return NS_OK; +} + +void XRE_NotifyProfile() { + NS_ASSERTION(gDirServiceProvider, "XRE_InitEmbedding was not called!"); + gDirServiceProvider->DoStartup(); +} + +void XRE_TermEmbedding() { + if (--sInitCounter != 0) return; + + NS_ASSERTION(gDirServiceProvider, + "XRE_TermEmbedding without XRE_InitEmbedding"); + + gDirServiceProvider->DoShutdown(); + NS_ShutdownXPCOM(nullptr); + delete gDirServiceProvider; +} + +const char* XRE_GeckoProcessTypeToString(GeckoProcessType aProcessType) { + return (aProcessType < GeckoProcessType_End) + ? kGeckoProcessTypeString[aProcessType] + : "invalid"; +} + +const char* XRE_ChildProcessTypeToAnnotation(GeckoProcessType aProcessType) { + switch (aProcessType) { + case GeckoProcessType_GMPlugin: + // The gecko media plugin and normal plugin processes are lumped together + // as a historical artifact. + return "plugin"; + case GeckoProcessType_Default: + return ""; + case GeckoProcessType_Content: + return "content"; + default: + return XRE_GeckoProcessTypeToString(aProcessType); + } +} + +namespace mozilla::startup { +GeckoProcessType sChildProcessType = GeckoProcessType_Default; +} // namespace mozilla::startup + +#if defined(MOZ_WIDGET_ANDROID) +void XRE_SetAndroidChildFds(JNIEnv* env, const XRE_AndroidChildFds& fds) { + mozilla::jni::SetGeckoThreadEnv(env); + mozilla::ipc::SetPrefsFd(fds.mPrefsFd); + mozilla::ipc::SetPrefMapFd(fds.mPrefMapFd); + IPC::Channel::SetClientChannelFd(fds.mIpcFd); + CrashReporter::SetNotificationPipeForChild(fds.mCrashFd); + CrashReporter::SetCrashAnnotationPipeForChild(fds.mCrashAnnotationFd); +} +#endif // defined(MOZ_WIDGET_ANDROID) + +void XRE_SetProcessType(const char* aProcessTypeString) { + static bool called = false; + if (called && sChildProcessType != GeckoProcessType_ForkServer) { + MOZ_CRASH(); + } + called = true; + + sChildProcessType = GeckoProcessType_Invalid; + for (int i = 0; i < (int)ArrayLength(kGeckoProcessTypeString); ++i) { + if (!strcmp(kGeckoProcessTypeString[i], aProcessTypeString)) { + sChildProcessType = static_cast<GeckoProcessType>(i); + return; + } + } +} + +#if defined(XP_WIN) +void SetTaskbarGroupId(const nsString& aId) { + if (FAILED(SetCurrentProcessExplicitAppUserModelID(aId.get()))) { + NS_WARNING( + "SetCurrentProcessExplicitAppUserModelID failed for child process."); + } +} +#endif + +#if defined(MOZ_SANDBOX) +void AddContentSandboxLevelAnnotation() { + if (XRE_GetProcessType() == GeckoProcessType_Content) { + int level = GetEffectiveContentSandboxLevel(); + CrashReporter::AnnotateCrashReport( + CrashReporter::Annotation::ContentSandboxLevel, level); + } +} +#endif /* MOZ_SANDBOX */ + +namespace { + +int GetDebugChildPauseTime() { + auto pauseStr = PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE"); + if (pauseStr && *pauseStr) { + int pause = atoi(pauseStr); + if (pause != 1) { // must be !=1 since =1 enables the default pause time +#if defined(OS_WIN) + pause *= 1000; // convert to ms +#endif + return pause; + } + } +#ifdef OS_POSIX + return 30; // seconds +#elif defined(OS_WIN) + return 10000; // milliseconds +#else + return 0; +#endif +} + +static bool IsCrashReporterEnabled(const char* aArg) { + // on windows and mac, |aArg| is the named pipe on which the server is + // listening for requests, or "-" if crash reporting is disabled. +#if defined(XP_MACOSX) || defined(XP_WIN) + return 0 != strcmp("-", aArg); +#else + // on POSIX, |aArg| is "true" if crash reporting is enabled, false otherwise + return 0 != strcmp("false", aArg); +#endif +} + +} // namespace + +nsresult XRE_InitChildProcess(int aArgc, char* aArgv[], + const XREChildData* aChildData) { + NS_ENSURE_ARG_MIN(aArgc, 2); + NS_ENSURE_ARG_POINTER(aArgv); + NS_ENSURE_ARG_POINTER(aArgv[0]); + MOZ_ASSERT(aChildData); + + NS_SetCurrentThreadName("MainThread"); + +#ifdef MOZ_ASAN_REPORTER + // In ASan reporter builds, we need to set ASan's log_path as early as + // possible, so it dumps its errors into files there instead of using + // the default stderr location. Since this is crucial for ASan reporter + // to work at all (and we don't want people to use a non-functional + // ASan reporter build), all failures while setting log_path are fatal. + // + // We receive this log_path via the ASAN_REPORTER_PATH environment variable + // because there is no other way to generically get the necessary profile + // directory in all child types without adding support for that in each + // child process type class (at the risk of missing this in a child). + // + // In certain cases (e.g. child startup through xpcshell or gtests), this + // code needs to remain disabled, as no ASAN_REPORTER_PATH would be available. + if (!PR_GetEnv("MOZ_DISABLE_ASAN_REPORTER") && !PR_GetEnv("MOZ_RUN_GTEST")) { + nsCOMPtr<nsIFile> asanReporterPath = GetFileFromEnv("ASAN_REPORTER_PATH"); + if (!asanReporterPath) { + MOZ_CRASH("Child did not receive ASAN_REPORTER_PATH!"); + } + setASanReporterPath(asanReporterPath); + } +#endif + +#if defined(XP_LINUX) && defined(MOZ_SANDBOX) + // This has to happen before glib thread pools are started. + mozilla::SandboxEarlyInit(); + // This just needs to happen before sandboxing, to initialize the + // cached value, but libmozsandbox can't see this symbol. + mozilla::GetNumberOfProcessors(); +#endif + +#ifdef MOZ_JPROF + // Call the code to install our handler + setupProfilingStuff(); +#endif + +#if defined(XP_WIN) + // From the --attach-console support in nsNativeAppSupportWin.cpp, but + // here we are a content child process, so we always attempt to attach + // to the parent's (ie, the browser's) console. + // Try to attach console to the parent process. + // It will succeed when the parent process is a command line, + // so that stdio will be displayed in it. + UseParentConsole(); + +# if defined(MOZ_SANDBOX) + if (aChildData->sandboxTargetServices) { + SandboxTarget::Instance()->SetTargetServices( + aChildData->sandboxTargetServices); + } +# endif +#endif + + // NB: This must be called before profiler_init + ScopedLogging logger; + + mozilla::LogModule::Init(aArgc, aArgv); + + AUTO_BASE_PROFILER_LABEL("XRE_InitChildProcess (around Gecko Profiler)", + OTHER); + AUTO_PROFILER_INIT; + AUTO_PROFILER_LABEL("XRE_InitChildProcess", OTHER); + + // Ensure AbstractThread is minimally setup, so async IPC messages + // work properly. + AbstractThread::InitTLS(); + + // Complete 'task_t' exchange for Mac OS X. This structure has the same size + // regardless of architecture so we don't have any cross-arch issues here. +#ifdef XP_MACOSX + if (aArgc < 1) return NS_ERROR_FAILURE; + +# if defined(MOZ_SANDBOX) + // Save the original number of arguments to pass to the sandbox + // setup routine which also uses the crash server argument. + int allArgc = aArgc; +# endif /* MOZ_SANDBOX */ + + const char* const mach_port_name = aArgv[--aArgc]; + + const int kTimeoutMs = 1000; + + MachSendMessage child_message(0); + if (!child_message.AddDescriptor(MachMsgPortDescriptor(mach_task_self()))) { + NS_WARNING("child AddDescriptor(mach_task_self()) failed."); + return NS_ERROR_FAILURE; + } + + ReceivePort child_recv_port; + mach_port_t raw_child_recv_port = child_recv_port.GetPort(); + if (!child_message.AddDescriptor( + MachMsgPortDescriptor(raw_child_recv_port))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + ReceivePort* ports_out_receiver = new ReceivePort(); + if (!child_message.AddDescriptor( + MachMsgPortDescriptor(ports_out_receiver->GetPort()))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + ReceivePort* ports_in_receiver = new ReceivePort(); + if (!child_message.AddDescriptor( + MachMsgPortDescriptor(ports_in_receiver->GetPort()))) { + NS_WARNING("Adding descriptor to message failed"); + return NS_ERROR_FAILURE; + } + + MachPortSender child_sender(mach_port_name); + kern_return_t err = child_sender.SendMessage(child_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + NS_WARNING("child SendMessage() failed"); + return NS_ERROR_FAILURE; + } + + MachReceiveMessage parent_message; + err = child_recv_port.WaitForMessage(&parent_message, kTimeoutMs); + if (err != KERN_SUCCESS) { + NS_WARNING("child WaitForMessage() failed"); + return NS_ERROR_FAILURE; + } + + if (parent_message.GetTranslatedPort(0) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(0) failed"); + return NS_ERROR_FAILURE; + } + + err = task_set_bootstrap_port(mach_task_self(), + parent_message.GetTranslatedPort(0)); + + if (parent_message.GetTranslatedPort(1) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(1) failed"); + return NS_ERROR_FAILURE; + } + MachPortSender* ports_out_sender = + new MachPortSender(parent_message.GetTranslatedPort(1)); + + if (parent_message.GetTranslatedPort(2) == MACH_PORT_NULL) { + NS_WARNING("child GetTranslatedPort(2) failed"); + return NS_ERROR_FAILURE; + } + MachPortSender* ports_in_sender = + new MachPortSender(parent_message.GetTranslatedPort(2)); + + if (err != KERN_SUCCESS) { + NS_WARNING("child task_set_bootstrap_port() failed"); + return NS_ERROR_FAILURE; + } + +# if defined(MOZ_SANDBOX) + std::string sandboxError; + if (!GeckoChildProcessHost::StartMacSandbox(allArgc, aArgv, sandboxError)) { + printf_stderr("Sandbox error: %s\n", sandboxError.c_str()); + MOZ_CRASH("Sandbox initialization failed"); + } +# endif /* MOZ_SANDBOX */ + +#endif /* XP_MACOSX */ + + SetupErrorHandling(aArgv[0]); + + bool exceptionHandlerIsSet = false; + if (!CrashReporter::IsDummy()) { +#if defined(XP_WIN) + if (aArgc < 1) { + return NS_ERROR_FAILURE; + } + const char* const crashTimeAnnotationArg = aArgv[--aArgc]; + uintptr_t crashTimeAnnotationFile = + static_cast<uintptr_t>(std::stoul(std::string(crashTimeAnnotationArg))); +#endif + + if (aArgc < 1) return NS_ERROR_FAILURE; + const char* const crashReporterArg = aArgv[--aArgc]; + + if (IsCrashReporterEnabled(crashReporterArg)) { +#if defined(XP_MACOSX) + exceptionHandlerIsSet = + CrashReporter::SetRemoteExceptionHandler(crashReporterArg); +#elif defined(XP_WIN) + exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler( + crashReporterArg, crashTimeAnnotationFile); +#else + exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler(); +#endif + + if (!exceptionHandlerIsSet) { + // Bug 684322 will add better visibility into this condition + NS_WARNING("Could not setup crash reporting\n"); + } + } + } + + gArgv = aArgv; + gArgc = aArgc; + +#ifdef MOZ_X11 + XInitThreads(); +#endif +#ifdef MOZ_WIDGET_GTK + // Setting the name here avoids the need to pass this through to gtk_init(). + g_set_prgname(aArgv[0]); +#endif + +#ifdef OS_POSIX + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS") || + PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { +# if defined(XP_LINUX) && defined(DEBUG) + if (prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY, 0, 0, 0) != 0) { + printf_stderr("Could not allow ptrace from any process.\n"); + } +# endif + printf_stderr( + "\n\nCHILDCHILDCHILDCHILD (process type %s)\n debug me @ %d\n\n", + XRE_GetProcessTypeString(), base::GetCurrentProcId()); + sleep(GetDebugChildPauseTime()); + } +#elif defined(OS_WIN) + if (PR_GetEnv("MOZ_DEBUG_CHILD_PROCESS")) { + NS_DebugBreak(NS_DEBUG_BREAK, + "Invoking NS_DebugBreak() to debug child process", nullptr, + __FILE__, __LINE__); + } else if (PR_GetEnv("MOZ_DEBUG_CHILD_PAUSE")) { + printf_stderr( + "\n\nCHILDCHILDCHILDCHILD (process type %s)\n debug me @ %d\n\n", + XRE_GetProcessTypeString(), base::GetCurrentProcId()); + ::Sleep(GetDebugChildPauseTime()); + } +#endif + + // child processes launched by GeckoChildProcessHost get this magic + // argument appended to their command lines + const char* const parentPIDString = aArgv[aArgc - 1]; + MOZ_ASSERT(parentPIDString, "NULL parent PID"); + --aArgc; + + char* end = 0; + base::ProcessId parentPID = strtol(parentPIDString, &end, 10); + MOZ_ASSERT(!*end, "invalid parent PID"); + +#ifdef XP_MACOSX + mozilla::ipc::SharedMemoryBasic::SetupMachMemory( + parentPID, ports_in_receiver, ports_in_sender, ports_out_sender, + ports_out_receiver, true); +#endif + +#if defined(XP_WIN) + // On Win7+, register the application user model id passed in by + // parent. This insures windows created by the container properly + // group with the parent app on the Win7 taskbar. + const char* const appModelUserId = aArgv[--aArgc]; + if (appModelUserId) { + // '-' implies no support + if (*appModelUserId != '-') { + nsString appId; + CopyASCIItoUTF16(nsDependentCString(appModelUserId), appId); + // The version string is encased in quotes + appId.Trim("\""); + // Set the id + SetTaskbarGroupId(appId); + } + } +#endif + + base::AtExitManager exitManager; + + nsresult rv = XRE_InitCommandLine(aArgc, aArgv); + if (NS_FAILED(rv)) { + return NS_ERROR_FAILURE; + } + + MessageLoop::Type uiLoopType; + switch (XRE_GetProcessType()) { + case GeckoProcessType_Content: + case GeckoProcessType_GPU: + case GeckoProcessType_VR: + case GeckoProcessType_RDD: + case GeckoProcessType_Socket: + // Content processes need the XPCOM/chromium frankenventloop + uiLoopType = MessageLoop::TYPE_MOZILLA_CHILD; + break; + case GeckoProcessType_GMPlugin: + case GeckoProcessType_RemoteSandboxBroker: + uiLoopType = MessageLoop::TYPE_DEFAULT; + break; + default: + uiLoopType = MessageLoop::TYPE_UI; + break; + } + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + if (aChildData->sandboxBrokerServices) { + SandboxBroker::Initialize(aChildData->sandboxBrokerServices); + SandboxBroker::GeckoDependentInitialize(); + } +#endif + + { + // This is a lexical scope for the MessageLoop below. We want it + // to go out of scope before NS_LogTerm() so that we don't get + // spurious warnings about XPCOM objects being destroyed from a + // static context. + + Maybe<IOInterposerInit> ioInterposerGuard; + + // Associate this thread with a UI MessageLoop + MessageLoop uiMessageLoop(uiLoopType); + { + UniquePtr<ProcessChild> process; + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + MOZ_CRASH("This makes no sense"); + break; + + case GeckoProcessType_Plugin: + process = MakeUnique<PluginProcessChild>(parentPID); + break; + + case GeckoProcessType_Content: + process = MakeUnique<ContentProcess>(parentPID); + break; + + case GeckoProcessType_IPDLUnitTest: +#ifdef MOZ_IPDL_TESTS + process = MakeUnique<IPDLUnitTestProcessChild>(parentPID); +#else + MOZ_CRASH("rebuild with --enable-ipdl-tests"); +#endif + break; + + case GeckoProcessType_GMPlugin: + process = MakeUnique<gmp::GMPProcessChild>(parentPID); + break; + + case GeckoProcessType_GPU: + process = MakeUnique<gfx::GPUProcessImpl>(parentPID); + break; + + case GeckoProcessType_VR: + process = MakeUnique<gfx::VRProcessChild>(parentPID); + break; + + case GeckoProcessType_RDD: + process = MakeUnique<RDDProcessImpl>(parentPID); + break; + + case GeckoProcessType_Socket: + ioInterposerGuard.emplace(); + process = MakeUnique<net::SocketProcessImpl>(parentPID); + break; + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + case GeckoProcessType_RemoteSandboxBroker: + process = MakeUnique<RemoteSandboxBrokerProcessChild>(parentPID); + break; +#endif + +#if defined(MOZ_ENABLE_FORKSERVER) + case GeckoProcessType_ForkServer: + MOZ_CRASH("Fork server should not go here"); + break; +#endif + default: + MOZ_CRASH("Unknown main thread class"); + } + + if (!process->Init(aArgc, aArgv)) { + return NS_ERROR_FAILURE; + } + +#if defined(XP_WIN) + // Set child processes up such that they will get killed after the + // chrome process is killed in cases where the user shuts the system + // down or logs off. + ::SetProcessShutdownParameters(0x280 - 1, SHUTDOWN_NORETRY); + + RefPtr<DllServices> dllSvc(DllServices::Get()); + auto dllSvcDisable = + MakeScopeExit([&dllSvc]() { dllSvc->DisableFull(); }); +#endif + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + // We need to do this after the process has been initialised, as + // InitLoggingIfRequired may need access to prefs. + mozilla::sandboxing::InitLoggingIfRequired( + aChildData->ProvideLogFunction); +#endif + if (XRE_GetProcessType() != GeckoProcessType_RemoteSandboxBroker) { + // Remote sandbox launcher process doesn't have prerequisites for + // these... + mozilla::FilePreferences::InitDirectoriesWhitelist(); + mozilla::FilePreferences::InitPrefs(); + OverrideDefaultLocaleIfNeeded(); + } + +#if defined(MOZ_SANDBOX) + AddContentSandboxLevelAnnotation(); +#endif + + // Run the UI event loop on the main thread. + uiMessageLoop.MessageLoop::Run(); + + // Allow ProcessChild to clean up after itself before going out of + // scope and being deleted + process->CleanUp(); + mozilla::Omnijar::CleanUp(); + +#if defined(XP_MACOSX) + // Everybody should be done using shared memory by now. + mozilla::ipc::SharedMemoryBasic::Shutdown(); +#endif + } + } + + if (exceptionHandlerIsSet) { + CrashReporter::UnsetRemoteExceptionHandler(); + } + + return XRE_DeinitCommandLine(); +} + +MessageLoop* XRE_GetIOMessageLoop() { + if (sChildProcessType == GeckoProcessType_Default) { + return BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO); + } + return IOThreadChild::message_loop(); +} + +namespace { + +class MainFunctionRunnable : public Runnable { + public: + NS_DECL_NSIRUNNABLE + + MainFunctionRunnable(MainFunction aFunction, void* aData) + : mozilla::Runnable("MainFunctionRunnable"), + mFunction(aFunction), + mData(aData) { + NS_ASSERTION(aFunction, "Don't give me a null pointer!"); + } + + private: + MainFunction mFunction; + void* mData; +}; + +} /* anonymous namespace */ + +NS_IMETHODIMP +MainFunctionRunnable::Run() { + mFunction(mData); + return NS_OK; +} + +nsresult XRE_InitParentProcess(int aArgc, char* aArgv[], + MainFunction aMainFunction, + void* aMainFunctionData) { + NS_ENSURE_ARG_MIN(aArgc, 1); + NS_ENSURE_ARG_POINTER(aArgv); + NS_ENSURE_ARG_POINTER(aArgv[0]); + + // Set main thread before we initialize the profiler + NS_SetMainThread(); + + mozilla::LogModule::Init(aArgc, aArgv); + + AUTO_BASE_PROFILER_LABEL("XRE_InitParentProcess (around Gecko Profiler)", + OTHER); + AUTO_PROFILER_INIT; + AUTO_PROFILER_LABEL("XRE_InitParentProcess", OTHER); + + ScopedXREEmbed embed; + + gArgc = aArgc; + gArgv = aArgv; + nsresult rv = XRE_InitCommandLine(gArgc, gArgv); + if (NS_FAILED(rv)) return NS_ERROR_FAILURE; + + { + embed.Start(); + + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); + + if (aMainFunction) { + nsCOMPtr<nsIRunnable> runnable = + new MainFunctionRunnable(aMainFunction, aMainFunctionData); + NS_ENSURE_TRUE(runnable, NS_ERROR_OUT_OF_MEMORY); + + nsresult rv = NS_DispatchToCurrentThread(runnable); + NS_ENSURE_SUCCESS(rv, rv); + } + + // Do event loop + if (NS_FAILED(appShell->Run())) { + NS_WARNING("Failed to run appshell"); + return NS_ERROR_FAILURE; + } + } + + return XRE_DeinitCommandLine(); +} + +#ifdef MOZ_IPDL_TESTS +//----------------------------------------------------------------------------- +// IPDL unit test + +int XRE_RunIPDLTest(int aArgc, char** aArgv) { + if (aArgc < 2) { + fprintf( + stderr, + "TEST-UNEXPECTED-FAIL | <---> | insufficient #args, need at least 2\n"); + return 1; + } + + void* data = reinterpret_cast<void*>(aArgv[aArgc - 1]); + + nsresult rv = XRE_InitParentProcess( + --aArgc, aArgv, mozilla::_ipdltest::IPDLUnitTestMain, data); + NS_ENSURE_SUCCESS(rv, 1); + + return 0; +} +#endif // ifdef MOZ_IPDL_TESTS + +nsresult XRE_RunAppShell() { + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + NS_ENSURE_TRUE(appShell, NS_ERROR_FAILURE); +#if defined(XP_MACOSX) + if (XRE_UseNativeEventProcessing()) { + // In content processes that want XPCOM (and hence want + // AppShell), we usually run our hybrid event loop through + // MessagePump::Run(), by way of nsBaseAppShell::Run(). The + // Cocoa nsAppShell impl, however, implements its own Run() + // that's unaware of MessagePump. That's all rather suboptimal, + // but oddly enough not a problem... usually. + // + // The problem with this setup comes during startup. + // XPCOM-in-subprocesses depends on IPC, e.g. to init the pref + // service, so we have to init IPC first. But, IPC also + // indirectly kinda-depends on XPCOM, because MessagePump + // schedules work from off-main threads (e.g. IO thread) by + // using NS_DispatchToMainThread(). If the IO thread receives a + // Message from the parent before nsThreadManager is + // initialized, then DispatchToMainThread() will fail, although + // MessagePump will remember the task. This race condition + // isn't a problem when appShell->Run() ends up in + // MessagePump::Run(), because MessagePump will immediate see it + // has work to do. It *is* a problem when we end up in [NSApp + // run], because it's not aware that MessagePump has work that + // needs to be processed; that was supposed to be signaled by + // nsIRunnable(s). + // + // So instead of hacking Cocoa nsAppShell or rewriting the + // event-loop system, we compromise here by processing any tasks + // that might have been enqueued on MessagePump, *before* + // MessagePump::ScheduleWork was able to successfully + // DispatchToMainThread(). + MessageLoop* loop = MessageLoop::current(); + bool couldNest = loop->NestableTasksAllowed(); + + loop->SetNestableTasksAllowed(true); + RefPtr<Runnable> task = new MessageLoop::QuitTask(); + loop->PostTask(task.forget()); + loop->Run(); + + loop->SetNestableTasksAllowed(couldNest); + } +#endif // XP_MACOSX + return appShell->Run(); +} + +void XRE_ShutdownChildProcess() { + MOZ_ASSERT(NS_IsMainThread(), "Wrong thread!"); + + mozilla::DebugOnly<MessageLoop*> ioLoop = XRE_GetIOMessageLoop(); + MOZ_ASSERT(!!ioLoop, "Bad shutdown order"); + + // Quit() sets off the following chain of events + // (1) UI loop starts quitting + // (2) UI loop returns from Run() in XRE_InitChildProcess() + // (3) ProcessChild goes out of scope and terminates the IO thread + // (4) ProcessChild joins the IO thread + // (5) exit() + MessageLoop::current()->Quit(); + +#if defined(XP_MACOSX) + nsCOMPtr<nsIAppShell> appShell(do_GetService(kAppShellCID)); + if (appShell) { + // On Mac, we might be only above nsAppShell::Run(), not + // MessagePump::Run(). See XRE_RunAppShell(). To account for + // that case, we fire off an Exit() here. If we were indeed + // above MessagePump::Run(), this Exit() is just superfluous. + appShell->Exit(); + } +#endif // XP_MACOSX +} + +namespace { +ContentParent* gContentParent; // long-lived, manually refcounted +TestShellParent* GetOrCreateTestShellParent() { + if (!gContentParent) { + // Use a "web" child process by default. File a bug if you don't like + // this and you're sure you wouldn't be better off writing a "browser" + // chrome mochitest where you can have multiple types of content + // processes. + RefPtr<ContentParent> parent = + ContentParent::GetNewOrUsedBrowserProcess(DEFAULT_REMOTE_TYPE); + parent.forget(&gContentParent); + } else if (!gContentParent->IsAlive()) { + return nullptr; + } + TestShellParent* tsp = gContentParent->GetTestShellSingleton(); + if (!tsp) { + tsp = gContentParent->CreateTestShell(); + } + return tsp; +} + +} // namespace + +bool XRE_SendTestShellCommand(JSContext* aCx, JSString* aCommand, + JS::Value* aCallback) { + JS::RootedString cmd(aCx, aCommand); + TestShellParent* tsp = GetOrCreateTestShellParent(); + NS_ENSURE_TRUE(tsp, false); + + nsAutoJSString command; + NS_ENSURE_TRUE(command.init(aCx, cmd), false); + + if (!aCallback) { + return tsp->SendExecuteCommand(command); + } + + TestShellCommandParent* callback = static_cast<TestShellCommandParent*>( + tsp->SendPTestShellCommandConstructor(command)); + NS_ENSURE_TRUE(callback, false); + + NS_ENSURE_TRUE(callback->SetCallback(aCx, *aCallback), false); + + return true; +} + +bool XRE_ShutdownTestShell() { + if (!gContentParent) { + return true; + } + bool ret = true; + if (gContentParent->IsAlive()) { + ret = gContentParent->DestroyTestShell( + gContentParent->GetTestShellSingleton()); + } + NS_RELEASE(gContentParent); + return ret; +} + +#ifdef MOZ_X11 +void XRE_InstallX11ErrorHandler() { +# ifdef MOZ_WIDGET_GTK + InstallGdkErrorHandler(); +# else + InstallX11ErrorHandler(); +# endif +} +#endif + +#ifdef MOZ_ENABLE_FORKSERVER +int XRE_ForkServer(int* aArgc, char*** aArgv) { + return mozilla::ipc::ForkServer::RunForkServer(aArgc, aArgv) ? 1 : 0; +} +#endif diff --git a/toolkit/xre/nsEmbeddingModule.cpp b/toolkit/xre/nsEmbeddingModule.cpp new file mode 100644 index 0000000000..34a1de1d44 --- /dev/null +++ b/toolkit/xre/nsEmbeddingModule.cpp @@ -0,0 +1,43 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "mozilla/ModuleUtils.h" + +#if defined(MOZ_XUL) && defined(NS_PRINTING) +# include "nsPrintingPromptService.h" +# include "nsPrintingProxy.h" + +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingPromptService, + nsPrintingPromptService::GetSingleton) +# ifdef PROXY_PRINTING +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsPrintingProxy, + nsPrintingProxy::GetInstance) +# endif + +NS_DEFINE_NAMED_CID(NS_PRINTINGPROMPTSERVICE_CID); +#endif + +static const mozilla::Module::CIDEntry kEmbeddingCIDs[] = { +#if defined(MOZ_XUL) && defined(NS_PRINTING) +# ifdef PROXY_PRINTING + {&kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, + nsPrintingPromptServiceConstructor, mozilla::Module::MAIN_PROCESS_ONLY}, + {&kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, nsPrintingProxyConstructor, + mozilla::Module::CONTENT_PROCESS_ONLY}, +# else + {&kNS_PRINTINGPROMPTSERVICE_CID, false, nullptr, + nsPrintingPromptServiceConstructor}, +# endif +#endif + {nullptr}}; + +static const mozilla::Module::ContractIDEntry kEmbeddingContracts[] = { +#if defined(MOZ_XUL) && defined(NS_PRINTING) + {NS_PRINTINGPROMPTSERVICE_CONTRACTID, &kNS_PRINTINGPROMPTSERVICE_CID}, +#endif + {nullptr}}; + +extern const mozilla::Module kEmbeddingModule = { + mozilla::Module::kVersion, kEmbeddingCIDs, kEmbeddingContracts}; diff --git a/toolkit/xre/nsGDKErrorHandler.cpp b/toolkit/xre/nsGDKErrorHandler.cpp new file mode 100644 index 0000000000..421abdf12f --- /dev/null +++ b/toolkit/xre/nsGDKErrorHandler.cpp @@ -0,0 +1,109 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsGDKErrorHandler.h" + +#include <gtk/gtk.h> +#include <gdk/gdkx.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> + +#include "nsDebug.h" +#include "nsString.h" +#include "nsX11ErrorHandler.h" + +#include "prenv.h" + +/* See https://bugzilla.gnome.org/show_bug.cgi?id=629608#c8 + * + * GDK implements X11 error traps to ignore X11 errors. + * Unfortunatelly We don't know which X11 events can be ignored + * so we have to utilize the Gdk error handler to avoid + * false alarms in Gtk3. + */ +static void GdkErrorHandler(const gchar* log_domain, GLogLevelFlags log_level, + const gchar* message, gpointer user_data) { + if (strstr(message, "X Window System error")) { + XErrorEvent event; + nsDependentCString buffer(message); + char* endptr; + + /* Parse Gdk X Window error message which has this format: + * (Details: serial XXXX error_code XXXX request_code XXXX (XXXX) minor_code + * XXXX) + */ + constexpr auto serialString = "(Details: serial "_ns; + int32_t start = buffer.Find(serialString); + if (start == kNotFound) { + MOZ_CRASH_UNSAFE(message); + } + + start += serialString.Length(); + errno = 0; + event.serial = strtol(buffer.BeginReading() + start, &endptr, 10); + if (errno) { + MOZ_CRASH_UNSAFE(message); + } + + constexpr auto errorCodeString = " error_code "_ns; + if (!StringBeginsWith(Substring(endptr, buffer.EndReading()), + errorCodeString)) { + MOZ_CRASH_UNSAFE(message); + } + + errno = 0; + event.error_code = strtol(endptr + errorCodeString.Length(), &endptr, 10); + if (errno) { + MOZ_CRASH_UNSAFE(message); + } + + constexpr auto requestCodeString = " request_code "_ns; + if (!StringBeginsWith(Substring(endptr, buffer.EndReading()), + requestCodeString)) { + MOZ_CRASH_UNSAFE(message); + } + + errno = 0; + event.request_code = + strtol(endptr + requestCodeString.Length(), &endptr, 10); + if (errno) { + MOZ_CRASH_UNSAFE(message); + } + + constexpr auto minorCodeString = " minor_code "_ns; + start = buffer.Find(minorCodeString, /* aIgnoreCase = */ false, + endptr - buffer.BeginReading()); + if (!start) { + MOZ_CRASH_UNSAFE(message); + } + + errno = 0; + event.minor_code = strtol( + buffer.BeginReading() + start + minorCodeString.Length(), nullptr, 10); + if (errno) { + MOZ_CRASH_UNSAFE(message); + } + + event.display = GDK_DISPLAY_XDISPLAY(gdk_display_get_default()); + // Gdk does not provide resource ID + event.resourceid = 0; + + X11Error(event.display, &event); + } else { + g_log_default_handler(log_domain, log_level, message, user_data); + MOZ_CRASH_UNSAFE(message); + } +} + +void InstallGdkErrorHandler() { + g_log_set_handler("Gdk", + (GLogLevelFlags)(G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL | + G_LOG_FLAG_RECURSION), + GdkErrorHandler, nullptr); + if (PR_GetEnv("MOZ_X_SYNC")) { + XSynchronize(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11True); + } +} diff --git a/toolkit/xre/nsGDKErrorHandler.h b/toolkit/xre/nsGDKErrorHandler.h new file mode 100644 index 0000000000..7c6cdce41f --- /dev/null +++ b/toolkit/xre/nsGDKErrorHandler.h @@ -0,0 +1,8 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifdef MOZ_WIDGET_GTK +void InstallGdkErrorHandler(); +#endif diff --git a/toolkit/xre/nsIAppStartupNotifier.h b/toolkit/xre/nsIAppStartupNotifier.h new file mode 100644 index 0000000000..ad2d8e4303 --- /dev/null +++ b/toolkit/xre/nsIAppStartupNotifier.h @@ -0,0 +1,53 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsIAppStartupNotifier_h___ +#define nsIAppStartupNotifier_h___ + +/* + Some components need to be run at the startup of mozilla or embedding - to + start new services etc. + + This interface provides a generic way to start up arbitrary components + without requiring them to hack into main1() (or into NS_InitEmbedding) as + it's currently being done for services such as wallet, command line handlers + etc. + + We will have a category called "app-startup" which components register + themselves in using the CategoryManager. + + Components can also (optionally) add the word "service," as a prefix + to the "value" they pass in during a call to AddCategoryEntry() as + shown below: + + categoryManager->AddCategoryEntry(APPSTARTUP_CATEGORY, "testcomp", + "service," NS_WALLETSERVICE_CONTRACTID + true, true, + getter_Copies(previous)); + + Presence of the "service" keyword indicates the components desire to + be started as a service. When the "service" keyword is not present + we just do a do_CreateInstance. + + When mozilla starts (and when NS_InitEmbedding()) is invoked + we create an instance of the AppStartupNotifier component (which + implements nsIObserver) and invoke its Observe() method. + + Observe() will enumerate the components registered into the + APPSTARTUP_CATEGORY and notify them that startup has begun + and release them. +*/ + +#define APPSTARTUP_CATEGORY "app-startup" + +/* + Please note that there's not a new interface in this file. + We're just leveraging nsIObserver instead of creating a + new one + + This file exists solely to provide the defines above +*/ + +#endif /* nsIAppStartupNotifier_h___ */ diff --git a/toolkit/xre/nsINativeAppSupport.idl b/toolkit/xre/nsINativeAppSupport.idl new file mode 100644 index 0000000000..7cfce02ab4 --- /dev/null +++ b/toolkit/xre/nsINativeAppSupport.idl @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsISupports.idl" + +/* nsINativeAppSupport + * + * This "pseudo" (in the XPCOM sense) interface provides for + * platform-specific general application support + * + * Due to the nature of the beast, this interface is not a full-blown + * XPCOM component. The primary reason is that objects that implement + * this interface generally must be operational *before* XPCOM (or any + * of the rest of Mozilla) are initialized. As a result, this + * interface is instantiated by somewhat unconventional means. + * + * To create the implementor of this interface, you call the function + * NS_CreateNativeAppSupport. This is done in the startup code + * in nsAppRunner.cpp + * + * The interface provides these functions: + * start - You call this to inform the native app support that the + * application is starting. In addition, it serves as a + * query as to whether the application should continue to + * run. + * + * If the returned boolean result is PR_FALSE, then the + * application should exit without further processing. In + * such cases, the returned nsresult indicates whether the + * reason to exit is due to an error or not. + * + * Win32 Note: In the case of starting a second instance + * of this executable, this function will return + * PR_FALSE and nsresult==NS_OK. This means that + * the command line arguments have been + * successfully passed to the instance of the + * application acting as a remote server. + * quit - Informs the native app support that the application is stopping. The + * app support should disable any functionality enabled by start. + * + * onLastWindowClosing - Called when the last window is closed. Used as a + * "soft" shutdown, passwords are flushed. + */ + +[scriptable, uuid(5fdf8480-1f98-11d4-8077-00600811a9c3)] +interface nsINativeAppSupport : nsISupports { + // Startup/shutdown. + boolean start(); + void enable(); + + void onLastWindowClosing(); + void ReOpen(); +}; diff --git a/toolkit/xre/nsIWinAppHelper.idl b/toolkit/xre/nsIWinAppHelper.idl new file mode 100644 index 0000000000..c8a602977c --- /dev/null +++ b/toolkit/xre/nsIWinAppHelper.idl @@ -0,0 +1,19 @@ +/* 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/. */ + +#include "nsISupports.idl" + +/** + * A scriptable interface used on Windows only to do some work from + * a special process that gets created with elevated privileges. + * + * @status UNSTABLE - This interface is not frozen and will probably change in + * future releases. + */ + +[scriptable, uuid(dc263ca8-b257-47eb-b5b7-339d9e0b90f7)] +interface nsIWinAppHelper : nsISupports +{ + readonly attribute boolean userCanElevate; +}; diff --git a/toolkit/xre/nsIXREDirProvider.idl b/toolkit/xre/nsIXREDirProvider.idl new file mode 100644 index 0000000000..ce599a83bc --- /dev/null +++ b/toolkit/xre/nsIXREDirProvider.idl @@ -0,0 +1,23 @@ +/* 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/. */ + +#include "nsISupports.idl" + +interface nsIFile; + +[scriptable, uuid(f6ee3c0a-5119-47fc-b1a7-ace9e1111fff)] +interface nsIXREDirProvider : nsISupports +{ + /** + * Only intended to be used from xpcshell tests. Allows setting the local + * and normal profile data directories. Calling this after something using + * them has started up will cause problems. + */ + void setUserDataDirectory(in nsIFile aFile, in boolean aLocal); + + /** + * Gets the hash for the current installation directory. + */ + AString getInstallHash(); +}; diff --git a/toolkit/xre/nsNativeAppSupportBase.cpp b/toolkit/xre/nsNativeAppSupportBase.cpp new file mode 100644 index 0000000000..43c329ec86 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportBase.cpp @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsNativeAppSupportBase.h" + +nsNativeAppSupportBase::nsNativeAppSupportBase() = default; + +nsNativeAppSupportBase::~nsNativeAppSupportBase() = default; + +NS_IMPL_ISUPPORTS(nsNativeAppSupportBase, nsINativeAppSupport) + +// Start answer defaults to OK. +NS_IMETHODIMP +nsNativeAppSupportBase::Start(bool* result) { + *result = true; + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportBase::Enable() { return NS_OK; } + +NS_IMETHODIMP +nsNativeAppSupportBase::ReOpen() { return NS_OK; } + +NS_IMETHODIMP +nsNativeAppSupportBase::OnLastWindowClosing() { return NS_OK; } diff --git a/toolkit/xre/nsNativeAppSupportBase.h b/toolkit/xre/nsNativeAppSupportBase.h new file mode 100644 index 0000000000..851ea358fe --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportBase.h @@ -0,0 +1,28 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 nsNativeAppSupportBase_h__ +#define nsNativeAppSupportBase_h__ + +#include "nsAppRunner.h" +#include "nsINativeAppSupport.h" + +// nsNativeAppSupportBase +// +// This is a default implementation of the nsINativeAppSupport interface +// declared in mozilla/xpfe/appshell/public/nsINativeAppSupport.h. + +class nsNativeAppSupportBase : public nsINativeAppSupport { + public: + nsNativeAppSupportBase(); + + NS_DECL_ISUPPORTS + NS_DECL_NSINATIVEAPPSUPPORT + + protected: + virtual ~nsNativeAppSupportBase(); +}; + +#endif diff --git a/toolkit/xre/nsNativeAppSupportCocoa.mm b/toolkit/xre/nsNativeAppSupportCocoa.mm new file mode 100644 index 0000000000..fd2e297af3 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportCocoa.mm @@ -0,0 +1,172 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsString.h" + +#import <CoreServices/CoreServices.h> +#import <Cocoa/Cocoa.h> + +#include "nsCOMPtr.h" +#include "nsCocoaFeatures.h" +#include "nsNativeAppSupportBase.h" + +#include "nsIBaseWindow.h" +#include "nsCommandLine.h" +#include "mozIDOMWindow.h" +#include "nsIWebNavigation.h" +#include "nsIWidget.h" +#include "nsIWindowMediator.h" +#include "nsPIDOMWindow.h" +#include "WidgetUtils.h" + +// This must be included last: +#include "nsObjCExceptions.h" + +using mozilla::widget::WidgetUtils; + +nsresult GetNativeWindowPointerFromDOMWindow(mozIDOMWindowProxy* a_window, + NSWindow** a_nativeWindow) { + *a_nativeWindow = nil; + if (!a_window) return NS_ERROR_INVALID_ARG; + + nsPIDOMWindowOuter* win = nsPIDOMWindowOuter::From(a_window); + nsCOMPtr<nsIWidget> widget = WidgetUtils::DOMWindowToWidget(win); + if (!widget) { + return NS_ERROR_FAILURE; + } + + *a_nativeWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); + + return NS_OK; +} + +class nsNativeAppSupportCocoa : public nsNativeAppSupportBase { + public: + nsNativeAppSupportCocoa() : mCanShowUI(false) {} + + NS_IMETHOD Start(bool* aRetVal) override; + NS_IMETHOD ReOpen() override; + NS_IMETHOD Enable() override; + + private: + bool mCanShowUI; +}; + +NS_IMETHODIMP +nsNativeAppSupportCocoa::Enable() { + mCanShowUI = true; + return NS_OK; +} + +NS_IMETHODIMP nsNativeAppSupportCocoa::Start(bool* _retval) { + int major, minor, bugfix; + nsCocoaFeatures::GetSystemVersion(major, minor, bugfix); + + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + // Check that the OS version is supported, if not return false, + // which will make the browser quit. In principle we could display an + // alert here. But the alert's message and buttons would require custom + // localization. So (for now at least) we just log an English message + // to the console before quitting. + if (major < 10 || (major == 10 && minor < 12)) { + NSLog(@"Minimum OS version requirement not met!"); + return NS_OK; + } + + *_retval = true; + + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +NS_IMETHODIMP +nsNativeAppSupportCocoa::ReOpen() { + NS_OBJC_BEGIN_TRY_ABORT_BLOCK_NSRESULT; + + if (!mCanShowUI) return NS_ERROR_FAILURE; + + bool haveNonMiniaturized = false; + bool haveOpenWindows = false; + bool done = false; + + nsCOMPtr<nsIWindowMediator> wm(do_GetService(NS_WINDOWMEDIATOR_CONTRACTID)); + if (!wm) { + return NS_ERROR_FAILURE; + } else { + nsCOMPtr<nsISimpleEnumerator> windowList; + wm->GetAppWindowEnumerator(nullptr, getter_AddRefs(windowList)); + bool more; + windowList->HasMoreElements(&more); + while (more) { + nsCOMPtr<nsISupports> nextWindow = nullptr; + windowList->GetNext(getter_AddRefs(nextWindow)); + nsCOMPtr<nsIBaseWindow> baseWindow(do_QueryInterface(nextWindow)); + if (!baseWindow) { + windowList->HasMoreElements(&more); + continue; + } else { + haveOpenWindows = true; + } + + nsCOMPtr<nsIWidget> widget = nullptr; + baseWindow->GetMainWidget(getter_AddRefs(widget)); + if (!widget) { + windowList->HasMoreElements(&more); + continue; + } + NSWindow* cocoaWindow = (NSWindow*)widget->GetNativeData(NS_NATIVE_WINDOW); + if (![cocoaWindow isMiniaturized]) { + haveNonMiniaturized = true; + break; // have un-minimized windows, nothing to do + } + windowList->HasMoreElements(&more); + } // end while + + if (!haveNonMiniaturized) { + // Deminiaturize the most recenty used window + nsCOMPtr<mozIDOMWindowProxy> mru; + wm->GetMostRecentWindow(nullptr, getter_AddRefs(mru)); + + if (mru) { + NSWindow* cocoaMru = nil; + GetNativeWindowPointerFromDOMWindow(mru, &cocoaMru); + if (cocoaMru) { + [cocoaMru deminiaturize:nil]; + done = true; + } + } + } // end if have non miniaturized + + if (!haveOpenWindows && !done) { + char* argv[] = {nullptr}; + + // use an empty command line to make the right kind(s) of window open + nsCOMPtr<nsICommandLineRunner> cmdLine(new nsCommandLine()); + + nsresult rv; + rv = cmdLine->Init(0, argv, nullptr, nsICommandLine::STATE_REMOTE_EXPLICIT); + NS_ENSURE_SUCCESS(rv, rv); + + return cmdLine->Run(); + } + + } // got window mediator + return NS_OK; + + NS_OBJC_END_TRY_ABORT_BLOCK_NSRESULT; +} + +#pragma mark - + +// Create and return an instance of class nsNativeAppSupportCocoa. +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport** aResult) { + *aResult = new nsNativeAppSupportCocoa; + if (!*aResult) return NS_ERROR_OUT_OF_MEMORY; + + NS_ADDREF(*aResult); + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportDefault.cpp b/toolkit/xre/nsNativeAppSupportDefault.cpp new file mode 100644 index 0000000000..a65ac4e1a5 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportDefault.cpp @@ -0,0 +1,15 @@ +/* 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/. */ + +#include "nsNativeAppSupportBase.h" + +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport** aResult) { + nsNativeAppSupportBase* native = new nsNativeAppSupportBase(); + if (!native) return NS_ERROR_OUT_OF_MEMORY; + + *aResult = native; + NS_ADDREF(*aResult); + + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportUnix.cpp b/toolkit/xre/nsNativeAppSupportUnix.cpp new file mode 100644 index 0000000000..1f6d9049ed --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportUnix.cpp @@ -0,0 +1,657 @@ +/* -*- 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/. */ + +#include "nsNativeAppSupportBase.h" +#include "nsCOMPtr.h" +#include "nsXPCOM.h" +#include "nsISupportsPrimitives.h" +#include "nsIObserverService.h" +#include "nsIAppStartup.h" +#include "nsServiceManagerUtils.h" +#include "prlink.h" +#include "nsXREDirProvider.h" +#include "nsReadableUtils.h" + +#include "nsIFile.h" +#include "nsDirectoryServiceDefs.h" +#include "nsPIDOMWindow.h" +#include "nsIWidget.h" +#include "mozilla/Services.h" + +#include <stdlib.h> +#include <glib.h> +#include <glib-object.h> +#include <gtk/gtk.h> + +#ifdef MOZ_X11 +# include <gdk/gdkx.h> +# include <X11/ICE/ICElib.h> +# include <X11/SM/SMlib.h> +# include <fcntl.h> +# include "nsThreadUtils.h" + +# include <pwd.h> +#endif + +#ifdef MOZ_ENABLE_DBUS +# include <dbus/dbus.h> +#endif + +#define MIN_GTK_MAJOR_VERSION 2 +#define MIN_GTK_MINOR_VERSION 10 +#define UNSUPPORTED_GTK_MSG \ + "We're sorry, this application requires a version of the GTK+ library that is not installed on your computer.\n\n\ +You have GTK+ %d.%d.\nThis application requires GTK+ %d.%d or newer.\n\n\ +Please upgrade your GTK+ library if you wish to use this application." + +#if MOZ_X11 +# undef IceSetIOErrorHandler +# undef IceAddConnectionWatch +# undef IceConnectionNumber +# undef IceProcessMessages +# undef IceGetConnectionContext +# undef SmcInteractDone +# undef SmcSaveYourselfDone +# undef SmcInteractRequest +# undef SmcCloseConnection +# undef SmcOpenConnection +# undef SmcSetProperties + +typedef IceIOErrorHandler (*IceSetIOErrorHandlerFn)(IceIOErrorHandler); +typedef int (*IceAddConnectionWatchFn)(IceWatchProc, IcePointer); +typedef int (*IceConnectionNumberFn)(IceConn); +typedef IceProcessMessagesStatus (*IceProcessMessagesFn)(IceConn, + IceReplyWaitInfo*, + Bool*); +typedef IcePointer (*IceGetConnectionContextFn)(IceConn); + +typedef void (*SmcInteractDoneFn)(SmcConn, Bool); +typedef void (*SmcSaveYourselfDoneFn)(SmcConn, Bool); +typedef int (*SmcInteractRequestFn)(SmcConn, int, SmcInteractProc, SmPointer); +typedef SmcCloseStatus (*SmcCloseConnectionFn)(SmcConn, int, char**); +typedef SmcConn (*SmcOpenConnectionFn)(char*, SmPointer, int, int, + unsigned long, SmcCallbacks*, + const char*, char**, int, char*); +typedef void (*SmcSetPropertiesFn)(SmcConn, int, SmProp**); + +static IceSetIOErrorHandlerFn IceSetIOErrorHandlerPtr; +static IceAddConnectionWatchFn IceAddConnectionWatchPtr; +static IceConnectionNumberFn IceConnectionNumberPtr; +static IceProcessMessagesFn IceProcessMessagesPtr; +static IceGetConnectionContextFn IceGetConnectionContextPtr; +static SmcInteractDoneFn SmcInteractDonePtr; +static SmcSaveYourselfDoneFn SmcSaveYourselfDonePtr; +static SmcInteractRequestFn SmcInteractRequestPtr; +static SmcCloseConnectionFn SmcCloseConnectionPtr; +static SmcOpenConnectionFn SmcOpenConnectionPtr; +static SmcSetPropertiesFn SmcSetPropertiesPtr; + +# define IceSetIOErrorHandler IceSetIOErrorHandlerPtr +# define IceAddConnectionWatch IceAddConnectionWatchPtr +# define IceConnectionNumber IceConnectionNumberPtr +# define IceProcessMessages IceProcessMessagesPtr +# define IceGetConnectionContext IceGetConnectionContextPtr +# define SmcInteractDone SmcInteractDonePtr +# define SmcSaveYourselfDone SmcSaveYourselfDonePtr +# define SmcInteractRequest SmcInteractRequestPtr +# define SmcCloseConnection SmcCloseConnectionPtr +# define SmcOpenConnection SmcOpenConnectionPtr +# define SmcSetProperties SmcSetPropertiesPtr + +enum ClientState { + STATE_DISCONNECTED, + STATE_REGISTERING, + STATE_IDLE, + STATE_INTERACTING, + STATE_SHUTDOWN_CANCELLED +}; + +static const char* gClientStateTable[] = {"DISCONNECTED", "REGISTERING", "IDLE", + "INTERACTING", "SHUTDOWN_CANCELLED"}; + +static LazyLogModule sMozSMLog("MozSM"); +#endif /* MOZ_X11 */ + +class nsNativeAppSupportUnix : public nsNativeAppSupportBase { + public: +#if MOZ_X11 + nsNativeAppSupportUnix() + : mSessionConnection(nullptr), mClientState(STATE_DISCONNECTED){}; + ~nsNativeAppSupportUnix() { + // this goes out of scope after "web-workers-shutdown" async shutdown phase + // so it's safe to disconnect here (i.e. the application won't lose data) + DisconnectFromSM(); + }; + + void DisconnectFromSM(); +#endif + NS_IMETHOD Start(bool* aRetVal) override; + NS_IMETHOD Enable() override; + + private: +#if MOZ_X11 + static void SaveYourselfCB(SmcConn smc_conn, SmPointer client_data, + int save_style, Bool shutdown, int interact_style, + Bool fast); + static void DieCB(SmcConn smc_conn, SmPointer client_data); + static void InteractCB(SmcConn smc_conn, SmPointer client_data); + static void SaveCompleteCB(SmcConn smc_conn, SmPointer client_data){}; + static void ShutdownCancelledCB(SmcConn smc_conn, SmPointer client_data); + void DoInteract(); + void SetClientState(ClientState aState) { + mClientState = aState; + MOZ_LOG(sMozSMLog, LogLevel::Debug, + ("New state = %s\n", gClientStateTable[aState])); + } + + SmcConn mSessionConnection; + ClientState mClientState; +#endif +}; + +#if MOZ_X11 +static gboolean process_ice_messages(IceConn connection) { + IceProcessMessagesStatus status; + + status = IceProcessMessages(connection, nullptr, nullptr); + + switch (status) { + case IceProcessMessagesSuccess: + return TRUE; + + case IceProcessMessagesIOError: { + nsNativeAppSupportUnix* native = static_cast<nsNativeAppSupportUnix*>( + IceGetConnectionContext(connection)); + native->DisconnectFromSM(); + } + return FALSE; + + case IceProcessMessagesConnectionClosed: + return FALSE; + + default: + g_assert_not_reached(); + } +} + +static gboolean ice_iochannel_watch(GIOChannel* channel, GIOCondition condition, + gpointer client_data) { + return process_ice_messages(static_cast<IceConn>(client_data)); +} + +static void ice_connection_watch(IceConn connection, IcePointer client_data, + Bool opening, IcePointer* watch_data) { + guint watch_id; + + if (opening) { + GIOChannel* channel; + int fd = IceConnectionNumber(connection); + + fcntl(fd, F_SETFD, fcntl(fd, F_GETFD, 0) | FD_CLOEXEC); + channel = g_io_channel_unix_new(fd); + watch_id = + g_io_add_watch(channel, static_cast<GIOCondition>(G_IO_IN | G_IO_ERR), + ice_iochannel_watch, connection); + g_io_channel_unref(channel); + + *watch_data = GUINT_TO_POINTER(watch_id); + } else { + watch_id = GPOINTER_TO_UINT(*watch_data); + g_source_remove(watch_id); + } +} + +static void ice_io_error_handler(IceConn connection) { + // override the default handler which would exit the application; + // do nothing and let ICELib handle the failure of the connection gracefully. +} + +static void ice_init(void) { + static bool initted = false; + + if (!initted) { + IceSetIOErrorHandler(ice_io_error_handler); + IceAddConnectionWatch(ice_connection_watch, nullptr); + initted = true; + } +} + +void nsNativeAppSupportUnix::InteractCB(SmcConn smc_conn, + SmPointer client_data) { + nsNativeAppSupportUnix* self = + static_cast<nsNativeAppSupportUnix*>(client_data); + + self->SetClientState(STATE_INTERACTING); + + // We do this asynchronously, as we spin the event loop recursively if + // a dialog is displayed. If we do this synchronously, we don't finish + // processing the current ICE event whilst the dialog is displayed, which + // means we won't process any more. libsm hates us if we do the InteractDone + // with a pending ShutdownCancelled, and we would certainly like to handle Die + // whilst a dialog is displayed + NS_DispatchToCurrentThread( + NewRunnableMethod("nsNativeAppSupportUnix::DoInteract", self, + &nsNativeAppSupportUnix::DoInteract)); +} + +void nsNativeAppSupportUnix::DoInteract() { + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + if (!obsServ) { + SmcInteractDone(mSessionConnection, False); + SmcSaveYourselfDone(mSessionConnection, True); + SetClientState(STATE_IDLE); + return; + } + + nsCOMPtr<nsISupportsPRBool> cancelQuit = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + + bool abortQuit = false; + if (cancelQuit) { + cancelQuit->SetData(false); + obsServ->NotifyObservers(cancelQuit, "quit-application-requested", nullptr); + + cancelQuit->GetData(&abortQuit); + } + + if (!abortQuit && mClientState == STATE_DISCONNECTED) { + // The session manager disappeared, whilst we were interacting, so + // quit now + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + + if (appService) { + bool userAllowedQuit = true; + appService->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit); + } + } else { + if (mClientState != STATE_SHUTDOWN_CANCELLED) { + // Only do this if the shutdown wasn't cancelled + SmcInteractDone(mSessionConnection, !!abortQuit); + SmcSaveYourselfDone(mSessionConnection, !abortQuit); + } + + SetClientState(STATE_IDLE); + } +} + +void nsNativeAppSupportUnix::SaveYourselfCB(SmcConn smc_conn, + SmPointer client_data, + int save_style, Bool shutdown, + int interact_style, Bool fast) { + nsNativeAppSupportUnix* self = + static_cast<nsNativeAppSupportUnix*>(client_data); + + // Expect a SaveYourselfCB if we're registering a new client. + // All properties are already set in Start() so just reply with + // SmcSaveYourselfDone if the callback matches the expected signature. + // + // Ancient versions (?) of xsm do not follow such an early SaveYourself with + // SaveComplete. This is a problem if the application freezes interaction + // while waiting for a response to SmcSaveYourselfDone. So never freeze + // interaction when in STATE_REGISTERING. + // + // That aside, we could treat each combination of flags appropriately and not + // special-case this. + if (self->mClientState == STATE_REGISTERING) { + self->SetClientState(STATE_IDLE); + + if (save_style == SmSaveLocal && interact_style == SmInteractStyleNone && + !shutdown && !fast) { + SmcSaveYourselfDone(self->mSessionConnection, True); + return; + } + } + + if (self->mClientState == STATE_SHUTDOWN_CANCELLED) { + // The last shutdown request was cancelled whilst we were interacting, + // and we haven't finished interacting yet. Switch the state back again + self->SetClientState(STATE_INTERACTING); + } + + nsCOMPtr<nsIObserverService> obsServ = + mozilla::services::GetObserverService(); + if (!obsServ) { + SmcSaveYourselfDone(smc_conn, True); + return; + } + + bool status = false; + nsCOMPtr<nsISupportsPRBool> didSaveSession = + do_CreateInstance(NS_SUPPORTS_PRBOOL_CONTRACTID); + + if (!didSaveSession) { + SmcSaveYourselfDone(smc_conn, True); + return; + } + + // Notify observers to save the session state + didSaveSession->SetData(false); + obsServ->NotifyObservers(didSaveSession, "session-save", nullptr); + + didSaveSession->GetData(&status); + + // If the interact style permits us to, we are shutting down and we didn't + // manage to (or weren't asked to) save the local state, then notify the user + // in advance that we are doing to quit (assuming that we aren't already + // doing so) + if (!status && shutdown && interact_style != SmInteractStyleNone) { + if (self->mClientState != STATE_INTERACTING) { + SmcInteractRequest(smc_conn, SmDialogNormal, + nsNativeAppSupportUnix::InteractCB, client_data); + } + } else { + SmcSaveYourselfDone(smc_conn, True); + } +} + +void nsNativeAppSupportUnix::DieCB(SmcConn smc_conn, SmPointer client_data) { + nsCOMPtr<nsIAppStartup> appService = + do_GetService("@mozilla.org/toolkit/app-startup;1"); + + if (appService) { + bool userAllowedQuit = false; + appService->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit); + } + // Quit causes the shutdown to begin but the shutdown process is asynchronous + // so we can't DisconnectFromSM() yet +} + +void nsNativeAppSupportUnix::ShutdownCancelledCB(SmcConn smc_conn, + SmPointer client_data) { + nsNativeAppSupportUnix* self = + static_cast<nsNativeAppSupportUnix*>(client_data); + + // Interacting is the only time when we wouldn't already have called + // SmcSaveYourselfDone. Do that now, then set the state to make sure we + // don't send it again after finishing interacting + if (self->mClientState == STATE_INTERACTING) { + SmcSaveYourselfDone(smc_conn, False); + self->SetClientState(STATE_SHUTDOWN_CANCELLED); + } +} + +void nsNativeAppSupportUnix::DisconnectFromSM() { + // the SM is free to exit any time after we disconnect, so callers must be + // sure to have reached a sufficiently advanced phase of shutdown that there + // is no risk of data loss: + // e.g. all async writes are complete by the end of "profile-before-change" + if (mSessionConnection) { + SetClientState(STATE_DISCONNECTED); + SmcCloseConnection(mSessionConnection, 0, nullptr); + mSessionConnection = nullptr; + gdk_x11_set_sm_client_id(nullptr); // follow gnome-client behaviour + } +} + +static void SetSMValue(SmPropValue& val, const nsCString& data) { + val.value = static_cast<SmPointer>(const_cast<char*>(data.get())); + val.length = data.Length(); +} + +static void SetSMProperty(SmProp& prop, const char* name, const char* type, + int numVals, SmPropValue vals[]) { + prop.name = const_cast<char*>(name); + prop.type = const_cast<char*>(type); + prop.num_vals = numVals; + prop.vals = vals; +} +#endif /* MOZ_X11 */ + +static void RemoveArg(char** argv) { + do { + *argv = *(argv + 1); + ++argv; + } while (*argv); + + --gArgc; +} + +NS_IMETHODIMP +nsNativeAppSupportUnix::Start(bool* aRetVal) { + NS_ASSERTION(gAppData, "gAppData must not be null."); + +// The dbus library is used by both nsWifiScannerDBus and BluetoothDBusService, +// from diffrent threads. This could lead to race conditions if the dbus is not +// initialized before making any other library calls. +#ifdef MOZ_ENABLE_DBUS + dbus_threads_init_default(); +#endif + + *aRetVal = true; + +#ifdef MOZ_X11 + gboolean sm_disable = FALSE; + if (!getenv("SESSION_MANAGER")) { + sm_disable = TRUE; + } + + nsAutoCString prev_client_id; + + char** curarg = gArgv + 1; + while (*curarg) { + char* arg = *curarg; + if (arg[0] == '-' && arg[1] == '-') { + arg += 2; + if (!strcmp(arg, "sm-disable")) { + RemoveArg(curarg); + sm_disable = TRUE; + continue; + } else if (!strcmp(arg, "sm-client-id")) { + RemoveArg(curarg); + if (*curarg[0] != '-') { + prev_client_id = *curarg; + RemoveArg(curarg); + } + continue; + } + } + + ++curarg; + } + + if (prev_client_id.IsEmpty()) { + prev_client_id = getenv("DESKTOP_AUTOSTART_ID"); + } + + // We don't want child processes to use the same ID + unsetenv("DESKTOP_AUTOSTART_ID"); + + char* client_id = nullptr; + if (!sm_disable) { + PRLibrary* iceLib = PR_LoadLibrary("libICE.so.6"); + if (!iceLib) { + return NS_OK; + } + + PRLibrary* smLib = PR_LoadLibrary("libSM.so.6"); + if (!smLib) { + PR_UnloadLibrary(iceLib); + return NS_OK; + } + + IceSetIOErrorHandler = (IceSetIOErrorHandlerFn)PR_FindFunctionSymbol( + iceLib, "IceSetIOErrorHandler"); + IceAddConnectionWatch = (IceAddConnectionWatchFn)PR_FindFunctionSymbol( + iceLib, "IceAddConnectionWatch"); + IceConnectionNumber = (IceConnectionNumberFn)PR_FindFunctionSymbol( + iceLib, "IceConnectionNumber"); + IceProcessMessages = (IceProcessMessagesFn)PR_FindFunctionSymbol( + iceLib, "IceProcessMessages"); + IceGetConnectionContext = (IceGetConnectionContextFn)PR_FindFunctionSymbol( + iceLib, "IceGetConnectionContext"); + if (!IceSetIOErrorHandler || !IceAddConnectionWatch || + !IceConnectionNumber || !IceProcessMessages || + !IceGetConnectionContext) { + PR_UnloadLibrary(iceLib); + PR_UnloadLibrary(smLib); + return NS_OK; + } + + SmcInteractDone = + (SmcInteractDoneFn)PR_FindFunctionSymbol(smLib, "SmcInteractDone"); + SmcSaveYourselfDone = (SmcSaveYourselfDoneFn)PR_FindFunctionSymbol( + smLib, "SmcSaveYourselfDone"); + SmcInteractRequest = (SmcInteractRequestFn)PR_FindFunctionSymbol( + smLib, "SmcInteractRequest"); + SmcCloseConnection = (SmcCloseConnectionFn)PR_FindFunctionSymbol( + smLib, "SmcCloseConnection"); + SmcOpenConnection = + (SmcOpenConnectionFn)PR_FindFunctionSymbol(smLib, "SmcOpenConnection"); + SmcSetProperties = + (SmcSetPropertiesFn)PR_FindFunctionSymbol(smLib, "SmcSetProperties"); + if (!SmcInteractDone || !SmcSaveYourselfDone || !SmcInteractRequest || + !SmcCloseConnection || !SmcOpenConnection || !SmcSetProperties) { + PR_UnloadLibrary(iceLib); + PR_UnloadLibrary(smLib); + return NS_OK; + } + + ice_init(); + + // all callbacks are mandatory in libSM 1.0, so listen even if we don't + // care. + unsigned long mask = SmcSaveYourselfProcMask | SmcDieProcMask | + SmcSaveCompleteProcMask | SmcShutdownCancelledProcMask; + + SmcCallbacks callbacks; + callbacks.save_yourself.callback = nsNativeAppSupportUnix::SaveYourselfCB; + callbacks.save_yourself.client_data = static_cast<SmPointer>(this); + + callbacks.die.callback = nsNativeAppSupportUnix::DieCB; + callbacks.die.client_data = static_cast<SmPointer>(this); + + callbacks.save_complete.callback = nsNativeAppSupportUnix::SaveCompleteCB; + callbacks.save_complete.client_data = nullptr; + + callbacks.shutdown_cancelled.callback = + nsNativeAppSupportUnix::ShutdownCancelledCB; + callbacks.shutdown_cancelled.client_data = static_cast<SmPointer>(this); + + char errbuf[256]; + mSessionConnection = SmcOpenConnection( + nullptr, this, SmProtoMajor, SmProtoMinor, mask, &callbacks, + prev_client_id.get(), &client_id, sizeof(errbuf), errbuf); + } + + if (!mSessionConnection) { + return NS_OK; + } + + LogModule::Init( + gArgc, gArgv); // need to make sure initialized before SetClientState + if (prev_client_id.IsEmpty() || + (client_id && !prev_client_id.Equals(client_id))) { + SetClientState(STATE_REGISTERING); + } else { + SetClientState(STATE_IDLE); + } + + gdk_x11_set_sm_client_id(client_id); + + // Set SM Properties + // SmCloneCommand, SmProgram, SmRestartCommand, SmUserID are required + // properties so must be set, and must have a sensible fallback value. + + // Determine executable path to use for XSMP session restore + + // Is there a request to suppress default binary launcher? + nsAutoCString path(getenv("MOZ_APP_LAUNCHER")); + + if (path.IsEmpty()) { + NS_ASSERTION(gDirServiceProvider, + "gDirServiceProvider is NULL! This shouldn't happen!"); + nsCOMPtr<nsIFile> executablePath; + nsresult rv; + + bool dummy; + rv = gDirServiceProvider->GetFile(XRE_EXECUTABLE_FILE, &dummy, + getter_AddRefs(executablePath)); + + if (NS_SUCCEEDED(rv)) { + // Strip off the -bin suffix to get the shell script we should run; this + // is what Breakpad does + nsAutoCString leafName; + rv = executablePath->GetNativeLeafName(leafName); + if (NS_SUCCEEDED(rv) && StringEndsWith(leafName, "-bin"_ns)) { + leafName.SetLength(leafName.Length() - strlen("-bin")); + executablePath->SetNativeLeafName(leafName); + } + + executablePath->GetNativePath(path); + } + } + + if (path.IsEmpty()) { + // can't determine executable path. Best fallback is name from + // application.ini but it might not resolve to the same executable at + // launch time. + path = gAppData->name; // will always be set + ToLowerCase(path); + MOZ_LOG(sMozSMLog, LogLevel::Warning, + ("Could not determine executable path. Falling back to %s.", + path.get())); + } + + SmProp propRestart, propClone, propProgram, propUser, *props[4]; + SmPropValue valsRestart[3], valsClone[1], valsProgram[1], valsUser[1]; + int n = 0; + + constexpr auto kClientIDParam = "--sm-client-id"_ns; + + SetSMValue(valsRestart[0], path); + SetSMValue(valsRestart[1], kClientIDParam); + SetSMValue(valsRestart[2], nsDependentCString(client_id)); + SetSMProperty(propRestart, SmRestartCommand, SmLISTofARRAY8, 3, valsRestart); + props[n++] = &propRestart; + + SetSMValue(valsClone[0], path); + SetSMProperty(propClone, SmCloneCommand, SmLISTofARRAY8, 1, valsClone); + props[n++] = &propClone; + + nsAutoCString appName(gAppData->name); // will always be set + ToLowerCase(appName); + + SetSMValue(valsProgram[0], appName); + SetSMProperty(propProgram, SmProgram, SmARRAY8, 1, valsProgram); + props[n++] = &propProgram; + + nsAutoCString userName; // username that started the program + struct passwd* pw = getpwuid(getuid()); + if (pw && pw->pw_name) { + userName = pw->pw_name; + } else { + userName = "nobody"_ns; + MOZ_LOG( + sMozSMLog, LogLevel::Warning, + ("Could not determine user-name. Falling back to %s.", userName.get())); + } + + SetSMValue(valsUser[0], userName); + SetSMProperty(propUser, SmUserID, SmARRAY8, 1, valsUser); + props[n++] = &propUser; + + SmcSetProperties(mSessionConnection, n, props); + + g_free(client_id); +#endif /* MOZ_X11 */ + + return NS_OK; +} + +NS_IMETHODIMP +nsNativeAppSupportUnix::Enable() { return NS_OK; } + +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport** aResult) { + nsNativeAppSupportBase* native = new nsNativeAppSupportUnix(); + if (!native) return NS_ERROR_OUT_OF_MEMORY; + + *aResult = native; + NS_ADDREF(*aResult); + + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportWin.cpp b/toolkit/xre/nsNativeAppSupportWin.cpp new file mode 100644 index 0000000000..02c2d39c38 --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportWin.cpp @@ -0,0 +1,69 @@ +/* -*- Mode: C++; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsNativeAppSupportBase.h" +#include "nsNativeAppSupportWin.h" + +#include "mozilla/WindowsConsole.h" + +using namespace mozilla; + +/* + * This code attaches the process to the appropriate console. + */ + +class nsNativeAppSupportWin : public nsNativeAppSupportBase { + public: + // Utility function to handle a Win32-specific command line + // option: "--console", which dynamically creates a Windows + // console. + void CheckConsole(); + + private: + ~nsNativeAppSupportWin() {} +}; // nsNativeAppSupportWin + +void nsNativeAppSupportWin::CheckConsole() { + for (int i = 1; i < gArgc; ++i) { + if (strcmp("-console", gArgv[i]) == 0 || + strcmp("--console", gArgv[i]) == 0 || + strcmp("/console", gArgv[i]) == 0) { + if (AllocConsole()) { + // Redirect the standard streams to the new console, but + // only if they haven't been redirected to a valid file. + // Visual Studio's _fileno() returns -2 for the standard + // streams if they aren't associated with an output stream. + if (_fileno(stdout) == -2) { + freopen("CONOUT$", "w", stdout); + } + // There is no CONERR$, so use CONOUT$ for stderr as well. + if (_fileno(stderr) == -2) { + freopen("CONOUT$", "w", stderr); + } + if (_fileno(stdin) == -2) { + freopen("CONIN$", "r", stdin); + } + } + } else if (strcmp("-attach-console", gArgv[i]) == 0 || + strcmp("--attach-console", gArgv[i]) == 0 || + strcmp("/attach-console", gArgv[i]) == 0) { + UseParentConsole(); + } + } +} + +// Create and return an instance of class nsNativeAppSupportWin. +nsresult NS_CreateNativeAppSupport(nsINativeAppSupport** aResult) { + nsNativeAppSupportWin* pNative = new nsNativeAppSupportWin; + if (!pNative) return NS_ERROR_OUT_OF_MEMORY; + + // Check for dynamic console creation request. + pNative->CheckConsole(); + + *aResult = pNative; + NS_ADDREF(*aResult); + + return NS_OK; +} diff --git a/toolkit/xre/nsNativeAppSupportWin.h b/toolkit/xre/nsNativeAppSupportWin.h new file mode 100644 index 0000000000..6804c716fe --- /dev/null +++ b/toolkit/xre/nsNativeAppSupportWin.h @@ -0,0 +1,26 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* This file has *public* stuff needed for the Win32 implementation of + * the nsINativeAppSupport interface. It has to be broken out into a + * separate file in order to ensure that the generated .h file can be + * used in a Win32 .rc file. See /mozilla/xpfe/bootstrap/splash.rc. + * + * This file, and the generated .h, are only needed on Win32 platforms. + */ + +// Constants identifying Win32 "native" resources. + +#define IDI_APPICON 1 +#define IDI_DOCUMENT 2 +#define IDI_NEWWINDOW 3 +#define IDI_NEWTAB 4 +#define IDI_PBMODE 5 +#ifndef IDI_APPLICATION +# define IDI_APPLICATION 32512 +#endif + +// String that goes in the WinXP Start Menu. +#define IDS_STARTMENU_APPNAME 103 diff --git a/toolkit/xre/nsSigHandlers.cpp b/toolkit/xre/nsSigHandlers.cpp new file mode 100644 index 0000000000..e90dad23d4 --- /dev/null +++ b/toolkit/xre/nsSigHandlers.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +/* + * This module is supposed to abstract signal handling away from the other + * platforms that do not support it. + */ + +#include "nsSigHandlers.h" + +#ifdef XP_UNIX + +# include <signal.h> +# include <stdio.h> +# include <string.h> +# include "prthread.h" +# include "plstr.h" +# include "prenv.h" +# include "nsDebug.h" +# include "nsXULAppAPI.h" + +# if defined(LINUX) +# include <sys/time.h> +# include <sys/resource.h> +# include <unistd.h> +# include <stdlib.h> // atoi +# include <sys/prctl.h> +# ifndef ANDROID // no Android impl +# include <ucontext.h> +# endif +# endif + +# if defined(SOLARIS) +# include <sys/resource.h> +# include <ucontext.h> +# endif + +// Note: some tests manipulate this value. +unsigned int _gdb_sleep_duration = 300; + +# if defined(LINUX) && !defined(ANDROID) && defined(DEBUG) && \ + (defined(__i386) || defined(__x86_64) || defined(PPC)) +# define CRAWL_STACK_ON_SIGSEGV +# endif + +# ifndef PR_SET_PTRACER +# define PR_SET_PTRACER 0x59616d61 +# endif +# ifndef PR_SET_PTRACER_ANY +# define PR_SET_PTRACER_ANY ((unsigned long)-1) +# endif + +# if defined(CRAWL_STACK_ON_SIGSEGV) + +# include <unistd.h> +# include "nsISupportsUtils.h" +# include "mozilla/StackWalk.h" + +static const char* gProgname = "huh?"; + +// NB: keep me up to date with the same variable in +// ipc/chromium/chrome/common/ipc_channel_posix.cc +static const int kClientChannelFd = 3; + +extern "C" { + +static void PrintStackFrame(uint32_t aFrameNumber, void* aPC, void* aSP, + void* aClosure) { + char buf[1024]; + MozCodeAddressDetails details; + + MozDescribeCodeAddress(aPC, &details); + MozFormatCodeAddressDetails(buf, sizeof(buf), aFrameNumber, aPC, &details); + fprintf(stdout, "%s\n", buf); + fflush(stdout); +} +} + +void ah_crap_handler(int signum) { + printf("\nProgram %s (pid = %d) received signal %d.\n", gProgname, getpid(), + signum); + + printf("Stack:\n"); + MozStackWalk(PrintStackFrame, /* skipFrames */ 2, /* maxFrames */ 0, nullptr); + + printf("Sleeping for %d seconds.\n", _gdb_sleep_duration); + printf("Type 'gdb %s %d' to attach your debugger to this thread.\n", + gProgname, getpid()); + + // Allow us to be ptraced by gdb on Linux with Yama restrictions enabled. + prctl(PR_SET_PTRACER, PR_SET_PTRACER_ANY); + + sleep(_gdb_sleep_duration); + + printf("Done sleeping...\n"); + + _exit(signum); +} + +void child_ah_crap_handler(int signum) { + if (!getenv("MOZ_DONT_UNBLOCK_PARENT_ON_CHILD_CRASH")) + close(kClientChannelFd); + ah_crap_handler(signum); +} + +# endif // CRAWL_STACK_ON_SIGSEGV + +# ifdef MOZ_WIDGET_GTK +// Need this include for version test below. +# include <glib.h> +# endif + +# if defined(MOZ_WIDGET_GTK) && \ + (GLIB_MAJOR_VERSION > 2 || \ + (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)) + +static GLogFunc orig_log_func = nullptr; + +extern "C" { +static void my_glib_log_func(const gchar* log_domain, GLogLevelFlags log_level, + const gchar* message, gpointer user_data); +} + +/* static */ void my_glib_log_func(const gchar* log_domain, + GLogLevelFlags log_level, + const gchar* message, gpointer user_data) { + if (log_level & + (G_LOG_LEVEL_ERROR | G_LOG_FLAG_FATAL | G_LOG_FLAG_RECURSION)) { + NS_DebugBreak(NS_DEBUG_ASSERTION, message, "glib assertion", __FILE__, + __LINE__); + } else if (log_level & (G_LOG_LEVEL_CRITICAL | G_LOG_LEVEL_WARNING)) { + NS_DebugBreak(NS_DEBUG_WARNING, message, "glib warning", __FILE__, + __LINE__); + } + + orig_log_func(log_domain, log_level, message, nullptr); +} + +# endif + +# ifdef SA_SIGINFO +static void fpehandler(int signum, siginfo_t* si, void* context) { + /* Integer divide by zero or integer overflow. */ + /* Note: FPE_INTOVF is ignored on Intel, PowerPC and SPARC systems. */ + if (si->si_code == FPE_INTDIV || si->si_code == FPE_INTOVF) { + NS_DebugBreak(NS_DEBUG_ABORT, "Divide by zero", nullptr, __FILE__, + __LINE__); + } + +# ifdef XP_MACOSX +# if defined(__i386__) || defined(__amd64__) + ucontext_t* uc = (ucontext_t*)context; + + _STRUCT_FP_CONTROL* ctrl = &uc->uc_mcontext->__fs.__fpu_fcw; + ctrl->__invalid = ctrl->__denorm = ctrl->__zdiv = ctrl->__ovrfl = + ctrl->__undfl = ctrl->__precis = 1; + + _STRUCT_FP_STATUS* status = &uc->uc_mcontext->__fs.__fpu_fsw; + status->__invalid = status->__denorm = status->__zdiv = status->__ovrfl = + status->__undfl = status->__precis = status->__stkflt = + status->__errsumm = 0; + + uint32_t* mxcsr = &uc->uc_mcontext->__fs.__fpu_mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +# endif +# endif +# if defined(LINUX) && !defined(ANDROID) + +# if defined(__i386__) + ucontext_t* uc = (ucontext_t*)context; + /* + * It seems that we have no access to mxcsr on Linux. libc + * seems to be translating cw/sw to mxcsr. + */ + unsigned long int* cw = &uc->uc_mcontext.fpregs->cw; + *cw |= FPU_EXCEPTION_MASK; + + unsigned long int* sw = &uc->uc_mcontext.fpregs->sw; + *sw &= ~FPU_STATUS_FLAGS; +# endif +# if defined(__amd64__) + ucontext_t* uc = (ucontext_t*)context; + + uint16_t* cw = &uc->uc_mcontext.fpregs->cwd; + *cw |= FPU_EXCEPTION_MASK; + + uint16_t* sw = &uc->uc_mcontext.fpregs->swd; + *sw &= ~FPU_STATUS_FLAGS; + + uint32_t* mxcsr = &uc->uc_mcontext.fpregs->mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +# endif +# endif +# ifdef SOLARIS + ucontext_t* uc = (ucontext_t*)context; + +# if defined(__i386) + uint32_t* cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[0]; + *cw |= FPU_EXCEPTION_MASK; + + uint32_t* sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[1]; + *sw &= ~FPU_STATUS_FLAGS; + + /* address of the instruction that caused the exception */ + uint32_t* ip = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.state[3]; + uc->uc_mcontext.gregs[REG_PC] = *ip; +# endif +# if defined(__amd64__) + uint16_t* cw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.cw; + *cw |= FPU_EXCEPTION_MASK; + + uint16_t* sw = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.sw; + *sw &= ~FPU_STATUS_FLAGS; + + uint32_t* mxcsr = &uc->uc_mcontext.fpregs.fp_reg_set.fpchip_state.mxcsr; + *mxcsr |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + *mxcsr &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +# endif +# endif +} +# endif + +void InstallSignalHandlers(const char* aProgname) { +# if defined(CRAWL_STACK_ON_SIGSEGV) + const char* tmp = PL_strdup(aProgname); + if (tmp) { + gProgname = tmp; + } +# endif // CRAWL_STACK_ON_SIGSEGV + + const char* gdbSleep = PR_GetEnv("MOZ_GDB_SLEEP"); + if (gdbSleep && *gdbSleep) { + unsigned int s; + if (1 == sscanf(gdbSleep, "%u", &s)) { + _gdb_sleep_duration = s; + } + } + +# if defined(CRAWL_STACK_ON_SIGSEGV) + if (!getenv("XRE_NO_WINDOWS_CRASH_DIALOG")) { + void (*crap_handler)(int) = GeckoProcessType_Default != XRE_GetProcessType() + ? child_ah_crap_handler + : ah_crap_handler; + signal(SIGSEGV, crap_handler); + signal(SIGILL, crap_handler); + signal(SIGABRT, crap_handler); + } +# endif // CRAWL_STACK_ON_SIGSEGV + +# ifdef SA_SIGINFO + /* Install a handler for floating point exceptions and disable them if they + * occur. */ + struct sigaction sa, osa; + sa.sa_flags = SA_ONSTACK | SA_RESTART | SA_SIGINFO; + sa.sa_sigaction = fpehandler; + sigemptyset(&sa.sa_mask); + sigaction(SIGFPE, &sa, &osa); +# endif + + if (!XRE_IsParentProcess()) { + /* + * If the user is debugging a Gecko parent process in gdb and hits ^C to + * suspend, a SIGINT signal will be sent to the child. We ignore this signal + * so the child isn't killed. + */ + signal(SIGINT, SIG_IGN); + } + +# if defined(DEBUG) && defined(LINUX) + const char* memLimit = PR_GetEnv("MOZ_MEM_LIMIT"); + if (memLimit && *memLimit) { + long m = atoi(memLimit); + m *= (1024 * 1024); + struct rlimit r; + r.rlim_cur = m; + r.rlim_max = m; + setrlimit(RLIMIT_AS, &r); + } +# endif + +# if defined(MOZ_WIDGET_GTK) && \ + (GLIB_MAJOR_VERSION > 2 || \ + (GLIB_MAJOR_VERSION == 2 && GLIB_MINOR_VERSION >= 6)) + const char* assertString = PR_GetEnv("XPCOM_DEBUG_BREAK"); + if (assertString && + (!strcmp(assertString, "suspend") || !strcmp(assertString, "stack") || + !strcmp(assertString, "abort") || !strcmp(assertString, "trap") || + !strcmp(assertString, "break"))) { + // Override the default glib logging function so we get stacks for it too. + orig_log_func = g_log_set_default_handler(my_glib_log_func, nullptr); + } +# endif +} + +#elif XP_WIN + +# include <windows.h> + +# ifdef _M_IX86 +/* + * WinNT.h prior to SDK7 does not expose the structure of the ExtendedRegisters + * for ia86. We known that MxCsr is at offset 0x18 and is a DWORD. + */ +# define MXCSR(ctx) (*(DWORD*)(((BYTE*)(ctx)->ExtendedRegisters) + 0x18)) +# endif + +# ifdef _M_X64 +# define MXCSR(ctx) (ctx)->MxCsr +# endif + +# if defined(_M_IX86) || defined(_M_X64) + +# ifdef _M_X64 +# define X87CW(ctx) (ctx)->FltSave.ControlWord +# define X87SW(ctx) (ctx)->FltSave.StatusWord +# else +# define X87CW(ctx) (ctx)->FloatSave.ControlWord +# define X87SW(ctx) (ctx)->FloatSave.StatusWord +# endif + +static LPTOP_LEVEL_EXCEPTION_FILTER gFPEPreviousFilter; + +LONG __stdcall FpeHandler(PEXCEPTION_POINTERS pe) { + PEXCEPTION_RECORD e = (PEXCEPTION_RECORD)pe->ExceptionRecord; + CONTEXT* c = (CONTEXT*)pe->ContextRecord; + + switch (e->ExceptionCode) { + case STATUS_FLOAT_DENORMAL_OPERAND: + case STATUS_FLOAT_DIVIDE_BY_ZERO: + case STATUS_FLOAT_INEXACT_RESULT: + case STATUS_FLOAT_INVALID_OPERATION: + case STATUS_FLOAT_OVERFLOW: + case STATUS_FLOAT_STACK_CHECK: + case STATUS_FLOAT_UNDERFLOW: + case STATUS_FLOAT_MULTIPLE_FAULTS: + case STATUS_FLOAT_MULTIPLE_TRAPS: + X87CW(c) |= FPU_EXCEPTION_MASK; /* disable all FPU exceptions */ + X87SW(c) &= ~FPU_STATUS_FLAGS; /* clear all pending FPU exceptions */ +# ifdef _M_IX86 + if (c->ContextFlags & CONTEXT_EXTENDED_REGISTERS) { +# endif + MXCSR(c) |= SSE_EXCEPTION_MASK; /* disable all SSE exceptions */ + MXCSR(c) &= ~SSE_STATUS_FLAGS; /* clear all pending SSE exceptions */ +# ifdef _M_IX86 + } +# endif + return EXCEPTION_CONTINUE_EXECUTION; + } + LONG action = EXCEPTION_CONTINUE_SEARCH; + if (gFPEPreviousFilter) action = gFPEPreviousFilter(pe); + + return action; +} + +void InstallSignalHandlers(const char* aProgname) { + gFPEPreviousFilter = SetUnhandledExceptionFilter(FpeHandler); +} + +# else + +void InstallSignalHandlers(const char* aProgname) {} + +# endif + +#else +# error No signal handling implementation for this platform. +#endif diff --git a/toolkit/xre/nsSigHandlers.h b/toolkit/xre/nsSigHandlers.h new file mode 100644 index 0000000000..f7424c026a --- /dev/null +++ b/toolkit/xre/nsSigHandlers.h @@ -0,0 +1,40 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#if defined(_M_IX86) || defined(_M_X64) || defined(__i386__) || \ + defined(__i386) || defined(__amd64__) + +/* + * x87 FPU Control Word: + * + * 0 -> IM Invalid Operation + * 1 -> DM Denormalized Operand + * 2 -> ZM Zero Divide + * 3 -> OM Overflow + * 4 -> UM Underflow + * 5 -> PM Precision + */ +# define FPU_EXCEPTION_MASK 0x3f + +/* + * x86 FPU Status Word: + * + * 0..5 -> Exception flags (see x86 FPU Control Word) + * 6 -> SF Stack Fault + * 7 -> ES Error Summary Status + */ +# define FPU_STATUS_FLAGS 0xff + +/* + * MXCSR Control and Status Register: + * + * 0..5 -> Exception flags (see x86 FPU Control Word) + * 6 -> DAZ Denormals Are Zero + * 7..12 -> Exception mask (see x86 FPU Control Word) + */ +# define SSE_STATUS_FLAGS FPU_EXCEPTION_MASK +# define SSE_EXCEPTION_MASK (FPU_EXCEPTION_MASK << 7) + +#endif diff --git a/toolkit/xre/nsUpdateDriver.cpp b/toolkit/xre/nsUpdateDriver.cpp new file mode 100644 index 0000000000..bd2d4d3e59 --- /dev/null +++ b/toolkit/xre/nsUpdateDriver.cpp @@ -0,0 +1,1081 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include <stdlib.h> +#include <stdio.h> +#include "nsUpdateDriver.h" +#include "nsXULAppAPI.h" +#include "nsAppRunner.h" +#include "nsIFile.h" +#include "nsVariant.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "prproces.h" +#include "mozilla/Logging.h" +#include "prenv.h" +#include "nsVersionComparator.h" +#include "nsDirectoryServiceDefs.h" +#include "nsThreadUtils.h" +#include "nsIXULAppInfo.h" +#include "mozilla/Preferences.h" +#include "nsPrintfCString.h" +#include "mozilla/DebugOnly.h" +#include "mozilla/Printf.h" +#include "mozilla/UniquePtr.h" +#include "nsIObserverService.h" +#include "nsNetCID.h" +#include "mozilla/Services.h" + +#ifdef XP_MACOSX +# include "nsILocalFileMac.h" +# include "nsCommandLineServiceMac.h" +# include "MacLaunchHelper.h" +# include "updaterfileutils_osx.h" +# include "mozilla/Monitor.h" +#endif + +#if defined(XP_WIN) +# include <direct.h> +# include <process.h> +# include <windows.h> +# include <shlwapi.h> +# include <strsafe.h> +# include "commonupdatedir.h" +# include "nsWindowsHelpers.h" +# define getcwd(path, size) _getcwd(path, size) +# define getpid() GetCurrentProcessId() +#elif defined(XP_UNIX) +# include <unistd.h> +# include <sys/wait.h> +#endif + +using namespace mozilla; + +static LazyLogModule sUpdateLog("updatedriver"); +// Some other file in our unified batch might have defined LOG already. +#ifdef LOG +# undef LOG +#endif +#define LOG(args) MOZ_LOG(sUpdateLog, mozilla::LogLevel::Debug, args) + +#ifdef XP_WIN +# define UPDATER_BIN "updater.exe" +# define MAINTENANCE_SVC_NAME L"MozillaMaintenance" +#elif XP_MACOSX +# define UPDATER_APP "updater.app" +# define UPDATER_BIN "org.mozilla.updater" +#else +# define UPDATER_BIN "updater" +#endif + +#ifdef XP_MACOSX +static void UpdateDriverSetupMacCommandLine(int& argc, char**& argv, + bool restart) { + if (NS_IsMainThread()) { + CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart); + return; + } + // Bug 1335916: SetupMacCommandLine calls a CoreFoundation function that + // asserts that it was called from the main thread, so if we are not the main + // thread, we have to dispatch that call to there. But we also have to get the + // result from it, so we can't just dispatch and return, we have to wait + // until the dispatched operation actually completes. So we also set up a + // monitor to signal us when that happens, and block until then. + Monitor monitor("nsUpdateDriver SetupMacCommandLine"); + + nsresult rv = NS_DispatchToMainThread(NS_NewRunnableFunction( + "UpdateDriverSetupMacCommandLine", + [&argc, &argv, restart, &monitor]() -> void { + CommandLineServiceMac::SetupMacCommandLine(argc, argv, restart); + MonitorAutoLock(monitor).Notify(); + })); + + if (NS_FAILED(rv)) { + LOG( + ("Update driver error dispatching SetupMacCommandLine to main thread: " + "%d\n", + rv)); + return; + } + + // The length of this wait is arbitrary, but should be long enough that having + // it expire means something is seriously wrong. + CVStatus status = + MonitorAutoLock(monitor).Wait(TimeDuration::FromSeconds(60)); + if (status == CVStatus::Timeout) { + LOG(("Update driver timed out waiting for SetupMacCommandLine\n")); + } +} +#endif + +static nsresult GetCurrentWorkingDir(nsACString& aOutPath) { + // Cannot use NS_GetSpecialDirectory because XPCOM is not yet initialized. + // This code is duplicated from xpcom/io/SpecialSystemDirectory.cpp: + + aOutPath.Truncate(); + +#if defined(XP_WIN) + wchar_t wpath[MAX_PATH]; + if (!_wgetcwd(wpath, ArrayLength(wpath))) { + return NS_ERROR_FAILURE; + } + CopyUTF16toUTF8(nsDependentString(wpath), aOutPath); +#else + char path[MAXPATHLEN]; + if (!getcwd(path, ArrayLength(path))) { + return NS_ERROR_FAILURE; + } + aOutPath = path; +#endif + + return NS_OK; +} + +/** + * Get the path to the installation directory. For Mac OS X this will be the + * bundle directory. + * + * @param appDir the application directory file object + * @param installDirPath the path to the installation directory + */ +static nsresult GetInstallDirPath(nsIFile* appDir, nsACString& installDirPath) { + nsresult rv; +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> parentDir1, parentDir2; + rv = appDir->GetParent(getter_AddRefs(parentDir1)); + NS_ENSURE_SUCCESS(rv, rv); + rv = parentDir1->GetParent(getter_AddRefs(parentDir2)); + NS_ENSURE_SUCCESS(rv, rv); + rv = parentDir2->GetNativePath(installDirPath); + NS_ENSURE_SUCCESS(rv, rv); +#elif XP_WIN + nsAutoString installDirPathW; + rv = appDir->GetPath(installDirPathW); + NS_ENSURE_SUCCESS(rv, rv); + CopyUTF16toUTF8(installDirPathW, installDirPath); +#else + rv = appDir->GetNativePath(installDirPath); + NS_ENSURE_SUCCESS(rv, rv); +#endif + return NS_OK; +} + +static bool GetFile(nsIFile* dir, const nsACString& name, + nsCOMPtr<nsIFile>& result) { + nsresult rv; + + nsCOMPtr<nsIFile> file; + rv = dir->Clone(getter_AddRefs(file)); + if (NS_FAILED(rv)) { + return false; + } + + rv = file->AppendNative(name); + if (NS_FAILED(rv)) { + return false; + } + + result = file; + return true; +} + +static bool GetStatusFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) { + return GetFile(dir, "update.status"_ns, result); +} + +/** + * Get the contents of the update.status file when the update.status file can + * be opened with read and write access. The reason it is opened for both read + * and write is to prevent trying to update when the user doesn't have write + * access to the update directory. + * + * @param statusFile the status file object. + * @param buf the buffer holding the file contents + * + * @return true if successful, false otherwise. + */ +template <size_t Size> +static bool GetStatusFileContents(nsIFile* statusFile, char (&buf)[Size]) { + static_assert( + Size > 16, + "Buffer needs to be large enough to hold the known status codes"); + + PRFileDesc* fd = nullptr; + nsresult rv = statusFile->OpenNSPRFileDesc(PR_RDWR, 0660, &fd); + if (NS_FAILED(rv)) { + return false; + } + + const int32_t n = PR_Read(fd, buf, Size); + PR_Close(fd); + + return (n >= 0); +} + +typedef enum { + eNoUpdateAction, + ePendingUpdate, + ePendingService, + ePendingElevate, + eAppliedUpdate, + eAppliedService, +} UpdateStatus; + +/** + * Returns a value indicating what needs to be done in order to handle an + * update. + * + * @param dir the directory in which we should look for an update.status file. + * @param statusFile the update.status file found in the directory. + * + * @return the update action to be performed. + */ +static UpdateStatus GetUpdateStatus(nsIFile* dir, + nsCOMPtr<nsIFile>& statusFile) { + if (GetStatusFile(dir, statusFile)) { + char buf[32]; + if (GetStatusFileContents(statusFile, buf)) { + const char kPending[] = "pending"; + const char kPendingService[] = "pending-service"; + const char kPendingElevate[] = "pending-elevate"; + const char kApplied[] = "applied"; + const char kAppliedService[] = "applied-service"; + if (!strncmp(buf, kPendingElevate, sizeof(kPendingElevate) - 1)) { + return ePendingElevate; + } + if (!strncmp(buf, kPendingService, sizeof(kPendingService) - 1)) { + return ePendingService; + } + if (!strncmp(buf, kPending, sizeof(kPending) - 1)) { + return ePendingUpdate; + } + if (!strncmp(buf, kAppliedService, sizeof(kAppliedService) - 1)) { + return eAppliedService; + } + if (!strncmp(buf, kApplied, sizeof(kApplied) - 1)) { + return eAppliedUpdate; + } + } + } + return eNoUpdateAction; +} + +static bool GetVersionFile(nsIFile* dir, nsCOMPtr<nsIFile>& result) { + return GetFile(dir, "update.version"_ns, result); +} + +// Compares the current application version with the update's application +// version. +static bool IsOlderVersion(nsIFile* versionFile, const char* appVersion) { + PRFileDesc* fd = nullptr; + nsresult rv = versionFile->OpenNSPRFileDesc(PR_RDONLY, 0660, &fd); + if (NS_FAILED(rv)) { + return true; + } + + char buf[32]; + const int32_t n = PR_Read(fd, buf, sizeof(buf)); + PR_Close(fd); + + if (n < 0) { + return false; + } + + // Trim off the trailing newline + if (buf[n - 1] == '\n') { + buf[n - 1] = '\0'; + } + + // If the update xml doesn't provide the application version the file will + // contain the string "null" and it is assumed that the update is not older. + const char kNull[] = "null"; + if (strncmp(buf, kNull, sizeof(kNull) - 1) == 0) { + return false; + } + + return mozilla::Version(appVersion) > buf; +} + +/** + * Applies, switches, or stages an update. + * + * @param greDir the GRE directory + * @param updateDir the update root directory + * @param appDir the application directory + * @param appArgc the number of args passed to the application + * @param appArgv the args passed to the application + * (used for restarting the application when necessary) + * @param restart true when a restart is necessary. + * @param isStaged true when the update has already been staged + * @param outpid (out) parameter holding the handle to the updater application + * when staging updates + */ +static void ApplyUpdate(nsIFile* greDir, nsIFile* updateDir, nsIFile* appDir, + int appArgc, char** appArgv, bool restart, + bool isStaged, ProcessType* outpid) { + // The following determines the update operation to perform. + // 1. When restart is false the update will be staged. + // 2. When restart is true and isStaged is false the update will apply the mar + // file to the installation directory. + // 3. When restart is true and isStaged is true the update will switch the + // staged update with the installation directory. + + nsresult rv; + + nsCOMPtr<nsIFile> updater; + nsAutoCString updaterPath; + nsAutoCString updateDirPath; +#if defined(XP_WIN) + // Get an nsIFile reference for the updater in the installation dir. + if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) { + return; + } + + // Get the path to the updater. + nsAutoString updaterPathW; + rv = updater->GetPath(updaterPathW); + if (NS_FAILED(rv)) { + return; + } + CopyUTF16toUTF8(updaterPathW, updaterPath); + + // Get the path to the update dir. + nsAutoString updateDirPathW; + rv = updateDir->GetPath(updateDirPathW); + if (NS_FAILED(rv)) { + return; + } + CopyUTF16toUTF8(updateDirPathW, updateDirPath); +#elif defined(XP_MACOSX) + // Get an nsIFile reference for the updater in the installation dir. + if (!GetFile(appDir, nsLiteralCString(UPDATER_APP), updater)) { + return; + } + rv = updater->AppendNative("Contents"_ns); + if (NS_FAILED(rv)) { + return; + } + rv = updater->AppendNative("MacOS"_ns); + if (NS_FAILED(rv)) { + return; + } + rv = updater->AppendNative(nsLiteralCString(UPDATER_BIN)); + if (NS_FAILED(rv)) { + return; + } + + // Get the path to the updater. + rv = updater->GetNativePath(updaterPath); + if (NS_FAILED(rv)) { + return; + } + + // Get the path to the update dir. + rv = updateDir->GetNativePath(updateDirPath); + if (NS_FAILED(rv)) { + return; + } +#else + // Get an nsIFile reference for the updater in the installation dir. + if (!GetFile(greDir, nsLiteralCString(UPDATER_BIN), updater)) { + return; + } + + // Get the path to the updater. + rv = updater->GetNativePath(updaterPath); + if (NS_FAILED(rv)) { + return; + } + + // Get the path to the update dir. + rv = updateDir->GetNativePath(updateDirPath); + if (NS_FAILED(rv)) { + return; + } +#endif + + // appFilePath and workingDirPath are only used when the application will be + // restarted. + nsAutoCString appFilePath; + nsAutoCString workingDirPath; + if (restart) { + // Get the path to the current working directory. + rv = GetCurrentWorkingDir(workingDirPath); + if (NS_FAILED(rv)) { + return; + } + + // Get the application file path used by the updater to restart the + // application after the update has finished. + nsCOMPtr<nsIFile> appFile; + XRE_GetBinaryPath(getter_AddRefs(appFile)); + if (!appFile) { + return; + } + +#if defined(XP_WIN) + nsAutoString appFilePathW; + rv = appFile->GetPath(appFilePathW); + if (NS_FAILED(rv)) { + return; + } + CopyUTF16toUTF8(appFilePathW, appFilePath); +#else + rv = appFile->GetNativePath(appFilePath); + if (NS_FAILED(rv)) { + return; + } +#endif + } + + // Get the installation directory path. + nsAutoCString installDirPath; + rv = GetInstallDirPath(appDir, installDirPath); + if (NS_FAILED(rv)) { + return; + } + + nsAutoCString applyToDirPath; + nsCOMPtr<nsIFile> updatedDir; + if (restart && !isStaged) { + // The install directory is the same as the apply to directory. + applyToDirPath.Assign(installDirPath); + } else { + // Get the directory where the update is staged or will be staged. +#if defined(XP_MACOSX) + if (!GetFile(updateDir, "Updated.app"_ns, updatedDir)) { +#else + if (!GetFile(appDir, "updated"_ns, updatedDir)) { +#endif + return; + } +#if defined(XP_WIN) + nsAutoString applyToDirPathW; + rv = updatedDir->GetPath(applyToDirPathW); + if (NS_FAILED(rv)) { + return; + } + CopyUTF16toUTF8(applyToDirPathW, applyToDirPath); +#else + rv = updatedDir->GetNativePath(applyToDirPath); +#endif + } + if (NS_FAILED(rv)) { + return; + } + + if (restart && isStaged) { + // When the update should already be staged make sure that the updated + // directory exists. + bool updatedDirExists = false; + if (NS_FAILED(updatedDir->Exists(&updatedDirExists)) || !updatedDirExists) { + return; + } + } + + // On platforms where we are not calling execv, we may need to make the + // updater executable wait for the calling process to exit. Otherwise, the + // updater may have trouble modifying our executable image (because it might + // still be in use). This is accomplished by passing our PID to the updater + // so that it can wait for us to exit. This is not perfect as there is a race + // condition that could bite us. It's possible that the calling process could + // exit before the updater waits on the specified PID, and in the meantime a + // new process with the same PID could be created. This situation is + // unlikely, however, given the way most operating systems recycle PIDs. We'll + // take our chances ;-) Construct the PID argument for this process to pass to + // the updater. + nsAutoCString pid; + if (restart) { +#if defined(XP_UNIX) & !defined(XP_MACOSX) + // When execv is used for an update that requires a restart 0 is passed + // which is ignored by the updater. + pid.AssignLiteral("0"); +#else + pid.AppendInt((int32_t)getpid()); +#endif + if (isStaged) { + // Append a special token to the PID in order to inform the updater that + // it should replace install with the updated directory. + pid.AppendLiteral("/replace"); + } + } else { + // Signal the updater application that it should stage the update. + pid.AssignLiteral("-1"); + } + + int argc = 5; + if (restart) { + argc = appArgc + 6; + if (gRestartedByOS) { + argc += 1; + } + } + char** argv = static_cast<char**>(malloc((argc + 1) * sizeof(char*))); + if (!argv) { + return; + } + argv[0] = (char*)updaterPath.get(); + argv[1] = (char*)updateDirPath.get(); + argv[2] = (char*)installDirPath.get(); + argv[3] = (char*)applyToDirPath.get(); + argv[4] = (char*)pid.get(); + if (restart && appArgc) { + argv[5] = (char*)workingDirPath.get(); + argv[6] = (char*)appFilePath.get(); + for (int i = 1; i < appArgc; ++i) { + argv[6 + i] = appArgv[i]; + } + if (gRestartedByOS) { + // We haven't truly started up, restore this argument so that we will have + // it upon restart. + argv[6 + appArgc] = const_cast<char*>("-os-restarted"); + } + } + argv[argc] = nullptr; + + if (restart && gSafeMode) { + PR_SetEnv("MOZ_SAFE_MODE_RESTART=1"); + } + + LOG(("spawning updater process [%s]\n", updaterPath.get())); + +#if defined(XP_UNIX) && !defined(XP_MACOSX) + // We use execv to spawn the updater process on all UNIX systems except Mac + // OSX since it is known to cause problems on the Mac. Windows has execv, but + // it is a faked implementation that doesn't really replace the current + // process. Instead it spawns a new process, so we gain nothing from using + // execv on Windows. + if (restart) { + int execResult = execv(updaterPath.get(), argv); + free(argv); + exit(execResult); + } + *outpid = fork(); + if (*outpid == -1) { + free(argv); + return; + } + if (*outpid == 0) { + int execResult = execv(updaterPath.get(), argv); + free(argv); + exit(execResult); + } +#elif defined(XP_WIN) + if (isStaged) { + // Launch the updater to replace the installation with the staged updated. + if (!WinLaunchChild(updaterPathW.get(), argc, argv)) { + free(argv); + return; + } + } else { + // Launch the updater to either stage or apply an update. + if (!WinLaunchChild(updaterPathW.get(), argc, argv, nullptr, outpid)) { + free(argv); + return; + } + } +#elif defined(XP_MACOSX) +UpdateDriverSetupMacCommandLine(argc, argv, restart); +// We need to detect whether elevation is required for this update. This can +// occur when an admin user installs the application, but another admin +// user attempts to update (see bug 394984). +if (restart && !IsRecursivelyWritable(installDirPath.get())) { + bool hasLaunched = LaunchElevatedUpdate(argc, argv, outpid); + free(argv); + if (!hasLaunched) { + LOG(("Failed to launch elevated update!")); + exit(1); + } + exit(0); +} + +if (isStaged) { + // Launch the updater to replace the installation with the staged updated. + LaunchChildMac(argc, argv); +} else { + // Launch the updater to either stage or apply an update. + LaunchChildMac(argc, argv, outpid); +} +#else +if (isStaged) { + // Launch the updater to replace the installation with the staged updated. + PR_CreateProcessDetached(updaterPath.get(), argv, nullptr, nullptr); +} else { + // Launch the updater to either stage or apply an update. + *outpid = PR_CreateProcess(updaterPath.get(), argv, nullptr, nullptr); +} +#endif + free(argv); + if (restart) { + exit(0); + } +} + +/** + * Wait briefly to see if a process terminates, then return true if it has. + */ +static bool ProcessHasTerminated(ProcessType pt) { +#if defined(XP_WIN) + if (WaitForSingleObject(pt, 1000)) { + return false; + } + CloseHandle(pt); + return true; +#elif defined(XP_MACOSX) + // We're waiting for the process to terminate in LaunchChildMac. + return true; +#elif defined(XP_UNIX) + int exitStatus; + pid_t exited = waitpid(pt, &exitStatus, WNOHANG); + if (exited == 0) { + // Process is still running. + sleep(1); + return false; + } + if (exited == -1) { + LOG(("Error while checking if the updater process is finished")); + // This shouldn't happen, but if it does, the updater process is lost to us, + // so the best we can do is pretend that it's exited. + return true; + } + // If we get here, the process has exited; make sure it exited normally. + if (WIFEXITED(exitStatus) && (WEXITSTATUS(exitStatus) != 0)) { + LOG(("Error while running the updater process, check update.log")); + } + return true; +#else + // No way to have a non-blocking implementation on these platforms, + // because we're using NSPR and it only provides a blocking wait. + int32_t exitCode; + PR_WaitProcess(pt, &exitCode); + if (exitCode != 0) { + LOG(("Error while running the updater process, check update.log")); + } + return true; +#endif +} + +nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, + int argc, char** argv, const char* appVersion, + bool restart, ProcessType* pid) { + nsresult rv; + + nsCOMPtr<nsIFile> updatesDir; + rv = updRootDir->Clone(getter_AddRefs(updatesDir)); + NS_ENSURE_SUCCESS(rv, rv); + rv = updatesDir->AppendNative("updates"_ns); + NS_ENSURE_SUCCESS(rv, rv); + rv = updatesDir->AppendNative("0"_ns); + NS_ENSURE_SUCCESS(rv, rv); + + // Return early since there isn't a valid update when the update application + // version file doesn't exist or if the update's application version is less + // than the current application version. The cleanup of the update will happen + // during post update processing in nsUpdateService.js. + nsCOMPtr<nsIFile> versionFile; + if (!GetVersionFile(updatesDir, versionFile) || + IsOlderVersion(versionFile, appVersion)) { + return NS_OK; + } + + nsCOMPtr<nsIFile> statusFile; + UpdateStatus status = GetUpdateStatus(updatesDir, statusFile); + switch (status) { + case ePendingUpdate: + case ePendingService: { + ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, false, pid); + break; + } + case eAppliedUpdate: + case eAppliedService: + // An update was staged and needs to be switched so the updated + // application is used. + ApplyUpdate(greDir, updatesDir, appDir, argc, argv, restart, true, pid); + break; + case ePendingElevate: + // No action should be performed since the user hasn't opted into + // elevating for the update so continue application startup. + case eNoUpdateAction: + // We don't need to do any special processing here, we'll just continue to + // startup the application. + break; + } + + return NS_OK; +} + +NS_IMPL_ISUPPORTS(nsUpdateProcessor, nsIUpdateProcessor) + +nsUpdateProcessor::nsUpdateProcessor() : mUpdaterPID(0) {} + +nsUpdateProcessor::~nsUpdateProcessor() = default; + +NS_IMETHODIMP +nsUpdateProcessor::ProcessUpdate() { + nsresult rv; + + nsCOMPtr<nsIProperties> ds = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID, &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> exeFile; + rv = ds->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(exeFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> appDir; + rv = exeFile->GetParent(getter_AddRefs(appDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> greDir; + rv = ds->Get(NS_GRE_DIR, NS_GET_IID(nsIFile), getter_AddRefs(greDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> updRoot; + rv = ds->Get(XRE_UPDATE_ROOT_DIR, NS_GET_IID(nsIFile), + getter_AddRefs(updRoot)); + NS_ASSERTION(NS_SUCCEEDED(rv), "Can't get the UpdRootD dir"); + + // XRE_UPDATE_ROOT_DIR should not fail but if it does fallback to the + // application directory just to be safe. + if (NS_FAILED(rv)) { + rv = appDir->Clone(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + } + + nsCOMPtr<nsIXULAppInfo> appInfo = + do_GetService("@mozilla.org/xre/app-info;1", &rv); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoCString appVersion; + rv = appInfo->GetVersion(appVersion); + NS_ENSURE_SUCCESS(rv, rv); + + // Copy the parameters to the StagedUpdateInfo structure shared with the + // watcher thread. + mInfo.mGREDir = greDir; + mInfo.mAppDir = appDir; + mInfo.mUpdateRoot = updRoot; + mInfo.mArgc = 0; + mInfo.mArgv = nullptr; + mInfo.mAppVersion = appVersion; + + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + nsCOMPtr<nsIRunnable> r = + NewRunnableMethod("nsUpdateProcessor::StartStagedUpdate", this, + &nsUpdateProcessor::StartStagedUpdate); + return NS_NewNamedThread("Update Watcher", getter_AddRefs(mProcessWatcher), + r); +} + +NS_IMETHODIMP +nsUpdateProcessor::FixUpdateDirectoryPerms(bool aUseServiceOnFailure) { +#ifndef XP_WIN + return NS_ERROR_NOT_IMPLEMENTED; +#else + enum class State { + Initializing, + WaitingToStart, + Starting, + WaitingForFinish, + }; + + class FixUpdateDirectoryPermsRunnable final : public mozilla::Runnable { + public: + FixUpdateDirectoryPermsRunnable(const char* aName, + bool aUseServiceOnFailure, + const nsAutoString& aInstallPath) + : Runnable(aName), + mState(State::Initializing) +# ifdef MOZ_MAINTENANCE_SERVICE + , + mUseServiceOnFailure(aUseServiceOnFailure) +# endif + { + size_t installPathSize = aInstallPath.Length() + 1; + mInstallPath = mozilla::MakeUnique<wchar_t[]>(installPathSize); + if (mInstallPath) { + HRESULT hrv = StringCchCopyW(mInstallPath.get(), installPathSize, + PromiseFlatString(aInstallPath).get()); + if (FAILED(hrv)) { + mInstallPath.reset(); + } + } + } + + NS_IMETHOD Run() override { +# ifdef MOZ_MAINTENANCE_SERVICE + // These constants control how often and how many times we poll the + // maintenance service to see if it has stopped. If there is no delay in + // the event queue, this works out to 8 minutes of polling. + const unsigned int kMaxQueries = 2400; + const unsigned int kQueryIntervalMS = 200; + // These constants control how often and how many times we attempt to + // start the service. If there is no delay in the event queue, this works + // out to 5 seconds of polling. + const unsigned int kMaxStartAttempts = 50; + const unsigned int kStartAttemptIntervalMS = 100; +# endif + + if (mState == State::Initializing) { + if (!mInstallPath) { + LOG( + ("Warning: No install path available in " + "FixUpdateDirectoryPermsRunnable\n")); + } + // In the event that the directory is owned by this user, we may be able + // to fix things without the maintenance service + mozilla::UniquePtr<wchar_t[]> updateDir; + HRESULT permResult = GetCommonUpdateDirectory( + mInstallPath.get(), SetPermissionsOf::AllFilesAndDirs, updateDir); + if (SUCCEEDED(permResult)) { + LOG(("Successfully fixed permissions from within Firefox\n")); + return NS_OK; + } +# ifdef MOZ_MAINTENANCE_SERVICE + else if (!mUseServiceOnFailure) { + LOG( + ("Error: Unable to fix permissions within Firefox and " + "maintenance service is disabled\n")); + return ReportUpdateError(); + } +# else + LOG(("Error: Unable to fix permissions\n")); + return ReportUpdateError(); +# endif + +# ifdef MOZ_MAINTENANCE_SERVICE + SC_HANDLE serviceManager = + OpenSCManager(nullptr, nullptr, + SC_MANAGER_CONNECT | SC_MANAGER_ENUMERATE_SERVICE); + mServiceManager.own(serviceManager); + if (!serviceManager) { + LOG( + ("Error: Unable to get the service manager. Cannot fix " + "permissions.\n")); + return NS_ERROR_FAILURE; + } + SC_HANDLE service = OpenServiceW(serviceManager, MAINTENANCE_SVC_NAME, + SERVICE_QUERY_STATUS | SERVICE_START); + mService.own(service); + if (!service) { + LOG( + ("Error: Unable to get the maintenance service. Unable fix " + "permissions without it.\n")); + return NS_ERROR_FAILURE; + } + + mStartServiceArgCount = mInstallPath ? 3 : 2; + mStartServiceArgs = + mozilla::MakeUnique<LPCWSTR[]>(mStartServiceArgCount); + if (!mStartServiceArgs) { + LOG( + ("Error: Unable to allocate memory for argument pointers. Cannot " + "fix permissions.\n")); + return NS_ERROR_FAILURE; + } + mStartServiceArgs[0] = L"MozillaMaintenance"; + mStartServiceArgs[1] = L"fix-update-directory-perms"; + if (mInstallPath) { + mStartServiceArgs[2] = mInstallPath.get(); + } + + mState = State::WaitingToStart; + mCurrentTry = 1; +# endif + } +# ifdef MOZ_MAINTENANCE_SERVICE + if (mState == State::WaitingToStart || + mState == State::WaitingForFinish) { + SERVICE_STATUS_PROCESS ssp; + DWORD bytesNeeded; + BOOL success = + QueryServiceStatusEx(mService, SC_STATUS_PROCESS_INFO, (LPBYTE)&ssp, + sizeof(SERVICE_STATUS_PROCESS), &bytesNeeded); + if (!success) { + DWORD lastError = GetLastError(); + // These 3 errors can occur when the service is not yet stopped but it + // is stopping. If we got another error, waiting will probably not + // help. + if (lastError != ERROR_INVALID_SERVICE_CONTROL && + lastError != ERROR_SERVICE_CANNOT_ACCEPT_CTRL && + lastError != ERROR_SERVICE_NOT_ACTIVE) { + LOG( + ("Error: Unable to query service when fixing permissions. Got " + "an error that cannot be fixed by waiting: 0x%lx\n", + lastError)); + return NS_ERROR_FAILURE; + } + if (mCurrentTry >= kMaxQueries) { + LOG( + ("Error: Unable to query service when fixing permissions: " + "Timed out after %u attempts.\n", + mCurrentTry)); + return NS_ERROR_FAILURE; + } + return RetryInMS(kQueryIntervalMS); + } else { // We successfully queried the service + if (ssp.dwCurrentState != SERVICE_STOPPED) { + return RetryInMS(kQueryIntervalMS); + } + if (mState == State::WaitingForFinish) { + if (ssp.dwWin32ExitCode != NO_ERROR) { + LOG( + ("Error: Maintenance Service was unable to fix update " + "directory permissions\n")); + return ReportUpdateError(); + } + LOG( + ("Maintenance service successully fixed update directory " + "permissions\n")); + return NS_OK; + } + mState = State::Starting; + mCurrentTry = 1; + } + } + if (mState == State::Starting) { + BOOL success = StartServiceW(mService, mStartServiceArgCount, + mStartServiceArgs.get()); + if (success) { + mState = State::WaitingForFinish; + mCurrentTry = 1; + return RetryInMS(kQueryIntervalMS); + } else if (mCurrentTry >= kMaxStartAttempts) { + LOG( + ("Error: Unable to fix permissions: Timed out after %u attempts " + "to start the maintenance service\n", + mCurrentTry)); + return NS_ERROR_FAILURE; + } + return RetryInMS(kStartAttemptIntervalMS); + } +# endif + // We should not have fallen through all three state checks above + LOG( + ("Error: Reached logically unreachable code when correcting update " + "directory permissions\n")); + return NS_ERROR_FAILURE; + } + + private: + State mState; + mozilla::UniquePtr<wchar_t[]> mInstallPath; +# ifdef MOZ_MAINTENANCE_SERVICE + bool mUseServiceOnFailure; + unsigned int mCurrentTry; + nsAutoServiceHandle mServiceManager; + nsAutoServiceHandle mService; + DWORD mStartServiceArgCount; + mozilla::UniquePtr<LPCWSTR[]> mStartServiceArgs; + + nsresult RetryInMS(unsigned int aDelayMS) { + ++mCurrentTry; + nsCOMPtr<nsIRunnable> runnable(this); + return NS_DelayedDispatchToCurrentThread(runnable.forget(), aDelayMS); + } +# endif + nsresult ReportUpdateError() { + return NS_DispatchToMainThread(NS_NewRunnableFunction( + "nsUpdateProcessor::FixUpdateDirectoryPerms::" + "FixUpdateDirectoryPermsRunnable::ReportUpdateError", + []() -> void { + nsCOMPtr<nsIObserverService> observerService = + services::GetObserverService(); + if (NS_WARN_IF(!observerService)) { + return; + } + observerService->NotifyObservers(nullptr, "update-error", + u"bad-perms"); + })); + } + }; + + nsCOMPtr<nsIProperties> dirSvc( + do_GetService("@mozilla.org/file/directory_service;1")); + NS_ENSURE_TRUE(dirSvc, NS_ERROR_FAILURE); + + nsCOMPtr<nsIFile> appPath; + nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(appPath)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> installDir; + rv = appPath->GetParent(getter_AddRefs(installDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString installPath; + rv = installDir->GetPath(installPath); + NS_ENSURE_SUCCESS(rv, rv); + + // Stream transport service has a thread pool we can use so that this happens + // off the main thread. + nsCOMPtr<nsIEventTarget> eventTarget = + do_GetService(NS_STREAMTRANSPORTSERVICE_CONTRACTID); + NS_ENSURE_TRUE(eventTarget, NS_ERROR_FAILURE); + + nsCOMPtr<nsIRunnable> runnable = new FixUpdateDirectoryPermsRunnable( + "FixUpdateDirectoryPermsRunnable", aUseServiceOnFailure, installPath); + rv = eventTarget->Dispatch(runnable.forget()); + NS_ENSURE_SUCCESS(rv, rv); +#endif + return NS_OK; +} + +void nsUpdateProcessor::StartStagedUpdate() { + MOZ_ASSERT(!NS_IsMainThread(), "main thread"); + + nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot, + mInfo.mArgc, mInfo.mArgv, + mInfo.mAppVersion.get(), false, &mUpdaterPID); + NS_ENSURE_SUCCESS_VOID(rv); + + if (mUpdaterPID) { + // Track the state of the updater process while it is staging an update. + rv = NS_DispatchToCurrentThread( + NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this, + &nsUpdateProcessor::WaitForProcess)); + NS_ENSURE_SUCCESS_VOID(rv); + } else { + // Failed to launch the updater process for some reason. + // We need to shutdown the current thread as there isn't anything more for + // us to do... + rv = NS_DispatchToMainThread( + NewRunnableMethod("nsUpdateProcessor::ShutdownWatcherThread", this, + &nsUpdateProcessor::ShutdownWatcherThread)); + NS_ENSURE_SUCCESS_VOID(rv); + } +} + +void nsUpdateProcessor::ShutdownWatcherThread() { + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + mProcessWatcher->Shutdown(); + mProcessWatcher = nullptr; +} + +void nsUpdateProcessor::WaitForProcess() { + MOZ_ASSERT(!NS_IsMainThread(), "main thread"); + if (ProcessHasTerminated(mUpdaterPID)) { + NS_DispatchToMainThread(NewRunnableMethod( + "nsUpdateProcessor::UpdateDone", this, &nsUpdateProcessor::UpdateDone)); + } else { + NS_DispatchToCurrentThread( + NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this, + &nsUpdateProcessor::WaitForProcess)); + } +} + +void nsUpdateProcessor::UpdateDone() { + MOZ_ASSERT(NS_IsMainThread(), "not main thread"); + + nsCOMPtr<nsIUpdateManager> um = + do_GetService("@mozilla.org/updates/update-manager;1"); + if (um) { + um->RefreshUpdateStatus(); + } + + ShutdownWatcherThread(); +} diff --git a/toolkit/xre/nsUpdateDriver.h b/toolkit/xre/nsUpdateDriver.h new file mode 100644 index 0000000000..cc85d95bfd --- /dev/null +++ b/toolkit/xre/nsUpdateDriver.h @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 nsUpdateDriver_h__ +#define nsUpdateDriver_h__ + +#include "nscore.h" +#include "nsIUpdateService.h" +#include "nsIThread.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "mozilla/Attributes.h" + +class nsIFile; + +#if defined(XP_WIN) +# include <windows.h> +typedef HANDLE ProcessType; +#elif defined(XP_UNIX) +typedef pid_t ProcessType; +#else +# include "prproces.h" +typedef PRProcess* ProcessType; +#endif + +/** + * This function processes any available updates. As part of that process, it + * may exit the current process and relaunch it at a later time. + * + * Two directories are passed to this function: greDir (where the actual + * binary resides) and appDir (which contains application.ini for XULRunner + * apps). If this is not a XULRunner app then appDir is identical to greDir. + * + * The argc and argv passed to this function should be what is needed to + * relaunch the current process. + * + * The appVersion param passed to this function is the current application's + * version and is used to determine if an update's version is older than the + * current application version. + * + * If you want the update to be processed without restarting, set the restart + * parameter to false. + * + * This function does not modify appDir. + */ +nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir, + int argc, char** argv, const char* appVersion, + bool restart = true, ProcessType* pid = nullptr); + +// The implementation of the update processor handles the task of loading the +// updater application for staging an update. +// XXX ehsan this is living in this file in order to make use of the existing +// stuff here, we might want to move it elsewhere in the future. +class nsUpdateProcessor final : public nsIUpdateProcessor { + public: + nsUpdateProcessor(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUPDATEPROCESSOR + + private: + ~nsUpdateProcessor(); + + struct StagedUpdateInfo { + StagedUpdateInfo() : mArgc(0), mArgv(nullptr) {} + ~StagedUpdateInfo() { + for (int i = 0; i < mArgc; ++i) { + delete[] mArgv[i]; + } + delete[] mArgv; + } + + nsCOMPtr<nsIFile> mGREDir; + nsCOMPtr<nsIFile> mAppDir; + nsCOMPtr<nsIFile> mUpdateRoot; + int mArgc; + char** mArgv; + nsCString mAppVersion; + }; + + private: + void StartStagedUpdate(); + void WaitForProcess(); + void UpdateDone(); + void ShutdownWatcherThread(); + + private: + ProcessType mUpdaterPID; + nsCOMPtr<nsIThread> mProcessWatcher; + StagedUpdateInfo mInfo; +}; +#endif // nsUpdateDriver_h__ diff --git a/toolkit/xre/nsUpdateSyncManager.cpp b/toolkit/xre/nsUpdateSyncManager.cpp new file mode 100644 index 0000000000..37ba83f4d7 --- /dev/null +++ b/toolkit/xre/nsUpdateSyncManager.cpp @@ -0,0 +1,130 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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/. */ + +#include "nsUpdateSyncManager.h" + +#include "mozilla/Unused.h" +#include "mozilla/Services.h" +#include "nsComponentManagerUtils.h" +#include "nsCRT.h" +#include "nsIFile.h" +#include "nsIObserverService.h" +#include "nsIProperties.h" +#include "nsString.h" +#include "nsXULAppAPI.h" + +// The lock code generates a path that already includes the vendor name, +// so this only needs to name the specific lock. +#define UPDATE_LOCK_NAME_TOKEN "UpdateLock" + +nsUpdateSyncManager* gUpdateSyncManager = nullptr; + +NS_IMPL_ISUPPORTS(nsUpdateSyncManager, nsIUpdateSyncManager, nsIObserver) + +nsUpdateSyncManager::nsUpdateSyncManager() { OpenLock(); } + +nsUpdateSyncManager::~nsUpdateSyncManager() { + ReleaseLock(); + gUpdateSyncManager = nullptr; +} + +already_AddRefed<nsUpdateSyncManager> nsUpdateSyncManager::GetSingleton() { + if (!gUpdateSyncManager) { + gUpdateSyncManager = new nsUpdateSyncManager(); + } + return do_AddRef(gUpdateSyncManager); +} + +NS_IMETHODIMP nsUpdateSyncManager::Observe(nsISupports* aSubject, + const char* aTopic, + const char16_t* aData) { + mozilla::Unused << aSubject; + mozilla::Unused << aData; + + // We want to hold the lock for as much of the lifetime of the app as we can, + // so we observe xpcom-startup so we get constructed as early as possible, + // which triggers constructing the singleton. + if (!nsCRT::strcmp(aTopic, NS_XPCOM_STARTUP_OBSERVER_ID)) { + nsCOMPtr<nsIObserverService> observerService = + mozilla::services::GetObserverService(); + if (observerService) { + return observerService->AddObserver(this, NS_XPCOM_SHUTDOWN_OBSERVER_ID, + false); + } + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + if (!nsCRT::strcmp(aTopic, NS_XPCOM_SHUTDOWN_OBSERVER_ID)) { + ReleaseLock(); + } + + return NS_OK; +} + +nsresult nsUpdateSyncManager::OpenLock() { + if (mLock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) { + // Lock is already open. + return NS_OK; + } + + // Only open the lock from the browser process. + // Our component registration should already have made sure of this. + if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) { + return NS_OK; + } + + nsCOMPtr<nsIProperties> dirSvc = + do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID); + NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE); + + nsCOMPtr<nsIFile> appFile; + nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile), + getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsIFile> appDirFile; + rv = appFile->GetParent(getter_AddRefs(appDirFile)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString appDirPath; + rv = appDirFile->GetPath(appDirPath); + NS_ENSURE_SUCCESS(rv, rv); + + mLock = mozilla::OpenMultiInstanceLock(UPDATE_LOCK_NAME_TOKEN, + PromiseFlatString(appDirPath).get()); + NS_ENSURE_TRUE(mLock, NS_ERROR_FAILURE); + + return NS_OK; +} + +void nsUpdateSyncManager::ReleaseLock() { + if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) { + // Lock is already released. + return; + } + + mozilla::ReleaseMultiInstanceLock(mLock); + mLock = MULTI_INSTANCE_LOCK_HANDLE_ERROR; +} + +NS_IMETHODIMP nsUpdateSyncManager::IsOtherInstanceRunning(bool* aResult) { + if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) { + return NS_ERROR_SERVICE_NOT_AVAILABLE; + } + + if (mLock == MULTI_INSTANCE_LOCK_HANDLE_ERROR) { + return NS_ERROR_NOT_INITIALIZED; + } + + bool rv = mozilla::IsOtherInstanceRunning(mLock, aResult); + NS_ENSURE_TRUE(rv, NS_ERROR_FAILURE); + + return NS_OK; +} + +NS_IMETHODIMP nsUpdateSyncManager::ResetLock() { + ReleaseLock(); + return OpenLock(); +} diff --git a/toolkit/xre/nsUpdateSyncManager.h b/toolkit/xre/nsUpdateSyncManager.h new file mode 100644 index 0000000000..24db46d0d4 --- /dev/null +++ b/toolkit/xre/nsUpdateSyncManager.h @@ -0,0 +1,49 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim:set ts=2 sw=2 sts=2 et cindent: */ +/* 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 nsUpdateSyncManager_h__ +#define nsUpdateSyncManager_h__ + +#include "mozilla/AlreadyAddRefed.h" +#include "nsIObserver.h" +#include "nsIUpdateService.h" +#include "MultiInstanceLock.h" + +// The update sync manager is responsible for making sure that only one +// instance of the application is running at the time we want to start updating +// it. It does this by taking a multi-instance lock very early during the +// application's startup process. Then, when app update tasks are ready to run, +// the update service asks us whether anything else has also taken that lock, +// which, if true, would mean another instance of the application is currently +// running and performing update tasks should be avoided (the update service +// also runs a timeout and eventually goes ahead with the update in order to +// prevent an external program from effectively disabling updates). +class nsUpdateSyncManager final : public nsIUpdateSyncManager, + public nsIObserver { + public: + nsUpdateSyncManager(); + + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIUPDATESYNCMANAGER + NS_DECL_NSIOBSERVER + + static already_AddRefed<nsUpdateSyncManager> GetSingleton(); + + private: + ~nsUpdateSyncManager(); + + nsUpdateSyncManager(nsUpdateSyncManager&) = delete; + nsUpdateSyncManager(nsUpdateSyncManager&&) = delete; + nsUpdateSyncManager& operator=(nsUpdateSyncManager&) = delete; + nsUpdateSyncManager& operator=(nsUpdateSyncManager&&) = delete; + + nsresult OpenLock(); + void ReleaseLock(); + + mozilla::MultiInstLockHandle mLock = MULTI_INSTANCE_LOCK_HANDLE_ERROR; +}; + +#endif // nsUpdateSyncManager_h__ diff --git a/toolkit/xre/nsWindowsRestart.cpp b/toolkit/xre/nsWindowsRestart.cpp new file mode 100644 index 0000000000..a1e9401231 --- /dev/null +++ b/toolkit/xre/nsWindowsRestart.cpp @@ -0,0 +1,151 @@ +/* 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/. */ + +// This file is not build directly. Instead, it is included in multiple +// shared objects. + +#ifdef nsWindowsRestart_cpp +# error \ + "nsWindowsRestart.cpp is not a header file, and must only be included once." +#else +# define nsWindowsRestart_cpp +#endif + +#include "mozilla/CmdLineAndEnvUtils.h" +#include "nsUTF8Utils.h" + +#include <shellapi.h> + +// Needed for CreateEnvironmentBlock +#include <userenv.h> +#ifndef __MINGW32__ +# pragma comment(lib, "userenv.lib") +#endif + +/** + * Convert UTF8 to UTF16 without using the normal XPCOM goop, which we + * can't link to updater.exe. + */ +static char16_t* AllocConvertUTF8toUTF16(const char* arg) { + // UTF16 can't be longer in units than UTF8 + size_t len = strlen(arg); + char16_t* s = new char16_t[(len + 1) * sizeof(char16_t)]; + if (!s) return nullptr; + + size_t dstLen = ::MultiByteToWideChar(CP_UTF8, 0, arg, len, + reinterpret_cast<wchar_t*>(s), len); + s[dstLen] = 0; + + return s; +} + +static void FreeAllocStrings(int argc, wchar_t** argv) { + while (argc) { + --argc; + delete[] argv[argc]; + } + + delete[] argv; +} + +static wchar_t** AllocConvertUTF8toUTF16Strings(int argc, char** argv) { + wchar_t** argvConverted = new wchar_t*[argc]; + if (!argvConverted) return nullptr; + + for (int i = 0; i < argc; ++i) { + argvConverted[i] = + reinterpret_cast<wchar_t*>(AllocConvertUTF8toUTF16(argv[i])); + if (!argvConverted[i]) { + FreeAllocStrings(i, argvConverted); + return nullptr; + } + } + return argvConverted; +} + +/** + * Launch a child process with the specified arguments. + * @note argv[0] is ignored + * @note The form of this function that takes char **argv expects UTF-8 + */ + +BOOL WinLaunchChild(const wchar_t* exePath, int argc, wchar_t** argv, + HANDLE userToken = nullptr, HANDLE* hProcess = nullptr); + +BOOL WinLaunchChild(const wchar_t* exePath, int argc, char** argv, + HANDLE userToken, HANDLE* hProcess) { + wchar_t** argvConverted = AllocConvertUTF8toUTF16Strings(argc, argv); + if (!argvConverted) return FALSE; + + BOOL ok = WinLaunchChild(exePath, argc, argvConverted, userToken, hProcess); + FreeAllocStrings(argc, argvConverted); + return ok; +} + +BOOL WinLaunchChild(const wchar_t* exePath, int argc, wchar_t** argv, + HANDLE userToken, HANDLE* hProcess) { + BOOL ok; + + mozilla::UniquePtr<wchar_t[]> cl(mozilla::MakeCommandLine(argc, argv)); + if (!cl) { + return FALSE; + } + + STARTUPINFOW si = {0}; + si.cb = sizeof(STARTUPINFOW); + si.lpDesktop = const_cast<LPWSTR>(L"winsta0\\Default"); + PROCESS_INFORMATION pi = {0}; + + if (userToken == nullptr) { + ok = CreateProcessW(exePath, cl.get(), + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + nullptr, // inherit my environment + nullptr, // use my current directory + &si, &pi); + } else { + // Create an environment block for the process we're about to start using + // the user's token. + LPVOID environmentBlock = nullptr; + if (!CreateEnvironmentBlock(&environmentBlock, userToken, TRUE)) { + environmentBlock = nullptr; + } + + ok = CreateProcessAsUserW(userToken, exePath, cl.get(), + nullptr, // no special security attributes + nullptr, // no special thread attributes + FALSE, // don't inherit filehandles + 0, // creation flags + environmentBlock, + nullptr, // use my current directory + &si, &pi); + + if (environmentBlock) { + DestroyEnvironmentBlock(environmentBlock); + } + } + + if (ok) { + if (hProcess) { + *hProcess = pi.hProcess; // the caller now owns the HANDLE + } else { + CloseHandle(pi.hProcess); + } + CloseHandle(pi.hThread); + } else { + LPVOID lpMsgBuf = nullptr; + FormatMessage(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | + FORMAT_MESSAGE_IGNORE_INSERTS, + nullptr, GetLastError(), + MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (LPTSTR)&lpMsgBuf, + 0, nullptr); + wprintf(L"Error restarting: %s\n", + lpMsgBuf ? static_cast<const wchar_t*>(lpMsgBuf) : L"(null)"); + if (lpMsgBuf) LocalFree(lpMsgBuf); + } + + return ok; +} diff --git a/toolkit/xre/nsWindowsWMain.cpp b/toolkit/xre/nsWindowsWMain.cpp new file mode 100644 index 0000000000..109c53cac9 --- /dev/null +++ b/toolkit/xre/nsWindowsWMain.cpp @@ -0,0 +1,140 @@ +/* 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 nsWindowsWMain_cpp +#define nsWindowsWMain_cpp + +// This file is a .cpp file meant to be included in nsBrowserApp.cpp and other +// similar bootstrap code. It converts wide-character windows wmain into UTF-8 +// narrow-character strings. + +#ifndef XP_WIN +# error This file only makes sense on Windows. +#endif + +#include "mozilla/Char16.h" +#include "nsUTF8Utils.h" + +#include <windows.h> + +#ifdef __MINGW32__ + +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 411826 */ + +# include <shellapi.h> + +int wmain(int argc, WCHAR** argv); + +int main(int argc, char** argv) { + LPWSTR commandLine = GetCommandLineW(); + int argcw = 0; + LPWSTR* argvw = CommandLineToArgvW(commandLine, &argcw); + if (!argvw) return 127; + + int result = wmain(argcw, argvw); + LocalFree(argvw); + return result; +} +#endif /* __MINGW32__ */ + +#define main NS_internal_main + +#ifndef XRE_WANT_ENVIRON +int main(int argc, char** argv); +#else +int main(int argc, char** argv, char** envp); +#endif + +static void SanitizeEnvironmentVariables() { + DWORD bufferSize = GetEnvironmentVariableW(L"PATH", nullptr, 0); + if (bufferSize) { + wchar_t* originalPath = new wchar_t[bufferSize]; + if (bufferSize - 1 == + GetEnvironmentVariableW(L"PATH", originalPath, bufferSize)) { + bufferSize = ExpandEnvironmentStringsW(originalPath, nullptr, 0); + if (bufferSize) { + wchar_t* newPath = new wchar_t[bufferSize]; + if (ExpandEnvironmentStringsW(originalPath, newPath, bufferSize)) { + SetEnvironmentVariableW(L"PATH", newPath); + } + delete[] newPath; + } + } + delete[] originalPath; + } +} + +static char* AllocConvertUTF16toUTF8(char16ptr_t arg) { + // be generous... UTF16 units can expand up to 3 UTF8 units + size_t len = wcslen(arg); + // ConvertUTF16toUTF8 requires +1. Let's do that here, too, lacking + // knowledge of Windows internals. + size_t dstLen = len * 3 + 1; + char* s = new char[dstLen + 1]; // Another +1 for zero terminator + if (!s) return nullptr; + + int written = + ::WideCharToMultiByte(CP_UTF8, 0, arg, len, s, dstLen, nullptr, nullptr); + s[written] = 0; + return s; +} + +static void FreeAllocStrings(int argc, char** argv) { + while (argc) { + --argc; + delete[] argv[argc]; + } + + delete[] argv; +} + +int wmain(int argc, WCHAR** argv) { + SanitizeEnvironmentVariables(); + SetDllDirectoryW(L""); + + // Only run this code if LauncherProcessWin.h was included beforehand, thus + // signalling that the hosting process should support launcher mode. +#if defined(mozilla_LauncherProcessWin_h) + mozilla::Maybe<int> launcherResult = + mozilla::LauncherMain(argc, argv, sAppData); + if (launcherResult) { + return launcherResult.value(); + } +#endif // defined(mozilla_LauncherProcessWin_h) + + char** argvConverted = new char*[argc + 1]; + if (!argvConverted) return 127; + + for (int i = 0; i < argc; ++i) { + argvConverted[i] = AllocConvertUTF16toUTF8(argv[i]); + if (!argvConverted[i]) { + return 127; + } + } + argvConverted[argc] = nullptr; + + // need to save argvConverted copy for later deletion. + char** deleteUs = new char*[argc + 1]; + if (!deleteUs) { + FreeAllocStrings(argc, argvConverted); + return 127; + } + for (int i = 0; i < argc; i++) deleteUs[i] = argvConverted[i]; +#ifndef XRE_WANT_ENVIRON + int result = main(argc, argvConverted); +#else + // Force creation of the multibyte _environ variable. + getenv("PATH"); + int result = main(argc, argvConverted, _environ); +#endif + + delete[] argvConverted; + FreeAllocStrings(argc, deleteUs); + + return result; +} + +#endif // nsWindowsWMain_cpp diff --git a/toolkit/xre/nsX11ErrorHandler.cpp b/toolkit/xre/nsX11ErrorHandler.cpp new file mode 100644 index 0000000000..3ba6d696fa --- /dev/null +++ b/toolkit/xre/nsX11ErrorHandler.cpp @@ -0,0 +1,146 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsX11ErrorHandler.h" + +#include "prenv.h" +#include "nsXULAppAPI.h" +#include "nsExceptionHandler.h" +#include "nsDebug.h" + +#include "mozilla/X11Util.h" +#include <X11/Xlib.h> + +#define BUFSIZE 2048 // What Xlib uses with XGetErrorDatabaseText + +extern "C" { +int X11Error(Display* display, XErrorEvent* event) { + // Get an indication of how long ago the request that caused the error was + // made. + unsigned long age = NextRequest(display) - event->serial; + + // Get a string to represent the request that caused the error. + nsAutoCString message; + if (event->request_code < 128) { + // Core protocol request + message.AppendInt(event->request_code); + } else { + // Extension request + + // man XSetErrorHandler says "the error handler should not call any + // functions (directly or indirectly) on the display that will generate + // protocol requests or that will look for input events" so we use another + // temporary Display to request extension information. This assumes on + // the DISPLAY environment variable has been set and matches what was used + // to open |display|. + Display* tmpDisplay = XOpenDisplay(nullptr); + if (tmpDisplay) { + int nExts; + char** extNames = XListExtensions(tmpDisplay, &nExts); + int first_error; + if (extNames) { + for (int i = 0; i < nExts; ++i) { + int major_opcode, first_event; + if (XQueryExtension(tmpDisplay, extNames[i], &major_opcode, + &first_event, &first_error) && + major_opcode == event->request_code) { + message.Append(extNames[i]); + message.Append('.'); + message.AppendInt(event->minor_code); + break; + } + } + + XFreeExtensionList(extNames); + } + XCloseDisplay(tmpDisplay); + } + } + + char buffer[BUFSIZE]; + if (message.IsEmpty()) { + buffer[0] = '\0'; + } else { + XGetErrorDatabaseText(display, "XRequest", message.get(), "", buffer, + sizeof(buffer)); + } + + nsAutoCString notes; + if (buffer[0]) { + notes.Append(buffer); + } else { + notes.AppendLiteral("Request "); + notes.AppendInt(event->request_code); + notes.Append('.'); + notes.AppendInt(event->minor_code); + } + + notes.AppendLiteral(": "); + + // Get a string to describe the error. + XGetErrorText(display, event->error_code, buffer, sizeof(buffer)); + notes.Append(buffer); + + // For requests where Xlib gets the reply synchronously, |age| will be 1 + // and the stack will include the function making the request. For + // asynchronous requests, the current stack will often be unrelated to the + // point of making the request, even if |age| is 1, but sometimes this may + // help us count back to the point of the request. With XSynchronize on, + // the stack will include the function making the request, even though + // |age| will be 2 for asynchronous requests because XSynchronize is + // implemented by an empty request from an XSync, which has not yet been + // processed. + if (age > 1) { + // XSynchronize returns the previous "after function". If a second + // XSynchronize call returns the same function after an enable call then + // synchronization must have already been enabled. + if (XSynchronize(display, X11True) == XSynchronize(display, X11False)) { + notes.AppendLiteral("; sync"); + } else { + notes.AppendLiteral("; "); + notes.AppendInt(uint32_t(age)); + notes.AppendLiteral(" requests ago"); + } + } + + switch (XRE_GetProcessType()) { + case GeckoProcessType_Default: + case GeckoProcessType_Plugin: + case GeckoProcessType_Content: + CrashReporter::AppendAppNotesToCrashReport(notes); + break; + default:; // crash report notes not supported. + } + +#ifdef DEBUG + // The resource id is unlikely to be useful in a crash report without + // context of other ids, but add it to the debug console output. + notes.AppendLiteral("; id=0x"); + notes.AppendInt(uint32_t(event->resourceid), 16); +# ifdef MOZ_X11 + // Actually, for requests where Xlib gets the reply synchronously, + // MOZ_X_SYNC=1 will not be necessary, but we don't have a table to tell us + // which requests get a synchronous reply. + if (!PR_GetEnv("MOZ_X_SYNC")) { + notes.AppendLiteral( + "\nRe-running with MOZ_X_SYNC=1 in the environment may give a more " + "helpful backtrace."); + } +# endif +#endif + + MOZ_CRASH_UNSAFE(notes.get()); +} +} + +void InstallX11ErrorHandler() { + XSetErrorHandler(X11Error); + + Display* display = mozilla::DefaultXDisplay(); + NS_ASSERTION(display, "No X display"); + if (PR_GetEnv("MOZ_X_SYNC")) { + XSynchronize(display, X11True); + } +} diff --git a/toolkit/xre/nsX11ErrorHandler.h b/toolkit/xre/nsX11ErrorHandler.h new file mode 100644 index 0000000000..6565c59262 --- /dev/null +++ b/toolkit/xre/nsX11ErrorHandler.h @@ -0,0 +1,18 @@ +/* -*- Mode: C++; tab-width: 40; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#ifdef MOZ_X11 +# include <X11/Xlib.h> +# include "X11UndefineNone.h" // Unset some macros defined by X.h included by Xlib.h + +/** + * InstallX11ErrorHandler is not suitable for processes running with GTK3 as + * GDK3 will replace the handler. This is still used for the plugin process, + * which runs with GTK2. + **/ +void InstallX11ErrorHandler(); + +extern "C" int X11Error(Display* display, XErrorEvent* event); +#endif diff --git a/toolkit/xre/nsXREDirProvider.cpp b/toolkit/xre/nsXREDirProvider.cpp new file mode 100644 index 0000000000..d9bcdb1f4e --- /dev/null +++ b/toolkit/xre/nsXREDirProvider.cpp @@ -0,0 +1,1769 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "nsAppRunner.h" +#include "nsXREDirProvider.h" +#ifndef ANDROID +# include "commonupdatedir.h" +#endif + +#include "jsapi.h" +#include "xpcpublic.h" + +#include "nsIAppStartup.h" +#include "nsIFile.h" +#include "nsIObserver.h" +#include "nsIObserverService.h" +#include "nsISimpleEnumerator.h" +#include "nsIToolkitProfileService.h" +#include "nsIXULRuntime.h" +#include "commonupdatedir.h" + +#include "nsAppDirectoryServiceDefs.h" +#include "nsDirectoryServiceDefs.h" +#include "nsDirectoryServiceUtils.h" +#include "nsXULAppAPI.h" +#include "nsCategoryManagerUtils.h" + +#include "nsDependentString.h" +#include "nsCOMArray.h" +#include "nsArrayEnumerator.h" +#include "nsEnumeratorUtils.h" +#include "nsReadableUtils.h" + +#include "GeckoProfiler.h" +#include "SpecialSystemDirectory.h" + +#include "mozilla/dom/ScriptSettings.h" + +#include "mozilla/AutoRestore.h" +#include "mozilla/Components.h" +#include "mozilla/Services.h" +#include "mozilla/Omnijar.h" +#include "mozilla/Preferences.h" +#include "mozilla/Telemetry.h" +#include "mozilla/XREAppData.h" +#include "nsPrintfCString.h" + +#include <stdlib.h> + +#ifdef XP_WIN +# include <windows.h> +# include <shlobj.h> +# include "WinUtils.h" +#endif +#ifdef XP_MACOSX +# include "nsILocalFileMac.h" +// for chflags() +# include <sys/stat.h> +# include <unistd.h> +#endif +#ifdef XP_UNIX +# include <ctype.h> +#endif +#ifdef XP_IOS +# include "UIKitDirProvider.h" +#endif + +#if defined(MOZ_SANDBOX) +# include "mozilla/SandboxSettings.h" +# include "nsIUUIDGenerator.h" +# include "mozilla/Unused.h" +# if defined(XP_WIN) +# include "sandboxBroker.h" +# endif +#endif + +#if defined(XP_MACOSX) +# define APP_REGISTRY_NAME "Application Registry" +#elif defined(XP_WIN) +# define APP_REGISTRY_NAME "registry.dat" +#else +# define APP_REGISTRY_NAME "appreg" +#endif + +#define PREF_OVERRIDE_DIRNAME "preferences" + +#if defined(MOZ_SANDBOX) +static already_AddRefed<nsIFile> GetProcessSandboxTempDir( + GeckoProcessType type); +static nsresult DeleteDirIfExists(nsIFile* dir); +static bool IsContentSandboxDisabled(); +static const char* GetProcessTempBaseDirKey(); +static already_AddRefed<nsIFile> CreateProcessSandboxTempDir( + GeckoProcessType procType); +#endif + +nsXREDirProvider* gDirServiceProvider = nullptr; +nsIFile* gDataDirHomeLocal = nullptr; +nsIFile* gDataDirHome = nullptr; +nsCOMPtr<nsIFile> gDataDirProfileLocal = nullptr; +nsCOMPtr<nsIFile> gDataDirProfile = nullptr; + +// These are required to allow nsXREDirProvider to be usable in xpcshell tests. +// where gAppData is null. +#if defined(XP_MACOSX) || defined(XP_WIN) || defined(XP_UNIX) +static const char* GetAppName() { + if (gAppData) { + return gAppData->name; + } + return nullptr; +} +#endif + +static const char* GetAppVendor() { + if (gAppData) { + return gAppData->vendor; + } + return nullptr; +} + +nsXREDirProvider::nsXREDirProvider() : mProfileNotified(false) { + gDirServiceProvider = this; +} + +nsXREDirProvider::~nsXREDirProvider() { + gDirServiceProvider = nullptr; + gDataDirHomeLocal = nullptr; + gDataDirHome = nullptr; +} + +already_AddRefed<nsXREDirProvider> nsXREDirProvider::GetSingleton() { + if (!gDirServiceProvider) { + new nsXREDirProvider(); // This sets gDirServiceProvider + } + return do_AddRef(gDirServiceProvider); +} + +nsresult nsXREDirProvider::Initialize( + nsIFile* aXULAppDir, nsIFile* aGREDir, + nsIDirectoryServiceProvider* aAppProvider) { + NS_ENSURE_ARG(aXULAppDir); + NS_ENSURE_ARG(aGREDir); + + mAppProvider = aAppProvider; + mXULAppDir = aXULAppDir; + mGREDir = aGREDir; +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // The GRE directory can be used in sandbox rules, so we need to make sure + // it doesn't contain any junction points or symlinks or the sandbox will + // reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks(mGREDir)) { + NS_WARNING("Failed to resolve GRE Dir."); + } + // If the mXULAppDir is different it lives below the mGREDir. To avoid + // confusion resolve that as well even though we don't need it for sandbox + // rules. Some tests rely on this for example. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks( + mXULAppDir)) { + NS_WARNING("Failed to resolve XUL App Dir."); + } +#endif + mGREDir->Clone(getter_AddRefs(mGREBinDir)); +#ifdef XP_MACOSX + mGREBinDir->SetNativeLeafName("MacOS"_ns); +#endif + + if (!mProfileDir) { + nsCOMPtr<nsIDirectoryServiceProvider> app(mAppProvider); + if (app) { + bool per = false; + app->GetFile(NS_APP_USER_PROFILE_50_DIR, &per, + getter_AddRefs(mProfileDir)); + NS_ASSERTION(per, "NS_APP_USER_PROFILE_50_DIR must be persistent!"); + NS_ASSERTION( + mProfileDir, + "NS_APP_USER_PROFILE_50_DIR not defined! This shouldn't happen!"); + } + } + + return NS_OK; +} + +nsresult nsXREDirProvider::SetProfile(nsIFile* aDir, nsIFile* aLocalDir) { + NS_ASSERTION(aDir && aLocalDir, "We don't support no-profile apps yet!"); + + nsresult rv; + + rv = EnsureDirectoryExists(aDir); + if (NS_FAILED(rv)) return rv; + + rv = EnsureDirectoryExists(aLocalDir); + if (NS_FAILED(rv)) return rv; + +#ifdef XP_MACOSX + bool same; + if (NS_SUCCEEDED(aDir->Equals(aLocalDir, &same)) && !same) { + // Ensure that the cache directory is not indexed by Spotlight + // (bug 718910). At least on OS X, the cache directory (under + // ~/Library/Caches/) is always the "local" user profile + // directory. This is confusing, since *both* user profile + // directories are "local" (they both exist under the user's + // home directory). But this usage dates back at least as far + // as the patch for bug 291033, where "local" seems to mean + // "suitable for temporary storage". Don't hide the cache + // directory if by some chance it and the "non-local" profile + // directory are the same -- there are bad side effects from + // hiding a profile directory under /Library/Application Support/ + // (see bug 801883). + nsAutoCString cacheDir; + if (NS_SUCCEEDED(aLocalDir->GetNativePath(cacheDir))) { + if (chflags(cacheDir.get(), UF_HIDDEN)) { + NS_WARNING("Failed to set Cache directory to HIDDEN."); + } + } + } +#endif + + mProfileDir = aDir; + mProfileLocalDir = aLocalDir; +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // The profile directory can be used in sandbox rules, so we need to make sure + // it doesn't contain any junction points or symlinks or the sandbox will + // reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks( + mProfileDir)) { + NS_WARNING("Failed to resolve Profile Dir."); + } +#endif + return NS_OK; +} + +NS_IMPL_QUERY_INTERFACE(nsXREDirProvider, nsIDirectoryServiceProvider, + nsIDirectoryServiceProvider2, nsIXREDirProvider, + nsIProfileStartup) + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXREDirProvider::AddRef() { return 1; } + +NS_IMETHODIMP_(MozExternalRefCountType) +nsXREDirProvider::Release() { return 0; } + +nsresult nsXREDirProvider::GetUserProfilesRootDir(nsIFile** aResult) { + nsCOMPtr<nsIFile> file; + nsresult rv = GetUserDataDirectory(getter_AddRefs(file), false); + + if (NS_SUCCEEDED(rv)) { +#if !defined(XP_UNIX) || defined(XP_MACOSX) + rv = file->AppendNative("Profiles"_ns); +#endif + // We must create the profile directory here if it does not exist. + nsresult tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + file.swap(*aResult); + return rv; +} + +nsresult nsXREDirProvider::GetUserProfilesLocalDir(nsIFile** aResult) { + nsCOMPtr<nsIFile> file; + nsresult rv = GetUserDataDirectory(getter_AddRefs(file), true); + + if (NS_SUCCEEDED(rv)) { +#if !defined(XP_UNIX) || defined(XP_MACOSX) + rv = file->AppendNative("Profiles"_ns); +#endif + // We must create the profile directory here if it does not exist. + nsresult tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + file.swap(*aResult); + return NS_OK; +} + +#if defined(XP_UNIX) || defined(XP_MACOSX) +/** + * Get the directory that is the parent of the system-wide directories + * for extensions and native manifests. + * + * On OSX this is /Library/Application Support/Mozilla + * On Linux this is /usr/{lib,lib64}/mozilla + * (for 32- and 64-bit systems respsectively) + */ +static nsresult GetSystemParentDirectory(nsIFile** aFile) { + nsresult rv; + nsCOMPtr<nsIFile> localDir; +# if defined(XP_MACOSX) + rv = GetOSXFolderType(kOnSystemDisk, kApplicationSupportFolderType, + getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + rv = localDir->AppendNative("Mozilla"_ns); + } +# else + constexpr auto dirname = +# ifdef HAVE_USR_LIB64_DIR + "/usr/lib64/mozilla"_ns +# elif defined(__OpenBSD__) || defined(__FreeBSD__) + "/usr/local/lib/mozilla"_ns +# else + "/usr/lib/mozilla"_ns +# endif + ; + rv = NS_NewNativeLocalFile(dirname, false, getter_AddRefs(localDir)); +# endif + + if (NS_SUCCEEDED(rv)) { + localDir.forget(aFile); + } + return rv; +} +#endif + +NS_IMETHODIMP +nsXREDirProvider::GetFile(const char* aProperty, bool* aPersistent, + nsIFile** aFile) { + nsresult rv; + + bool gettingProfile = false; + + if (!strcmp(aProperty, NS_APP_USER_PROFILE_LOCAL_50_DIR)) { + // If XRE_NotifyProfile hasn't been called, don't fall through to + // mAppProvider on the profile keys. + if (!mProfileNotified) return NS_ERROR_FAILURE; + + if (mProfileLocalDir) return mProfileLocalDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(aProperty, aPersistent, aFile); + + // This falls through to the case below + gettingProfile = true; + } + if (!strcmp(aProperty, NS_APP_USER_PROFILE_50_DIR) || gettingProfile) { + if (!mProfileNotified) return NS_ERROR_FAILURE; + + if (mProfileDir) return mProfileDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(aProperty, aPersistent, aFile); + + // If we don't succeed here, bail early so that we aren't reentrant + // through the "GetProfileDir" call below. + return NS_ERROR_FAILURE; + } + + if (mAppProvider) { + rv = mAppProvider->GetFile(aProperty, aPersistent, aFile); + if (NS_SUCCEEDED(rv) && *aFile) return rv; + } + + *aPersistent = true; + + if (!strcmp(aProperty, NS_GRE_DIR)) { +#if defined(MOZ_WIDGET_ANDROID) + // On Android, libraries and other internal files are inside the APK, a zip + // file, so this folder doesn't really make sense. + return NS_ERROR_FAILURE; +#else + return mGREDir->Clone(aFile); +#endif + } else if (!strcmp(aProperty, NS_GRE_BIN_DIR)) { +#if defined(MOZ_WIDGET_ANDROID) + // Same as NS_GRE_DIR + return NS_ERROR_FAILURE; +#else + return mGREBinDir->Clone(aFile); +#endif + } else if (!strcmp(aProperty, NS_OS_CURRENT_PROCESS_DIR) || + !strcmp(aProperty, NS_APP_INSTALL_CLEANUP_DIR)) { + return GetAppDir()->Clone(aFile); + } + + rv = NS_ERROR_FAILURE; + nsCOMPtr<nsIFile> file; + + if (!strcmp(aProperty, NS_APP_PREF_DEFAULTS_50_DIR)) { +#if defined(MOZ_WIDGET_ANDROID) + // Same as NS_GRE_DIR + return NS_ERROR_FAILURE; +#else + // return the GRE default prefs directory here, and the app default prefs + // directory (if applicable) in NS_APP_PREFS_DEFAULTS_DIR_LIST. + rv = mGREDir->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) { + rv = file->AppendNative("defaults"_ns); + if (NS_SUCCEEDED(rv)) rv = file->AppendNative("pref"_ns); + } +#endif + } else if (!strcmp(aProperty, NS_APP_APPLICATION_REGISTRY_DIR) || + !strcmp(aProperty, XRE_USER_APP_DATA_DIR)) { + rv = GetUserAppDataDirectory(getter_AddRefs(file)); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_NATIVE_MANIFESTS)) { + nsCOMPtr<nsIFile> localDir; + + rv = ::GetSystemParentDirectory(getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + localDir.swap(file); + } + } else if (!strcmp(aProperty, XRE_USER_NATIVE_MANIFESTS)) { + nsCOMPtr<nsIFile> localDir; + rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false); + if (NS_SUCCEEDED(rv)) { +# if defined(XP_MACOSX) + rv = localDir->AppendNative("Mozilla"_ns); +# else + rv = localDir->AppendNative(".mozilla"_ns); +# endif + } + if (NS_SUCCEEDED(rv)) { + localDir.swap(file); + } + } +#endif + else if (!strcmp(aProperty, XRE_UPDATE_ROOT_DIR)) { + rv = GetUpdateRootDir(getter_AddRefs(file)); + } else if (!strcmp(aProperty, XRE_OLD_UPDATE_ROOT_DIR)) { + rv = GetUpdateRootDir(getter_AddRefs(file), true); + } else if (!strcmp(aProperty, NS_APP_APPLICATION_REGISTRY_FILE)) { + rv = GetUserAppDataDirectory(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) + rv = file->AppendNative(nsLiteralCString(APP_REGISTRY_NAME)); + } else if (!strcmp(aProperty, NS_APP_USER_PROFILES_ROOT_DIR)) { + rv = GetUserProfilesRootDir(getter_AddRefs(file)); + } else if (!strcmp(aProperty, NS_APP_USER_PROFILES_LOCAL_ROOT_DIR)) { + rv = GetUserProfilesLocalDir(getter_AddRefs(file)); + } else if (!strcmp(aProperty, XRE_EXECUTABLE_FILE)) { + nsCOMPtr<nsIFile> lf; + rv = XRE_GetBinaryPath(getter_AddRefs(lf)); + if (NS_SUCCEEDED(rv)) file = lf; + } + + else if (!strcmp(aProperty, NS_APP_PROFILE_DIR_STARTUP) && mProfileDir) { + return mProfileDir->Clone(aFile); + } else if (!strcmp(aProperty, NS_APP_PROFILE_LOCAL_DIR_STARTUP)) { + if (mProfileLocalDir) return mProfileLocalDir->Clone(aFile); + + if (mProfileDir) return mProfileDir->Clone(aFile); + + if (mAppProvider) + return mAppProvider->GetFile(NS_APP_PROFILE_DIR_STARTUP, aPersistent, + aFile); + } +#if defined(XP_UNIX) || defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_LOCAL_EXTENSION_PARENT_DIR)) { +# ifdef ENABLE_SYSTEM_EXTENSION_DIRS + return GetSystemExtensionsDirectory(aFile); +# else + return NS_ERROR_FAILURE; +# endif + } +#endif +#if defined(XP_UNIX) && !defined(XP_MACOSX) + else if (!strcmp(aProperty, XRE_SYS_SHARE_EXTENSION_PARENT_DIR)) { +# ifdef ENABLE_SYSTEM_EXTENSION_DIRS +# if defined(__OpenBSD__) || defined(__FreeBSD__) + static const char* const sysLExtDir = "/usr/local/share/mozilla/extensions"; +# else + static const char* const sysLExtDir = "/usr/share/mozilla/extensions"; +# endif + return NS_NewNativeLocalFile(nsDependentCString(sysLExtDir), false, aFile); +# else + return NS_ERROR_FAILURE; +# endif + } +#endif + else if (!strcmp(aProperty, XRE_USER_SYS_EXTENSION_DIR)) { +#ifdef ENABLE_SYSTEM_EXTENSION_DIRS + return GetSysUserExtensionsDirectory(aFile); +#else + return NS_ERROR_FAILURE; +#endif + } else if (!strcmp(aProperty, XRE_USER_SYS_EXTENSION_DEV_DIR)) { + return GetSysUserExtensionsDevDirectory(aFile); + } else if (!strcmp(aProperty, XRE_USER_RUNTIME_DIR)) { +#if defined(XP_UNIX) + nsPrintfCString path("/run/user/%d/%s/", getuid(), GetAppName()); + ToLowerCase(path); + return NS_NewNativeLocalFile(path, false, aFile); +#else + return NS_ERROR_FAILURE; +#endif + } else if (!strcmp(aProperty, XRE_APP_DISTRIBUTION_DIR)) { + bool persistent = false; + rv = GetFile(NS_GRE_DIR, &persistent, getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) rv = file->AppendNative("distribution"_ns); + } else if (!strcmp(aProperty, XRE_APP_FEATURES_DIR)) { + rv = GetAppDir()->Clone(getter_AddRefs(file)); + if (NS_SUCCEEDED(rv)) rv = file->AppendNative("features"_ns); + } else if (!strcmp(aProperty, XRE_ADDON_APP_DIR)) { + nsCOMPtr<nsIDirectoryServiceProvider> dirsvc( + do_GetService("@mozilla.org/file/directory_service;1", &rv)); + if (NS_FAILED(rv)) return rv; + bool unused; + rv = dirsvc->GetFile("XCurProcD", &unused, getter_AddRefs(file)); + } +#if defined(MOZ_SANDBOX) + else if (!strcmp(aProperty, NS_APP_CONTENT_PROCESS_TEMP_DIR)) { + if (!mContentTempDir && NS_FAILED((rv = LoadContentProcessTempDir()))) { + return rv; + } + rv = mContentTempDir->Clone(getter_AddRefs(file)); + } +#endif // defined(MOZ_SANDBOX) +#if defined(MOZ_SANDBOX) + else if (0 == strcmp(aProperty, NS_APP_PLUGIN_PROCESS_TEMP_DIR)) { + if (!mPluginTempDir && NS_FAILED((rv = LoadPluginProcessTempDir()))) { + return rv; + } + rv = mPluginTempDir->Clone(getter_AddRefs(file)); + } +#endif // defined(MOZ_SANDBOX) + else if (NS_SUCCEEDED(GetProfileStartupDir(getter_AddRefs(file)))) { + // We need to allow component, xpt, and chrome registration to + // occur prior to the profile-after-change notification. + if (!strcmp(aProperty, NS_APP_USER_CHROME_DIR)) { + rv = file->AppendNative("chrome"_ns); + } + } + + if (NS_SUCCEEDED(rv) && file) { + file.forget(aFile); + return NS_OK; + } + + bool ensureFilePermissions = false; + + if (NS_SUCCEEDED(GetProfileDir(getter_AddRefs(file)))) { + if (!strcmp(aProperty, NS_APP_PREFS_50_DIR)) { + rv = NS_OK; + } else if (!strcmp(aProperty, NS_APP_PREFS_50_FILE)) { + rv = file->AppendNative("prefs.js"_ns); + } else if (!strcmp(aProperty, NS_APP_PREFS_OVERRIDE_DIR)) { + rv = mProfileDir->Clone(getter_AddRefs(file)); + nsresult tmp = + file->AppendNative(nsLiteralCString(PREF_OVERRIDE_DIRNAME)); + if (NS_FAILED(tmp)) { + rv = tmp; + } + tmp = EnsureDirectoryExists(file); + if (NS_FAILED(tmp)) { + rv = tmp; + } + } + } + if (NS_FAILED(rv) || !file) return NS_ERROR_FAILURE; + + if (ensureFilePermissions) { + bool fileToEnsureExists; + bool isWritable; + if (NS_SUCCEEDED(file->Exists(&fileToEnsureExists)) && fileToEnsureExists && + NS_SUCCEEDED(file->IsWritable(&isWritable)) && !isWritable) { + uint32_t permissions; + if (NS_SUCCEEDED(file->GetPermissions(&permissions))) { + rv = file->SetPermissions(permissions | 0600); + NS_ASSERTION(NS_SUCCEEDED(rv), "failed to ensure file permissions"); + } + } + } + + file.forget(aFile); + return NS_OK; +} + +static void LoadDirIntoArray(nsIFile* dir, const char* const* aAppendList, + nsCOMArray<nsIFile>& aDirectories) { + if (!dir) return; + + nsCOMPtr<nsIFile> subdir; + dir->Clone(getter_AddRefs(subdir)); + if (!subdir) return; + + for (const char* const* a = aAppendList; *a; ++a) { + subdir->AppendNative(nsDependentCString(*a)); + } + + bool exists; + if (NS_SUCCEEDED(subdir->Exists(&exists)) && exists) { + aDirectories.AppendObject(subdir); + } +} + +NS_IMETHODIMP +nsXREDirProvider::GetFiles(const char* aProperty, + nsISimpleEnumerator** aResult) { + nsresult rv; + + nsCOMPtr<nsISimpleEnumerator> appEnum; + nsCOMPtr<nsIDirectoryServiceProvider2> appP2(do_QueryInterface(mAppProvider)); + if (appP2) { + rv = appP2->GetFiles(aProperty, getter_AddRefs(appEnum)); + if (NS_FAILED(rv)) { + appEnum = nullptr; + } else if (rv != NS_SUCCESS_AGGREGATE_RESULT) { + appEnum.forget(aResult); + return NS_OK; + } + } + + nsCOMPtr<nsISimpleEnumerator> xreEnum; + rv = GetFilesInternal(aProperty, getter_AddRefs(xreEnum)); + if (NS_FAILED(rv)) { + if (appEnum) { + appEnum.forget(aResult); + return NS_SUCCESS_AGGREGATE_RESULT; + } + + return rv; + } + + rv = NS_NewUnionEnumerator(aResult, appEnum, xreEnum); + if (NS_FAILED(rv)) return rv; + + return NS_SUCCESS_AGGREGATE_RESULT; +} + +#if defined(MOZ_SANDBOX) + +static const char* GetProcessTempBaseDirKey() { +# if defined(XP_WIN) + return NS_WIN_LOW_INTEGRITY_TEMP_BASE; +# else + return NS_OS_TEMP_DIR; +# endif +} + +// +// Sets mContentTempDir so that it refers to the appropriate temp dir. +// If the sandbox is enabled, NS_APP_CONTENT_PROCESS_TEMP_DIR, otherwise +// NS_OS_TEMP_DIR is used. +// +nsresult nsXREDirProvider::LoadContentProcessTempDir() { + // The parent is responsible for creating the sandbox temp dir. + if (XRE_IsParentProcess()) { + mContentProcessSandboxTempDir = + CreateProcessSandboxTempDir(GeckoProcessType_Content); + mContentTempDir = mContentProcessSandboxTempDir; + } else { + mContentTempDir = !IsContentSandboxDisabled() + ? GetProcessSandboxTempDir(GeckoProcessType_Content) + : nullptr; + } + + if (!mContentTempDir) { + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mContentTempDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +# if defined(XP_WIN) + // The content temp dir can be used in sandbox rules, so we need to make sure + // it doesn't contain any junction points or symlinks or the sandbox will + // reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks( + mContentTempDir)) { + NS_WARNING("Failed to resolve Content Temp Dir."); + } +# endif + + return NS_OK; +} + +// +// Sets mPluginTempDir so that it refers to the appropriate temp dir. +// If NS_APP_PLUGIN_PROCESS_TEMP_DIR fails for any reason, NS_OS_TEMP_DIR +// is used. +// +nsresult nsXREDirProvider::LoadPluginProcessTempDir() { + // The parent is responsible for creating the sandbox temp dir. + if (XRE_IsParentProcess()) { + mPluginProcessSandboxTempDir = + CreateProcessSandboxTempDir(GeckoProcessType_Plugin); + mPluginTempDir = mPluginProcessSandboxTempDir; + } else { + MOZ_ASSERT(XRE_IsPluginProcess()); + mPluginTempDir = GetProcessSandboxTempDir(GeckoProcessType_Plugin); + } + + if (!mPluginTempDir) { + nsresult rv = + NS_GetSpecialDirectory(NS_OS_TEMP_DIR, getter_AddRefs(mPluginTempDir)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + } + +# if defined(XP_WIN) + // The temp dir is used in sandbox rules, so we need to make sure + // it doesn't contain any junction points or symlinks or the sandbox will + // reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks( + mPluginTempDir)) { + NS_WARNING("Failed to resolve plugin temp dir."); + } +# endif + + return NS_OK; +} + +static bool IsContentSandboxDisabled() { + return !mozilla::BrowserTabsRemoteAutostart() || + (!mozilla::IsContentSandboxEnabled()); +} + +// +// If a process sandbox temp dir is to be used, returns an nsIFile +// for the directory. Returns null if an error occurs. +// +static already_AddRefed<nsIFile> GetProcessSandboxTempDir( + GeckoProcessType type) { + nsCOMPtr<nsIFile> localFile; + + nsresult rv = NS_GetSpecialDirectory(GetProcessTempBaseDirKey(), + getter_AddRefs(localFile)); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + MOZ_ASSERT((type == GeckoProcessType_Content) || + (type == GeckoProcessType_Plugin)); + + const char* prefKey = (type == GeckoProcessType_Content) + ? "security.sandbox.content.tempDirSuffix" + : "security.sandbox.plugin.tempDirSuffix"; + + nsAutoString tempDirSuffix; + rv = mozilla::Preferences::GetString(prefKey, tempDirSuffix); + if (NS_WARN_IF(NS_FAILED(rv)) || tempDirSuffix.IsEmpty()) { + return nullptr; + } + + rv = localFile->Append(u"Temp-"_ns + tempDirSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + return localFile.forget(); +} + +// +// Create a temporary directory for use from sandboxed processes. +// Only called in the parent. The path is derived from a UUID stored in a +// pref which is available to content and plugin processes. Returns null +// if the content sandbox is disabled or if an error occurs. +// +static already_AddRefed<nsIFile> CreateProcessSandboxTempDir( + GeckoProcessType procType) { + if ((procType == GeckoProcessType_Content) && IsContentSandboxDisabled()) { + return nullptr; + } + + MOZ_ASSERT((procType == GeckoProcessType_Content) || + (procType == GeckoProcessType_Plugin)); + + // Get (and create if blank) temp directory suffix pref. + const char* pref = (procType == GeckoProcessType_Content) + ? "security.sandbox.content.tempDirSuffix" + : "security.sandbox.plugin.tempDirSuffix"; + + nsresult rv; + nsAutoString tempDirSuffix; + mozilla::Preferences::GetString(pref, tempDirSuffix); + if (tempDirSuffix.IsEmpty()) { + nsCOMPtr<nsIUUIDGenerator> uuidgen = + do_GetService("@mozilla.org/uuid-generator;1", &rv); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + nsID uuid; + rv = uuidgen->GenerateUUIDInPlace(&uuid); + if (NS_WARN_IF(NS_FAILED(rv))) { + return nullptr; + } + + char uuidChars[NSID_LENGTH]; + uuid.ToProvidedString(uuidChars); + tempDirSuffix.AssignASCII(uuidChars, NSID_LENGTH); +# ifdef XP_UNIX + // Braces in a path are somewhat annoying to deal with + // and pretty alien on Unix + tempDirSuffix.StripChars(u"{}"); +# endif + + // Save the pref + rv = mozilla::Preferences::SetString(pref, tempDirSuffix); + if (NS_WARN_IF(NS_FAILED(rv))) { + // If we fail to save the pref we don't want to create the temp dir, + // because we won't be able to clean it up later. + return nullptr; + } + + nsCOMPtr<nsIPrefService> prefsvc = mozilla::Preferences::GetService(); + if (!prefsvc || NS_FAILED((rv = prefsvc->SavePrefFile(nullptr)))) { + // Again, if we fail to save the pref file we might not be able to clean + // up the temp directory, so don't create one. Note that in the case + // the preference values allows an off main thread save, the successful + // return from the call doesn't mean we actually saved the file. See + // bug 1364496 for details. + NS_WARNING("Failed to save pref file, cannot create temp dir."); + return nullptr; + } + } + + nsCOMPtr<nsIFile> sandboxTempDir = GetProcessSandboxTempDir(procType); + if (!sandboxTempDir) { + NS_WARNING("Failed to determine sandbox temp dir path."); + return nullptr; + } + + // Remove the directory. It may exist due to a previous crash. + if (NS_FAILED(DeleteDirIfExists(sandboxTempDir))) { + NS_WARNING("Failed to reset sandbox temp dir."); + return nullptr; + } + + // Create the directory + rv = sandboxTempDir->Create(nsIFile::DIRECTORY_TYPE, 0700); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create sandbox temp dir."); + return nullptr; + } + + return sandboxTempDir.forget(); +} + +static nsresult DeleteDirIfExists(nsIFile* dir) { + if (dir) { + // Don't return an error if the directory doesn't exist. + // Windows Remove() returns NS_ERROR_FILE_NOT_FOUND while + // OS X returns NS_ERROR_FILE_TARGET_DOES_NOT_EXIST. + nsresult rv = dir->Remove(/* aRecursive */ true); + if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND && + rv != NS_ERROR_FILE_TARGET_DOES_NOT_EXIST) { + return rv; + } + } + return NS_OK; +} + +#endif // defined(MOZ_SANDBOX) + +static const char* const kAppendPrefDir[] = {"defaults", "preferences", + nullptr}; + +nsresult nsXREDirProvider::GetFilesInternal(const char* aProperty, + nsISimpleEnumerator** aResult) { + nsresult rv = NS_OK; + *aResult = nullptr; + + if (!strcmp(aProperty, NS_APP_PREFS_DEFAULTS_DIR_LIST)) { + nsCOMArray<nsIFile> directories; + + LoadDirIntoArray(mXULAppDir, kAppendPrefDir, directories); + + rv = NS_NewArrayEnumerator(aResult, directories, NS_GET_IID(nsIFile)); + } else if (!strcmp(aProperty, NS_APP_CHROME_DIR_LIST)) { + // NS_APP_CHROME_DIR_LIST is only used to get default (native) icons + // for OS window decoration. + + static const char* const kAppendChromeDir[] = {"chrome", nullptr}; + nsCOMArray<nsIFile> directories; + LoadDirIntoArray(mXULAppDir, kAppendChromeDir, directories); + + rv = NS_NewArrayEnumerator(aResult, directories, NS_GET_IID(nsIFile)); + } else + rv = NS_ERROR_FAILURE; + + return rv; +} + +NS_IMETHODIMP +nsXREDirProvider::GetDirectory(nsIFile** aResult) { + NS_ENSURE_TRUE(mProfileDir, NS_ERROR_NOT_INITIALIZED); + + return mProfileDir->Clone(aResult); +} + +void nsXREDirProvider::InitializeUserPrefs() { + if (!mPrefsInitialized) { + // Temporarily set mProfileNotified to true so that the preference service + // can access the profile directory during initialization. Afterwards, clear + // it so that no other code can inadvertently access it until we get to + // profile-do-change. + mozilla::AutoRestore<bool> ar(mProfileNotified); + mProfileNotified = true; + + mozilla::Preferences::InitializeUserPrefs(); + } +} + +void nsXREDirProvider::FinishInitializingUserPrefs() { + if (!mPrefsInitialized) { + // See InitializeUserPrefs above. + mozilla::AutoRestore<bool> ar(mProfileNotified); + mProfileNotified = true; + + mozilla::Preferences::FinishInitializingUserPrefs(); + + mPrefsInitialized = true; + } +} + +NS_IMETHODIMP +nsXREDirProvider::DoStartup() { + nsresult rv; + + if (!mProfileNotified) { + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + if (!obsSvc) return NS_ERROR_FAILURE; + + mProfileNotified = true; + + /* + Make sure we've setup prefs before profile-do-change to be able to use + them to track crashes and because we want to begin crash tracking before + other code run from this notification since they may cause crashes. + */ + MOZ_ASSERT(mPrefsInitialized); + + bool safeModeNecessary = false; + nsCOMPtr<nsIAppStartup> appStartup( + mozilla::components::AppStartup::Service()); + if (appStartup) { + rv = appStartup->TrackStartupCrashBegin(&safeModeNecessary); + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) + NS_WARNING("Error while beginning startup crash tracking"); + + if (!gSafeMode && safeModeNecessary) { + appStartup->RestartInSafeMode(nsIAppStartup::eForceQuit); + return NS_OK; + } + } + + static const char16_t kStartup[] = {'s', 't', 'a', 'r', + 't', 'u', 'p', '\0'}; + obsSvc->NotifyObservers(nullptr, "profile-do-change", kStartup); + + // Initialize the Enterprise Policies service in the parent process + // In the content process it's loaded on demand when needed + if (XRE_IsParentProcess()) { + nsCOMPtr<nsIObserver> policies( + do_GetService("@mozilla.org/enterprisepolicies;1")); + if (policies) { + policies->Observe(nullptr, "policies-startup", nullptr); + } + } + +#if defined(MOZ_SANDBOX) && defined(XP_WIN) + // Call SandboxBroker to initialize things that depend on Gecko machinery + // like the directory provider. We insert this initialization code here + // (rather than in XRE_mainRun) because we need NS_APP_USER_PROFILE_50_DIR + // to be known and so that any child content processes spawned by extensions + // from the notifications below will have all the requisite directories + // white-listed for read/write access. An example of this is the + // tor-launcher launching the network configuration window. See bug 1485836. + mozilla::SandboxBroker::GeckoDependentInitialize(); +#endif + + // Init the Extension Manager + nsCOMPtr<nsIObserver> em = + do_GetService("@mozilla.org/addons/integration;1"); + if (em) { + em->Observe(nullptr, "addons-startup", nullptr); + } else { + NS_WARNING("Failed to create Addons Manager."); + } + + obsSvc->NotifyObservers(nullptr, "profile-after-change", kStartup); + + // Any component that has registered for the profile-after-change category + // should also be created at this time. + (void)NS_CreateServicesFromCategory("profile-after-change", nullptr, + "profile-after-change"); + + if (gSafeMode && safeModeNecessary) { + static const char16_t kCrashed[] = {'c', 'r', 'a', 's', + 'h', 'e', 'd', '\0'}; + obsSvc->NotifyObservers(nullptr, "safemode-forced", kCrashed); + } + + // 1 = Regular mode, 2 = Safe mode, 3 = Safe mode forced + int mode = 1; + if (gSafeMode) { + if (safeModeNecessary) + mode = 3; + else + mode = 2; + } + mozilla::Telemetry::Accumulate(mozilla::Telemetry::SAFE_MODE_USAGE, mode); + + // Telemetry about number of profiles. + nsCOMPtr<nsIToolkitProfileService> profileService = + do_GetService("@mozilla.org/toolkit/profile-service;1"); + if (profileService) { + uint32_t count = 0; + rv = profileService->GetProfileCount(&count); + if (NS_WARN_IF(NS_FAILED(rv))) { + return rv; + } + + mozilla::Telemetry::Accumulate(mozilla::Telemetry::NUMBER_OF_PROFILES, + count); + } + + obsSvc->NotifyObservers(nullptr, "profile-initial-state", nullptr); + +#if defined(MOZ_SANDBOX) + // Makes sure the content temp dir has been loaded if it hasn't been + // already. In the parent this ensures it has been created before we attempt + // to start any content processes. + if (!mContentTempDir) { + mozilla::Unused << NS_WARN_IF(NS_FAILED(LoadContentProcessTempDir())); + } +#endif +#if defined(MOZ_SANDBOX) + if (!mPluginTempDir) { + mozilla::Unused << NS_WARN_IF(NS_FAILED(LoadPluginProcessTempDir())); + } +#endif + } + return NS_OK; +} + +void nsXREDirProvider::DoShutdown() { + AUTO_PROFILER_LABEL("nsXREDirProvider::DoShutdown", OTHER); + + if (mProfileNotified) { + nsCOMPtr<nsIObserverService> obsSvc = + mozilla::services::GetObserverService(); + NS_ASSERTION(obsSvc, "No observer service?"); + if (obsSvc) { + static const char16_t kShutdownPersist[] = u"shutdown-persist"; + obsSvc->NotifyObservers(nullptr, "profile-change-net-teardown", + kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-change-teardown", + kShutdownPersist); + +#ifdef DEBUG + // Not having this causes large intermittent leaks. See bug 1340425. + if (JSContext* cx = mozilla::dom::danger::GetJSContext()) { + JS_GC(cx); + } +#endif + + obsSvc->NotifyObservers(nullptr, "profile-before-change", + kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-before-change-qm", + kShutdownPersist); + obsSvc->NotifyObservers(nullptr, "profile-before-change-telemetry", + kShutdownPersist); + } + mProfileNotified = false; + } + + gDataDirProfileLocal = nullptr; + gDataDirProfile = nullptr; + + if (XRE_IsParentProcess()) { +#if defined(MOZ_SANDBOX) + mozilla::Unused << DeleteDirIfExists(mContentProcessSandboxTempDir); + mozilla::Unused << DeleteDirIfExists(mPluginProcessSandboxTempDir); +#endif + } +} + +#ifdef XP_WIN +static nsresult GetShellFolderPath(KNOWNFOLDERID folder, nsAString& _retval) { + DWORD flags = KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS; + PWSTR path = nullptr; + + if (!SUCCEEDED(SHGetKnownFolderPath(folder, flags, NULL, &path))) { + return NS_ERROR_NOT_AVAILABLE; + } + + _retval = nsDependentString(path); + CoTaskMemFree(path); + return NS_OK; +} + +/** + * Provides a fallback for getting the path to APPDATA or LOCALAPPDATA by + * querying the registry when the call to SHGetSpecialFolderLocation or + * SHGetPathFromIDListW is unable to provide these paths (Bug 513958). + */ +static nsresult GetRegWindowsAppDataFolder(bool aLocal, nsAString& _retval) { + HKEY key; + LPCWSTR keyName = + L"Software\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Shell Folders"; + DWORD res = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &key); + if (res != ERROR_SUCCESS) { + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + DWORD type, size; + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), nullptr, + &type, nullptr, &size); + // The call to RegQueryValueExW must succeed, the type must be REG_SZ, the + // buffer size must not equal 0, and the buffer size be a multiple of 2. + if (res != ERROR_SUCCESS || type != REG_SZ || size == 0 || size % 2 != 0) { + ::RegCloseKey(key); + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + // |size| may or may not include room for the terminating null character + DWORD resultLen = size / 2; + + if (!_retval.SetLength(resultLen, mozilla::fallible)) { + ::RegCloseKey(key); + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + auto begin = _retval.BeginWriting(); + + res = RegQueryValueExW(key, (aLocal ? L"Local AppData" : L"AppData"), nullptr, + nullptr, (LPBYTE)begin, &size); + ::RegCloseKey(key); + if (res != ERROR_SUCCESS) { + _retval.SetLength(0); + return NS_ERROR_NOT_AVAILABLE; + } + + if (!_retval.CharAt(resultLen - 1)) { + // It was already null terminated. + _retval.Truncate(resultLen - 1); + } + + return NS_OK; +} +#endif + +static nsresult HashInstallPath(nsAString& aInstallPath, nsAString& aPathHash) { + const char* vendor = GetAppVendor(); + if (vendor && vendor[0] == '\0') { + vendor = nullptr; + } + + mozilla::UniquePtr<NS_tchar[]> hash; + bool success = + ::GetInstallHash(PromiseFlatString(aInstallPath).get(), vendor, hash); + if (!success) { + return NS_ERROR_FAILURE; + } + + // The hash string is a NS_tchar*, which is wchar* in Windows and char* + // elsewhere. +#ifdef XP_WIN + aPathHash.Assign(hash.get()); +#else + aPathHash.AssignASCII(hash.get()); +#endif + return NS_OK; +} + +/** + * Gets a hash of the installation directory. + */ +nsresult nsXREDirProvider::GetInstallHash(nsAString& aPathHash) { + nsCOMPtr<nsIFile> installDir; + nsCOMPtr<nsIFile> appFile; + bool per = false; + nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = appFile->GetParent(getter_AddRefs(installDir)); + NS_ENSURE_SUCCESS(rv, rv); + + // It is possible that the path we have is on a case insensitive + // filesystem in which case the path may vary depending on how the + // application is called. We want to normalize the case somehow. +#ifdef XP_WIN + // Windows provides a way to get the correct case. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks( + installDir)) { + NS_WARNING("Failed to resolve install directory."); + } +#elif defined(MOZ_WIDGET_COCOA) + // On OSX roundtripping through an FSRef fixes the case. + FSRef ref; + nsCOMPtr<nsILocalFileMac> macFile = do_QueryInterface(installDir); + rv = macFile->GetFSRef(&ref); + NS_ENSURE_SUCCESS(rv, rv); + rv = NS_NewLocalFileWithFSRef(&ref, true, getter_AddRefs(macFile)); + NS_ENSURE_SUCCESS(rv, rv); + installDir = static_cast<nsIFile*>(macFile); +#endif + // On linux XRE_EXECUTABLE_FILE already seems to be set to the correct path. + + nsAutoString installPath; + rv = installDir->GetPath(installPath); + NS_ENSURE_SUCCESS(rv, rv); + + return HashInstallPath(installPath, aPathHash); +} + +/** + * Before bug 1555319 the directory hashed can have had an incorrect case. + * Access to that hash is still available through this function. It is needed so + * we can migrate users who may have an incorrect hash in profiles.ini. This + * support can probably be removed in a few releases time. + */ +nsresult nsXREDirProvider::GetLegacyInstallHash(nsAString& aPathHash) { + nsCOMPtr<nsIFile> installDir; + nsCOMPtr<nsIFile> appFile; + bool per = false; + nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = appFile->GetParent(getter_AddRefs(installDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsAutoString installPath; + rv = installDir->GetPath(installPath); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef XP_WIN +# if defined(MOZ_THUNDERBIRD) || defined(MOZ_SUITE) + // Convert a 64-bit install path to what would have been the 32-bit install + // path to allow users to migrate their profiles from one to the other. + PWSTR pathX86 = nullptr; + HRESULT hres = + SHGetKnownFolderPath(FOLDERID_ProgramFilesX86, 0, nullptr, &pathX86); + if (SUCCEEDED(hres)) { + nsDependentString strPathX86(pathX86); + if (!StringBeginsWith(installPath, strPathX86, + nsCaseInsensitiveStringComparator)) { + PWSTR path = nullptr; + hres = SHGetKnownFolderPath(FOLDERID_ProgramFiles, 0, nullptr, &path); + if (SUCCEEDED(hres)) { + if (StringBeginsWith(installPath, nsDependentString(path), + nsCaseInsensitiveStringComparator)) { + installPath.Replace(0, wcslen(path), strPathX86); + } + } + CoTaskMemFree(path); + } + } + CoTaskMemFree(pathX86); +# endif +#endif + return HashInstallPath(installPath, aPathHash); +} + +nsresult nsXREDirProvider::GetUpdateRootDir(nsIFile** aResult, + bool aGetOldLocation) { +#ifndef XP_WIN + // There is no old update location on platforms other than Windows. Windows is + // the only platform for which we migrated the update directory. + if (aGetOldLocation) { + return NS_ERROR_NOT_IMPLEMENTED; + } +#endif + nsCOMPtr<nsIFile> updRoot; + nsCOMPtr<nsIFile> appFile; + bool per = false; + nsresult rv = GetFile(XRE_EXECUTABLE_FILE, &per, getter_AddRefs(appFile)); + NS_ENSURE_SUCCESS(rv, rv); + rv = appFile->GetParent(getter_AddRefs(updRoot)); + NS_ENSURE_SUCCESS(rv, rv); + +#ifdef XP_MACOSX + nsCOMPtr<nsIFile> appRootDirFile; + nsCOMPtr<nsIFile> localDir; + nsAutoString appDirPath; + if (NS_FAILED(appFile->GetParent(getter_AddRefs(appRootDirFile))) || + NS_FAILED(appRootDirFile->GetPath(appDirPath)) || + NS_FAILED(GetUserDataDirectoryHome(getter_AddRefs(localDir), true))) { + return NS_ERROR_FAILURE; + } + + int32_t dotIndex = appDirPath.RFind(".app"); + if (dotIndex == kNotFound) { + dotIndex = appDirPath.Length(); + } + appDirPath = Substring(appDirPath, 1, dotIndex - 1); + + bool hasVendor = GetAppVendor() && strlen(GetAppVendor()) != 0; + if (hasVendor || GetAppName()) { + if (NS_FAILED(localDir->AppendNative( + nsDependentCString(hasVendor ? GetAppVendor() : GetAppName())))) { + return NS_ERROR_FAILURE; + } + } else if (NS_FAILED(localDir->AppendNative("Mozilla"_ns))) { + return NS_ERROR_FAILURE; + } + + if (NS_FAILED(localDir->Append(u"updates"_ns)) || + NS_FAILED(localDir->AppendRelativePath(appDirPath))) { + return NS_ERROR_FAILURE; + } + + localDir.forget(aResult); + return NS_OK; + +#elif XP_WIN + nsAutoString installPath; + rv = updRoot->GetPath(installPath); + NS_ENSURE_SUCCESS(rv, rv); + + mozilla::UniquePtr<wchar_t[]> updatePath; + HRESULT hrv; + if (aGetOldLocation) { + const char* vendor = GetAppVendor(); + if (vendor && vendor[0] == '\0') { + vendor = nullptr; + } + const char* appName = GetAppName(); + if (appName && appName[0] == '\0') { + appName = nullptr; + } + hrv = GetUserUpdateDirectory(PromiseFlatString(installPath).get(), vendor, + appName, updatePath); + } else { + hrv = GetCommonUpdateDirectory(PromiseFlatString(installPath).get(), + SetPermissionsOf::BaseDirIfNotExists, + updatePath); + } + if (FAILED(hrv)) { + return NS_ERROR_FAILURE; + } + nsAutoString updatePathStr; + updatePathStr.Assign(updatePath.get()); + updRoot->InitWithPath(updatePathStr); +#endif // XP_WIN + updRoot.forget(aResult); + return NS_OK; +} + +nsresult nsXREDirProvider::GetProfileStartupDir(nsIFile** aResult) { + if (mProfileDir) return mProfileDir->Clone(aResult); + + if (mAppProvider) { + nsCOMPtr<nsIFile> needsclone; + bool dummy; + nsresult rv = mAppProvider->GetFile(NS_APP_PROFILE_DIR_STARTUP, &dummy, + getter_AddRefs(needsclone)); + if (NS_SUCCEEDED(rv)) return needsclone->Clone(aResult); + } + + return NS_ERROR_FAILURE; +} + +nsresult nsXREDirProvider::GetProfileDir(nsIFile** aResult) { + if (mProfileDir) { + if (!mProfileNotified) return NS_ERROR_FAILURE; + + return mProfileDir->Clone(aResult); + } + + if (mAppProvider) { + nsCOMPtr<nsIFile> needsclone; + bool dummy; + nsresult rv = mAppProvider->GetFile(NS_APP_USER_PROFILE_50_DIR, &dummy, + getter_AddRefs(needsclone)); + if (NS_SUCCEEDED(rv)) return needsclone->Clone(aResult); + } + + return NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR, aResult); +} + +NS_IMETHODIMP +nsXREDirProvider::SetUserDataDirectory(nsIFile* aFile, bool aLocal) { + if (aLocal) { + NS_IF_RELEASE(gDataDirHomeLocal); + NS_IF_ADDREF(gDataDirHomeLocal = aFile); + } else { + NS_IF_RELEASE(gDataDirHome); + NS_IF_ADDREF(gDataDirHome = aFile); + } + + return NS_OK; +} + +/* static */ +nsresult nsXREDirProvider::SetUserDataProfileDirectory(nsCOMPtr<nsIFile>& aFile, + bool aLocal) { + if (aLocal) { + gDataDirProfileLocal = aFile; + } else { + gDataDirProfile = aFile; + } + + return NS_OK; +} + +nsresult nsXREDirProvider::GetUserDataDirectoryHome(nsIFile** aFile, + bool aLocal) { + // Copied from nsAppFileLocationProvider (more or less) + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + if (aLocal && gDataDirHomeLocal) { + return gDataDirHomeLocal->Clone(aFile); + } + if (!aLocal && gDataDirHome) { + return gDataDirHome->Clone(aFile); + } + +#if defined(XP_MACOSX) + FSRef fsRef; + OSType folderType; + if (aLocal) { + folderType = kCachedDataFolderType; + } else { +# ifdef MOZ_THUNDERBIRD + folderType = kDomainLibraryFolderType; +# else + folderType = kApplicationSupportFolderType; +# endif + } + OSErr err = ::FSFindFolder(kUserDomain, folderType, kCreateFolder, &fsRef); + NS_ENSURE_FALSE(err, NS_ERROR_FAILURE); + + rv = NS_NewNativeLocalFile(""_ns, true, getter_AddRefs(localDir)); + NS_ENSURE_SUCCESS(rv, rv); + + nsCOMPtr<nsILocalFileMac> dirFileMac = do_QueryInterface(localDir); + NS_ENSURE_TRUE(dirFileMac, NS_ERROR_UNEXPECTED); + + rv = dirFileMac->InitWithFSRef(&fsRef); + NS_ENSURE_SUCCESS(rv, rv); + + localDir = dirFileMac; +#elif defined(XP_IOS) + nsAutoCString userDir; + if (GetUIKitDirectory(aLocal, userDir)) { + rv = NS_NewNativeLocalFile(userDir, true, getter_AddRefs(localDir)); + } else { + rv = NS_ERROR_FAILURE; + } + NS_ENSURE_SUCCESS(rv, rv); +#elif defined(XP_WIN) + nsString path; + if (aLocal) { + rv = GetShellFolderPath(FOLDERID_LocalAppData, path); + if (NS_FAILED(rv)) rv = GetRegWindowsAppDataFolder(aLocal, path); + } + if (!aLocal || NS_FAILED(rv)) { + rv = GetShellFolderPath(FOLDERID_RoamingAppData, path); + if (NS_FAILED(rv)) { + if (!aLocal) rv = GetRegWindowsAppDataFolder(aLocal, path); + } + } + NS_ENSURE_SUCCESS(rv, rv); + + rv = NS_NewLocalFile(path, true, getter_AddRefs(localDir)); +#elif defined(XP_UNIX) + const char* homeDir = getenv("HOME"); + if (!homeDir || !*homeDir) return NS_ERROR_FAILURE; + +# ifdef ANDROID /* We want (ProfD == ProfLD) on Android. */ + aLocal = false; +# endif + + if (aLocal) { + // If $XDG_CACHE_HOME is defined use it, otherwise use $HOME/.cache. + const char* cacheHome = getenv("XDG_CACHE_HOME"); + if (cacheHome && *cacheHome) { + rv = NS_NewNativeLocalFile(nsDependentCString(cacheHome), true, + getter_AddRefs(localDir)); + } else { + rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true, + getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) rv = localDir->AppendNative(".cache"_ns); + } + } else { + rv = NS_NewNativeLocalFile(nsDependentCString(homeDir), true, + getter_AddRefs(localDir)); + } +#else +# error "Don't know how to get product dir on your platform" +#endif + + NS_IF_ADDREF(*aFile = localDir); + return rv; +} + +nsresult nsXREDirProvider::GetSysUserExtensionsDirectory(nsIFile** aFile) { + nsCOMPtr<nsIFile> localDir; + nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendSysUserExtensionPath(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureDirectoryExists(localDir); + NS_ENSURE_SUCCESS(rv, rv); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // This is used in sandbox rules, so we need to make sure it doesn't contain + // any junction points or symlinks or the sandbox will reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks(localDir)) { + NS_WARNING("Failed to resolve sys user extensions directory."); + } +#endif + + localDir.forget(aFile); + return NS_OK; +} + +nsresult nsXREDirProvider::GetSysUserExtensionsDevDirectory(nsIFile** aFile) { + nsCOMPtr<nsIFile> localDir; + nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), false); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendSysUserExtensionsDevPath(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureDirectoryExists(localDir); + NS_ENSURE_SUCCESS(rv, rv); + +#if defined(XP_WIN) && defined(MOZ_SANDBOX) + // This is used in sandbox rules, so we need to make sure it doesn't contain + // any junction points or symlinks or the sandbox will reject those rules. + if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks(localDir)) { + NS_WARNING("Failed to resolve sys user extensions dev directory."); + } +#endif + + localDir.forget(aFile); + return NS_OK; +} + +#if defined(XP_UNIX) || defined(XP_MACOSX) +nsresult nsXREDirProvider::GetSystemExtensionsDirectory(nsIFile** aFile) { + nsresult rv; + nsCOMPtr<nsIFile> localDir; + + rv = GetSystemParentDirectory(getter_AddRefs(localDir)); + if (NS_SUCCEEDED(rv)) { + constexpr auto sExtensions = +# if defined(XP_MACOSX) + "Extensions"_ns +# else + "extensions"_ns +# endif + ; + + rv = localDir->AppendNative(sExtensions); + if (NS_SUCCEEDED(rv)) { + localDir.forget(aFile); + } + } + return rv; +} +#endif + +nsresult nsXREDirProvider::GetUserDataDirectory(nsIFile** aFile, bool aLocal) { + nsCOMPtr<nsIFile> localDir; + + if (aLocal && gDataDirProfileLocal) { + return gDataDirProfileLocal->Clone(aFile); + } + if (!aLocal && gDataDirProfile) { + return gDataDirProfile->Clone(aFile); + } + + nsresult rv = GetUserDataDirectoryHome(getter_AddRefs(localDir), aLocal); + NS_ENSURE_SUCCESS(rv, rv); + + rv = AppendProfilePath(localDir, aLocal); + NS_ENSURE_SUCCESS(rv, rv); + + rv = EnsureDirectoryExists(localDir); + NS_ENSURE_SUCCESS(rv, rv); + + nsXREDirProvider::SetUserDataProfileDirectory(localDir, aLocal); + + localDir.forget(aFile); + return NS_OK; +} + +nsresult nsXREDirProvider::EnsureDirectoryExists(nsIFile* aDirectory) { + nsresult rv = aDirectory->Create(nsIFile::DIRECTORY_TYPE, 0700); + + if (rv == NS_ERROR_FILE_ALREADY_EXISTS) { + rv = NS_OK; + } + return rv; +} + +nsresult nsXREDirProvider::AppendSysUserExtensionPath(nsIFile* aFile) { + NS_ASSERTION(aFile, "Null pointer!"); + + nsresult rv; + +#if defined(XP_MACOSX) || defined(XP_WIN) + + static const char* const sXR = "Mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "Extensions"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(XP_UNIX) + + static const char* const sXR = ".mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "extensions"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#else +# error "Don't know how to get XRE user extension path on your platform" +#endif + return NS_OK; +} + +nsresult nsXREDirProvider::AppendSysUserExtensionsDevPath(nsIFile* aFile) { + MOZ_ASSERT(aFile); + + nsresult rv; + +#if defined(XP_MACOSX) || defined(XP_WIN) + + static const char* const sXR = "Mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "SystemExtensionsDev"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(XP_UNIX) + + static const char* const sXR = ".mozilla"; + rv = aFile->AppendNative(nsDependentCString(sXR)); + NS_ENSURE_SUCCESS(rv, rv); + + static const char* const sExtensions = "systemextensionsdev"; + rv = aFile->AppendNative(nsDependentCString(sExtensions)); + NS_ENSURE_SUCCESS(rv, rv); + +#else +# error "Don't know how to get XRE system extension dev path on your platform" +#endif + return NS_OK; +} + +nsresult nsXREDirProvider::AppendProfilePath(nsIFile* aFile, bool aLocal) { + NS_ASSERTION(aFile, "Null pointer!"); + + // If there is no XREAppData then there is no information to use to build + // the profile path so just do nothing. This should only happen in xpcshell + // tests. + if (!gAppData) { + return NS_OK; + } + + nsAutoCString profile; + nsAutoCString appName; + nsAutoCString vendor; + if (gAppData->profile) { + profile = gAppData->profile; + } else { + appName = gAppData->name; + vendor = gAppData->vendor; + } + + nsresult rv = NS_OK; + +#if defined(XP_MACOSX) + if (!profile.IsEmpty()) { + rv = AppendProfileString(aFile, profile.get()); + } else { + // Note that MacOS ignores the vendor when creating the profile hierarchy - + // all application preferences directories live alongside one another in + // ~/Library/Application Support/ + rv = aFile->AppendNative(appName); + } + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(XP_WIN) + if (!profile.IsEmpty()) { + rv = AppendProfileString(aFile, profile.get()); + } else { + if (!vendor.IsEmpty()) { + rv = aFile->AppendNative(vendor); + NS_ENSURE_SUCCESS(rv, rv); + } + rv = aFile->AppendNative(appName); + } + NS_ENSURE_SUCCESS(rv, rv); + +#elif defined(ANDROID) + // The directory used for storing profiles + // The parent of this directory is set in GetUserDataDirectoryHome + // XXX: handle gAppData->profile properly + // XXXsmaug ...and the rest of the profile creation! + rv = aFile->AppendNative(nsDependentCString("mozilla")); + NS_ENSURE_SUCCESS(rv, rv); +#elif defined(XP_UNIX) + nsAutoCString folder; + // Make it hidden (by starting with "."), except when local (the + // profile is already under ~/.cache or XDG_CACHE_HOME). + if (!aLocal) folder.Assign('.'); + + if (!profile.IsEmpty()) { + // Skip any leading path characters + const char* profileStart = profile.get(); + while (*profileStart == '/' || *profileStart == '\\') profileStart++; + + // On the off chance that someone wanted their folder to be hidden don't + // let it become ".." + if (*profileStart == '.' && !aLocal) profileStart++; + + folder.Append(profileStart); + ToLowerCase(folder); + + rv = AppendProfileString(aFile, folder.BeginReading()); + } else { + if (!vendor.IsEmpty()) { + folder.Append(vendor); + ToLowerCase(folder); + + rv = aFile->AppendNative(folder); + NS_ENSURE_SUCCESS(rv, rv); + + folder.Truncate(); + } + + // This can be the case in tests. + if (!appName.IsEmpty()) { + folder.Append(appName); + ToLowerCase(folder); + + rv = aFile->AppendNative(folder); + } + } + NS_ENSURE_SUCCESS(rv, rv); + +#else +# error "Don't know how to get profile path on your platform" +#endif + return NS_OK; +} + +nsresult nsXREDirProvider::AppendProfileString(nsIFile* aFile, + const char* aPath) { + NS_ASSERTION(aFile, "Null file!"); + NS_ASSERTION(aPath, "Null path!"); + + nsAutoCString pathDup(aPath); + + char* path = pathDup.BeginWriting(); + + nsresult rv; + char* subdir; + while ((subdir = NS_strtok("/\\", &path))) { + rv = aFile->AppendNative(nsDependentCString(subdir)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} diff --git a/toolkit/xre/nsXREDirProvider.h b/toolkit/xre/nsXREDirProvider.h new file mode 100644 index 0000000000..e55b4e153c --- /dev/null +++ b/toolkit/xre/nsXREDirProvider.h @@ -0,0 +1,165 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 _nsXREDirProvider_h__ +#define _nsXREDirProvider_h__ + +#include "nsIDirectoryService.h" +#include "nsIProfileMigrator.h" +#include "nsIFile.h" +#include "nsIXREDirProvider.h" + +#include "nsCOMPtr.h" +#include "nsCOMArray.h" +#include "mozilla/Attributes.h" + +// {5573967d-f6cf-4c63-8e0e-9ac06e04d62b} +#define NS_XREDIRPROVIDER_CID \ + { \ + 0x5573967d, 0xf6cf, 0x4c63, { \ + 0x8e, 0x0e, 0x9a, 0xc0, 0x6e, 0x04, 0xd6, 0x2b \ + } \ + } +#define NS_XREDIRPROVIDER_CONTRACTID "@mozilla.org/xre/directory-provider;1" + +class nsXREDirProvider final : public nsIDirectoryServiceProvider2, + public nsIXREDirProvider, + public nsIProfileStartup { + public: + // we use a custom isupports implementation (no refcount) + NS_IMETHOD QueryInterface(REFNSIID aIID, void** aInstancePtr) override; + NS_IMETHOD_(MozExternalRefCountType) AddRef(void) override; + NS_IMETHOD_(MozExternalRefCountType) Release(void) override; + + NS_DECL_NSIDIRECTORYSERVICEPROVIDER + NS_DECL_NSIDIRECTORYSERVICEPROVIDER2 + NS_DECL_NSIXREDIRPROVIDER + NS_DECL_NSIPROFILESTARTUP + + nsXREDirProvider(); + + // if aXULAppDir is null, use gArgv[0] + nsresult Initialize(nsIFile* aXULAppDir, nsIFile* aGREDir, + nsIDirectoryServiceProvider* aAppProvider = nullptr); + ~nsXREDirProvider(); + + static already_AddRefed<nsXREDirProvider> GetSingleton(); + + nsresult GetUserProfilesRootDir(nsIFile** aResult); + nsresult GetUserProfilesLocalDir(nsIFile** aResult); + + nsresult GetLegacyInstallHash(nsAString& aPathHash); + + // We only set the profile dir, we don't ensure that it exists; + // that is the responsibility of the toolkit profile service. + // We also don't fire profile-changed notifications... that is + // the responsibility of the apprunner. + nsresult SetProfile(nsIFile* aProfileDir, nsIFile* aProfileLocalDir); + + void InitializeUserPrefs(); + void FinishInitializingUserPrefs(); + + void DoShutdown(); + + static nsresult GetUserAppDataDirectory(nsIFile** aFile) { + return GetUserDataDirectory(aFile, false); + } + static nsresult GetUserLocalDataDirectory(nsIFile** aFile) { + return GetUserDataDirectory(aFile, true); + } + + // GetUserDataDirectory gets the profile path from gAppData. + static nsresult GetUserDataDirectory(nsIFile** aFile, bool aLocal); + + /* make sure you clone it, if you need to do stuff to it */ + nsIFile* GetGREDir() { return mGREDir; } + nsIFile* GetGREBinDir() { return mGREBinDir; } + nsIFile* GetAppDir() { + if (mXULAppDir) return mXULAppDir; + return mGREDir; + } + + /** + * Get the directory under which update directory is created. + * This method may be called before XPCOM is started. aResult + * is a clone, it may be modified. + * + * If aGetOldLocation is true, this function will return the location of + * the update directory before it was moved from the user profile directory + * to a per-installation directory. This functionality is only meant to be + * used for migration of the update directory to the new location. It is only + * valid to request the old update location on Windows, since that is the only + * platform on which the update directory was migrated. + */ + nsresult GetUpdateRootDir(nsIFile** aResult, bool aGetOldLocation = false); + + /** + * Get the profile startup directory as determined by this class or by + * mAppProvider. This method may be called before XPCOM is started. aResult + * is a clone, it may be modified. + */ + nsresult GetProfileStartupDir(nsIFile** aResult); + + /** + * Get the profile directory as determined by this class or by an + * embedder-provided XPCOM directory provider. Only call this method + * when XPCOM is initialized! aResult is a clone, it may be modified. + */ + nsresult GetProfileDir(nsIFile** aResult); + + protected: + nsresult GetFilesInternal(const char* aProperty, + nsISimpleEnumerator** aResult); + static nsresult GetUserDataDirectoryHome(nsIFile** aFile, bool aLocal); + static nsresult GetSysUserExtensionsDirectory(nsIFile** aFile); + static nsresult GetSysUserExtensionsDevDirectory(nsIFile** aFile); +#if defined(XP_UNIX) || defined(XP_MACOSX) + static nsresult GetSystemExtensionsDirectory(nsIFile** aFile); +#endif + static nsresult EnsureDirectoryExists(nsIFile* aDirectory); + + // Determine the profile path within the UAppData directory. This is different + // on every major platform. + static nsresult AppendProfilePath(nsIFile* aFile, bool aLocal); + + static nsresult AppendSysUserExtensionPath(nsIFile* aFile); + static nsresult AppendSysUserExtensionsDevPath(nsIFile* aFile); + + // Internal helper that splits a path into components using the '/' and '\\' + // delimiters. + static inline nsresult AppendProfileString(nsIFile* aFile, const char* aPath); + +#if defined(MOZ_SANDBOX) + // Load the temp directory for sandboxed content processes + nsresult LoadContentProcessTempDir(); + nsresult LoadPluginProcessTempDir(); +#endif + + void Append(nsIFile* aDirectory); + + nsCOMPtr<nsIDirectoryServiceProvider> mAppProvider; + // On OSX, mGREDir points to .app/Contents/Resources + nsCOMPtr<nsIFile> mGREDir; + // On OSX, mGREBinDir points to .app/Contents/MacOS + nsCOMPtr<nsIFile> mGREBinDir; + // On OSX, mXULAppDir points to .app/Contents/Resources/browser + nsCOMPtr<nsIFile> mXULAppDir; + nsCOMPtr<nsIFile> mProfileDir; + nsCOMPtr<nsIFile> mProfileLocalDir; + bool mProfileNotified; + bool mPrefsInitialized = false; +#if defined(MOZ_SANDBOX) + nsCOMPtr<nsIFile> mContentTempDir; + nsCOMPtr<nsIFile> mContentProcessSandboxTempDir; + nsCOMPtr<nsIFile> mPluginTempDir; + nsCOMPtr<nsIFile> mPluginProcessSandboxTempDir; +#endif + + private: + static nsresult SetUserDataProfileDirectory(nsCOMPtr<nsIFile>& aFile, + bool aLocal); +}; + +#endif diff --git a/toolkit/xre/platform.ini b/toolkit/xre/platform.ini new file mode 100644 index 0000000000..01c8b741a1 --- /dev/null +++ b/toolkit/xre/platform.ini @@ -0,0 +1,17 @@ +#if 0 +; 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/. +#endif +#filter substitution +#include @TOPOBJDIR@/buildid.h +#include @TOPOBJDIR@/source-repo.h +[Build] +BuildID=@MOZ_BUILDID@ +Milestone=@GRE_MILESTONE@ +#ifdef MOZ_SOURCE_REPO +SourceRepository=@MOZ_SOURCE_REPO@ +#endif +#ifdef MOZ_SOURCE_STAMP +SourceStamp=@MOZ_SOURCE_STAMP@ +#endif diff --git a/toolkit/xre/test/.eslintrc.js b/toolkit/xre/test/.eslintrc.js new file mode 100644 index 0000000000..1e0293e76e --- /dev/null +++ b/toolkit/xre/test/.eslintrc.js @@ -0,0 +1,9 @@ +"use strict"; + +module.exports = { + extends: [ + "plugin:mozilla/mochitest-test", + "plugin:mozilla/browser-test", + "plugin:mozilla/xpcshell-test", + ], +}; diff --git a/toolkit/xre/test/browser.ini b/toolkit/xre/test/browser.ini new file mode 100644 index 0000000000..7f6eaced59 --- /dev/null +++ b/toolkit/xre/test/browser.ini @@ -0,0 +1,4 @@ +[DEFAULT] + +[browser_checkdllblockliststate.js] +skip-if = os != "win" || ccov # Bug 1531789 diff --git a/toolkit/xre/test/browser_checkdllblockliststate.js b/toolkit/xre/test/browser_checkdllblockliststate.js new file mode 100644 index 0000000000..0054e0a8de --- /dev/null +++ b/toolkit/xre/test/browser_checkdllblockliststate.js @@ -0,0 +1,16 @@ +// Any copyright is dedicated to the Public Domain. +// http://creativecommons.org/publicdomain/zero/1.0/ + +// Tests that the dll blocklist initializes correctly during test runs. +add_task(async function test() { + await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function( + browser + ) { + ok( + Services.appinfo.windowsDLLBlocklistStatus, + "Windows dll blocklist status should be true, indicating it is " + + "running properly. A failure in this test is considered a " + + "release blocker." + ); + }); +}); diff --git a/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp new file mode 100644 index 0000000000..cba65ae71a --- /dev/null +++ b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp @@ -0,0 +1,173 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "gtest/gtest.h" + +#include "mozilla/AssembleCmdLine.h" +#include "mozilla/CmdLineAndEnvUtils.h" +#include "mozilla/UniquePtrExtensions.h" +#include "WinRemoteMessage.h" + +using namespace mozilla; + +template <typename T> +struct TestCase { + const T* mArgs[4]; + const wchar_t* mExpected; +}; + +#define ALPHA_IN_UTF8 "\xe3\x82\xa2\xe3\x83\xab\xe3\x83\x95\xe3\x82\xa1" +#define OMEGA_IN_UTF8 "\xe3\x82\xaa\xe3\x83\xa1\xe3\x82\xac" +#define ALPHA_IN_UTF16 L"\u30A2\u30EB\u30D5\u30A1" +#define OMEGA_IN_UTF16 L"\u30AA\u30E1\u30AC" +#define UPPER_CYRILLIC_P_IN_UTF8 "\xd0\xa0" +#define LOWER_CYRILLIC_P_IN_UTF8 "\xd1\x80" +#define UPPER_CYRILLIC_P_IN_UTF16 L"\u0420" +#define LOWER_CYRILLIC_P_IN_UTF16 L"\u0440" + +TestCase<char> testCases[] = { + // Copied from TestXREMakeCommandLineWin.ini + {{"a:\\", nullptr}, L"a:\\"}, + {{"a:\"", nullptr}, L"a:\\\""}, + {{"a:\\b c", nullptr}, L"\"a:\\b c\""}, + {{"a:\\b c\"", nullptr}, L"\"a:\\b c\\\"\""}, + {{"a:\\b c\\d e", nullptr}, L"\"a:\\b c\\d e\""}, + {{"a:\\b c\\d e\"", nullptr}, L"\"a:\\b c\\d e\\\"\""}, + {{"a:\\", nullptr}, L"a:\\"}, + {{"a:\"", "b:\\c d", nullptr}, L"a:\\\" \"b:\\c d\""}, + {{"a", "b:\" c:\\d", "e", nullptr}, L"a \"b:\\\" c:\\d\" e"}, + {{"abc", "d", "e", nullptr}, L"abc d e"}, + {{"a b c", "d", "e", nullptr}, L"\"a b c\" d e"}, + {{"a\\\\\\b", "de fg", "h", nullptr}, L"a\\\\\\b \"de fg\" h"}, + {{"a", "b", nullptr}, L"a b"}, + {{"a\tb", nullptr}, L"\"a\tb\""}, + {{"a\\\"b", "c", "d", nullptr}, L"a\\\\\\\"b c d"}, + {{"a\\\"b", "c", nullptr}, L"a\\\\\\\"b c"}, + {{"a\\\\\\b c", nullptr}, L"\"a\\\\\\b c\""}, + {{"\"a", nullptr}, L"\\\"a"}, + {{"\\a", nullptr}, L"\\a"}, + {{"\\\\\\a", nullptr}, L"\\\\\\a"}, + {{"\\\\\\\"a", nullptr}, L"\\\\\\\\\\\\\\\"a"}, + {{"a\\\"b c\" d e", nullptr}, L"\"a\\\\\\\"b c\\\" d e\""}, + {{"a\\\\\"b", "c d e", nullptr}, L"a\\\\\\\\\\\"b \"c d e\""}, + {{"a:\\b", "c\\" ALPHA_IN_UTF8, OMEGA_IN_UTF8 "\\d", nullptr}, + L"a:\\b c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d"}, + {{"a:\\b", "c\\" ALPHA_IN_UTF8 " " OMEGA_IN_UTF8 "\\d", nullptr}, + L"a:\\b \"c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d\""}, + {{ALPHA_IN_UTF8, OMEGA_IN_UTF8, nullptr}, + ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16}, + + // More single-argument cases + {{"", nullptr}, L""}, + {{"a\fb", nullptr}, L"\"a\fb\""}, + {{"a\nb", nullptr}, L"\"a\nb\""}, + {{"a\rb", nullptr}, L"\"a\rb\""}, + {{"a\vb", nullptr}, L"\"a\vb\""}, + {{"\"a\" \"b\"", nullptr}, L"\"\\\"a\\\" \\\"b\\\"\""}, + {{"\"a\\b\" \"c\\d\"", nullptr}, L"\"\\\"a\\b\\\" \\\"c\\d\\\"\""}, + {{"\\\\ \\\\", nullptr}, L"\"\\\\ \\\\\\\\\""}, + {{"\"\" \"\"", nullptr}, L"\"\\\"\\\" \\\"\\\"\""}, + {{ALPHA_IN_UTF8 "\\" OMEGA_IN_UTF8, nullptr}, + ALPHA_IN_UTF16 L"\\" OMEGA_IN_UTF16}, + {{ALPHA_IN_UTF8 " " OMEGA_IN_UTF8, nullptr}, + L"\"" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\""}, +}; + +TEST(AssembleCommandLineWin, assembleCmdLine) +{ + for (const auto& testCase : testCases) { + UniqueFreePtr<wchar_t> assembled; + wchar_t* assembledRaw = nullptr; + EXPECT_EQ(assembleCmdLine(testCase.mArgs, &assembledRaw, CP_UTF8), 0); + assembled.reset(assembledRaw); + + EXPECT_STREQ(assembled.get(), testCase.mExpected); + } +} + +TEST(CommandLineParserWin, HandleCommandLine) +{ + CommandLineParserWin<char> parser; + for (const auto& testCase : testCases) { + NS_ConvertUTF16toUTF8 utf8(testCase.mExpected); + parser.HandleCommandLine(utf8.get()); + + if (utf8.Length() == 0) { + EXPECT_EQ(parser.Argc(), 0); + continue; + } + + for (int i = 0; i < parser.Argc(); ++i) { + EXPECT_NE(testCase.mArgs[i], nullptr); + EXPECT_STREQ(parser.Argv()[i], testCase.mArgs[i]); + } + EXPECT_EQ(testCase.mArgs[parser.Argc()], nullptr); + } +} + +TEST(WinRemoteMessage, SendReceive) +{ + const char kCommandline[] = + "dummy.exe /arg1 --arg2 \"3rd arg\" " + "4th=\"" UPPER_CYRILLIC_P_IN_UTF8 " " LOWER_CYRILLIC_P_IN_UTF8 "\""; + const wchar_t kCommandlineW[] = + L"dummy.exe /arg1 --arg2 \"3rd arg\" " + L"4th=\"" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16 L"\""; + const wchar_t* kExpectedArgsW[] = { + L"-arg1", L"-arg2", L"3rd arg", + L"4th=" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16}; + + char workingDirA[MAX_PATH]; + wchar_t workingDirW[MAX_PATH]; + EXPECT_NE(getcwd(workingDirA, MAX_PATH), nullptr); + EXPECT_NE(_wgetcwd(workingDirW, MAX_PATH), nullptr); + + WinRemoteMessageSender v0(kCommandline); + WinRemoteMessageSender v1(kCommandline, workingDirA); + WinRemoteMessageSender v2(kCommandlineW, workingDirW); + + WinRemoteMessageReceiver receiver; + int32_t len; + nsAutoString arg; + nsCOMPtr<nsIFile> workingDir; + + receiver.Parse(v0.CopyData()); + EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len))); + EXPECT_EQ(len, ArrayLength(kExpectedArgsW)); + for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) { + EXPECT_TRUE( + NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg))); + EXPECT_STREQ(arg.get(), kExpectedArgsW[i]); + } + EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory( + getter_AddRefs(workingDir)), + NS_ERROR_NOT_INITIALIZED); + + receiver.Parse(v1.CopyData()); + EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len))); + EXPECT_EQ(len, ArrayLength(kExpectedArgsW)); + for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) { + EXPECT_TRUE( + NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg))); + EXPECT_STREQ(arg.get(), kExpectedArgsW[i]); + } + EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory( + getter_AddRefs(workingDir)))); + EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg))); + EXPECT_STREQ(arg.get(), workingDirW); + + receiver.Parse(v2.CopyData()); + EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len))); + EXPECT_EQ(len, ArrayLength(kExpectedArgsW)); + for (int i = 0; i < ArrayLength(kExpectedArgsW); ++i) { + EXPECT_TRUE( + NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg))); + EXPECT_STREQ(arg.get(), kExpectedArgsW[i]); + } + EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory( + getter_AddRefs(workingDir)))); + EXPECT_TRUE(NS_SUCCEEDED(workingDir->GetPath(arg))); + EXPECT_STREQ(arg.get(), workingDirW); +} diff --git a/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp new file mode 100644 index 0000000000..bce64aacd5 --- /dev/null +++ b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp @@ -0,0 +1,53 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" +#include "nsAppRunner.h" +#include "nsString.h" + +void CheckCompatVersionCompare(const nsCString& aOldCompatVersion, + const nsCString& aNewCompatVersion, + bool aExpectedSame, bool aExpectedDowngrade) { + printf("Comparing '%s' to '%s'.\n", aOldCompatVersion.get(), + aNewCompatVersion.get()); + + int32_t result = CompareCompatVersions(aOldCompatVersion, aNewCompatVersion); + + ASSERT_EQ(aExpectedSame, result == 0) + << "Version sameness check should match."; + ASSERT_EQ(aExpectedDowngrade, result > 0) + << "Version downgrade check should match."; +} + +void CheckExpectedResult(const char* aOldAppVersion, const char* aNewAppVersion, + bool aExpectedSame, bool aExpectedDowngrade) { + nsCString oldCompatVersion; + BuildCompatVersion(aOldAppVersion, "", "", oldCompatVersion); + + nsCString newCompatVersion; + BuildCompatVersion(aNewAppVersion, "", "", newCompatVersion); + + CheckCompatVersionCompare(oldCompatVersion, newCompatVersion, aExpectedSame, + aExpectedDowngrade); +} + +TEST(CompatVersionCompare, CompareVersionChange) +{ + // Identical + CheckExpectedResult("67.0", "67.0", true, false); + + // Version changes + CheckExpectedResult("67.0", "68.0", false, false); + CheckExpectedResult("68.0", "67.0", false, true); + CheckExpectedResult("67.0", "67.0.1", true, false); + CheckExpectedResult("67.0.1", "67.0", true, false); + CheckExpectedResult("67.0.1", "67.0.1", true, false); + CheckExpectedResult("67.0.1", "67.0.2", true, false); + CheckExpectedResult("67.0.2", "67.0.1", true, false); + + // Check that if the last run was safe mode then we consider this an upgrade. + CheckCompatVersionCompare( + "Safe Mode"_ns, "67.0.1_20000000000000/20000000000000"_ns, false, false); +} diff --git a/toolkit/xre/test/gtest/TestUntrustedModules.cpp b/toolkit/xre/test/gtest/TestUntrustedModules.cpp new file mode 100644 index 0000000000..661b6919bf --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules.cpp @@ -0,0 +1,382 @@ +/* vim:set ts=2 sw=2 sts=2 et: */ +/* Any copyright is dedicated to the Public Domain. + * http://creativecommons.org/publicdomain/zero/1.0/ + */ + +#include "gtest/gtest.h" + +#include "js/RegExp.h" +#include "mozilla/SpinEventLoopUntil.h" +#include "mozilla/UntrustedModulesProcessor.h" +#include "mozilla/WinDllServices.h" +#include "nsContentUtils.h" +#include "nsDirectoryServiceDefs.h" +#include "TelemetryFixture.h" +#include "UntrustedModulesBackupService.h" +#include "UntrustedModulesDataSerializer.h" + +class ModuleLoadCounter final { + nsDataHashtable<nsStringCaseInsensitiveHashKey, int> mCounters; + + public: + template <int N> + ModuleLoadCounter(const nsString (&aNames)[N], const int (&aCounts)[N]) + : mCounters(N) { + for (int i = 0; i < N; ++i) { + mCounters.Put(aNames[i], aCounts[i]); + } + } + + template <int N> + bool Remains(const nsString (&aNames)[N], const int (&aCounts)[N]) { + EXPECT_EQ(mCounters.Count(), N); + if (mCounters.Count() != N) { + return false; + } + + bool result = true; + for (int i = 0; i < N; ++i) { + int* entry = mCounters.GetValue(aNames[i]); + if (!entry) { + wprintf(L"%s is not registered.\n", aNames[i].get()); + result = false; + } else if (*entry != aCounts[i]) { + // We can return false, but let's print out all unmet modules + // which may be helpful to investigate test failures. + wprintf(L"%s:%4d\n", aNames[i].get(), *entry); + result = false; + } + } + return result; + } + + bool IsDone() const { + bool allZero = true; + for (auto iter = mCounters.ConstIter(); !iter.Done(); iter.Next()) { + if (iter.Data() < 0) { + // If any counter is negative, we know the test fails. + // No need to continue. + return true; + } + if (iter.Data() > 0) { + allZero = false; + } + } + // If all counters are zero, the test finished nicely. Otherwise, those + // counters are expected to be decremented later. Let's continue. + return allZero; + } + + void Decrement(const nsString& aName) { + if (int* entry = mCounters.GetValue(aName)) { + --(*entry); + } + } +}; + +class UntrustedModulesCollector { + static constexpr int kMaximumPendingQueries = 200; + Vector<UntrustedModulesData> mData; + + public: + Vector<UntrustedModulesData>& Data() { return mData; } + + nsresult Collect(ModuleLoadCounter& aChecker) { + nsresult rv = NS_OK; + + mData.clear(); + int pendingQueries = 0; + + EXPECT_TRUE(SpinEventLoopUntil([this, &pendingQueries, &aChecker, &rv]() { + // Some of expected loaded modules are still missing + // after kMaximumPendingQueries queries were submitted. + // Giving up here to avoid an infinite loop. + if (pendingQueries >= kMaximumPendingQueries) { + rv = NS_ERROR_ABORT; + return true; + } + + ++pendingQueries; + + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->GetUntrustedModulesData()->Then( + GetMainThreadSerialEventTarget(), __func__, + [this, &pendingQueries, + &aChecker](Maybe<UntrustedModulesData>&& aResult) { + EXPECT_GT(pendingQueries, 0); + --pendingQueries; + + if (aResult.isSome()) { + wprintf(L"Received data. (pendingQueries=%d)\n", pendingQueries); + for (const auto& evt : aResult.ref().mEvents) { + aChecker.Decrement(evt.mRequestedDllName); + } + EXPECT_TRUE(mData.emplaceBack(std::move(aResult.ref()))); + } + }, + [&pendingQueries, &rv](nsresult aReason) { + EXPECT_GT(pendingQueries, 0); + --pendingQueries; + + wprintf(L"GetUntrustedModulesData() failed - %08x\n", aReason); + EXPECT_TRUE(false); + rv = aReason; + }); + + // Keep calling GetUntrustedModulesData() until we meet the condition. + return aChecker.IsDone(); + })); + + EXPECT_TRUE(SpinEventLoopUntil( + [&pendingQueries]() { return pendingQueries <= 0; })); + + return rv; + } +}; + +static void ValidateUntrustedModules(const UntrustedModulesData& aData) { + EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default); + EXPECT_EQ(aData.mPid, ::GetCurrentProcessId()); + + nsTHashtable<nsPtrHashKey<void>> moduleSet; + for (auto iter = aData.mModules.ConstIter(); !iter.Done(); iter.Next()) { + const RefPtr<ModuleRecord>& module = iter.Data(); + moduleSet.PutEntry(module); + } + + for (const auto& evt : aData.mEvents) { + EXPECT_EQ(evt.mThreadId, ::GetCurrentThreadId()); + // Make sure mModule is pointing to an entry of mModules. + EXPECT_TRUE(moduleSet.Contains(evt.mModule)); + EXPECT_FALSE(evt.mIsDependent); + EXPECT_EQ(evt.mLoadStatus, 0); + } + + // No check for the mXULLoadDurationMS field because the field has a value + // in CCov build GTest, but it is empty in non-CCov build (bug 1681936). + EXPECT_GT(aData.mEvents.length(), 0); + EXPECT_GT(aData.mStacks.GetModuleCount(), 0); + EXPECT_EQ(aData.mSanitizationFailures, 0); + EXPECT_EQ(aData.mTrustTestFailures, 0); +} + +class UntrustedModulesFixture : public TelemetryTestFixture { + static constexpr int kLoadCountBeforeDllServices = 5; + static constexpr int kLoadCountAfterDllServices = 5; + static constexpr uint32_t kMaxModulesArrayLen = 10; + + // One of the important test scenarios is to load modules before DllServices + // is initialized and to make sure those loading events are forwarded when + // DllServices is initialized. + // However, GTest instantiates a Fixture class every testcase and there is + // no way to re-enable DllServices and UntrustedModulesProcessor once it's + // disabled, which means no matter how many testcases we have, only the + // first testcase exercises that scenario. That's why we implement that + // test scenario in InitialModuleLoadOnce as a static member and runs it + // in the first testcase to be executed. + static INIT_ONCE sInitLoadOnce; + static UntrustedModulesCollector sInitLoadDataCollector; + + static nsString PrependWorkingDir(const nsAString& aLeaf) { + nsCOMPtr<nsIFile> file; + EXPECT_TRUE(NS_SUCCEEDED(NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, + getter_AddRefs(file)))); + EXPECT_TRUE(NS_SUCCEEDED(file->Append(aLeaf))); + bool exists; + EXPECT_TRUE(NS_SUCCEEDED(file->Exists(&exists)) && exists); + nsString fullPath; + EXPECT_TRUE(NS_SUCCEEDED(file->GetPath(fullPath))); + return fullPath; + } + + static BOOL CALLBACK InitialModuleLoadOnce(PINIT_ONCE, void*, void**); + + protected: + static constexpr int kInitLoadCount = + kLoadCountBeforeDllServices + kLoadCountAfterDllServices; + static const nsString kTestModules[]; + + static void LoadAndFree(const nsAString& aLeaf) { + nsModuleHandle dll(::LoadLibraryW(PrependWorkingDir(aLeaf).get())); + EXPECT_TRUE(!!dll); + } + + virtual void SetUp() override { + TelemetryTestFixture::SetUp(); + ::InitOnceExecuteOnce(&sInitLoadOnce, InitialModuleLoadOnce, nullptr, + nullptr); + } + + static const Vector<UntrustedModulesData>& GetInitLoadData() { + return sInitLoadDataCollector.Data(); + } + + // This method is useful if we want a new instance of UntrustedModulesData + // which is not copyable. + static UntrustedModulesData CollectSingleData() { + // If we call LoadAndFree more than once, those loading events are + // likely to be merged into an instance of UntrustedModulesData, + // meaning the length of the collector's vector is at least one but + // the exact number is unknown. + LoadAndFree(kTestModules[0]); + + UntrustedModulesCollector collector; + ModuleLoadCounter waitForOne({kTestModules[0]}, {1}); + EXPECT_TRUE(NS_SUCCEEDED(collector.Collect(waitForOne))); + EXPECT_TRUE(waitForOne.Remains({kTestModules[0]}, {0})); + EXPECT_EQ(collector.Data().length(), 1); + + // Cannot "return collector.Data()[0]" as copy ctor is deleted. + return UntrustedModulesData(std::move(collector.Data()[0])); + } + + template <typename DataFetcherT> + void ValidateJSValue(const char16_t* aPattern, size_t aPatternLength, + DataFetcherT&& aDataFetcher) { + AutoJSContextWithGlobal cx(mCleanGlobal); + mozilla::Telemetry::UntrustedModulesDataSerializer serializer( + cx.GetJSContext(), kMaxModulesArrayLen); + EXPECT_TRUE(!!serializer); + aDataFetcher(serializer); + + JS::RootedValue jsval(cx.GetJSContext()); + serializer.GetObject(&jsval); + + nsAutoString json; + EXPECT_TRUE(nsContentUtils::StringifyJSON(cx.GetJSContext(), &jsval, json)); + + JS::RootedObject re( + cx.GetJSContext(), + JS::NewUCRegExpObject(cx.GetJSContext(), aPattern, aPatternLength, + JS::RegExpFlag::Global)); + EXPECT_TRUE(!!re); + + JS::RootedValue matchResult(cx.GetJSContext(), JS::NullValue()); + size_t idx = 0; + EXPECT_TRUE(JS::ExecuteRegExpNoStatics(cx.GetJSContext(), re, json.get(), + json.Length(), &idx, true, + &matchResult)); + // On match, with aOnlyMatch = true, ExecuteRegExpNoStatics returns boolean + // true. If no match, ExecuteRegExpNoStatics returns Null. + EXPECT_TRUE(matchResult.isBoolean() && matchResult.toBoolean()); + if (!matchResult.toBoolean()) { + // If match failed, print out the actual JSON kindly. + wprintf(L"JSON: %s\n", json.get()); + wprintf(L"RE: %s\n", aPattern); + } + } +}; + +const nsString UntrustedModulesFixture::kTestModules[] = { + u"TestUntrustedModules_Dll1.dll"_ns, u"TestUntrustedModules_Dll2.dll"_ns}; +INIT_ONCE UntrustedModulesFixture::sInitLoadOnce = INIT_ONCE_STATIC_INIT; +UntrustedModulesCollector UntrustedModulesFixture::sInitLoadDataCollector; + +BOOL CALLBACK UntrustedModulesFixture::InitialModuleLoadOnce(PINIT_ONCE, void*, + void**) { + for (int i = 0; i < kLoadCountBeforeDllServices; ++i) { + for (const auto& mod : kTestModules) { + LoadAndFree(mod); + } + } + + RefPtr<DllServices> dllSvc(DllServices::Get()); + dllSvc->StartUntrustedModulesProcessor(); + + for (int i = 0; i < kLoadCountAfterDllServices; ++i) { + for (const auto& mod : kTestModules) { + LoadAndFree(mod); + } + } + + ModuleLoadCounter waitForTwo(kTestModules, {kInitLoadCount, kInitLoadCount}); + EXPECT_EQ(sInitLoadDataCollector.Collect(waitForTwo), NS_OK); + EXPECT_TRUE(waitForTwo.Remains(kTestModules, {0, 0})); + + for (const auto& event : GetInitLoadData()) { + ValidateUntrustedModules(event); + } + + // Data was removed when retrieved. No data is retrieved again. + UntrustedModulesCollector collector; + ModuleLoadCounter waitOnceForEach(kTestModules, {1, 1}); + EXPECT_EQ(collector.Collect(waitOnceForEach), NS_ERROR_ABORT); + EXPECT_TRUE(waitOnceForEach.Remains(kTestModules, {1, 1})); + + return TRUE; +} + +#define PROCESS_OBJ(TYPE, PID) \ + u"\"" TYPE u"\\." PID u"\":{" \ + u"\"processType\":\"" TYPE u"\",\"elapsed\":\\d+\\.\\d+," \ + u"\"sanitizationFailures\":0,\"trustTestFailures\":0," \ + u"\"events\":\\[{" \ + u"\"processUptimeMS\":\\d+,\"loadDurationMS\":\\d+\\.\\d+," \ + u"\"threadID\":\\d+,\"threadName\":\"Main Thread\"," \ + u"\"baseAddress\":\"0x[0-9a-f]+\",\"moduleIndex\":0," \ + u"\"isDependent\":false,\"loadStatus\":0}\\]," \ + u"\"combinedStacks\":{" \ + u"\"memoryMap\":\\[\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\"\\]" \ + u"(,\\[\"\\w+\\.\\w+\",\"[0-9A-Z]+\\\"\\])*\\]," \ + u"\"stacks\":\\[\\[\\[\\d+,\\d+\\]" \ + u"(,\\[\\d+,\\d+\\])*\\]\\]}}" + +TEST_F(UntrustedModulesFixture, Serialize) { + // clang-format off + const char16_t kPattern[] = u"{\"structVersion\":1," + u"\"modules\":\\[{" + u"\"resolvedDllName\":\"TestUntrustedModules_Dll1\\.dll\"," + u"\"fileVersion\":\"1\\.2\\.3\\.4\"," + u"\"companyName\":\"Mozilla Corporation\",\"trustFlags\":0}\\]," + u"\"processes\":{" + PROCESS_OBJ(u"browser", u"0xabc") u"," + PROCESS_OBJ(u"browser", u"0x4") u"," + PROCESS_OBJ(u"rdd", u"0x4") + u"}}"; + // clang-format on + + UntrustedModulesBackupData backup1, backup2; + { + UntrustedModulesData data1 = CollectSingleData(); + UntrustedModulesData data2 = CollectSingleData(); + UntrustedModulesData data3 = CollectSingleData(); + + data1.mPid = 0xabc; + data2.mPid = 0x4; + data2.mProcessType = GeckoProcessType_RDD; + data3.mPid = 0x4; + + backup1.Add(std::move(data1)); + backup2.Add(std::move(data2)); + backup1.Add(std::move(data3)); + } + + ValidateJSValue(kPattern, ArrayLength(kPattern) - 1, + [&backup1, &backup2]( + Telemetry::UntrustedModulesDataSerializer& aSerializer) { + EXPECT_TRUE(NS_SUCCEEDED(aSerializer.Add(backup1))); + EXPECT_TRUE(NS_SUCCEEDED(aSerializer.Add(backup2))); + }); +} + +TEST_F(UntrustedModulesFixture, Backup) { + using BackupType = UntrustedModulesBackupService::BackupType; + + RefPtr<UntrustedModulesBackupService> backupSvc( + UntrustedModulesBackupService::Get()); + for (int i = 0; i < 5; ++i) { + backupSvc->Backup(BackupType::Staging, CollectSingleData()); + } + + backupSvc->SettleAllStagingData(); + EXPECT_TRUE(backupSvc->Ref(BackupType::Staging).IsEmpty()); + + for (auto iter = backupSvc->Ref(BackupType::Settled).ConstIter(); + !iter.Done(); iter.Next()) { + const RefPtr<UntrustedModulesDataContainer>& container = iter.Data(); + EXPECT_TRUE(!!container); + const UntrustedModulesData& data = container->mData; + EXPECT_EQ(iter.Key(), ProcessHashKey(data.mProcessType, data.mPid)); + ValidateUntrustedModules(data); + } +} diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp new file mode 100644 index 0000000000..4f6ce877eb --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +#include <windows.h> + +BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; } diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc new file mode 100644 index 0000000000..2358b88b93 --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc @@ -0,0 +1,38 @@ +/* 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/. */
+
+#include <winver.h>
+
+/////////////////////////////////////////////////////////////////////////////
+//
+// Version
+//
+
+VS_VERSION_INFO VERSIONINFO
+ FILEVERSION 1,2,3,4 // This field will be collected
+ PRODUCTVERSION 5,6,7,8
+ FILEFLAGSMASK 0x3fL
+#ifdef _DEBUG
+ FILEFLAGS 0x1L
+#else
+ FILEFLAGS 0x0L
+#endif
+ FILEOS VOS__WINDOWS32
+ FILETYPE VFT_DLL
+ FILESUBTYPE 0x0L
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904e4"
+ BEGIN
+ VALUE "CompanyName", "Mozilla Corporation"
+ VALUE "OriginalFilename", "TestUntrustedModules_Dll1.dll"
+ VALUE "ProductName", "Test DLL"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build new file mode 100644 index 0000000000..57fc59ca8a --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build @@ -0,0 +1,17 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +DIST_INSTALL = False + +SharedLibrary("TestUntrustedModules_Dll1") + +UNIFIED_SOURCES = [ + "TestUntrustedModules_Dll1.cpp", +] + +RCFILE = "TestUntrustedModules_Dll1.rc" + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll1.dll"] diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp new file mode 100644 index 0000000000..4f6ce877eb --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp @@ -0,0 +1,7 @@ +/* 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/. */ + +#include <windows.h> + +BOOL WINAPI DllMain(HINSTANCE, DWORD, LPVOID) { return TRUE; } diff --git a/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build new file mode 100644 index 0000000000..fcefe41329 --- /dev/null +++ b/toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build @@ -0,0 +1,15 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# 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/. + +DIST_INSTALL = False + +SharedLibrary("TestUntrustedModules_Dll2") + +UNIFIED_SOURCES = [ + "TestUntrustedModules_Dll2.cpp", +] + +if CONFIG["COMPILE_ENVIRONMENT"]: + TEST_HARNESS_FILES.gtest += ["!TestUntrustedModules_Dll2.dll"] diff --git a/toolkit/xre/test/gtest/moz.build b/toolkit/xre/test/gtest/moz.build new file mode 100644 index 0000000000..e98d2deea8 --- /dev/null +++ b/toolkit/xre/test/gtest/moz.build @@ -0,0 +1,31 @@ +# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +Library("xretest") + +UNIFIED_SOURCES = [ + "TestCompatVersionCompare.cpp", +] + +include("/ipc/chromium/chromium-config.mozbuild") + +LOCAL_INCLUDES += [ + "/toolkit/components/remote", + "/toolkit/components/telemetry/other", + "/toolkit/components/telemetry/tests/gtest", +] + +if CONFIG["OS_TARGET"] == "WINNT": + UNIFIED_SOURCES += [ + "TestAssembleCommandLineWin.cpp", + "TestUntrustedModules.cpp", + ] + TEST_DIRS += [ + "TestUntrustedModules_Dll1", + "TestUntrustedModules_Dll2", + ] + +FINAL_LIBRARY = "xul-gtest" diff --git a/toolkit/xre/test/marionette/marionette.ini b/toolkit/xre/test/marionette/marionette.ini new file mode 100644 index 0000000000..f4beae89ea --- /dev/null +++ b/toolkit/xre/test/marionette/marionette.ini @@ -0,0 +1,2 @@ +[test_fission_autostart.py] +[test_exitcode.py] diff --git a/toolkit/xre/test/marionette/test_exitcode.py b/toolkit/xre/test/marionette/test_exitcode.py new file mode 100644 index 0000000000..32fd3d2bc0 --- /dev/null +++ b/toolkit/xre/test/marionette/test_exitcode.py @@ -0,0 +1,33 @@ +from marionette_harness import MarionetteTestCase + + +class TestFissionAutostart(MarionetteTestCase): + def test_normal_exit(self): + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + + def call_quit(): + self.marionette.execute_script( + """ + const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit); + """, + sandbox="system", + ) + + self.marionette.quit(in_app=True, callback=call_quit) + self.assertEqual(self.marionette.instance.runner.returncode, 0) + + def test_exit_code(self): + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + + def call_quit(): + self.marionette.execute_script( + """ + const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); + Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit, 5); + """, + sandbox="system", + ) + + self.marionette.quit(in_app=True, callback=call_quit) + self.assertEqual(self.marionette.instance.runner.returncode, 5) diff --git a/toolkit/xre/test/marionette/test_fission_autostart.py b/toolkit/xre/test/marionette/test_fission_autostart.py new file mode 100644 index 0000000000..b448307d2a --- /dev/null +++ b/toolkit/xre/test/marionette/test_fission_autostart.py @@ -0,0 +1,416 @@ +from __future__ import absolute_import, print_function + +from marionette_harness import MarionetteTestCase +from contextlib import contextmanager + + +class ExperimentStatus: + UNENROLLED = 0 + ENROLLED_CONTROL = 1 + ENROLLED_TREATMENT = 2 + DISQUALIFIED = 3 + + +class Prefs: + ENROLLMENT_STATUS = "fission.experiment.enrollmentStatus" + STARTUP_ENROLLMENT_STATUS = "fission.experiment.startupEnrollmentStatus" + FISSION_AUTOSTART = "fission.autostart" + FISSION_AUTOSTART_SESSION = "fission.autostart.session" + + +ENV_ENABLE_FISSION = "MOZ_FORCE_ENABLE_FISSION" +ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S" + + +DECISION_STATUS = { + "experimentControl": 1, + "experimentTreatment": 2, + "disabledByE10sEnv": 3, + "enabledByEnv": 4, + "disabledBySafeMode": 5, + "enabledByDefault": 6, + "disabledByDefault": 7, + "enabledByUserPref": 8, + "disabledByUserPref": 9, + "disabledByE10sOther": 10, +} + + +class TestFissionAutostart(MarionetteTestCase): + SANDBOX_NAME = "fission-autostart" + + def execute_script(self, code, *args, **kwargs): + with self.marionette.using_context(self.marionette.CONTEXT_CHROME): + return self.marionette.execute_script( + code, new_sandbox=False, sandbox=self.SANDBOX_NAME, *args, **kwargs + ) + + def get_fission_status(self): + return self.execute_script( + r""" + let win = Services.wm.getMostRecentWindow("navigator:browser"); + return { + fissionAutostart: Services.appinfo.fissionAutostart, + fissionExperimentStatus: Services.appinfo.fissionExperimentStatus, + decisionStatus: Services.appinfo.fissionDecisionStatus, + decisionStatusString: Services.appinfo.fissionDecisionStatusString, + useRemoteSubframes: win.docShell.nsILoadContext.useRemoteSubframes, + fissionAutostartSession: Services.prefs.getBoolPref("fission.autostart.session"), + dynamicFissionAutostart: Services.prefs.getBoolPref("fission.autostart"), + }; + """ + ) + + def check_fission_status(self, enabled, experiment, decision, dynamic=None): + if dynamic is None: + dynamic = enabled + + expected = { + "fissionAutostart": enabled, + "fissionExperimentStatus": experiment, + "decisionStatus": DECISION_STATUS[decision], + "decisionStatusString": decision, + "useRemoteSubframes": enabled, + "fissionAutostartSession": enabled, + "dynamicFissionAutostart": dynamic, + } + + status = self.get_fission_status() + + for prop, value in expected.items(): + self.assertEqual( + status[prop], + value, + "%s should have the value `%r`, but has `%r`" + % (prop, value, status[prop]), + ) + + def check_pref_locked(self): + PREF = Prefs.FISSION_AUTOSTART + + if PREF in self.marionette.instance.required_prefs: + return True + + res = self.execute_script( + r""" + const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" + ); + return { + prefLocked: Services.prefs.prefIsLocked(arguments[0]), + releaseOrBeta: AppConstants.RELEASE_OR_BETA, + }; + """, + script_args=(PREF,), + ) + + if res["prefLocked"]: + self.assertTrue( + res["releaseOrBeta"], "Preference should only be locked on release/beta" + ) + return True + return False + + def set_env(self, env, value): + self.execute_script( + "env.set(arguments[0], arguments[1]);", script_args=(env, value) + ) + + def get_env(self, env): + return self.execute_script("return env.get(arguments[0]);", script_args=(env,)) + + def set_enrollment_status(self, status): + self.marionette.set_pref(Prefs.ENROLLMENT_STATUS, status, default_branch=True) + + startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS) + self.assertEqual( + startup_status, + status, + "Startup enrollment status (%r) should match new " + "session status (%r)" % (startup_status, status), + ) + + def restart(self, prefs=None, env=None): + if prefs: + self.marionette.set_prefs(prefs) + + if env: + for name, value in env.items(): + self.set_env(name, value) + + self.marionette.restart(in_app=True, clean=False) + self.setUpSession() + + # Sanity check our environment. + if prefs: + for key, val in prefs.items(): + if val is not None: + self.assertEqual(self.marionette.get_pref(key), val) + if env: + for key, val in env.items(): + self.assertEqual(self.get_env(key), val or "") + + def setUpSession(self): + self.marionette.set_context(self.marionette.CONTEXT_CHROME) + + self.execute_script( + r""" + // We're running in a function, in a sandbox, that inherits from an + // X-ray wrapped window. Anything we want to be globally available + // needs to be defined on that window. + ChromeUtils.import("resource://gre/modules/Services.jsm", window); + window.env = Cc["@mozilla.org/process/environment;1"] + .getService(Ci.nsIEnvironment); + """ + ) + + @contextmanager + def full_restart(self): + profile = self.marionette.instance.profile + try: + self.marionette.quit(in_app=True, clean=False) + yield profile + finally: + self.marionette.start_session() + self.setUpSession() + + def setUp(self): + super(TestFissionAutostart, self).setUp() + + self.setUpSession() + + def tearDown(self): + self.marionette.restart(clean=True) + + super(TestFissionAutostart, self).tearDown() + + def test_runtime_changes(self): + """Tests that changes to preferences during runtime do not have any + effect on the current session.""" + + if self.check_pref_locked(): + # Need to be able to flip Fission prefs for this test to work. + return + + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.UNENROLLED, + decision="disabledByDefault", + ) + + self.restart(prefs={Prefs.FISSION_AUTOSTART: True}) + + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByUserPref", + ) + + self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByUserPref", + ) + + self.marionette.set_pref(Prefs.FISSION_AUTOSTART, False) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByUserPref", + dynamic=False, + ) + + self.marionette.clear_pref(Prefs.FISSION_AUTOSTART) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByUserPref", + dynamic=False, + ) + + self.restart() + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.ENROLLED_CONTROL, + decision="experimentControl", + ) + + self.marionette.set_pref( + Prefs.ENROLLMENT_STATUS, ExperimentStatus.UNENROLLED, default_branch=True + ) + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.ENROLLED_CONTROL, + decision="experimentControl", + ) + + self.set_env(ENV_ENABLE_FISSION, "1") + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.ENROLLED_CONTROL, + decision="experimentControl", + ) + + def test_fission_precedence(self): + if self.check_pref_locked(): + # Need to be able to flip Fission prefs for this test to work. + return + + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.UNENROLLED, + decision="disabledByDefault", + ) + + self.restart( + prefs={Prefs.FISSION_AUTOSTART: False}, env={ENV_ENABLE_FISSION: "1"} + ) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByEnv", + dynamic=False, + ) + + self.restart( + prefs={Prefs.FISSION_AUTOSTART: True}, env={ENV_ENABLE_FISSION: ""} + ) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.UNENROLLED, + decision="enabledByUserPref", + ) + + self.restart(prefs={Prefs.FISSION_AUTOSTART: None}) + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.UNENROLLED, + decision="disabledByDefault", + ) + + self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT) + self.restart() + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.ENROLLED_TREATMENT, + decision="experimentTreatment", + ) + + self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL) + self.restart() + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.ENROLLED_CONTROL, + decision="experimentControl", + ) + + self.marionette.set_pref(Prefs.FISSION_AUTOSTART, True) + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.ENROLLED_CONTROL, + decision="experimentControl", + dynamic=True, + ) + + self.assertEqual( + self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), + ExperimentStatus.DISQUALIFIED, + "Setting fission.autostart should disqualify", + ) + + self.restart() + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.DISQUALIFIED, + decision="enabledByUserPref", + ) + + app_version = self.execute_script("return Services.appinfo.version") + self.restart(env={ENV_DISABLE_E10S: app_version}) + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.DISQUALIFIED, + decision="disabledByE10sEnv", + dynamic=True, + ) + + def test_fission_startup(self): + if self.check_pref_locked(): + # Need to be able to flip Fission prefs for this test to work. + return + + # Starting the browser with STARTUP_ENROLLMENT_STATUS set to treatment + # should make the current session run under treatment. + with self.full_restart() as profile: + profile.set_preferences( + { + Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT, + }, + filename="prefs.js", + ) + + self.assertEqual( + self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), + ExperimentStatus.UNENROLLED, + "Dynamic pref should be unenrolled", + ) + self.assertEqual( + self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), + ExperimentStatus.ENROLLED_TREATMENT, + "Startup pref should be in treatment", + ) + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.ENROLLED_TREATMENT, + decision="experimentTreatment", + ) + + # If normandy doesn't re-set `ENROLLMENT_STATUS` during the session, it + # should be cleared back to disabled after a restart. + self.marionette.restart(in_app=True, clean=False) + + self.assertEqual( + self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), + ExperimentStatus.UNENROLLED, + "Should unenroll dynamic pref after shutdown", + ) + self.assertEqual( + self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), + ExperimentStatus.UNENROLLED, + "Should unenroll startup pref after shutdown", + ) + self.check_fission_status( + enabled=False, + experiment=ExperimentStatus.UNENROLLED, + decision="disabledByDefault", + ) + + # If the browser is started with a customized `fisison.autostart`, + # while also enrolled in an experiment, the experiment should be + # disqualified at startup. + with self.full_restart() as profile: + profile.set_preferences( + { + Prefs.FISSION_AUTOSTART: True, + Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT, + }, + filename="prefs.js", + ) + + self.assertEqual( + self.marionette.get_pref(Prefs.ENROLLMENT_STATUS), + ExperimentStatus.DISQUALIFIED, + "Should disqualify dynamic pref on startup", + ) + self.assertEqual( + self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS), + ExperimentStatus.DISQUALIFIED, + "Should disqualify startup pref on startup", + ) + + self.check_fission_status( + enabled=True, + experiment=ExperimentStatus.DISQUALIFIED, + decision="enabledByUserPref", + ) diff --git a/toolkit/xre/test/mochitest.ini b/toolkit/xre/test/mochitest.ini new file mode 100644 index 0000000000..ccbb08ed97 --- /dev/null +++ b/toolkit/xre/test/mochitest.ini @@ -0,0 +1,3 @@ +[DEFAULT] + +[test_fpuhandler.html] diff --git a/toolkit/xre/test/show_hash.js b/toolkit/xre/test/show_hash.js new file mode 100644 index 0000000000..28674e6840 --- /dev/null +++ b/toolkit/xre/test/show_hash.js @@ -0,0 +1,4 @@ +const xre = Cc["@mozilla.org/xre/directory-provider;1"].getService( + Ci.nsIXREDirProvider +); +dump(`${xre.getInstallHash(false)}\n`); diff --git a/toolkit/xre/test/test_fpuhandler.html b/toolkit/xre/test/test_fpuhandler.html new file mode 100644 index 0000000000..a3869f2ecf --- /dev/null +++ b/toolkit/xre/test/test_fpuhandler.html @@ -0,0 +1,36 @@ +<head> + <title>Floating-point exception handler test</title> + <script src="/tests/SimpleTest/SimpleTest.js"></script> + +<body onload="runTest()"> + <embed id="plugin1" type="application/x-test" width="400" height="400"></embed> + + <script class="testbody" type="application/javascript"> + SimpleTest.waitForExplicitFinish(); + + function doDiv(x, y) { + var z; + z = x / y; + + for (let i = 0 + x; i < 1000; ++i) + z = y / x; + + z = x / y; + return z; + } + + function runTest() { + ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash"); + + try { + document.getElementById("plugin1").enableFPExceptions(); + } catch (e) { + ok(true, "No special code to set the FPU bit in the testplugin."); + SimpleTest.finish(); + return; + } + + ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash again."); + SimpleTest.finish(); + } + </script> diff --git a/toolkit/xre/test/test_install_hash.js b/toolkit/xre/test/test_install_hash.js new file mode 100644 index 0000000000..3b97640e4c --- /dev/null +++ b/toolkit/xre/test/test_install_hash.js @@ -0,0 +1,139 @@ +/* Any copyright is dedicated to the Public Domain. + http://creativecommons.org/publicdomain/zero/1.0/ */ + +/** + * This runs the xpcshell binary with different cases for the executable path. + * They should all result in the same installation hash. + */ + +const { Services } = ChromeUtils.import("resource://gre/modules/Services.jsm"); +const { Subprocess } = ChromeUtils.import( + "resource://gre/modules/Subprocess.jsm" +); + +const XRE = Cc["@mozilla.org/xre/directory-provider;1"].getService( + Ci.nsIXREDirProvider +); +const HASH = XRE.getInstallHash(false); +const EXE = Services.dirsvc.get("XREExeF", Ci.nsIFile); +const SCRIPT = do_get_file("show_hash.js", false); + +async function getHash(bin) { + try { + let proc = await Subprocess.call({ + command: bin.path, + arguments: [SCRIPT.path], + }); + + let result = ""; + let string; + while ((string = await proc.stdout.readString())) { + result += string; + } + + return result.trim(); + } catch (e) { + if (e.errorCode == Subprocess.ERROR_BAD_EXECUTABLE) { + return null; + } + throw e; + } +} + +// Walks through a path's entries and calls a mutator function to change the +// case of each. +function mutatePath(path, mutator) { + let parts = []; + let index = 0; + while (path.parent != null) { + parts.push(mutator(path.leafName, index++)); + path = path.parent; + } + + while (parts.length) { + path.append(parts.pop()); + } + + return path; +} + +// Counts how many path parts a mutator will be called for. +function countParts(path) { + let index = 0; + while (path.parent != null) { + path = path.parent; + index++; + } + return index; +} + +add_task(async function testSameBinary() { + // Running with the same binary path should definitely work and give the same + // hash. + Assert.equal( + await getHash(EXE), + HASH, + "Should have the same hash when running the same binary." + ); +}); + +add_task(async function testUpperCase() { + let upper = mutatePath(EXE, p => p.toLocaleUpperCase()); + let hash = await getHash(upper); + + // We may not get a hash if any part of the filesystem is case sensitive. + if (hash) { + Assert.equal( + hash, + HASH, + `Should have seen the same hash from ${upper.path}.` + ); + } +}); + +add_task(async function testLowerCase() { + let lower = mutatePath(EXE, p => p.toLocaleLowerCase()); + let hash = await getHash(lower); + + // We may not get a hash if any part of the filesystem is case sensitive. + if (hash) { + Assert.equal( + hash, + HASH, + `Should have seen the same hash from ${lower.path}.` + ); + } +}); + +add_task(async function testEachPart() { + // We need to check the case where only some of the directories in the path + // are case insensitive. + + let count = countParts(EXE); + for (let i = 0; i < count; i++) { + let upper = mutatePath(EXE, (p, index) => + index == i ? p.toLocaleUpperCase() : p + ); + let lower = mutatePath(EXE, (p, index) => + index == i ? p.toLocaleLowerCase() : p + ); + + let upperHash = await getHash(upper); + if (upperHash) { + Assert.equal( + upperHash, + HASH, + `Should have seen the same hash from ${upper.path}.` + ); + } + + let lowerHash = await getHash(lower); + if (lowerHash) { + Assert.equal( + lowerHash, + HASH, + `Should have seen the same hash from ${lower.path}.` + ); + } + } +}); diff --git a/toolkit/xre/test/test_launch_without_hang.js b/toolkit/xre/test/test_launch_without_hang.js new file mode 100644 index 0000000000..5a26382eda --- /dev/null +++ b/toolkit/xre/test/test_launch_without_hang.js @@ -0,0 +1,262 @@ +// 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/. + +// bug 1360493 +// Launch the browser a number of times, testing startup hangs. + +"use strict"; + +const Cm = Components.manager; + +ChromeUtils.import("resource://gre/modules/Services.jsm", this); +const { AppConstants } = ChromeUtils.import( + "resource://gre/modules/AppConstants.jsm" +); + +const APP_TIMER_TIMEOUT_MS = 1000 * 15; +const TRY_COUNT = 50; + +// Sets a group of environment variables, returning the old values. +// newVals AND return value is an array of { key: "", value: "" } +function setEnvironmentVariables(newVals) { + let oldVals = []; + let env = Cc["@mozilla.org/process/environment;1"].getService( + Ci.nsIEnvironment + ); + for (let i = 0; i < newVals.length; ++i) { + let key = newVals[i].key; + let value = newVals[i].value; + let oldObj = { key }; + if (env.exists(key)) { + oldObj.value = env.get(key); + } else { + oldObj.value = null; + } + + env.set(key, value); + oldVals.push(oldObj); + } + return oldVals; +} + +function getFirefoxExecutableFilename() { + if (AppConstants.platform === "win") { + return AppConstants.MOZ_APP_NAME + ".exe"; + } + return AppConstants.MOZ_APP_NAME; +} + +// Returns a nsIFile to the firefox.exe executable file +function getFirefoxExecutableFile() { + let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file = Services.dirsvc.get("GreBinD", Ci.nsIFile); + + file.append(getFirefoxExecutableFilename()); + return file; +} + +// Takes an executable and arguments, and wraps it in a call to the system shell. +// Technique adapted from \toolkit\mozapps\update\tests\unit_service_updater\xpcshellUtilsAUS.js +// to avoid child process console output polluting the xpcshell log. +// returns { file: (nsIFile), args: [] } +function wrapLaunchInShell(file, args) { + let ret = {}; + + if (AppConstants.platform === "win") { + ret.file = Services.dirsvc.get("WinD", Ci.nsIFile); + ret.file.append("System32"); + ret.file.append("cmd.exe"); + ret.args = ["/D", "/Q", "/C", file.path].concat(args).concat([">nul"]); + } else { + ret.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + ret.file.initWithPath("/usr/bin/env"); + ret.args = [file.path].concat(args).concat(["> /dev/null"]); + } + + Assert.ok( + ret.file.exists(), + "Executable file should exist: " + ret.file.path + ); + + return ret; +} + +// Needed because process.kill() kills the console, not its child process, firefox. +function terminateFirefox(completion) { + let executableName = getFirefoxExecutableFilename(); + let file; + let args; + + if (AppConstants.platform === "win") { + file = Services.dirsvc.get("WinD", Ci.nsIFile); + file.append("System32"); + file.append("taskkill.exe"); + args = ["/F", "/IM", executableName]; + } else { + file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile); + file.initWithPath("/usr/bin/killall"); + args = [executableName]; + } + + info("launching application: " + file.path); + info(" with args: " + args.join(" ")); + + let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess); + process.init(file); + + let processObserver = { + observe: function PO_observe(aSubject, aTopic, aData) { + info("topic: " + aTopic + ", process exitValue: " + process.exitValue); + + Assert.equal( + process.exitValue, + 0, + "Terminate firefox process exit value should be 0" + ); + Assert.equal( + aTopic, + "process-finished", + "Terminate firefox observer topic should be process-finished" + ); + + if (completion) { + completion(); + } + }, + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + }; + + process.runAsync(args, args.length, processObserver); + + info(" with pid: " + process.pid); +} + +// Launches file with args asynchronously, failing if the process did not +// exit within timeoutMS milliseconds. If a timeout occurs, handler() +// is called. +function launchProcess(file, args, env, timeoutMS, handler, attemptCount) { + let state = {}; + + state.attempt = attemptCount; + + state.processObserver = { + observe: function PO_observe(aSubject, aTopic, aData) { + if (!state.appTimer) { + // the app timer has been canceled; this process has timed out already so don't process further. + handler(false); + return; + } + + info( + "topic: " + aTopic + ", process exitValue: " + state.process.exitValue + ); + + info("Restoring environment variables"); + setEnvironmentVariables(state.oldEnv); + + state.appTimer.cancel(); + state.appTimer = null; + + Assert.equal( + state.process.exitValue, + 0, + "the application process exit value should be 0" + ); + Assert.equal( + aTopic, + "process-finished", + "the application process observer topic should be process-finished" + ); + + handler(true); + }, + QueryInterface: ChromeUtils.generateQI(["nsIObserver"]), + }; + + // The timer callback to kill the process if it takes too long. + state.appTimerCallback = { + notify: function TC_notify(aTimer) { + state.appTimer = null; + + info("Restoring environment variables"); + setEnvironmentVariables(state.oldEnv); + + if (state.process.isRunning) { + info("attempting to kill process"); + + // This will cause the shell process to exit as well, triggering our process observer. + terminateFirefox(function terminateFirefoxCompletion() { + Assert.ok(false, "Launch application timer expired"); + }); + } + }, + QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]), + }; + + info("launching application: " + file.path); + info(" with args: " + args.join(" ")); + info(" with environment: "); + for (let i = 0; i < env.length; ++i) { + info(" " + env[i].key + "=" + env[i].value); + } + + state.process = Cc["@mozilla.org/process/util;1"].createInstance( + Ci.nsIProcess + ); + state.process.init(file); + + state.appTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer); + state.appTimer.initWithCallback( + state.appTimerCallback, + timeoutMS, + Ci.nsITimer.TYPE_ONE_SHOT + ); + + state.oldEnv = setEnvironmentVariables(env); + + state.process.runAsync(args, args.length, state.processObserver); + + info(" with pid: " + state.process.pid); +} + +function run_test() { + do_test_pending(); + + let env = [ + { key: "MOZ_CRASHREPORTER_DISABLE", value: null }, + { key: "MOZ_CRASHREPORTER", value: "1" }, + { key: "MOZ_CRASHREPORTER_NO_REPORT", value: "1" }, + { key: "MOZ_CRASHREPORTER_SHUTDOWN", value: "1" }, + { key: "XPCOM_DEBUG_BREAK", value: "stack-and-abort" }, + ]; + + let triesStarted = 1; + + let handler = function launchFirefoxHandler(okToContinue) { + triesStarted++; + if (triesStarted <= TRY_COUNT && okToContinue) { + testTry(); + } else { + do_test_finished(); + } + }; + + let testTry = function testTry() { + let shell = wrapLaunchInShell(getFirefoxExecutableFile(), [ + "-no-remote", + "-test-launch-without-hang", + ]); + info("Try attempt #" + triesStarted); + launchProcess( + shell.file, + shell.args, + env, + APP_TIMER_TIMEOUT_MS, + handler, + triesStarted + ); + }; + + testTry(); +} diff --git a/toolkit/xre/test/win/Makefile.in b/toolkit/xre/test/win/Makefile.in new file mode 100644 index 0000000000..5a67b8eac8 --- /dev/null +++ b/toolkit/xre/test/win/Makefile.in @@ -0,0 +1,11 @@ +# 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/. + +MOZ_WINCONSOLE = 1 + +include $(topsrcdir)/config/rules.mk + +check:: + @echo 'Running TestXREMakeCommandLineWin tests' + @$(RUN_TEST_PROGRAM) $(FINAL_TARGET)/TestXREMakeCommandLineWin.exe diff --git a/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp new file mode 100644 index 0000000000..273c1cb72a --- /dev/null +++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp @@ -0,0 +1,779 @@ +/* -*- 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 https://mozilla.org/MPL/2.0/. */ + +#define MOZ_USE_LAUNCHER_ERROR + +#include "mozilla/LauncherRegistryInfo.h" +#include "mozilla/NativeNt.h" +#include "mozilla/ScopeExit.h" +#include "mozilla/Unused.h" +#include "nsWindowsHelpers.h" + +#include "LauncherRegistryInfo.cpp" + +#include <string> + +static const char kMsgStart[] = "TEST-FAILED | LauncherRegistryInfo | "; + +static const wchar_t kRegKeyPath[] = L"SOFTWARE\\" EXPAND_STRING_MACRO( + MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(MOZ_APP_BASENAME) L"\\Launcher"; +static const wchar_t kBrowserSuffix[] = L"|Browser"; +static const wchar_t kLauncherSuffix[] = L"|Launcher"; +static const wchar_t kImageSuffix[] = L"|Image"; +static const wchar_t kTelemetrySuffix[] = L"|Telemetry"; + +static std::wstring gBrowserValue; +static std::wstring gLauncherValue; +static std::wstring gImageValue; +static std::wstring gTelemetryValue; + +static DWORD gMyImageTimestamp; + +#define RUN_TEST(result, fn) \ + if ((result = fn()).isErr()) { \ + const mozilla::LauncherError& err = result.inspectErr(); \ + printf("%s%s | %08lx (%s:%d)\n", kMsgStart, #fn, err.mError.AsHResult(), \ + err.mFile, err.mLine); \ + return 1; \ + } + +#define EXPECT_COMMIT_IS_OK() \ + do { \ + mozilla::LauncherVoidResult vr2 = info.Commit(); \ + if (vr2.isErr()) { \ + return vr2; \ + } \ + } while (0) + +#define EXPECT_CHECK_RESULT_IS(desired, expected) \ + do { \ + mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> \ + result = info.Check(mozilla::LauncherRegistryInfo::desired); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.unwrap() != mozilla::LauncherRegistryInfo::expected) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL); \ + } \ + } while (0) + +#define EXPECT_ENABLED_STATE_IS(expected) \ + do { \ + mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> \ + enabled = info.IsEnabled(); \ + if (enabled.isErr()) { \ + return enabled.propagateErr(); \ + } \ + if (enabled.unwrap() != mozilla::LauncherRegistryInfo::expected) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_TELEMETRY_IS_ENABLED(expected) \ + do { \ + mozilla::LauncherResult<bool> enabled = info.IsTelemetryEnabled(); \ + if (enabled.isErr()) { \ + return enabled.propagateErr(); \ + } \ + if (enabled.unwrap() != expected) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_REG_DWORD_EXISTS_AND_EQ(name, expected) \ + do { \ + mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \ + ReadRegistryValueData<DWORD>(name, REG_DWORD); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.inspect().isNothing() || \ + result.inspect().value() != expected) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_REG_QWORD_EXISTS(name) \ + do { \ + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \ + ReadRegistryValueData<uint64_t>(name, REG_QWORD); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.inspect().isNothing()) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_REG_QWORD_EXISTS_AND_EQ(name, expected) \ + do { \ + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \ + ReadRegistryValueData<uint64_t>(name, REG_QWORD); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.inspect().isNothing() || \ + result.inspect().value() != expected) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_REG_DWORD_DOES_NOT_EXIST(name) \ + do { \ + mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \ + ReadRegistryValueData<DWORD>(name, REG_DWORD); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.inspect().isSome()) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +#define EXPECT_REG_QWORD_DOES_NOT_EXIST(name) \ + do { \ + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \ + ReadRegistryValueData<uint64_t>(name, REG_QWORD); \ + if (result.isErr()) { \ + return result.propagateErr(); \ + } \ + if (result.inspect().isSome()) { \ + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \ + } \ + } while (0) + +template <typename T> +static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData( + const std::wstring& name, DWORD expectedType) { + T data; + DWORD dataLen = sizeof(data); + DWORD type; + LSTATUS status = ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str(), + RRF_RT_ANY, &type, &data, &dataLen); + if (status == ERROR_FILE_NOT_FOUND) { + return mozilla::Maybe<T>(); + } + + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + if (type != expectedType) { + return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH); + } + + return mozilla::Some(data); +} + +template <typename T> +static mozilla::LauncherVoidResult WriteRegistryValueData( + const std::wstring& name, DWORD type, T data) { + LSTATUS status = ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, + name.c_str(), type, &data, sizeof(T)); + if (status != ERROR_SUCCESS) { + return LAUNCHER_ERROR_FROM_WIN32(status); + } + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult DeleteRegistryValueData( + const std::wstring& name) { + LSTATUS status = + ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str()); + if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) { + return mozilla::Ok(); + } + + return LAUNCHER_ERROR_FROM_WIN32(status); +} + +static mozilla::LauncherVoidResult DeleteAllRegstryValues() { + // Unblock commit via ReflectPrefToRegistry + // (We need to set false, and then true to bypass the early return) + mozilla::LauncherRegistryInfo info; + mozilla::LauncherVoidResult vr = info.ReflectPrefToRegistry(false); + vr = info.ReflectPrefToRegistry(true); + if (vr.isErr()) { + return vr; + } + + vr = DeleteRegistryValueData(gImageValue); + if (vr.isErr()) { + return vr; + } + + vr = DeleteRegistryValueData(gLauncherValue); + if (vr.isErr()) { + return vr; + } + + vr = DeleteRegistryValueData(gBrowserValue); + if (vr.isErr()) { + return vr; + } + + return DeleteRegistryValueData(gTelemetryValue); +} + +static mozilla::LauncherVoidResult SetupEnabledScenario() { + // Reset the registry state to an enabled state. First, we delete all existing + // registry values (if any). + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + + // Now we run Check(Launcher)... + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + EXPECT_COMMIT_IS_OK(); + // ...and Check(Browser) + EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + // By this point we are considered to be fully enabled. + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestEmptyRegistry() { + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + EXPECT_COMMIT_IS_OK(); + + // LauncherRegistryInfo should have created Launcher and Image values + EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp); + EXPECT_REG_QWORD_EXISTS(gLauncherValue); + EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue); + + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestNormal() { + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, QPCNowRaw()); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + // Make sure the browser timestamp is newer than the launcher's + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs = + ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD); + if (launcherTs.isErr()) { + return launcherTs.propagateErr(); + } + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserTs = + ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD); + if (browserTs.isErr()) { + return browserTs.propagateErr(); + } + if (launcherTs.inspect().isNothing() || browserTs.inspect().isNothing() || + browserTs.inspect().value() <= launcherTs.inspect().value()) { + return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL); + } + + EXPECT_ENABLED_STATE_IS(EnabledState::Enabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestBrowserNoLauncher() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + vr = DeleteRegistryValueData(gLauncherValue); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + // Verify that we still don't have a launcher timestamp + EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue); + // Verify that the browser timestamp is now zero + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL); + + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestLauncherNoBrowser() { + constexpr uint64_t launcherTs = 0x77777777; + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + // Launcher's timestamps is kept intact while browser's is set to 0. + EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL); + + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestBrowserLessThanLauncher() { + constexpr uint64_t launcherTs = 0x77777777, browserTs = 0x66666666; + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, browserTs); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + // Launcher's timestamps is kept intact while browser's is set to 0. + EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL); + + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestImageTimestampChange() { + // This should reset the timestamps and then essentially run like + // TestEmptyRegistry + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, 1ULL); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 2ULL); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + EXPECT_COMMIT_IS_OK(); + + EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp); + EXPECT_REG_QWORD_EXISTS(gLauncherValue); + EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestImageTimestampChangeWhenDisabled() { + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678); + if (vr.isErr()) { + return vr; + } + vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 0ULL); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + + EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp); + EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0); + + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestDisableDueToFailure() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + // Check that we are indeed enabled. + mozilla::LauncherRegistryInfo info; + EXPECT_ENABLED_STATE_IS(EnabledState::Enabled); + + // Now call DisableDueToFailure + mozilla::LauncherVoidResult lvr = info.DisableDueToFailure(); + if (lvr.isErr()) { + return lvr.propagateErr(); + } + + // We should now be FailDisabled + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + // If we delete the launcher timestamp, IsEnabled should then return + // ForceDisabled. + vr = DeleteRegistryValueData(gLauncherValue); + if (vr.isErr()) { + return vr; + } + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestPrefReflection() { + // Reset the registry to a known good state. + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + // Let's see what happens when we flip the pref to OFF. + mozilla::LauncherRegistryInfo info; + mozilla::LauncherVoidResult reflectOk = info.ReflectPrefToRegistry(false); + if (reflectOk.isErr()) { + return reflectOk.propagateErr(); + } + + // Launcher timestamp should be non-existent. + EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue); + // Browser timestamp should be zero + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL); + // IsEnabled should give us ForceDisabled + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + // Now test to see what happens when the pref is set to ON. + reflectOk = info.ReflectPrefToRegistry(true); + if (reflectOk.isErr()) { + return reflectOk.propagateErr(); + } + + // Launcher and browser timestamps should be non-existent. + EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue); + EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue); + + // IsEnabled should give us Enabled. + EXPECT_ENABLED_STATE_IS(EnabledState::Enabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestTelemetryConfig() { + mozilla::LauncherVoidResult vr = DeleteAllRegstryValues(); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_TELEMETRY_IS_ENABLED(false); + + mozilla::LauncherVoidResult reflectOk = + info.ReflectTelemetryPrefToRegistry(false); + if (reflectOk.isErr()) { + return reflectOk.propagateErr(); + } + EXPECT_TELEMETRY_IS_ENABLED(false); + + reflectOk = info.ReflectTelemetryPrefToRegistry(true); + if (reflectOk.isErr()) { + return reflectOk.propagateErr(); + } + EXPECT_TELEMETRY_IS_ENABLED(true); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestCommitAbort() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + // Retrieve the current timestamps to compare later + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherValue = + ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD); + if (launcherValue.isErr() || launcherValue.inspect().isNothing()) { + return launcherValue.propagateErr(); + } + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserValue = + ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD); + if (browserValue.isErr() || browserValue.inspect().isNothing()) { + return browserValue.propagateErr(); + } + uint64_t launcherTs = launcherValue.inspect().value(); + uint64_t browserTs = browserValue.inspect().value(); + + vr = []() -> mozilla::LauncherVoidResult { + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + // No commit + return mozilla::Ok(); + }(); + if (vr.isErr()) { + return vr; + } + + // Exiting the scope discards the change. + mozilla::LauncherRegistryInfo info; + EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs); + + // Commit -> Check -> Abort -> Commit + EXPECT_COMMIT_IS_OK(); + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + info.Abort(); + EXPECT_COMMIT_IS_OK(); + + // Nothing is changed. + EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs); + EXPECT_ENABLED_STATE_IS(EnabledState::Enabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestDisableDuringLauncherLaunch() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs = + ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD); + if (launcherTs.isErr()) { + return launcherTs.propagateErr(); + } + if (launcherTs.inspect().isNothing()) { + return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); + } + + vr = []() -> mozilla::LauncherVoidResult { + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + + // Call DisableDueToFailure with a different instance + mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult { + mozilla::LauncherRegistryInfo info; + mozilla::LauncherVoidResult vr = info.DisableDueToFailure(); + if (vr.isErr()) { + return vr.propagateErr(); + } + return mozilla::Ok(); + }(); + if (vr.isErr()) { + return vr; + } + + // Commit after disable. + EXPECT_COMMIT_IS_OK(); + + return mozilla::Ok(); + }(); + if (vr.isErr()) { + return vr; + } + + // Make sure we're still FailDisabled and the launcher's timestamp is not + // updated + mozilla::LauncherRegistryInfo info; + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs.inspect().value()); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestDisableDuringBrowserLaunch() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + EXPECT_COMMIT_IS_OK(); + + vr = []() -> mozilla::LauncherVoidResult { + mozilla::LauncherRegistryInfo info; + EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser); + + // Call DisableDueToFailure with a different instance + mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult { + mozilla::LauncherRegistryInfo info; + mozilla::LauncherVoidResult vr = info.DisableDueToFailure(); + if (vr.isErr()) { + return vr.propagateErr(); + } + return mozilla::Ok(); + }(); + if (vr.isErr()) { + return vr; + } + + // Commit after disable. + EXPECT_COMMIT_IS_OK(); + + return mozilla::Ok(); + }(); + if (vr.isErr()) { + return vr; + } + + // Make sure we're still FailDisabled + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + return mozilla::Ok(); +} + +static mozilla::LauncherVoidResult TestReEnable() { + mozilla::LauncherVoidResult vr = SetupEnabledScenario(); + if (vr.isErr()) { + return vr; + } + + // Make FailDisabled + mozilla::LauncherRegistryInfo info; + vr = info.DisableDueToFailure(); + if (vr.isErr()) { + return vr.propagateErr(); + } + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + // Attempt to launch when FailDisabled: Still be FailDisabled + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled); + + // Change the timestamp + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678); + if (vr.isErr()) { + return vr; + } + + // Attempt to launch again: Launcher comes back + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher); + EXPECT_COMMIT_IS_OK(); + + // Make ForceDisabled + vr = info.ReflectPrefToRegistry(false); + if (vr.isErr()) { + return vr.propagateErr(); + } + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + // Attempt to launch when ForceDisabled: Still be ForceDisabled + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + // Change the timestamp + vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678); + if (vr.isErr()) { + return vr; + } + + // Attempt to launch again: Still be ForceDisabled + EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser); + EXPECT_COMMIT_IS_OK(); + EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled); + + return mozilla::Ok(); +} + +int main(int argc, char* argv[]) { + auto fullPath = mozilla::GetFullBinaryPath(); + if (!fullPath) { + return 1; + } + + // Global setup for all tests + gBrowserValue = fullPath.get(); + gBrowserValue += kBrowserSuffix; + + gLauncherValue = fullPath.get(); + gLauncherValue += kLauncherSuffix; + + gImageValue = fullPath.get(); + gImageValue += kImageSuffix; + + gTelemetryValue = fullPath.get(); + gTelemetryValue += kTelemetrySuffix; + + mozilla::LauncherResult<DWORD> timestamp = 0; + RUN_TEST(timestamp, GetCurrentImageTimestamp); + gMyImageTimestamp = timestamp.unwrap(); + + auto onExit = mozilla::MakeScopeExit( + []() { mozilla::Unused << DeleteAllRegstryValues(); }); + + mozilla::LauncherVoidResult vr = mozilla::Ok(); + + // All testcases should call SetupEnabledScenario() or + // DeleteAllRegstryValues() to be order-independent + RUN_TEST(vr, TestEmptyRegistry); + RUN_TEST(vr, TestNormal); + RUN_TEST(vr, TestBrowserNoLauncher); + RUN_TEST(vr, TestLauncherNoBrowser); + RUN_TEST(vr, TestBrowserLessThanLauncher); + RUN_TEST(vr, TestImageTimestampChange); + RUN_TEST(vr, TestImageTimestampChangeWhenDisabled); + RUN_TEST(vr, TestDisableDueToFailure); + RUN_TEST(vr, TestPrefReflection); + RUN_TEST(vr, TestTelemetryConfig); + RUN_TEST(vr, TestCommitAbort); + RUN_TEST(vr, TestDisableDuringLauncherLaunch); + RUN_TEST(vr, TestDisableDuringBrowserLaunch); + RUN_TEST(vr, TestReEnable); + + return 0; +} diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp new file mode 100644 index 0000000000..c12550c3d6 --- /dev/null +++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp @@ -0,0 +1,266 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include <stdio.h> +#include <stdlib.h> +#include <windows.h> +// Support for _setmode +#include <fcntl.h> +#include <io.h> + +#include "nsWindowsRestart.cpp" + +// CommandLineToArgvW may return different values for argv[0] since it contains +// the path to the binary that was executed so we prepend an argument that is +// quoted with a space to prevent argv[1] being appended to argv[0]. +#define DUMMY_ARG1 L"\"arg 1\" " + +#ifndef MAXPATHLEN +# ifdef PATH_MAX +# define MAXPATHLEN PATH_MAX +# elif defined(MAX_PATH) +# define MAXPATHLEN MAX_PATH +# elif defined(_MAX_PATH) +# define MAXPATHLEN _MAX_PATH +# elif defined(CCHMAXPATH) +# define MAXPATHLEN CCHMAXPATH +# else +# define MAXPATHLEN 1024 +# endif +#endif + +#define TEST_NAME L"XRE MakeCommandLine" +#define MAX_TESTS 100 + +// Verbose output can be enabled by defining VERBOSE 1 +#define VERBOSE 0 + +// Compares compareCmdLine with the output of MakeCommandLine. This is +// accomplished by converting inCmdLine to an argument list with +// CommandLineToArgvW and converting it back to a command line with +// MakeCommandLine. +static int verifyCmdLineCreation(wchar_t* inCmdLine, wchar_t* compareCmdLine, + bool passes, int testNum) { + int rv = 0; + int i; + int inArgc; + int outArgc; + bool isEqual; + + // When debugging with command lines containing Unicode characters greater + // than 255 you can set the mode for stdout to Unicode so the console will + // receive the correct characters though it won't display them properly unless + // the console's font has been set to one that can display the characters. You + // can also redirect the console output to a file that has been saved as + // Unicode to view the characters. + // _setmode(_fileno(stdout), _O_WTEXT); + + // Prepend an additional argument to the command line. CommandLineToArgvW + // handles argv[0] differently than other arguments since argv[0] is the path + // to the binary being executed and MakeCommandLine only handles argv[1] and + // larger. + wchar_t* inCmdLineNew = (wchar_t*)malloc( + (wcslen(DUMMY_ARG1) + wcslen(inCmdLine) + 1) * sizeof(wchar_t)); + wcscpy(inCmdLineNew, DUMMY_ARG1); + wcscat(inCmdLineNew, inCmdLine); + LPWSTR* inArgv = CommandLineToArgvW(inCmdLineNew, &inArgc); + + auto outCmdLine = mozilla::MakeCommandLine(inArgc - 1, inArgv + 1); + wchar_t* outCmdLineNew = (wchar_t*)malloc( + (wcslen(DUMMY_ARG1) + wcslen(outCmdLine.get()) + 1) * sizeof(wchar_t)); + wcscpy(outCmdLineNew, DUMMY_ARG1); + wcscat(outCmdLineNew, outCmdLine.get()); + LPWSTR* outArgv = CommandLineToArgvW(outCmdLineNew, &outArgc); + + if (VERBOSE) { + wprintf(L"\n"); + wprintf(L"Verbose Output\n"); + wprintf(L"--------------\n"); + wprintf(L"Input command line : >%s<\n", inCmdLine); + wprintf(L"MakeComandLine output: >%s<\n", outCmdLine.get()); + wprintf(L"Expected command line: >%s<\n", compareCmdLine); + + wprintf(L"input argc : %d\n", inArgc - 1); + wprintf(L"output argc: %d\n", outArgc - 1); + + for (i = 1; i < inArgc; ++i) { + wprintf(L"input argv[%d] : >%s<\n", i - 1, inArgv[i]); + } + + for (i = 1; i < outArgc; ++i) { + wprintf(L"output argv[%d]: >%s<\n", i - 1, outArgv[i]); + } + wprintf(L"\n"); + } + + isEqual = (inArgc == outArgc); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | ARGC Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + return rv; + } + + for (i = 1; i < inArgc; ++i) { + isEqual = (wcscmp(inArgv[i], outArgv[i]) == 0); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | ARGV Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + return rv; + } + } + + isEqual = (wcscmp(outCmdLine.get(), compareCmdLine) == 0); + if (!isEqual) { + wprintf(L"TEST-%s-FAIL | %s | Command Line Comparison (check %2d)\n", + passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum); + if (passes) { + rv = 1; + } + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + return rv; + } + + if (rv == 0) { + if (passes) { + wprintf(L"TEST-PASS | %s | check %2d\n", TEST_NAME, testNum); + } else { + wprintf(L"TEST-UNEXPECTED-PASS | %s | check %2d\n", TEST_NAME, testNum); + rv = 1; + } + } + + LocalFree(inArgv); + LocalFree(outArgv); + free(inCmdLineNew); + free(outCmdLineNew); + return rv; +} + +int wmain(int argc, wchar_t* argv[]) { + int i; + int rv = 0; + + if (argc > 1 && (_wcsicmp(argv[1], L"-check-one") != 0 || argc != 3)) { + fwprintf(stderr, + L"Displays and validates output from MakeCommandLine.\n\n"); + fwprintf(stderr, L"Usage: %s -check-one <test number>\n\n", argv[0]); + fwprintf(stderr, + L" <test number>\tSpecifies the test number to run from the\n"); + fwprintf(stderr, L"\t\tTestXREMakeCommandLineWin.ini file.\n"); + return 255; + } + + wchar_t inifile[MAXPATHLEN]; + if (!::GetModuleFileNameW(0, inifile, MAXPATHLEN)) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | GetModuleFileNameW\n", TEST_NAME); + return 2; + } + + WCHAR* slash = wcsrchr(inifile, '\\'); + if (!slash) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | wcsrchr\n", TEST_NAME); + return 3; + } + + wcscpy(slash + 1, L"TestXREMakeCommandLineWin.ini\0"); + + for (i = 0; i < MAX_TESTS; ++i) { + wchar_t sInputVal[MAXPATHLEN]; + wchar_t sOutputVal[MAXPATHLEN]; + wchar_t sPassesVal[MAXPATHLEN]; + wchar_t sInputKey[MAXPATHLEN]; + wchar_t sOutputKey[MAXPATHLEN]; + wchar_t sPassesKey[MAXPATHLEN]; + + if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) { + i = _wtoi(argv[2]); + } + + _snwprintf(sInputKey, MAXPATHLEN, L"input_%d", i); + _snwprintf(sOutputKey, MAXPATHLEN, L"output_%d", i); + _snwprintf(sPassesKey, MAXPATHLEN, L"passes_%d", i); + + if (!GetPrivateProfileStringW(L"MakeCommandLineTests", sInputKey, nullptr, + sInputVal, MAXPATHLEN, inifile)) { + if (i == 0 || (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0)) { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | see following explanation:\n", + TEST_NAME); + wprintf( + L"ERROR: Either the TestXREMakeCommandLineWin.ini file doesn't " + L"exist\n"); + if (argc > 1 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) { + wprintf( + L"ERROR: or the test is not defined in the MakeCommandLineTests " + L"section.\n"); + } else { + wprintf( + L"ERROR: or it has no tests defined in the MakeCommandLineTests " + L"section.\n"); + } + wprintf(L"ERROR: File: %s\n", inifile); + return 4; + } + break; + } + + GetPrivateProfileStringW(L"MakeCommandLineTests", sOutputKey, nullptr, + sOutputVal, MAXPATHLEN, inifile); + GetPrivateProfileStringW(L"MakeCommandLineTests", sPassesKey, nullptr, + sPassesVal, MAXPATHLEN, inifile); + + rv |= verifyCmdLineCreation( + sInputVal, sOutputVal, + (_wcsicmp(sPassesVal, L"false") == 0) ? FALSE : TRUE, i); + + if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0) { + break; + } + } + + if (rv == 0) { + wprintf(L"TEST-PASS | %s | all checks passed\n", TEST_NAME); + } else { + wprintf(L"TEST-UNEXPECTED-FAIL | %s | some checks failed\n", TEST_NAME); + } + + return rv; +} + +#ifdef __MINGW32__ + +/* MingW currently does not implement a wide version of the + startup routines. Workaround is to implement something like + it ourselves. See bug 411826 */ + +# include <shellapi.h> + +int main(int argc, char** argv) { + LPWSTR commandLine = GetCommandLineW(); + int argcw = 0; + LPWSTR* argvw = CommandLineToArgvW(commandLine, &argcw); + if (!argvw) return 127; + + int result = wmain(argcw, argvw); + LocalFree(argvw); + return result; +} +#endif /* __MINGW32__ */ diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini new file mode 100644 index 0000000000..dbb529d1b1 --- /dev/null +++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini @@ -0,0 +1,94 @@ +; A typical MakeCommandLine test will contain an input and an output name value +; pair. The value for input_xx is the input command line and the value for +; output_xx is the expected output command line. +; +; A test that is known to fail can be added as follows. If the passes_xx name +; value pair doesn't exist it defaults to true. +; input_99=yabadaba +; output_99=doo +; passes_99=false +; +; If a value starts and ends with single or double quotation marks then it must +; be enclosed in single or double quotation marks due to GetPrivateProfileString +; discarding the outmost quotation marks. See GetPrivateProfileString on MSDN +; for more information. +; http://msdn.microsoft.com/en-us/library/ms724353.aspx + +[MakeCommandLineTests] +input_0=a:\ +output_0=a:\ + +input_1=""a:\"" +output_1=a:\" + +input_2=""a:\b c"" +output_2=""a:\b c"" + +input_3=""a:\b c\"" +output_3=""a:\b c\""" + +input_4=""a:\b c\d e"" +output_4=""a:\b c\d e"" + +input_5=""a:\b c\d e\"" +output_5=""a:\b c\d e\""" + +input_6=""a:\\"" +output_6=a:\ + +input_7="a:\" "b:\c d" +output_7=a:\" "b:\c d" + +input_8="a "b:\" "c:\d e"" +output_8="a "b:\" c:\d" e" + +input_9="abc" d e +output_9=abc d e + +input_10="a b c" d e +output_10="a b c" d e + +input_11=a\\\b d"e f"g h +output_11=a\\\b "de fg" h + +input_12=a b +output_12=a b + +input_13=""a b"" +output_13=""a b"" + +input_14=a\\\"b c d +output_14=a\\\"b c d + +input_15=a\\\"b c" +output_15=a\\\"b c + +input_16=""a\\\b c" +output_16=""a\\\b c"" + +input_17=\"a +output_17=\"a + +input_18=\\"a +output_18=\a + +input_19=\\"\\\\"a +output_19=\\\a + +input_20=\\"\\\\\"a +output_20=\\\\\\\"a + +input_21="a\\\"b c\" d e +output_21=""a\\\"b c\" d e"" + +input_22=a\\\\\"b c" d e" +output_22=a\\\\\"b "c d e" + +input_23=a:\b c\アルファ オメガ\d +output_23=a:\b c\アルファ オメガ\d + +input_24=a:\b "c\アルファ オメガ\d" +output_24=a:\b "c\アルファ オメガ\d" + +input_25=アルファ オメガ +output_25=アルファ オメガ diff --git a/toolkit/xre/test/win/moz.build b/toolkit/xre/test/win/moz.build new file mode 100644 index 0000000000..8dbdbe5389 --- /dev/null +++ b/toolkit/xre/test/win/moz.build @@ -0,0 +1,44 @@ +# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*- +# vim: set filetype=python: +# 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/. + +GeckoCppUnitTests( + [ + "TestXREMakeCommandLineWin", + ], + linkage=None, +) + +# This needs to be installed alongside the above unit test. +FINAL_TARGET_FILES += [ + "TestXREMakeCommandLineWin.ini", +] + +DEFINES["NS_NO_XPCOM"] = True + +LOCAL_INCLUDES += [ + "/config", + "/toolkit/xre", +] + +DisableStlWrapping() +USE_STATIC_LIBS = True + +OS_LIBS += [ + "comctl32", + "shell32", + "ws2_32", +] + +if CONFIG["MOZ_LAUNCHER_PROCESS"]: + GeckoCppUnitTests( + [ + "TestLauncherRegistryInfo", + ], + linkage=None, + ) + # Needed for TestLauncherRegistryInfo + for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR"): + DEFINES[var] = '"%s"' % CONFIG[var] diff --git a/toolkit/xre/test/xpcshell.ini b/toolkit/xre/test/xpcshell.ini new file mode 100644 index 0000000000..d8ca7d87fe --- /dev/null +++ b/toolkit/xre/test/xpcshell.ini @@ -0,0 +1,16 @@ +# 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/. + +[DEFAULT] +tags = native + +[test_launch_without_hang.js] +run-sequentially = Has to launch application binary +skip-if = toolkit == 'android' +[test_install_hash.js] +# Android doesn't ship Subprocess.jsm and debug builds output garbage that the +# test cannot handle. +skip-if = toolkit == 'android' || debug +support-files = + show_hash.js diff --git a/toolkit/xre/updaterfileutils_osx.h b/toolkit/xre/updaterfileutils_osx.h new file mode 100644 index 0000000000..1fa0db1f4e --- /dev/null +++ b/toolkit/xre/updaterfileutils_osx.h @@ -0,0 +1,13 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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 updaterfileutils_osx_h_ +#define updaterfileutils_osx_h_ + +extern "C" { +bool IsRecursivelyWritable(const char* aPath); +} + +#endif diff --git a/toolkit/xre/updaterfileutils_osx.mm b/toolkit/xre/updaterfileutils_osx.mm new file mode 100644 index 0000000000..15a4b3a5ba --- /dev/null +++ b/toolkit/xre/updaterfileutils_osx.mm @@ -0,0 +1,46 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* 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/. */ + +#include "updaterfileutils_osx.h" + +#include <Cocoa/Cocoa.h> + +bool IsRecursivelyWritable(const char* aPath) { + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + NSString* rootPath = [NSString stringWithUTF8String:aPath]; + NSFileManager* fileManager = [NSFileManager defaultManager]; + NSError* error = nil; + NSArray* subPaths = [fileManager subpathsOfDirectoryAtPath:rootPath error:&error]; + NSMutableArray* paths = [NSMutableArray arrayWithCapacity:[subPaths count] + 1]; + [paths addObject:@""]; + [paths addObjectsFromArray:subPaths]; + + if (error) { + [pool drain]; + return false; + } + + for (NSString* currPath in paths) { + NSString* child = [rootPath stringByAppendingPathComponent:currPath]; + + NSDictionary* attributes = [fileManager attributesOfItemAtPath:child error:&error]; + if (error) { + [pool drain]; + return false; + } + + // Don't check for writability of files pointed to by symlinks, as they may + // not be descendants of the root path. + if ([attributes fileType] != NSFileTypeSymbolicLink && + [fileManager isWritableFileAtPath:child] == NO) { + [pool drain]; + return false; + } + } + + [pool drain]; + return true; +} |