summaryrefslogtreecommitdiffstats
path: root/toolkit/xre
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--toolkit/xre/AssembleCmdLine.h136
-rw-r--r--toolkit/xre/AutoSQLiteLifetime.cpp156
-rw-r--r--toolkit/xre/AutoSQLiteLifetime.h24
-rw-r--r--toolkit/xre/Bootstrap.cpp122
-rw-r--r--toolkit/xre/Bootstrap.h171
-rw-r--r--toolkit/xre/CmdLineAndEnvUtils.cpp43
-rw-r--r--toolkit/xre/CmdLineAndEnvUtils.h657
-rw-r--r--toolkit/xre/CreateAppData.cpp77
-rw-r--r--toolkit/xre/DllPrefetchExperimentRegistryInfo.cpp112
-rw-r--r--toolkit/xre/DllPrefetchExperimentRegistryInfo.h50
-rw-r--r--toolkit/xre/EventTracer.cpp219
-rw-r--r--toolkit/xre/EventTracer.h23
-rw-r--r--toolkit/xre/LauncherRegistryInfo.cpp533
-rw-r--r--toolkit/xre/LauncherRegistryInfo.h99
-rw-r--r--toolkit/xre/MacApplicationDelegate.h17
-rw-r--r--toolkit/xre/MacApplicationDelegate.mm406
-rw-r--r--toolkit/xre/MacAutoreleasePool.h31
-rw-r--r--toolkit/xre/MacAutoreleasePool.mm17
-rw-r--r--toolkit/xre/MacLaunchHelper.h23
-rw-r--r--toolkit/xre/MacLaunchHelper.mm118
-rw-r--r--toolkit/xre/ModuleEvaluator.cpp253
-rw-r--r--toolkit/xre/ModuleEvaluator.h48
-rw-r--r--toolkit/xre/ModuleVersionInfo.cpp111
-rw-r--r--toolkit/xre/ModuleVersionInfo.h71
-rw-r--r--toolkit/xre/MultiInstanceLock.cpp197
-rw-r--r--toolkit/xre/MultiInstanceLock.h84
-rw-r--r--toolkit/xre/PolicyChecks.h42
-rw-r--r--toolkit/xre/ProfileReset.cpp149
-rw-r--r--toolkit/xre/ProfileReset.h84
-rw-r--r--toolkit/xre/SafeMode.h92
-rw-r--r--toolkit/xre/UIKitDirProvider.h13
-rw-r--r--toolkit/xre/UIKitDirProvider.mm18
-rw-r--r--toolkit/xre/UntrustedModulesData.cpp422
-rw-r--r--toolkit/xre/UntrustedModulesData.h618
-rw-r--r--toolkit/xre/UntrustedModulesProcessor.cpp1042
-rw-r--r--toolkit/xre/UntrustedModulesProcessor.h157
-rw-r--r--toolkit/xre/WinDllServices.cpp135
-rw-r--r--toolkit/xre/WinDllServices.h56
-rw-r--r--toolkit/xre/WinTokenUtils.cpp72
-rw-r--r--toolkit/xre/WinTokenUtils.h18
-rw-r--r--toolkit/xre/components.conf66
-rw-r--r--toolkit/xre/glxtest.cpp1222
-rw-r--r--toolkit/xre/moz.build287
-rw-r--r--toolkit/xre/nsAndroidStartup.cpp41
-rw-r--r--toolkit/xre/nsAppRunner.cpp5653
-rw-r--r--toolkit/xre/nsAppRunner.h169
-rw-r--r--toolkit/xre/nsAppStartupNotifier.cpp71
-rw-r--r--toolkit/xre/nsAppStartupNotifier.h17
-rw-r--r--toolkit/xre/nsCommandLineServiceMac.h20
-rw-r--r--toolkit/xre/nsCommandLineServiceMac.mm92
-rw-r--r--toolkit/xre/nsConsoleWriter.cpp80
-rw-r--r--toolkit/xre/nsEmbedFunctions.cpp1003
-rw-r--r--toolkit/xre/nsEmbeddingModule.cpp43
-rw-r--r--toolkit/xre/nsGDKErrorHandler.cpp109
-rw-r--r--toolkit/xre/nsGDKErrorHandler.h8
-rw-r--r--toolkit/xre/nsIAppStartupNotifier.h53
-rw-r--r--toolkit/xre/nsINativeAppSupport.idl55
-rw-r--r--toolkit/xre/nsIWinAppHelper.idl19
-rw-r--r--toolkit/xre/nsIXREDirProvider.idl23
-rw-r--r--toolkit/xre/nsNativeAppSupportBase.cpp28
-rw-r--r--toolkit/xre/nsNativeAppSupportBase.h28
-rw-r--r--toolkit/xre/nsNativeAppSupportCocoa.mm172
-rw-r--r--toolkit/xre/nsNativeAppSupportDefault.cpp15
-rw-r--r--toolkit/xre/nsNativeAppSupportUnix.cpp657
-rw-r--r--toolkit/xre/nsNativeAppSupportWin.cpp69
-rw-r--r--toolkit/xre/nsNativeAppSupportWin.h26
-rw-r--r--toolkit/xre/nsSigHandlers.cpp371
-rw-r--r--toolkit/xre/nsSigHandlers.h40
-rw-r--r--toolkit/xre/nsUpdateDriver.cpp1081
-rw-r--r--toolkit/xre/nsUpdateDriver.h95
-rw-r--r--toolkit/xre/nsUpdateSyncManager.cpp130
-rw-r--r--toolkit/xre/nsUpdateSyncManager.h49
-rw-r--r--toolkit/xre/nsWindowsRestart.cpp151
-rw-r--r--toolkit/xre/nsWindowsWMain.cpp140
-rw-r--r--toolkit/xre/nsX11ErrorHandler.cpp146
-rw-r--r--toolkit/xre/nsX11ErrorHandler.h18
-rw-r--r--toolkit/xre/nsXREDirProvider.cpp1769
-rw-r--r--toolkit/xre/nsXREDirProvider.h165
-rw-r--r--toolkit/xre/platform.ini17
-rw-r--r--toolkit/xre/test/.eslintrc.js9
-rw-r--r--toolkit/xre/test/browser.ini4
-rw-r--r--toolkit/xre/test/browser_checkdllblockliststate.js16
-rw-r--r--toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp173
-rw-r--r--toolkit/xre/test/gtest/TestCompatVersionCompare.cpp53
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules.cpp382
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp7
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc38
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll1/moz.build17
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp7
-rw-r--r--toolkit/xre/test/gtest/TestUntrustedModules_Dll2/moz.build15
-rw-r--r--toolkit/xre/test/gtest/moz.build31
-rw-r--r--toolkit/xre/test/marionette/marionette.ini2
-rw-r--r--toolkit/xre/test/marionette/test_exitcode.py33
-rw-r--r--toolkit/xre/test/marionette/test_fission_autostart.py416
-rw-r--r--toolkit/xre/test/mochitest.ini3
-rw-r--r--toolkit/xre/test/show_hash.js4
-rw-r--r--toolkit/xre/test/test_fpuhandler.html36
-rw-r--r--toolkit/xre/test/test_install_hash.js139
-rw-r--r--toolkit/xre/test/test_launch_without_hang.js262
-rw-r--r--toolkit/xre/test/win/Makefile.in11
-rw-r--r--toolkit/xre/test/win/TestLauncherRegistryInfo.cpp779
-rw-r--r--toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp266
-rw-r--r--toolkit/xre/test/win/TestXREMakeCommandLineWin.ini94
-rw-r--r--toolkit/xre/test/win/moz.build44
-rw-r--r--toolkit/xre/test/xpcshell.ini16
-rw-r--r--toolkit/xre/updaterfileutils_osx.h13
-rw-r--r--toolkit/xre/updaterfileutils_osx.mm46
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, &registry_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;
+}