summaryrefslogtreecommitdiffstats
path: root/toolkit/xre
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre')
-rw-r--r--toolkit/xre/AssembleCmdLine.h139
-rw-r--r--toolkit/xre/AutoSQLiteLifetime.cpp156
-rw-r--r--toolkit/xre/AutoSQLiteLifetime.h24
-rw-r--r--toolkit/xre/Bootstrap.cpp117
-rw-r--r--toolkit/xre/Bootstrap.h176
-rw-r--r--toolkit/xre/CmdLineAndEnvUtils.cpp43
-rw-r--r--toolkit/xre/CmdLineAndEnvUtils.h732
-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/GeckoArgs.h153
-rw-r--r--toolkit/xre/LauncherRegistryInfo.cpp651
-rw-r--r--toolkit/xre/LauncherRegistryInfo.h104
-rw-r--r--toolkit/xre/MacApplicationDelegate.h17
-rw-r--r--toolkit/xre/MacApplicationDelegate.mm409
-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.mm117
-rw-r--r--toolkit/xre/MacRunFromDmgUtils.h33
-rw-r--r--toolkit/xre/MacRunFromDmgUtils.mm513
-rw-r--r--toolkit/xre/MultiInstanceLock.cpp295
-rw-r--r--toolkit/xre/MultiInstanceLock.h92
-rw-r--r--toolkit/xre/PolicyChecks.h44
-rw-r--r--toolkit/xre/ProfileReset.cpp146
-rw-r--r--toolkit/xre/ProfileReset.h84
-rw-r--r--toolkit/xre/SafeMode.h90
-rw-r--r--toolkit/xre/UIKitDirProvider.h13
-rw-r--r--toolkit/xre/UIKitDirProvider.mm18
-rw-r--r--toolkit/xre/WinTokenUtils.cpp72
-rw-r--r--toolkit/xre/WinTokenUtils.h18
-rw-r--r--toolkit/xre/components.conf71
-rw-r--r--toolkit/xre/detect_win32k_conflicts.h20
-rw-r--r--toolkit/xre/detect_win32k_conflicts/.gitignore2
-rw-r--r--toolkit/xre/detect_win32k_conflicts/Cargo.toml10
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/detect.rs165
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/error.rs36
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/ffi.rs57
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/lib.rs18
-rw-r--r--toolkit/xre/detect_win32k_conflicts/src/registry.rs360
-rw-r--r--toolkit/xre/detect_win32k_conflicts/tests/basic.rs14
-rw-r--r--toolkit/xre/dllservices/DynamicBlocklist.h291
-rw-r--r--toolkit/xre/dllservices/DynamicBlocklistWriter.cpp139
-rw-r--r--toolkit/xre/dllservices/ModuleEvaluator.cpp253
-rw-r--r--toolkit/xre/dllservices/ModuleEvaluator.h50
-rw-r--r--toolkit/xre/dllservices/ModuleVersionInfo.cpp111
-rw-r--r--toolkit/xre/dllservices/ModuleVersionInfo.h71
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesData.cpp446
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesData.h650
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesProcessor.cpp1040
-rw-r--r--toolkit/xre/dllservices/UntrustedModulesProcessor.h176
-rw-r--r--toolkit/xre/dllservices/WinDllServices.cpp142
-rw-r--r--toolkit/xre/dllservices/WinDllServices.h57
-rw-r--r--toolkit/xre/dllservices/moz.build45
-rw-r--r--toolkit/xre/dllservices/mozglue/Authenticode.cpp439
-rw-r--r--toolkit/xre/dllservices/mozglue/Authenticode.h32
-rw-r--r--toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp60
-rw-r--r--toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h21
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h125
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderObserver.cpp149
-rw-r--r--toolkit/xre/dllservices/mozglue/LoaderObserver.h45
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp104
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h44
-rw-r--r--toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h174
-rw-r--r--toolkit/xre/dllservices/mozglue/NtLoaderAPI.h23
-rw-r--r--toolkit/xre/dllservices/mozglue/SharedSection.h28
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp781
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h79
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h46
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in356
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h88
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsDllServices.h212
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp91
-rw-r--r--toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h39
-rw-r--r--toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py750
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp89
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Arm64.h221
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h1031
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h141
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h1728
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h205
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h142
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h1000
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h773
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h285
-rw-r--r--toolkit/xre/dllservices/mozglue/interceptor/moz.build26
-rw-r--r--toolkit/xre/dllservices/mozglue/moz.build80
-rw-r--r--toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h819
-rw-r--r--toolkit/xre/dllservices/tests/AssemblyPayloads.h241
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.cpp1416
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest17
-rw-r--r--toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp159
-rw-r--r--toolkit/xre/dllservices/tests/TestIATPatcher.cpp121
-rw-r--r--toolkit/xre/dllservices/tests/TestMMPolicy.cpp204
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp223
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp12
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc42
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build21
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp461
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc38
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build17
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp7
-rw-r--r--toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build15
-rw-r--r--toolkit/xre/dllservices/tests/gtest/moz.build35
-rw-r--r--toolkit/xre/dllservices/tests/moz.build46
-rw-r--r--toolkit/xre/glxtest.cpp1281
-rw-r--r--toolkit/xre/metrics.yaml11
-rw-r--r--toolkit/xre/moz.build293
-rw-r--r--toolkit/xre/nsAndroidStartup.cpp57
-rw-r--r--toolkit/xre/nsAppRunner.cpp6273
-rw-r--r--toolkit/xre/nsAppRunner.h168
-rw-r--r--toolkit/xre/nsAppStartupNotifier.cpp72
-rw-r--r--toolkit/xre/nsAppStartupNotifier.h18
-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.cpp852
-rw-r--r--toolkit/xre/nsGDKErrorHandler.cpp117
-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.mm177
-rw-r--r--toolkit/xre/nsNativeAppSupportDefault.cpp15
-rw-r--r--toolkit/xre/nsNativeAppSupportUnix.cpp658
-rw-r--r--toolkit/xre/nsNativeAppSupportWin.cpp66
-rw-r--r--toolkit/xre/nsNativeAppSupportWin.h29
-rw-r--r--toolkit/xre/nsSigHandlers.cpp402
-rw-r--r--toolkit/xre/nsSigHandlers.h40
-rw-r--r--toolkit/xre/nsUpdateDriver.cpp952
-rw-r--r--toolkit/xre/nsUpdateDriver.h112
-rw-r--r--toolkit/xre/nsUpdateSyncManager.cpp129
-rw-r--r--toolkit/xre/nsUpdateSyncManager.h49
-rw-r--r--toolkit/xre/nsWindowsRestart.cpp197
-rw-r--r--toolkit/xre/nsWindowsWMain.cpp176
-rw-r--r--toolkit/xre/nsX11ErrorHandler.cpp154
-rw-r--r--toolkit/xre/nsX11ErrorHandler.h20
-rw-r--r--toolkit/xre/nsXREDirProvider.cpp1660
-rw-r--r--toolkit/xre/nsXREDirProvider.h168
-rw-r--r--toolkit/xre/platform.ini17
-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.cpp227
-rw-r--r--toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp372
-rw-r--r--toolkit/xre/test/gtest/TestCompatVersionCompare.cpp53
-rw-r--r--toolkit/xre/test/gtest/TestGeckoArgs.cpp179
-rw-r--r--toolkit/xre/test/gtest/moz.build24
-rw-r--r--toolkit/xre/test/marionette/gen_win32k_tests.py212
-rw-r--r--toolkit/xre/test/marionette/marionette.ini6
-rw-r--r--toolkit/xre/test/marionette/test_exitcode.py31
-rw-r--r--toolkit/xre/test/marionette/test_fission_autostart.py410
-rw-r--r--toolkit/xre/test/marionette/test_win32k_enrollment.py2506
-rw-r--r--toolkit/xre/test/marionette/test_win32k_enrollment.template.py204
-rw-r--r--toolkit/xre/test/marionette/win32k_tests.txt167
-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.js138
-rw-r--r--toolkit/xre/test/test_launch_without_hang.js256
-rw-r--r--toolkit/xre/test/win/Makefile.in11
-rw-r--r--toolkit/xre/test/win/TestLauncherRegistryInfo.cpp783
-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/mochitest/browser_env_path_long.ini7
-rw-r--r--toolkit/xre/test/win/mochitest/browser_env_path_long.js15
-rw-r--r--toolkit/xre/test/win/moz.build55
-rw-r--r--toolkit/xre/test/xpcshell.ini20
-rw-r--r--toolkit/xre/updaterfileutils_osx.h13
-rw-r--r--toolkit/xre/updaterfileutils_osx.mm46
189 files changed, 43287 insertions, 0 deletions
diff --git a/toolkit/xre/AssembleCmdLine.h b/toolkit/xre/AssembleCmdLine.h
new file mode 100644
index 0000000000..d969fedfed
--- /dev/null
+++ b/toolkit/xre/AssembleCmdLine.h
@@ -0,0 +1,139 @@
+/* -*- 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 is empty or contains white space, it needs to
+ * be quoted.
+ */
+ if (**arg == '\0' || 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..b81fde839b
--- /dev/null
+++ b/toolkit/xre/Bootstrap.cpp
@@ -0,0 +1,117 @@
+/* -*- 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, bool xpcshell,
+ const char* outFilePath) override {
+ ::GeckoStart(aEnv, argv, argc, aAppData, xpcshell, outFilePath);
+ }
+
+ 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_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..9d46bec55b
--- /dev/null
+++ b/toolkit/xre/Bootstrap.h
@@ -0,0 +1,176 @@
+/* -*- 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/Maybe.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Variant.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,
+ bool xpcshell, const char* outFilePath);
+#endif
+
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+namespace sandbox {
+class BrokerServices;
+}
+#endif
+
+namespace mozilla {
+
+struct StaticXREAppData;
+
+struct BootstrapConfig {
+#if defined(XP_WIN) && defined(MOZ_SANDBOX)
+ /* Chromium sandbox BrokerServices. */
+ sandbox::BrokerServices* sandboxBrokerServices;
+#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, bool xpcshell,
+ const char* outFilePath) = 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_ENABLE_FORKSERVER
+ virtual int XRE_ForkServer(int* argc, char*** argv) = 0;
+#endif
+};
+
+enum class LibLoadingStrategy {
+ NoReadAhead,
+ ReadAhead,
+};
+
+#if defined(XP_WIN)
+using DLErrorType = unsigned long; // (DWORD)
+#else
+using DLErrorType = UniqueFreePtr<char>;
+#endif
+
+using BootstrapError = Variant<nsresult, DLErrorType>;
+
+using BootstrapResult = ::mozilla::Result<Bootstrap::UniquePtr, BootstrapError>;
+
+/**
+ * Creates and returns the singleton instance 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&);
+BootstrapResult GetBootstrap(
+ const char* aXPCOMFile = nullptr,
+ LibLoadingStrategy aLibLoadingStrategy = LibLoadingStrategy::NoReadAhead);
+#else
+extern "C" NS_EXPORT void NS_FROZENCALL
+XRE_GetBootstrap(Bootstrap::UniquePtr& b);
+
+inline BootstrapResult 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..cad34d2503
--- /dev/null
+++ b/toolkit/xre/CmdLineAndEnvUtils.h
@@ -0,0 +1,732 @@
+/* -*- 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>
+#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/Maybe.h"
+#include "mozilla/MemoryChecking.h"
+#include "mozilla/TypedEnumBits.h"
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.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 {
+
+#if 'a' == '\x61'
+// Valid option characters must have the same representation in every locale
+// (which is true for most of ASCII, barring \x5C and \x7E).
+static inline constexpr bool isValidOptionCharacter(char c) {
+ // We specifically avoid the use of `islower` here; it's locale-dependent, and
+ // may return true for non-ASCII values in some locales.
+ return ('0' <= c && c <= '9') || ('a' <= c && c <= 'z') || c == '-';
+};
+
+// Convert uppercase to lowercase, locale-insensitively.
+static inline constexpr char toLowercase(char c) {
+ // We specifically avoid the use of `tolower` here; it's locale-dependent, and
+ // may output ASCII values for non-ASCII input (or vice versa) in some
+ // locales.
+ return ('A' <= c && c <= 'Z') ? char(c | ' ') : c;
+};
+
+// Convert a CharT to a char, ensuring that no CharT is mapped to any valid
+// option character except the unique CharT naturally corresponding thereto.
+template <typename CharT>
+static inline constexpr char toNarrow(CharT c) {
+ // confirmed to compile down to nothing when `CharT` is `char`
+ return (c & static_cast<CharT>(0xff)) == c ? c : 0xff;
+};
+#else
+// The target system's character set isn't even ASCII-compatible. If you're
+// porting Gecko to such a platform, you'll have to implement these yourself.
+# error Character conversion functions not implemented for this platform.
+#endif
+
+// Case-insensitively compare a string taken from the command-line (`mixedstr`)
+// to the text of some known command-line option (`lowerstr`).
+template <typename CharT>
+static inline bool strimatch(const char* lowerstr, const CharT* mixedstr) {
+ while (*lowerstr) {
+ if (!*mixedstr) return false; // mixedstr is shorter
+
+ // Non-ASCII strings may compare incorrectly depending on the user's locale.
+ // Some ASCII-safe characters are also dispermitted for semantic reasons
+ // and simplicity.
+ if (!isValidOptionCharacter(*lowerstr)) return false;
+
+ if (toLowercase(toNarrow(*mixedstr)) != *lowerstr) {
+ return false; // no match
+ }
+
+ ++lowerstr;
+ ++mixedstr;
+ }
+
+ if (*mixedstr) return false; // lowerstr is shorter
+
+ return true;
+}
+
+// Given a command-line argument, return Nothing if it isn't structurally a
+// command-line option, and Some(<the option text>) if it is.
+template <typename CharT>
+mozilla::Maybe<const CharT*> ReadAsOption(const CharT* str) {
+ if (!str) {
+ return Nothing();
+ }
+ if (*str == '-') {
+ str++;
+ if (*str == '-') {
+ str++;
+ }
+ return Some(str);
+ }
+#ifdef XP_WIN
+ if (*str == '/') {
+ return Some(str + 1);
+ }
+#endif
+ return Nothing();
+}
+
+} // namespace internal
+
+using internal::strimatch;
+
+const wchar_t kCommandLineDelimiter[] = L" \t";
+
+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 char* aArg,
+ const CharT** aParam = nullptr,
+ CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
+ using internal::ReadAsOption;
+ MOZ_ASSERT(aArgv && aArg);
+
+ CharT** curarg = aArgv + 1; // skip argv[0]
+ ArgResult ar = ARG_NONE;
+
+ while (*curarg) {
+ if (const auto arg = ReadAsOption(*curarg)) {
+ if (strimatch(aArg, arg.value())) {
+ if (aFlags & CheckArgFlag::RemoveArg) {
+ RemoveArg(aArgc, curarg);
+ } else {
+ ++curarg;
+ }
+
+ if (!aParam) {
+ ar = ARG_FOUND;
+ break;
+ }
+
+ if (*curarg) {
+ if (ReadAsOption(*curarg)) {
+ 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 ArgResult CheckArg(int& aArgc, CharT** aArgv, const char* aArg,
+ std::nullptr_t,
+ CheckArgFlag aFlags = CheckArgFlag::RemoveArg) {
+ return CheckArg<CharT>(aArgc, aArgv, aArg,
+ static_cast<const CharT**>(nullptr), aFlags);
+}
+
+namespace internal {
+// template <typename T>
+// constexpr bool IsStringRange =
+// std::convertible_to<std::ranges::range_value_t<T>, const char *>;
+
+template <typename CharT, typename ListT>
+// requires IsStringRange<ListT>
+static bool MatchesAnyOf(CharT const* unknown, ListT const& known) {
+ for (const char* k : known) {
+ if (strimatch(k, unknown)) {
+ return true;
+ }
+ }
+ return false;
+}
+
+template <typename CharT, typename ReqContainerT, typename OptContainerT>
+// requires IsStringRange<ReqContainerT> && IsStringRange<OptContainerT>
+inline bool EnsureCommandlineSafeImpl(int aArgc, CharT** aArgv,
+ ReqContainerT const& requiredParams,
+ OptContainerT const& optionalParams) {
+ // We expect either no -osint, or the full commandline to be:
+ //
+ // app -osint [<optional-param>...] <required-param> <required-argument>
+ //
+ // Otherwise, we abort to avoid abuse of other command-line handlers from apps
+ // that do a poor job escaping links they give to the OS.
+ //
+ // Note that the above implies that optional parameters do not themselves take
+ // arguments. This is a security feature, to prevent the possible injection of
+ // additional parameters via such arguments. (See, e.g., bug 384384.)
+
+ static constexpr const char* osintLit = "osint";
+
+ // If "-osint" (or the equivalent) is not present, then this is trivially
+ // satisfied.
+ if (CheckArg(aArgc, aArgv, osintLit, nullptr, CheckArgFlag::None) !=
+ ARG_FOUND) {
+ return true;
+ }
+
+ // There should be at least 4 items present:
+ // <app name> -osint <required param> <arg>.
+ if (aArgc < 4) {
+ return false;
+ }
+
+ // The first parameter must be osint.
+ const auto arg1 = ReadAsOption(aArgv[1]);
+ if (!arg1) return false;
+ if (!strimatch(osintLit, arg1.value())) {
+ return false;
+ }
+ // Following this is any number of optional parameters, terminated by a
+ // required parameter.
+ int pos = 2;
+ while (true) {
+ if (pos >= aArgc) return false;
+
+ auto const arg = ReadAsOption(aArgv[pos]);
+ if (!arg) return false;
+
+ if (MatchesAnyOf(arg.value(), optionalParams)) {
+ ++pos;
+ continue;
+ }
+
+ if (MatchesAnyOf(arg.value(), requiredParams)) {
+ ++pos;
+ break;
+ }
+
+ return false;
+ }
+
+ // There must be one argument remaining...
+ if (pos + 1 != aArgc) return false;
+ // ... which must not be another option.
+ if (ReadAsOption(aArgv[pos])) {
+ return false;
+ }
+
+ // Nothing ill-formed was passed.
+ return true;
+}
+
+// C (and so C++) disallows empty arrays. Rather than require callers to jump
+// through hoops to specify an empty optional-argument list, allow either its
+// omission or its specification as `nullptr`, and do the hoop-jumping here.
+//
+// No such facility is provided for requiredParams, which must have at least one
+// entry.
+template <typename CharT, typename ReqContainerT>
+inline bool EnsureCommandlineSafeImpl(int aArgc, CharT** aArgv,
+ ReqContainerT const& requiredParams,
+ std::nullptr_t _ = nullptr) {
+ struct {
+ inline const char** begin() const { return nullptr; }
+ inline const char** end() const { return nullptr; }
+ } emptyContainer;
+ return EnsureCommandlineSafeImpl(aArgc, aArgv, requiredParams,
+ emptyContainer);
+}
+} // namespace internal
+
+template <typename CharT, typename ReqContainerT,
+ typename OptContainerT = std::nullptr_t>
+inline void EnsureCommandlineSafe(
+ int aArgc, CharT** aArgv, ReqContainerT const& requiredParams,
+ OptContainerT const& optionalParams = nullptr) {
+ if (!internal::EnsureCommandlineSafeImpl(aArgc, aArgv, requiredParams,
+ optionalParams)) {
+ exit(127);
+ }
+}
+
+#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);
+
+ 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. This satisfies
+// the examples at:
+// https://docs.microsoft.com/en-us/cpp/cpp/main-function-command-line-args#results-of-parsing-command-lines
+// https://docs.microsoft.com/en-us/previous-versions/17w5ykft(v=vs.85)
+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; }
+
+ // Returns the number of characters handled
+ int HandleCommandLine(const nsTSubstring<T>& aCmdLineString) {
+ Release();
+
+ if (aCmdLineString.IsEmpty()) {
+ return 0;
+ }
+
+ int justCounting = 1;
+ // Flags, etc.
+ int init = 1;
+ int between, quoted, bSlashCount;
+ const T* p;
+ const T* const pEnd = aCmdLineString.EndReading();
+ nsTAutoString<T> arg;
+
+ // We loop if we've not finished the second pass through.
+ while (1) {
+ // Initialize if required.
+ if (init) {
+ p = aCmdLineString.BeginReading();
+ between = 1;
+ mArgc = quoted = bSlashCount = 0;
+
+ init = 0;
+ }
+
+ const T charCurr = (p < pEnd) ? *p : 0;
+ const T charNext = (p + 1 < pEnd) ? *(p + 1) : 0;
+
+ if (between) {
+ // We are traversing whitespace between args.
+ // Check for start of next arg.
+ if (charCurr != 0 && !wcschr(kCommandLineDelimiter, charCurr)) {
+ // Start of another arg.
+ between = 0;
+ arg.Truncate();
+ switch (charCurr) {
+ case '\\':
+ // Count the backslash.
+ bSlashCount = 1;
+ break;
+ case '"':
+ // Remember we're inside quotes.
+ quoted = 1;
+ break;
+ default:
+ // Add character to arg.
+ arg += charCurr;
+ break;
+ }
+ } else {
+ // Another space between args, ignore it.
+ }
+ } else {
+ // We are processing the contents of an argument.
+ // Check for whitespace or end.
+ if (charCurr == 0 ||
+ (!quoted && wcschr(kCommandLineDelimiter, charCurr))) {
+ // 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 (charCurr) {
+ 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 (charNext == '"') {
+ // 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 += charCurr;
+ break;
+ }
+ }
+ }
+
+ // Check for end of input.
+ if (charCurr) {
+ // 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;
+ }
+ }
+ }
+
+ return p - aCmdLineString.BeginReading();
+ }
+};
+# 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/GeckoArgs.h b/toolkit/xre/GeckoArgs.h
new file mode 100644
index 0000000000..7db63c4301
--- /dev/null
+++ b/toolkit/xre/GeckoArgs.h
@@ -0,0 +1,153 @@
+/* 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_GeckoArgs_h
+#define mozilla_GeckoArgs_h
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/Maybe.h"
+
+#include <array>
+#include <cctype>
+#include <climits>
+#include <string>
+#include <vector>
+
+namespace mozilla {
+
+namespace geckoargs {
+
+template <typename T>
+struct CommandLineArg {
+ Maybe<T> Get(int& aArgc, char** aArgv,
+ const CheckArgFlag aFlags = CheckArgFlag::RemoveArg);
+
+ const char* Name() { return sName; };
+
+ void Put(std::vector<std::string>& aArgs);
+ void Put(T aValue, std::vector<std::string>& aArgs);
+
+ const char* sName;
+ const char* sMatch;
+};
+
+/// Get()
+
+template <>
+inline Maybe<const char*> CommandLineArg<const char*>::Get(
+ int& aArgc, char** aArgv, const CheckArgFlag aFlags) {
+ MOZ_ASSERT(aArgv, "aArgv must be initialized before CheckArg()");
+ const char* rv = nullptr;
+ if (ARG_FOUND == CheckArg(aArgc, aArgv, sMatch, &rv, aFlags)) {
+ return Some(rv);
+ }
+ return Nothing();
+}
+
+template <>
+inline Maybe<bool> CommandLineArg<bool>::Get(int& aArgc, char** aArgv,
+ const CheckArgFlag aFlags) {
+ MOZ_ASSERT(aArgv, "aArgv must be initialized before CheckArg()");
+ if (ARG_FOUND ==
+ CheckArg(aArgc, aArgv, sMatch, (const char**)nullptr, aFlags)) {
+ return Some(true);
+ }
+ return Nothing();
+}
+
+template <>
+inline Maybe<uint64_t> CommandLineArg<uint64_t>::Get(
+ int& aArgc, char** aArgv, const CheckArgFlag aFlags) {
+ MOZ_ASSERT(aArgv, "aArgv must be initialized before CheckArg()");
+ const char* rv = nullptr;
+ if (ARG_FOUND == CheckArg(aArgc, aArgv, sMatch, &rv, aFlags)) {
+ errno = 0;
+ char* endptr = nullptr;
+ uint64_t conv = std::strtoull(rv, &endptr, 10);
+ if (errno == 0 && endptr && *endptr == '\0') {
+ return Some(conv);
+ }
+ }
+ return Nothing();
+}
+
+/// Put()
+
+template <>
+inline void CommandLineArg<const char*>::Put(const char* aValue,
+ std::vector<std::string>& aArgs) {
+ aArgs.push_back(Name());
+ aArgs.push_back(aValue);
+}
+
+template <>
+inline void CommandLineArg<bool>::Put(bool aValue,
+ std::vector<std::string>& aArgs) {
+ if (aValue) {
+ aArgs.push_back(Name());
+ }
+}
+
+template <>
+inline void CommandLineArg<bool>::Put(std::vector<std::string>& aArgs) {
+ Put(true, aArgs);
+}
+
+template <>
+inline void CommandLineArg<uint64_t>::Put(uint64_t aValue,
+ std::vector<std::string>& aArgs) {
+ aArgs.push_back(Name());
+ aArgs.push_back(std::to_string(aValue));
+}
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic push
+# pragma GCC diagnostic ignored "-Wunused-variable"
+#endif
+
+static CommandLineArg<const char*> sParentBuildID{"-parentBuildID",
+ "parentbuildid"};
+static CommandLineArg<const char*> sAppDir{"-appDir", "appdir"};
+static CommandLineArg<const char*> sProfile{"-profile", "profile"};
+
+static CommandLineArg<uint64_t> sJsInitHandle{"-jsInitHandle", "jsinithandle"};
+static CommandLineArg<uint64_t> sJsInitLen{"-jsInitLen", "jsinitlen"};
+static CommandLineArg<uint64_t> sPrefsHandle{"-prefsHandle", "prefshandle"};
+static CommandLineArg<uint64_t> sPrefsLen{"-prefsLen", "prefslen"};
+static CommandLineArg<uint64_t> sPrefMapHandle{"-prefMapHandle",
+ "prefmaphandle"};
+static CommandLineArg<uint64_t> sPrefMapSize{"-prefMapSize", "prefmapsize"};
+
+static CommandLineArg<uint64_t> sChildID{"-childID", "childid"};
+
+static CommandLineArg<uint64_t> sSandboxingKind{"-sandboxingKind",
+ "sandboxingkind"};
+
+static CommandLineArg<bool> sSafeMode{"-safeMode", "safemode"};
+
+static CommandLineArg<bool> sIsForBrowser{"-isForBrowser", "isforbrowser"};
+static CommandLineArg<bool> sNotForBrowser{"-notForBrowser", "notforbrowser"};
+
+#if defined(XP_WIN)
+# if defined(MOZ_SANDBOX)
+static CommandLineArg<bool> sWin32kLockedDown{"-win32kLockedDown",
+ "win32klockeddown"};
+# endif // defined(MOZ_SANDBOX)
+# if defined(ACCESSIBILITY)
+static CommandLineArg<uint64_t> sA11yResourceId{"-a11yResourceId",
+ "a11yresourceid"};
+# endif // defined(ACCESSIBILITY)
+static CommandLineArg<bool> sDisableDynamicDllBlocklist{
+ "-disableDynamicBlocklist", "disabledynamicblocklist"};
+#endif // defined(XP_WIN)
+
+#if defined(__GNUC__)
+# pragma GCC diagnostic pop
+#endif
+
+} // namespace geckoargs
+
+} // namespace mozilla
+
+#endif // mozilla_GeckoArgs_h
diff --git a/toolkit/xre/LauncherRegistryInfo.cpp b/toolkit/xre/LauncherRegistryInfo.cpp
new file mode 100644
index 0000000000..70f6ea2f18
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.cpp
@@ -0,0 +1,651 @@
+/* -*- 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 "commonupdatedir.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+
+#include <cwctype>
+#include <shlobj.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);
+}
+
+static mozilla::LauncherResult<mozilla::UniquePtr<wchar_t[]>>
+ReadRegistryValueString(const nsAutoRegKey& aKey, const std::wstring& aName) {
+ mozilla::UniquePtr<wchar_t[]> buf;
+ DWORD dataLen;
+ LSTATUS status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(),
+ RRF_RT_REG_SZ, nullptr, nullptr, &dataLen);
+ if (status == ERROR_FILE_NOT_FOUND) {
+ return buf;
+ }
+
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ buf = mozilla::MakeUnique<wchar_t[]>(dataLen / sizeof(wchar_t));
+
+ status = ::RegGetValueW(aKey.get(), nullptr, aName.c_str(), RRF_RT_REG_SZ,
+ nullptr, buf.get(), &dataLen);
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ return buf;
+}
+
+static mozilla::LauncherVoidResult WriteRegistryValueString(
+ const nsAutoRegKey& aKey, const std::wstring& aName,
+ const std::wstring& aValue) {
+ DWORD dataBytes = (aValue.size() + 1) * sizeof(wchar_t);
+ LSTATUS status = ::RegSetValueExW(
+ aKey.get(), aName.c_str(), /*Reserved*/ 0, REG_SZ,
+ reinterpret_cast<const BYTE*>(aValue.c_str()), dataBytes);
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ return mozilla::Ok();
+}
+
+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";
+const wchar_t LauncherRegistryInfo::kBlocklistSuffix[] = L"|Blocklist";
+
+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;
+}
+
+const std::wstring& LauncherRegistryInfo::ResolveBlocklistValueName() {
+ if (mBlocklistValueName.empty()) {
+ mBlocklistValueName.assign(mBinPath);
+ mBlocklistValueName.append(kBlocklistSuffix,
+ ArrayLength(kBlocklistSuffix) - 1);
+ }
+
+ return mBlocklistValueName;
+}
+
+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);
+}
+
+LauncherResult<std::wstring>
+LauncherRegistryInfo::BuildDefaultBlocklistFilename() {
+ // These flags are chosen to avoid I/O, see bug 1363398.
+ const DWORD flags =
+ KF_FLAG_SIMPLE_IDLIST | KF_FLAG_DONT_VERIFY | KF_FLAG_NO_ALIAS;
+ PWSTR rawPath = nullptr;
+ HRESULT hr =
+ ::SHGetKnownFolderPath(FOLDERID_RoamingAppData, flags, nullptr, &rawPath);
+ if (FAILED(hr)) {
+ ::CoTaskMemFree(rawPath);
+ return LAUNCHER_ERROR_FROM_HRESULT(hr);
+ }
+
+ UniquePtr<wchar_t, CoTaskMemFreeDeleter> appDataPath(rawPath);
+ std::wstring defaultBlocklistPath(appDataPath.get());
+
+ UniquePtr<NS_tchar[]> hash;
+ std::wstring binPathLower;
+ binPathLower.reserve(mBinPath.size());
+ std::transform(mBinPath.begin(), mBinPath.end(),
+ std::back_inserter(binPathLower), std::towlower);
+ if (!::GetInstallHash(reinterpret_cast<const char16_t*>(binPathLower.c_str()),
+ hash)) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_INVALID_DATA);
+ }
+
+ defaultBlocklistPath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\blocklist-");
+ defaultBlocklistPath.append(hash.get());
+
+ return defaultBlocklistPath;
+}
+
+LauncherResult<std::wstring> LauncherRegistryInfo::GetBlocklistFileName() {
+ LauncherResult<Disposition> disposition = Open();
+ if (disposition.isErr()) {
+ return disposition.propagateErr();
+ }
+
+ LauncherResult<UniquePtr<wchar_t[]>> readResult =
+ ReadRegistryValueString(mRegKey, ResolveBlocklistValueName());
+ if (readResult.isErr()) {
+ return readResult.propagateErr();
+ }
+
+ if (readResult.inspect()) {
+ UniquePtr<wchar_t[]> buf = readResult.unwrap();
+ return std::wstring(buf.get());
+ }
+
+ LauncherResult<std::wstring> defaultBlocklistPath =
+ BuildDefaultBlocklistFilename();
+ if (defaultBlocklistPath.isErr()) {
+ return defaultBlocklistPath.propagateErr();
+ }
+
+ LauncherVoidResult writeResult = WriteRegistryValueString(
+ mRegKey, ResolveBlocklistValueName(), defaultBlocklistPath.inspect());
+ if (writeResult.isErr()) {
+ return writeResult.propagateErr();
+ }
+
+ return defaultBlocklistPath;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/LauncherRegistryInfo.h b/toolkit/xre/LauncherRegistryInfo.h
new file mode 100644
index 0000000000..c89ed66a69
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.h
@@ -0,0 +1,104 @@
+/* -*- 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();
+ LauncherResult<std::wstring> GetBlocklistFileName();
+
+ 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();
+ LauncherResult<std::wstring> BuildDefaultBlocklistFilename();
+
+ const std::wstring& ResolveLauncherValueName();
+ const std::wstring& ResolveBrowserValueName();
+ const std::wstring& ResolveImageTimestampValueName();
+ const std::wstring& ResolveTelemetryValueName();
+ const std::wstring& ResolveBlocklistValueName();
+
+ 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;
+ std::wstring mBlocklistValueName;
+
+ 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[];
+ static const wchar_t kBlocklistSuffix[];
+};
+
+} // 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..d632209c4b
--- /dev/null
+++ b/toolkit/xre/MacApplicationDelegate.mm
@@ -0,0 +1,409 @@
+/* -*- 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>
+#include "NativeMenuMac.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"
+#include "nsStandaloneNativeMenu.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_IGNORE_BLOCK;
+
+ [GeckoNSApplication sharedApplication];
+
+ NS_OBJC_END_TRY_IGNORE_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_IGNORE_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_IGNORE_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_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_BLOCK_RETURN(nil);
+}
+
+- (void)dealloc {
+ NS_OBJC_BEGIN_TRY_IGNORE_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_IGNORE_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_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_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_BLOCK_RETURN;
+
+ // 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> dockMenuInterface;
+ rv = dockSupport->GetDockMenu(getter_AddRefs(dockMenuInterface));
+ if (NS_FAILED(rv) || !dockMenuInterface) return menu;
+
+ RefPtr<mozilla::widget::NativeMenuMac> dockMenu =
+ static_cast<nsStandaloneNativeMenu*>(dockMenuInterface.get())->GetNativeMenu();
+
+ // Give the menu the opportunity to update itself before display.
+ dockMenu->MenuWillOpen();
+
+ // Obtain a copy of the native menu.
+ NSMenu* nativeDockMenu = dockMenu->NativeNSMenu();
+ if (!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_BLOCK_RETURN(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..c6c0d97c18
--- /dev/null
+++ b/toolkit/xre/MacLaunchHelper.mm
@@ -0,0 +1,117 @@
+/* -*- 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 <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/MacRunFromDmgUtils.h b/toolkit/xre/MacRunFromDmgUtils.h
new file mode 100644
index 0000000000..d93e6445b1
--- /dev/null
+++ b/toolkit/xre/MacRunFromDmgUtils.h
@@ -0,0 +1,33 @@
+/* -*- 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 MacRunFromDmgUtils_h_
+#define MacRunFromDmgUtils_h_
+
+namespace mozilla::MacRunFromDmgUtils {
+
+/**
+ * Returns true if the app is running from the read-only filesystem of a
+ * mounted .dmg. Returns false if not, or if we fail to determine whether it
+ * is.
+ */
+bool IsAppRunningFromDmg();
+
+/**
+ * Checks whether the app is running from a read-only .dmg image or a read-only
+ * app translocated location and, if so, asks the user for permission before
+ * attempting to install the app and launch it.
+ *
+ * Returns true if the app has been installed and relaunched, in which case
+ * this instance of the app should exit.
+ */
+bool MaybeInstallAndRelaunch();
+
+} // namespace mozilla::MacRunFromDmgUtils
+
+#endif
diff --git a/toolkit/xre/MacRunFromDmgUtils.mm b/toolkit/xre/MacRunFromDmgUtils.mm
new file mode 100644
index 0000000000..38381b5c48
--- /dev/null
+++ b/toolkit/xre/MacRunFromDmgUtils.mm
@@ -0,0 +1,513 @@
+/* -*- 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 <AppKit/AppKit.h>
+#include <ApplicationServices/ApplicationServices.h>
+#include <CoreFoundation/CoreFoundation.h>
+#include <CoreServices/CoreServices.h>
+#include <IOKit/IOKitLib.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/mount.h>
+#include <sys/param.h>
+
+#include "MacRunFromDmgUtils.h"
+#include "MacLaunchHelper.h"
+
+#include "mozilla/ErrorResult.h"
+#include "mozilla/glean/GleanMetrics.h"
+#include "mozilla/intl/Localization.h"
+#include "mozilla/Telemetry.h"
+#include "nsCocoaFeatures.h"
+#include "nsCocoaUtils.h"
+#include "nsCommandLine.h"
+#include "nsCommandLineServiceMac.h"
+#include "nsILocalFileMac.h"
+#include "nsIMacDockSupport.h"
+#include "nsObjCExceptions.h"
+#include "prenv.h"
+#include "nsString.h"
+#ifdef MOZ_UPDATER
+# include "nsUpdateDriver.h"
+#endif
+#include "SDKDeclarations.h"
+
+// For IOKit docs, see:
+// https://developer.apple.com/documentation/iokit
+// https://developer.apple.com/library/archive/documentation/DeviceDrivers/Conceptual/IOKitFundamentals/
+
+namespace mozilla::MacRunFromDmgUtils {
+
+/**
+ * Opens a dialog to ask the user whether the existing app in the Applications
+ * folder should be launched, or if the user wants to proceed with launching
+ * the app from the .dmg.
+ * Returns true if the dialog is successfully opened and the user chooses to
+ * launch the app from the Applications folder, otherwise returns false.
+ */
+static bool AskUserIfWeShouldLaunchExistingInstall() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // Try to get the localized strings:
+ nsTArray<nsCString> resIds = {
+ "branding/brand.ftl"_ns,
+ "toolkit/global/run-from-dmg.ftl"_ns,
+ };
+ RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true);
+
+ ErrorResult rv;
+ nsAutoCString mozTitle, mozMessage, mozLaunchExisting, mozLaunchFromDMG;
+ l10n->FormatValueSync("prompt-to-launch-existing-app-title"_ns, {}, mozTitle, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-launch-existing-app-message"_ns, {}, mozMessage, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-launch-existing-app-yes-button"_ns, {}, mozLaunchExisting, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-launch-existing-app-no-button"_ns, {}, mozLaunchFromDMG, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+
+ NSString* title = [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozTitle.get())];
+ NSString* message =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozMessage.get())];
+ NSString* launchExisting =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozLaunchExisting.get())];
+ NSString* launchFromDMG =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozLaunchFromDMG.get())];
+
+ NSAlert* alert = [[[NSAlert alloc] init] autorelease];
+
+ // Note that we don't set an icon since the app icon is used by default.
+ [alert setAlertStyle:NSAlertStyleInformational];
+ [alert setMessageText:title];
+ [alert setInformativeText:message];
+ // Note that if the user hits 'Enter' the "Install" button is activated,
+ // whereas if they hit 'Space' the "Don't Install" button is activated.
+ // That's standard behavior so probably desirable.
+ [alert addButtonWithTitle:launchExisting];
+ NSButton* launchFromDMGButton = [alert addButtonWithTitle:launchFromDMG];
+ // Since the "Don't Install" button doesn't have the title "Cancel" we need
+ // to map the Escape key to it manually:
+ [launchFromDMGButton setKeyEquivalent:@"\e"];
+
+ __block NSInteger result = -1;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ result = [alert runModal];
+ [NSApp stop:nil];
+ });
+
+ // We need to call run on NSApp here for accessibility. See
+ // AskUserIfWeShouldInstall for a detailed explanation.
+ [NSApp run];
+ MOZ_ASSERT(result != -1);
+
+ return result == NSAlertFirstButtonReturn;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+/**
+ * Opens a dialog to ask the user whether the app should be installed to their
+ * Applications folder. Returns true if the dialog is successfully opened and
+ * the user accept, otherwise returns false.
+ */
+static bool AskUserIfWeShouldInstall() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ // Try to get the localized strings:
+ nsTArray<nsCString> resIds = {
+ "branding/brand.ftl"_ns,
+ "toolkit/global/run-from-dmg.ftl"_ns,
+ };
+ RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true);
+
+ ErrorResult rv;
+ nsAutoCString mozTitle, mozMessage, mozInstall, mozDontInstall;
+ l10n->FormatValueSync("prompt-to-install-title"_ns, {}, mozTitle, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-install-message"_ns, {}, mozMessage, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-install-yes-button"_ns, {}, mozInstall, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+ l10n->FormatValueSync("prompt-to-install-no-button"_ns, {}, mozDontInstall, rv);
+ if (rv.Failed()) {
+ return false;
+ }
+
+ NSString* title = [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozTitle.get())];
+ NSString* message =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozMessage.get())];
+ NSString* install =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozInstall.get())];
+ NSString* dontInstall =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozDontInstall.get())];
+
+ NSAlert* alert = [[[NSAlert alloc] init] autorelease];
+
+ // Note that we don't set an icon since the app icon is used by default.
+ [alert setAlertStyle:NSAlertStyleInformational];
+ [alert setMessageText:title];
+ [alert setInformativeText:message];
+ // Note that if the user hits 'Enter' the "Install" button is activated,
+ // whereas if they hit 'Space' the "Don't Install" button is activated.
+ // That's standard behavior so probably desirable.
+ [alert addButtonWithTitle:install];
+ NSButton* dontInstallButton = [alert addButtonWithTitle:dontInstall];
+ // Since the "Don't Install" button doesn't have the title "Cancel" we need
+ // to map the Escape key to it manually:
+ [dontInstallButton setKeyEquivalent:@"\e"];
+
+ // We need to call run on NSApp to allow accessibility. We only run it
+ // for this specific alert which blocks the app's loop until the user
+ // responds, it then subsequently stops the app's loop.
+ //
+ // AskUserIfWeShouldInstall
+ // |
+ // | ---> [NSApp run]
+ // | |
+ // | | -----> task
+ // | | | -----------> [alert runModal]
+ // | | | | (User selects button)
+ // | | | <--------------- done
+ // | | |
+ // | | | -----------> [NSApp stop:nil]
+ // | | | <-----------
+ // | | <--------
+ // | <-------
+ // done
+ __block NSInteger result = -1;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ result = [alert runModal];
+ [NSApp stop:nil];
+ });
+
+ [NSApp run];
+ MOZ_ASSERT(result != -1);
+
+ return result == NSAlertFirstButtonReturn;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+static void ShowInstallFailedDialog() {
+ NS_OBJC_BEGIN_TRY_IGNORE_BLOCK;
+
+ // Try to get the localized strings:
+ nsTArray<nsCString> resIds = {
+ "branding/brand.ftl"_ns,
+ "toolkit/global/run-from-dmg.ftl"_ns,
+ };
+ RefPtr<intl::Localization> l10n = intl::Localization::Create(resIds, true);
+
+ ErrorResult rv;
+ nsAutoCString mozTitle, mozMessage;
+ l10n->FormatValueSync("install-failed-title"_ns, {}, mozTitle, rv);
+ if (rv.Failed()) {
+ return;
+ }
+ l10n->FormatValueSync("install-failed-message"_ns, {}, mozMessage, rv);
+ if (rv.Failed()) {
+ return;
+ }
+
+ NSString* title = [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozTitle.get())];
+ NSString* message =
+ [NSString stringWithUTF8String:reinterpret_cast<const char*>(mozMessage.get())];
+
+ NSAlert* alert = [[[NSAlert alloc] init] autorelease];
+
+ [alert setAlertStyle:NSAlertStyleWarning];
+ [alert setMessageText:title];
+ [alert setInformativeText:message];
+
+ __block NSInteger result = -1;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ result = [alert runModal];
+ [NSApp stop:nil];
+ });
+
+ // We need to call run on NSApp here for accessibility. See
+ // AskUserIfWeShouldInstall for a detailed explanation.
+ [NSApp run];
+ MOZ_ASSERT(result != -1);
+ (void)result;
+
+ NS_OBJC_END_TRY_IGNORE_BLOCK;
+}
+
+/**
+ * Helper to launch macOS tasks via NSTask.
+ */
+static void LaunchTask(NSString* aPath, NSArray* aArguments) {
+ if (@available(macOS 10.13, *)) {
+ NSTask* task = [[NSTask alloc] init];
+ [task setExecutableURL:[NSURL fileURLWithPath:aPath]];
+ if (aArguments) {
+ [task setArguments:aArguments];
+ }
+ [task launchAndReturnError:nil];
+ [task release];
+ } else {
+ NSArray* arguments = aArguments;
+ if (!arguments) {
+ arguments = @[];
+ }
+ [NSTask launchedTaskWithLaunchPath:aPath arguments:arguments];
+ }
+}
+
+static void LaunchInstalledApp(NSString* aBundlePath) {
+ LaunchTask([[NSBundle bundleWithPath:aBundlePath] executablePath], nil);
+}
+
+static void RegisterAppWithLaunchServices(NSString* aBundlePath) {
+ NSArray* arguments = @[ @"-f", aBundlePath ];
+ LaunchTask(@"/System/Library/Frameworks/CoreServices.framework/Frameworks/"
+ @"LaunchServices.framework/Support/lsregister",
+ arguments);
+}
+
+static void StripQuarantineBit(NSString* aBundlePath) {
+ NSArray* arguments = @[ @"-d", @"com.apple.quarantine", aBundlePath ];
+ LaunchTask(@"/usr/bin/xattr", arguments);
+}
+
+#ifdef MOZ_UPDATER
+bool LaunchElevatedDmgInstall(NSString* aBundlePath, NSArray* aArguments) {
+ NSTask* task;
+ if (@available(macOS 10.13, *)) {
+ task = [[NSTask alloc] init];
+ [task setExecutableURL:[NSURL fileURLWithPath:aBundlePath]];
+ if (aArguments) {
+ [task setArguments:aArguments];
+ }
+ [task launchAndReturnError:nil];
+ } else {
+ NSArray* arguments = aArguments;
+ if (!arguments) {
+ arguments = @[];
+ }
+ task = [NSTask launchedTaskWithLaunchPath:aBundlePath arguments:arguments];
+ }
+
+ bool didSucceed = InstallPrivilegedHelper();
+ [task waitUntilExit];
+ if (@available(macOS 10.13, *)) {
+ [task release];
+ }
+ if (!didSucceed) {
+ AbortElevatedUpdate();
+ }
+
+ return didSucceed;
+}
+#endif
+
+// Note: both arguments are expected to contain the app name (to end with
+// '.app').
+static bool InstallFromPath(NSString* aBundlePath, NSString* aDestPath) {
+ bool installSuccessful = false;
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ if ([fileManager copyItemAtPath:aBundlePath toPath:aDestPath error:nil]) {
+ RegisterAppWithLaunchServices(aDestPath);
+ StripQuarantineBit(aDestPath);
+ installSuccessful = true;
+ }
+
+#ifdef MOZ_UPDATER
+ // The installation may have been unsuccessful if the user did not have the
+ // rights to write to the Applications directory. Check for this situation and
+ // launch an elevated installation if necessary. Rather than creating a new,
+ // dedicated executable for this installation and incurring the
+ // added maintenance burden of yet another executable, we are using the
+ // updater binary. Since bug 394984 landed, the updater has the ability to
+ // install and launch itself as a Privileged Helper tool, which is what is
+ // necessary here.
+ NSString* destDir = [aDestPath stringByDeletingLastPathComponent];
+ if (!installSuccessful && ![fileManager isWritableFileAtPath:destDir]) {
+ NSString* updaterBinPath = [NSString pathWithComponents:@[
+ aBundlePath, @"Contents", @"MacOS", [NSString stringWithUTF8String:UPDATER_APP], @"Contents",
+ @"MacOS", [NSString stringWithUTF8String:UPDATER_BIN]
+ ]];
+
+ NSArray* arguments = @[ @"-dmgInstall", aBundlePath, aDestPath ];
+ LaunchElevatedDmgInstall(updaterBinPath, arguments);
+ installSuccessful = [fileManager fileExistsAtPath:aDestPath];
+ }
+#endif
+
+ if (!installSuccessful) {
+ return false;
+ }
+
+ // Pin to dock:
+ nsresult rv;
+ nsCOMPtr<nsIMacDockSupport> dockSupport =
+ do_GetService("@mozilla.org/widget/macdocksupport;1", &rv);
+ if (NS_SUCCEEDED(rv) && dockSupport) {
+ bool isInDock;
+ nsAutoString appPath, appToReplacePath;
+ nsCocoaUtils::GetStringForNSString(aDestPath, appPath);
+ nsCocoaUtils::GetStringForNSString(aBundlePath, appToReplacePath);
+ dockSupport->EnsureAppIsPinnedToDock(appPath, appToReplacePath, &isInDock);
+ }
+
+ return true;
+}
+
+bool IsAppRunningFromDmg() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ const char* path = [[[NSBundle mainBundle] bundlePath] fileSystemRepresentation];
+
+ struct statfs statfsBuf;
+ if (statfs(path, &statfsBuf) != 0) {
+ return false;
+ }
+
+ // Optimization to minimize impact on startup time:
+ if (!(statfsBuf.f_flags & MNT_RDONLY)) {
+ return false;
+ }
+
+ // Get the "BSD device name" ("diskXsY", as found in /dev) of the filesystem
+ // our app is on so we can use IOKit to get its IOMedia object:
+ const char devDirPath[] = "/dev/";
+ const int devDirPathLength = strlen(devDirPath);
+ if (strncmp(statfsBuf.f_mntfromname, devDirPath, devDirPathLength) != 0) {
+ // This has been observed to happen under App Translocation. In this case,
+ // path is the translocated path, and f_mntfromname is the path under
+ // /Volumes. Do another stat on that path.
+ nsCString volumesPath(statfsBuf.f_mntfromname);
+ if (statfs(volumesPath.get(), &statfsBuf) != 0) {
+ return false;
+ }
+
+ if (strncmp(statfsBuf.f_mntfromname, devDirPath, devDirPathLength) != 0) {
+ // It still doesn't begin with /dev/, bail out.
+ return false;
+ }
+ }
+ const char* bsdDeviceName = statfsBuf.f_mntfromname + devDirPathLength;
+
+ // Get the IOMedia object:
+ // (Note: IOServiceGetMatchingServices takes ownership of serviceDict's ref.)
+ CFMutableDictionaryRef serviceDict = IOBSDNameMatching(kIOMasterPortDefault, 0, bsdDeviceName);
+ if (!serviceDict) {
+ return false;
+ }
+ io_service_t media = IOServiceGetMatchingService(kIOMasterPortDefault, serviceDict);
+ if (!media || !IOObjectConformsTo(media, "IOMedia")) {
+ return false;
+ }
+
+ // Search the parent chain for a service implementing the disk image class
+ // (taking care to start with `media` itself):
+ io_service_t imageDrive = IO_OBJECT_NULL;
+ io_iterator_t iter;
+ if (IORegistryEntryCreateIterator(media, kIOServicePlane,
+ kIORegistryIterateRecursively | kIORegistryIterateParents,
+ &iter) != KERN_SUCCESS) {
+ IOObjectRelease(media);
+ return false;
+ }
+ const char* imageClass =
+ nsCocoaFeatures::OnMontereyOrLater() ? "AppleDiskImageDevice" : "IOHDIXHDDrive";
+ for (imageDrive = media; imageDrive; imageDrive = IOIteratorNext(iter)) {
+ if (IOObjectConformsTo(imageDrive, imageClass)) {
+ break;
+ }
+ IOObjectRelease(imageDrive);
+ }
+ IOObjectRelease(iter);
+
+ if (imageDrive) {
+ IOObjectRelease(imageDrive);
+ return true;
+ }
+ return false;
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+bool MaybeInstallAndRelaunch() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ @autoreleasepool {
+ bool isFromDmg = IsAppRunningFromDmg();
+ bool isTranslocated = false;
+ if (!isFromDmg) {
+ NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
+ if ([bundlePath containsString:@"/AppTranslocation/"]) {
+ isTranslocated = true;
+ }
+ }
+
+ if (!isFromDmg && !isTranslocated) {
+ return false;
+ }
+
+ // The Applications directory may not be at /Applications, although in
+ // practice we're unlikely to encounter since run-from-.dmg is really an
+ // issue with novice mac users. Still, look it up correctly:
+ NSArray* applicationsDirs =
+ NSSearchPathForDirectoriesInDomains(NSApplicationDirectory, NSLocalDomainMask, YES);
+ NSString* applicationsDir = applicationsDirs[0];
+
+ // Sanity check dir exists
+ NSFileManager* fileManager = [NSFileManager defaultManager];
+ BOOL isDir;
+ if (![fileManager fileExistsAtPath:applicationsDir isDirectory:&isDir] || !isDir) {
+ return false;
+ }
+
+ NSString* bundlePath = [[NSBundle mainBundle] bundlePath];
+ NSString* appName = [bundlePath lastPathComponent];
+ NSString* destPath = [applicationsDir stringByAppendingPathComponent:appName];
+
+ // If the app (an app of the same name) is already installed we can't really
+ // tell without asking if we're dealing with the edge case of an
+ // inexperienced user running from .dmg by mistake, or if we're dealing with
+ // a more sophisticated user intentionally running from .dmg.
+ if ([fileManager fileExistsAtPath:destPath]) {
+ if (AskUserIfWeShouldLaunchExistingInstall()) {
+ StripQuarantineBit(destPath);
+ LaunchInstalledApp(destPath);
+ return true;
+ }
+ return false;
+ }
+
+ if (!AskUserIfWeShouldInstall()) {
+ return false;
+ }
+
+ if (!InstallFromPath(bundlePath, destPath)) {
+ ShowInstallFailedDialog();
+ return false;
+ }
+
+ LaunchInstalledApp(destPath);
+
+ return true;
+ }
+
+ NS_OBJC_END_TRY_BLOCK_RETURN(false);
+}
+
+} // namespace mozilla::MacRunFromDmgUtils
diff --git a/toolkit/xre/MultiInstanceLock.cpp b/toolkit/xre/MultiInstanceLock.cpp
new file mode 100644
index 0000000000..230df80a1f
--- /dev/null
+++ b/toolkit/xre/MultiInstanceLock.cpp
@@ -0,0 +1,295 @@
+/* -*- 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
+
+#ifdef XP_WIN
+# include <shlwapi.h>
+#else
+# include <fcntl.h>
+# include <sys/stat.h>
+# include <sys/types.h>
+#endif
+
+#ifdef XP_WIN
+# include "WinUtils.h"
+#endif
+
+#ifdef MOZ_WIDGET_COCOA
+# include "nsILocalFileMac.h"
+#endif
+
+namespace mozilla {
+
+static bool GetLockFileName(const char* nameToken, const char16_t* installPath,
+ nsCString& filePath) {
+#ifdef XP_WIN
+ // On Windows, the lock file is placed at the path
+ // [updateDirectory]\[nameToken]-[pathHash], so first we need to get the
+ // update directory path and then append the file name.
+
+ // Note: This will return something like
+ // C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38\updates\<hash>
+ // But we actually are going to want to return the root update directory,
+ // the grandparent of this directory, which will look something like this:
+ // C:\ProgramData\Mozilla-1de4eec8-1241-4177-a864-e594e8d1fb38
+ mozilla::UniquePtr<wchar_t[]> updateDir;
+ HRESULT hr = GetCommonUpdateDirectory(
+ reinterpret_cast<const wchar_t*>(installPath), updateDir);
+ if (FAILED(hr)) {
+ return false;
+ }
+
+ // For the path manipulation that we are about to do, it is important that
+ // the update directory have no trailing slash.
+ size_t len = wcslen(updateDir.get());
+ if (len == 0) {
+ return false;
+ }
+ if (updateDir.get()[len - 1] == '/' || updateDir.get()[len - 1] == '\\') {
+ updateDir.get()[len - 1] = '\0';
+ }
+
+ wchar_t* hashPtr = PathFindFileNameW(updateDir.get());
+ // PathFindFileNameW returns a pointer to the beginning of the string on
+ // failure.
+ if (hashPtr == updateDir.get()) {
+ return false;
+ }
+
+ // We need to make a copy of the hash before we modify updateDir to get the
+ // root update dir.
+ size_t hashSize = wcslen(hashPtr) + 1;
+ mozilla::UniquePtr<wchar_t[]> hash = mozilla::MakeUnique<wchar_t[]>(hashSize);
+ errno_t error = wcscpy_s(hash.get(), hashSize, hashPtr);
+ if (error != 0) {
+ return false;
+ }
+
+ // Get the root update dir from the update dir.
+ BOOL success = PathRemoveFileSpecW(updateDir.get());
+ if (!success) {
+ return false;
+ }
+ success = PathRemoveFileSpecW(updateDir.get());
+ if (!success) {
+ return false;
+ }
+
+ filePath =
+ nsPrintfCString("%s\\%s-%s", NS_ConvertUTF16toUTF8(updateDir.get()).get(),
+ nameToken, NS_ConvertUTF16toUTF8(hash.get()).get());
+
+#else
+ mozilla::UniquePtr<NS_tchar[]> pathHash;
+ if (!GetInstallHash(installPath, pathHash)) {
+ return false;
+ }
+
+ // 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;
+ if (!GetLockFileName(nameToken, installPath, filePath)) {
+ return MULTI_INSTANCE_LOCK_HANDLE_ERROR;
+ }
+
+ // 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, 0, 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);
+ ::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
+}
+
+already_AddRefed<nsIFile> GetNormalizedAppFile(nsIFile* aAppFile) {
+ // If we're given an app file, use it; otherwise, get it from the ambient
+ // directory service.
+ nsresult rv;
+ nsCOMPtr<nsIFile> appFile;
+ if (aAppFile) {
+ rv = aAppFile->Clone(getter_AddRefs(appFile));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ } else {
+ nsCOMPtr<nsIProperties> dirSvc =
+ do_GetService(NS_DIRECTORY_SERVICE_CONTRACTID);
+ NS_ENSURE_TRUE(dirSvc, nullptr);
+
+ rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(appFile));
+ NS_ENSURE_SUCCESS(rv, nullptr);
+ }
+
+ // 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.
+ // On Linux XRE_EXECUTABLE_FILE already seems to be set to the correct path.
+ //
+ // See similar nsXREDirProvider::GetInstallHash. The main difference here is
+ // to allow lookup to fail on OSX, because some tests use a nonexistent
+ // appFile.
+#ifdef XP_WIN
+ // Windows provides a way to get the correct case.
+ if (!mozilla::widget::WinUtils::ResolveJunctionPointsAndSymLinks(appFile)) {
+ 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(appFile);
+ if (macFile && NS_SUCCEEDED(macFile->GetFSRef(&ref)) &&
+ NS_SUCCEEDED(
+ NS_NewLocalFileWithFSRef(&ref, true, getter_AddRefs(macFile)))) {
+ appFile = static_cast<nsIFile*>(macFile);
+ } else {
+ NS_WARNING("Failed to resolve install directory.");
+ }
+#endif
+
+ return appFile.forget();
+}
+
+}; // namespace mozilla
diff --git a/toolkit/xre/MultiInstanceLock.h b/toolkit/xre/MultiInstanceLock.h
new file mode 100644
index 0000000000..28d0e17d87
--- /dev/null
+++ b/toolkit/xre/MultiInstanceLock.h
@@ -0,0 +1,92 @@
+/* -*- 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);
+
+// 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.
+// When aAppFile is NULL, this function returns a nsIFile with a normalized
+// path for the currently running binary. When aAppFile is not null,
+// this function ensures the file path is properly normalized.
+already_AddRefed<nsIFile> GetNormalizedAppFile(nsIFile* aAppFile);
+
+}; // namespace mozilla
+
+#endif // MULTIINSTANCELOCK_H
diff --git a/toolkit/xre/PolicyChecks.h b/toolkit/xre/PolicyChecks.h
new file mode 100644
index 0000000000..9b6cfaf9c8
--- /dev/null
+++ b/toolkit/xre/PolicyChecks.h
@@ -0,0 +1,44 @@
+/* -*- 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>
+
+# define POLICY_REGKEY_NAME L"SOFTWARE\\Policies\\Mozilla\\" MOZ_APP_BASENAME
+
+// 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, POLICY_REGKEY_NAME, 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..8ed01e6535
--- /dev/null
+++ b/toolkit/xre/ProfileReset.cpp
@@ -0,0 +1,146 @@
+/* -*- 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 "mozilla/Components.h"
+#include "mozilla/XREAppData.h"
+
+#include "mozilla/SpinEventLoopUntil.h"
+#include "mozilla/Unused.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::components::StringBundle::Service();
+ 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<nsIThread> cleanupThread;
+ rv = NS_NewNamedThread("ResetCleanup", 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("xre:ProfileResetCreateBackup"_ns,
+ [&]() { 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..9323ff1a9c
--- /dev/null
+++ b/toolkit/xre/SafeMode.h
@@ -0,0 +1,90 @@
+/* -*- 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, "safe-mode", 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/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..b59964e794
--- /dev/null
+++ b/toolkit/xre/components.conf
@@ -0,0 +1,71 @@
+# -*- 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 = [
+ {
+ 'name': 'XULRuntime',
+ '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', 'nsICrashReporter'],
+ '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'],
+ },
+ {
+ 'cid': '{4e4aae11-8901-46cc-8217-dad7c5415873}',
+ 'contract_ids': ['@mozilla.org/embedcomp/dialogparam;1'],
+ 'type': 'nsDialogParamBlock',
+ 'headers': ['/toolkit/components/windowwatcher/nsDialogParamBlock.h'],
+ },
+ {
+ 'cid': '{5f5e59ce-27bc-47eb-9d1f-b09ca9049836}',
+ 'contract_ids': ['@mozilla.org/toolkit/profile-service;1'],
+ 'singleton': True,
+ 'constructor': 'NS_GetToolkitProfileService',
+ 'type': 'nsToolkitProfileService',
+ 'headers': ['/toolkit/profile/nsToolkitProfileService.h'],
+ },
+]
diff --git a/toolkit/xre/detect_win32k_conflicts.h b/toolkit/xre/detect_win32k_conflicts.h
new file mode 100644
index 0000000000..62ff737bcd
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts.h
@@ -0,0 +1,20 @@
+/* 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 TOOLKIT_XRE_DETECT_WIN32K_CONFLICTS_H
+#define TOOLKIT_XRE_DETECT_WIN32K_CONFLICTS_H
+#include <cinttypes>
+
+// C interface for the `detect_win32k_conflicts` Rust crate
+
+struct ConflictingMitigationStatus {
+ bool caller_check;
+ bool sim_exec;
+ bool stack_pivot;
+};
+
+extern "C" bool detect_win32k_conflicting_mitigations(
+ ConflictingMitigationStatus* aConflictingMitigationStatus);
+
+#endif // TOOLKIT_XRE_DETECT_WIN32K_CONFLICTS_H
diff --git a/toolkit/xre/detect_win32k_conflicts/.gitignore b/toolkit/xre/detect_win32k_conflicts/.gitignore
new file mode 100644
index 0000000000..869df07dae
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/.gitignore
@@ -0,0 +1,2 @@
+/target
+Cargo.lock \ No newline at end of file
diff --git a/toolkit/xre/detect_win32k_conflicts/Cargo.toml b/toolkit/xre/detect_win32k_conflicts/Cargo.toml
new file mode 100644
index 0000000000..a94402df9e
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "detect_win32k_conflicts"
+version = "0.1.0"
+edition = "2021"
+license = "MPL-2.0"
+
+[dependencies]
+log = "0.4"
+thiserror = "1"
+winapi = { version = "0.3", features = ["winerror", "winreg"] }
diff --git a/toolkit/xre/detect_win32k_conflicts/src/detect.rs b/toolkit/xre/detect_win32k_conflicts/src/detect.rs
new file mode 100644
index 0000000000..e21c80e157
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/detect.rs
@@ -0,0 +1,165 @@
+/* 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 contains the actual functions that explore the registry for the status
+//! of the conflicting mitigations.
+
+use super::error::DetectConflictError;
+use super::registry::{RegKey, RegValue};
+
+use std::ffi::OsStr;
+
+/// Subkey that the Windows Exploit Protection status is located in
+const EXPLOIT_PROTECTION_SUBKEY: &str =
+ "SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Image File Execution Options";
+
+/// Name of the value within that subkey that contains the exploit protection status
+const MITIGATION_VALUE_NAME: &str = "MitigationOptions";
+
+/// The bit number within the value with the StackPivot status
+const STACK_PIVOT_BIT: usize = 80;
+
+/// The bit number within the value with the CallerCheck status
+const CALLER_CHECK_BIT: usize = 84;
+
+/// The bit number within the value with the SimExec status
+const SIM_EXEC_BIT: usize = 88;
+
+/// Printable, FFI-safe structure containing the status of the conflicting mitigations
+#[repr(C)]
+#[derive(Clone, Copy, Debug, Default, PartialEq)]
+pub struct ConflictingMitigationStatus {
+ /// The status of the CallerCheck feature. 0 = disabled, 1 = enabled
+ caller_check: bool,
+ /// The status of the SimExec feature. 0 = disabled, 1 = enabled
+ sim_exec: bool,
+ /// The status of the StackPivot feature. 0 = disabled, 1 = enabled
+ stack_pivot: bool,
+}
+
+impl std::fmt::Display for ConflictingMitigationStatus {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
+ use std::fmt::Write;
+ write_status(f, "CallerCheck", self.caller_check)?;
+ f.write_char(' ')?;
+ write_status(f, "SimExec", self.sim_exec)?;
+ f.write_char(' ')?;
+ write_status(f, "StackPivot", self.stack_pivot)
+ }
+}
+
+/// Search the registry for win32k-conflicting mitigations for the given process
+///
+/// `process_name` is the name of the process that will be checked for conflicting mitigations
+/// It uses the same case-insensitive algorithm that the Windows Registry uses itself to detect
+/// whether a process needs mitigations or not
+///
+/// (This will usually be `firefox.exe`)
+///
+/// # Caveat
+///
+/// Windows allows an arbitrary number of "filters", which are subkeys of the main key
+/// that also have a `MitigationOptions` value. They are used to allow filtering by path.
+///
+/// For simplicity, we "flatten" the path filters when we figure this out, and so the
+/// returned value will be the logical "OR" of all mitigations enabled with any filter
+///
+/// So if there are two entries for "C:\Firefox\firefox.exe" with mitigations `A` and `B` enabled
+/// and another entry for "C:\Program Files\Mozilla Firefox\firefox.exe" with mitigation `C`
+/// enabled, we will return that `A`, `B`, and `C` are all enabled
+pub fn get_conflicting_mitigations(
+ process_name: impl AsRef<OsStr>,
+) -> Result<ConflictingMitigationStatus, DetectConflictError> {
+ let process_name = process_name.as_ref();
+
+ let key = RegKey::root_local_machine()
+ .try_open_subkey(EXPLOIT_PROTECTION_SUBKEY)?
+ .ok_or(DetectConflictError::ExploitProtectionKeyMissing)?;
+
+ let process_key = match key.try_open_subkey(process_name)? {
+ Some(key) => key,
+ None => {
+ log::info!(
+ "process name {:?} not found in exploit protection",
+ process_name
+ );
+ return Ok(ConflictingMitigationStatus::default());
+ }
+ };
+
+ // First get mitigation status for the root key
+ let mut status = get_conflicting_mitigations_for_key(&process_key)?;
+
+ // Then get mitigation status for each subkey and logical-or them all together
+ for subkey_name in process_key.subkey_names() {
+ let subkey_name = subkey_name?;
+
+ let subkey = process_key
+ .try_open_subkey(subkey_name)?
+ .expect("a subkey somehow doesn't exist after enumerating it");
+
+ let subkey_status = get_conflicting_mitigations_for_key(&subkey)?;
+ status.caller_check |= subkey_status.caller_check;
+ status.sim_exec |= subkey_status.sim_exec;
+ status.stack_pivot |= subkey_status.stack_pivot;
+ }
+
+ log::info!(
+ "process name {:?} has mitigation status {:?}",
+ process_name,
+ status
+ );
+
+ Ok(status)
+}
+
+/// Check a key to see if it has a "MitigationOptions" value with any conflicting mitigations
+/// enabled
+fn get_conflicting_mitigations_for_key(
+ key: &RegKey,
+) -> Result<ConflictingMitigationStatus, DetectConflictError> {
+ let value = match key.try_get_value(MITIGATION_VALUE_NAME)? {
+ Some(value) => value,
+ None => return Ok(ConflictingMitigationStatus::default()),
+ };
+
+ let bits = match value {
+ RegValue::Binary(bits) => bits,
+ _ => return Ok(ConflictingMitigationStatus::default()),
+ };
+
+ Ok(ConflictingMitigationStatus {
+ caller_check: get_bit(&bits, CALLER_CHECK_BIT)?,
+ sim_exec: get_bit(&bits, SIM_EXEC_BIT)?,
+ stack_pivot: get_bit(&bits, STACK_PIVOT_BIT)?,
+ })
+}
+
+/// Retrieve a bit from a vector-of-bitflags, which is what will be returned by the registry
+/// entry for exploit protection. The bits are numbered first by increasing value within a byte,
+/// and then by increasing index within the slice.
+///
+/// Ex: bit 0 = (index 0, mask 0x01), bit 7 = (index 0, mask 0x80), bit 8 = (index 1, mask 0x01)
+fn get_bit(bytes: &[u8], bit_idx: usize) -> Result<bool, DetectConflictError> {
+ let byte = bytes
+ .get(bit_idx / 8)
+ .ok_or(DetectConflictError::RegValueTooShort)?;
+
+ Ok(if byte & (1 << (bit_idx % 8)) != 0 {
+ true
+ } else {
+ false
+ })
+}
+
+/// Write the status of a feature to the given formatter. `bit == 0` means feature is disabled,
+/// `bit == 1` means enabled
+fn write_status(
+ f: &mut std::fmt::Formatter<'_>,
+ name: &str,
+ bit: bool,
+) -> Result<(), std::fmt::Error> {
+ let status_str = if bit { "enabled" } else { "disabled" };
+ write!(f, "{} is {}.", name, status_str)
+}
diff --git a/toolkit/xre/detect_win32k_conflicts/src/error.rs b/toolkit/xre/detect_win32k_conflicts/src/error.rs
new file mode 100644
index 0000000000..cc73d04b2d
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/error.rs
@@ -0,0 +1,36 @@
+/* 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/. */
+
+//! Contains the error type for this crate
+
+use thiserror::Error;
+
+/// The error type for this crate
+#[derive(Debug, Error)]
+pub enum DetectConflictError {
+ /// An exploit protection key was not found in the registry
+ #[error("exploit protection key missing")]
+ ExploitProtectionKeyMissing,
+ /// Failed to enumerate the next registry subkey
+ #[error("failed to enumerate next registry subkey. code: {0}")]
+ RegEnumKeyFailed(u32),
+ /// Failed to get a value from the registry
+ #[error("failed to get registry value. code: {0}")]
+ RegGetValueFailed(u32),
+ /// Failed to get the length of a value from the registry
+ #[error("failed to get registry value length. code: {0}")]
+ RegGetValueLenFailed(u32),
+ /// Failed to open a registry key
+ #[error("failed to open registry key. code: {0}")]
+ RegOpenKeyFailed(u32),
+ /// Exploit Protection registry value was too short
+ #[error("exploit protection registry value too short")]
+ RegValueTooShort,
+ /// Failed to query key for subkey length
+ #[error("failed to query key for max subkey length. code: {0}")]
+ RegQueryInfoKeyFailed(u32),
+ /// A registry value had an unsupported type
+ #[error("key has unsupported value type: {0}")]
+ UnsupportedValueType(u32),
+}
diff --git a/toolkit/xre/detect_win32k_conflicts/src/ffi.rs b/toolkit/xre/detect_win32k_conflicts/src/ffi.rs
new file mode 100644
index 0000000000..826f452726
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/ffi.rs
@@ -0,0 +1,57 @@
+/* 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/. */
+
+//! C foreign-function interface for this crate
+
+use super::detect::{get_conflicting_mitigations, ConflictingMitigationStatus};
+
+/// C FFI for `get_conflicting_mitigations`
+///
+/// Checks the current process to see if any conflicting mitigations would be enabled
+/// by Windows Exploit Protection
+///
+/// `conflicting_mitigations` is an out pointer that will receive the status of the
+/// Win32k conflicting mitigations
+///
+/// On success, returns `1`. On failure, returns `0` and `conflicting_mitigations` is left
+/// untouched
+///
+/// # Safety
+///
+/// `conflicting_mitigations` must not be a non-const, non-null ptr and **must be initialized**
+///
+/// # Example
+///
+/// ```C++
+/// void myFunc() {
+/// ConflictingMitigationStatus status = {}; // GOOD - value-initializes the object
+/// if(!detect_win32k_conflicting_mitigations(&status)) {
+/// // error
+/// }
+/// }
+/// ```
+#[no_mangle]
+pub unsafe extern "C" fn detect_win32k_conflicting_mitigations(
+ conflicting_mitigations: &mut ConflictingMitigationStatus,
+) -> bool {
+ let process_path =
+ std::env::current_exe().expect("unable to determine the path of our executable");
+ let file_name = process_path
+ .file_name()
+ .expect("executable doesn't have a file name");
+
+ match get_conflicting_mitigations(file_name) {
+ Ok(status) => {
+ *conflicting_mitigations = status;
+ true
+ }
+ Err(e) => {
+ log::error!(
+ "Failed to get Win32k Lockdown conflicting mitigation status: {}",
+ e
+ );
+ false
+ }
+ }
+}
diff --git a/toolkit/xre/detect_win32k_conflicts/src/lib.rs b/toolkit/xre/detect_win32k_conflicts/src/lib.rs
new file mode 100644
index 0000000000..4333603b92
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/lib.rs
@@ -0,0 +1,18 @@
+/* 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/. */
+
+//! Crate to detect Windows Exploit Protection mitigations that may interfere with Win32k
+//! Lockdown
+
+#![deny(missing_docs)]
+
+mod detect;
+mod error;
+mod ffi;
+mod registry;
+
+pub use self::detect::get_conflicting_mitigations;
+pub use self::detect::ConflictingMitigationStatus;
+pub use self::error::DetectConflictError;
+pub use self::ffi::detect_win32k_conflicting_mitigations;
diff --git a/toolkit/xre/detect_win32k_conflicts/src/registry.rs b/toolkit/xre/detect_win32k_conflicts/src/registry.rs
new file mode 100644
index 0000000000..f375a64284
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/src/registry.rs
@@ -0,0 +1,360 @@
+/* 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 contains helpers for exploring the Windows Registry
+
+use super::error::DetectConflictError;
+
+use std::ffi::{OsStr, OsString};
+use std::os::windows::ffi::{OsStrExt, OsStringExt};
+
+use winapi::{
+ shared::{
+ minwindef::HKEY,
+ winerror::{ERROR_FILE_NOT_FOUND, ERROR_NO_MORE_ITEMS, ERROR_SUCCESS},
+ },
+ um::{
+ winnt::{KEY_READ, REG_BINARY, REG_DWORD, REG_SZ},
+ winreg::{
+ RegCloseKey, RegEnumKeyExW, RegGetValueW, RegOpenKeyExW, RegQueryInfoKeyW,
+ HKEY_LOCAL_MACHINE, RRF_RT_REG_BINARY, RRF_RT_REG_DWORD, RRF_RT_REG_SZ,
+ },
+ },
+};
+
+/// An open key in the Windows Registry
+///
+/// (Note that Windows ref-counts keys internally, so we don't need subkeys to "hang on" to
+/// their parents)
+#[derive(Debug)]
+pub struct RegKey {
+ /// The Win32 handle of the open key
+ handle: HKEY,
+}
+
+impl RegKey {
+ /// Open the global HKEY_LOCAL_MACHINE handle
+ pub fn root_local_machine() -> RegKey {
+ RegKey {
+ handle: HKEY_LOCAL_MACHINE,
+ }
+ }
+ /// Try to open a subkey relative to this key with read-only access rights
+ ///
+ /// Returns `None` if the subkey doesn't exist
+ pub fn try_open_subkey(
+ &self,
+ subkey: impl AsRef<OsStr>,
+ ) -> Result<Option<RegKey>, DetectConflictError> {
+ let win32_subkey = to_win32_string(subkey.as_ref());
+
+ let mut subkey_handle = std::ptr::null_mut();
+ let rv = unsafe {
+ RegOpenKeyExW(
+ self.handle,
+ win32_subkey.as_ptr(),
+ 0, // options
+ KEY_READ,
+ &mut subkey_handle,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ match rv {
+ ERROR_SUCCESS => {
+ assert!(!subkey_handle.is_null());
+
+ Ok(Some(RegKey {
+ handle: subkey_handle,
+ }))
+ }
+ ERROR_FILE_NOT_FOUND => Ok(None),
+ _ => Err(DetectConflictError::RegOpenKeyFailed(rv)),
+ }
+ }
+ /// Try to get a value with the given name
+ ///
+ /// Returns `None` if there is no value with that name
+ ///
+ /// Note that you can pass the empty string to get the default value
+ pub fn try_get_value(
+ &self,
+ value_name: impl AsRef<OsStr>,
+ ) -> Result<Option<RegValue>, DetectConflictError> {
+ let win32_value_name = to_win32_string(value_name.as_ref());
+
+ // First we query the value's length and type so we know how to create the data buffer
+ let mut value_type = 0;
+ let mut value_len = 0;
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_BINARY | RRF_RT_REG_DWORD | RRF_RT_REG_SZ,
+ &mut value_type,
+ std::ptr::null_mut(), // no data ptr, just query length & type
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv == ERROR_FILE_NOT_FOUND {
+ return Ok(None);
+ }
+
+ if rv != ERROR_SUCCESS || value_len == 0 {
+ return Err(DetectConflictError::RegGetValueLenFailed(rv));
+ }
+
+ if value_type == REG_SZ {
+ // buffer length is in wide characters
+ let buffer_len = value_len / 2;
+
+ assert_eq!(buffer_len * 2, value_len);
+
+ let mut buffer = vec![0u16; buffer_len.try_into().unwrap()];
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_SZ,
+ std::ptr::null_mut(), // no need to query type again
+ buffer.as_mut_ptr().cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::String(from_win32_string(&buffer))))
+ } else if value_type == REG_BINARY {
+ let mut buffer = vec![0u8; value_len.try_into().unwrap()];
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_BINARY,
+ std::ptr::null_mut(), // no need to query type again
+ buffer.as_mut_ptr().cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::Binary(buffer)))
+ } else if value_type == REG_DWORD {
+ assert_eq!(value_len, 4);
+
+ let mut buffer = 0u32;
+
+ let rv = unsafe {
+ RegGetValueW(
+ self.handle,
+ std::ptr::null(), // subkey
+ win32_value_name.as_ptr(),
+ RRF_RT_REG_DWORD,
+ std::ptr::null_mut(), // no need to query type again
+ (&mut buffer as *mut u32).cast(),
+ &mut value_len,
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegGetValueFailed(rv));
+ }
+
+ Ok(Some(RegValue::Dword(buffer)))
+ } else {
+ Err(DetectConflictError::UnsupportedValueType(value_type))
+ }
+ }
+ /// Get an iterator that returns the names of all the subkeys of this key
+ pub fn subkey_names(&self) -> SubkeyNames<'_> {
+ SubkeyNames::new(self)
+ }
+}
+
+impl Drop for RegKey {
+ fn drop(&mut self) {
+ assert!(!self.handle.is_null());
+
+ if self.handle == HKEY_LOCAL_MACHINE {
+ return;
+ }
+
+ let rv: u32 = unsafe { RegCloseKey(self.handle) }.try_into().unwrap();
+
+ if rv != ERROR_SUCCESS {
+ log::warn!("failed to close opened registry key");
+ }
+ }
+}
+
+/// An iterator through the subkeys of a key
+///
+/// Returns items of type `Result<OsString, DetectConflictError>`. If an error occurs, calling `next()` again
+/// will likely just continue to result in errors, so it's best to just abandon ship at that
+/// point
+#[derive(Debug)]
+pub struct SubkeyNames<'a> {
+ /// The key that is being iterated
+ key: &'a RegKey,
+ /// The current index of the subkey -- Incremented each successful call to `next()`
+ index: u32,
+ /// A buffer that is large enough to hold the longest subkey name
+ buffer: Option<Vec<u16>>,
+}
+
+impl<'a> SubkeyNames<'a> {
+ /// Create a new subkey name iterator
+ fn new(key: &'a RegKey) -> SubkeyNames<'a> {
+ SubkeyNames {
+ key,
+ index: 0,
+ buffer: None,
+ }
+ }
+ /// Initialize `self.buffer` with a vector that's long enough to hold the longest subkey name
+ /// (if it isn't already)
+ fn create_buffer_if_needed(&mut self) -> Result<(), DetectConflictError> {
+ if self.buffer.is_some() {
+ return Ok(());
+ }
+
+ let mut max_key_length = 0;
+
+ let rv = unsafe {
+ RegQueryInfoKeyW(
+ self.key.handle,
+ std::ptr::null_mut(), // class ptr
+ std::ptr::null_mut(), // class len ptr
+ std::ptr::null_mut(), // reserved
+ std::ptr::null_mut(), // num of subkeys ptr
+ &mut max_key_length,
+ std::ptr::null_mut(), // max class length ptr
+ std::ptr::null_mut(), // number of values ptr
+ std::ptr::null_mut(), // longest value name ptr
+ std::ptr::null_mut(), // longest value data ptr
+ std::ptr::null_mut(), // DACL ptr
+ std::ptr::null_mut(), // last write time ptr
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv != ERROR_SUCCESS {
+ return Err(DetectConflictError::RegQueryInfoKeyFailed(rv));
+ }
+
+ // +1 for the null terminator
+ let max_key_length = usize::try_from(max_key_length).unwrap() + 1;
+
+ self.buffer = Some(vec![0u16; max_key_length]);
+
+ Ok(())
+ }
+}
+
+impl<'a> Iterator for SubkeyNames<'a> {
+ type Item = Result<OsString, DetectConflictError>;
+
+ fn next(&mut self) -> Option<Self::Item> {
+ if let Err(e) = self.create_buffer_if_needed() {
+ return Some(Err(e));
+ }
+
+ let buffer = self.buffer.as_mut().unwrap();
+ let mut name_len = u32::try_from(buffer.len()).unwrap();
+
+ let rv = unsafe {
+ RegEnumKeyExW(
+ self.key.handle,
+ self.index,
+ buffer.as_mut_ptr(),
+ &mut name_len,
+ std::ptr::null_mut(), // reserved
+ std::ptr::null_mut(), // class ptr
+ std::ptr::null_mut(), // class len ptr
+ std::ptr::null_mut(), // last write time ptr
+ )
+ }
+ .try_into()
+ .unwrap();
+
+ if rv == ERROR_NO_MORE_ITEMS {
+ return None;
+ }
+
+ if rv != ERROR_SUCCESS {
+ return Some(Err(DetectConflictError::RegEnumKeyFailed(rv)));
+ }
+
+ self.index += 1;
+
+ // +1 for the null terminator
+ let name_len = usize::try_from(name_len).unwrap() + 1;
+
+ Some(Ok(from_win32_string(&buffer[0..name_len])))
+ }
+}
+
+/// A single value in the registry
+#[derive(Clone, Debug, PartialEq)]
+pub enum RegValue {
+ /// A REG_BINARY value
+ Binary(Vec<u8>),
+ /// A REG_DWORD value
+ Dword(u32),
+ /// A REG_SZ value
+ String(OsString),
+}
+
+/// Convert an OsStr to a null-terminated wide character string
+///
+/// The input string must not contain any interior null values, and the resulting wide
+/// character array will have a null terminator appended
+fn to_win32_string(s: &OsStr) -> Vec<u16> {
+ // +1 for the null terminator
+ let mut result = Vec::with_capacity(s.len() + 1);
+
+ for wc in s.encode_wide() {
+ assert_ne!(wc, 0);
+ result.push(wc);
+ }
+
+ result.push(0);
+ result
+}
+
+/// Convert a null-terminated wide character string into an OsString
+///
+/// The passed-in slice must have exactly one wide null character as the last element
+fn from_win32_string(s: &[u16]) -> OsString {
+ for (idx, wc) in s.iter().enumerate() {
+ if *wc == 0 {
+ assert_eq!(idx, s.len() - 1);
+ return OsString::from_wide(&s[0..idx]);
+ }
+ }
+ panic!("missing null terminator at end of win32 string");
+}
diff --git a/toolkit/xre/detect_win32k_conflicts/tests/basic.rs b/toolkit/xre/detect_win32k_conflicts/tests/basic.rs
new file mode 100644
index 0000000000..d16a0bd976
--- /dev/null
+++ b/toolkit/xre/detect_win32k_conflicts/tests/basic.rs
@@ -0,0 +1,14 @@
+/* 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/. */
+
+use detect_win32k_conflicts::{detect_win32k_conflicting_mitigations, ConflictingMitigationStatus};
+
+#[test]
+fn basic() {
+ let mut status = ConflictingMitigationStatus::default();
+
+ let result = unsafe { detect_win32k_conflicting_mitigations(&mut status) };
+
+ assert!(result);
+}
diff --git a/toolkit/xre/dllservices/DynamicBlocklist.h b/toolkit/xre/dllservices/DynamicBlocklist.h
new file mode 100644
index 0000000000..9bdc7f4a51
--- /dev/null
+++ b/toolkit/xre/dllservices/DynamicBlocklist.h
@@ -0,0 +1,291 @@
+/* -*- 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_DynamicBlocklist_h
+#define mozilla_DynamicBlocklist_h
+
+#include <winternl.h>
+
+#include "nsWindowsHelpers.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "mozilla/dom/Promise.h"
+# include "nsIOutputStream.h"
+# include "nsTHashSet.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+#if !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS)
+# include "mozilla/CmdLineAndEnvUtils.h"
+# define BLOCKLIST_INSERT_TEST_ENTRY
+#endif // !defined(MOZILLA_INTERNAL_API) && defined(ENABLE_TESTS)
+
+namespace mozilla {
+using DllBlockInfo = DllBlockInfoT<UNICODE_STRING>;
+
+struct DynamicBlockListBase {
+ static constexpr uint32_t kSignature = 'FFBL'; // Firefox Blocklist
+ static constexpr uint32_t kCurrentVersion = 1;
+
+ struct FileHeader {
+ uint32_t mSignature;
+ uint32_t mFileVersion;
+ uint32_t mPayloadSize;
+ };
+};
+// Define this class in a header so that TestCrossProcessWin
+// can include and test it.
+class DynamicBlockList final : public DynamicBlockListBase {
+ uint32_t mPayloadSize;
+ UniquePtr<uint8_t[]> mPayload;
+
+#ifdef ENABLE_TESTS
+ // These two definitions are needed for the DynamicBlocklistWriter to avoid
+ // writing this test entry out to the blocklist file, so compile these in
+ // even if MOZILLA_INTERNAL_API is defined.
+ public:
+ static constexpr wchar_t kTestDll[] = L"TestDllBlocklist_UserBlocked.dll";
+ // kTestDll is null-terminated, but we don't want that for the blocklist
+ // file
+ static constexpr size_t kTestDllBytes = sizeof(kTestDll) - sizeof(wchar_t);
+
+ private:
+# ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ // Set up a test entry in the blocklist, used for testing purposes.
+ void AssignTestEntry(DllBlockInfo* testEntry, uint32_t nameOffset) {
+ testEntry->mName.Length = kTestDllBytes;
+ testEntry->mName.MaximumLength = kTestDllBytes;
+ testEntry->mName.Buffer = reinterpret_cast<PWSTR>(nameOffset);
+ testEntry->mMaxVersion = DllBlockInfo::ALL_VERSIONS;
+ testEntry->mFlags = DllBlockInfo::FLAGS_DEFAULT;
+ }
+
+ void CreateListWithTestEntry() {
+ mPayloadSize = sizeof(DllBlockInfo) * 2 + kTestDllBytes;
+ mPayload = MakeUnique<uint8_t[]>(mPayloadSize);
+ DllBlockInfo* entry = reinterpret_cast<DllBlockInfo*>(mPayload.get());
+ AssignTestEntry(entry, sizeof(DllBlockInfo) * 2);
+ memcpy(mPayload.get() + sizeof(DllBlockInfo) * 2, kTestDll, kTestDllBytes);
+ ++entry;
+ entry->mName.Length = 0;
+ entry->mName.MaximumLength = 0;
+ }
+# endif // BLOCKLIST_INSERT_TEST_ENTRY
+#endif // ENABLE_TESTS
+
+ bool LoadFile(const wchar_t* aPath) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ const bool shouldInsertBlocklistTestEntry =
+ MOZ_UNLIKELY(mozilla::EnvHasValue("MOZ_DISABLE_NONLOCAL_CONNECTIONS") ||
+ mozilla::EnvHasValue("MOZ_RUN_GTEST"));
+#endif
+
+ nsAutoHandle file(
+ ::CreateFileW(aPath, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr));
+ if (!file) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // If the blocklist file doesn't exist, we still want to include a test
+ // entry for testing purposes.
+ CreateListWithTestEntry();
+ return true;
+ }
+#endif
+ return false;
+ }
+
+ DWORD bytesRead = 0;
+ FileHeader header;
+ BOOL ok =
+ ::ReadFile(file.get(), &header, sizeof(header), &bytesRead, nullptr);
+ if (!ok) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // If we have a problem reading the blocklist file, we still want to
+ // include a test entry for testing purposes.
+ CreateListWithTestEntry();
+ return true;
+ }
+#endif
+ return false;
+ }
+ if (bytesRead != sizeof(header)) {
+ return false;
+ }
+
+ if (header.mSignature != kSignature ||
+ header.mFileVersion != kCurrentVersion) {
+ return false;
+ }
+
+ uint32_t destinationPayloadSize = header.mPayloadSize;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ // Write an extra entry for testing purposes
+ destinationPayloadSize += sizeof(DllBlockInfo) + kTestDllBytes;
+ }
+#endif
+ UniquePtr<uint8_t[]> payload =
+ MakeUnique<uint8_t[]>(destinationPayloadSize);
+ ok = ::ReadFile(file.get(), payload.get(), header.mPayloadSize, &bytesRead,
+ nullptr);
+ if (!ok || bytesRead != header.mPayloadSize) {
+ return false;
+ }
+
+ uint32_t sizeOfPayloadToIterateOver = header.mPayloadSize;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ bool haveWrittenTestEntry = false;
+ UNICODE_STRING testUnicodeString;
+ // Cast the const-ness away since we're only going to use
+ // this to compare against strings.
+ testUnicodeString.Buffer = const_cast<PWSTR>(kTestDll);
+ testUnicodeString.Length = kTestDllBytes;
+ testUnicodeString.MaximumLength = kTestDllBytes;
+#endif
+ for (uint32_t offset = 0; offset < sizeOfPayloadToIterateOver;
+ offset += sizeof(DllBlockInfo)) {
+ DllBlockInfo* entry =
+ reinterpret_cast<DllBlockInfo*>(payload.get() + offset);
+ if (!entry->mName.Length) {
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry && !haveWrittenTestEntry) {
+ // Shift everything forward
+ memmove(payload.get() + offset + sizeof(DllBlockInfo),
+ payload.get() + offset, header.mPayloadSize - offset);
+ AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes);
+ haveWrittenTestEntry = true;
+ }
+#endif
+ break;
+ }
+
+ size_t stringOffset = reinterpret_cast<size_t>(entry->mName.Buffer);
+ if (stringOffset + entry->mName.Length > header.mPayloadSize) {
+ entry->mName.Length = 0;
+ break;
+ }
+ entry->mName.MaximumLength = entry->mName.Length;
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry && !haveWrittenTestEntry) {
+ UNICODE_STRING currentUnicodeString;
+ currentUnicodeString.Buffer =
+ reinterpret_cast<PWSTR>(payload.get() + stringOffset);
+ currentUnicodeString.Length = entry->mName.Length;
+ currentUnicodeString.MaximumLength = entry->mName.Length;
+ if (RtlCompareUnicodeString(&currentUnicodeString, &testUnicodeString,
+ TRUE) > 0) {
+ // Shift everything forward
+ memmove(payload.get() + offset + sizeof(DllBlockInfo),
+ payload.get() + offset, header.mPayloadSize - offset);
+ AssignTestEntry(entry, destinationPayloadSize - kTestDllBytes);
+ haveWrittenTestEntry = true;
+ // Now we have expanded the area of valid memory, so there is more
+ // allowable space to iterate over.
+ sizeOfPayloadToIterateOver = destinationPayloadSize;
+ offset += sizeof(DllBlockInfo);
+ ++entry;
+ }
+ // The start of the string section will be moving ahead (or has already
+ // moved ahead) to make room for the test entry
+ entry->mName.Buffer +=
+ sizeof(DllBlockInfo) / sizeof(entry->mName.Buffer[0]);
+ }
+#endif
+ }
+#ifdef BLOCKLIST_INSERT_TEST_ENTRY
+ if (shouldInsertBlocklistTestEntry) {
+ memcpy(payload.get() + destinationPayloadSize - kTestDllBytes, kTestDll,
+ kTestDllBytes);
+ }
+#endif
+
+ mPayloadSize = destinationPayloadSize;
+ mPayload = std::move(payload);
+ return true;
+ }
+
+ public:
+ DynamicBlockList() : mPayloadSize(0) {}
+ explicit DynamicBlockList(const wchar_t* aPath) : mPayloadSize(0) {
+ LoadFile(aPath);
+ }
+
+ DynamicBlockList(DynamicBlockList&&) = default;
+ DynamicBlockList& operator=(DynamicBlockList&&) = default;
+ DynamicBlockList(const DynamicBlockList&) = delete;
+ DynamicBlockList& operator=(const DynamicBlockList&) = delete;
+
+ constexpr uint32_t GetPayloadSize() const { return mPayloadSize; }
+
+ // Return the number of bytes copied
+ size_t CopyTo(void* aBuffer, size_t aBufferLength) const {
+ if (mPayloadSize > aBufferLength) {
+ return 0;
+ }
+ memcpy(aBuffer, mPayload.get(), mPayloadSize);
+ return mPayloadSize;
+ }
+};
+
+#if defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS)
+
+class DynamicBlocklistWriter final : public DynamicBlockListBase {
+ template <typename T>
+ static nsresult WriteValue(nsIOutputStream* aOutputStream, const T& aValue) {
+ uint32_t written;
+ return aOutputStream->Write(reinterpret_cast<const char*>(&aValue),
+ sizeof(T), &written);
+ }
+
+ template <typename T>
+ static nsresult WriteBuffer(nsIOutputStream* aOutputStream, const T* aBuffer,
+ uint32_t aBufferSize) {
+ uint32_t written;
+ return aOutputStream->Write(reinterpret_cast<const char*>(aBuffer),
+ aBufferSize, &written);
+ }
+
+ RefPtr<dom::Promise> mPromise;
+ Vector<DllBlockInfo> mArray;
+ // All strings are packed in this buffer without null characters
+ UniquePtr<uint8_t[]> mStringBuffer;
+
+ size_t mArraySize;
+ size_t mStringBufferSize;
+
+ nsresult WriteToFile(const nsAString& aName) const;
+
+ public:
+ DynamicBlocklistWriter(
+ RefPtr<dom::Promise> aPromise,
+ const nsTHashSet<nsStringCaseInsensitiveHashKey>& aBlocklist);
+ ~DynamicBlocklistWriter() = default;
+
+ DynamicBlocklistWriter(DynamicBlocklistWriter&&) = default;
+ DynamicBlocklistWriter& operator=(DynamicBlocklistWriter&&) = default;
+ DynamicBlocklistWriter(const DynamicBlocklistWriter&) = delete;
+ DynamicBlocklistWriter& operator=(const DynamicBlocklistWriter&) = delete;
+
+ bool IsReady() const { return mStringBuffer.get(); }
+
+ void Run();
+ void Cancel();
+};
+
+#endif // defined(MOZILLA_INTERNAL_API) && defined(MOZ_LAUNCHER_PROCESS)
+
+} // namespace mozilla
+
+#endif // mozilla_DynamicBlocklist_h
diff --git a/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
new file mode 100644
index 0000000000..e967d073c5
--- /dev/null
+++ b/toolkit/xre/dllservices/DynamicBlocklistWriter.cpp
@@ -0,0 +1,139 @@
+/* -*- 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/DynamicBlocklist.h"
+#include "mozilla/LauncherRegistryInfo.h"
+
+#include "nsISafeOutputStream.h"
+#include "nsNetUtil.h"
+
+namespace mozilla {
+#if ENABLE_TESTS
+nsDependentString testEntryString(DynamicBlockList::kTestDll,
+ DynamicBlockList::kTestDllBytes /
+ sizeof(DynamicBlockList::kTestDll[0]));
+#endif
+
+bool ShouldWriteEntry(const nsAString& name) {
+#if ENABLE_TESTS
+ return name != testEntryString;
+#else
+ return true;
+#endif
+}
+
+DynamicBlocklistWriter::DynamicBlocklistWriter(
+ RefPtr<dom::Promise> aPromise,
+ const nsTHashSet<nsStringCaseInsensitiveHashKey>& aBlocklist)
+ : mPromise(aPromise), mArraySize(0), mStringBufferSize(0) {
+ CheckedUint32 payloadSize;
+ bool hasTestEntry = false;
+ for (const nsAString& name : aBlocklist) {
+ if (ShouldWriteEntry(name)) {
+ payloadSize += name.Length() * sizeof(char16_t);
+ if (!payloadSize.isValid()) {
+ return;
+ }
+ } else {
+ hasTestEntry = true;
+ }
+ }
+
+ uint32_t entriesToWrite = aBlocklist.Count();
+ if (hasTestEntry) {
+ entriesToWrite -= 1;
+ }
+
+ mStringBufferSize = payloadSize.value();
+ mArraySize = (entriesToWrite + 1) * sizeof(DllBlockInfo);
+
+ payloadSize += mArraySize;
+ if (!payloadSize.isValid()) {
+ return;
+ }
+
+ mStringBuffer = MakeUnique<uint8_t[]>(mStringBufferSize);
+ Unused << mArray.resize(entriesToWrite + 1); // aBlockEntries + sentinel
+
+ size_t currentStringOffset = 0;
+ size_t i = 0;
+ for (const nsAString& name : aBlocklist) {
+ if (!ShouldWriteEntry(name)) {
+ continue;
+ }
+ const uint32_t nameSize = name.Length() * sizeof(char16_t);
+
+ mArray[i].mMaxVersion = DllBlockInfo::ALL_VERSIONS;
+ mArray[i].mFlags = DllBlockInfo::Flags::FLAGS_DEFAULT;
+
+ // Copy the module's name to the string buffer and store its offset
+ // in mName.Buffer
+ memcpy(mStringBuffer.get() + currentStringOffset, name.BeginReading(),
+ nameSize);
+ mArray[i].mName.Buffer =
+ reinterpret_cast<wchar_t*>(mArraySize + currentStringOffset);
+ // Only keep mName.Length and leave mName.MaximumLength to be zero
+ mArray[i].mName.Length = nameSize;
+
+ currentStringOffset += nameSize;
+ ++i;
+ }
+}
+
+nsresult DynamicBlocklistWriter::WriteToFile(const nsAString& aName) const {
+ nsCOMPtr<nsIFile> file;
+ MOZ_TRY(NS_NewLocalFile(aName, true, getter_AddRefs(file)));
+
+ nsCOMPtr<nsIOutputStream> stream;
+ MOZ_TRY(NS_NewSafeLocalFileOutputStream(getter_AddRefs(stream), file));
+
+ MOZ_TRY(WriteValue(stream, kSignature));
+ MOZ_TRY(WriteValue(stream, kCurrentVersion));
+ MOZ_TRY(WriteValue(stream,
+ static_cast<uint32_t>(mArraySize + mStringBufferSize)));
+ MOZ_TRY(WriteBuffer(stream, mArray.begin(), mArraySize));
+ MOZ_TRY(WriteBuffer(stream, mStringBuffer.get(), mStringBufferSize));
+
+ nsresult rv;
+ nsCOMPtr<nsISafeOutputStream> safeStream = do_QueryInterface(stream, &rv);
+ if (NS_FAILED(rv)) {
+ return rv;
+ }
+
+ return safeStream->Finish();
+}
+
+void DynamicBlocklistWriter::Run() {
+ MOZ_ASSERT(!NS_IsMainThread());
+
+ nsresult rv = NS_ERROR_ABORT;
+
+ LauncherRegistryInfo regInfo;
+ LauncherResult<std::wstring> blocklistFile = regInfo.GetBlocklistFileName();
+ if (blocklistFile.isOk()) {
+ const wchar_t* rawBuffer = blocklistFile.inspect().c_str();
+ rv = WriteToFile(nsDependentString(rawBuffer));
+ }
+
+ NS_DispatchToMainThread(
+ // Don't capture mPromise by copy because we're not in the main thread
+ NS_NewRunnableFunction(__func__, [promise = std::move(mPromise), rv]() {
+ if (NS_SUCCEEDED(rv)) {
+ promise->MaybeResolve(JS::NullHandleValue);
+ } else {
+ promise->MaybeReject(rv);
+ }
+ }));
+}
+
+void DynamicBlocklistWriter::Cancel() {
+ MOZ_ASSERT(NS_IsMainThread());
+ if (mPromise) {
+ mPromise->MaybeReject(NS_ERROR_ABORT);
+ }
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/ModuleEvaluator.cpp b/toolkit/xre/dllservices/ModuleEvaluator.cpp
new file mode 100644
index 0000000000..58814e34a5
--- /dev/null
+++ b/toolkit/xre/dllservices/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"
+
+namespace mozilla {
+
+// 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;
+}
+
+/* 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/dllservices/ModuleEvaluator.h b/toolkit/xre/dllservices/ModuleEvaluator.h
new file mode 100644
index 0000000000..565b5d4da4
--- /dev/null
+++ b/toolkit/xre/dllservices/ModuleEvaluator.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_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"
+
+#include <shtypes.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/dllservices/ModuleVersionInfo.cpp b/toolkit/xre/dllservices/ModuleVersionInfo.cpp
new file mode 100644
index 0000000000..c8c2956a74
--- /dev/null
+++ b/toolkit/xre/dllservices/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\\%02lX%02lX%02lX%02lX\\%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/dllservices/ModuleVersionInfo.h b/toolkit/xre/dllservices/ModuleVersionInfo.h
new file mode 100644
index 0000000000..2b46817417
--- /dev/null
+++ b/toolkit/xre/dllservices/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/dllservices/UntrustedModulesData.cpp b/toolkit/xre/dllservices/UntrustedModulesData.cpp
new file mode 100644
index 0000000000..8b99eb8573
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesData.cpp
@@ -0,0 +1,446 @@
+/* -*- 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;
+}
+
+namespace mozilla {
+
+static Maybe<double> QPCLoadDurationToMilliseconds(
+ const ModuleLoadInfo& aNtInfo) {
+ if (aNtInfo.IsBare()) {
+ return Nothing();
+ }
+
+ return Some(QPCToMilliseconds<double>(aNtInfo.mLoadTimeInfo.QuadPart));
+}
+
+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)
+ : 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(aModLoadInfo.mNtLoadInfo.mIsDependent),
+ mLoadStatus(static_cast<uint32_t>(aModLoadInfo.mNtLoadInfo.mStatus)) {
+ if (!mModule || !(*mModule)) {
+ return;
+ }
+
+ mRequestedDllName = aModLoadInfo.mNtLoadInfo.mRequestedDllName.AsString();
+
+ // If we're in the main process, sanitize the requested DLL name here.
+ // If not, we cannot use PreparePathForTelemetry because it may try to
+ // delayload shlwapi.dll and could fail if the process is sandboxed.
+ // We leave mRequestedDllName unsanitized here and sanitize it when
+ // transferring it to the main process.
+ // (See ParamTraits<mozilla::UntrustedModulesData>::ReadEvent)
+ if (XRE_IsParentProcess()) {
+ SanitizeRequestedDllName();
+ }
+}
+
+void ProcessedModuleLoadEvent::SanitizeRequestedDllName() {
+ if (!mRequestedDllName.IsEmpty() &&
+ !widget::WinUtils::PreparePathForTelemetry(mRequestedDllName)) {
+ // If we cannot sanitize a path, we simply do not provide that field to
+ // Telemetry.
+ mRequestedDllName.Truncate();
+ }
+}
+
+/* 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, UntrustedModuleLoadingEvents&& aEvents,
+ Vector<Telemetry::ProcessedStack>&& aStacks) {
+ MOZ_ASSERT(aEvents.length() == aStacks.length());
+ for (const auto& entry : aModules) {
+ if (entry.GetData()->IsTrusted()) {
+ // Filter out trusted module records
+ continue;
+ }
+
+ Unused << mModules.LookupOrInsert(entry.GetKey(), entry.GetData());
+ }
+
+ MOZ_ASSERT(mEvents.length() <= kMaxEvents);
+
+ mNumEvents += aStacks.length();
+ mEvents.extendBack(std::move(aEvents));
+ for (auto&& stack : aStacks) {
+ mStacks.AddStack(stack);
+ }
+}
+
+void UntrustedModulesData::MergeModules(UntrustedModulesData& aNewData) {
+ for (auto item : aNewData.mEvents) {
+ mModules.WithEntryHandle(item->mEvent.mModule->mResolvedNtName,
+ [&](auto&& addPtr) {
+ 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.
+ item->mEvent.mModule = addPtr.Data();
+ } else {
+ addPtr.Insert(item->mEvent.mModule);
+ }
+ });
+ }
+}
+
+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 (!mNumEvents) {
+ mNumEvents = newData.mNumEvents;
+ mModules = std::move(newData.mModules);
+ mEvents = std::move(newData.mEvents);
+ mStacks = std::move(newData.mStacks);
+ return;
+ }
+
+ MergeModules(newData);
+ mNumEvents += newData.mNumEvents;
+ mEvents.extendBack(std::move(newData.mEvents));
+ mStacks.AddStacks(newData.mStacks);
+}
+
+void UntrustedModulesData::Truncate() {
+ mStacks.Clear();
+
+ if (mNumEvents <= kMaxEvents) {
+ return;
+ }
+
+ UntrustedModuleLoadingEvents events;
+ events.splice(0, mEvents, mNumEvents - kMaxEvents, kMaxEvents);
+ std::swap(events, mEvents);
+ mNumEvents = kMaxEvents;
+}
+
+void UntrustedModulesData::MergeWithoutStacks(UntrustedModulesData&& aNewData) {
+ // Don't merge loading events of a different process
+ MOZ_ASSERT((mProcessType == aNewData.mProcessType) &&
+ (mPid == aNewData.mPid));
+ MOZ_ASSERT(!mStacks.GetStackCount());
+
+ UntrustedModulesData newData(std::move(aNewData));
+
+ if (mNumEvents > 0) {
+ MergeModules(newData);
+ } else {
+ mModules = std::move(newData.mModules);
+ }
+
+ mNumEvents += newData.mNumEvents;
+ mEvents.extendBack(std::move(newData.mEvents));
+
+ Truncate();
+}
+
+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);
+ std::swap(mNumEvents, aOther.mNumEvents);
+ std::swap(mEvents, 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/dllservices/UntrustedModulesData.h b/toolkit/xre/dllservices/UntrustedModulesData.h
new file mode 100644
index 0000000000..1bd6b91a36
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesData.h
@@ -0,0 +1,650 @@
+/* -*- 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/LinkedList.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);
+
+ 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;
+
+ void SanitizeRequestedDllName();
+
+ 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>() {}
+};
+
+struct ProcessedModuleLoadEventContainer final
+ : public LinkedListElement<ProcessedModuleLoadEventContainer> {
+ ProcessedModuleLoadEvent mEvent;
+ ProcessedModuleLoadEventContainer() = default;
+ explicit ProcessedModuleLoadEventContainer(ProcessedModuleLoadEvent&& aEvent)
+ : mEvent(std::move(aEvent)) {}
+
+ ProcessedModuleLoadEventContainer(ProcessedModuleLoadEventContainer&&) =
+ default;
+ ProcessedModuleLoadEventContainer& operator=(
+ ProcessedModuleLoadEventContainer&&) = default;
+ ProcessedModuleLoadEventContainer(const ProcessedModuleLoadEventContainer&) =
+ delete;
+ ProcessedModuleLoadEventContainer& operator=(
+ const ProcessedModuleLoadEventContainer&) = delete;
+};
+using UntrustedModuleLoadingEvents =
+ AutoCleanLinkedList<ProcessedModuleLoadEventContainer>;
+
+class UntrustedModulesData final {
+ // Merge aNewData.mEvents into this->mModules and also
+ // make module entries in aNewData point to items in this->mModules.
+ void MergeModules(UntrustedModulesData& aNewData);
+
+ public:
+ // Ensure mEvents will never retain more than kMaxEvents events.
+ // This constant matches the maximum in Telemetry::CombinedStacks.
+ static constexpr size_t kMaxEvents = 50;
+
+ UntrustedModulesData()
+ : mProcessType(XRE_GetProcessType()),
+ mPid(::GetCurrentProcessId()),
+ mNumEvents(0),
+ 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.isEmpty() || mSanitizationFailures || mTrustTestFailures ||
+ mXULLoadDurationMS.isSome();
+ }
+
+ void AddNewLoads(const ModulesMap& aModulesMap,
+ UntrustedModuleLoadingEvents&& aEvents,
+ Vector<Telemetry::ProcessedStack>&& aStacks);
+ void Merge(UntrustedModulesData&& aNewData);
+ void MergeWithoutStacks(UntrustedModulesData&& aNewData);
+ void Swap(UntrustedModulesData& aOther);
+
+ // Drop callstack data and old loading events.
+ void Truncate();
+
+ GeckoProcessType mProcessType;
+ DWORD mPid;
+ TimeDuration mElapsed;
+ ModulesMap mModules;
+ uint32_t mNumEvents;
+ UntrustedModuleLoadingEvents 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(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt64(aParam.AsInteger());
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint64_t ver;
+ if (!aReader->ReadUInt64(&ver)) {
+ return false;
+ }
+
+ *aResult = ver;
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::VendorInfo> {
+ typedef mozilla::VendorInfo paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mSource));
+ WriteParam(aWriter, aParam.mVendor);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t source;
+ if (!aReader->ReadUInt32(&source)) {
+ return false;
+ }
+
+ aResult->mSource = static_cast<mozilla::VendorInfo::Source>(source);
+
+ if (!ReadParam(aReader, &aResult->mVendor)) {
+ return false;
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModuleRecord> {
+ typedef mozilla::ModuleRecord paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mResolvedNtName);
+
+ nsAutoString resolvedDosName;
+ if (aParam.mResolvedDosName) {
+ mozilla::DebugOnly<nsresult> rv =
+ aParam.mResolvedDosName->GetPath(resolvedDosName);
+ MOZ_ASSERT(NS_SUCCEEDED(rv));
+ }
+
+ WriteParam(aWriter, resolvedDosName);
+ WriteParam(aWriter, aParam.mSanitizedDllName);
+ WriteParam(aWriter, aParam.mVersion);
+ WriteParam(aWriter, aParam.mVendorInfo);
+ aWriter->WriteUInt32(static_cast<uint32_t>(aParam.mTrustFlags));
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mResolvedNtName)) {
+ return false;
+ }
+
+ nsAutoString resolvedDosName;
+ if (!ReadParam(aReader, &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(aReader, &aResult->mSanitizedDllName)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mVersion)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mVendorInfo)) {
+ return false;
+ }
+
+ uint32_t trustFlags;
+ if (!aReader->ReadUInt32(&trustFlags)) {
+ return false;
+ }
+
+ aResult->mTrustFlags = static_cast<mozilla::ModuleTrustFlags>(trustFlags);
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModulesMap> {
+ typedef mozilla::ModulesMap paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(aParam.Count());
+
+ for (const auto& entry : aParam) {
+ MOZ_RELEASE_ASSERT(entry.GetData());
+ WriteParam(aWriter, entry.GetKey());
+ WriteParam(aWriter, *(entry.GetData()));
+ }
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t count;
+ if (!ReadParam(aReader, &count)) {
+ return false;
+ }
+
+ for (uint32_t current = 0; current < count; ++current) {
+ nsAutoString key;
+ if (!ReadParam(aReader, &key) || key.IsEmpty()) {
+ return false;
+ }
+
+ RefPtr<mozilla::ModuleRecord> rec(new mozilla::ModuleRecord());
+ if (!ReadParam(aReader, rec.get())) {
+ return false;
+ }
+
+ aResult->InsertOrUpdate(key, std::move(rec));
+ }
+
+ return true;
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::ModulePaths> {
+ typedef mozilla::ModulePaths paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aParam.mModuleNtPaths.match(
+ [aWriter](const paramType::SetType& aSet) { WriteSet(aWriter, aSet); },
+ [aWriter](const paramType::VecType& aVec) {
+ WriteVector(aWriter, aVec);
+ });
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t len;
+ if (!aReader->ReadUInt32(&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(aReader, &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(MessageWriter* aWriter, const paramType::SetType& aSet) {
+ aWriter->WriteUInt32(aSet.Count());
+ for (const auto& key : aSet.Keys()) {
+ WriteParam(aWriter, key);
+ }
+ }
+
+ // NB: This function must write out the vector in the same format as WriteSet
+ static void WriteVector(MessageWriter* aWriter,
+ const paramType::VecType& aVec) {
+ aWriter->WriteUInt32(aVec.length());
+ for (auto const& item : aVec) {
+ WriteParam(aWriter, item);
+ }
+ }
+};
+
+template <>
+struct ParamTraits<mozilla::UntrustedModulesData> {
+ typedef mozilla::UntrustedModulesData paramType;
+
+ static void Write(MessageWriter* aWriter, const paramType& aParam) {
+ aWriter->WriteUInt32(aParam.mProcessType);
+ aWriter->WriteULong(aParam.mPid);
+ WriteParam(aWriter, aParam.mElapsed);
+ WriteParam(aWriter, aParam.mModules);
+
+ aWriter->WriteUInt32(aParam.mNumEvents);
+ for (auto event : aParam.mEvents) {
+ WriteEvent(aWriter, event->mEvent);
+ }
+
+ WriteParam(aWriter, aParam.mStacks);
+ WriteParam(aWriter, aParam.mXULLoadDurationMS);
+ aWriter->WriteUInt32(aParam.mSanitizationFailures);
+ aWriter->WriteUInt32(aParam.mTrustTestFailures);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ uint32_t processType;
+ if (!aReader->ReadUInt32(&processType)) {
+ return false;
+ }
+
+ aResult->mProcessType = static_cast<GeckoProcessType>(processType);
+
+ if (!aReader->ReadULong(&aResult->mPid)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mElapsed)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mModules)) {
+ return false;
+ }
+
+ // We read mEvents manually so that we can use ReadEvent defined below.
+ if (!ReadParam(aReader, &aResult->mNumEvents)) {
+ return false;
+ }
+
+ for (uint32_t curEventIdx = 0; curEventIdx < aResult->mNumEvents;
+ ++curEventIdx) {
+ auto newEvent =
+ mozilla::MakeUnique<mozilla::ProcessedModuleLoadEventContainer>();
+ if (!ReadEvent(aReader, &newEvent->mEvent, aResult->mModules)) {
+ return false;
+ }
+ aResult->mEvents.insertBack(newEvent.release());
+ }
+
+ if (!ReadParam(aReader, &aResult->mStacks)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mXULLoadDurationMS)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&aResult->mSanitizationFailures)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&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(MessageWriter* aWriter,
+ const mozilla::ProcessedModuleLoadEvent& aParam) {
+ aWriter->WriteUInt64(aParam.mProcessUptimeMS);
+ WriteParam(aWriter, aParam.mLoadDurationMS);
+ aWriter->WriteULong(aParam.mThreadId);
+ WriteParam(aWriter, aParam.mThreadName);
+ WriteParam(aWriter, aParam.mRequestedDllName);
+ WriteParam(aWriter, aParam.mBaseAddress);
+ WriteParam(aWriter, aParam.mIsDependent);
+ WriteParam(aWriter, 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(aWriter, aParam.mModule->mResolvedNtName);
+ }
+
+ // Because ProcessedModuleLoadEvent depends on a hash table from
+ // UntrustedModulesData, we do its deserialization as part of this
+ // specialization.
+ static bool ReadEvent(MessageReader* aReader,
+ mozilla::ProcessedModuleLoadEvent* aResult,
+ const mozilla::ModulesMap& aModulesMap) {
+ if (!aReader->ReadUInt64(&aResult->mProcessUptimeMS)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mLoadDurationMS)) {
+ return false;
+ }
+
+ if (!aReader->ReadULong(&aResult->mThreadId)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mThreadName)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mRequestedDllName)) {
+ return false;
+ }
+
+ // When ProcessedModuleLoadEvent was constructed in a child process, we left
+ // mRequestedDllName unsanitized, so now is a good time to sanitize it.
+ aResult->SanitizeRequestedDllName();
+
+ if (!ReadParam(aReader, &aResult->mBaseAddress)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mIsDependent)) {
+ return false;
+ }
+
+ if (!ReadParam(aReader, &aResult->mLoadStatus)) {
+ return false;
+ }
+
+ nsAutoString resolvedNtName;
+ if (!ReadParam(aReader, &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(MessageWriter* aWriter, const paramType& aParam) {
+ WriteParam(aWriter, aParam.mModules);
+ aWriter->WriteUInt32(aParam.mTrustTestFailures);
+ }
+
+ static bool Read(MessageReader* aReader, paramType* aResult) {
+ if (!ReadParam(aReader, &aResult->mModules)) {
+ return false;
+ }
+
+ if (!aReader->ReadUInt32(&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/dllservices/UntrustedModulesProcessor.cpp b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp
new file mode 100644
index 0000000000..0bab977e41
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.cpp
@@ -0,0 +1,1040 @@
+/* -*- 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/dom/ContentParent.h"
+#include "mozilla/Likely.h"
+#include "mozilla/net/SocketProcessChild.h"
+#include "mozilla/net/SocketProcessParent.h"
+#include "mozilla/RDDChild.h"
+#include "mozilla/RDDParent.h"
+#include "mozilla/RDDProcessManager.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;
+};
+
+/* static */
+bool UntrustedModulesProcessor::IsSupportedProcessType() {
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Default:
+ case GeckoProcessType_Content:
+ case GeckoProcessType_Socket:
+ 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(
+ bool aIsReadyForBackgroundProcessing) {
+ if (!IsSupportedProcessType()) {
+ return nullptr;
+ }
+
+ RefPtr<UntrustedModulesProcessor> result(
+ new UntrustedModulesProcessor(aIsReadyForBackgroundProcessing));
+ return result.forget();
+}
+
+NS_IMPL_ISUPPORTS(UntrustedModulesProcessor, nsIObserver)
+
+static const uint32_t kThreadTimeoutMS = 120000; // 2 minutes
+
+UntrustedModulesProcessor::UntrustedModulesProcessor(
+ bool aIsReadyForBackgroundProcessing)
+ : mThread(new LazyIdleThread(kThreadTimeoutMS, "Untrusted Modules"_ns,
+ LazyIdleThread::ManualShutdown)),
+ mUnprocessedMutex(
+ "mozilla::UntrustedModulesProcessor::mUnprocessedMutex"),
+ mModuleCacheMutex(
+ "mozilla::UntrustedModulesProcessor::mModuleCacheMutex"),
+ mStatus(aIsReadyForBackgroundProcessing ? Status::Allowed
+ : Status::StartingUp) {
+ 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);
+ obsServ->AddObserver(this, "unblock-untrusted-modules-thread", false);
+ if (XRE_IsContentProcess()) {
+ obsServ->AddObserver(this, "content-child-will-shutdown", false);
+ }
+}
+
+bool UntrustedModulesProcessor::IsReadyForBackgroundProcessing() const {
+ return mStatus == Status::Allowed;
+}
+
+void UntrustedModulesProcessor::Disable() {
+ // Ensure that mThread cannot run at low priority anymore
+ BackgroundPriorityRegion::Clear(mThread);
+
+ // No more background processing allowed beyond this point
+ if (mStatus.exchange(Status::ShuttingDown) != Status::Allowed) {
+ 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;
+ }
+
+ if (!strcmp(aTopic, "unblock-untrusted-modules-thread")) {
+ nsCOMPtr<nsIObserverService> obs(services::GetObserverService());
+ obs->RemoveObserver(this, "unblock-untrusted-modules-thread");
+
+ mStatus.compareExchange(Status::StartingUp, Status::Allowed);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ // If we're shutting down, stop here.
+ return NS_OK;
+ }
+
+ if (XRE_IsParentProcess()) {
+ // Propagate notification to child processes
+ nsTArray<dom::ContentParent*> contentProcesses;
+ dom::ContentParent::GetAll(contentProcesses);
+ for (auto* proc : contentProcesses) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ if (auto* proc = net::SocketProcessParent::GetSingleton()) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ if (auto* rddMgr = RDDProcessManager::Get()) {
+ if (auto* proc = rddMgr->GetRDDChild()) {
+ Unused << proc->SendUnblockUntrustedModulesThread();
+ }
+ }
+ }
+
+ 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");
+ obsServ->RemoveObserver(this, "unblock-untrusted-modules-thread");
+ 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 (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ nsCOMPtr<nsIRunnable> runnable(NewRunnableMethod(
+ "UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue", this,
+ &UntrustedModulesProcessor::BackgroundProcessModuleLoadQueue));
+
+ mThread->Dispatch(runnable.forget(), NS_DISPATCH_NORMAL);
+}
+
+void UntrustedModulesProcessor::Enqueue(
+ glue::EnhancedModuleLoadInfo&& aModLoadInfo) {
+ if (mStatus == Status::ShuttingDown) {
+ return;
+ }
+
+ DWORD bgThreadId = ToWin32ThreadId(mThread);
+ if (aModLoadInfo.mNtLoadInfo.mThreadId == bgThreadId) {
+ // Exclude loads that were caused by our own background thread
+ return;
+ }
+
+ MutexAutoLock lock(mUnprocessedMutex);
+
+ mUnprocessedModuleLoads.insertBack(
+ new UnprocessedModuleLoadInfoContainer(std::move(aModLoadInfo)));
+
+ ScheduleNonEmptyQueueProcessing(lock);
+}
+
+void UntrustedModulesProcessor::Enqueue(ModuleLoadInfoVec&& aEvents) {
+ if (mStatus == Status::ShuttingDown) {
+ 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) {
+ mUnprocessedModuleLoads.insertBack(
+ new UnprocessedModuleLoadInfoContainer(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 (!IsReadyForBackgroundProcessing()) {
+ 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->IsReadyForBackgroundProcessing()) {
+ // 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 (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ BackgroundPriorityRegion bgRgn;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ ProcessModuleLoadQueue();
+}
+
+RefPtr<ModuleRecord> UntrustedModulesProcessor::GetOrAddModuleRecord(
+ const ModuleEvaluator& aModEval, const nsAString& aResolvedNtPath) {
+ MOZ_ASSERT(XRE_IsParentProcess());
+
+ MutexAutoLock lock(mModuleCacheMutex);
+ return mGlobalModuleCache.WithEntryHandle(
+ aResolvedNtPath, [&](auto&& addPtr) -> RefPtr<ModuleRecord> {
+ if (addPtr) {
+ return addPtr.Data();
+ }
+
+ RefPtr<ModuleRecord> newMod(new ModuleRecord(aResolvedNtPath));
+ if (!(*newMod)) {
+ return nullptr;
+ }
+
+ Maybe<ModuleTrustFlags> maybeTrust = aModEval.GetTrust(*newMod);
+ if (maybeTrust.isNothing()) {
+ return nullptr;
+ }
+
+ newMod->mTrustFlags = maybeTrust.value();
+
+ return addPtr.Insert(std::move(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->IsReadyForBackgroundProcessing()) {
+ // 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->IsReadyForBackgroundProcessing()) {
+ // 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));
+}
+
+UnprocessedModuleLoads UntrustedModulesProcessor::ExtractLoadingEventsToProcess(
+ size_t aMaxLength) {
+ UnprocessedModuleLoads loadsToProcess;
+
+ MutexAutoLock lock(mUnprocessedMutex);
+ CancelScheduledProcessing(lock);
+
+ loadsToProcess.splice(0, mUnprocessedModuleLoads, 0, aMaxLength);
+ return loadsToProcess;
+}
+
+// This function contains multiple IsReadyForBackgroundProcessing() 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;
+ }
+
+ UnprocessedModuleLoads loadsToProcess =
+ ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
+ if (!IsReadyForBackgroundProcessing() || loadsToProcess.isEmpty()) {
+ return;
+ }
+
+ ModuleEvaluator modEval;
+ MOZ_ASSERT(!!modEval);
+ if (!modEval) {
+ return;
+ }
+
+ Telemetry::BatchProcessedStackGenerator stackProcessor;
+ Maybe<double> maybeXulLoadDuration;
+ Vector<Telemetry::ProcessedStack> processedStacks;
+ UntrustedModuleLoadingEvents processedEvents;
+ uint32_t sanitizationFailures = 0;
+ uint32_t trustTestFailures = 0;
+
+ for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
+ glue::EnhancedModuleLoadInfo& entry = container->mInfo;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ RefPtr<ModuleRecord> module(GetOrAddModuleRecord(
+ modEval, entry.mNtLoadInfo.mSectionName.AsString()));
+ 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 (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
+ std::move(entry.mNtLoadInfo.mBacktrace);
+ ProcessedModuleLoadEvent event(std::move(entry), std::move(module));
+
+ if (!event) {
+ // We don't have a sanitized DLL path, so we cannot include this event
+ // for privacy reasons.
+ ++sanitizationFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (event.IsTrusted()) {
+ if (event.IsXULLoad()) {
+ maybeXulLoadDuration = event.mLoadDurationMS;
+ }
+
+ // Trusted modules are not included in the ping
+ continue;
+ }
+
+ mProcessedModuleLoads.mModules.LookupOrInsert(
+ event.mModule->mResolvedNtName, event.mModule);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::ProcessedStack processedStack =
+ stackProcessor.GetStackAndModules(backtrace);
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Unused << processedStacks.emplaceBack(std::move(processedStack));
+ processedEvents.insertBack(
+ new ProcessedModuleLoadEventContainer(std::move(event)));
+ }
+
+ if (processedStacks.empty() && processedEvents.isEmpty() &&
+ !sanitizationFailures && !trustTestFailures) {
+ // Nothing to save
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ // Modules have been added to mProcessedModuleLoads.mModules
+ // in the loop above. Passing an empty ModulesMap to AddNewLoads.
+ mProcessedModuleLoads.AddNewLoads(ModulesMap{}, 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 ::mozilla::SendGetModulesTrust(dom::ContentChild::GetSingleton(),
+ std::move(aModules), runNormal);
+ }
+ case GeckoProcessType_RDD: {
+ return ::mozilla::SendGetModulesTrust(RDDParent::GetSingleton(),
+ std::move(aModules), runNormal);
+ }
+ case GeckoProcessType_Socket: {
+ return ::mozilla::SendGetModulesTrust(
+ net::SocketProcessChild::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());
+
+ UnprocessedModuleLoads loadsToProcess =
+ ExtractLoadingEventsToProcess(UntrustedModulesData::kMaxEvents);
+ if (loadsToProcess.isEmpty()) {
+ // Nothing to process
+ return GetModulesTrustPromise::CreateAndResolve(Nothing(), __func__);
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ nsTHashtable<nsStringCaseInsensitiveHashKey> moduleNtPathSet;
+
+ // Build a set of modules to be processed by the parent
+ for (UnprocessedModuleLoadInfoContainer* container : loadsToProcess) {
+ glue::EnhancedModuleLoadInfo& entry = container->mInfo;
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return GetModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ moduleNtPathSet.PutEntry(entry.mNtLoadInfo.mSectionName.AsString());
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ 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;
+ UnprocessedModuleLoads& loads = aModulesAndLoads.mLoads;
+
+ if (modules.IsEmpty() && !trustTestFailures) {
+ // No data, nothing to save.
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::BatchProcessedStackGenerator stackProcessor;
+
+ Maybe<double> maybeXulLoadDuration;
+ Vector<Telemetry::ProcessedStack> processedStacks;
+ UntrustedModuleLoadingEvents processedEvents;
+ uint32_t sanitizationFailures = 0;
+
+ if (!modules.IsEmpty()) {
+ for (UnprocessedModuleLoadInfoContainer* container : loads) {
+ glue::EnhancedModuleLoadInfo& item = container->mInfo;
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ RefPtr<ModuleRecord> module(GetModuleRecord(modules, item));
+ if (!module) {
+ // If module is null then |item| is trusted
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ glue::EnhancedModuleLoadInfo::BacktraceType backtrace =
+ std::move(item.mNtLoadInfo.mBacktrace);
+ ProcessedModuleLoadEvent event(std::move(item), std::move(module));
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (!event) {
+ // We don't have a sanitized DLL path, so we cannot include this event
+ // for privacy reasons.
+ ++sanitizationFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ if (event.IsXULLoad()) {
+ maybeXulLoadDuration = event.mLoadDurationMS;
+ // We saved the XUL load duration, but it is still trusted, so we
+ // continue.
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ return;
+ }
+
+ Telemetry::ProcessedStack processedStack =
+ stackProcessor.GetStackAndModules(backtrace);
+
+ Unused << processedStacks.emplaceBack(std::move(processedStack));
+ processedEvents.insertBack(
+ new ProcessedModuleLoadEventContainer(std::move(event)));
+ }
+ }
+
+ if (processedStacks.empty() && processedEvents.isEmpty() &&
+ !sanitizationFailures && !trustTestFailures) {
+ // Nothing to save
+ return;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ 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__);
+ }
+
+ for (auto& resolvedNtPath :
+ aModPaths.mModuleNtPaths.as<ModulePaths::VecType>()) {
+ if (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ MOZ_ASSERT(!resolvedNtPath.IsEmpty());
+ if (resolvedNtPath.IsEmpty()) {
+ continue;
+ }
+
+ RefPtr<ModuleRecord> module(GetOrAddModuleRecord(modEval, resolvedNtPath));
+ if (!module) {
+ // We failed to obtain trust information.
+ ++trustTestFailures;
+ continue;
+ }
+
+ if (!IsReadyForBackgroundProcessing()) {
+ 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 (!IsReadyForBackgroundProcessing()) {
+ return ModulesTrustPromise::CreateAndReject(
+ NS_ERROR_ILLEGAL_DURING_SHUTDOWN, __func__);
+ }
+
+ modMap.InsertOrUpdate(resolvedNtPath, std::move(module));
+ }
+
+ return ModulesTrustPromise::CreateAndResolve(std::move(result), __func__);
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/UntrustedModulesProcessor.h b/toolkit/xre/dllservices/UntrustedModulesProcessor.h
new file mode 100644
index 0000000000..1da5a25b2c
--- /dev/null
+++ b/toolkit/xre/dllservices/UntrustedModulesProcessor.h
@@ -0,0 +1,176 @@
+/* -*- 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>;
+
+struct UnprocessedModuleLoadInfoContainer final
+ : public LinkedListElement<UnprocessedModuleLoadInfoContainer> {
+ glue::EnhancedModuleLoadInfo mInfo;
+
+ template <typename T>
+ explicit UnprocessedModuleLoadInfoContainer(T&& aInfo)
+ : mInfo(std::move(aInfo)) {}
+
+ UnprocessedModuleLoadInfoContainer(
+ const UnprocessedModuleLoadInfoContainer&) = delete;
+ UnprocessedModuleLoadInfoContainer& operator=(
+ const UnprocessedModuleLoadInfoContainer&) = delete;
+};
+using UnprocessedModuleLoads =
+ AutoCleanLinkedList<UnprocessedModuleLoadInfoContainer>;
+
+class UntrustedModulesProcessor final : public nsIObserver {
+ public:
+ static RefPtr<UntrustedModulesProcessor> Create(
+ bool aIsReadyForBackgroundProcessing);
+
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+
+ // Called to check if the parent process is ready when a child process
+ // is spanwed
+ bool IsReadyForBackgroundProcessing() const;
+
+ // 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;
+ explicit UntrustedModulesProcessor(bool aIsReadyForBackgroundProcessing);
+
+ static bool IsSupportedProcessType();
+
+ void AddObservers();
+ void RemoveObservers();
+
+ void ScheduleNonEmptyQueueProcessing(const MutexAutoLock& aProofOfLock);
+ void CancelScheduledProcessing(const MutexAutoLock& aProofOfLock);
+ void DispatchBackgroundProcessing();
+
+ void BackgroundProcessModuleLoadQueue();
+ void ProcessModuleLoadQueue();
+
+ // Extract the loading events from mUnprocessedModuleLoads to process and
+ // move to mProcessedModuleLoads. It's guaranteed that the total length of
+ // mProcessedModuleLoads will not exceed |aMaxLength|.
+ UnprocessedModuleLoads ExtractLoadingEventsToProcess(size_t aMaxLength);
+
+ class ModulesMapResultWithLoads final {
+ public:
+ ModulesMapResultWithLoads(Maybe<ModulesMapResult>&& aModMapResult,
+ UnprocessedModuleLoads&& aLoads)
+ : mModMapResult(std::move(aModMapResult)), mLoads(std::move(aLoads)) {}
+ Maybe<ModulesMapResult> mModMapResult;
+ UnprocessedModuleLoads 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);
+
+ // This function is only called by the parent process
+ RefPtr<ModuleRecord> GetOrAddModuleRecord(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 MOZ_UNANNOTATED;
+ Mutex mModuleCacheMutex MOZ_UNANNOTATED;
+
+ // The members in this group are protected by mUnprocessedMutex
+ UnprocessedModuleLoads mUnprocessedModuleLoads;
+ nsCOMPtr<nsIRunnable> mIdleRunnable;
+
+ // This member must only be touched on mThread
+ UntrustedModulesData mProcessedModuleLoads;
+
+ enum class Status { StartingUp, Allowed, ShuttingDown };
+
+ // This member may be touched by any thread
+ Atomic<Status> mStatus;
+
+ // Cache all module records, including ones trusted and ones loaded in
+ // child processes, in the browser process to avoid evaluating the same
+ // module multiple times
+ ModulesMap mGlobalModuleCache;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_UntrustedModulesProcessor_h
diff --git a/toolkit/xre/dllservices/WinDllServices.cpp b/toolkit/xre/dllservices/WinDllServices.cpp
new file mode 100644
index 0000000000..b98a15252b
--- /dev/null
+++ b/toolkit/xre/dllservices/WinDllServices.cpp
@@ -0,0 +1,142 @@
+/* -*- 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;
+}
+
+DllServices::~DllServices() { DisableFull(); }
+
+void DllServices::StartUntrustedModulesProcessor(bool aIsStartingUp) {
+ MOZ_ASSERT(NS_IsMainThread());
+ MOZ_ASSERT(!mUntrustedModulesProcessor);
+ mUntrustedModulesProcessor = UntrustedModulesProcessor::Create(aIsStartingUp);
+}
+
+bool DllServices::IsReadyForBackgroundProcessing() const {
+ return mUntrustedModulesProcessor &&
+ mUntrustedModulesProcessor->IsReadyForBackgroundProcessing();
+}
+
+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/dllservices/WinDllServices.h b/toolkit/xre/dllservices/WinDllServices.h
new file mode 100644
index 0000000000..a5d4f9b363
--- /dev/null
+++ b/toolkit/xre/dllservices/WinDllServices.h
@@ -0,0 +1,57 @@
+/* -*- 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 final : public glue::DllServices {
+ public:
+ static DllServices* Get();
+
+ virtual void DisableFull() override;
+
+ static const char* kTopicDllLoadedMainThread;
+ static const char* kTopicDllLoadedNonMainThread;
+
+ void StartUntrustedModulesProcessor(bool aIsReadyForBackgroundProcessing);
+ bool IsReadyForBackgroundProcessing() const;
+
+ RefPtr<UntrustedModulesPromise> GetUntrustedModulesData();
+
+ RefPtr<ModulesTrustPromise> GetModulesTrust(ModulePaths&& aModPaths,
+ bool aRunAtNormalPriority);
+
+ private:
+ DllServices() = default;
+ ~DllServices();
+
+ 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/dllservices/moz.build b/toolkit/xre/dllservices/moz.build
new file mode 100644
index 0000000000..bb23f5fb53
--- /dev/null
+++ b/toolkit/xre/dllservices/moz.build
@@ -0,0 +1,45 @@
+# -*- 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/.
+
+with Files("**"):
+ BUG_COMPONENT = ("Core", "DLL Services")
+
+Library("dllservices")
+
+FINAL_LIBRARY = "xul"
+
+EXPORTS.mozilla += [
+ "DynamicBlocklist.h",
+ "ModuleVersionInfo.h",
+ "UntrustedModulesData.h",
+ "UntrustedModulesProcessor.h",
+ "WinDllServices.h",
+]
+
+DIRS += [
+ "mozglue",
+]
+
+UNIFIED_SOURCES += [
+ "ModuleEvaluator.cpp",
+ "ModuleVersionInfo.cpp",
+ "UntrustedModulesData.cpp",
+ "UntrustedModulesProcessor.cpp",
+ "WinDllServices.cpp",
+]
+
+if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ UNIFIED_SOURCES += [
+ "DynamicBlocklistWriter.cpp",
+ ]
+
+TEST_DIRS += [
+ "tests",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/xre/dllservices/mozglue/Authenticode.cpp b/toolkit/xre/dllservices/mozglue/Authenticode.cpp
new file mode 100644
index 0000000000..ee702b2ac6
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/Authenticode.cpp
@@ -0,0 +1,439 @@
+/* -*- 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/. */
+
+// We need Windows 8 functions and structures to be able to verify SHA-256.
+#if defined(_WIN32_WINNT)
+# undef _WIN32_WINNT
+# define _WIN32_WINNT _WIN32_WINNT_WIN8
+#endif // defined(_WIN32_WINNT)
+#if defined(NTDDI_VERSION)
+# undef NTDDI_VERSION
+# define NTDDI_VERSION NTDDI_WIN8
+#endif // defined(NTDDI_VERSION)
+
+#include "Authenticode.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsHelpers.h"
+
+#include <windows.h>
+#include <softpub.h>
+#include <wincrypt.h>
+#include <wintrust.h>
+#include <mscat.h>
+
+#include <string.h>
+
+namespace {
+
+struct CertStoreDeleter {
+ typedef HCERTSTORE pointer;
+ void operator()(pointer aStore) { ::CertCloseStore(aStore, 0); }
+};
+
+struct CryptMsgDeleter {
+ typedef HCRYPTMSG pointer;
+ void operator()(pointer aMsg) { ::CryptMsgClose(aMsg); }
+};
+
+struct CertContextDeleter {
+ void operator()(PCCERT_CONTEXT aCertContext) {
+ ::CertFreeCertificateContext(aCertContext);
+ }
+};
+
+struct CATAdminContextDeleter {
+ typedef HCATADMIN pointer;
+ void operator()(pointer aCtx) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminReleaseContext)>
+ pCryptCATAdminReleaseContext(L"wintrust.dll",
+ "CryptCATAdminReleaseContext");
+
+ MOZ_ASSERT(!!pCryptCATAdminReleaseContext);
+ if (!pCryptCATAdminReleaseContext) {
+ return;
+ }
+
+ pCryptCATAdminReleaseContext(aCtx, 0);
+ }
+};
+
+typedef mozilla::UniquePtr<HCERTSTORE, CertStoreDeleter> CertStoreUniquePtr;
+typedef mozilla::UniquePtr<HCRYPTMSG, CryptMsgDeleter> CryptMsgUniquePtr;
+typedef mozilla::UniquePtr<const CERT_CONTEXT, CertContextDeleter>
+ CertContextUniquePtr;
+typedef mozilla::UniquePtr<HCATADMIN, CATAdminContextDeleter>
+ CATAdminContextUniquePtr;
+
+static const DWORD kEncodingTypes = X509_ASN_ENCODING | PKCS_7_ASN_ENCODING;
+
+class SignedBinary final {
+ public:
+ SignedBinary(const wchar_t* aFilePath, mozilla::AuthenticodeFlags aFlags);
+
+ explicit operator bool() const { return mCertStore && mCryptMsg && mCertCtx; }
+
+ mozilla::UniquePtr<wchar_t[]> GetOrgName();
+
+ SignedBinary(const SignedBinary&) = delete;
+ SignedBinary(SignedBinary&&) = delete;
+ SignedBinary& operator=(const SignedBinary&) = delete;
+ SignedBinary& operator=(SignedBinary&&) = delete;
+
+ private:
+ bool VerifySignature(const wchar_t* aFilePath);
+ bool QueryObject(const wchar_t* aFilePath);
+ static bool VerifySignatureInternal(WINTRUST_DATA& aTrustData);
+
+ private:
+ enum class TrustSource { eNone, eEmbedded, eCatalog };
+
+ private:
+ const mozilla::AuthenticodeFlags mFlags;
+ TrustSource mTrustSource;
+ CertStoreUniquePtr mCertStore;
+ CryptMsgUniquePtr mCryptMsg;
+ CertContextUniquePtr mCertCtx;
+};
+
+SignedBinary::SignedBinary(const wchar_t* aFilePath,
+ mozilla::AuthenticodeFlags aFlags)
+ : mFlags(aFlags), mTrustSource(TrustSource::eNone) {
+ if (!VerifySignature(aFilePath)) {
+ return;
+ }
+
+ DWORD certInfoLen = 0;
+ BOOL ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
+ nullptr, &certInfoLen);
+ if (!ok) {
+ return;
+ }
+
+ auto certInfoBuf = mozilla::MakeUnique<char[]>(certInfoLen);
+
+ ok = CryptMsgGetParam(mCryptMsg.get(), CMSG_SIGNER_CERT_INFO_PARAM, 0,
+ certInfoBuf.get(), &certInfoLen);
+ if (!ok) {
+ return;
+ }
+
+ auto certInfo = reinterpret_cast<CERT_INFO*>(certInfoBuf.get());
+
+ PCCERT_CONTEXT certCtx =
+ CertFindCertificateInStore(mCertStore.get(), kEncodingTypes, 0,
+ CERT_FIND_SUBJECT_CERT, certInfo, nullptr);
+ if (!certCtx) {
+ return;
+ }
+
+ mCertCtx.reset(certCtx);
+}
+
+bool SignedBinary::QueryObject(const wchar_t* aFilePath) {
+ DWORD encodingType, contentType, formatType;
+ HCERTSTORE rawCertStore;
+ HCRYPTMSG rawCryptMsg;
+ BOOL result = ::CryptQueryObject(CERT_QUERY_OBJECT_FILE, aFilePath,
+ CERT_QUERY_CONTENT_FLAG_PKCS7_SIGNED_EMBED,
+ CERT_QUERY_FORMAT_FLAG_BINARY, 0,
+ &encodingType, &contentType, &formatType,
+ &rawCertStore, &rawCryptMsg, nullptr);
+ if (!result) {
+ return false;
+ }
+
+ mCertStore.reset(rawCertStore);
+ mCryptMsg.reset(rawCryptMsg);
+
+ return true;
+}
+
+/**
+ * @param aTrustData must be a WINTRUST_DATA structure that has been zeroed out
+ * and then populated at least with its |cbStruct|,
+ * |dwUnionChoice|, and appropriate union field. This function
+ * will then populate the remaining fields as appropriate.
+ */
+/* static */
+bool SignedBinary::VerifySignatureInternal(WINTRUST_DATA& aTrustData) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::WinVerifyTrust)>
+ pWinVerifyTrust(L"wintrust.dll", "WinVerifyTrust");
+ if (!pWinVerifyTrust) {
+ return false;
+ }
+
+ aTrustData.dwUIChoice = WTD_UI_NONE;
+ aTrustData.fdwRevocationChecks = WTD_REVOKE_NONE;
+ aTrustData.dwStateAction = WTD_STATEACTION_VERIFY;
+ aTrustData.dwProvFlags = WTD_CACHE_ONLY_URL_RETRIEVAL;
+
+ const HWND hwnd = (HWND)INVALID_HANDLE_VALUE;
+ GUID policyGUID = WINTRUST_ACTION_GENERIC_VERIFY_V2;
+ LONG result = pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
+
+ aTrustData.dwStateAction = WTD_STATEACTION_CLOSE;
+ pWinVerifyTrust(hwnd, &policyGUID, &aTrustData);
+
+ return result == ERROR_SUCCESS;
+}
+
+bool SignedBinary::VerifySignature(const wchar_t* aFilePath) {
+ // First, try the binary itself
+ if (QueryObject(aFilePath)) {
+ mTrustSource = TrustSource::eEmbedded;
+ if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
+ return true;
+ }
+
+ WINTRUST_FILE_INFO fileInfo = {sizeof(fileInfo)};
+ fileInfo.pcwszFilePath = aFilePath;
+
+ WINTRUST_DATA trustData = {sizeof(trustData)};
+ trustData.dwUnionChoice = WTD_CHOICE_FILE;
+ trustData.pFile = &fileInfo;
+
+ return VerifySignatureInternal(trustData);
+ }
+
+ // We didn't find anything in the binary, so now try a catalog file.
+
+ // First, we open a catalog admin context.
+ HCATADMIN rawCatAdmin;
+
+ // Windows 7 also exports the CryptCATAdminAcquireContext2 API, but it does
+ // *not* sign its binaries with SHA-256, so we use the old API in that case.
+ if (mozilla::IsWin8OrLater()) {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminAcquireContext2)>
+ pCryptCATAdminAcquireContext2(L"wintrust.dll",
+ "CryptCATAdminAcquireContext2");
+ if (!pCryptCATAdminAcquireContext2) {
+ return false;
+ }
+
+ CERT_STRONG_SIGN_PARA policy = {sizeof(policy)};
+ policy.dwInfoChoice = CERT_STRONG_SIGN_OID_INFO_CHOICE;
+ policy.pszOID = const_cast<char*>(
+ szOID_CERT_STRONG_SIGN_OS_CURRENT); // -Wwritable-strings
+
+ if (!pCryptCATAdminAcquireContext2(&rawCatAdmin, nullptr,
+ BCRYPT_SHA256_ALGORITHM, &policy, 0)) {
+ return false;
+ }
+ } else {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminAcquireContext)>
+ pCryptCATAdminAcquireContext(L"wintrust.dll",
+ "CryptCATAdminAcquireContext");
+
+ if (!pCryptCATAdminAcquireContext ||
+ !pCryptCATAdminAcquireContext(&rawCatAdmin, nullptr, 0)) {
+ return false;
+ }
+ }
+
+ CATAdminContextUniquePtr catAdmin(rawCatAdmin);
+
+ // Now we need to hash the file at aFilePath.
+ // Since we're hashing this file, let's open it with a sequential scan hint.
+ HANDLE rawFile =
+ ::CreateFileW(aFilePath, GENERIC_READ,
+ FILE_SHARE_READ | FILE_SHARE_DELETE | FILE_SHARE_WRITE,
+ nullptr, OPEN_EXISTING, FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
+ if (rawFile == INVALID_HANDLE_VALUE) {
+ return false;
+ }
+
+ nsAutoHandle file(rawFile);
+ DWORD hashLen = 0;
+ mozilla::UniquePtr<BYTE[]> hashBuf;
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminCalcHashFromFileHandle2)>
+ pCryptCATAdminCalcHashFromFileHandle2(
+ L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle2");
+ if (pCryptCATAdminCalcHashFromFileHandle2) {
+ if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
+ nullptr, 0) &&
+ ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
+
+ if (!pCryptCATAdminCalcHashFromFileHandle2(rawCatAdmin, rawFile, &hashLen,
+ hashBuf.get(), 0)) {
+ return false;
+ }
+ } else {
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminCalcHashFromFileHandle)>
+ pCryptCATAdminCalcHashFromFileHandle(
+ L"wintrust.dll", "CryptCATAdminCalcHashFromFileHandle");
+
+ if (!pCryptCATAdminCalcHashFromFileHandle) {
+ return false;
+ }
+
+ if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, nullptr, 0) &&
+ ::GetLastError() != ERROR_INSUFFICIENT_BUFFER) {
+ return false;
+ }
+
+ hashBuf = mozilla::MakeUnique<BYTE[]>(hashLen);
+
+ if (!pCryptCATAdminCalcHashFromFileHandle(rawFile, &hashLen, hashBuf.get(),
+ 0)) {
+ return false;
+ }
+ }
+
+ // Now that we've hashed the file, query the catalog system to see if any
+ // catalogs reference a binary with our hash.
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminEnumCatalogFromHash)>
+ pCryptCATAdminEnumCatalogFromHash(L"wintrust.dll",
+ "CryptCATAdminEnumCatalogFromHash");
+ if (!pCryptCATAdminEnumCatalogFromHash) {
+ return false;
+ }
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATAdminReleaseCatalogContext)>
+ pCryptCATAdminReleaseCatalogContext(L"wintrust.dll",
+ "CryptCATAdminReleaseCatalogContext");
+ if (!pCryptCATAdminReleaseCatalogContext) {
+ return false;
+ }
+
+ HCATINFO catInfoHdl = pCryptCATAdminEnumCatalogFromHash(
+ rawCatAdmin, hashBuf.get(), hashLen, 0, nullptr);
+ if (!catInfoHdl) {
+ return false;
+ }
+
+ // We can't use UniquePtr for this because the deleter function requires two
+ // parameters.
+ auto cleanCatInfoHdl =
+ mozilla::MakeScopeExit([rawCatAdmin, catInfoHdl]() -> void {
+ pCryptCATAdminReleaseCatalogContext(rawCatAdmin, catInfoHdl, 0);
+ });
+
+ // We found a catalog! Now query for the path to the catalog file.
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::CryptCATCatalogInfoFromContext)>
+ pCryptCATCatalogInfoFromContext(L"wintrust.dll",
+ "CryptCATCatalogInfoFromContext");
+ if (!pCryptCATCatalogInfoFromContext) {
+ return false;
+ }
+
+ CATALOG_INFO_ catInfo = {sizeof(catInfo)};
+ if (!pCryptCATCatalogInfoFromContext(catInfoHdl, &catInfo, 0)) {
+ return false;
+ }
+
+ if (!QueryObject(catInfo.wszCatalogFile)) {
+ return false;
+ }
+
+ mTrustSource = TrustSource::eCatalog;
+
+ if (mFlags & mozilla::AuthenticodeFlags::SkipTrustVerification) {
+ return true;
+ }
+
+ // WINTRUST_CATALOG_INFO::pcwszMemberTag is commonly set to the string
+ // representation of the file hash, so we build that here.
+
+ DWORD strHashBufLen = (hashLen * 2) + 1;
+ auto strHashBuf = mozilla::MakeUnique<wchar_t[]>(strHashBufLen);
+ if (!::CryptBinaryToStringW(hashBuf.get(), hashLen,
+ CRYPT_STRING_HEXRAW | CRYPT_STRING_NOCRLF,
+ strHashBuf.get(), &strHashBufLen)) {
+ return false;
+ }
+
+ // Ensure that the tag is uppercase for WinVerifyTrust
+ // NB: CryptBinaryToStringW overwrites strHashBufLen with the length excluding
+ // the null terminator, so we need to add it back for this call.
+ if (_wcsupr_s(strHashBuf.get(), strHashBufLen + 1)) {
+ return false;
+ }
+
+ // Now, given the path to the catalog, and the path to the member (ie, the
+ // binary whose hash we are validating), we may now validate. If the
+ // validation is successful, we then QueryObject on the *catalog file*
+ // instead of the binary.
+
+ WINTRUST_CATALOG_INFO wtCatInfo = {sizeof(wtCatInfo)};
+ wtCatInfo.pcwszCatalogFilePath = catInfo.wszCatalogFile;
+ wtCatInfo.pcwszMemberTag = strHashBuf.get();
+ wtCatInfo.pcwszMemberFilePath = aFilePath;
+ wtCatInfo.hMemberFile = rawFile;
+ if (mozilla::IsWin8OrLater()) {
+ wtCatInfo.hCatAdmin = rawCatAdmin;
+ }
+
+ WINTRUST_DATA trustData = {sizeof(trustData)};
+ trustData.dwUnionChoice = WTD_CHOICE_CATALOG;
+ trustData.pCatalog = &wtCatInfo;
+
+ return VerifySignatureInternal(trustData);
+}
+
+mozilla::UniquePtr<wchar_t[]> SignedBinary::GetOrgName() {
+ DWORD charCount = CertGetNameStringW(
+ mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE, 0, nullptr, nullptr, 0);
+ if (charCount <= 1) {
+ // Not found
+ return nullptr;
+ }
+
+ auto result = mozilla::MakeUnique<wchar_t[]>(charCount);
+ charCount = CertGetNameStringW(mCertCtx.get(), CERT_NAME_SIMPLE_DISPLAY_TYPE,
+ 0, nullptr, result.get(), charCount);
+ MOZ_ASSERT(charCount > 1);
+
+ return result;
+}
+
+} // anonymous namespace
+
+namespace mozilla {
+
+class AuthenticodeImpl : public Authenticode {
+ public:
+ virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override;
+};
+
+UniquePtr<wchar_t[]> AuthenticodeImpl::GetBinaryOrgName(
+ const wchar_t* aFilePath, AuthenticodeFlags aFlags) {
+ SignedBinary bin(aFilePath, aFlags);
+ if (!bin) {
+ return nullptr;
+ }
+
+ return bin.GetOrgName();
+}
+
+static AuthenticodeImpl sAuthenticodeImpl;
+
+Authenticode* GetAuthenticode() { return &sAuthenticodeImpl; }
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/Authenticode.h b/toolkit/xre/dllservices/mozglue/Authenticode.h
new file mode 100644
index 0000000000..182512da2c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/Authenticode.h
@@ -0,0 +1,32 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_Authenticode_h
+#define mozilla_Authenticode_h
+
+#include "mozilla/Maybe.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+
+enum class AuthenticodeFlags : uint32_t {
+ Default = 0,
+ SkipTrustVerification = 1,
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(AuthenticodeFlags)
+
+class Authenticode {
+ public:
+ virtual UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) = 0;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_Authenticode_h
diff --git a/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp
new file mode 100644
index 0000000000..f3eef13504
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.cpp
@@ -0,0 +1,60 @@
+/* -*- 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 "CacheNtDllThunk.h"
+
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/StaticPtr.h"
+
+namespace mozilla {
+
+static StaticAutoPtr<Buffer<IMAGE_THUNK_DATA>> sCachedNtDllThunk;
+
+// This static method initializes sCachedNtDllThunk. Because it's called in
+// XREMain::XRE_main, which happens long before WindowsProcessLauncher's ctor
+// accesses sCachedNtDllThunk, there is no race on sCachedNtDllThunk, thus
+// no mutex is needed.
+static void CacheNtDllThunk() {
+ if (sCachedNtDllThunk) {
+ return;
+ }
+
+ do {
+ nt::PEHeaders ourExeImage(::GetModuleHandleW(nullptr));
+ if (!ourExeImage) {
+ break;
+ }
+
+ nt::PEHeaders ntdllImage(::GetModuleHandleW(L"ntdll.dll"));
+ if (!ntdllImage) {
+ break;
+ }
+
+ Maybe<Range<const uint8_t>> ntdllBoundaries = ntdllImage.GetBounds();
+ if (!ntdllBoundaries) {
+ break;
+ }
+
+ Maybe<Span<IMAGE_THUNK_DATA>> maybeNtDllThunks =
+ ourExeImage.GetIATThunksForModule("ntdll.dll", ntdllBoundaries.ptr());
+ if (maybeNtDllThunks.isNothing()) {
+ break;
+ }
+
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>(maybeNtDllThunks.value());
+ return;
+ } while (false);
+
+ // Failed to cache IAT. Initializing the variable with nullptr.
+ sCachedNtDllThunk = new Buffer<IMAGE_THUNK_DATA>();
+}
+
+static Buffer<IMAGE_THUNK_DATA>* GetCachedNtDllThunk() {
+ return sCachedNtDllThunk;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h
new file mode 100644
index 0000000000..403e001afc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/CacheNtDllThunk.h
@@ -0,0 +1,21 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_CacheNtDllThunk_h
+#define mozilla_CacheNtDllThunk_h
+
+#include "mozilla/Types.h"
+#include "mozilla/Buffer.h"
+#include "mozilla/NativeNt.h"
+
+namespace mozilla {
+
+MFBT_API void CacheNtDllThunk();
+MFBT_API Buffer<IMAGE_THUNK_DATA>* GetCachedNtDllThunk();
+
+}; // namespace mozilla
+
+#endif // mozilla_CacheNtDllThunk_h
diff --git a/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h b/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h
new file mode 100644
index 0000000000..16ee93c987
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderAPIInterfaces.h
@@ -0,0 +1,125 @@
+/* -*- 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_LoaderAPIInterfaces_h
+#define mozilla_LoaderAPIInterfaces_h
+
+#include "nscore.h"
+#include "mozilla/glue/SharedSection.h"
+#include "mozilla/ModuleLoadInfo.h"
+
+namespace mozilla {
+namespace nt {
+
+class NS_NO_VTABLE LoaderObserver {
+ public:
+ /**
+ * Notification that a DLL load has begun.
+ *
+ * @param aContext Outparam that allows this observer to store any context
+ * information pertaining to the current load.
+ * @param aRequestedDllName The DLL name requested by whatever invoked the
+ * loader. This name may not match the effective
+ * name of the DLL once the loader has completed
+ * its path search.
+ */
+ virtual void OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) = 0;
+
+ /**
+ * Query the observer to determine whether the DLL named |aLSPLeafName| needs
+ * to be substituted with another module, and substitute the module handle
+ * when necessary.
+ *
+ * @return true when substitution occurs, otherwise false
+ */
+ virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) = 0;
+
+ /**
+ * Notification that a DLL load has ended.
+ *
+ * @param aContext The context that was set by the corresponding call to
+ * OnBeginDllLoad
+ * @param aNtStatus The NTSTATUS returned by LdrLoadDll
+ * @param aModuleLoadInfo Telemetry information that was gathered about the
+ * load.
+ */
+ virtual void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) = 0;
+
+ /**
+ * Called to inform the observer that it is no longer active and, if
+ * necessary, call aNext->OnForward() with any accumulated telemetry
+ * information.
+ */
+ virtual void Forward(LoaderObserver* aNext) = 0;
+
+ /**
+ * Receives a vector of module load telemetry from a previous LoaderObserver.
+ */
+ virtual void OnForward(ModuleLoadInfoVec&& aInfo) = 0;
+};
+
+class NS_NO_VTABLE LoaderAPI {
+ public:
+ /**
+ * Construct a new ModuleLoadInfo structure and notify the LoaderObserver
+ * that a library load is beginning.
+ */
+ virtual ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) = 0;
+
+ /**
+ * Query to determine whether the DLL named |aLSPLeafName| needs to be
+ * substituted with another module, and substitute the module handle when
+ * necessary.
+ *
+ * @return true when substitution occurs, otherwise false
+ */
+ virtual bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) = 0;
+
+ /**
+ * Notification that a DLL load has ended.
+ */
+ virtual void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) = 0;
+
+ /**
+ * Given the address of a mapped section, obtain the name of the file that is
+ * backing it.
+ */
+ virtual AllocatedUnicodeString GetSectionName(void* aSectionAddr) = 0;
+
+ using InitDllBlocklistOOPFnPtr = LauncherVoidResultWithLineInfo (*)(
+ const wchar_t*, HANDLE, const IMAGE_THUNK_DATA*, const bool, const bool);
+ using HandleLauncherErrorFnPtr = void (*)(const LauncherError&, const char*);
+
+ /**
+ * Return a pointer to winlauncher's function.
+ * Used by sandboxBroker::LaunchApp.
+ */
+ virtual InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() = 0;
+ virtual HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() = 0;
+ virtual SharedSection* GetSharedSection() = 0;
+};
+
+struct WinLauncherServices final {
+ nt::LoaderAPI::InitDllBlocklistOOPFnPtr mInitDllBlocklistOOP;
+ nt::LoaderAPI::HandleLauncherErrorFnPtr mHandleLauncherError;
+ SharedSection* mSharedSection;
+
+ WinLauncherServices()
+ : mInitDllBlocklistOOP(nullptr),
+ mHandleLauncherError(nullptr),
+ mSharedSection(nullptr) {}
+};
+
+} // namespace nt
+} // namespace mozilla
+
+#endif // mozilla_LoaderAPIInterfaces_h
diff --git a/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp b/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp
new file mode 100644
index 0000000000..d2014365c4
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderObserver.cpp
@@ -0,0 +1,149 @@
+/* -*- 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 "LoaderObserver.h"
+
+#include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/glue/WindowsUnicode.h"
+#include "mozilla/StackWalk_windows.h"
+#include "mozilla/TimeStamp.h"
+
+namespace mozilla {
+
+extern glue::Win32SRWLock gDllServicesLock;
+extern glue::detail::DllServicesBase* gDllServices;
+
+namespace glue {
+
+void LoaderObserver::OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aRequestedDllName) {
+ MOZ_ASSERT(aContext);
+ if (IsProfilerPresent()) {
+ UniquePtr<char[]> utf8RequestedDllName(WideToUTF8(aRequestedDllName));
+ BASE_PROFILER_MARKER_TEXT(
+ "DllLoad", OTHER, MarkerTiming::IntervalStart(),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(
+ utf8RequestedDllName.get()));
+ *aContext = utf8RequestedDllName.release();
+ }
+
+#ifdef _M_AMD64
+ // Prevent the stack walker from suspending this thread when LdrLoadDll
+ // holds the RtlLookupFunctionEntry lock.
+ SuppressStackWalking();
+#endif
+}
+
+bool LoaderObserver::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) {
+ // Currently unsupported
+ return false;
+}
+
+void LoaderObserver::OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+#ifdef _M_AMD64
+ DesuppressStackWalking();
+#endif
+
+ if (aContext) {
+ UniquePtr<char[]> utf8RequestedDllName{static_cast<char*>(aContext)};
+ BASE_PROFILER_MARKER_TEXT(
+ "DllLoad", OTHER, MarkerTiming::IntervalEnd(),
+ mozilla::ProfilerString8View::WrapNullTerminatedString(
+ utf8RequestedDllName.get()));
+ }
+
+ // We want to record a denied DLL load regardless of |aNtStatus| because
+ // |aNtStatus| is set to access-denied when DLL load was blocked.
+ if ((!NT_SUCCESS(aNtStatus) && !aModuleLoadInfo.WasDenied()) ||
+ !aModuleLoadInfo.WasMapped()) {
+ return;
+ }
+
+ { // Scope for lock
+ AutoSharedLock lock(gDllServicesLock);
+ if (gDllServices) {
+ gDllServices->DispatchDllLoadNotification(std::move(aModuleLoadInfo));
+ return;
+ }
+ }
+
+ // No dll services, save for later
+ AutoExclusiveLock lock(mLock);
+ if (!mEnabled) {
+ return;
+ }
+
+ if (!mModuleLoads) {
+ mModuleLoads = new ModuleLoadInfoVec();
+ }
+
+ Unused << mModuleLoads->emplaceBack(
+ std::forward<ModuleLoadInfo>(aModuleLoadInfo));
+}
+
+void LoaderObserver::Forward(nt::LoaderObserver* aNext) {
+ MOZ_ASSERT_UNREACHABLE(
+ "This implementation does not forward to any more "
+ "nt::LoaderObserver objects");
+}
+
+void LoaderObserver::Forward(detail::DllServicesBase* aNext) {
+ MOZ_ASSERT(aNext);
+ if (!aNext) {
+ return;
+ }
+
+ ModuleLoadInfoVec* moduleLoads = nullptr;
+
+ { // Scope for lock
+ AutoExclusiveLock lock(mLock);
+ moduleLoads = mModuleLoads;
+ mModuleLoads = nullptr;
+ }
+
+ if (!moduleLoads) {
+ return;
+ }
+
+ aNext->DispatchModuleLoadBacklogNotification(std::move(*moduleLoads));
+ delete moduleLoads;
+}
+
+void LoaderObserver::Disable() {
+ ModuleLoadInfoVec* moduleLoads = nullptr;
+
+ { // Scope for lock
+ AutoExclusiveLock lock(mLock);
+ moduleLoads = mModuleLoads;
+ mModuleLoads = nullptr;
+ mEnabled = false;
+ }
+
+ delete moduleLoads;
+}
+
+void LoaderObserver::OnForward(ModuleLoadInfoVec&& aInfo) {
+ AutoExclusiveLock lock(mLock);
+ if (!mModuleLoads) {
+ mModuleLoads = new ModuleLoadInfoVec();
+ }
+
+ MOZ_ASSERT(mModuleLoads->empty());
+ if (mModuleLoads->empty()) {
+ *mModuleLoads = std::move(aInfo);
+ } else {
+ // This should not happen, but we can handle it
+ for (auto&& item : aInfo) {
+ Unused << mModuleLoads->append(std::move(item));
+ }
+ }
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/LoaderObserver.h b/toolkit/xre/dllservices/mozglue/LoaderObserver.h
new file mode 100644
index 0000000000..39b13035a3
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/LoaderObserver.h
@@ -0,0 +1,45 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_LoaderObserver_h
+#define mozilla_glue_LoaderObserver_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/glue/WinUtils.h"
+#include "mozilla/UniquePtr.h"
+
+namespace mozilla {
+namespace glue {
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS LoaderObserver final
+ : public nt::LoaderObserver {
+ public:
+ constexpr LoaderObserver() : mModuleLoads(nullptr), mEnabled(true) {}
+
+ void OnBeginDllLoad(void** aContext,
+ PCUNICODE_STRING aPreliminaryDllName) final;
+ bool SubstituteForLSP(PCUNICODE_STRING aLspLeafName,
+ PHANDLE aOutHandle) final;
+ void OnEndDllLoad(void* aContext, NTSTATUS aNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) final;
+ void Forward(nt::LoaderObserver* aNext) final;
+ void OnForward(ModuleLoadInfoVec&& aInfo) final;
+
+ void Forward(mozilla::glue::detail::DllServicesBase* aSvc);
+ void Disable();
+
+ private:
+ Win32SRWLock mLock;
+ ModuleLoadInfoVec* mModuleLoads;
+ bool mEnabled;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_LoaderObserver_h
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp
new file mode 100644
index 0000000000..a49505adbc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.cpp
@@ -0,0 +1,104 @@
+/* -*- 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 "ModuleLoadFrame.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/UniquePtr.h"
+#include "NtLoaderAPI.h"
+
+#include <string.h>
+
+#include "WindowsFallbackLoaderAPI.h"
+
+static bool IsNullTerminated(PCUNICODE_STRING aStr) {
+ return aStr && (aStr->MaximumLength >= (aStr->Length + sizeof(WCHAR))) &&
+ aStr->Buffer && aStr->Buffer[aStr->Length / sizeof(WCHAR)] == 0;
+}
+
+static mozilla::FallbackLoaderAPI gFallbackLoaderAPI;
+
+namespace mozilla {
+namespace glue {
+
+nt::LoaderAPI* ModuleLoadFrame::sLoaderAPI;
+
+using GetNtLoaderAPIFn = decltype(&mozilla::GetNtLoaderAPI);
+
+/* static */
+void ModuleLoadFrame::StaticInit(nt::LoaderObserver* aNewObserver,
+ nt::WinLauncherServices* aOutWinLauncher) {
+ const auto pGetNtLoaderAPI = reinterpret_cast<GetNtLoaderAPIFn>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "GetNtLoaderAPI"));
+ if (!pGetNtLoaderAPI) {
+ // This case occurs in processes other than firefox.exe that do not contain
+ // the launcher process blocklist.
+ gFallbackLoaderAPI.SetObserver(aNewObserver);
+ sLoaderAPI = &gFallbackLoaderAPI;
+
+ if (aOutWinLauncher) {
+ aOutWinLauncher->mHandleLauncherError = [](const mozilla::LauncherError&,
+ const char*) {};
+ // We intentionally leave mInitDllBlocklistOOP null to make sure calling
+ // mInitDllBlocklistOOP in non-Firefox hits MOZ_RELEASE_ASSERT.
+ }
+ return;
+ }
+
+ sLoaderAPI = pGetNtLoaderAPI(aNewObserver);
+ MOZ_ASSERT(sLoaderAPI);
+
+ if (aOutWinLauncher) {
+ aOutWinLauncher->mInitDllBlocklistOOP = sLoaderAPI->GetDllBlocklistInitFn();
+ aOutWinLauncher->mHandleLauncherError =
+ sLoaderAPI->GetHandleLauncherErrorFn();
+ aOutWinLauncher->mSharedSection = sLoaderAPI->GetSharedSection();
+ }
+}
+
+ModuleLoadFrame::ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName)
+ : mAlreadyLoaded(false),
+ mContext(nullptr),
+ mDllLoadStatus(STATUS_UNSUCCESSFUL),
+ mLoadInfo(sLoaderAPI->ConstructAndNotifyBeginDllLoad(&mContext,
+ aRequestedDllName)) {
+ if (!aRequestedDllName) {
+ return;
+ }
+
+ UniquePtr<WCHAR[]> nameBuf;
+ const WCHAR* name = nullptr;
+
+ if (IsNullTerminated(aRequestedDllName)) {
+ name = aRequestedDllName->Buffer;
+ } else {
+ USHORT charLenExclNul = aRequestedDllName->Length / sizeof(WCHAR);
+ USHORT charLenInclNul = charLenExclNul + 1;
+ nameBuf = MakeUnique<WCHAR[]>(charLenInclNul);
+ if (!wcsncpy_s(nameBuf.get(), charLenInclNul, aRequestedDllName->Buffer,
+ charLenExclNul)) {
+ name = nameBuf.get();
+ }
+ }
+
+ mAlreadyLoaded = name && !!::GetModuleHandleW(name);
+}
+
+ModuleLoadFrame::~ModuleLoadFrame() {
+ sLoaderAPI->NotifyEndDllLoad(mContext, mDllLoadStatus, std::move(mLoadInfo));
+}
+
+void ModuleLoadFrame::SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle) {
+ mDllLoadStatus = aNtStatus;
+ void* baseAddr = mozilla::nt::PEHeaders::HModuleToBaseAddr<void*>(
+ reinterpret_cast<HMODULE>(aHandle));
+ mLoadInfo.mBaseAddr = baseAddr;
+ if (!mAlreadyLoaded) {
+ mLoadInfo.mSectionName = sLoaderAPI->GetSectionName(baseAddr);
+ }
+}
+
+} // namespace glue
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h
new file mode 100644
index 0000000000..d47e42ab0c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadFrame.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_ModuleLoadFrame_h
+#define mozilla_glue_ModuleLoadFrame_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+
+namespace mozilla {
+namespace glue {
+
+class MOZ_RAII ModuleLoadFrame final {
+ public:
+ explicit ModuleLoadFrame(PCUNICODE_STRING aRequestedDllName);
+ ~ModuleLoadFrame();
+
+ void SetLoadStatus(NTSTATUS aNtStatus, HANDLE aHandle);
+
+ ModuleLoadFrame(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame(ModuleLoadFrame&&) = delete;
+ ModuleLoadFrame& operator=(const ModuleLoadFrame&) = delete;
+ ModuleLoadFrame& operator=(ModuleLoadFrame&&) = delete;
+
+ static void StaticInit(nt::LoaderObserver* aNewObserver,
+ nt::WinLauncherServices* aOutWinLauncher);
+
+ private:
+ bool mAlreadyLoaded;
+ void* mContext;
+ NTSTATUS mDllLoadStatus;
+ ModuleLoadInfo mLoadInfo;
+
+ private:
+ static nt::LoaderAPI* sLoaderAPI;
+};
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_ModuleLoadFrame_h
diff --git a/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h
new file mode 100644
index 0000000000..807adaae0a
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/ModuleLoadInfo.h
@@ -0,0 +1,174 @@
+/* -*- 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_ModuleLoadInfo_h
+#define mozilla_ModuleLoadInfo_h
+
+#include "mozilla/NativeNt.h"
+#include "mozilla/Vector.h"
+#include "mozilla/Unused.h"
+
+namespace mozilla {
+
+struct ModuleLoadInfo final {
+ enum class Status : uint32_t {
+ Loaded = 0,
+ Blocked,
+ Redirected,
+ };
+
+ // We do not provide these methods inside Gecko proper.
+#if !defined(MOZILLA_INTERNAL_API)
+
+ /**
+ * This constructor is for use by the LdrLoadDll hook.
+ */
+ explicit ModuleLoadInfo(PCUNICODE_STRING aRequestedDllName)
+ : mLoadTimeInfo(),
+ mThreadId(nt::RtlGetCurrentThreadId()),
+ mRequestedDllName(aRequestedDllName),
+ mBaseAddr(nullptr),
+ mStatus(Status::Loaded),
+ mIsDependent(false) {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mBeginTimestamp);
+# else
+ ::RtlQueryPerformanceCounter(&mBeginTimestamp);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * This constructor is used by the NtMapViewOfSection hook IF AND ONLY IF
+ * the LdrLoadDll hook did not already construct a ModuleLoadInfo for the
+ * current DLL load. This may occur while the loader is loading dependencies
+ * of another library.
+ */
+ ModuleLoadInfo(nt::AllocatedUnicodeString&& aSectionName,
+ const void* aBaseAddr, Status aLoadStatus, bool aIsDependent)
+ : mLoadTimeInfo(),
+ mThreadId(nt::RtlGetCurrentThreadId()),
+ mSectionName(std::move(aSectionName)),
+ mBaseAddr(aBaseAddr),
+ mStatus(aLoadStatus),
+ mIsDependent(aIsDependent) {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mBeginTimestamp);
+# else
+ ::RtlQueryPerformanceCounter(&mBeginTimestamp);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * Marks the time that LdrLoadDll began loading this library.
+ */
+ void SetBeginLoadTimeStamp() {
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&mLoadTimeInfo);
+# else
+ ::RtlQueryPerformanceCounter(&mLoadTimeInfo);
+# endif // defined(IMPL_MFBT)
+ }
+
+ /**
+ * Marks the time that LdrLoadDll finished loading this library.
+ */
+ void SetEndLoadTimeStamp() {
+ LARGE_INTEGER endTimeStamp;
+# if defined(IMPL_MFBT)
+ ::QueryPerformanceCounter(&endTimeStamp);
+# else
+ ::RtlQueryPerformanceCounter(&endTimeStamp);
+# endif // defined(IMPL_MFBT)
+
+ LONGLONG& timeInfo = mLoadTimeInfo.QuadPart;
+ if (!timeInfo) {
+ return;
+ }
+
+ timeInfo = endTimeStamp.QuadPart - timeInfo;
+ }
+
+ /**
+ * Saves the current thread's call stack.
+ */
+ void CaptureBacktrace() {
+ const DWORD kMaxBacktraceSize = 512;
+
+ if (!mBacktrace.resize(kMaxBacktraceSize)) {
+ return;
+ }
+
+ // We don't use a Win32 variant here because Win32's CaptureStackBackTrace
+ // is just a macro that resolve to this function anyway.
+ WORD numCaptured = ::RtlCaptureStackBackTrace(2, kMaxBacktraceSize,
+ mBacktrace.begin(), nullptr);
+ Unused << mBacktrace.resize(numCaptured);
+ // These backtraces might stick around for a while, so let's trim any
+ // excess memory.
+ mBacktrace.shrinkStorageToFit();
+ }
+
+#endif // !defined(MOZILLA_INTERNAL_API)
+
+ ModuleLoadInfo(ModuleLoadInfo&&) = default;
+ ModuleLoadInfo& operator=(ModuleLoadInfo&&) = default;
+
+ ModuleLoadInfo() = delete;
+ ModuleLoadInfo(const ModuleLoadInfo&) = delete;
+ ModuleLoadInfo& operator=(const ModuleLoadInfo&) = delete;
+
+ /**
+ * A "bare" module load is one that was mapped without the code passing
+ * through a call to ntdll!LdrLoadDll.
+ */
+ bool IsBare() const {
+ // SetBeginLoadTimeStamp() and SetEndLoadTimeStamp() are only called by the
+ // LdrLoadDll hook, so when mLoadTimeInfo == 0, we know that we are bare.
+ return !mLoadTimeInfo.QuadPart;
+ }
+
+ /**
+ * Returns true for DLL loads where LdrLoadDll was called but
+ * NtMapViewOfSection was not. This will happen for DLL requests where the DLL
+ * was already mapped into memory by a previous request.
+ */
+ bool WasMapped() const { return !mSectionName.IsEmpty(); }
+
+ /**
+ * Returns true for DLL load which was denied by our blocklist.
+ */
+ bool WasDenied() const {
+ return mStatus == ModuleLoadInfo::Status::Blocked ||
+ mStatus == ModuleLoadInfo::Status::Redirected;
+ }
+
+ // Timestamp for the creation of this event
+ LARGE_INTEGER mBeginTimestamp;
+ // Duration of the LdrLoadDll call
+ LARGE_INTEGER mLoadTimeInfo;
+ // Thread ID of this DLL load
+ DWORD mThreadId;
+ // The name requested of LdrLoadDll by its caller
+ nt::AllocatedUnicodeString mRequestedDllName;
+ // The name of the DLL that backs section that was mapped by the loader. This
+ // string is the effective name of the DLL that was resolved by the loader's
+ // path search algorithm.
+ nt::AllocatedUnicodeString mSectionName;
+ // The base address of the module's mapped section
+ const void* mBaseAddr;
+ // If the module was successfully loaded, stack trace of the DLL load request
+ Vector<PVOID, 0, nt::RtlAllocPolicy> mBacktrace;
+ // The status of DLL load
+ Status mStatus;
+ // Whether the module is one of the executables's dependent modules or not
+ bool mIsDependent;
+};
+
+using ModuleLoadInfoVec = Vector<ModuleLoadInfo, 0, nt::RtlAllocPolicy>;
+
+} // namespace mozilla
+
+#endif // mozilla_ModuleLoadInfo_h
diff --git a/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h b/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h
new file mode 100644
index 0000000000..628609092b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/NtLoaderAPI.h
@@ -0,0 +1,23 @@
+/* -*- 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_NtLoaderAPI_h
+#define mozilla_NtLoaderAPI_h
+
+#include "mozilla/LoaderAPIInterfaces.h"
+
+#if !defined(IMPL_MFBT)
+# error "This should only be included from mozglue!"
+#endif // !defined(IMPL_MFBT)
+
+namespace mozilla {
+
+extern "C" MOZ_IMPORT_API nt::LoaderAPI* GetNtLoaderAPI(
+ nt::LoaderObserver* aNewObserver);
+
+} // namespace mozilla
+
+#endif // mozilla_NtLoaderAPI_h
diff --git a/toolkit/xre/dllservices/mozglue/SharedSection.h b/toolkit/xre/dllservices/mozglue/SharedSection.h
new file mode 100644
index 0000000000..162c8e8343
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/SharedSection.h
@@ -0,0 +1,28 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_SharedSection_h
+#define mozilla_glue_SharedSection_h
+
+#include <winternl.h>
+#include "nscore.h"
+#include "mozilla/Span.h"
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+namespace mozilla::nt {
+
+// This interface provides a way to access winlauncher's shared section
+// through DllServices.
+struct NS_NO_VTABLE SharedSection {
+ virtual Span<const wchar_t> GetDependentModules() = 0;
+ // Returns a span of the whole space for dynamic blocklist entries.
+ // Use IsValidDynamicBlocklistEntry() to determine the end of the list.
+ virtual Span<const DllBlockInfoT<UNICODE_STRING>> GetDynamicBlocklist() = 0;
+};
+
+} // namespace mozilla::nt
+
+#endif // mozilla_glue_SharedSection_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
new file mode 100644
index 0000000000..9f0b50a583
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.cpp
@@ -0,0 +1,781 @@
+/* -*- 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 <windows.h>
+#include <winternl.h>
+
+#pragma warning(push)
+#pragma warning(disable : 4275 4530) // See msvc-stl-wrapper.template.h
+#include <map>
+#pragma warning(pop)
+
+#include "Authenticode.h"
+#include "BaseProfiler.h"
+#include "nsWindowsDllInterceptor.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/StackWalk_windows.h"
+#include "mozilla/TimeStamp.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "nsWindowsHelpers.h"
+#include "WindowsDllBlocklist.h"
+#include "mozilla/AutoProfilerLabel.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/glue/WindowsDllServices.h"
+#include "mozilla/glue/WinUtils.h"
+
+// Start new implementation
+#include "LoaderObserver.h"
+#include "ModuleLoadFrame.h"
+#include "mozilla/glue/WindowsUnicode.h"
+
+namespace mozilla {
+
+glue::Win32SRWLock gDllServicesLock;
+glue::detail::DllServicesBase* gDllServices;
+
+} // namespace mozilla
+
+using namespace mozilla;
+
+using CrashReporter::Annotation;
+using CrashReporter::AnnotationWriter;
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE const char*
+#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
+
+// define this for very verbose dll load debug spew
+#undef DEBUG_very_verbose
+
+static uint32_t sInitFlags;
+static bool sBlocklistInitAttempted;
+static bool sBlocklistInitFailed;
+static bool sUser32BeforeBlocklist;
+
+typedef MOZ_NORETURN_PTR void(__fastcall* BaseThreadInitThunk_func)(
+ BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam);
+static WindowsDllInterceptor::FuncHookType<BaseThreadInitThunk_func>
+ stub_BaseThreadInitThunk;
+
+typedef NTSTATUS(NTAPI* LdrLoadDll_func)(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName,
+ PHANDLE handle);
+static WindowsDllInterceptor::FuncHookType<LdrLoadDll_func> stub_LdrLoadDll;
+
+#ifdef _M_AMD64
+typedef decltype(RtlInstallFunctionTableCallback)*
+ RtlInstallFunctionTableCallback_func;
+static WindowsDllInterceptor::FuncHookType<RtlInstallFunctionTableCallback_func>
+ stub_RtlInstallFunctionTableCallback;
+
+extern uint8_t* sMsMpegJitCodeRegionStart;
+extern size_t sMsMpegJitCodeRegionSize;
+
+BOOLEAN WINAPI patched_RtlInstallFunctionTableCallback(
+ DWORD64 TableIdentifier, DWORD64 BaseAddress, DWORD Length,
+ PGET_RUNTIME_FUNCTION_CALLBACK Callback, PVOID Context,
+ PCWSTR OutOfProcessCallbackDll) {
+ // msmpeg2vdec.dll sets up a function table callback for their JIT code that
+ // just terminates the process, because their JIT doesn't have unwind info.
+ // If we see this callback being registered, record the region address, so
+ // that StackWalk.cpp can avoid unwinding addresses in this region.
+ //
+ // To keep things simple I'm not tracking unloads of msmpeg2vdec.dll.
+ // Worst case the stack walker will needlessly avoid a few pages of memory.
+
+ // Tricky: GetModuleHandleExW adds a ref by default; GetModuleHandleW doesn't.
+ HMODULE callbackModule = nullptr;
+ DWORD moduleFlags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
+
+ // These GetModuleHandle calls enter a critical section on Win7.
+ AutoSuppressStackWalking suppress;
+
+ if (GetModuleHandleExW(moduleFlags, (LPWSTR)Callback, &callbackModule) &&
+ GetModuleHandleW(L"msmpeg2vdec.dll") == callbackModule) {
+ sMsMpegJitCodeRegionStart = (uint8_t*)BaseAddress;
+ sMsMpegJitCodeRegionSize = Length;
+ }
+
+ return stub_RtlInstallFunctionTableCallback(TableIdentifier, BaseAddress,
+ Length, Callback, Context,
+ OutOfProcessCallbackDll);
+}
+#endif
+
+template <class T>
+struct RVAMap {
+ RVAMap(HANDLE map, DWORD offset) {
+ SYSTEM_INFO info;
+ GetSystemInfo(&info);
+
+ DWORD alignedOffset =
+ (offset / info.dwAllocationGranularity) * info.dwAllocationGranularity;
+
+ MOZ_ASSERT(offset - alignedOffset < info.dwAllocationGranularity, "Wtf");
+
+ mRealView = ::MapViewOfFile(map, FILE_MAP_READ, 0, alignedOffset,
+ sizeof(T) + (offset - alignedOffset));
+
+ mMappedView =
+ mRealView
+ ? reinterpret_cast<T*>((char*)mRealView + (offset - alignedOffset))
+ : nullptr;
+ }
+ ~RVAMap() {
+ if (mRealView) {
+ ::UnmapViewOfFile(mRealView);
+ }
+ }
+ operator const T*() const { return mMappedView; }
+ const T* operator->() const { return mMappedView; }
+
+ private:
+ const T* mMappedView;
+ void* mRealView;
+};
+
+static DWORD GetTimestamp(const wchar_t* path) {
+ DWORD timestamp = 0;
+
+ HANDLE file = ::CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr,
+ OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, nullptr);
+ if (file != INVALID_HANDLE_VALUE) {
+ HANDLE map =
+ ::CreateFileMappingW(file, nullptr, PAGE_READONLY, 0, 0, nullptr);
+ if (map) {
+ RVAMap<IMAGE_DOS_HEADER> peHeader(map, 0);
+ if (peHeader) {
+ RVAMap<IMAGE_NT_HEADERS> ntHeader(map, peHeader->e_lfanew);
+ if (ntHeader) {
+ timestamp = ntHeader->FileHeader.TimeDateStamp;
+ }
+ }
+ ::CloseHandle(map);
+ }
+ ::CloseHandle(file);
+ }
+
+ return timestamp;
+}
+
+// This lock protects both the reentrancy sentinel and the crash reporter
+// data structures.
+static CRITICAL_SECTION sLock;
+
+/**
+ * Some versions of Windows call LoadLibraryEx to get the version information
+ * for a DLL, which causes our patched LdrLoadDll implementation to re-enter
+ * itself and cause infinite recursion and a stack-exhaustion crash. We protect
+ * against reentrancy by allowing recursive loads of the same DLL.
+ *
+ * Note that we don't use __declspec(thread) because that doesn't work in DLLs
+ * loaded via LoadLibrary and there can be a limited number of TLS slots, so
+ * we roll our own.
+ */
+class ReentrancySentinel {
+ public:
+ explicit ReentrancySentinel(const char* dllName) {
+ DWORD currentThreadId = GetCurrentThreadId();
+ AutoCriticalSection lock(&sLock);
+ mPreviousDllName = (*sThreadMap)[currentThreadId];
+
+ // If there is a DLL currently being loaded and it has the same name
+ // as the current attempt, we're re-entering.
+ mReentered = mPreviousDllName && !stricmp(mPreviousDllName, dllName);
+ (*sThreadMap)[currentThreadId] = dllName;
+ }
+
+ ~ReentrancySentinel() {
+ DWORD currentThreadId = GetCurrentThreadId();
+ AutoCriticalSection lock(&sLock);
+ (*sThreadMap)[currentThreadId] = mPreviousDllName;
+ }
+
+ bool BailOut() const { return mReentered; };
+
+ static void InitializeStatics() {
+ InitializeCriticalSection(&sLock);
+ sThreadMap = new std::map<DWORD, const char*>;
+ }
+
+ private:
+ static std::map<DWORD, const char*>* sThreadMap;
+
+ const char* mPreviousDllName;
+ bool mReentered;
+};
+
+std::map<DWORD, const char*>* ReentrancySentinel::sThreadMap;
+
+using WritableBuffer = mozilla::glue::detail::WritableBuffer<1024>;
+
+/**
+ * This is a linked list of DLLs that have been blocked. It doesn't use
+ * mozilla::LinkedList because this is an append-only list and doesn't need
+ * to be doubly linked.
+ */
+class DllBlockSet {
+ public:
+ static void Add(const char* name, unsigned long long version);
+
+ // Write the list of blocked DLLs to a WritableBuffer object. This method is
+ // run after a crash occurs and must therefore not use the heap, etc.
+ static void Write(WritableBuffer& buffer);
+
+ private:
+ DllBlockSet(const char* name, unsigned long long version)
+ : mName(name), mVersion(version), mNext(nullptr) {}
+
+ const char* mName; // points into the gWindowsDllBlocklist string
+ unsigned long long mVersion;
+ DllBlockSet* mNext;
+
+ static DllBlockSet* gFirst;
+};
+
+DllBlockSet* DllBlockSet::gFirst;
+
+void DllBlockSet::Add(const char* name, unsigned long long version) {
+ AutoCriticalSection lock(&sLock);
+ for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+ if (0 == strcmp(b->mName, name) && b->mVersion == version) {
+ return;
+ }
+ }
+ // Not already present
+ DllBlockSet* n = new DllBlockSet(name, version);
+ n->mNext = gFirst;
+ gFirst = n;
+}
+
+void DllBlockSet::Write(WritableBuffer& buffer) {
+ // It would be nicer to use AutoCriticalSection here. However, its destructor
+ // might not run if an exception occurs, in which case we would never leave
+ // the critical section. (MSVC warns about this possibility.) So we
+ // enter and leave manually.
+ ::EnterCriticalSection(&sLock);
+
+ // Because this method is called after a crash occurs, and uses heap memory,
+ // protect this entire block with a structured exception handler.
+ MOZ_SEH_TRY {
+ for (DllBlockSet* b = gFirst; b; b = b->mNext) {
+ // write name[,v.v.v.v];
+ buffer.Write(b->mName, strlen(b->mName));
+ if (b->mVersion != DllBlockInfo::ALL_VERSIONS) {
+ buffer.Write(",", 1);
+ uint16_t parts[4];
+ parts[0] = b->mVersion >> 48;
+ parts[1] = (b->mVersion >> 32) & 0xFFFF;
+ parts[2] = (b->mVersion >> 16) & 0xFFFF;
+ parts[3] = b->mVersion & 0xFFFF;
+ for (int p = 0; p < 4; ++p) {
+ char buf[32];
+ _ltoa_s(parts[p], buf, sizeof(buf), 10);
+ buffer.Write(buf, strlen(buf));
+ if (p != 3) {
+ buffer.Write(".", 1);
+ }
+ }
+ }
+ buffer.Write(";", 1);
+ }
+ }
+ MOZ_SEH_EXCEPT(EXCEPTION_EXECUTE_HANDLER) {}
+
+ ::LeaveCriticalSection(&sLock);
+}
+
+static UniquePtr<wchar_t[]> getFullPath(PWCHAR filePath, wchar_t* fname) {
+ // In Windows 8, the first parameter seems to be used for more than just the
+ // path name. For example, its numerical value can be 1. Passing a non-valid
+ // pointer to SearchPathW will cause a crash, so we need to check to see if we
+ // are handed a valid pointer, and otherwise just pass nullptr to SearchPathW.
+ PWCHAR sanitizedFilePath = nullptr;
+ if ((uintptr_t(filePath) >= 65536) && ((uintptr_t(filePath) & 1) == 0)) {
+ sanitizedFilePath = filePath;
+ }
+
+ // figure out the length of the string that we need
+ DWORD pathlen =
+ SearchPathW(sanitizedFilePath, fname, L".dll", 0, nullptr, nullptr);
+ if (pathlen == 0) {
+ return nullptr;
+ }
+
+ auto full_fname = MakeUnique<wchar_t[]>(pathlen + 1);
+ if (!full_fname) {
+ // couldn't allocate memory?
+ return nullptr;
+ }
+
+ // now actually grab it
+ SearchPathW(sanitizedFilePath, fname, L".dll", pathlen + 1, full_fname.get(),
+ nullptr);
+ return full_fname;
+}
+
+// No builtin function to find the last character matching a set
+static wchar_t* lastslash(wchar_t* s, int len) {
+ for (wchar_t* c = s + len - 1; c >= s; --c) {
+ if (*c == L'\\' || *c == L'/') {
+ return c;
+ }
+ }
+ return nullptr;
+}
+
+static NTSTATUS NTAPI patched_LdrLoadDll(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName,
+ PHANDLE handle) {
+ // We have UCS2 (UTF16?), we want ASCII, but we also just want the filename
+ // portion
+#define DLLNAME_MAX 128
+ char dllName[DLLNAME_MAX + 1];
+ wchar_t* dll_part;
+ char* dot;
+
+ int len = moduleFileName->Length / 2;
+ wchar_t* fname = moduleFileName->Buffer;
+ UniquePtr<wchar_t[]> full_fname;
+
+ // The filename isn't guaranteed to be null terminated, but in practice
+ // it always will be; ensure that this is so, and bail if not.
+ // This is done instead of the more robust approach because of bug 527122,
+ // where lots of weird things were happening when we tried to make a copy.
+ if (moduleFileName->MaximumLength < moduleFileName->Length + 2 ||
+ fname[len] != 0) {
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll: non-null terminated string found!\n");
+#endif
+ goto continue_loading;
+ }
+
+ dll_part = lastslash(fname, len);
+ if (dll_part) {
+ dll_part = dll_part + 1;
+ len -= dll_part - fname;
+ } else {
+ dll_part = fname;
+ }
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: dll_part '%S' %d\n", dll_part, len);
+#endif
+
+ // if it's too long, then, we assume we won't want to block it,
+ // since DLLNAME_MAX should be at least long enough to hold the longest
+ // entry in our blocklist.
+ if (len > DLLNAME_MAX) {
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll: len too long! %d\n", len);
+#endif
+ goto continue_loading;
+ }
+
+ // copy over to our char byte buffer, lowercasing ASCII as we go
+ for (int i = 0; i < len; i++) {
+ wchar_t c = dll_part[i];
+
+ if (c > 0x7f) {
+ // welp, it's not ascii; if we need to add non-ascii things to
+ // our blocklist, we'll have to remove this limitation.
+ goto continue_loading;
+ }
+
+ // ensure that dll name is all lowercase
+ if (c >= 'A' && c <= 'Z') c += 'a' - 'A';
+
+ dllName[i] = (char)c;
+ }
+
+ dllName[len] = 0;
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: dll name '%s'\n", dllName);
+#endif
+
+ if (!(sInitFlags & eDllBlocklistInitFlagWasBootstrapped)) {
+ // Block a suspicious binary that uses various 12-digit hex strings
+ // e.g. MovieMode.48CA2AEFA22D.dll (bug 973138)
+ dot = strchr(dllName, '.');
+ if (dot && (strchr(dot + 1, '.') == dot + 13)) {
+ char* end = nullptr;
+ _strtoui64(dot + 1, &end, 16);
+ if (end == dot + 13) {
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+ // Block binaries where the filename is at least 16 hex digits
+ if (dot && ((dot - dllName) >= 16)) {
+ char* current = dllName;
+ while (current < dot && isxdigit(*current)) {
+ current++;
+ }
+ if (current == dot) {
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+
+ // then compare to everything on the blocklist
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(info);
+ while (info->mName) {
+ if (strcmp(info->mName, dllName) == 0) break;
+
+ info++;
+ }
+
+ if (info->mName) {
+ bool load_ok = false;
+
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: info->mName: '%s'\n", info->mName);
+#endif
+
+ if (info->mFlags & DllBlockInfo::REDIRECT_TO_NOOP_ENTRYPOINT) {
+ printf_stderr(
+ "LdrLoadDll: "
+ "Ignoring the REDIRECT_TO_NOOP_ENTRYPOINT flag\n");
+ }
+
+ if ((info->mFlags & DllBlockInfo::BLOCK_WIN8_AND_OLDER) &&
+ IsWin8Point1OrLater()) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::BLOCK_WIN7_AND_OLDER) &&
+ IsWin8OrLater()) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::CHILD_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::UTILITY_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsUtilityProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::SOCKET_PROCESSES_ONLY) &&
+ !(sInitFlags & eDllBlocklistInitFlagIsSocketProcess)) {
+ goto continue_loading;
+ }
+
+ if ((info->mFlags & DllBlockInfo::BROWSER_PROCESS_ONLY) &&
+ (sInitFlags & eDllBlocklistInitFlagIsChildProcess)) {
+ goto continue_loading;
+ }
+
+ unsigned long long fVersion = DllBlockInfo::ALL_VERSIONS;
+
+ if (info->mMaxVersion != DllBlockInfo::ALL_VERSIONS) {
+ ReentrancySentinel sentinel(dllName);
+ if (sentinel.BailOut()) {
+ goto continue_loading;
+ }
+
+ full_fname = getFullPath(filePath, fname);
+ if (!full_fname) {
+ // uh, we couldn't find the DLL at all, so...
+ printf_stderr(
+ "LdrLoadDll: Blocking load of '%s' (SearchPathW didn't find "
+ "it?)\n",
+ dllName);
+ return STATUS_DLL_NOT_FOUND;
+ }
+
+ if (info->mFlags & DllBlockInfo::USE_TIMESTAMP) {
+ fVersion = GetTimestamp(full_fname.get());
+ if (fVersion > info->mMaxVersion) {
+ load_ok = true;
+ }
+ } else {
+ LauncherResult<ModuleVersion> version =
+ GetModuleVersion(full_fname.get());
+ // If we failed to get the version information, we block.
+ if (version.isOk()) {
+ load_ok = !info->IsVersionBlocked(version.unwrap());
+ }
+ }
+ }
+
+ if (!load_ok) {
+ printf_stderr(
+ "LdrLoadDll: Blocking load of '%s' -- see "
+ "http://www.mozilla.com/en-US/blocklist/\n",
+ dllName);
+ DllBlockSet::Add(info->mName, fVersion);
+ return STATUS_DLL_NOT_FOUND;
+ }
+ }
+ }
+
+continue_loading:
+#ifdef DEBUG_very_verbose
+ printf_stderr("LdrLoadDll: continuing load... ('%S')\n",
+ moduleFileName->Buffer);
+#endif
+
+ glue::ModuleLoadFrame loadFrame(moduleFileName);
+
+ NTSTATUS ret;
+ HANDLE myHandle;
+
+ {
+#if defined(_M_AMD64) || defined(_M_ARM64)
+ AutoSuppressStackWalking suppress;
+#endif
+ ret = stub_LdrLoadDll(filePath, flags, moduleFileName, &myHandle);
+ }
+
+ if (handle) {
+ *handle = myHandle;
+ }
+
+ loadFrame.SetLoadStatus(ret, myHandle);
+
+ return ret;
+}
+
+#if defined(NIGHTLY_BUILD)
+// Map of specific thread proc addresses we should block. In particular,
+// LoadLibrary* APIs which indicate DLL injection
+static void* gStartAddressesToBlock[4];
+#endif // defined(NIGHTLY_BUILD)
+
+static bool ShouldBlockThread(void* aStartAddress) {
+ // Allows crashfirefox.exe to continue to work. Also if your threadproc is
+ // null, this crash is intentional.
+ if (aStartAddress == nullptr) {
+ return false;
+ }
+
+#if defined(NIGHTLY_BUILD)
+ for (auto p : gStartAddressesToBlock) {
+ if (p == aStartAddress) {
+ return true;
+ }
+ }
+#endif
+
+ bool shouldBlock = false;
+ MEMORY_BASIC_INFORMATION startAddressInfo = {0};
+ if (VirtualQuery(aStartAddress, &startAddressInfo,
+ sizeof(startAddressInfo))) {
+ shouldBlock |= startAddressInfo.State != MEM_COMMIT;
+ shouldBlock |= startAddressInfo.Protect != PAGE_EXECUTE_READ;
+ }
+
+ return shouldBlock;
+}
+
+// Allows blocked threads to still run normally through BaseThreadInitThunk, in
+// case there's any magic there that we shouldn't skip.
+static DWORD WINAPI NopThreadProc(void* /* aThreadParam */) { return 0; }
+
+static MOZ_NORETURN void __fastcall patched_BaseThreadInitThunk(
+ BOOL aIsInitialThread, void* aStartAddress, void* aThreadParam) {
+ if (ShouldBlockThread(aStartAddress)) {
+ aStartAddress = (void*)NopThreadProc;
+ }
+
+ stub_BaseThreadInitThunk(aIsInitialThread, aStartAddress, aThreadParam);
+}
+
+static WindowsDllInterceptor NtDllIntercept;
+static WindowsDllInterceptor Kernel32Intercept;
+
+static void GetNativeNtBlockSetWriter();
+
+static glue::LoaderObserver gMozglueLoaderObserver;
+static nt::WinLauncherServices gWinLauncher;
+
+MFBT_API void DllBlocklist_Initialize(uint32_t aInitFlags) {
+ if (sBlocklistInitAttempted) {
+ return;
+ }
+ sBlocklistInitAttempted = true;
+
+ sInitFlags = aInitFlags;
+
+ glue::ModuleLoadFrame::StaticInit(&gMozglueLoaderObserver, &gWinLauncher);
+
+#ifdef _M_AMD64
+ if (!IsWin8OrLater()) {
+ Kernel32Intercept.Init(L"kernel32.dll");
+ // The crash that this hook works around is only seen on Win7.
+ stub_RtlInstallFunctionTableCallback.Set(
+ Kernel32Intercept, "RtlInstallFunctionTableCallback",
+ &patched_RtlInstallFunctionTableCallback);
+ }
+#endif
+
+ // Bug 1361410: WRusr.dll will overwrite our hook and cause a crash.
+ // Workaround: If we detect WRusr.dll, don't hook.
+ if (!GetModuleHandleW(L"WRusr.dll")) {
+ Kernel32Intercept.Init(L"kernel32.dll");
+ if (!stub_BaseThreadInitThunk.SetDetour(Kernel32Intercept,
+ "BaseThreadInitThunk",
+ &patched_BaseThreadInitThunk)) {
+#ifdef DEBUG
+ printf_stderr("BaseThreadInitThunk hook failed\n");
+#endif
+ }
+ }
+
+#if defined(NIGHTLY_BUILD)
+ // Populate a list of thread start addresses to block.
+ HMODULE hKernel = GetModuleHandleW(L"kernel32.dll");
+ if (hKernel) {
+ void* pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryA");
+ gStartAddressesToBlock[0] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryW");
+ gStartAddressesToBlock[1] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExA");
+ gStartAddressesToBlock[2] = pProc;
+
+ pProc = (void*)GetProcAddress(hKernel, "LoadLibraryExW");
+ gStartAddressesToBlock[3] = pProc;
+ }
+#endif
+
+ if (aInitFlags & eDllBlocklistInitFlagWasBootstrapped) {
+ GetNativeNtBlockSetWriter();
+ return;
+ }
+
+ // There are a couple of exceptional cases where we skip user32.dll check.
+ // - If the the process was bootstrapped by the launcher process, AppInit
+ // DLLs will be intercepted by the new DllBlockList. No need to check
+ // here.
+ // - The code to initialize the base profiler loads winmm.dll which
+ // statically links user32.dll on an older Windows. This means if the base
+ // profiler is active before coming here, we cannot fully intercept AppInit
+ // DLLs. Given that the base profiler is used outside the typical use
+ // cases, it's ok not to check user32.dll in this scenario.
+ const bool skipUser32Check =
+ (sInitFlags & eDllBlocklistInitFlagWasBootstrapped) ||
+ (!IsWin10AnniversaryUpdateOrLater() &&
+ baseprofiler::profiler_is_active());
+
+ // In order to be effective against AppInit DLLs, the blocklist must be
+ // initialized before user32.dll is loaded into the process (bug 932100).
+ if (!skipUser32Check && GetModuleHandleW(L"user32.dll")) {
+ sUser32BeforeBlocklist = true;
+#ifdef DEBUG
+ printf_stderr("DLL blocklist was unable to intercept AppInit DLLs.\n");
+#endif
+ }
+
+ NtDllIntercept.Init("ntdll.dll");
+
+ ReentrancySentinel::InitializeStatics();
+
+ // We specifically use a detour, because there are cases where external
+ // code also tries to hook LdrLoadDll, and doesn't know how to relocate our
+ // nop space patches. (Bug 951827)
+ bool ok = stub_LdrLoadDll.SetDetour(NtDllIntercept, "LdrLoadDll",
+ &patched_LdrLoadDll);
+
+ if (!ok) {
+ sBlocklistInitFailed = true;
+#ifdef DEBUG
+ printf_stderr("LdrLoadDll hook failed, no dll blocklisting active\n");
+#endif
+ }
+
+ // If someone injects a thread early that causes user32.dll to load off the
+ // main thread this causes issues, so load it as soon as we've initialized
+ // the block-list. (See bug 1400637)
+ if (!sUser32BeforeBlocklist && !IsWin32kLockedDown()) {
+ ::LoadLibraryW(L"user32.dll");
+ }
+}
+
+#ifdef DEBUG
+MFBT_API void DllBlocklist_Shutdown() {}
+#endif // DEBUG
+
+static void InternalWriteNotes(AnnotationWriter& aWriter) {
+ WritableBuffer buffer;
+ DllBlockSet::Write(buffer);
+
+ aWriter.Write(Annotation::BlockedDllList, buffer.Data(), buffer.Length());
+
+ if (sBlocklistInitFailed) {
+ aWriter.Write(Annotation::BlocklistInitFailed, "1");
+ }
+
+ if (sUser32BeforeBlocklist) {
+ aWriter.Write(Annotation::User32BeforeBlocklist, "1");
+ }
+}
+
+using WriterFn = void (*)(AnnotationWriter&);
+static WriterFn gWriterFn = &InternalWriteNotes;
+
+static void GetNativeNtBlockSetWriter() {
+ auto nativeWriter = reinterpret_cast<WriterFn>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "NativeNtBlockSet_Write"));
+ if (nativeWriter) {
+ gWriterFn = nativeWriter;
+ }
+}
+
+MFBT_API void DllBlocklist_WriteNotes(AnnotationWriter& aWriter) {
+ MOZ_ASSERT(gWriterFn);
+ gWriterFn(aWriter);
+}
+
+MFBT_API bool DllBlocklist_CheckStatus() {
+ if (sBlocklistInitFailed || sUser32BeforeBlocklist) return false;
+ return true;
+}
+
+// ============================================================================
+// This section is for DLL Services
+// ============================================================================
+
+namespace mozilla {
+Authenticode* GetAuthenticode();
+} // namespace mozilla
+
+/**
+ * Please note that DllBlocklist_SetFullDllServices is called with
+ * aSvc = nullptr to de-initialize the resources even though they
+ * have been initialized via DllBlocklist_SetBasicDllServices.
+ */
+MFBT_API void DllBlocklist_SetFullDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc) {
+ glue::AutoExclusiveLock lock(gDllServicesLock);
+ if (aSvc) {
+ aSvc->SetAuthenticodeImpl(GetAuthenticode());
+ aSvc->SetWinLauncherServices(gWinLauncher);
+ gMozglueLoaderObserver.Forward(aSvc);
+ }
+
+ gDllServices = aSvc;
+}
+
+MFBT_API void DllBlocklist_SetBasicDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc) {
+ if (!aSvc) {
+ return;
+ }
+
+ aSvc->SetAuthenticodeImpl(GetAuthenticode());
+ gMozglueLoaderObserver.Disable();
+}
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
new file mode 100644
index 0000000000..d67f49a17e
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklist.h
@@ -0,0 +1,79 @@
+/* -*- 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 mozilla_windowsdllblocklist_h
+#define mozilla_windowsdllblocklist_h
+
+#if (defined(_MSC_VER) || defined(__MINGW32__)) && \
+ (defined(_M_IX86) || defined(_M_X64) || defined(_M_ARM64))
+
+# include <windows.h>
+# include "CrashAnnotations.h"
+# include "mozilla/Attributes.h"
+# include "mozilla/Types.h"
+
+# define HAS_DLL_BLOCKLIST
+
+enum DllBlocklistInitFlags {
+ eDllBlocklistInitFlagDefault = 0,
+ eDllBlocklistInitFlagIsChildProcess = 1,
+ eDllBlocklistInitFlagWasBootstrapped = 2,
+ eDllBlocklistInitFlagIsUtilityProcess = 4,
+ eDllBlocklistInitFlagIsSocketProcess = 8
+};
+
+// Only available from within firefox.exe
+# if !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API)
+extern uint32_t gBlocklistInitFlags;
+# endif // !defined(IMPL_MFBT) && !defined(MOZILLA_INTERNAL_API)
+
+MFBT_API void DllBlocklist_Initialize(
+ uint32_t aInitFlags = eDllBlocklistInitFlagDefault);
+MFBT_API void DllBlocklist_WriteNotes(CrashReporter::AnnotationWriter& aWriter);
+MFBT_API bool DllBlocklist_CheckStatus();
+
+// This export intends to clean up after DllBlocklist_Initialize().
+// It's disabled in release builds for performance and to limit callers' ability
+// to interfere with dll blocking.
+# ifdef DEBUG
+MFBT_API void DllBlocklist_Shutdown();
+# endif // DEBUG
+
+namespace mozilla {
+namespace glue {
+namespace detail {
+// Forward declaration
+class DllServicesBase;
+
+template <size_t N>
+class WritableBuffer {
+ char mBuffer[N];
+ size_t mLen;
+
+ size_t Available() const { return sizeof(mBuffer) - mLen; }
+
+ public:
+ WritableBuffer() : mBuffer{0}, mLen(0) {}
+
+ void Write(const char* aData, size_t aLen) {
+ size_t writable_len = std::min(aLen, Available());
+ memcpy(mBuffer + mLen, aData, writable_len);
+ mLen += writable_len;
+ }
+
+ size_t Length() const { return mLen; }
+ const char* Data() const { return mBuffer; }
+};
+} // namespace detail
+} // namespace glue
+} // namespace mozilla
+
+MFBT_API void DllBlocklist_SetFullDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc);
+MFBT_API void DllBlocklist_SetBasicDllServices(
+ mozilla::glue::detail::DllServicesBase* aSvc);
+
+#endif // defined(_MSC_VER) && (defined(_M_IX86) || defined(_M_X64))
+#endif // mozilla_windowsdllblocklist_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h
new file mode 100644
index 0000000000..b9f176ac02
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistCommon.h
@@ -0,0 +1,46 @@
+/* -*- 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_WindowsDllBlocklistCommon_h
+#define mozilla_WindowsDllBlocklistCommon_h
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/WindowsDllBlocklistInfo.h"
+
+#if !defined(DLL_BLOCKLIST_STRING_TYPE)
+# error "You must define DLL_BLOCKLIST_STRING_TYPE"
+#endif // !defined(DLL_BLOCKLIST_STRING_TYPE)
+
+#define DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(name) \
+ using DllBlockInfo = mozilla::DllBlockInfoT<DLL_BLOCKLIST_STRING_TYPE>; \
+ static const DllBlockInfo name[] = {
+#define DLL_BLOCKLIST_DEFINITIONS_BEGIN \
+ DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gWindowsDllBlocklist)
+
+#define DLL_BLOCKLIST_DEFINITIONS_END \
+ {} \
+ } \
+ ;
+
+#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \
+ const DllBlockInfo* name = &list[0]
+
+#define DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(name) \
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist)
+
+#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, list) \
+ const DllBlockInfo* name = &list[mozilla::ArrayLength(list) - 1]
+
+#define DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(name) \
+ DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY_FOR(name, gWindowsDllBlocklist)
+
+#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, list) \
+ const size_t name = mozilla::ArrayLength(list) - 1
+
+#define DECLARE_DLL_BLOCKLIST_NUM_ENTRIES(name) \
+ DECLARE_DLL_BLOCKLIST_NUM_ENTRIES_FOR(name, gWindowsDllBlocklist)
+
+#endif // mozilla_WindowsDllBlocklistCommon_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in
new file mode 100644
index 0000000000..99360930ca
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistDefs.in
@@ -0,0 +1,356 @@
+# -*- 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/.
+
+# This file exposes five lists:
+# ALL_PROCESSES, BROWSER_PROCESS, CHILD_PROCESSES, UTILITY_PROCESSES,
+# and SOCKET_PROCESSES
+#
+# In addition, each of those lists supports a special variant for test-only
+# entries:
+# ALL_PROCESSES_TESTS, BROWSER_PROCESS_TESTS, CHILD_PROCESSES_TESTS,
+# UTILITY_PROCESSES_TESTS, and SOCKET_PROCESSES_TESTS
+#
+# Choose the list that is applicable to the applicable process type(s) for your
+# DLL block.
+#
+# The currently supported blocklist entry types are:
+# DllBlocklistEntry, A11yBlocklistEntry, LspBlocklistEntry,
+# RedirectToNoOpEntryPoint
+# (See gen_dll_blocklist_defs.py for their documentation.)
+#
+# Example:
+# ALL_PROCESSES += [
+# DllBlocklistEntry("foo.dll", (1, 2, 3, 4)),
+# DllBlocklistEntry("foo.dll", ALL_VERSIONS),
+# DllBlocklistEntry("foo.dll", UNVERSIONED),
+# DllBlocklistEntry("foo.dll", 0x0000123400000000),
+# DllBlocklistEntry("foo.dll", PETimeStamp(0x12345678)),
+# ]
+#
+# The version parameter the "last bad" version, that is, we block anything that
+# is less-than or equal to that version.
+
+ALL_PROCESSES += [
+ # NPFFAddon - Known malware
+ DllBlocklistEntry("npffaddon.dll", ALL_VERSIONS),
+
+ # AVG 8 - Antivirus vendor AVG, old version, plugin already blocklisted
+ DllBlocklistEntry("avgrsstx.dll", (8,5,0,401)),
+
+ # calc.dll - Suspected malware
+ DllBlocklistEntry("calc.dll", (1,0,0,1)),
+
+ # hook.dll - Suspected malware
+ DllBlocklistEntry("hook.dll", ALL_VERSIONS),
+
+ # GoogleDesktopNetwork3.dll - Extremely old, unversioned instances
+ # of this DLL cause crashes
+ DllBlocklistEntry("googledesktopnetwork3.dll", UNVERSIONED),
+
+ # rdolib.dll - Suspected malware
+ DllBlocklistEntry("rdolib.dll", (6,0,88,4)),
+
+ # fgjk4wvb.dll - Suspected malware
+ DllBlocklistEntry("fgjk4wvb.dll", (8,8,8,8)),
+
+ # radhslib.dll - Naomi internet filter - unmaintained since 2006
+ DllBlocklistEntry("radhslib.dll", UNVERSIONED),
+
+ # Music download filter for vkontakte.ru - old instances
+ # of this DLL cause crashes
+ DllBlocklistEntry("vksaver.dll", (2,2,2,0)),
+
+ # Topcrash in Firefox 4.0b1
+ DllBlocklistEntry("rlxf.dll", (1,2,323,1)),
+
+ # psicon.dll - Topcrashes in Thunderbird, and some crashes in Firefox
+ # Adobe photoshop library, now redundant in later installations
+ DllBlocklistEntry("psicon.dll", ALL_VERSIONS),
+
+ # Topcrash in Firefox 4 betas (bug 618899),
+ DllBlocklistEntry("accelerator.dll", (3,2,1,6)),
+
+ # Topcrash with Roboform in Firefox 8 (bug 699134),
+ DllBlocklistEntry("rf-firefox.dll", (7,6,1,0)),
+ DllBlocklistEntry("roboform.dll", (7,6,1,0)),
+
+ # Topcrash with Babylon Toolbar on FF16+ (bug 721264),
+ DllBlocklistEntry("babyfox.dll", ALL_VERSIONS),
+
+ # sprotector.dll crashes, bug 957258
+ DllBlocklistEntry("sprotector.dll", ALL_VERSIONS),
+
+ # Windows Media Foundation FLAC decoder and type sniffer (bug 839031).
+ DllBlocklistEntry("mfflac.dll", ALL_VERSIONS),
+
+ # Older Relevant Knowledge DLLs cause us to crash (bug 904001).
+ DllBlocklistEntry("rlnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("pmnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("opnx.dll", (1, 3, 334, 9)),
+ DllBlocklistEntry("prnx.dll", (1, 3, 334, 9)),
+
+ # Older belgian ID card software causes Firefox to crash or hang on
+ # shutdown, bug 831285 and 918399.
+ DllBlocklistEntry("beid35cardlayer.dll", (3, 5, 6, 6968)),
+
+ # bug 925459, bitguard crashes
+ DllBlocklistEntry("bitguard.dll", ALL_VERSIONS),
+
+ # bug 812683 - crashes in Windows library when Asus Gamer OSD is installed
+ # Software is discontinued/unsupported
+ DllBlocklistEntry("atkdx11disp.dll", ALL_VERSIONS),
+
+ # Topcrash with Conduit SearchProtect, bug 944542
+ DllBlocklistEntry("spvc32.dll", ALL_VERSIONS),
+
+ # Topcrash with V-bates, bug 1002748 and bug 1023239
+ DllBlocklistEntry("libinject.dll", UNVERSIONED),
+ DllBlocklistEntry("libinject2.dll", PETimeStamp(0x537DDC93)),
+ DllBlocklistEntry("libredir2.dll", PETimeStamp(0x5385B7ED)),
+
+ # Crashes with RoboForm2Go written against old SDK, bug 988311/1196859
+ DllBlocklistEntry("rf-firefox-22.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rf-firefox-40.dll", ALL_VERSIONS),
+
+ # Crashes with DesktopTemperature, bug 1046382
+ DllBlocklistEntry("dtwxsvc.dll", PETimeStamp(0x53153234)),
+
+ # Startup crashes with Lenovo Onekey Theater, bug 1123778
+ DllBlocklistEntry("activedetect32.dll", UNVERSIONED),
+ DllBlocklistEntry("activedetect64.dll", UNVERSIONED),
+ DllBlocklistEntry("windowsapihookdll32.dll", UNVERSIONED),
+ DllBlocklistEntry("windowsapihookdll64.dll", UNVERSIONED),
+
+ # Flash crashes with RealNetworks RealDownloader, bug 1132663
+ DllBlocklistEntry("rndlnpshimswf.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rndlmainbrowserrecordplugin.dll", ALL_VERSIONS),
+
+ # Startup crashes with RealNetworks Browser Record Plugin, bug 1170141
+ DllBlocklistEntry("nprpffbrowserrecordext.dll", ALL_VERSIONS),
+ DllBlocklistEntry("nprndlffbrowserrecordext.dll", ALL_VERSIONS),
+
+ # Crashes with CyberLink YouCam, bug 1136968
+ DllBlocklistEntry("ycwebcamerasource.ax", (2, 0, 0, 1611)),
+
+ # Old version of WebcamMax crashes WebRTC, bug 1130061
+ DllBlocklistEntry("vwcsource.ax", (1, 5, 0, 0)),
+
+ # NetOp School, discontinued product, bug 763395
+ DllBlocklistEntry("nlsp.dll", (6, 23, 2012, 19)),
+
+ # Orbit Downloader, bug 1222819
+ DllBlocklistEntry("grabdll.dll", (2, 6, 1, 0)),
+ DllBlocklistEntry("grabkernel.dll", (1, 0, 0, 1)),
+
+ # ESET, bug 1229252
+ DllBlocklistEntry("eoppmonitor.dll", ALL_VERSIONS),
+
+ # SS2OSD, bug 1262348
+ DllBlocklistEntry("ss2osd.dll", ALL_VERSIONS),
+ DllBlocklistEntry("ss2devprops.dll", ALL_VERSIONS),
+
+ # NHASUSSTRIXOSD.DLL, bug 1269244
+ DllBlocklistEntry("nhasusstrixosd.dll", ALL_VERSIONS),
+ DllBlocklistEntry("nhasusstrixdevprops.dll", ALL_VERSIONS),
+
+ # Crashes with PremierOpinion/RelevantKnowledge, bug 1277846
+ DllBlocklistEntry("opls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("opls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("pmls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("pmls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("prls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("prls64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rlls.dll", ALL_VERSIONS),
+ DllBlocklistEntry("rlls64.dll", ALL_VERSIONS),
+
+ # Vorbis DirectShow filters, bug 1239690.
+ DllBlocklistEntry("vorbis.acm", (0, 0, 3, 6)),
+
+ # AhnLab Internet Security, bug 1311969
+ DllBlocklistEntry("nzbrcom.dll", ALL_VERSIONS),
+
+ # K7TotalSecurity, bug 1339083 and bug 1694489.
+ DllBlocklistEntry("k7pswsen.dll", (15, 2, 2, 102)),
+
+ # smci*.dll - goobzo crashware (bug 1339908),
+ DllBlocklistEntry("smci32.dll", ALL_VERSIONS),
+ DllBlocklistEntry("smci64.dll", ALL_VERSIONS),
+
+ # Crashes with Internet Download Manager, bug 1333486
+ DllBlocklistEntry("idmcchandler7.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler7_64.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler5.dll", ALL_VERSIONS),
+ DllBlocklistEntry("idmcchandler5_64.dll", ALL_VERSIONS),
+
+ # Nahimic 2 breaks applicaton update (bug 1356637),
+ DllBlocklistEntry("nahimic2devprops.dll", (2, 5, 19, 0xffff)),
+ # Nahimic is causing crashes, bug 1233556
+ DllBlocklistEntry("nahimicmsiosd.dll", UNVERSIONED),
+ # Nahimic is causing crashes, bug 1360029
+ DllBlocklistEntry("nahimicvrdevprops.dll", UNVERSIONED),
+ DllBlocklistEntry("nahimic2osd.dll", (2, 5, 19, 0xffff)),
+ DllBlocklistEntry("nahimicmsidevprops.dll", UNVERSIONED),
+
+ # Bug 1268470 - crashes with Kaspersky Lab on Windows 8
+ DllBlocklistEntry("klsihk64.dll", (14, 0, 456, 0xffff),
+ BLOCK_WIN8_AND_OLDER),
+
+ # Bug 1579758, crashes with OpenSC nightly version 0.19.0.448 and lower
+ DllBlocklistEntry("onepin-opensc-pkcs11.dll", (0, 19, 0, 448)),
+
+ # Avecto Privilege Guard causes crashes, bug 1385542
+ DllBlocklistEntry("pghook.dll", ALL_VERSIONS),
+
+ # Old versions of G DATA BankGuard, bug 1421991
+ DllBlocklistEntry("banksafe64.dll", (1, 2, 15299, 65535)),
+
+ # Old versions of G DATA, bug 1043775
+ DllBlocklistEntry("gdkbfltdll64.dll", (1, 0, 14141, 240)),
+
+ # Dell Backup and Recovery tool causes crashes, bug 1433408
+ DllBlocklistEntry("dbroverlayiconnotbackuped.dll", (1, 8, 0, 9)),
+ DllBlocklistEntry("dbroverlayiconbackuped.dll", (1, 8, 0, 9)),
+
+ # NVIDIA nView Desktop Management causes crashes, bug 1465787
+ DllBlocklistEntry("nviewh64.dll", (6, 14, 10, 14847)),
+
+ # Ivanti Endpoint Security, bug 1553776
+ DllBlocklistEntry("sxwmon.dll", ALL_VERSIONS),
+ DllBlocklistEntry("sxwmon64.dll", ALL_VERSIONS),
+
+ # 360 Safeguard/360 Total Security causes a11y crashes, bug 1536227.
+ DllBlocklistEntry("safemon64.dll", ALL_VERSIONS),
+
+ # Old versions of Digital Guardian, bug 1318858, bug 1603974,
+ # and bug 1672367
+ RedirectToNoOpEntryPoint("dgapi.dll", (7, 5, 0xffff, 0xffff)),
+ RedirectToNoOpEntryPoint("dgapi64.dll", (7, 5, 0xffff, 0xffff)),
+
+ # Old versions of COMODO Internet Security, bug 1608048
+ DllBlocklistEntry("IseGuard32.dll", (1, 6, 13835, 184)),
+ DllBlocklistEntry("IseGuard64.dll", (1, 6, 13835, 184)),
+
+ # Old version of COMODO Firewall, bug 1407712 and bug 1624336
+ DllBlocklistEntry("guard64.dll", (8, 4, 0, 65535)),
+
+ # Old version of Panda Security, bug 1637984 and bug 1705125
+ DllBlocklistEntry("PavLspHook64.dll", (9, 2, 2, 1), BLOCK_WIN7_AND_OLDER),
+ DllBlocklistEntry("PavSHook64.dll", (9, 2, 2, 1), BLOCK_WIN7_AND_OLDER),
+
+ # Webroot SecureAnywhere causes crashes, bug 1700281
+ DllBlocklistEntry("WRDll.x64.dll", (1, 1, 0, 227)),
+ DllBlocklistEntry("WRDll.x86.dll", (1, 1, 0, 227)),
+
+ # Webroot SecureAnywhere causes deadlocks, bug 1752466
+ DllBlocklistEntry("WRusr.dll", (9, 0, 32, 49)),
+
+ # InfoWatch Device Monitor causes crashes, bug 1704276
+ DllBlocklistEntry("iwprn.dll", (6, 9, 11, 360)),
+ DllBlocklistEntry("iwprn_x86.dll", (6, 9, 11, 360)),
+
+ # Forcepoint DLLs causing crashes, bug 1767993
+ DllBlocklistEntry("qipcap.dll", (7, 7, 819, 1)),
+ DllBlocklistEntry("qipcap64.dll", (7, 7, 819, 1)),
+
+ # Cylance, bug 1756190 and bug 1799562
+ DllBlocklistEntry("CylanceMemDef64.dll", ALL_VERSIONS),
+]
+
+ALL_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_matchbyname.dll", ALL_VERSIONS),
+ DllBlocklistEntry("testdllblocklist_matchbyversion.dll", (5, 5, 5, 5)),
+ DllBlocklistEntry("testdllblocklist_allowbyversion.dll", (5, 5, 5, 5)),
+ RedirectToNoOpEntryPoint("testdllblocklist_noopentrypoint.dll",
+ (5, 5, 5, 5)),
+]
+
+BROWSER_PROCESS += [
+ # RealPlayer, bug 1418535, bug 1437417
+ # Versions before 18.1.11.0 cause severe performance problems.
+ A11yBlocklistEntry("dtvhooks.dll", (18, 1, 10, 0xffff)),
+ A11yBlocklistEntry("dtvhooks64.dll", (18, 1, 10, 0xffff)),
+
+ # SolidWorks Windows Explorer integration causes crashes, bug 1566109
+ # and bug 1468250
+ DllBlocklistEntry("Database.dll", ALL_VERSIONS),
+
+ # Hancom Office shell extension causes crashes when the file picker is
+ # opened. See bug 1581092.
+ DllBlocklistEntry("hncshellext64.dll", (1, 0, 0 ,3)),
+
+ # Cambridge Silicon Radio, bug 1634538
+ DllBlocklistEntry("BLEtokenCredentialProvider.dll", (2, 1, 63, 0)),
+
+ # FYunZip and PuddingZip, loaded as shell extension, cause crashes
+ # bug 1576728
+ DllBlocklistEntry("oly64.dll", (1, 1, 3, 19920)),
+ DllBlocklistEntry("oly.dll", (1, 1, 3, 19920)),
+ DllBlocklistEntry("pdzipmenu64.dll", (1, 4, 4, 20103)),
+ DllBlocklistEntry("pdzipmenu32.dll", (1, 4, 4, 20103)),
+
+ # McAfee Data Loss Prevention causes crashs with multiple signatures,
+ # bug 1634090, bug 1717676
+ DllBlocklistEntry("fcagff.dll", (11, 6, 300, 2)),
+ DllBlocklistEntry("fcagff64.dll", (11, 6, 300, 2)),
+
+ # AVerMedia's virtual camera module causes crashes (bug 1692908)
+ DllBlocklistEntry("avmvirtualsource.ax", (1, 0, 0, 3)),
+
+ # Avast (bug 1794064)
+ DllBlocklistEntry("aswJsFlt.dll", (18, 0, 1473, 0)),
+
+ # ASUS Web Storage (bug 1714940)
+ DllBlocklistEntry("asuswsshellext64.dll", (1, 1, 0, 27)),
+
+ # ExplorerPatcher (bug 1798707)
+ DllBlocklistEntry("explorerpatcher.amd64.dll", ALL_VERSIONS),
+]
+
+CHILD_PROCESSES += [
+ # Causes crashes in the GPU process with WebRender enabled, bug 1544435
+ DllBlocklistEntry("wbload.dll", ALL_VERSIONS),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1766022
+ DllBlocklistEntry("videocapturer.dll", ALL_VERSIONS),
+ DllBlocklistEntry("videocapturerhk32.dll", ALL_VERSIONS),
+ DllBlocklistEntry("videocapturerhk64.dll", ALL_VERSIONS),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1766029
+ # The crash seems to be in the *_m module which has been unloaded.
+ DllBlocklistEntry("safaweb.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb_m.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb64.dll", (3, 0, 21, 3181)),
+ DllBlocklistEntry("safaweb64_m.dll", (3, 0, 21, 3181)),
+
+ # Causes crashes in the content process with win32k lockdown, bug 1769309
+ DllBlocklistEntry("hmpalert.dll", (3, 8, 8, 889)),
+]
+
+SOCKET_PROCESSES += [
+ # Causes crashes in the socket process, bug 1760668
+ DllBlocklistEntry("ipseng64.dll", (17, 2, 6, 25)),
+ DllBlocklistEntry("ipseng32.dll", (17, 2, 6, 25)),
+
+ # Causes crashes in the socket process, bug 1807038
+ DllBlocklistEntry("kwsui64.dll", (2022, 2, 8, 125)),
+]
+
+SOCKET_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_socketprocessonly.dll", ALL_VERSIONS),
+]
+
+UTILITY_PROCESSES += [
+ # Generated dynamic code that we block
+ DllBlocklistEntry("cyinjct.dll", ALL_VERSIONS),
+]
+
+UTILITY_PROCESSES_TESTS += [
+ # DLLs used by TestDllBlocklist* gTests
+ DllBlocklistEntry("testdllblocklist_utilityprocessonly.dll", ALL_VERSIONS),
+]
+
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h
new file mode 100644
index 0000000000..c808e49ca1
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllBlocklistInfo.h
@@ -0,0 +1,88 @@
+/* -*- 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_WindowsDllBlocklistInfo_h
+#define mozilla_WindowsDllBlocklistInfo_h
+
+#include <stdint.h>
+
+namespace mozilla {
+
+template <typename StrType>
+struct DllBlockInfoT {
+ // The name of the DLL -- in LOWERCASE! It will be compared to
+ // a lowercase version of the DLL name only.
+ StrType mName;
+
+ // If mMaxVersion is ALL_VERSIONS, we'll block all versions of this
+ // dll. Otherwise, we'll block all versions less than or equal to
+ // the given version, as queried by GetFileVersionInfo and
+ // VS_FIXEDFILEINFO's dwFileVersionMS and dwFileVersionLS fields.
+ //
+ // Note that the version is usually 4 components, which is A.B.C.D
+ // encoded as 0x AAAA BBBB CCCC DDDD ULL (spaces added for clarity),
+ // but it's not required to be of that format.
+ uint64_t mMaxVersion;
+
+ // If the USE_TIMESTAMP flag is set, then we use the timestamp from
+ // the IMAGE_FILE_HEADER in lieu of a version number.
+ //
+ // If the UTILITY_PROCESSES_ONLY or SOCKET_PROCESSES_ONLY flags are
+ // set, the dll will only be blocked for these specific child
+ // processes. Note that these are a subset of CHILD_PROCESSES_ONLY.
+ enum Flags {
+ FLAGS_DEFAULT = 0,
+ BLOCK_WIN7_AND_OLDER = 1 << 0,
+ BLOCK_WIN8_AND_OLDER = 1 << 1,
+ USE_TIMESTAMP = 1 << 2,
+ CHILD_PROCESSES_ONLY = 1 << 3,
+ BROWSER_PROCESS_ONLY = 1 << 4,
+ REDIRECT_TO_NOOP_ENTRYPOINT = 1 << 5,
+ UTILITY_PROCESSES_ONLY = 1 << 6,
+ SOCKET_PROCESSES_ONLY = 1 << 7,
+ } mFlags;
+
+ bool IsVersionBlocked(const uint64_t aOther) const {
+ if (mMaxVersion == ALL_VERSIONS) {
+ return true;
+ }
+
+ return aOther <= mMaxVersion;
+ }
+
+ bool IsValidDynamicBlocklistEntry() const;
+
+ static const uint64_t ALL_VERSIONS = (uint64_t)-1LL;
+
+ // DLLs sometimes ship without a version number, particularly early
+ // releases. Blocking "version <= 0" has the effect of blocking unversioned
+ // DLLs (since the call to get version info fails), but not blocking
+ // any versioned instance.
+ static const uint64_t UNVERSIONED = 0ULL;
+};
+
+} // namespace mozilla
+
+// Convert the 4 (decimal) components of a DLL version number into a
+// single unsigned long long, as needed by the blocklist
+#if defined(_MSC_VER) && !defined(__clang__)
+
+// MSVC does not properly handle the constexpr MAKE_VERSION, so we use a macro
+// instead (ugh).
+# define MAKE_VERSION(a, b, c, d) \
+ ((a##ULL << 48) + (b##ULL << 32) + (c##ULL << 16) + d##ULL)
+
+#else
+
+static inline constexpr uint64_t MAKE_VERSION(uint16_t a, uint16_t b,
+ uint16_t c, uint16_t d) {
+ return static_cast<uint64_t>(a) << 48 | static_cast<uint64_t>(b) << 32 |
+ static_cast<uint64_t>(c) << 16 | static_cast<uint64_t>(d);
+}
+
+#endif
+
+#endif // mozilla_WindowsDllBlocklistInfo_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsDllServices.h b/toolkit/xre/dllservices/mozglue/WindowsDllServices.h
new file mode 100644
index 0000000000..17e85991e5
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsDllServices.h
@@ -0,0 +1,212 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef mozilla_glue_WindowsDllServices_h
+#define mozilla_glue_WindowsDllServices_h
+
+#include <utility>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Authenticode.h"
+#include "mozilla/LoaderAPIInterfaces.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/WinHeaderOnlyUtils.h"
+#include "mozilla/WindowsDllBlocklist.h"
+#include "mozilla/mozalloc.h"
+
+#if defined(MOZILLA_INTERNAL_API)
+# include "MainThreadUtils.h"
+# include "nsISupportsImpl.h"
+# include "nsString.h"
+# include "nsThreadUtils.h"
+# include "prthread.h"
+# include "mozilla/SchedulerGroup.h"
+#endif // defined(MOZILLA_INTERNAL_API)
+
+// For PCUNICODE_STRING
+#include <winternl.h>
+
+namespace mozilla {
+namespace glue {
+namespace detail {
+
+class DllServicesBase : public Authenticode {
+ public:
+ /**
+ * WARNING: This method is called from within an unsafe context that holds
+ * multiple locks inside the Windows loader. The only thing that
+ * this function should be used for is dispatching the event to our
+ * event loop so that it may be handled in a safe context.
+ */
+ virtual void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) = 0;
+
+ /**
+ * This function accepts module load events to be processed later for
+ * the untrusted modules telemetry ping.
+ *
+ * WARNING: This method is run from within the Windows loader and should
+ * only perform trivial, loader-friendly operations.
+ */
+ virtual void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) = 0;
+
+ void SetAuthenticodeImpl(Authenticode* aAuthenticode) {
+ mAuthenticode = aAuthenticode;
+ }
+
+ void SetWinLauncherServices(const nt::WinLauncherServices& aWinLauncher) {
+ mWinLauncher = aWinLauncher;
+ }
+
+ template <typename... Args>
+ LauncherVoidResultWithLineInfo InitDllBlocklistOOP(Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(mWinLauncher.mInitDllBlocklistOOP);
+ return mWinLauncher.mInitDllBlocklistOOP(std::forward<Args>(aArgs)...);
+ }
+
+ template <typename... Args>
+ void HandleLauncherError(Args&&... aArgs) {
+ MOZ_RELEASE_ASSERT(mWinLauncher.mHandleLauncherError);
+ mWinLauncher.mHandleLauncherError(std::forward<Args>(aArgs)...);
+ }
+
+ nt::SharedSection* GetSharedSection() { return mWinLauncher.mSharedSection; }
+
+ // In debug builds we override GetBinaryOrgName to add a Gecko-specific
+ // assertion. OTOH, we normally do not want people overriding this function,
+ // so we'll make it final in the release case, thus covering all bases.
+#if defined(DEBUG)
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) override
+#else
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final
+#endif // defined(DEBUG)
+ {
+ if (!mAuthenticode) {
+ return nullptr;
+ }
+
+ return mAuthenticode->GetBinaryOrgName(aFilePath, aFlags);
+ }
+
+ virtual void DisableFull() { DllBlocklist_SetFullDllServices(nullptr); }
+
+ DllServicesBase(const DllServicesBase&) = delete;
+ DllServicesBase(DllServicesBase&&) = delete;
+ DllServicesBase& operator=(const DllServicesBase&) = delete;
+ DllServicesBase& operator=(DllServicesBase&&) = delete;
+
+ protected:
+ DllServicesBase() : mAuthenticode(nullptr) {}
+
+ virtual ~DllServicesBase() = default;
+
+ void EnableFull() { DllBlocklist_SetFullDllServices(this); }
+ void EnableBasic() { DllBlocklist_SetBasicDllServices(this); }
+
+ private:
+ Authenticode* mAuthenticode;
+ nt::WinLauncherServices mWinLauncher;
+};
+
+} // namespace detail
+
+#if defined(MOZILLA_INTERNAL_API)
+
+struct EnhancedModuleLoadInfo final {
+ explicit EnhancedModuleLoadInfo(ModuleLoadInfo&& aModLoadInfo)
+ : mNtLoadInfo(std::move(aModLoadInfo)) {
+ // Only populate mThreadName when we're on the same thread as the event
+ if (mNtLoadInfo.mThreadId == ::GetCurrentThreadId()) {
+ mThreadName = PR_GetThreadName(PR_GetCurrentThread());
+ }
+ MOZ_ASSERT(!mNtLoadInfo.mSectionName.IsEmpty());
+ }
+
+ EnhancedModuleLoadInfo(EnhancedModuleLoadInfo&&) = default;
+ EnhancedModuleLoadInfo& operator=(EnhancedModuleLoadInfo&&) = default;
+
+ EnhancedModuleLoadInfo(const EnhancedModuleLoadInfo&) = delete;
+ EnhancedModuleLoadInfo& operator=(const EnhancedModuleLoadInfo&) = delete;
+
+ nsDependentString GetSectionName() const {
+ return mNtLoadInfo.mSectionName.AsString();
+ }
+
+ using BacktraceType = decltype(ModuleLoadInfo::mBacktrace);
+
+ ModuleLoadInfo mNtLoadInfo;
+ nsCString mThreadName;
+};
+
+class DllServices : public detail::DllServicesBase {
+ public:
+ void DispatchDllLoadNotification(ModuleLoadInfo&& aModLoadInfo) final {
+ nsCOMPtr<nsIRunnable> runnable(
+ NewRunnableMethod<StoreCopyPassByRRef<EnhancedModuleLoadInfo>>(
+ "DllServices::NotifyDllLoad", this, &DllServices::NotifyDllLoad,
+ std::move(aModLoadInfo)));
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ }
+
+ void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) final {
+ nsCOMPtr<nsIRunnable> runnable(
+ NewRunnableMethod<StoreCopyPassByRRef<ModuleLoadInfoVec>>(
+ "DllServices::NotifyModuleLoadBacklog", this,
+ &DllServices::NotifyModuleLoadBacklog, std::move(aEvents)));
+
+ SchedulerGroup::Dispatch(TaskCategory::Other, runnable.forget());
+ }
+
+# if defined(DEBUG)
+ UniquePtr<wchar_t[]> GetBinaryOrgName(
+ const wchar_t* aFilePath,
+ AuthenticodeFlags aFlags = AuthenticodeFlags::Default) final {
+ // This function may perform disk I/O, so we should never call it on the
+ // main thread.
+ MOZ_ASSERT(!NS_IsMainThread());
+ return detail::DllServicesBase::GetBinaryOrgName(aFilePath, aFlags);
+ }
+# endif // defined(DEBUG)
+
+ NS_INLINE_DECL_THREADSAFE_VIRTUAL_REFCOUNTING(DllServices)
+
+ protected:
+ DllServices() = default;
+ ~DllServices() = default;
+
+ virtual void NotifyDllLoad(EnhancedModuleLoadInfo&& aModLoadInfo) = 0;
+ virtual void NotifyModuleLoadBacklog(ModuleLoadInfoVec&& aEvents) = 0;
+};
+
+#else
+
+class BasicDllServices final : public detail::DllServicesBase {
+ public:
+ BasicDllServices() { EnableBasic(); }
+
+ ~BasicDllServices() = default;
+
+ // Not useful in this class, so provide a default implementation
+ virtual void DispatchDllLoadNotification(
+ ModuleLoadInfo&& aModLoadInfo) override {}
+
+ virtual void DispatchModuleLoadBacklogNotification(
+ ModuleLoadInfoVec&& aEvents) override {}
+};
+
+#endif // defined(MOZILLA_INTERNAL_API)
+
+} // namespace glue
+} // namespace mozilla
+
+#endif // mozilla_glue_WindowsDllServices_h
diff --git a/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp
new file mode 100644
index 0000000000..bda309d499
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.cpp
@@ -0,0 +1,91 @@
+/* -*- 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 "WindowsFallbackLoaderAPI.h"
+
+namespace mozilla {
+
+ModuleLoadInfo FallbackLoaderAPI::ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) {
+ ModuleLoadInfo loadInfo(aRequestedDllName);
+
+ MOZ_ASSERT(mLoaderObserver);
+ if (mLoaderObserver) {
+ mLoaderObserver->OnBeginDllLoad(aContext, aRequestedDllName);
+ }
+
+ return loadInfo;
+}
+
+bool FallbackLoaderAPI::SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) {
+ MOZ_ASSERT(mLoaderObserver);
+ if (!mLoaderObserver) {
+ return false;
+ }
+
+ return mLoaderObserver->SubstituteForLSP(aLSPLeafName, aOutHandle);
+}
+
+void FallbackLoaderAPI::NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) {
+ aModuleLoadInfo.SetEndLoadTimeStamp();
+
+ if (NT_SUCCESS(aLoadNtStatus)) {
+ aModuleLoadInfo.CaptureBacktrace();
+ }
+
+ MOZ_ASSERT(mLoaderObserver);
+ if (mLoaderObserver) {
+ mLoaderObserver->OnEndDllLoad(aContext, aLoadNtStatus,
+ std::move(aModuleLoadInfo));
+ }
+}
+
+nt::AllocatedUnicodeString FallbackLoaderAPI::GetSectionName(
+ void* aSectionAddr) {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::NtQueryVirtualMemory)>
+ pNtQueryVirtualMemory(L"ntdll.dll", "NtQueryVirtualMemory");
+ MOZ_ASSERT(pNtQueryVirtualMemory);
+
+ if (!pNtQueryVirtualMemory) {
+ return nt::AllocatedUnicodeString();
+ }
+
+ nt::MemorySectionNameBuf buf;
+ NTSTATUS ntStatus =
+ pNtQueryVirtualMemory(::GetCurrentProcess(), aSectionAddr,
+ MemorySectionName, &buf, sizeof(buf), nullptr);
+ if (!NT_SUCCESS(ntStatus)) {
+ return nt::AllocatedUnicodeString();
+ }
+
+ return nt::AllocatedUnicodeString(&buf.mSectionFileName);
+}
+
+nt::LoaderAPI::InitDllBlocklistOOPFnPtr
+FallbackLoaderAPI::GetDllBlocklistInitFn() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+nt::LoaderAPI::HandleLauncherErrorFnPtr
+FallbackLoaderAPI::GetHandleLauncherErrorFn() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+nt::SharedSection* FallbackLoaderAPI::GetSharedSection() {
+ MOZ_ASSERT_UNREACHABLE("This should not be called so soon!");
+ return nullptr;
+}
+
+void FallbackLoaderAPI::SetObserver(nt::LoaderObserver* aLoaderObserver) {
+ mLoaderObserver = aLoaderObserver;
+}
+
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h
new file mode 100644
index 0000000000..8d6fcb2f0c
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/WindowsFallbackLoaderAPI.h
@@ -0,0 +1,39 @@
+/* -*- 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_WindowsFallbackLoaderAPI_h
+#define mozilla_WindowsFallbackLoaderAPI_h
+
+#include "mozilla/Attributes.h"
+#include "NtLoaderAPI.h"
+
+namespace mozilla {
+
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FallbackLoaderAPI final
+ : public nt::LoaderAPI {
+ public:
+ constexpr FallbackLoaderAPI() : mLoaderObserver(nullptr) {}
+
+ ModuleLoadInfo ConstructAndNotifyBeginDllLoad(
+ void** aContext, PCUNICODE_STRING aRequestedDllName) final;
+ bool SubstituteForLSP(PCUNICODE_STRING aLSPLeafName,
+ PHANDLE aOutHandle) final;
+ void NotifyEndDllLoad(void* aContext, NTSTATUS aLoadNtStatus,
+ ModuleLoadInfo&& aModuleLoadInfo) final;
+ nt::AllocatedUnicodeString GetSectionName(void* aSectionAddr) final;
+ nt::LoaderAPI::InitDllBlocklistOOPFnPtr GetDllBlocklistInitFn() final;
+ nt::LoaderAPI::HandleLauncherErrorFnPtr GetHandleLauncherErrorFn() final;
+ nt::SharedSection* GetSharedSection() final;
+
+ void SetObserver(nt::LoaderObserver* aLoaderObserver);
+
+ private:
+ nt::LoaderObserver* mLoaderObserver;
+};
+
+} // namespace mozilla
+
+#endif // mozilla_WindowsFallbackLoaderAPI_h
diff --git a/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py b/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py
new file mode 100644
index 0000000000..056db4d995
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/gen_dll_blocklist_defs.py
@@ -0,0 +1,750 @@
+# -*- 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/.
+
+import os
+from copy import deepcopy
+from struct import unpack
+from uuid import UUID
+
+from six import iteritems
+
+H_HEADER = """/* -*- 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 file was auto-generated from {0} by gen_dll_blocklist_data.py. */
+
+#ifndef mozilla_{1}_h
+#define mozilla_{1}_h
+
+"""
+
+H_FOOTER = """#endif // mozilla_{1}_h
+
+"""
+
+H_DEFS_BEGIN_DEFAULT = """#include "mozilla/WindowsDllBlocklistCommon.h"
+
+DLL_BLOCKLIST_DEFINITIONS_BEGIN
+
+"""
+
+H_DEFS_END_DEFAULT = """
+DLL_BLOCKLIST_DEFINITIONS_END
+
+"""
+
+H_BEGIN_LSP = """#include <guiddef.h>
+
+static const GUID gLayerGuids[] = {
+
+"""
+
+H_END_LSP = """
+};
+
+"""
+
+H_BEGIN_A11Y = """#include "mozilla/WindowsDllBlocklistCommon.h"
+
+DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gBlockedInprocDlls)
+
+"""
+
+# These flag names should match the ones defined in WindowsDllBlocklistCommon.h
+FLAGS_DEFAULT = "FLAGS_DEFAULT"
+BLOCK_WIN8_AND_OLDER = "BLOCK_WIN8_AND_OLDER"
+BLOCK_WIN7_AND_OLDER = "BLOCK_WIN7_AND_OLDER"
+USE_TIMESTAMP = "USE_TIMESTAMP"
+CHILD_PROCESSES_ONLY = "CHILD_PROCESSES_ONLY"
+BROWSER_PROCESS_ONLY = "BROWSER_PROCESS_ONLY"
+SUBSTITUTE_LSP_PASSTHROUGH = "SUBSTITUTE_LSP_PASSTHROUGH"
+REDIRECT_TO_NOOP_ENTRYPOINT = "REDIRECT_TO_NOOP_ENTRYPOINT"
+UTILITY_PROCESSES_ONLY = "UTILITY_PROCESSES_ONLY"
+SOCKET_PROCESSES_ONLY = "SOCKET_PROCESSES_ONLY"
+
+# Only these flags are available in the input script
+INPUT_ONLY_FLAGS = {
+ BLOCK_WIN8_AND_OLDER,
+ BLOCK_WIN7_AND_OLDER,
+}
+
+
+def FILTER_ALLOW_ALL(entry):
+ # A11y entries are special, so we always exclude those by default
+ # (so it's not really allowing 'all', but it is simpler to reason about by
+ # pretending that it is allowing all.)
+ return not isinstance(entry, A11yBlocklistEntry)
+
+
+def FILTER_DENY_ALL(entry):
+ return False
+
+
+def FILTER_ALLOW_ONLY_A11Y(entry):
+ return isinstance(entry, A11yBlocklistEntry)
+
+
+def FILTER_ALLOW_ONLY_LSP(entry):
+ return isinstance(entry, LspBlocklistEntry)
+
+
+def FILTER_TESTS_ONLY(entry):
+ return not isinstance(entry, A11yBlocklistEntry) and entry.is_test()
+
+
+def derive_test_key(key):
+ return key + "_TESTS"
+
+
+ALL_DEFINITION_LISTS = (
+ "ALL_PROCESSES",
+ "BROWSER_PROCESS",
+ "CHILD_PROCESSES",
+ "UTILITY_PROCESSES",
+ "SOCKET_PROCESSES",
+)
+
+
+class BlocklistDescriptor(object):
+ """This class encapsulates every file that is output from this script.
+ Each instance has a name, an "input specification", and optional "flag
+ specification" and "output specification" entries.
+ """
+
+ DEFAULT_OUTSPEC = {
+ "mode": "",
+ "filter": FILTER_ALLOW_ALL,
+ "begin": H_DEFS_BEGIN_DEFAULT,
+ "end": H_DEFS_END_DEFAULT,
+ }
+
+ FILE_NAME_TPL = "WindowsDllBlocklist{0}Defs"
+
+ OutputDir = None
+ ExistingFd = None
+ ExistingFdLeafName = None
+
+ def __init__(self, name, inspec, **kwargs):
+ """Positional arguments:
+
+ name -- String containing the name of the output list.
+
+ inspec -- One or more members of ALL_DEFINITION_LISTS. The input used
+ for this blocklist file is the union of all lists specified by this
+ variable.
+
+ Keyword arguments:
+
+ outspec -- an optional list of dicts that specify how the lists output
+ will be written out to a file. Each dict may contain the following keys:
+
+ 'mode' -- a string that specifies a mode that is used when writing
+ out list entries to this particular output. This is passed in as the
+ mode argument to DllBlocklistEntry's write method.
+
+ 'filter' -- a function that, given a blocklist entry, decides
+ whether or not that entry shall be included in this output file.
+
+ 'begin' -- a string that is written to the output file after writing
+ the file's header, but prior to writing out any blocklist entries.
+
+ 'end' -- a string that is written to the output file after writing
+ out any blocklist entries but before the file's footer.
+
+ Any unspecified keys will be assigned default values.
+
+ flagspec -- an optional dict whose keys consist of one or more of the
+ list names from inspec. Each corresponding value is a set of flags that
+ should be applied to each entry from that list. For example, the
+ flagspec:
+
+ {'CHILD_PROCESSES': {CHILD_PROCESSES_ONLY}}
+
+ causes any blocklist entries from the CHILD_PROCESSES list to be output
+ with the CHILD_PROCESSES_ONLY flag set.
+
+ """
+
+ self._name = name
+
+ # inspec's elements must all come from ALL_DEFINITION_LISTS
+ assert not (set(inspec).difference(set(ALL_DEFINITION_LISTS)))
+
+ # Internally to the descriptor, we store input specifications as a dict
+ # that maps each input blocklist name to the set of flags to be applied
+ # to each entry in that blocklist.
+ self._inspec = {blocklist: set() for blocklist in inspec}
+
+ self._outspecs = kwargs.get("outspec", BlocklistDescriptor.DEFAULT_OUTSPEC)
+ if isinstance(self._outspecs, dict):
+ # _outspecs should always be stored as a list of dicts
+ self._outspecs = [self._outspecs]
+
+ flagspecs = kwargs.get("flagspec", dict())
+ # flagspec's keys must all come from ALL_DEFINITION_LISTS
+ assert not (set(flagspecs.keys()).difference(set(self._inspec.keys())))
+
+ # Merge the flags from flagspec into _inspec's sets
+ for blocklist, flagspec in iteritems(flagspecs):
+ spec = self._inspec[blocklist]
+ if not isinstance(spec, set):
+ raise TypeError("Flag spec for list %s must be a set!" % blocklist)
+ spec.update(flagspec)
+
+ @staticmethod
+ def set_output_fd(fd):
+ """The build system has already provided an open file descriptor for
+ one of our outputs. We save that here so that we may use that fd once
+ we're ready to process that fd's file. We also obtain the output dir for
+ use as the base directory for the other output files that we open.
+ """
+ BlocklistDescriptor.ExistingFd = fd
+ (
+ BlocklistDescriptor.OutputDir,
+ BlocklistDescriptor.ExistingFdLeafName,
+ ) = os.path.split(fd.name)
+
+ @staticmethod
+ def ensure_no_dupes(defs):
+ """Ensure that defs does not contain any dupes. We raise a ValueError
+ because this is a bug in the input and requires developer intervention.
+ """
+ seen = set()
+ for e in defs:
+ name = e.get_name()
+ if name not in seen:
+ seen.add(name)
+ else:
+ raise ValueError("Duplicate entry found: %s" % name)
+
+ @staticmethod
+ def get_test_entries(exec_env, blocklist):
+ """Obtains all test entries for the corresponding blocklist, and also
+ ensures that each entry has its test flag set.
+
+ Positional arguments:
+
+ exec_env -- dict containing the globals that were passed to exec to
+ when the input script was run.
+
+ blocklist -- The name of the blocklist to obtain tests from. This
+ should be one of the members of ALL_DEFINITION_LISTS
+ """
+ test_key = derive_test_key(blocklist)
+
+ def set_is_test(elem):
+ elem.set_is_test()
+ return elem
+
+ return map(set_is_test, exec_env[test_key])
+
+ def gen_list(self, exec_env, filter_func):
+ """Generates a sorted list of blocklist entries from the input script,
+ filtered via filter_func.
+
+ Positional arguments:
+
+ exec_env -- dict containing the globals that were passed to exec to
+ when the input script was run. This function expects exec_env to
+ contain lists of blocklist entries, keyed using one of the members of
+ ALL_DEFINITION_LISTS.
+
+ filter_func -- a filter function that evaluates each blocklist entry
+ to determine whether or not it should be included in the results.
+ """
+
+ # This list contains all the entries across all blocklists that we
+ # potentially want to process
+ unified_list = []
+
+ # For each blocklist specified in the _inspec, we query the globals
+ # for their entries, add any flags, and then add them to the
+ # unified_list.
+ for blocklist, listflags in iteritems(self._inspec):
+
+ def add_list_flags(elem):
+ # We deep copy so that flags set for an entry in one blocklist
+ # do not interfere with flags set for an entry in a different
+ # list.
+ result = deepcopy(elem)
+ result.add_flags(listflags)
+ return result
+
+ # We add list flags *before* filtering because the filters might
+ # want to access flags as part of their operation.
+ unified_list.extend(map(add_list_flags, exec_env[blocklist]))
+
+ # We also add any test entries for the lists specified by _inspec
+ unified_list.extend(
+ map(add_list_flags, self.get_test_entries(exec_env, blocklist))
+ )
+
+ # There should be no dupes in the input. If there are, raise an error.
+ self.ensure_no_dupes(unified_list)
+
+ # Now we filter out any unwanted list entries
+ filtered_list = filter(filter_func, unified_list)
+
+ # Sort the list on entry name so that the blocklist code may use
+ # binary search if it so chooses.
+ return sorted(filtered_list, key=lambda e: e.get_name())
+
+ @staticmethod
+ def get_fd(outspec_leaf_name):
+ """If BlocklistDescriptor.ExistingFd corresponds to outspec_leaf_name,
+ then we return that. Otherwise, we construct a new absolute path to
+ outspec_leaf_name and open a new file descriptor for writing.
+ """
+ if (
+ not BlocklistDescriptor.ExistingFd
+ or BlocklistDescriptor.ExistingFdLeafName != outspec_leaf_name
+ ):
+ new_name = os.path.join(BlocklistDescriptor.OutputDir, outspec_leaf_name)
+ return open(new_name, "w")
+
+ fd = BlocklistDescriptor.ExistingFd
+ BlocklistDescriptor.ExistingFd = None
+ return fd
+
+ def write(self, src_file, exec_env):
+ """Write out all output files that are specified by this descriptor.
+
+ Positional arguments:
+
+ src_file -- name of the input file from which the lists were generated.
+
+ exec_env -- dictionary containing the lists that were parsed from the
+ input file when it was executed.
+ """
+
+ for outspec in self._outspecs:
+ # Use DEFAULT_OUTSPEC to supply defaults for any unused outspec keys
+ effective_outspec = BlocklistDescriptor.DEFAULT_OUTSPEC.copy()
+ effective_outspec.update(outspec)
+
+ entries = self.gen_list(exec_env, effective_outspec["filter"])
+ if not entries:
+ continue
+
+ mode = effective_outspec["mode"]
+
+ # Since each output descriptor may generate output across multiple
+ # modes, each list is uniquified via the concatenation of our name
+ # and the mode.
+ listname = self._name + mode
+ leafname_no_ext = BlocklistDescriptor.FILE_NAME_TPL.format(listname)
+ leafname = leafname_no_ext + ".h"
+
+ with self.get_fd(leafname) as output:
+ print(H_HEADER.format(src_file, leafname_no_ext), file=output, end="")
+ print(effective_outspec["begin"], file=output, end="")
+
+ for e in entries:
+ e.write(output, mode)
+
+ print(effective_outspec["end"], file=output, end="")
+ print(H_FOOTER.format(src_file, leafname_no_ext), file=output, end="")
+
+
+A11Y_OUTPUT_SPEC = {
+ "filter": FILTER_ALLOW_ONLY_A11Y,
+ "begin": H_BEGIN_A11Y,
+}
+
+LSP_MODE_GUID = "Guid"
+
+LSP_OUTPUT_SPEC = [
+ {
+ "mode": LSP_MODE_GUID,
+ "filter": FILTER_ALLOW_ONLY_LSP,
+ "begin": H_BEGIN_LSP,
+ "end": H_END_LSP,
+ },
+]
+
+GENERATED_BLOCKLIST_FILES = [
+ BlocklistDescriptor("A11y", ["BROWSER_PROCESS"], outspec=A11Y_OUTPUT_SPEC),
+ BlocklistDescriptor(
+ "Launcher",
+ ALL_DEFINITION_LISTS,
+ flagspec={
+ "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
+ "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
+ "UTILITY_PROCESSES": {UTILITY_PROCESSES_ONLY},
+ "SOCKET_PROCESSES": {SOCKET_PROCESSES_ONLY},
+ },
+ ),
+ BlocklistDescriptor(
+ "Legacy",
+ ALL_DEFINITION_LISTS,
+ flagspec={
+ "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
+ "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
+ "UTILITY_PROCESSES": {UTILITY_PROCESSES_ONLY},
+ "SOCKET_PROCESSES": {SOCKET_PROCESSES_ONLY},
+ },
+ ),
+ # Roughed-in for the moment; we'll enable this in bug 1238735
+ # BlocklistDescriptor('LSP', ALL_DEFINITION_LISTS, outspec=LSP_OUTPUT_SPEC),
+ BlocklistDescriptor(
+ "Test", ALL_DEFINITION_LISTS, outspec={"filter": FILTER_TESTS_ONLY}
+ ),
+]
+
+
+class PETimeStamp(object):
+ def __init__(self, ts):
+ max_timestamp = (2 ** 32) - 1
+ if ts < 0 or ts > max_timestamp:
+ raise ValueError("Invalid timestamp value")
+ self._value = ts
+
+ def __str__(self):
+ return "0x%08XU" % self._value
+
+
+class Version(object):
+ """Encapsulates a DLL version."""
+
+ ALL_VERSIONS = 0xFFFFFFFFFFFFFFFF
+ UNVERSIONED = 0
+
+ def __init__(self, *args):
+ """There are multiple ways to construct a Version:
+
+ As a tuple containing four elements (recommended);
+ As four integral arguments;
+ As a PETimeStamp;
+ As a long integer.
+
+ The tuple and list formats require the value of each element to be
+ between 0 and 0xFFFF, inclusive.
+ """
+
+ self._ver = Version.UNVERSIONED
+
+ if len(args) == 1:
+ if isinstance(args[0], tuple):
+ self.validate_iterable(args[0])
+
+ self._ver = "MAKE_VERSION%r" % (args[0],)
+ elif isinstance(args[0], PETimeStamp):
+ self._ver = args[0]
+ else:
+ self._ver = int(args[0])
+ elif len(args) == 4:
+ self.validate_iterable(args)
+
+ self._ver = "MAKE_VERSION%r" % (tuple(args),)
+ else:
+ raise ValueError("Bad arguments to Version constructor")
+
+ def validate_iterable(self, arg):
+ if len(arg) != 4:
+ raise ValueError("Versions must be a 4-tuple")
+
+ for component in arg:
+ if not isinstance(component, int) or component < 0 or component > 0xFFFF:
+ raise ValueError(
+ "Each version component must be a 16-bit " "unsigned integer"
+ )
+
+ def build_long(self, args):
+ self.validate_iterable(args)
+ return (
+ (int(args[0]) << 48)
+ | (int(args[1]) << 32)
+ | (int(args[2]) << 16)
+ | int(args[3])
+ )
+
+ def is_timestamp(self):
+ return isinstance(self._ver, PETimeStamp)
+
+ def __str__(self):
+ if isinstance(self._ver, int):
+ if self._ver == Version.ALL_VERSIONS:
+ return "DllBlockInfo::ALL_VERSIONS"
+
+ if self._ver == Version.UNVERSIONED:
+ return "DllBlockInfo::UNVERSIONED"
+
+ return "0x%016XULL" % self._ver
+
+ return str(self._ver)
+
+
+class DllBlocklistEntry(object):
+ TEST_CONDITION = "defined(ENABLE_TESTS)"
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """Positional arguments:
+
+ name -- The leaf name of the DLL.
+
+ ver -- The maximum version to be blocked. NB: The comparison used by the
+ blocklist is <=, so you should specify the last bad version, as opposed
+ to the first good version.
+
+ flags -- iterable containing the flags that should be applicable to
+ this blocklist entry.
+
+ Keyword arguments:
+
+ condition -- a string containing a C++ preprocessor expression. This
+ condition is written as a condition for an #if/#endif block that is
+ generated around the entry during output.
+ """
+
+ self.check_ascii(name)
+ self._name = name.lower()
+ self._ver = Version(ver)
+
+ self._flags = set()
+ self.add_flags(flags)
+ if self._ver.is_timestamp():
+ self._flags.add(USE_TIMESTAMP)
+
+ self._cond = kwargs.get("condition", set())
+ if isinstance(self._cond, str):
+ self._cond = {self._cond}
+
+ @staticmethod
+ def check_ascii(name):
+ try:
+ # Supported in Python 3.7
+ if not name.isascii():
+ raise ValueError('DLL name "%s" must be ASCII!' % name)
+ return
+ except AttributeError:
+ pass
+
+ try:
+ name.encode("ascii")
+ except UnicodeEncodeError:
+ raise ValueError('DLL name "%s" must be ASCII!' % name)
+
+ def get_name(self):
+ return self._name
+
+ def set_condition(self, cond):
+ self._cond.add(cond)
+
+ def get_condition(self):
+ if len(self._cond) == 1:
+ fmt = "{0}"
+ else:
+ fmt = "({0})"
+
+ return " && ".join([fmt.format(c) for c in self._cond])
+
+ def set_is_test(self):
+ self.set_condition(DllBlocklistEntry.TEST_CONDITION)
+
+ def is_test(self):
+ return self._cond and DllBlocklistEntry.TEST_CONDITION in self._cond
+
+ def add_flags(self, new_flags):
+ if isinstance(new_flags, str):
+ self._flags.add(new_flags)
+ else:
+ self._flags.update(new_flags)
+
+ @staticmethod
+ def get_flag_string(flag):
+ return "DllBlockInfo::" + flag
+
+ def get_flags_list(self):
+ return self._flags
+
+ def write(self, output, mode):
+ if self._cond:
+ print("#if %s" % self.get_condition(), file=output)
+
+ flags_str = ""
+
+ flags = self.get_flags_list()
+ if flags:
+ flags_str = ", " + " | ".join(map(self.get_flag_string, flags))
+
+ entry_str = ' DLL_BLOCKLIST_ENTRY("%s", %s%s)' % (
+ self._name,
+ str(self._ver),
+ flags_str,
+ )
+ print(entry_str, file=output)
+
+ if self._cond:
+ print("#endif // %s" % self.get_condition(), file=output)
+
+
+class A11yBlocklistEntry(DllBlocklistEntry):
+ """Represents a blocklist entry for injected a11y DLLs. This class does
+ not need to do anything special compared to a DllBlocklistEntry; we just
+ use this type to distinguish a11y entries from "regular" blocklist entries.
+ """
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """These arguments are identical to DllBlocklistEntry.__init__"""
+
+ super(A11yBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
+
+
+class RedirectToNoOpEntryPoint(DllBlocklistEntry):
+ """Represents a blocklist entry to hook the entrypoint into a function
+ just returning TRUE to keep a module alive and harmless.
+ This entry is intended to block a DLL which is injected by IAT patching
+ which is planted by a kernel callback routine for LoadImage because
+ blocking such a DLL makes a process fail to launch.
+ """
+
+ def __init__(self, name, ver, flags=(), **kwargs):
+ """These arguments are identical to DllBlocklistEntry.__init__"""
+
+ super(RedirectToNoOpEntryPoint, self).__init__(name, ver, flags, **kwargs)
+
+ def get_flags_list(self):
+ flags = super(RedirectToNoOpEntryPoint, self).get_flags_list()
+ # RedirectToNoOpEntryPoint items always include the following flag
+ flags.add(REDIRECT_TO_NOOP_ENTRYPOINT)
+ return flags
+
+
+class LspBlocklistEntry(DllBlocklistEntry):
+ """Represents a blocklist entry for a WinSock Layered Service Provider (LSP)."""
+
+ GUID_UNPACK_FMT_LE = "<IHHBBBBBBBB"
+ Guids = dict()
+
+ def __init__(self, name, ver, guids, flags=(), **kwargs):
+ """Positional arguments:
+
+ name -- The leaf name of the DLL.
+
+ ver -- The maximum version to be blocked. NB: The comparison used by the
+ blocklist is <=, so you should specify the last bad version, as opposed
+ to the first good version.
+
+ guids -- Either a string or list of strings containing the GUIDs that
+ uniquely identify the LSP. These GUIDs are generated by the developer of
+ the LSP and are registered with WinSock alongside the LSP. We record
+ this GUID as part of the "Winsock_LSP" annotation in our crash reports.
+
+ flags -- iterable containing the flags that should be applicable to
+ this blocklist entry.
+
+ Keyword arguments:
+
+ condition -- a string containing a C++ preprocessor expression. This
+ condition is written as a condition for an #if/#endif block that is
+ generated around the entry during output.
+ """
+
+ super(LspBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
+ if not guids:
+ raise ValueError("Missing GUID(s)!")
+
+ if isinstance(guids, str):
+ self.insert(UUID(guids), name)
+ else:
+ for guid in guids:
+ self.insert(UUID(guid), name)
+
+ def insert(self, guid, name):
+ # Some explanation here: Multiple DLLs (and thus multiple instances of
+ # LspBlocklistEntry) may share the same GUIDs. To ensure that we do not
+ # have any duplicates, we store each GUID in a class variable, Guids.
+ # We include the original DLL name from the blocklist definitions so
+ # that we may output a comment above each GUID indicating which entries
+ # correspond to which GUID.
+ LspBlocklistEntry.Guids.setdefault(guid, []).append(name)
+
+ def get_flags_list(self):
+ flags = super(LspBlocklistEntry, self).get_flags_list()
+ # LSP entries always include the following flag
+ flags.add(SUBSTITUTE_LSP_PASSTHROUGH)
+ return flags
+
+ @staticmethod
+ def as_c_struct(guid, names):
+ parts = unpack(LspBlocklistEntry.GUID_UNPACK_FMT_LE, guid.bytes_le)
+ str_guid = (
+ " // %r\n // {%s}\n { 0x%08x, 0x%04x, 0x%04x, "
+ "{ 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x }"
+ " }"
+ % (
+ names,
+ str(guid),
+ parts[0],
+ parts[1],
+ parts[2],
+ parts[3],
+ parts[4],
+ parts[5],
+ parts[6],
+ parts[7],
+ parts[8],
+ parts[9],
+ parts[10],
+ )
+ )
+ return str_guid
+
+ def write(self, output, mode):
+ if mode != LSP_MODE_GUID:
+ super(LspBlocklistEntry, self).write(output, mode)
+ return
+
+ # We dump the entire contents of Guids on the first call, and then
+ # clear it. Remaining invocations of this method are no-ops.
+ if LspBlocklistEntry.Guids:
+ result = ",\n".join(
+ [
+ self.as_c_struct(guid, names)
+ for guid, names in iteritems(LspBlocklistEntry.Guids)
+ ]
+ )
+ print(result, file=output)
+ LspBlocklistEntry.Guids.clear()
+
+
+def exec_script_file(script_name, globals):
+ with open(script_name) as script:
+ exec(compile(script.read(), script_name, "exec"), globals)
+
+
+def gen_blocklists(first_fd, defs_filename):
+
+ BlocklistDescriptor.set_output_fd(first_fd)
+
+ # exec_env defines the global variables that will be present in the
+ # execution environment when defs_filename is run by exec.
+ exec_env = {
+ # Add the blocklist entry types
+ "A11yBlocklistEntry": A11yBlocklistEntry,
+ "DllBlocklistEntry": DllBlocklistEntry,
+ "LspBlocklistEntry": LspBlocklistEntry,
+ "RedirectToNoOpEntryPoint": RedirectToNoOpEntryPoint,
+ # Add the special version types
+ "ALL_VERSIONS": Version.ALL_VERSIONS,
+ "UNVERSIONED": Version.UNVERSIONED,
+ "PETimeStamp": PETimeStamp,
+ }
+
+ # Import definition lists into exec_env
+ for defname in ALL_DEFINITION_LISTS:
+ exec_env[defname] = []
+ # For each defname, add a special list for test-only entries
+ exec_env[derive_test_key(defname)] = []
+
+ # Import flags into exec_env
+ exec_env.update({flag: flag for flag in INPUT_ONLY_FLAGS})
+
+ # Now execute the input script with exec_env providing the globals
+ exec_script_file(defs_filename, exec_env)
+
+ # Tell the output descriptors to write out the output files.
+ for desc in GENERATED_BLOCKLIST_FILES:
+ desc.write(defs_filename, exec_env)
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp
new file mode 100644
index 0000000000..81d8e6d09b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.cpp
@@ -0,0 +1,89 @@
+/* -*- 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 "Arm64.h"
+
+#include "mozilla/ResultVariant.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+struct PCRelativeLoadTest {
+ // Bitmask to be ANDed with the instruction to isolate the bits that this
+ // instance is interested in
+ uint32_t mTestMask;
+ // The desired bits that we want to see after masking
+ uint32_t mMatchBits;
+ // If we match, mDecodeFn provide the code to decode the instruction.
+ LoadOrBranch (*mDecodeFn)(const uintptr_t aPC, const uint32_t aInst);
+};
+
+static LoadOrBranch ADRPDecode(const uintptr_t aPC, const uint32_t aInst) {
+ // Keep in mind that on Windows aarch64, uint32_t is little-endian
+ const uint32_t kMaskDataProcImmPcRelativeImmLo = 0x60000000;
+ const uint32_t kMaskDataProcImmPcRelativeImmHi = 0x00FFFFE0;
+
+ uintptr_t base = aPC;
+ intptr_t offset = SignExtend<intptr_t>(
+ ((aInst & kMaskDataProcImmPcRelativeImmHi) >> 3) |
+ ((aInst & kMaskDataProcImmPcRelativeImmLo) >> 29),
+ 21);
+
+ base &= ~0xFFFULL;
+ offset <<= 12;
+
+ uint8_t reg = aInst & 0x1F;
+
+ return LoadOrBranch(base + offset, reg);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst) {
+ int32_t offset = SignExtend<int32_t>(aInst & 0x03FFFFFFU, 26);
+ return LoadOrBranch(aPC + offset);
+}
+
+// Order is important here; more specific encoding tests must be placed before
+// less specific encoding tests.
+static const PCRelativeLoadTest gPCRelTests[] = {
+ {0x9FC00000, 0x10000000, nullptr}, // ADR
+ {0x9FC00000, 0x90000000, &ADRPDecode}, // ADRP
+ {0xFF000000, 0x58000000, nullptr}, // LDR (literal) 64-bit GPR
+ {0x3B000000, 0x18000000, nullptr}, // LDR (literal) (remaining forms)
+ {0x7C000000, 0x14000000, nullptr}, // B (unconditional immediate)
+ {0xFE000000, 0x54000000, nullptr}, // B.Cond
+ {0x7E000000, 0x34000000, nullptr}, // Compare and branch (imm)
+ {0x7E000000, 0x36000000, nullptr}, // Test and branch (imm)
+ {0xFE000000, 0xD6000000, nullptr} // Unconditional branch (reg)
+};
+
+/**
+ * In this function we interate through each entry in |gPCRelTests|, AND
+ * |aInst| with |test.mTestMask| to isolate the bits that we're interested in,
+ * then compare that result against |test.mMatchBits|. If we have a match,
+ * then that particular entry is applicable to |aInst|. If |test.mDecodeFn| is
+ * present, then we call it to decode the instruction. If it is not present,
+ * then we assume that this particular instruction is unsupported.
+ */
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst) {
+ for (auto&& test : gPCRelTests) {
+ if ((aInst & test.mTestMask) == test.mMatchBits) {
+ if (!test.mDecodeFn) {
+ return Err(PCRelCheckError::NoDecoderAvailable);
+ }
+
+ return test.mDecodeFn(aPC, aInst);
+ }
+ }
+
+ return Err(PCRelCheckError::InstructionNotPCRel);
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h
new file mode 100644
index 0000000000..4070fbf99f
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Arm64.h
@@ -0,0 +1,221 @@
+/* -*- 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_interceptor_Arm64_h
+#define mozilla_interceptor_Arm64_h
+
+#include <type_traits>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Result.h"
+#include "mozilla/Saturate.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+namespace arm64 {
+
+// clang-format off
+enum class IntegerConditionCode : uint8_t {
+ // From the ARMv8 Architectural Reference Manual, Section C1.2.4
+ // Description Condition Flags
+ EQ = 0b0000, // == Z == 1
+ NE = 0b0001, // != Z == 0
+ CS = 0b0010, // carry set C == 1
+ HS = 0b0010, // carry set (alias) C == 1
+ CC = 0b0011, // carry clear C == 0
+ LO = 0b0011, // carry clear (alias) C == 0
+ MI = 0b0100, // < 0 N == 1
+ PL = 0b0101, // >= 0 N == 0
+ VS = 0b0110, // overflow V == 1
+ VC = 0b0111, // no overflow V == 0
+ HI = 0b1000, // unsigned > C == 1 && Z == 0
+ LS = 0b1001, // unsigned <= !(C == 1 && Z == 0)
+ GE = 0b1010, // signed >= N == V
+ LT = 0b1011, // signed < N != V
+ GT = 0b1100, // signed > Z == 0 && N == V
+ LE = 0b1101, // signed <= !(Z == 0 && N == V)
+ AL = 0b1110, // unconditional <Any>
+ NV = 0b1111 // unconditional (but AL is the preferred encoding)
+};
+// clang-format on
+
+struct LoadOrBranch {
+ enum class Type {
+ Load,
+ Branch,
+ };
+
+ // Load constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const uint8_t aDestReg)
+ : mType(Type::Load), mAbsAddress(aAbsAddress), mDestReg(aDestReg) {
+ MOZ_ASSERT(aDestReg < 32);
+ }
+
+ // Unconditional branch constructor
+ explicit LoadOrBranch(const uintptr_t aAbsAddress)
+ : mType(Type::Branch),
+ mAbsAddress(aAbsAddress),
+ mCond(IntegerConditionCode::AL) {}
+
+ // Conditional branch constructor
+ LoadOrBranch(const uintptr_t aAbsAddress, const IntegerConditionCode aCond)
+ : mType(Type::Branch), mAbsAddress(aAbsAddress), mCond(aCond) {}
+
+ Type mType;
+
+ // The absolute address to be loaded into a register, or branched to
+ uintptr_t mAbsAddress;
+
+ union {
+ // The destination register for the load
+ uint8_t mDestReg;
+
+ // The condition code for the branch
+ IntegerConditionCode mCond;
+ };
+};
+
+enum class PCRelCheckError {
+ InstructionNotPCRel,
+ NoDecoderAvailable,
+};
+
+MFBT_API Result<LoadOrBranch, PCRelCheckError> CheckForPCRel(
+ const uintptr_t aPC, const uint32_t aInst);
+
+/**
+ * Casts |aValue| to a |ResultT| via sign extension.
+ *
+ * This function should be used when extracting signed immediate values from
+ * an instruction.
+ *
+ * @param aValue The value to be sign extended. This value should already be
+ * isolated from the remainder of the instruction's bits and
+ * shifted all the way to the right.
+ * @param aNumValidBits The number of bits in |aValue| that contain the
+ * immediate signed value, including the sign bit.
+ */
+template <typename ResultT>
+inline ResultT SignExtend(const uint32_t aValue, const uint8_t aNumValidBits) {
+ static_assert(std::is_integral_v<ResultT> && std::is_signed_v<ResultT>,
+ "ResultT must be a signed integral type");
+ MOZ_ASSERT(aNumValidBits < 32U && aNumValidBits > 1);
+
+ using UnsignedResultT = std::decay_t<std::make_unsigned_t<ResultT>>;
+
+ const uint8_t kResultWidthBits = sizeof(ResultT) * 8;
+
+ // Shift left unsigned
+ const uint8_t shiftAmt = kResultWidthBits - aNumValidBits;
+ UnsignedResultT shiftedLeft = static_cast<UnsignedResultT>(aValue)
+ << shiftAmt;
+
+ // Now shift right signed
+ auto result = static_cast<ResultT>(shiftedLeft);
+ result >>= shiftAmt;
+
+ return result;
+}
+
+inline static uint32_t BuildUnconditionalBranchToRegister(const uint32_t aReg) {
+ MOZ_ASSERT(aReg < 32);
+ // BR aReg
+ return 0xD61F0000 | (aReg << 5);
+}
+
+MFBT_API LoadOrBranch BUncondImmDecode(const uintptr_t aPC,
+ const uint32_t aInst);
+
+/**
+ * If |aTarget| is more than 128MB away from |aPC|, we need to use a veneer.
+ */
+inline static bool IsVeneerRequired(const uintptr_t aPC,
+ const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ uintptr_t absDiff = Abs(saturated.value());
+
+ return absDiff >= 0x08000000U;
+}
+
+inline static bool IsUnconditionalBranchImm(const uint32_t aInst) {
+ return (aInst & 0xFC000000U) == 0x14000000U;
+}
+
+inline static Maybe<uint32_t> BuildUnconditionalBranchImm(
+ const uintptr_t aPC, const uintptr_t aTarget) {
+ detail::Saturate<intptr_t> saturated(aTarget);
+ saturated -= aPC;
+
+ CheckedInt<int32_t> offset(saturated.value());
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ // offset should be a multiple of 4
+ MOZ_ASSERT(offset.value() % 4 == 0);
+ if (offset.value() % 4) {
+ return Nothing();
+ }
+
+ offset /= 4;
+ if (!offset.isValid()) {
+ return Nothing();
+ }
+
+ uint32_t signbits = static_cast<uint32_t>(offset.value()) & 0xFE000000;
+ // Ensure that offset is small enough to fit into the 26 bit region.
+ // We check that the sign bits are either all ones or all zeros.
+ MOZ_ASSERT(signbits == 0xFE000000 || !signbits);
+ if (signbits && signbits != 0xFE000000) {
+ return Nothing();
+ }
+
+ uint32_t masked = static_cast<uint32_t>(offset.value()) & 0x03FFFFFF;
+
+ // B imm26
+ return Some(0x14000000U | masked);
+}
+
+/**
+ * Allocate and construct a veneer that provides an absolute 64-bit branch to
+ * the hook function.
+ */
+template <typename TrampPoolT>
+inline static uintptr_t MakeVeneer(TrampPoolT& aTrampPool, void* aPrimaryTramp,
+ const uintptr_t aDestAddress) {
+ auto maybeVeneer = aTrampPool.GetNextTrampoline();
+ if (!maybeVeneer) {
+ return 0;
+ }
+
+ Trampoline<typename TrampPoolT::MMPolicyT> veneer(
+ std::move(maybeVeneer.ref()));
+
+ // Write the same header information that is used for trampolines
+ veneer.WriteEncodedPointer(nullptr);
+ veneer.WriteEncodedPointer(aPrimaryTramp);
+
+ veneer.StartExecutableCode();
+
+ // Register 16 is explicitly intended for veneers in ARM64, so we use that
+ // register without fear of clobbering anything important.
+ veneer.WriteLoadLiteral(aDestAddress, 16);
+ veneer.WriteInstruction(BuildUnconditionalBranchToRegister(16));
+
+ return reinterpret_cast<uintptr_t>(veneer.EndExecutableCode());
+}
+
+} // namespace arm64
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Arm64_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h b/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h
new file mode 100644
index 0000000000..0a309a1065
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/MMPolicies.h
@@ -0,0 +1,1031 @@
+/* -*- 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_interceptor_MMPolicies_h
+#define mozilla_interceptor_MMPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/MathAlgorithms.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Span.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsMapRemoteView.h"
+#include "mozilla/WindowsUnwindInfo.h"
+
+#include <windows.h>
+
+#if (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+PVOID WINAPI VirtualAlloc2(HANDLE Process, PVOID BaseAddress, SIZE_T Size,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+PVOID WINAPI MapViewOfFile3(HANDLE FileMapping, HANDLE Process,
+ PVOID BaseAddress, ULONG64 Offset, SIZE_T ViewSize,
+ ULONG AllocationType, ULONG PageProtection,
+ MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ ULONG ParameterCount);
+#endif // (NTDDI_VERSION < NTDDI_WIN10_RS4) || defined(__MINGW32__)
+
+// _CRT_RAND_S is not defined everywhere, but we need it.
+#if !defined(_CRT_RAND_S)
+extern "C" errno_t rand_s(unsigned int* randomValue);
+#endif // !defined(_CRT_RAND_S)
+
+// Declaring only the functions we need in NativeNt.h. To include the entire
+// NativeNt.h causes circular dependency.
+namespace mozilla {
+namespace nt {
+SIZE_T WINAPI VirtualQueryEx(HANDLE aProcess, LPCVOID aAddress,
+ PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+
+SIZE_T WINAPI VirtualQuery(LPCVOID aAddress, PMEMORY_BASIC_INFORMATION aMemInfo,
+ SIZE_T aMemInfoLen);
+} // namespace nt
+} // namespace mozilla
+
+namespace mozilla {
+namespace interceptor {
+
+// This class implements memory operations not involving any kernel32's
+// functions, so that derived classes can use them.
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcessPrimitive {
+ protected:
+ bool ProtectInternal(decltype(&::VirtualProtect) aVirtualProtect,
+ void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = aVirtualProtect(aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtect can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ public:
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ ::memcpy(aToPtr, aFromPtr, aLen);
+ return true;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQuery(reinterpret_cast<LPCVOID>(aVAddress), &mbi,
+ sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyBase {
+ protected:
+ static uintptr_t AlignDown(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned & (-aAlignTo);
+ }
+
+ static uintptr_t AlignUp(const uintptr_t aUnaligned,
+ const uintptr_t aAlignTo) {
+ MOZ_ASSERT(IsPowerOfTwo(aAlignTo));
+#pragma warning(suppress : 4146)
+ return aUnaligned + ((-aUnaligned) & (aAlignTo - 1));
+ }
+
+ static PVOID AlignUpToRegion(PVOID aUnaligned, uintptr_t aAlignTo,
+ size_t aLen, size_t aDesiredLen) {
+ uintptr_t unaligned = reinterpret_cast<uintptr_t>(aUnaligned);
+ uintptr_t aligned = AlignUp(unaligned, aAlignTo);
+ MOZ_ASSERT(aligned >= unaligned);
+
+ if (aLen < aligned - unaligned) {
+ return nullptr;
+ }
+
+ aLen -= (aligned - unaligned);
+ return reinterpret_cast<PVOID>((aLen >= aDesiredLen) ? aligned : 0);
+ }
+
+ public:
+#if defined(NIGHTLY_BUILD)
+ Maybe<DetourError> mLastError;
+ const Maybe<DetourError>& GetLastDetourError() const { return mLastError; }
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mLastError = Some(DetourError(std::forward<Args>(aArgs)...));
+ }
+#else
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {}
+#endif // defined(NIGHTLY_BUILD)
+
+ DWORD ComputeAllocationSize(const uint32_t aRequestedSize) const {
+ MOZ_ASSERT(aRequestedSize);
+ DWORD result = aRequestedSize;
+
+ const uint32_t granularity = GetAllocGranularity();
+
+ uint32_t mod = aRequestedSize % granularity;
+ if (mod) {
+ result += (granularity - mod);
+ }
+
+ return result;
+ }
+
+ DWORD GetAllocGranularity() const {
+ static const DWORD kAllocGranularity = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwAllocationGranularity;
+ }();
+
+ return kAllocGranularity;
+ }
+
+ DWORD GetPageSize() const {
+ static const DWORD kPageSize = []() -> DWORD {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }();
+
+ return kPageSize;
+ }
+
+ uintptr_t GetMaxUserModeAddress() const {
+ static const uintptr_t kMaxUserModeAddr = []() -> uintptr_t {
+ SYSTEM_INFO sysInfo;
+ ::GetSystemInfo(&sysInfo);
+ return reinterpret_cast<uintptr_t>(sysInfo.lpMaximumApplicationAddress);
+ }();
+
+ return kMaxUserModeAddr;
+ }
+
+ static const uint8_t* GetLowerBound(const Span<const uint8_t>& aBounds) {
+ return &(*aBounds.cbegin());
+ }
+
+ static const uint8_t* GetUpperBoundIncl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is inclusive.
+ return &(*(aBounds.cend() - 1));
+ }
+
+ static const uint8_t* GetUpperBoundExcl(const Span<const uint8_t>& aBounds) {
+ // We return an upper bound that is exclusive by adding 1 to the inclusive
+ // upper bound.
+ return GetUpperBoundIncl(aBounds) + 1;
+ }
+
+ /**
+ * It is convenient for us to provide address range information based on a
+ * "pivot" and a distance from that pivot, as branch instructions operate
+ * within a range of the program counter. OTOH, to actually manage the
+ * regions of memory, it is easier to think about them in terms of their
+ * lower and upper bounds. This function converts from the former format to
+ * the latter format.
+ */
+ Maybe<Span<const uint8_t>> SpanFromPivotAndDistance(
+ const uint32_t aSize, const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) const {
+ if (!aPivotAddr || !aMaxDistanceFromPivot) {
+ return Nothing();
+ }
+
+ // We don't allow regions below 1MB so that we're not allocating near any
+ // sensitive areas in our address space.
+ const uintptr_t kMinAllowableAddress = 0x100000;
+
+ const uintptr_t kGranularity(GetAllocGranularity());
+
+ // We subtract the max distance from the pivot to determine our lower bound.
+ CheckedInt<uintptr_t> lowerBound(aPivotAddr);
+ lowerBound -= aMaxDistanceFromPivot;
+ if (lowerBound.isValid()) {
+ // In this case, the subtraction has not underflowed, but we still want
+ // the lower bound to be at least kMinAllowableAddress.
+ lowerBound = std::max(lowerBound.value(), kMinAllowableAddress);
+ } else {
+ // In this case, we underflowed. Forcibly set the lower bound to
+ // kMinAllowableAddress.
+ lowerBound = CheckedInt<uintptr_t>(kMinAllowableAddress);
+ }
+
+ // Align up to the next unit of allocation granularity when necessary.
+ lowerBound = AlignUp(lowerBound.value(), kGranularity);
+ MOZ_ASSERT(lowerBound.isValid());
+ if (!lowerBound.isValid()) {
+ return Nothing();
+ }
+
+ // We must ensure that our region is below the maximum allowable user-mode
+ // address, or our reservation will fail.
+ const uintptr_t kMaxUserModeAddr = GetMaxUserModeAddress();
+
+ // We add the max distance from the pivot to determine our upper bound.
+ CheckedInt<uintptr_t> upperBound(aPivotAddr);
+ upperBound += aMaxDistanceFromPivot;
+ if (upperBound.isValid()) {
+ // In this case, the addition has not overflowed, but we still want
+ // the upper bound to be at most kMaxUserModeAddr.
+ upperBound = std::min(upperBound.value(), kMaxUserModeAddr);
+ } else {
+ // In this case, we overflowed. Forcibly set the upper bound to
+ // kMaxUserModeAddr.
+ upperBound = CheckedInt<uintptr_t>(kMaxUserModeAddr);
+ }
+
+ // Subtract the desired allocation size so that any chunk allocated in the
+ // region will be reachable.
+ upperBound -= aSize;
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ // Align down to the next unit of allocation granularity when necessary.
+ upperBound = AlignDown(upperBound.value(), kGranularity);
+ if (!upperBound.isValid()) {
+ return Nothing();
+ }
+
+ MOZ_ASSERT(lowerBound.value() < upperBound.value());
+ if (lowerBound.value() >= upperBound.value()) {
+ return Nothing();
+ }
+
+ // Return the result as a Span
+ return Some(Span(reinterpret_cast<const uint8_t*>(lowerBound.value()),
+ upperBound.value() - lowerBound.value()));
+ }
+
+ /**
+ * This function locates a virtual memory region of |aDesiredBytesLen| that
+ * resides in the interval [aRangeMin, aRangeMax). We do this by scanning the
+ * virtual memory space for a block of unallocated memory that is sufficiently
+ * large.
+ */
+ PVOID FindRegion(HANDLE aProcess, const size_t aDesiredBytesLen,
+ const uint8_t* aRangeMin, const uint8_t* aRangeMax) {
+ // Convert the given pointers to uintptr_t because we should not
+ // compare two pointers unless they are from the same array or object.
+ uintptr_t rangeMin = reinterpret_cast<uintptr_t>(aRangeMin);
+ uintptr_t rangeMax = reinterpret_cast<uintptr_t>(aRangeMax);
+
+ const DWORD kGranularity = GetAllocGranularity();
+ if (!aDesiredBytesLen) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDLEN);
+ return nullptr;
+ }
+
+ MOZ_ASSERT(rangeMin < rangeMax);
+ if (rangeMin >= rangeMax) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_INVALIDRANGE);
+ return nullptr;
+ }
+
+ // Generate a randomized base address that falls within the interval
+ // [aRangeMin, aRangeMax - aDesiredBytesLen]
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ // Reduce rnd to a value that falls within the acceptable range
+ uintptr_t maxOffset =
+ (rangeMax - rangeMin - aDesiredBytesLen) / kGranularity;
+ // Divide by maxOffset + 1 because maxOffset * kGranularity is acceptable.
+ uintptr_t offset = (uintptr_t(rnd) % (maxOffset + 1)) * kGranularity;
+
+ // Start searching at this address
+ const uintptr_t searchStart = rangeMin + offset;
+ // The max address needs to incorporate the desired length
+ const uintptr_t kMaxPtr = rangeMax - aDesiredBytesLen;
+
+ MOZ_DIAGNOSTIC_ASSERT(searchStart <= kMaxPtr);
+
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T len = sizeof(mbi);
+
+ // Scan the range for a free chunk that is at least as large as
+ // aDesiredBytesLen
+ // Scan [searchStart, kMaxPtr]
+ for (uintptr_t address = searchStart; address <= kMaxPtr;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ // |mbi.BaseAddress| is aligned with the page granularity, but may not
+ // be aligned with the allocation granularity. VirtualAlloc does not
+ // accept such a non-aligned address unless the corresponding allocation
+ // region is free. So we get the next boundary's start address.
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ // Scan [aRangeMin, searchStart)
+ for (uintptr_t address = rangeMin; address < searchStart;) {
+ if (nt::VirtualQueryEx(aProcess, reinterpret_cast<uint8_t*>(address),
+ &mbi, len) != len) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_VIRTUALQUERY_ERROR,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ if (mbi.State == MEM_FREE) {
+ PVOID regionStart = AlignUpToRegion(mbi.BaseAddress, kGranularity,
+ mbi.RegionSize, aDesiredBytesLen);
+ if (regionStart) {
+ return regionStart;
+ }
+ }
+
+ address = reinterpret_cast<uintptr_t>(mbi.BaseAddress) + mbi.RegionSize;
+ }
+
+ SetLastDetourError(MMPOLICY_RESERVE_FINDREGION_NO_FREE_REGION,
+ ::GetLastError());
+ return nullptr;
+ }
+
+ /**
+ * This function reserves a |aSize| block of virtual memory.
+ *
+ * When |aBounds| is Nothing, it just calls |aReserveFn| and lets Windows
+ * choose the base address.
+ *
+ * Otherwise, it tries to call |aReserveRangeFn| to reserve the memory within
+ * the bounds provided by |aBounds|. It is advantageous to use this function
+ * because the OS's VM manager has better information as to which base
+ * addresses are the best to use.
+ *
+ * If |aReserveRangeFn| retuns Nothing, this means that the platform support
+ * is not available. In that case, we fall back to manually computing a region
+ * to use for reserving the memory by calling |FindRegion|.
+ */
+ template <typename ReserveFnT, typename ReserveRangeFnT>
+ PVOID Reserve(HANDLE aProcess, const uint32_t aSize,
+ const ReserveFnT& aReserveFn,
+ const ReserveRangeFnT& aReserveRangeFn,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aBounds) {
+ // No restrictions, let the OS choose the base address
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_NOBOUND_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+
+ const uint8_t* lowerBound = GetLowerBound(aBounds.ref());
+ const uint8_t* upperBoundExcl = GetUpperBoundExcl(aBounds.ref());
+
+ Maybe<PVOID> result =
+ aReserveRangeFn(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (result) {
+ return result.value();
+ }
+
+ // aReserveRangeFn is not available on this machine. We'll do a manual
+ // search.
+
+ size_t curAttempt = 0;
+ const size_t kMaxAttempts = 8;
+
+ // We loop here because |FindRegion| may return a base address that
+ // is reserved elsewhere before we have had a chance to reserve it
+ // ourselves.
+ while (curAttempt < kMaxAttempts) {
+ PVOID base = FindRegion(aProcess, aSize, lowerBound, upperBoundExcl);
+ if (!base) {
+ return nullptr;
+ }
+
+ result = Some(aReserveFn(aProcess, base, aSize));
+ if (result.value()) {
+ return result.value();
+ }
+
+ ++curAttempt;
+ }
+
+ // If we run out of attempts, we fall through to the default case where
+ // the system chooses any base address it wants. In that case, the hook
+ // will be set on a best-effort basis.
+ PVOID ret = aReserveFn(aProcess, nullptr, aSize);
+ if (!ret) {
+ SetLastDetourError(MMPOLICY_RESERVE_FINAL_RESERVE_ERROR,
+ ::GetLastError());
+ }
+ return ret;
+ }
+};
+
+class MOZ_TRIVIAL_CTOR_DTOR MMPolicyInProcess
+ : public MMPolicyInProcessPrimitive,
+ public MMPolicyBase {
+ public:
+ typedef MMPolicyInProcess MMPolicyT;
+
+ constexpr MMPolicyInProcess()
+ : mBase(nullptr), mReservationSize(0), mCommitOffset(0) {}
+
+ MMPolicyInProcess(const MMPolicyInProcess&) = delete;
+ MMPolicyInProcess& operator=(const MMPolicyInProcess&) = delete;
+
+ MMPolicyInProcess(MMPolicyInProcess&& aOther)
+ : mBase(nullptr), mReservationSize(0), mCommitOffset(0) {
+ *this = std::move(aOther);
+ }
+
+ MMPolicyInProcess& operator=(MMPolicyInProcess&& aOther) {
+ mBase = aOther.mBase;
+ aOther.mBase = nullptr;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ return *this;
+ }
+
+ explicit operator bool() const { return !!mBase; }
+
+ /**
+ * Should we unhook everything upon destruction?
+ */
+ bool ShouldUnhookUponDestruction() const { return true; }
+
+#if defined(_M_IX86)
+ bool WriteAtomic(void* aDestPtr, const uint16_t aValue) const {
+ *static_cast<uint16_t*>(aDestPtr) = aValue;
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(::VirtualProtect, aVAddress, aSize, aProtFlags,
+ aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(::GetCurrentProcess(), nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_EXECUTE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (mBase + mReservationSize) <=
+ reinterpret_cast<uint8_t*>(0x0000000080000000ULL);
+ }
+
+ static constexpr bool kSupportsUnwindInfo = true;
+
+ mozilla::UniquePtr<uint8_t[]> LookupUnwindInfo(
+ uintptr_t aOrigFuncAddr, uint32_t* aOffsetFromBeginAddr,
+ uint32_t* aOffsetToEndAddr, uintptr_t* aOrigImageBase) const {
+ DWORD64 origImageBase = 0;
+ auto origFuncEntry =
+ RtlLookupFunctionEntry(aOrigFuncAddr, &origImageBase, nullptr);
+ if (!origFuncEntry) {
+ return nullptr;
+ }
+
+ if (aOffsetFromBeginAddr) {
+ *aOffsetFromBeginAddr =
+ aOrigFuncAddr - (origImageBase + origFuncEntry->BeginAddress);
+ }
+ if (aOffsetToEndAddr) {
+ *aOffsetToEndAddr =
+ (origImageBase + origFuncEntry->EndAddress) - aOrigFuncAddr;
+ }
+ if (aOrigImageBase) {
+ *aOrigImageBase = origImageBase;
+ }
+ return reinterpret_cast<const UnwindInfo*>(origImageBase +
+ origFuncEntry->UnwindData)
+ ->Copy();
+ }
+
+ bool AddFunctionTable(uintptr_t aFunctionTable, uint32_t aEntryCount,
+ uintptr_t aBaseAddress) const {
+ return bool(
+ RtlAddFunctionTable(reinterpret_cast<PRUNTIME_FUNCTION>(aFunctionTable),
+ aEntryCount, aBaseAddress));
+ }
+#endif // defined(_M_X64)
+
+ protected:
+ uint8_t* GetLocalView() const { return mBase; }
+
+ uintptr_t GetRemoteView() const {
+ // Same as local view for in-process
+ return reinterpret_cast<uintptr_t>(mBase);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize) {
+ return 0;
+ }
+
+ if (mBase) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ auto reserveFn = [](HANDLE aProcess, PVOID aBase, uint32_t aSize) -> PVOID {
+ return ::VirtualAlloc(aBase, aSize, MEM_RESERVE, PAGE_NOACCESS);
+ };
+
+ auto reserveWithinRangeFn =
+ [](HANDLE aProcess, uint32_t aSize, const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::VirtualAlloc2)>
+ pVirtualAlloc2(L"kernelbase.dll", "VirtualAlloc2");
+ if (!pVirtualAlloc2) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pVirtualAlloc2(aProcess, nullptr, aSize, MEM_RESERVE,
+ PAGE_NOACCESS, &memParam, 1));
+ };
+
+ mBase = static_cast<uint8_t*>(
+ MMPolicyBase::Reserve(::GetCurrentProcess(), mReservationSize,
+ reserveFn, reserveWithinRangeFn, aBounds));
+
+ if (!mBase) {
+ return 0;
+ }
+
+ return mReservationSize;
+ }
+
+ bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
+ const uint32_t aRequestedLength) {
+ if (!(*this)) {
+ return false;
+ }
+
+ uint32_t limit = aRequestedOffset + aRequestedLength - 1;
+ if (limit < mCommitOffset) {
+ // No commit required
+ return true;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
+ if (mCommitOffset >= mReservationSize) {
+ return false;
+ }
+
+ PVOID local = ::VirtualAlloc(mBase + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!local) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+ private:
+ uint8_t* mBase;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+};
+
+// This class manages in-process memory access without using functions
+// imported from kernel32.dll. Instead, it uses functions in its own
+// function table that are provided from outside.
+class MMPolicyInProcessEarlyStage : public MMPolicyInProcessPrimitive {
+ public:
+ struct Kernel32Exports {
+ decltype(&::FlushInstructionCache) mFlushInstructionCache;
+ decltype(&::GetModuleHandleW) mGetModuleHandleW;
+ decltype(&::GetSystemInfo) mGetSystemInfo;
+ decltype(&::VirtualProtect) mVirtualProtect;
+ };
+
+ private:
+ static DWORD GetPageSize(const Kernel32Exports& aK32Exports) {
+ SYSTEM_INFO sysInfo;
+ aK32Exports.mGetSystemInfo(&sysInfo);
+ return sysInfo.dwPageSize;
+ }
+
+ const Kernel32Exports& mK32Exports;
+ const DWORD mPageSize;
+
+ public:
+ explicit MMPolicyInProcessEarlyStage(const Kernel32Exports& aK32Exports)
+ : mK32Exports(aK32Exports), mPageSize(GetPageSize(mK32Exports)) {}
+
+ // The pattern of constructing a local static variable with a lambda,
+ // which can be seen in MMPolicyBase, is compiled into code with the
+ // critical section APIs like EnterCriticalSection imported from kernel32.dll.
+ // Because this class needs to be able to run in a process's early stage
+ // when IAT is not yet resolved, we cannot use that patten, thus simply
+ // caching a value as a local member in the class.
+ DWORD GetPageSize() const { return mPageSize; }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ return ProtectInternal(mK32Exports.mVirtualProtect, aVAddress, aSize,
+ aProtFlags, aPrevProtFlags);
+ }
+
+ bool FlushInstructionCache() const {
+ const HANDLE kCurrentProcess = reinterpret_cast<HANDLE>(-1);
+ return !!mK32Exports.mFlushInstructionCache(kCurrentProcess, nullptr, 0);
+ }
+};
+
+class MMPolicyOutOfProcess : public MMPolicyBase {
+ public:
+ typedef MMPolicyOutOfProcess MMPolicyT;
+
+ explicit MMPolicyOutOfProcess(HANDLE aProcess)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(aProcess);
+ ::DuplicateHandle(::GetCurrentProcess(), aProcess, ::GetCurrentProcess(),
+ &mProcess, kAccessFlags, FALSE, 0);
+ MOZ_ASSERT(mProcess);
+ }
+
+ explicit MMPolicyOutOfProcess(DWORD aPid)
+ : mProcess(::OpenProcess(kAccessFlags, FALSE, aPid)),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ MOZ_ASSERT(mProcess);
+ }
+
+ ~MMPolicyOutOfProcess() { Destroy(); }
+
+ MMPolicyOutOfProcess(MMPolicyOutOfProcess&& aOther)
+ : mProcess(nullptr),
+ mMapping(nullptr),
+ mLocalView(nullptr),
+ mRemoteView(nullptr),
+ mReservationSize(0),
+ mCommitOffset(0) {
+ *this = std::move(aOther);
+ }
+
+ MMPolicyOutOfProcess(const MMPolicyOutOfProcess& aOther) = delete;
+ MMPolicyOutOfProcess& operator=(const MMPolicyOutOfProcess&) = delete;
+
+ MMPolicyOutOfProcess& operator=(MMPolicyOutOfProcess&& aOther) {
+ Destroy();
+
+ mProcess = aOther.mProcess;
+ aOther.mProcess = nullptr;
+
+ mMapping = aOther.mMapping;
+ aOther.mMapping = nullptr;
+
+ mLocalView = aOther.mLocalView;
+ aOther.mLocalView = nullptr;
+
+ mRemoteView = aOther.mRemoteView;
+ aOther.mRemoteView = nullptr;
+
+ mReservationSize = aOther.mReservationSize;
+ aOther.mReservationSize = 0;
+
+ mCommitOffset = aOther.mCommitOffset;
+ aOther.mCommitOffset = 0;
+
+ return *this;
+ }
+
+ explicit operator bool() const {
+ return mProcess && mMapping && mLocalView && mRemoteView;
+ }
+
+ bool ShouldUnhookUponDestruction() const {
+ // We don't clean up hooks for remote processes; they are expected to
+ // outlive our process.
+ return false;
+ }
+
+ // This function reads as many bytes as |aLen| from the target process and
+ // succeeds only when the entire area to be read is accessible.
+ bool Read(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ // This function reads as many bytes as possible from the target process up
+ // to |aLen| bytes and returns the number of bytes which was actually read.
+ size_t TryRead(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return 0;
+ }
+
+ uint32_t pageSize = GetPageSize();
+ uintptr_t pageMask = pageSize - 1;
+
+ auto rangeStart = reinterpret_cast<uintptr_t>(aFromPtr);
+ auto rangeEnd = rangeStart + aLen;
+
+ while (rangeStart < rangeEnd) {
+ SIZE_T numBytes = 0;
+ BOOL ok = ::ReadProcessMemory(mProcess, aFromPtr, aToPtr,
+ rangeEnd - rangeStart, &numBytes);
+ if (ok) {
+ return numBytes;
+ }
+
+ // If ReadProcessMemory fails, try to read up to each page boundary from
+ // the end of the requested area one by one.
+ if (rangeEnd & pageMask) {
+ rangeEnd &= ~pageMask;
+ } else {
+ rangeEnd -= pageSize;
+ }
+ }
+
+ return 0;
+ }
+
+ bool Write(void* aToPtr, const void* aFromPtr, size_t aLen) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ SIZE_T numBytes = 0;
+ BOOL ok = ::WriteProcessMemory(mProcess, aToPtr, aFromPtr, aLen, &numBytes);
+ return ok && numBytes == aLen;
+ }
+
+ bool Protect(void* aVAddress, size_t aSize, uint32_t aProtFlags,
+ uint32_t* aPrevProtFlags) const {
+ MOZ_ASSERT(mProcess);
+ if (!mProcess) {
+ return false;
+ }
+
+ MOZ_ASSERT(aPrevProtFlags);
+ BOOL ok = ::VirtualProtectEx(mProcess, aVAddress, aSize, aProtFlags,
+ reinterpret_cast<PDWORD>(aPrevProtFlags));
+ if (!ok && aPrevProtFlags) {
+ // VirtualProtectEx can fail but still set valid protection flags.
+ // Let's clear those upon failure.
+ *aPrevProtFlags = 0;
+ }
+
+ return !!ok;
+ }
+
+ /**
+ * @return true if the page that hosts aVAddress is accessible.
+ */
+ bool IsPageAccessible(uintptr_t aVAddress) const {
+ MEMORY_BASIC_INFORMATION mbi;
+ SIZE_T result = nt::VirtualQueryEx(
+ mProcess, reinterpret_cast<LPCVOID>(aVAddress), &mbi, sizeof(mbi));
+
+ return result && mbi.AllocationProtect && mbi.State == MEM_COMMIT &&
+ mbi.Protect != PAGE_NOACCESS;
+ }
+
+ bool FlushInstructionCache() const {
+ return !!::FlushInstructionCache(mProcess, nullptr, 0);
+ }
+
+ static DWORD GetTrampWriteProtFlags() { return PAGE_READWRITE; }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB() const {
+ return (GetRemoteView() + mReservationSize) <= 0x0000000080000000ULL;
+ }
+
+ // TODO: We should also implement unwind info for our out-of-process policy.
+ static constexpr bool kSupportsUnwindInfo = false;
+
+ inline mozilla::UniquePtr<uint8_t[]> LookupUnwindInfo(
+ uintptr_t aOrigFuncAddr, uint32_t* aOffsetFromBeginAddr,
+ uint32_t* aOffsetToEndAddr, uintptr_t* aOrigImageBase) const {
+ return nullptr;
+ }
+
+ inline bool AddFunctionTable(uintptr_t aNewTable, uint32_t aEntryCount,
+ uintptr_t aBaseAddress) const {
+ return false;
+ }
+#endif // defined(_M_X64)
+
+ protected:
+ uint8_t* GetLocalView() const { return mLocalView; }
+
+ uintptr_t GetRemoteView() const {
+ return reinterpret_cast<uintptr_t>(mRemoteView);
+ }
+
+ /**
+ * @return the effective number of bytes reserved, or 0 on failure
+ */
+ uint32_t Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ if (!aSize || !mProcess) {
+ SetLastDetourError(MMPOLICY_RESERVE_INVALIDARG);
+ return 0;
+ }
+
+ if (mRemoteView) {
+ MOZ_ASSERT(mReservationSize >= aSize);
+ SetLastDetourError(MMPOLICY_RESERVE_ZERO_RESERVATIONSIZE);
+ return mReservationSize;
+ }
+
+ mReservationSize = ComputeAllocationSize(aSize);
+
+ mMapping = ::CreateFileMappingW(INVALID_HANDLE_VALUE, nullptr,
+ PAGE_EXECUTE_READWRITE | SEC_RESERVE, 0,
+ mReservationSize, nullptr);
+ if (!mMapping) {
+ SetLastDetourError(MMPOLICY_RESERVE_CREATEFILEMAPPING, ::GetLastError());
+ return 0;
+ }
+
+ mLocalView = static_cast<uint8_t*>(
+ ::MapViewOfFile(mMapping, FILE_MAP_WRITE, 0, 0, 0));
+ if (!mLocalView) {
+ SetLastDetourError(MMPOLICY_RESERVE_MAPVIEWOFFILE, ::GetLastError());
+ return 0;
+ }
+
+ auto reserveFn = [mapping = mMapping](HANDLE aProcess, PVOID aBase,
+ uint32_t aSize) -> PVOID {
+ return mozilla::MapRemoteViewOfFile(mapping, aProcess, 0ULL, aBase, 0, 0,
+ PAGE_EXECUTE_READ);
+ };
+
+ auto reserveWithinRangeFn =
+ [mapping = mMapping](HANDLE aProcess, uint32_t aSize,
+ const uint8_t* aRangeMin,
+ const uint8_t* aRangeMaxExcl) -> Maybe<PVOID> {
+ static const StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::MapViewOfFile3)>
+ pMapViewOfFile3(L"kernelbase.dll", "MapViewOfFile3");
+ if (!pMapViewOfFile3) {
+ return Nothing();
+ }
+
+ // NB: MEM_ADDRESS_REQUIREMENTS::HighestEndingAddress is *inclusive*
+ MEM_ADDRESS_REQUIREMENTS memReq = {
+ const_cast<uint8_t*>(aRangeMin),
+ const_cast<uint8_t*>(aRangeMaxExcl - 1)};
+
+ MEM_EXTENDED_PARAMETER memParam = {};
+ memParam.Type = MemExtendedParameterAddressRequirements;
+ memParam.Pointer = &memReq;
+
+ return Some(pMapViewOfFile3(mapping, aProcess, nullptr, 0, aSize, 0,
+ PAGE_EXECUTE_READ, &memParam, 1));
+ };
+
+ mRemoteView = MMPolicyBase::Reserve(mProcess, mReservationSize, reserveFn,
+ reserveWithinRangeFn, aBounds);
+ if (!mRemoteView) {
+ return 0;
+ }
+
+ return mReservationSize;
+ }
+
+ bool MaybeCommitNextPage(const uint32_t aRequestedOffset,
+ const uint32_t aRequestedLength) {
+ if (!(*this)) {
+ return false;
+ }
+
+ uint32_t limit = aRequestedOffset + aRequestedLength - 1;
+ if (limit < mCommitOffset) {
+ // No commit required
+ return true;
+ }
+
+ MOZ_DIAGNOSTIC_ASSERT(mCommitOffset < mReservationSize);
+ if (mCommitOffset >= mReservationSize) {
+ return false;
+ }
+
+ PVOID local = ::VirtualAlloc(mLocalView + mCommitOffset, GetPageSize(),
+ MEM_COMMIT, PAGE_READWRITE);
+ if (!local) {
+ return false;
+ }
+
+ PVOID remote = ::VirtualAllocEx(
+ mProcess, static_cast<uint8_t*>(mRemoteView) + mCommitOffset,
+ GetPageSize(), MEM_COMMIT, PAGE_EXECUTE_READ);
+ if (!remote) {
+ return false;
+ }
+
+ mCommitOffset += GetPageSize();
+ return true;
+ }
+
+ private:
+ void Destroy() {
+ // We always leak the remote view
+ if (mLocalView) {
+ ::UnmapViewOfFile(mLocalView);
+ mLocalView = nullptr;
+ }
+
+ if (mMapping) {
+ ::CloseHandle(mMapping);
+ mMapping = nullptr;
+ }
+
+ if (mProcess) {
+ ::CloseHandle(mProcess);
+ mProcess = nullptr;
+ }
+ }
+
+ private:
+ HANDLE mProcess;
+ HANDLE mMapping;
+ uint8_t* mLocalView;
+ PVOID mRemoteView;
+ uint32_t mReservationSize;
+ uint32_t mCommitOffset;
+
+ static const DWORD kAccessFlags = PROCESS_QUERY_INFORMATION |
+ PROCESS_VM_OPERATION | PROCESS_VM_READ |
+ PROCESS_VM_WRITE;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_MMPolicies_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h
new file mode 100644
index 0000000000..e39a38fafd
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherBase.h
@@ -0,0 +1,141 @@
+/* -*- 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_interceptor_PatcherBase_h
+#define mozilla_interceptor_PatcherBase_h
+
+#include "mozilla/interceptor/TargetFunction.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy>
+struct GetProcAddressSelector;
+
+template <>
+struct GetProcAddressSelector<MMPolicyOutOfProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyOutOfProcess& aMMPolicy) const {
+ auto exportSection =
+ mozilla::nt::PEExportSection<MMPolicyOutOfProcess>::Get(aModule,
+ aMMPolicy);
+ return exportSection.GetProcAddress(aName);
+ }
+};
+
+template <>
+struct GetProcAddressSelector<MMPolicyInProcess> {
+ FARPROC operator()(HMODULE aModule, const char* aName,
+ const MMPolicyInProcess&) const {
+ // PEExportSection works for MMPolicyInProcess, too, but the native
+ // GetProcAddress is still better because PEExportSection does not
+ // solve a forwarded entry.
+ return ::GetProcAddress(aModule, aName);
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllPatcherBase {
+ protected:
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ template <typename... Args>
+ explicit WindowsDllPatcherBase(Args&&... aArgs)
+ : mVMPolicy(std::forward<Args>(aArgs)...) {}
+
+ ReadOnlyTargetFunction<MMPolicyT> ResolveRedirectedAddress(
+ FARPROC aOriginalFunction) {
+ uintptr_t currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+
+#if defined(_M_IX86) || defined(_M_X64)
+ uintptr_t prevAddr = 0;
+ while (prevAddr != currAddr) {
+ ReadOnlyTargetFunction<MMPolicyT> currFunc(mVMPolicy, currAddr);
+ prevAddr = currAddr;
+
+ // If function entry is jmp rel8 stub to the internal implementation, we
+ // resolve redirected address from the jump target.
+ uintptr_t nextAddr = 0;
+ if (currFunc.IsRelativeShortJump(&nextAddr)) {
+ int8_t offset = nextAddr - currFunc.GetAddress() - 2;
+
+# if defined(_M_X64)
+ // We redirect to the target of a short jump backwards if the target
+ // is another jump (only 32-bit displacement is currently supported).
+ // This case is used by GetFileAttributesW in Win7 x64.
+ if ((offset < 0) && (currFunc.IsValidAtOffset(2 + offset))) {
+ ReadOnlyTargetFunction<MMPolicyT> redirectFn(mVMPolicy, nextAddr);
+ if (redirectFn.IsIndirectNearJump(&nextAddr)) {
+ return redirectFn;
+ }
+ }
+# endif
+
+ // We check the downstream has enough nop-space only when the offset is
+ // positive. Otherwise we stop chasing redirects and let the caller
+ // fail to hook.
+ if (offset > 0) {
+ bool isNopSpace = true;
+ for (int8_t i = 0; i < offset; i++) {
+ if (currFunc[2 + i] != 0x90) {
+ isNopSpace = false;
+ break;
+ }
+ }
+
+ if (isNopSpace) {
+ currAddr = nextAddr;
+ }
+ }
+# if defined(_M_X64)
+ } else if (currFunc.IsIndirectNearJump(&nextAddr) ||
+ currFunc.IsRelativeNearJump(&nextAddr)) {
+# else
+ } else if (currFunc.IsIndirectNearJump(&nextAddr)) {
+# endif
+ // If function entry is jmp [disp32] such as used by kernel32, we
+ // resolve redirected address from import table. For x64, we resolve
+ // a relative near jump for TestDllInterceptor with --disable-optimize.
+ currAddr = nextAddr;
+ }
+ }
+#endif // defined(_M_IX86) || defined(_M_X64)
+
+ if (currAddr != reinterpret_cast<uintptr_t>(aOriginalFunction) &&
+ !mVMPolicy.IsPageAccessible(currAddr)) {
+ currAddr = reinterpret_cast<uintptr_t>(aOriginalFunction);
+ }
+ return ReadOnlyTargetFunction<MMPolicyT>(mVMPolicy, currAddr);
+ }
+
+ public:
+ FARPROC GetProcAddress(HMODULE aModule, const char* aName) const {
+ GetProcAddressSelector<MMPolicyT> selector;
+ return selector(aModule, aName, mVMPolicy);
+ }
+
+ bool IsPageAccessible(uintptr_t aAddress) const {
+ return mVMPolicy.IsPageAccessible(aAddress);
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mVMPolicy.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ mVMPolicy.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ protected:
+ VMPolicy mVMPolicy;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherBase_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h
new file mode 100644
index 0000000000..1e8afcf9bc
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherDetour.h
@@ -0,0 +1,1728 @@
+/* -*- 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_interceptor_PatcherDetour_h
+#define mozilla_interceptor_PatcherDetour_h
+
+#if defined(_M_ARM64)
+# include "mozilla/interceptor/Arm64.h"
+#endif // defined(_M_ARM64)
+#include <utility>
+
+#include "mozilla/Maybe.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/TypedEnumBits.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/interceptor/PatcherBase.h"
+#include "mozilla/interceptor/Trampoline.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+
+#define COPY_CODES(NBYTES) \
+ do { \
+ tramp.CopyCodes(origBytes.GetAddress(), NBYTES); \
+ origBytes += NBYTES; \
+ } while (0)
+
+namespace mozilla {
+namespace interceptor {
+
+enum class DetourFlags : uint32_t {
+ eDefault = 0,
+ eEnable10BytePatch = 1, // Allow 10-byte patches when conditions allow
+ eTestOnlyForceShortPatch =
+ 2, // Force short patches at all times (x86-64 and arm64 testing only)
+ eDontResolveRedirection =
+ 4, // Don't resolve the redirection of JMP (e.g. kernel32 -> kernelbase)
+};
+
+MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(DetourFlags)
+
+// This class is responsible to do tasks which depend on MMPolicy, decoupled
+// from VMPolicy. We already have WindowsDllPatcherBase, but it needs to
+// depend on VMPolicy to hold an instance of VMPolicy as a member.
+template <typename MMPolicyT>
+class WindowsDllDetourPatcherPrimitive {
+ protected:
+#if defined(_M_ARM64)
+ // LDR x16, .+8
+ static const uint32_t kLdrX16Plus8 = 0x58000050U;
+#endif // defined(_M_ARM64)
+
+ static void ApplyDefaultPatch(WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+#if defined(_M_IX86)
+ target.WriteByte(0xe9); // jmp
+ target.WriteDisp32(aDest); // hook displacement
+#elif defined(_M_X64)
+ // mov r11, address
+ target.WriteByte(0x49);
+ target.WriteByte(0xbb);
+ target.WritePointer(aDest);
+
+ // jmp r11
+ target.WriteByte(0x41);
+ target.WriteByte(0xff);
+ target.WriteByte(0xe3);
+#elif defined(_M_ARM64)
+ // The default patch requires 16 bytes
+ // LDR x16, .+8
+ target.WriteLong(kLdrX16Plus8);
+ // BR x16
+ target.WriteLong(arm64::BuildUnconditionalBranchToRegister(16));
+ target.WritePointer(aDest);
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ public:
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+#if defined(_M_IX86)
+ return 5;
+#elif defined(_M_X64)
+ return 13;
+#elif defined(_M_ARM64)
+ return 16;
+#else
+# error "Unsupported processor architecture"
+#endif
+ }
+
+ WindowsDllDetourPatcherPrimitive() = default;
+
+ WindowsDllDetourPatcherPrimitive(const WindowsDllDetourPatcherPrimitive&) =
+ delete;
+ WindowsDllDetourPatcherPrimitive(WindowsDllDetourPatcherPrimitive&&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ const WindowsDllDetourPatcherPrimitive&) = delete;
+ WindowsDllDetourPatcherPrimitive& operator=(
+ WindowsDllDetourPatcherPrimitive&&) = delete;
+
+ bool AddIrreversibleHook(const MMPolicyT& aMMPolicy, FARPROC aTargetFn,
+ intptr_t aHookDest) {
+ ReadOnlyTargetFunction<MMPolicyT> targetReadOnly(aMMPolicy, aTargetFn);
+
+ WritableTargetFunction<MMPolicyT> targetWritable(
+ targetReadOnly.Promote(GetWorstCaseRequiredBytesToPatch()));
+ if (!targetWritable) {
+ return false;
+ }
+
+ ApplyDefaultPatch(targetWritable, aHookDest);
+
+ return targetWritable.Commit();
+ }
+};
+
+template <typename VMPolicy>
+class WindowsDllDetourPatcher final
+ : public WindowsDllDetourPatcherPrimitive<typename VMPolicy::MMPolicyT>,
+ public WindowsDllPatcherBase<VMPolicy> {
+ using MMPolicyT = typename VMPolicy::MMPolicyT;
+ using TrampPoolT = typename VMPolicy::PoolType;
+ using PrimitiveT = WindowsDllDetourPatcherPrimitive<MMPolicyT>;
+ Maybe<DetourFlags> mFlags;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllDetourPatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllDetourPatcher() { Clear(); }
+
+ WindowsDllDetourPatcher(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher(WindowsDllDetourPatcher&&) = delete;
+ WindowsDllDetourPatcher& operator=(const WindowsDllDetourPatcher&) = delete;
+ WindowsDllDetourPatcher& operator=(WindowsDllDetourPatcher&&) = delete;
+
+ void Clear() {
+ if (!this->mVMPolicy.ShouldUnhookUponDestruction()) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ size_t nBytes = 1 + sizeof(intptr_t);
+#elif defined(_M_X64)
+ size_t nBytes = 2 + sizeof(intptr_t);
+#elif defined(_M_ARM64)
+ size_t nBytes = 2 * sizeof(uint32_t) + sizeof(uintptr_t);
+#else
+# error "Unknown processor type"
+#endif
+
+ const auto& tramps = this->mVMPolicy.Items();
+ for (auto&& tramp : tramps) {
+ // First we read the pointer to the interceptor instance.
+ Maybe<uintptr_t> instance = tramp.ReadEncodedPointer();
+ if (!instance) {
+ continue;
+ }
+
+ if (instance.value() != reinterpret_cast<uintptr_t>(this)) {
+ // tramp does not belong to this interceptor instance.
+ continue;
+ }
+
+ auto clearInstance = MakeScopeExit([&tramp]() -> void {
+ // Clear the instance pointer so that no future instances with the same
+ // |this| pointer will attempt to reset its hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+ });
+
+ // Now we read the pointer to the intercepted function.
+ Maybe<uintptr_t> interceptedFn = tramp.ReadEncodedPointer();
+ if (!interceptedFn) {
+ continue;
+ }
+
+ WritableTargetFunction<MMPolicyT> origBytes(
+ this->mVMPolicy, interceptedFn.value(), nBytes);
+ if (!origBytes) {
+ continue;
+ }
+
+#if defined(_M_IX86) || defined(_M_X64)
+
+ Maybe<uint8_t> maybeOpcode1 = origBytes.ReadByte();
+ if (!maybeOpcode1) {
+ continue;
+ }
+
+ uint8_t opcode1 = maybeOpcode1.value();
+
+# if defined(_M_IX86)
+ // Ensure the JMP from CreateTrampoline is where we expect it to be.
+ MOZ_ASSERT(opcode1 == 0xE9);
+ if (opcode1 != 0xE9) {
+ continue;
+ }
+
+ intptr_t startOfTrampInstructions =
+ static_cast<intptr_t>(tramp.GetCurrentRemoteAddress());
+
+ origBytes.WriteDisp32(startOfTrampInstructions);
+ if (!origBytes) {
+ continue;
+ }
+
+ origBytes.Commit();
+# elif defined(_M_X64)
+ // Note: At the moment we clear 13-byte patches by replacing the jump to
+ // the patched function by a jump to the stub code. The original
+ // bytes of the original function are *not* restored. This implies
+ // that the stub code outlives our cleaning, so unwind information
+ // remains useful and must not be removed here.
+ if (opcode1 == 0x49) {
+ if (!Clear13BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (opcode1 == 0xB8) {
+ if (!Clear10BytePatch(origBytes)) {
+ continue;
+ }
+ } else if (opcode1 == 0x48) {
+ // The original function was just a different trampoline
+ if (!ClearTrampolinePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+# endif
+
+#elif defined(_M_ARM64)
+
+ // Ensure that we see the instruction that we expect
+ Maybe<uint32_t> inst1 = origBytes.ReadLong();
+ if (!inst1) {
+ continue;
+ }
+
+ if (inst1.value() == this->kLdrX16Plus8) {
+ if (!Clear16BytePatch(origBytes, tramp.GetCurrentRemoteAddress())) {
+ continue;
+ }
+ } else if (arm64::IsUnconditionalBranchImm(inst1.value())) {
+ if (!Clear4BytePatch(inst1.value(), origBytes)) {
+ continue;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ continue;
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+ }
+
+ this->mVMPolicy.Clear();
+ }
+
+#if defined(_M_X64)
+ bool Clear13BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint8_t> maybeOpcode2 = aOrigBytes.ReadByte();
+ if (!maybeOpcode2) {
+ return false;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xBB) {
+ return false;
+ }
+
+ aOrigBytes.WritePointer(aResetToAddress);
+ if (!aOrigBytes) {
+ return false;
+ }
+
+ return aOrigBytes.Commit();
+ }
+
+ bool ClearTrampolinePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aPtrToResetToAddress) {
+ // The target of the trampoline we replaced is stored at
+ // aPtrToResetToAddress. We simply put it back where we got it from.
+ Maybe<uint8_t> maybeOpcode2 = aOrigBytes.ReadByte();
+ if (!maybeOpcode2) {
+ return false;
+ }
+
+ uint8_t opcode2 = maybeOpcode2.value();
+ if (opcode2 != 0xB8) {
+ return false;
+ }
+
+ auto oldPtr = *(reinterpret_cast<const uintptr_t*>(aPtrToResetToAddress));
+
+ aOrigBytes.WritePointer(oldPtr);
+ if (!aOrigBytes) {
+ return false;
+ }
+
+ return aOrigBytes.Commit();
+ }
+
+ bool Clear10BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ Maybe<uint32_t> maybePtr32 = aOrigBytes.ReadLong();
+ if (!maybePtr32) {
+ return false;
+ }
+
+ uint32_t ptr32 = maybePtr32.value();
+ // We expect the high bit to be clear
+ if (ptr32 & 0x80000000) {
+ return false;
+ }
+
+ uintptr_t trampPtr = ptr32;
+
+ // trampPtr points to an intermediate trampoline that contains a 13-byte
+ // patch. We back up by sizeof(uintptr_t) so that we can access the pointer
+ // to the stub trampoline.
+ WritableTargetFunction<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), 13 + sizeof(uintptr_t));
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint8_t> maybeOpcode1 = writableIntermediate.ReadByte();
+ if (!maybeOpcode1) {
+ return false;
+ }
+
+ // We expect this opcode to be the beginning of our normal mov r11, ptr
+ // patch sequence.
+ uint8_t opcode1 = maybeOpcode1.value();
+ if (opcode1 != 0x49) {
+ return false;
+ }
+
+ // Now we can just delegate the rest to our normal 13-byte patch clearing.
+ return Clear13BytePatch(writableIntermediate, stubTramp.value());
+ }
+#endif // defined(_M_X64)
+
+#if defined(_M_ARM64)
+ bool Clear4BytePatch(const uint32_t aBranchImm,
+ WritableTargetFunction<MMPolicyT>& aOrigBytes) {
+ MOZ_ASSERT(arm64::IsUnconditionalBranchImm(aBranchImm));
+
+ arm64::LoadOrBranch decoded = arm64::BUncondImmDecode(
+ aOrigBytes.GetCurrentAddress() - sizeof(uint32_t), aBranchImm);
+
+ uintptr_t trampPtr = decoded.mAbsAddress;
+
+ // trampPtr points to an intermediate trampoline that contains a veneer.
+ // We back up by sizeof(uintptr_t) so that we can access the pointer to the
+ // stub trampoline.
+
+ // We want trampLen to be the size of the veneer, plus one pointer (since
+ // we are backing up trampPtr by one pointer)
+ size_t trampLen = 16 + sizeof(uintptr_t);
+
+ WritableTargetFunction<MMPolicyT> writableIntermediate(
+ this->mVMPolicy, trampPtr - sizeof(uintptr_t), trampLen);
+ if (!writableIntermediate) {
+ return false;
+ }
+
+ Maybe<uintptr_t> stubTramp = writableIntermediate.ReadEncodedPtr();
+ if (!stubTramp || !stubTramp.value()) {
+ return false;
+ }
+
+ Maybe<uint32_t> inst1 = writableIntermediate.ReadLong();
+ if (!inst1 || inst1.value() != this->kLdrX16Plus8) {
+ return false;
+ }
+
+ return Clear16BytePatch(writableIntermediate, stubTramp.value());
+ }
+
+ bool Clear16BytePatch(WritableTargetFunction<MMPolicyT>& aOrigBytes,
+ const uintptr_t aResetToAddress) {
+ Maybe<uint32_t> inst2 = aOrigBytes.ReadLong();
+ if (!inst2) {
+ return false;
+ }
+
+ if (inst2.value() != arm64::BuildUnconditionalBranchToRegister(16)) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized patch!");
+ return false;
+ }
+
+ // Clobber the pointer to our hook function with a pointer to the
+ // start of the trampoline.
+ aOrigBytes.WritePointer(aResetToAddress);
+ aOrigBytes.Commit();
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+ void Init(DetourFlags aFlags = DetourFlags::eDefault) {
+ if (Initialized()) {
+ return;
+ }
+
+#if defined(_M_X64)
+ if (aFlags & DetourFlags::eTestOnlyForceShortPatch) {
+ aFlags |= DetourFlags::eEnable10BytePatch;
+ }
+#endif // defined(_M_X64)
+
+ mFlags = Some(aFlags);
+ }
+
+ bool Initialized() const { return mFlags.isSome(); }
+
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ ReadOnlyTargetFunction<MMPolicyT> target(
+ (mFlags.value() & DetourFlags::eDontResolveRedirection)
+ ? ReadOnlyTargetFunction<MMPolicyT>(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(aTargetFn))
+ : this->ResolveRedirectedAddress(aTargetFn));
+
+ TrampPoolT* trampPool = nullptr;
+
+#if defined(_M_ARM64)
+ // ARM64 uses two passes to build its trampoline. The first pass uses a
+ // null tramp to determine how many bytes are needed. Once that is known,
+ // CreateTrampoline calls itself recursively with a "real" tramp.
+ Trampoline<MMPolicyT> tramp(nullptr);
+#else
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve();
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return false;
+ }
+
+ trampPool = maybeTrampPool.ptr();
+
+ Maybe<Trampoline<MMPolicyT>> maybeTramp(trampPool->GetNextTrampoline());
+ if (!maybeTramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_NEXT_TRAMPOLINE_ERROR);
+ return false;
+ }
+
+ Trampoline<MMPolicyT> tramp(std::move(maybeTramp.ref()));
+#endif
+
+ CreateTrampoline(target, trampPool, tramp, aHookDest, aOrigFunc);
+ if (!*aOrigFunc) {
+ return false;
+ }
+
+ return true;
+ }
+
+ private:
+ /**
+ * This function returns a maximum distance that can be reached by a single
+ * unconditional jump instruction. This is dependent on the processor ISA.
+ * Note that this distance is *exclusive* when added to the pivot, so the
+ * distance returned by this function is actually
+ * (maximum_absolute_offset + 1).
+ */
+ static uint32_t GetDefaultPivotDistance() {
+#if defined(_M_ARM64)
+ // Immediate unconditional branch allows for +/- 128MB
+ return 0x08000000U;
+#elif defined(_M_IX86) || defined(_M_X64)
+ // For these ISAs, our distance will assume the use of an unconditional jmp
+ // with a 32-bit signed displacement.
+ return 0x80000000U;
+#else
+# error "Not defined for this processor arch"
+#endif
+ }
+
+ /**
+ * If we're reserving trampoline space for a specific module, we base the
+ * pivot off of the median address of the module's .text section. While this
+ * may not be precise, it should be accurate enough for our purposes: To
+ * ensure that the trampoline space is reachable by any executable code in the
+ * module.
+ */
+ Maybe<TrampPoolT> ReserveForModule(HMODULE aModule) {
+ nt::PEHeaders moduleHeaders(aModule);
+ if (!moduleHeaders) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_PE_ERROR);
+ return Nothing();
+ }
+
+ Maybe<Span<const uint8_t>> textSectionInfo =
+ moduleHeaders.GetTextSectionInfo();
+ if (!textSectionInfo) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_TEXT_ERROR);
+ return Nothing();
+ }
+
+ const uint8_t* median = textSectionInfo.value().data() +
+ (textSectionInfo.value().LengthBytes() / 2);
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(
+ reinterpret_cast<uintptr_t>(median), GetDefaultPivotDistance());
+ if (!maybeTrampPool) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_RESERVE_FOR_MODULE_RESERVE_ERROR);
+ }
+ return maybeTrampPool;
+ }
+
+ Maybe<TrampPoolT> DoReserve(HMODULE aModule = nullptr) {
+ if (aModule) {
+ return ReserveForModule(aModule);
+ }
+
+ uintptr_t pivot = 0;
+ uint32_t distance = 0;
+
+#if defined(_M_X64)
+ if (mFlags.value() & DetourFlags::eEnable10BytePatch) {
+ // We must stay below the 2GB mark because a 10-byte patch uses movsxd
+ // (ie, sign extension) to expand the pointer to 64-bits, so bit 31 of any
+ // pointers into the reserved region must be 0.
+ pivot = 0x40000000U;
+ distance = 0x40000000U;
+ }
+#endif // defined(_M_X64)
+
+ Maybe<TrampPoolT> maybeTrampPool = this->mVMPolicy.Reserve(pivot, distance);
+#if defined(NIGHTLY_BUILD)
+ if (!maybeTrampPool && this->GetLastDetourError().isNothing()) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_DO_RESERVE_ERROR);
+ }
+#endif // defined(NIGHTLY_BUILD)
+ return maybeTrampPool;
+ }
+
+ protected:
+#if !defined(_M_ARM64)
+
+ const static int kPageSize = 4096;
+
+ // rex bits
+ static const BYTE kMaskHighNibble = 0xF0;
+ static const BYTE kRexOpcode = 0x40;
+ static const BYTE kMaskRexW = 0x08;
+ static const BYTE kMaskRexR = 0x04;
+ static const BYTE kMaskRexX = 0x02;
+ static const BYTE kMaskRexB = 0x01;
+
+ // mod r/m bits
+ static const BYTE kRegFieldShift = 3;
+ static const BYTE kMaskMod = 0xC0;
+ static const BYTE kMaskReg = 0x38;
+ static const BYTE kMaskRm = 0x07;
+ static const BYTE kRmNeedSib = 0x04;
+ static const BYTE kModReg = 0xC0;
+ static const BYTE kModDisp32 = 0x80;
+ static const BYTE kModDisp8 = 0x40;
+ static const BYTE kModNoRegDisp = 0x00;
+ static const BYTE kRmNoRegDispDisp32 = 0x05;
+
+ // sib bits
+ static const BYTE kMaskSibScale = 0xC0;
+ static const BYTE kMaskSibIndex = 0x38;
+ static const BYTE kMaskSibBase = 0x07;
+ static const BYTE kSibBaseEbp = 0x05;
+
+ // Register bit IDs.
+ static const BYTE kRegAx = 0x0;
+ static const BYTE kRegCx = 0x1;
+ static const BYTE kRegDx = 0x2;
+ static const BYTE kRegBx = 0x3;
+ static const BYTE kRegSp = 0x4;
+ static const BYTE kRegBp = 0x5;
+ static const BYTE kRegSi = 0x6;
+ static const BYTE kRegDi = 0x7;
+
+ // Special ModR/M codes. These indicate operands that cannot be simply
+ // memcpy-ed.
+ // Operand is a 64-bit RIP-relative address.
+ static const int kModOperand64 = -2;
+ // Operand is not yet handled by our trampoline.
+ static const int kModUnknown = -1;
+
+ /**
+ * Returns the number of bytes taken by the ModR/M byte, SIB (if present)
+ * and the instruction's operand. In special cases, the special MODRM codes
+ * above are returned.
+ * aModRm points to the ModR/M byte of the instruction.
+ * On return, aSubOpcode (if present) is filled with the subopcode/register
+ * code found in the ModR/M byte.
+ */
+ int CountModRmSib(const ReadOnlyTargetFunction<MMPolicyT>& aModRm,
+ BYTE* aSubOpcode = nullptr) {
+ int numBytes = 1; // Start with 1 for mod r/m byte itself
+ switch (*aModRm & kMaskMod) {
+ case kModReg:
+ return numBytes;
+ case kModDisp8:
+ numBytes += 1;
+ break;
+ case kModDisp32:
+ numBytes += 4;
+ break;
+ case kModNoRegDisp:
+ if ((*aModRm & kMaskRm) == kRmNoRegDispDisp32) {
+# if defined(_M_X64)
+ if (aSubOpcode) {
+ *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
+ }
+ return kModOperand64;
+# else
+ // On IA-32, all ModR/M instruction modes address memory relative to 0
+ numBytes += 4;
+# endif
+ } else if (((*aModRm & kMaskRm) == kRmNeedSib &&
+ (*(aModRm + 1) & kMaskSibBase) == kSibBaseEbp)) {
+ numBytes += 4;
+ }
+ break;
+ default:
+ // This should not be reachable
+ MOZ_ASSERT_UNREACHABLE("Impossible value for modr/m byte mod bits");
+ return kModUnknown;
+ }
+ if ((*aModRm & kMaskRm) == kRmNeedSib) {
+ // SIB byte
+ numBytes += 1;
+ }
+ if (aSubOpcode) {
+ *aSubOpcode = (*aModRm & kMaskReg) >> kRegFieldShift;
+ }
+ return numBytes;
+ }
+
+# if defined(_M_X64)
+ enum class JumpType{Je, Jne, Jae, Jmp, Call};
+
+ static bool GenerateJump(Trampoline<MMPolicyT>& aTramp,
+ uintptr_t aAbsTargetAddress, const JumpType aType) {
+ // Near call, absolute indirect, address given in r/m32
+ if (aType == JumpType::Call) {
+ // CALL [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x15);
+ // The offset to jump destination -- 2 bytes after the current position.
+ aTramp.WriteInteger(2);
+ aTramp.WriteByte(0xeb); // JMP + 8 (jump over target address)
+ aTramp.WriteByte(8);
+ aTramp.WritePointer(aAbsTargetAddress);
+ return !!aTramp;
+ }
+
+ // Write an opposite conditional jump because the destination branches
+ // are swapped.
+ if (aType == JumpType::Je) {
+ // JNE RIP+14
+ aTramp.WriteByte(0x75);
+ aTramp.WriteByte(14);
+ } else if (aType == JumpType::Jne) {
+ // JE RIP+14
+ aTramp.WriteByte(0x74);
+ aTramp.WriteByte(14);
+ } else if (aType == JumpType::Jae) {
+ // JB RIP+14
+ aTramp.WriteByte(0x72);
+ aTramp.WriteByte(14);
+ }
+
+ // Near jmp, absolute indirect, address given in r/m32
+ // JMP [RIP+0]
+ aTramp.WriteByte(0xff);
+ aTramp.WriteByte(0x25);
+ // The offset to jump destination is 0
+ aTramp.WriteInteger(0);
+ aTramp.WritePointer(aAbsTargetAddress);
+
+ return !!aTramp;
+ }
+# endif
+
+ enum ePrefixGroupBits{eNoPrefixes = 0, ePrefixGroup1 = (1 << 0),
+ ePrefixGroup2 = (1 << 1), ePrefixGroup3 = (1 << 2),
+ ePrefixGroup4 = (1 << 3)};
+
+ int CountPrefixBytes(const ReadOnlyTargetFunction<MMPolicyT>& aBytes,
+ unsigned char* aOutGroupBits) {
+ unsigned char& groupBits = *aOutGroupBits;
+ groupBits = eNoPrefixes;
+ int index = 0;
+ while (true) {
+ switch (aBytes[index]) {
+ // Group 1
+ case 0xF0: // LOCK
+ case 0xF2: // REPNZ
+ case 0xF3: // REP / REPZ
+ if (groupBits & ePrefixGroup1) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup1;
+ ++index;
+ break;
+
+ // Group 2
+ case 0x2E: // CS override / branch not taken
+ case 0x36: // SS override
+ case 0x3E: // DS override / branch taken
+ case 0x64: // FS override
+ case 0x65: // GS override
+ if (groupBits & ePrefixGroup2) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup2;
+ ++index;
+ break;
+
+ // Group 3
+ case 0x66: // operand size override
+ if (groupBits & ePrefixGroup3) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup3;
+ ++index;
+ break;
+
+ // Group 4
+ case 0x67: // Address size override
+ if (groupBits & ePrefixGroup4) {
+ return -1;
+ }
+ groupBits |= ePrefixGroup4;
+ ++index;
+ break;
+
+ default:
+ return index;
+ }
+ }
+ }
+
+ // Return a ModR/M byte made from the 2 Mod bits, the register used for the
+ // reg bits and the register used for the R/M bits.
+ BYTE BuildModRmByte(BYTE aModBits, BYTE aReg, BYTE aRm) {
+ MOZ_ASSERT((aRm & kMaskRm) == aRm);
+ MOZ_ASSERT((aModBits & kMaskMod) == aModBits);
+ MOZ_ASSERT(((aReg << kRegFieldShift) & kMaskReg) ==
+ (aReg << kRegFieldShift));
+ return aModBits | (aReg << kRegFieldShift) | aRm;
+ }
+
+#endif // !defined(_M_ARM64)
+
+ // If originalFn is a recognized trampoline then patch it to call aDest,
+ // set *aTramp and *aOutTramp to that trampoline's target and return true.
+ bool PatchIfTargetIsRecognizedTrampoline(
+ Trampoline<MMPolicyT>& aTramp,
+ ReadOnlyTargetFunction<MMPolicyT>& aOriginalFn, intptr_t aDest,
+ void** aOutTramp) {
+#if defined(_M_X64)
+ // Variation 1:
+ // 48 b8 imm64 mov rax, imm64
+ // ff e0 jmp rax
+ //
+ // Variation 2:
+ // 48 b8 imm64 mov rax, imm64
+ // 50 push rax
+ // c3 ret
+ if ((aOriginalFn[0] == 0x48) && (aOriginalFn[1] == 0xB8) &&
+ ((aOriginalFn[10] == 0xFF && aOriginalFn[11] == 0xE0) ||
+ (aOriginalFn[10] == 0x50 && aOriginalFn[11] == 0xC3))) {
+ uintptr_t originalTarget =
+ (aOriginalFn + 2).template ChasePointer<uintptr_t>();
+
+ // Skip the first two bytes (48 b8) so that we can overwrite the imm64
+ WritableTargetFunction<MMPolicyT> target(aOriginalFn.Promote(8, 2));
+ if (!target) {
+ return false;
+ }
+
+ // Write the new JMP target address.
+ target.WritePointer(aDest);
+ if (!target.Commit()) {
+ return false;
+ }
+
+ // Store the old target address so we can restore it when we're cleared
+ aTramp.WritePointer(originalTarget);
+ if (!aTramp) {
+ return false;
+ }
+
+ *aOutTramp = reinterpret_cast<void*>(originalTarget);
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ return false;
+ }
+
+#if defined(_M_ARM64)
+ bool Apply4BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ MOZ_ASSERT(aTrampPool);
+ if (!aTrampPool) {
+ return false;
+ }
+
+ uintptr_t hookDest = arm64::MakeVeneer(*aTrampPool, aTrampPtr, aDest);
+ if (!hookDest) {
+ return false;
+ }
+
+ Maybe<uint32_t> branchImm = arm64::BuildUnconditionalBranchImm(
+ target.GetCurrentAddress(), hookDest);
+ if (!branchImm) {
+ return false;
+ }
+
+ target.WriteLong(branchImm.value());
+
+ return true;
+ }
+#endif // defined(_M_ARM64)
+
+#if defined(_M_X64)
+ bool Apply10BytePatch(TrampPoolT* aTrampPool, void* aTrampPtr,
+ WritableTargetFunction<MMPolicyT>& target,
+ intptr_t aDest) {
+ // Note: Even if the target function is also below 2GB, we still use an
+ // intermediary trampoline so that we consistently have a 64-bit pointer
+ // that we can use to reset the trampoline upon interceptor shutdown.
+ Maybe<Trampoline<MMPolicyT>> maybeCallTramp(
+ aTrampPool->GetNextTrampoline());
+ if (!maybeCallTramp) {
+ return false;
+ }
+
+ Trampoline<MMPolicyT> callTramp(std::move(maybeCallTramp.ref()));
+
+ // Write a null instance so that Clear() does not consider this tramp to
+ // be a normal tramp to be torn down.
+ callTramp.WriteEncodedPointer(nullptr);
+ // Use the second pointer slot to store a pointer to the primary tramp
+ callTramp.WriteEncodedPointer(aTrampPtr);
+ callTramp.StartExecutableCode();
+
+ // mov r11, address
+ callTramp.WriteByte(0x49);
+ callTramp.WriteByte(0xbb);
+ callTramp.WritePointer(aDest);
+
+ // jmp r11
+ callTramp.WriteByte(0x41);
+ callTramp.WriteByte(0xff);
+ callTramp.WriteByte(0xe3);
+
+ void* callTrampStart = callTramp.EndExecutableCode();
+ if (!callTrampStart) {
+ return false;
+ }
+
+ target.WriteByte(0xB8); // MOV EAX, IMM32
+
+ // Assert that the topmost 33 bits are 0
+ MOZ_ASSERT(
+ !(reinterpret_cast<uintptr_t>(callTrampStart) & (~0x7FFFFFFFULL)));
+
+ target.WriteLong(static_cast<uint32_t>(
+ reinterpret_cast<uintptr_t>(callTrampStart) & 0x7FFFFFFFU));
+ target.WriteByte(0x48); // REX.W
+ target.WriteByte(0x63); // MOVSXD r64, r/m32
+ // dest: rax, src: eax
+ target.WriteByte(BuildModRmByte(kModReg, kRegAx, kRegAx));
+ target.WriteByte(0xFF); // JMP /4
+ target.WriteByte(BuildModRmByte(kModReg, 4, kRegAx)); // rax
+
+ return true;
+ }
+#endif // defined(_M_X64)
+
+ void CreateTrampoline(ReadOnlyTargetFunction<MMPolicyT>& origBytes,
+ TrampPoolT* aTrampPool, Trampoline<MMPolicyT>& aTramp,
+ intptr_t aDest, void** aOutTramp) {
+ *aOutTramp = nullptr;
+
+ Trampoline<MMPolicyT>& tramp = aTramp;
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_INVALID_TRAMPOLINE);
+ return;
+ }
+
+ // The beginning of the trampoline contains two pointer-width slots:
+ // [0]: |this|, so that we know whether the trampoline belongs to us;
+ // [1]: Pointer to original function, so that we can reset the hooked
+ // function to its original behavior upon destruction. In rare cases
+ // where the function was already a different trampoline, this is
+ // just a pointer to that trampoline's target address.
+ tramp.WriteEncodedPointer(this);
+ if (!tramp) {
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_WRITE_POINTER_ERROR);
+ return;
+ }
+
+ auto clearInstanceOnFailure = MakeScopeExit([this, aOutTramp, &tramp,
+ &origBytes]() -> void {
+ // *aOutTramp is not set until CreateTrampoline has completed
+ // successfully, so we can use that to check for success.
+ if (*aOutTramp) {
+ return;
+ }
+
+ // Clear the instance pointer so that we don't try to reset a
+ // nonexistent hook.
+ tramp.Rewind();
+ tramp.WriteEncodedPointer(nullptr);
+
+#if defined(NIGHTLY_BUILD)
+ origBytes.Rewind();
+ this->SetLastDetourError(
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR);
+ DetourError& lastError = *this->mVMPolicy.mLastError;
+ size_t bytesToCapture = std::min(
+ ArrayLength(lastError.mOrigBytes),
+ static_cast<size_t>(PrimitiveT::GetWorstCaseRequiredBytesToPatch()));
+# if defined(_M_ARM64)
+ size_t numInstructionsToCapture = bytesToCapture / sizeof(uint32_t);
+ auto origBytesDst = reinterpret_cast<uint32_t*>(lastError.mOrigBytes);
+ for (size_t i = 0; i < numInstructionsToCapture; ++i) {
+ origBytesDst[i] = origBytes.ReadNextInstruction();
+ }
+# else
+ for (size_t i = 0; i < bytesToCapture; ++i) {
+ lastError.mOrigBytes[i] = origBytes[i];
+ }
+# endif // defined(_M_ARM64)
+#else
+ // Silence -Wunused-lambda-capture in non-Nightly.
+ Unused << this;
+ Unused << origBytes;
+#endif // defined(NIGHTLY_BUILD)
+ });
+
+ tramp.WritePointer(origBytes.AsEncodedPtr());
+ if (!tramp) {
+ return;
+ }
+
+ if (PatchIfTargetIsRecognizedTrampoline(tramp, origBytes, aDest,
+ aOutTramp)) {
+ return;
+ }
+
+ tramp.StartExecutableCode();
+
+ constexpr uint32_t kWorstCaseBytesRequired =
+ PrimitiveT::GetWorstCaseRequiredBytesToPatch();
+
+#if defined(_M_IX86)
+ int pJmp32 = -1;
+ while (origBytes.GetOffset() < kWorstCaseBytesRequired) {
+ // Understand some simple instructions that might be found in a
+ // prologue; we might need to extend this as necessary.
+ //
+ // Note! If we ever need to understand jump instructions, we'll
+ // need to rewrite the displacement argument.
+ unsigned char prefixGroups;
+ int numPrefixBytes = CountPrefixBytes(origBytes, &prefixGroups);
+ if (numPrefixBytes < 0 ||
+ (prefixGroups & (ePrefixGroup3 | ePrefixGroup4))) {
+ // Either the prefix sequence was bad, or there are prefixes that
+ // we don't currently support (groups 3 and 4)
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+
+ origBytes += numPrefixBytes;
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ ++origBytes;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0x0f &&
+ (origBytes[1] == 0x10 || origBytes[1] == 0x11)) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ origBytes += 2;
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ origBytes += len;
+ } else if (*origBytes == 0xA1) {
+ // MOV eax, [seg:offset]
+ origBytes += 5;
+ } else if (*origBytes == 0xB8) {
+ // MOV 0xB8: http://ref.x86asm.net/coder32.html#xB8
+ origBytes += 5;
+ } else if (*origBytes == 0x33 && (origBytes[1] & kMaskMod) == kModReg) {
+ // XOR r32, r32
+ origBytes += 2;
+ } else if ((*origBytes & 0xf8) == 0x40) {
+ // INC r32
+ origBytes += 1;
+ } else if (*origBytes == 0x83) {
+ uint8_t mod = static_cast<uint8_t>(origBytes[1]) & kMaskMod;
+ uint8_t rm = static_cast<uint8_t>(origBytes[1]) & kMaskRm;
+ if (mod == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ origBytes += 3;
+ } else if (mod == kModDisp8 && rm != kRmNeedSib) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP [r+disp8], imm8
+ origBytes += 4;
+ } else {
+ // bail
+ MOZ_ASSERT_UNREACHABLE("Unrecognized bit opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x68) {
+ // PUSH with 4-byte operand
+ origBytes += 5;
+ } else if ((*origBytes & 0xf0) == 0x50) {
+ // 1-byte PUSH/POP
+ ++origBytes;
+ } else if (*origBytes == 0x6A) {
+ // PUSH imm8
+ origBytes += 2;
+ } else if (*origBytes == 0xe9) {
+ pJmp32 = origBytes.GetOffset();
+ // jmp 32bit offset
+ origBytes += 5;
+ } else if (*origBytes == 0xff && origBytes[1] == 0x25) {
+ // jmp [disp32]
+ origBytes += 6;
+ } else if (*origBytes == 0xc2) {
+ // ret imm16. We can't handle this but it happens. We don't ASSERT but
+ // we do fail to hook.
+# if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING("Cannot hook method -- RET opcode found");
+# endif
+ return;
+ } else {
+ // printf ("Unknown x86 instruction byte 0x%02x, aborting trampoline\n",
+ // *origBytes);
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ }
+
+ // The trampoline is a copy of the instructions that we just traced,
+ // followed by a jump that we add below.
+ tramp.CopyFrom(origBytes.GetBaseAddress(), origBytes.GetOffset());
+ if (!tramp) {
+ return;
+ }
+#elif defined(_M_X64)
+ bool foundJmp = false;
+ // |use10BytePatch| should always default to |false| in production. It is
+ // not set to true unless we detect that a 10-byte patch is necessary.
+ // OTOH, for testing purposes, if we want to force a 10-byte patch, we
+ // always initialize |use10BytePatch| to |true|.
+ bool use10BytePatch =
+ (mFlags.value() & DetourFlags::eTestOnlyForceShortPatch) ==
+ DetourFlags::eTestOnlyForceShortPatch;
+ const uint32_t bytesRequired =
+ use10BytePatch ? 10 : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequired) {
+ // If we found JMP 32bit offset, we require that the next bytes must
+ // be NOP or INT3. There is no reason to copy them.
+ // TODO: This used to trigger for Je as well. Now that I allow
+ // instructions after CALL and JE, I don't think I need that.
+ // The only real value of this condition is that if code follows a JMP
+ // then its _probably_ the target of a JMP somewhere else and we
+ // will be overwriting it, which would be tragic. This seems
+ // highly unlikely.
+ if (foundJmp) {
+ if (*origBytes == 0x90 || *origBytes == 0xcc) {
+ ++origBytes;
+ continue;
+ }
+
+ // If our trampoline space is located in the lowest 2GB, we can do a ten
+ // byte patch instead of a thirteen byte patch.
+ if (aTrampPool && aTrampPool->IsInLowest2GB() &&
+ origBytes.GetOffset() >= 10) {
+ use10BytePatch = true;
+ break;
+ }
+
+ MOZ_ASSERT_UNREACHABLE("Opcode sequence includes commands after JMP");
+ return;
+ }
+ if (*origBytes == 0x0f) {
+ COPY_CODES(1);
+ if (*origBytes == 0x1f) {
+ // nop (multibyte)
+ COPY_CODES(1);
+ if ((*origBytes & 0xc0) == 0x40 && (*origBytes & 0x7) == 0x04) {
+ COPY_CODES(3);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x05) {
+ // syscall
+ COPY_CODES(1);
+ } else if (*origBytes == 0x10 || *origBytes == 0x11) {
+ // SSE: movups xmm, xmm/m128
+ // movups xmm/m128, xmm
+ COPY_CODES(1);
+ int nModRmSibBytes = CountModRmSib(origBytes);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ } else {
+ COPY_CODES(nModRmSibBytes);
+ }
+ } else if (*origBytes >= 0x83 && *origBytes <= 0x85) {
+ // 0f 83 cd JAE rel32
+ // 0f 84 cd JE rel32
+ // 0f 85 cd JNE rel32
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x83];
+ ++origBytes;
+ --tramp; // overwrite the 0x0f we copied above
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ jumpType)) {
+ return;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various 32-bit MOVs
+ COPY_CODES(1);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else if (*origBytes == 0x40 || *origBytes == 0x41) {
+ // Plain REX or REX.B
+ COPY_CODES(1);
+ if ((*origBytes & 0xf0) == 0x50) {
+ // push/pop with Rx register
+ COPY_CODES(1);
+ } else if (*origBytes >= 0xb8 && *origBytes <= 0xbf) {
+ // mov r32, imm32
+ COPY_CODES(5);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x44) {
+ // REX.R
+ COPY_CODES(1);
+
+ // TODO: Combine with the "0x89" case below in the REX.W section
+ if (*origBytes == 0x89) {
+ // mov r/m32, r32
+ COPY_CODES(1);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x45) {
+ // REX.R & REX.B
+ COPY_CODES(1);
+
+ if (*origBytes == 0x33) {
+ // xor r32, r32
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfa) == 0x48) {
+ // REX.W | REX.WR | REX.WRB | REX.WB
+ COPY_CODES(1);
+
+ if (*origBytes == 0x81 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, dword
+ COPY_CODES(6);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0xe8) {
+ // sub r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 &&
+ (origBytes[1] & (kMaskMod | kMaskReg)) == kModReg) {
+ // add r, byte
+ COPY_CODES(3);
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0x2b && (origBytes[1] & kMaskMod) == kModReg) {
+ // sub r64, r64
+ COPY_CODES(2);
+ } else if (*origBytes == 0x85) {
+ // 85 /r => TEST r/m32, r32
+ if ((origBytes[1] & 0xc0) == 0xc0) {
+ COPY_CODES(2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if ((*origBytes & 0xfd) == 0x89) {
+ // MOV r/m64, r64 | MOV r64, r/m64
+ BYTE reg;
+ int len = CountModRmSib(origBytes + 1, &reg);
+ if (len < 0) {
+ MOZ_ASSERT(len == kModOperand64);
+ if (len != kModOperand64) {
+ return;
+ }
+ origBytes += 2; // skip the MOV and MOD R/M bytes
+
+ // The instruction MOVs 64-bit data from a RIP-relative memory
+ // address (determined with a 32-bit offset from RIP) into a
+ // 64-bit register.
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+
+ if (reg == kRegAx) {
+ // Destination is RAX. Encode instruction as MOVABS with a
+ // 64-bit absolute address as its immediate operand.
+ tramp.WriteByte(0xa1);
+ tramp.WritePointer(absAddr);
+ } else {
+ // The MOV must be done in two steps. First, we MOVABS the
+ // absolute 64-bit address into our target register.
+ // Then, we MOV from that address into the register
+ // using register-indirect addressing.
+ tramp.WriteByte(0xb8 + reg);
+ tramp.WritePointer(absAddr);
+ tramp.WriteByte(0x48);
+ tramp.WriteByte(0x8b);
+ tramp.WriteByte(BuildModRmByte(kModNoRegDisp, reg, reg));
+ }
+ } else {
+ COPY_CODES(len + 1);
+ }
+ } else if ((*origBytes & 0xf8) == 0xb8) {
+ // MOV r64, imm64
+ COPY_CODES(9);
+ } else if (*origBytes == 0xc7) {
+ // MOV r/m64, imm32
+ if (origBytes[1] == 0x44) {
+ // MOV [r64+disp8], imm32
+ // ModR/W + SIB + disp8 + imm32
+ COPY_CODES(8);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ // JMP /4
+ if ((origBytes[1] & 0xc0) == 0x0 && (origBytes[1] & 0x07) == 0x5) {
+ origBytes += 2;
+ --tramp; // overwrite the REX.W/REX.RW we copied above
+
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Jmp)) {
+ return;
+ }
+
+ foundJmp = true;
+ } else {
+ // not support yet!
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x8d) {
+ // LEA reg, addr
+ if ((origBytes[1] & kMaskMod) == 0x0 &&
+ (origBytes[1] & kMaskRm) == 0x5) {
+ // [rip+disp32]
+ // convert 32bit offset to 64bit direct and convert instruction
+ // to a simple 64-bit mov
+ BYTE reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ origBytes += 2;
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+ tramp.WriteByte(0xb8 + reg); // move
+ tramp.WritePointer(absAddr);
+ } else {
+ // Above we dealt with RIP-relative instructions. Any other
+ // operand form can simply be copied.
+ int len = CountModRmSib(origBytes + 1);
+ // We handled the kModOperand64 -- ie RIP-relative -- case above
+ MOZ_ASSERT(len > 0);
+ COPY_CODES(len + 1);
+ }
+ } else if (*origBytes == 0x63 && (origBytes[1] & kMaskMod) == kModReg) {
+ // movsxd r64, r32 (move + sign extend)
+ COPY_CODES(2);
+ } else {
+ // not support yet!
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x66) {
+ // operand override prefix
+ COPY_CODES(1);
+ // This is the same as the x86 version
+ if (*origBytes >= 0x88 && *origBytes <= 0x8B) {
+ // various MOVs
+ unsigned char b = origBytes[1];
+ if (((b & 0xc0) == 0xc0) ||
+ (((b & 0xc0) == 0x00) && ((b & 0x07) != 0x04) &&
+ ((b & 0x07) != 0x05))) {
+ // REG=r, R/M=r or REG=r, R/M=[r]
+ COPY_CODES(2);
+ } else if ((b & 0xc0) == 0x40) {
+ if ((b & 0x07) == 0x04) {
+ // REG=r, R/M=[SIB + disp8]
+ COPY_CODES(4);
+ } else {
+ // REG=r, R/M=[r + disp8]
+ COPY_CODES(3);
+ }
+ } else {
+ // complex MOV, bail
+ MOZ_ASSERT_UNREACHABLE("Unrecognized MOV opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x44 && origBytes[1] == 0x89) {
+ // mov word ptr [reg+disp8], reg
+ COPY_CODES(2);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ // no way to support this yet.
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ }
+ } else if ((*origBytes & 0xf0) == 0x50) {
+ // 1-byte push/pop
+ COPY_CODES(1);
+ } else if (*origBytes == 0x65) {
+ // GS prefix
+ //
+ // The entry of GetKeyState on Windows 10 has the following code.
+ // 65 48 8b 04 25 30 00 00 00 mov rax,qword ptr gs:[30h]
+ // (GS prefix + REX + MOV (0x8b) ...)
+ if (origBytes[1] == 0x48 &&
+ (origBytes[2] >= 0x88 && origBytes[2] <= 0x8b)) {
+ COPY_CODES(3);
+ int len = CountModRmSib(origBytes);
+ if (len < 0) {
+ // no way to support this yet.
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x80 && origBytes[1] == 0x3d) {
+ origBytes += 2;
+
+ // cmp byte ptr [rip-relative address], imm8
+ // We'll compute the absolute address and do the cmp in r11
+
+ // push r11 (to save the old value)
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x53);
+
+ uintptr_t absAddr = origBytes.ReadDisp32AsAbsolute();
+
+ // mov r11, absolute address
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0xbb);
+ tramp.WritePointer(absAddr);
+
+ // cmp byte ptr [r11],...
+ tramp.WriteByte(0x41);
+ tramp.WriteByte(0x80);
+ tramp.WriteByte(0x3b);
+
+ // ...imm8
+ COPY_CODES(1);
+
+ // pop r11 (doesn't affect the flags from the cmp)
+ tramp.WriteByte(0x49);
+ tramp.WriteByte(0x5b);
+ } else if (*origBytes == 0x90) {
+ // nop
+ COPY_CODES(1);
+ } else if ((*origBytes & 0xf8) == 0xb8) {
+ // MOV r32, imm32
+ COPY_CODES(5);
+ } else if (*origBytes == 0x33) {
+ // xor r32, r/m32
+ COPY_CODES(2);
+ } else if (*origBytes == 0xf6) {
+ // test r/m8, imm8 (used by ntdll on Windows 10 x64)
+ // (no flags are affected by near jmp since there is no task switch,
+ // so it is ok for a jmp to be written immediately after a test)
+ BYTE subOpcode = 0;
+ int nModRmSibBytes = CountModRmSib(origBytes + 1, &subOpcode);
+ if (nModRmSibBytes < 0 || subOpcode != 0) {
+ // Unsupported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(2 + nModRmSibBytes);
+ } else if (*origBytes == 0x85) {
+ // test r/m32, r32
+ int nModRmSibBytes = CountModRmSib(origBytes + 1);
+ if (nModRmSibBytes < 0) {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(1 + nModRmSibBytes);
+ } else if (*origBytes == 0xd1 && (origBytes[1] & kMaskMod) == kModReg) {
+ // bit shifts/rotates : (SA|SH|RO|RC)(R|L) r32
+ // (e.g. 0xd1 0xe0 is SAL, 0xd1 0xc8 is ROR)
+ COPY_CODES(2);
+ } else if (*origBytes == 0x83 && (origBytes[1] & kMaskMod) == kModReg) {
+ // ADD|OR|ADC|SBB|AND|SUB|XOR|CMP r, imm8
+ COPY_CODES(3);
+ } else if (*origBytes == 0xc3) {
+ // ret
+ COPY_CODES(1);
+ } else if (*origBytes == 0xcc) {
+ // int 3
+ COPY_CODES(1);
+ } else if (*origBytes == 0xe8 || *origBytes == 0xe9) {
+ // CALL (0xe8) or JMP (0xe9) 32bit offset
+ foundJmp = *origBytes == 0xe9;
+ ++origBytes;
+
+ if (!GenerateJump(tramp, origBytes.ReadDisp32AsAbsolute(),
+ foundJmp ? JumpType::Jmp : JumpType::Call)) {
+ return;
+ }
+ } else if (*origBytes >= 0x73 && *origBytes <= 0x75) {
+ // 73 cb JAE rel8
+ // 74 cb JE rel8
+ // 75 cb JNE rel8
+ const JumpType kJumpTypes[] = {JumpType::Jae, JumpType::Je,
+ JumpType::Jne};
+ auto jumpType = kJumpTypes[*origBytes - 0x73];
+ uint8_t offset = origBytes[1];
+
+ origBytes += 2;
+
+ if (!GenerateJump(tramp, origBytes.OffsetToAbsolute(offset),
+ jumpType)) {
+ return;
+ }
+ } else if (*origBytes == 0xff) {
+ uint8_t mod = origBytes[1] & kMaskMod;
+ uint8_t reg = (origBytes[1] & kMaskReg) >> kRegFieldShift;
+ uint8_t rm = origBytes[1] & kMaskRm;
+ if (mod == kModReg && (reg == 0 || reg == 1 || reg == 2 || reg == 6)) {
+ // INC|DEC|CALL|PUSH r64
+ COPY_CODES(2);
+ } else if (mod == kModNoRegDisp && reg == 2 &&
+ rm == kRmNoRegDispDisp32) {
+ // FF 15 CALL [disp32]
+ origBytes += 2;
+ if (!GenerateJump(tramp, origBytes.ChasePointerFromDisp(),
+ JumpType::Call)) {
+ return;
+ }
+ } else if (reg == 4) {
+ // FF /4 (Opcode=ff, REG=4): JMP r/m
+ if (mod == kModNoRegDisp && rm == kRmNoRegDispDisp32) {
+ // FF 25 JMP [disp32]
+ foundJmp = true;
+
+ origBytes += 2;
+
+ uintptr_t jmpDest = origBytes.ChasePointerFromDisp();
+
+ if (!GenerateJump(tramp, jmpDest, JumpType::Jmp)) {
+ return;
+ }
+ } else {
+ // JMP r/m except JMP [disp32]
+ int len = CountModRmSib(origBytes + 1);
+ if (len < 0) {
+ // RIP-relative not yet supported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+
+ COPY_CODES(len + 1);
+
+ foundJmp = true;
+ }
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ } else if (*origBytes == 0x83 && (origBytes[1] & 0xf8) == 0x60) {
+ // and [r+d], imm8
+ COPY_CODES(5);
+ } else if (*origBytes == 0xc6) {
+ // mov [r+d], imm8
+ int len = CountModRmSib(origBytes + 1);
+ if (len < 0) {
+ // RIP-relative not yet supported
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ COPY_CODES(len + 2);
+ } else {
+ MOZ_ASSERT_UNREACHABLE("Unrecognized opcode sequence");
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+
+ // The number of bytes required to facilitate a detour depends on the
+ // proximity of the hook function to the target function. In the best case,
+ // we can branch within +/- 128MB of the current location, requiring only
+ // 4 bytes. In the worst case, we need 16 bytes to load an absolute address
+ // into a register and then branch to it.
+ const uint32_t bytesRequiredFromDecode =
+ (mFlags.value() & DetourFlags::eTestOnlyForceShortPatch)
+ ? 4
+ : kWorstCaseBytesRequired;
+
+ while (origBytes.GetOffset() < bytesRequiredFromDecode) {
+ uintptr_t curPC = origBytes.GetCurrentAbsolute();
+ uint32_t curInst = origBytes.ReadNextInstruction();
+
+ Result<arm64::LoadOrBranch, arm64::PCRelCheckError> pcRelInfo =
+ arm64::CheckForPCRel(curPC, curInst);
+ if (pcRelInfo.isErr()) {
+ if (pcRelInfo.unwrapErr() ==
+ arm64::PCRelCheckError::InstructionNotPCRel) {
+ // Instruction is not PC-relative, we can just copy it verbatim
+ tramp.WriteInstruction(curInst);
+ continue;
+ }
+
+ // At this point we have determined that there is no decoder available
+ // for the current, PC-relative, instruction.
+
+ // origBytes is now pointing one instruction past the one that we
+ // need the trampoline to jump back to.
+ if (!origBytes.BackUpOneInstruction()) {
+ return;
+ }
+
+ break;
+ }
+
+ // We need to load an absolute address into a particular register
+ tramp.WriteLoadLiteral(pcRelInfo.inspect().mAbsAddress,
+ pcRelInfo.inspect().mDestReg);
+ }
+
+#else
+# error "Unknown processor type"
+#endif
+
+ if (origBytes.GetOffset() > 100) {
+ // printf ("Too big!");
+ return;
+ }
+
+#if defined(_M_IX86)
+ if (pJmp32 >= 0) {
+ // Jump directly to the original target of the jump instead of jumping to
+ // the original function. Adjust jump target displacement to jump location
+ // in the trampoline.
+ tramp.AdjustDisp32AtOffset(pJmp32 + 1, origBytes.GetBaseAddress());
+ } else {
+ tramp.WriteByte(0xe9); // jmp
+ tramp.WriteDisp32(origBytes.GetAddress());
+ }
+#elif defined(_M_X64)
+ // If we found a Jmp, we don't need to add another instruction. However,
+ // if we found a _conditional_ jump or a CALL (or no control operations
+ // at all) then we still need to run the rest of aOriginalFunction.
+ if (!foundJmp) {
+ if (!GenerateJump(tramp, origBytes.GetAddress(), JumpType::Jmp)) {
+ return;
+ }
+ }
+#elif defined(_M_ARM64)
+ // Let's find out how many bytes we have available to us for patching
+ uint32_t numBytesForPatching = tramp.GetCurrentExecutableCodeLen();
+
+ if (!numBytesForPatching) {
+ // There's nothing we can do
+ return;
+ }
+
+ if (tramp.IsNull()) {
+ // Recursive case
+ HMODULE targetModule = nullptr;
+
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!::GetModuleHandleExW(
+ GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS |
+ GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT,
+ reinterpret_cast<LPCWSTR>(origBytes.GetBaseAddress()),
+ &targetModule)) {
+ return;
+ }
+ }
+
+ Maybe<TrampPoolT> maybeTrampPool = DoReserve(targetModule);
+ MOZ_ASSERT(maybeTrampPool);
+ if (!maybeTrampPool) {
+ return;
+ }
+
+ Maybe<Trampoline<MMPolicyT>> maybeRealTramp(
+ maybeTrampPool.ref().GetNextTrampoline());
+ if (!maybeRealTramp) {
+ return;
+ }
+
+ origBytes.Rewind();
+ CreateTrampoline(origBytes, maybeTrampPool.ptr(), maybeRealTramp.ref(),
+ aDest, aOutTramp);
+ return;
+ }
+
+ // Write the branch from the trampoline back to the original code
+
+ tramp.WriteLoadLiteral(origBytes.GetAddress(), 16);
+ tramp.WriteInstruction(arm64::BuildUnconditionalBranchToRegister(16));
+#else
+# error "Unsupported processor architecture"
+#endif
+
+ // The trampoline is now complete.
+ void* trampPtr = tramp.EndExecutableCode();
+ if (!trampPtr) {
+ return;
+ }
+
+#ifdef _M_X64
+ if constexpr (MMPolicyT::kSupportsUnwindInfo) {
+ DebugOnly<bool> unwindInfoAdded = tramp.AddUnwindInfo(
+ origBytes.GetBaseAddress(), origBytes.GetOffset());
+ MOZ_ASSERT(unwindInfoAdded);
+ }
+#endif // _M_X64
+
+ WritableTargetFunction<MMPolicyT> target(origBytes.Promote());
+ if (!target) {
+ return;
+ }
+
+ do {
+ // Now patch the original function.
+ // When we're instructed to apply a non-default patch, apply it and exit.
+ // If non-default patching fails, bail out, no fallback.
+ // Otherwise, we go straight to the default patch.
+
+#if defined(_M_X64)
+ if (use10BytePatch) {
+ if (!Apply10BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#elif defined(_M_ARM64)
+ if (numBytesForPatching < kWorstCaseBytesRequired) {
+ if (!Apply4BytePatch(aTrampPool, trampPtr, target, aDest)) {
+ return;
+ }
+ break;
+ }
+#endif
+
+ PrimitiveT::ApplyDefaultPatch(target, aDest);
+ } while (false);
+
+ if (!target.Commit()) {
+ return;
+ }
+
+ // Output the trampoline, thus signalling that this call was a success
+ *aOutTramp = trampPtr;
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_PatcherDetour_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h b/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h
new file mode 100644
index 0000000000..deee87e0f8
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/PatcherNopSpace.h
@@ -0,0 +1,205 @@
+/* -*- 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_interceptor_PatcherNopSpace_h
+#define mozilla_interceptor_PatcherNopSpace_h
+
+#if defined(_M_IX86)
+
+# include "mozilla/interceptor/PatcherBase.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename VMPolicy>
+class WindowsDllNopSpacePatcher final : public WindowsDllPatcherBase<VMPolicy> {
+ typedef typename VMPolicy::MMPolicyT MMPolicyT;
+
+ // For remembering the addresses of functions we've patched.
+ mozilla::Vector<void*> mPatchedFns;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllNopSpacePatcher(Args&&... aArgs)
+ : WindowsDllPatcherBase<VMPolicy>(std::forward<Args>(aArgs)...) {}
+
+ ~WindowsDllNopSpacePatcher() { Clear(); }
+
+ WindowsDllNopSpacePatcher(const WindowsDllNopSpacePatcher&) = delete;
+ WindowsDllNopSpacePatcher(WindowsDllNopSpacePatcher&&) = delete;
+ WindowsDllNopSpacePatcher& operator=(const WindowsDllNopSpacePatcher&) =
+ delete;
+ WindowsDllNopSpacePatcher& operator=(WindowsDllNopSpacePatcher&&) = delete;
+
+ void Clear() {
+ // Restore the mov edi, edi to the beginning of each function we patched.
+
+ for (auto&& ptr : mPatchedFns) {
+ WritableTargetFunction<MMPolicyT> fn(
+ this->mVMPolicy, reinterpret_cast<uintptr_t>(ptr), sizeof(uint16_t));
+ if (!fn) {
+ continue;
+ }
+
+ // mov edi, edi
+ fn.CommitAndWriteShort(0xff8b);
+ }
+
+ mPatchedFns.clear();
+ }
+
+ /**
+ * NVIDIA Optimus drivers utilize Microsoft Detours 2.x to patch functions
+ * in our address space. There is a bug in Detours 2.x that causes it to
+ * patch at the wrong address when attempting to detour code that is already
+ * NOP space patched. This function is an effort to detect the presence of
+ * this NVIDIA code in our address space and disable NOP space patching if it
+ * is. We also check AppInit_DLLs since this is the mechanism that the Optimus
+ * drivers use to inject into our process.
+ */
+ static bool IsCompatible() {
+ // These DLLs are known to have bad interactions with this style of patching
+ const wchar_t* kIncompatibleDLLs[] = {L"detoured.dll", L"_etoured.dll",
+ L"nvd3d9wrap.dll", L"nvdxgiwrap.dll"};
+ // See if the infringing DLLs are already loaded
+ for (unsigned int i = 0; i < mozilla::ArrayLength(kIncompatibleDLLs); ++i) {
+ if (GetModuleHandleW(kIncompatibleDLLs[i])) {
+ return false;
+ }
+ }
+ if (GetModuleHandleW(L"user32.dll")) {
+ // user32 is loaded but the infringing DLLs are not, assume we're safe to
+ // proceed.
+ return true;
+ }
+ // If user32 has not loaded yet, check AppInit_DLLs to ensure that Optimus
+ // won't be loaded once user32 is initialized.
+ HKEY hkey = NULL;
+ if (!RegOpenKeyExW(
+ HKEY_LOCAL_MACHINE,
+ L"SOFTWARE\\Microsoft\\Windows NT\\CurrentVersion\\Windows", 0,
+ KEY_QUERY_VALUE, &hkey)) {
+ nsAutoRegKey key(hkey);
+ DWORD numBytes = 0;
+ const wchar_t kAppInitDLLs[] = L"AppInit_DLLs";
+ // Query for required buffer size
+ LONG status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ nullptr, &numBytes);
+ mozilla::UniquePtr<wchar_t[]> data;
+ if (!status) {
+ // Allocate the buffer and query for the actual data
+ data = mozilla::MakeUnique<wchar_t[]>((numBytes + 1) / sizeof(wchar_t));
+ status = RegQueryValueExW(hkey, kAppInitDLLs, nullptr, nullptr,
+ (LPBYTE)data.get(), &numBytes);
+ }
+ if (!status) {
+ // For each token, split up the filename components and then check the
+ // name of the file.
+ const wchar_t kDelimiters[] = L", ";
+ wchar_t* tokenContext = nullptr;
+ wchar_t* token = wcstok_s(data.get(), kDelimiters, &tokenContext);
+ while (token) {
+ wchar_t fname[_MAX_FNAME] = {0};
+ if (!_wsplitpath_s(token, nullptr, 0, nullptr, 0, fname,
+ mozilla::ArrayLength(fname), nullptr, 0)) {
+ // nvinit.dll is responsible for bootstrapping the DLL injection, so
+ // that is the library that we check for here
+ const wchar_t kNvInitName[] = L"nvinit";
+ if (!_wcsnicmp(fname, kNvInitName,
+ mozilla::ArrayLength(kNvInitName))) {
+ return false;
+ }
+ }
+ token = wcstok_s(nullptr, kDelimiters, &tokenContext);
+ }
+ }
+ }
+ return true;
+ }
+
+ bool AddHook(FARPROC aTargetFn, intptr_t aHookDest, void** aOrigFunc) {
+ if (!IsCompatible()) {
+# if defined(MOZILLA_INTERNAL_API)
+ NS_WARNING("NOP space patching is unavailable for compatibility reasons");
+# endif
+ return false;
+ }
+
+ MOZ_ASSERT(aTargetFn);
+ if (!aTargetFn) {
+ return false;
+ }
+
+ ReadOnlyTargetFunction<MMPolicyT> readOnlyTargetFn(
+ this->ResolveRedirectedAddress(aTargetFn));
+
+ if (!WriteHook(readOnlyTargetFn, aHookDest, aOrigFunc)) {
+ return false;
+ }
+
+ return mPatchedFns.append(
+ reinterpret_cast<void*>(readOnlyTargetFn.GetBaseAddress()));
+ }
+
+ bool WriteHook(const ReadOnlyTargetFunction<MMPolicyT>& aFn,
+ intptr_t aHookDest, void** aOrigFunc) {
+ // Ensure we can read and write starting at fn - 5 (for the long jmp we're
+ // going to write) and ending at fn + 2 (for the short jmp up to the long
+ // jmp). These bytes may span two pages with different protection.
+ WritableTargetFunction<MMPolicyT> writableFn(aFn.Promote(7, -5));
+ if (!writableFn) {
+ return false;
+ }
+
+ // Check that the 5 bytes before the function are NOP's or INT 3's,
+ const uint8_t nopOrBp[] = {0x90, 0xCC};
+ if (!writableFn.template VerifyValuesAreOneOf<uint8_t, 5>(nopOrBp)) {
+ return false;
+ }
+
+ // ... and that the first 2 bytes of the function are mov(edi, edi).
+ // There are two ways to encode the same thing:
+ //
+ // 0x89 0xff == mov r/m, r
+ // 0x8b 0xff == mov r, r/m
+ //
+ // where "r" is register and "r/m" is register or memory.
+ // Windows seems to use 0x8B 0xFF. We include 0x89 0xFF out of paranoia.
+
+ // (These look backwards because little-endian)
+ const uint16_t possibleEncodings[] = {0xFF8B, 0xFF89};
+ if (!writableFn.template VerifyValuesAreOneOf<uint16_t, 1>(
+ possibleEncodings, 5)) {
+ return false;
+ }
+
+ // Write a long jump into the space above the function.
+ writableFn.WriteByte(0xe9); // jmp
+ if (!writableFn) {
+ return false;
+ }
+
+ writableFn.WriteDisp32(aHookDest); // target
+ if (!writableFn) {
+ return false;
+ }
+
+ // Set aOrigFunc here, because after this point, aHookDest might be called,
+ // and aHookDest might use the aOrigFunc pointer.
+ *aOrigFunc = reinterpret_cast<void*>(writableFn.GetCurrentAddress() +
+ sizeof(uint16_t));
+
+ // Short jump up into our long jump.
+ return writableFn.CommitAndWriteShort(0xF9EB); // jmp $-5
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // defined(_M_IX86)
+
+#endif // mozilla_interceptor_PatcherNopSpace_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h b/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h
new file mode 100644
index 0000000000..d45d031613
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/RangeMap.h
@@ -0,0 +1,142 @@
+/* -*- 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_interceptor_RangeMap_h
+#define mozilla_interceptor_RangeMap_h
+
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mozalloc.h"
+#include "mozilla/Span.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+
+#include <algorithm>
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class maintains a vector of VMSharingPolicyUnique objects, sorted on
+ * the memory range that is used for reserving each object.
+ *
+ * This is used by VMSharingPolicyShared for creating and looking up VM regions
+ * that are within proximity of the applicable range.
+ *
+ * VMSharingPolicyUnique objects managed by this class are reused whenever
+ * possible. If no range is required, we just return the first available
+ * policy.
+ *
+ * If no range is required and no policies have yet been allocated, we create
+ * a new one with a null range as a default.
+ */
+template <typename MMPolicyT>
+class RangeMap final {
+ private:
+ /**
+ * This class is used as the comparison key for sorting and insertion.
+ */
+ class Range {
+ public:
+ constexpr Range() : mBase(0), mLimit(0) {}
+
+ explicit Range(const Maybe<Span<const uint8_t>>& aBounds)
+ : mBase(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetLowerBound(aBounds.ref()))
+ : 0),
+ mLimit(aBounds ? reinterpret_cast<const uintptr_t>(
+ MMPolicyT::GetUpperBoundIncl(aBounds.ref()))
+ : 0) {}
+
+ Range& operator=(const Range&) = default;
+ Range(const Range&) = default;
+ Range(Range&&) = default;
+ Range& operator=(Range&&) = default;
+
+ bool operator<(const Range& aOther) const {
+ return mBase < aOther.mBase ||
+ (mBase == aOther.mBase && mLimit < aOther.mLimit);
+ }
+
+ bool Contains(const Range& aOther) const {
+ return mBase <= aOther.mBase && mLimit >= aOther.mLimit;
+ }
+
+ private:
+ uintptr_t mBase;
+ uintptr_t mLimit;
+ };
+
+ class PolicyInfo final : public Range {
+ public:
+ explicit PolicyInfo(const Range& aRange)
+ : Range(aRange),
+ mPolicy(MakeUnique<VMSharingPolicyUnique<MMPolicyT>>()) {}
+
+ PolicyInfo(const PolicyInfo&) = delete;
+ PolicyInfo& operator=(const PolicyInfo&) = delete;
+
+ PolicyInfo(PolicyInfo&& aOther) = default;
+ PolicyInfo& operator=(PolicyInfo&& aOther) = default;
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy() { return mPolicy.get(); }
+
+ private:
+ UniquePtr<VMSharingPolicyUnique<MMPolicyT>> mPolicy;
+ };
+
+ using VectorType = Vector<PolicyInfo, 0, InfallibleAllocPolicy>;
+
+ public:
+ constexpr RangeMap() : mPolicies(nullptr) {}
+
+ VMSharingPolicyUnique<MMPolicyT>* GetPolicy(
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ Range testRange(aBounds);
+
+ if (!mPolicies) {
+ mPolicies = new VectorType();
+ }
+
+ // If no bounds are specified, we just use the first available policy
+ if (!aBounds) {
+ if (mPolicies->empty()) {
+ if (!mPolicies->append(PolicyInfo(testRange))) {
+ return nullptr;
+ }
+ }
+
+ return GetFirstPolicy();
+ }
+
+ // mPolicies is sorted, so we search
+ auto itr =
+ std::lower_bound(mPolicies->begin(), mPolicies->end(), testRange);
+ if (itr != mPolicies->end() && itr->Contains(testRange)) {
+ return itr->GetPolicy();
+ }
+
+ itr = mPolicies->insert(itr, PolicyInfo(testRange));
+
+ MOZ_ASSERT(std::is_sorted(mPolicies->begin(), mPolicies->end()));
+
+ return itr->GetPolicy();
+ }
+
+ private:
+ VMSharingPolicyUnique<MMPolicyT>* GetFirstPolicy() {
+ MOZ_RELEASE_ASSERT(mPolicies && !mPolicies->empty());
+ return mPolicies->begin()->GetPolicy();
+ }
+
+ private:
+ VectorType* mPolicies;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_RangeMap_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h b/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h
new file mode 100644
index 0000000000..40be1ad08b
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/TargetFunction.h
@@ -0,0 +1,1000 @@
+/* -*- 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_interceptor_TargetFunction_h
+#define mozilla_interceptor_TargetFunction_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BinarySearch.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/Unused.h"
+#include "mozilla/Vector.h"
+
+#include <memory>
+#include <type_traits>
+
+namespace mozilla {
+namespace interceptor {
+
+#if defined(_M_IX86)
+
+template <typename T>
+bool CommitAndWriteShortInternal(const T& aMMPolicy, void* aDest,
+ uint16_t aValue);
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyInProcess>(
+ const MMPolicyInProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.WriteAtomic(aDest, aValue);
+}
+
+template <>
+inline bool CommitAndWriteShortInternal<MMPolicyOutOfProcess>(
+ const MMPolicyOutOfProcess& aMMPolicy, void* aDest, uint16_t aValue) {
+ return aMMPolicy.Write(aDest, &aValue, sizeof(uint16_t));
+}
+
+#endif // defined(_M_IX86)
+
+// Forward declaration
+template <typename MMPolicy>
+class ReadOnlyTargetFunction;
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS WritableTargetFunction final {
+ class AutoProtect final {
+ using ProtectParams = Tuple<uintptr_t, uint32_t>;
+
+ public:
+ explicit AutoProtect(const MMPolicy& aMMPolicy) : mMMPolicy(aMMPolicy) {}
+
+ AutoProtect(const MMPolicy& aMMPolicy, uintptr_t aAddr, size_t aNumBytes,
+ uint32_t aNewProt)
+ : mMMPolicy(aMMPolicy) {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ const uintptr_t limit = aAddr + aNumBytes - 1;
+ const uintptr_t limitPageNum = limit / pageSize;
+ const uintptr_t basePageNum = aAddr / pageSize;
+ const uintptr_t numPagesToChange = limitPageNum - basePageNum + 1;
+
+ // We'll use the base address of the page instead of aAddr
+ uintptr_t curAddr = basePageNum * pageSize;
+
+ // Now change the protection on each page
+ for (uintptr_t curPage = 0; curPage < numPagesToChange;
+ ++curPage, curAddr += pageSize) {
+ uint32_t prevProt;
+ if (!aMMPolicy.Protect(reinterpret_cast<void*>(curAddr), pageSize,
+ aNewProt, &prevProt)) {
+ Clear();
+ return;
+ }
+
+ // Save the previous protection for curAddr so that we can revert this
+ // in the destructor.
+ if (!mProtects.append(MakeTuple(curAddr, prevProt))) {
+ Clear();
+ return;
+ }
+ }
+ }
+
+ AutoProtect(AutoProtect&& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mProtects(std::move(aOther.mProtects)) {
+ aOther.mProtects.clear();
+ }
+
+ ~AutoProtect() { Clear(); }
+
+ explicit operator bool() const { return !mProtects.empty(); }
+
+ AutoProtect(const AutoProtect&) = delete;
+ AutoProtect& operator=(const AutoProtect&) = delete;
+ AutoProtect& operator=(AutoProtect&&) = delete;
+
+ private:
+ void Clear() {
+ const uint32_t pageSize = mMMPolicy.GetPageSize();
+ for (auto&& entry : mProtects) {
+ uint32_t prevProt;
+ DebugOnly<bool> ok =
+ mMMPolicy.Protect(reinterpret_cast<void*>(Get<0>(entry)), pageSize,
+ Get<1>(entry), &prevProt);
+ MOZ_ASSERT(ok);
+ }
+
+ mProtects.clear();
+ }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ // We include two entries of inline storage as that is most common in the
+ // worst case.
+ Vector<ProtectParams, 2> mProtects;
+ };
+
+ public:
+ /**
+ * Used to initialize an invalid WritableTargetFunction, thus signalling an
+ * error.
+ */
+ explicit WritableTargetFunction(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mFunc(0),
+ mNumBytes(0),
+ mOffset(0),
+ mStartWriteOffset(0),
+ mAccumulatedStatus(false),
+ mProtect(aMMPolicy) {}
+
+ WritableTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc,
+ size_t aNumBytes)
+ : mMMPolicy(aMMPolicy),
+ mFunc(aFunc),
+ mNumBytes(aNumBytes),
+ mOffset(0),
+ mStartWriteOffset(0),
+ mAccumulatedStatus(true),
+ mProtect(aMMPolicy, aFunc, aNumBytes, PAGE_EXECUTE_READWRITE) {}
+
+ WritableTargetFunction(WritableTargetFunction&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mFunc(aOther.mFunc),
+ mNumBytes(aOther.mNumBytes),
+ mOffset(aOther.mOffset),
+ mStartWriteOffset(aOther.mStartWriteOffset),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mAccumulatedStatus(aOther.mAccumulatedStatus),
+ mProtect(std::move(aOther.mProtect)) {
+ aOther.mAccumulatedStatus = false;
+ }
+
+ ~WritableTargetFunction() {
+ MOZ_ASSERT(mLocalBytes.empty(), "Did you forget to call Commit?");
+ }
+
+ WritableTargetFunction(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(const WritableTargetFunction&) = delete;
+ WritableTargetFunction& operator=(WritableTargetFunction&&) = delete;
+
+ /**
+ * @return true if data was successfully committed.
+ */
+ bool Commit() {
+ if (!(*this)) {
+ return false;
+ }
+
+ if (mLocalBytes.empty()) {
+ // Nothing to commit, treat like success
+ return true;
+ }
+
+ bool ok =
+ mMMPolicy.Write(reinterpret_cast<void*>(mFunc + mStartWriteOffset),
+ mLocalBytes.begin(), mLocalBytes.length());
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+
+ mStartWriteOffset += mLocalBytes.length();
+
+ mLocalBytes.clear();
+ return true;
+ }
+
+ explicit operator bool() const { return mProtect && mAccumulatedStatus; }
+
+ void WriteByte(const uint8_t& aValue) {
+ if (!mLocalBytes.append(aValue)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint8_t);
+ }
+
+ Maybe<uint8_t> ReadByte() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uint8_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uint8_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uint8_t);
+ mStartWriteOffset += sizeof(uint8_t);
+ return Some(value);
+ }
+
+ Maybe<uintptr_t> ReadEncodedPtr() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uintptr_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uintptr_t);
+ mStartWriteOffset += sizeof(uintptr_t);
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(value));
+ }
+
+ Maybe<uint32_t> ReadLong() {
+ // Reading is only permitted prior to any writing
+ MOZ_ASSERT(mOffset == mStartWriteOffset);
+ if (mOffset > mStartWriteOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ uint32_t value;
+ if (!mMMPolicy.Read(&value, reinterpret_cast<const void*>(mFunc + mOffset),
+ sizeof(uint32_t))) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ mOffset += sizeof(uint32_t);
+ mStartWriteOffset += sizeof(uint32_t);
+ return Some(value);
+ }
+
+ void WriteShort(const uint16_t& aValue) {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
+ sizeof(uint16_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint16_t);
+ }
+
+#if defined(_M_IX86)
+ public:
+ /**
+ * Commits any dirty writes, and then writes a short, atomically if possible.
+ * This call may succeed in both inproc and outproc cases, but atomicity
+ * is only guaranteed in the inproc case.
+ */
+ bool CommitAndWriteShort(const uint16_t aValue) {
+ // First, commit everything that has been written until now
+ if (!Commit()) {
+ return false;
+ }
+
+ // Now immediately write the short, atomically if inproc
+ bool ok = CommitAndWriteShortInternal(
+ mMMPolicy, reinterpret_cast<void*>(mFunc + mStartWriteOffset), aValue);
+ if (!ok) {
+ return false;
+ }
+
+ mMMPolicy.FlushInstructionCache();
+ mStartWriteOffset += sizeof(uint16_t);
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ void WriteDisp32(const uintptr_t aAbsTarget) {
+ intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
+ static_cast<intptr_t>(mFunc + mOffset + sizeof(int32_t));
+
+ CheckedInt<int32_t> checkedDisp(diff);
+ MOZ_ASSERT(checkedDisp.isValid());
+ if (!checkedDisp.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ int32_t disp = checkedDisp.value();
+ if (!mLocalBytes.append(reinterpret_cast<uint8_t*>(&disp),
+ sizeof(int32_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(int32_t);
+ }
+
+#if defined(_M_X64) || defined(_M_ARM64)
+ void WriteLong(const uint32_t aValue) {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aValue),
+ sizeof(uint32_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uint32_t);
+ }
+#endif // defined(_M_X64)
+
+ void WritePointer(const uintptr_t aAbsTarget) {
+ if (!mLocalBytes.append(reinterpret_cast<const uint8_t*>(&aAbsTarget),
+ sizeof(uintptr_t))) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += sizeof(uintptr_t);
+ }
+
+ /**
+ * @param aValues N-sized array of type T that specifies the set of values
+ * that are permissible in the first M bytes of the target
+ * function at aOffset.
+ * @return true if M values of type T in the function are members of the
+ * set specified by aValues.
+ */
+ template <typename T, size_t M, size_t N>
+ bool VerifyValuesAreOneOf(const T (&aValues)[N], const uint8_t aOffset = 0) {
+ T buf[M];
+ if (!mMMPolicy.Read(
+ buf, reinterpret_cast<const void*>(mFunc + mOffset + aOffset),
+ M * sizeof(T))) {
+ return false;
+ }
+
+ for (auto&& fnValue : buf) {
+ bool match = false;
+ for (auto&& testValue : aValues) {
+ match |= (fnValue == testValue);
+ }
+
+ if (!match) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ uintptr_t GetCurrentAddress() const { return mFunc + mOffset; }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ const uintptr_t mFunc;
+ const size_t mNumBytes;
+ uint32_t mOffset;
+ uint32_t mStartWriteOffset;
+
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ bool mAccumulatedStatus;
+ AutoProtect mProtect;
+};
+
+template <typename MMPolicy>
+class ReadOnlyTargetBytes {
+ public:
+ ReadOnlyTargetBytes(const MMPolicy& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy), mBase(reinterpret_cast<const uint8_t*>(aBase)) {}
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase) {}
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther = 0)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase + aOffsetFromOther) {}
+
+ void EnsureLimit(uint32_t aDesiredLimit) {
+ // In the out-proc case we use this function to read the target function's
+ // bytes in the other process into a local buffer. We don't need that for
+ // the in-process case because we already have direct access to our target
+ // function's bytes.
+ }
+
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ // Same as EnsureLimit above. We don't need to ensure for the in-process.
+ return aDesiredLimit;
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ if (!aOffset) {
+ return true;
+ }
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
+ uintptr_t adjusted = base + aOffset;
+ uint32_t pageSize = mMMPolicy.GetPageSize();
+
+ // If |adjusted| is within the same page as |mBase|, we're still valid
+ if ((base / pageSize) == (adjusted / pageSize)) {
+ return true;
+ }
+
+ // Otherwise, let's query |adjusted|
+ return mMMPolicy.IsPageAccessible(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const { return mBase; }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicy& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ const MMPolicy& mMMPolicy;
+ uint8_t const* const mBase;
+};
+
+template <>
+class ReadOnlyTargetBytes<MMPolicyOutOfProcess> {
+ public:
+ ReadOnlyTargetBytes(const MMPolicyOutOfProcess& aMMPolicy, const void* aBase)
+ : mMMPolicy(aMMPolicy), mBase(reinterpret_cast<const uint8_t*>(aBase)) {}
+
+ ReadOnlyTargetBytes(ReadOnlyTargetBytes&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mLocalBytes(std::move(aOther.mLocalBytes)),
+ mBase(aOther.mBase) {}
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase) {
+ Unused << mLocalBytes.appendAll(aOther.mLocalBytes);
+ }
+
+ ReadOnlyTargetBytes(const ReadOnlyTargetBytes& aOther,
+ const uint32_t aOffsetFromOther)
+ : mMMPolicy(aOther.mMMPolicy), mBase(aOther.mBase + aOffsetFromOther) {
+ if (aOffsetFromOther >= aOther.mLocalBytes.length()) {
+ return;
+ }
+
+ Unused << mLocalBytes.append(aOther.mLocalBytes.begin() + aOffsetFromOther,
+ aOther.mLocalBytes.end());
+ }
+
+ void EnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return;
+ }
+
+ size_t newSize = aDesiredLimit + 1;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ bool ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ if (ok) {
+ return;
+ }
+
+ // We couldn't pull more bytes than needed (which may happen if those extra
+ // bytes are not accessible). In this case, we try just to get the bare
+ // minimum.
+ newSize = aDesiredLimit + 1;
+ resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ ok = mMMPolicy.Read(&mLocalBytes[prevSize], mBase + prevSize,
+ newSize - prevSize);
+ MOZ_RELEASE_ASSERT(ok);
+ }
+
+ // This function tries to ensure as many bytes as possible up to
+ // |aDesiredLimit| bytes, returning how many bytes were actually ensured.
+ // As EnsureLimit does, we allocate an extra byte in local to make sure
+ // mLocalBytes always has at least one byte even though the target memory
+ // was inaccessible at all.
+ uint32_t TryEnsureLimit(uint32_t aDesiredLimit) {
+ size_t prevSize = mLocalBytes.length();
+ if (aDesiredLimit < prevSize) {
+ return aDesiredLimit;
+ }
+
+ size_t newSize = aDesiredLimit;
+ if (newSize < kInlineStorage) {
+ // Always try to read as much memory as we can at once
+ newSize = kInlineStorage;
+ }
+
+ bool resizeOk = mLocalBytes.resize(newSize);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ size_t bytesRead = mMMPolicy.TryRead(&mLocalBytes[prevSize],
+ mBase + prevSize, newSize - prevSize);
+
+ newSize = prevSize + bytesRead;
+
+ resizeOk = mLocalBytes.resize(newSize + 1);
+ MOZ_RELEASE_ASSERT(resizeOk);
+
+ mLocalBytes[newSize] = 0;
+ return newSize;
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ if (!aOffset) {
+ return true;
+ }
+
+ uintptr_t base = reinterpret_cast<uintptr_t>(mBase);
+ uintptr_t adjusted = base + aOffset;
+ uint32_t pageSize = mMMPolicy.GetPageSize();
+
+ // If |adjusted| is within the same page as |mBase|, we're still valid
+ if ((base / pageSize) == (adjusted / pageSize)) {
+ return true;
+ }
+
+ // Otherwise, let's query |adjusted|
+ return mMMPolicy.IsPageAccessible(adjusted);
+ }
+
+ /**
+ * This returns a pointer to a *potentially local copy* of the target
+ * function's bytes. The returned pointer should not be used for any
+ * pointer arithmetic relating to the target function.
+ */
+ const uint8_t* GetLocalBytes() const {
+ if (mLocalBytes.empty()) {
+ return nullptr;
+ }
+
+ return mLocalBytes.begin();
+ }
+
+ /**
+ * This returns a pointer to the target function's bytes. The returned pointer
+ * may possibly belong to another process, so while it should be used for
+ * pointer arithmetic, it *must not* be dereferenced.
+ */
+ uintptr_t GetBase() const { return reinterpret_cast<uintptr_t>(mBase); }
+
+ const MMPolicyOutOfProcess& GetMMPolicy() const { return mMMPolicy; }
+
+ ReadOnlyTargetBytes& operator=(const ReadOnlyTargetBytes&) = delete;
+ ReadOnlyTargetBytes& operator=(ReadOnlyTargetBytes&&) = delete;
+
+ private:
+ // In an ideal world, we'd only read 5 bytes on 32-bit and 13 bytes on 64-bit,
+ // to match the minimum bytes that we need to write in in order to patch the
+ // target function. Since the actual opcodes will often require us to pull in
+ // extra bytes above that minimum, we set the inline storage to be larger than
+ // those minima in an effort to give the Vector extra wiggle room before it
+ // needs to touch the heap.
+#if defined(_M_IX86)
+ static const size_t kInlineStorage = 16;
+#elif defined(_M_X64) || defined(_M_ARM64)
+ static const size_t kInlineStorage = 32;
+#endif
+
+ const MMPolicyOutOfProcess& mMMPolicy;
+ Vector<uint8_t, kInlineStorage> mLocalBytes;
+ uint8_t const* const mBase;
+};
+
+template <typename MMPolicy>
+class TargetBytesPtr {
+ public:
+ typedef TargetBytesPtr<MMPolicy> Type;
+
+ static Type Make(const MMPolicy& aMMPolicy, const void* aFunc) {
+ return TargetBytesPtr(aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const TargetBytesPtr& aOther,
+ const uint32_t aOffsetFromOther) {
+ return TargetBytesPtr(aOther, aOffsetFromOther);
+ }
+
+ ReadOnlyTargetBytes<MMPolicy>* operator->() { return &mTargetBytes; }
+
+ TargetBytesPtr(TargetBytesPtr&& aOther)
+ : mTargetBytes(std::move(aOther.mTargetBytes)) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther)
+ : mTargetBytes(aOther.mTargetBytes) {}
+
+ TargetBytesPtr& operator=(const TargetBytesPtr&) = delete;
+ TargetBytesPtr& operator=(TargetBytesPtr&&) = delete;
+
+ private:
+ TargetBytesPtr(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(aMMPolicy, aFunc) {}
+
+ TargetBytesPtr(const TargetBytesPtr& aOther, const uint32_t aOffsetFromOther)
+ : mTargetBytes(aOther.mTargetBytes, aOffsetFromOther) {}
+
+ ReadOnlyTargetBytes<MMPolicy> mTargetBytes;
+};
+
+template <>
+class TargetBytesPtr<MMPolicyOutOfProcess> {
+ public:
+ typedef std::shared_ptr<ReadOnlyTargetBytes<MMPolicyOutOfProcess>> Type;
+
+ static Type Make(const MMPolicyOutOfProcess& aMMPolicy, const void* aFunc) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ aMMPolicy, aFunc);
+ }
+
+ static Type CopyFromOffset(const Type& aOther,
+ const uint32_t aOffsetFromOther) {
+ return std::make_shared<ReadOnlyTargetBytes<MMPolicyOutOfProcess>>(
+ *aOther, aOffsetFromOther);
+ }
+};
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS ReadOnlyTargetFunction final {
+ public:
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, const void* aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aFunc)),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, FARPROC aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(
+ aMMPolicy, reinterpret_cast<const void*>(aFunc))),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(const MMPolicy& aMMPolicy, uintptr_t aFunc)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(
+ aMMPolicy, reinterpret_cast<const void*>(aFunc))),
+ mOffset(0) {}
+
+ ReadOnlyTargetFunction(ReadOnlyTargetFunction&& aOther)
+ : mTargetBytes(std::move(aOther.mTargetBytes)), mOffset(aOther.mOffset) {}
+
+ ReadOnlyTargetFunction& operator=(const ReadOnlyTargetFunction&) = delete;
+ ReadOnlyTargetFunction& operator=(ReadOnlyTargetFunction&&) = delete;
+
+ ~ReadOnlyTargetFunction() = default;
+
+ ReadOnlyTargetFunction operator+(const uint32_t aOffset) const {
+ return ReadOnlyTargetFunction(*this, mOffset + aOffset);
+ }
+
+ uintptr_t GetBaseAddress() const { return mTargetBytes->GetBase(); }
+
+ uintptr_t GetAddress() const { return mTargetBytes->GetBase() + mOffset; }
+
+ uintptr_t AsEncodedPtr() const {
+ return EncodePtr(
+ reinterpret_cast<void*>(mTargetBytes->GetBase() + mOffset));
+ }
+
+ static uintptr_t EncodePtr(void* aPtr) {
+ return reinterpret_cast<uintptr_t>(::EncodePointer(aPtr));
+ }
+
+ static uintptr_t DecodePtr(uintptr_t aEncodedPtr) {
+ return reinterpret_cast<uintptr_t>(
+ ::DecodePointer(reinterpret_cast<PVOID>(aEncodedPtr)));
+ }
+
+ bool IsValidAtOffset(const int8_t aOffset) const {
+ return mTargetBytes->IsValidAtOffset(aOffset);
+ }
+
+#if defined(_M_ARM64)
+
+ uint32_t ReadNextInstruction() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(uint32_t));
+ uint32_t instruction = *reinterpret_cast<const uint32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ mOffset += sizeof(uint32_t);
+ return instruction;
+ }
+
+ bool BackUpOneInstruction() {
+ if (mOffset < sizeof(uint32_t)) {
+ return false;
+ }
+
+ mOffset -= sizeof(uint32_t);
+ return true;
+ }
+
+#else
+
+ uint8_t const& operator*() const {
+ mTargetBytes->EnsureLimit(mOffset);
+ return *(mTargetBytes->GetLocalBytes() + mOffset);
+ }
+
+ uint8_t const& operator[](uint32_t aIndex) const {
+ mTargetBytes->EnsureLimit(mOffset + aIndex);
+ return *(mTargetBytes->GetLocalBytes() + mOffset + aIndex);
+ }
+
+ ReadOnlyTargetFunction& operator++() {
+ ++mOffset;
+ return *this;
+ }
+
+ ReadOnlyTargetFunction& operator+=(uint32_t aDelta) {
+ mOffset += aDelta;
+ return *this;
+ }
+
+ uintptr_t ReadDisp32AsAbsolute() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(int32_t));
+ int32_t disp = *reinterpret_cast<const int32_t*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ uintptr_t result =
+ mTargetBytes->GetBase() + mOffset + sizeof(int32_t) + disp;
+ mOffset += sizeof(int32_t);
+ return result;
+ }
+
+ bool IsRelativeShortJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xeb) {
+ int8_t offset = static_cast<int8_t>((*this)[1]);
+ *aOutTarget = GetAddress() + 2 + offset;
+ return true;
+ }
+ return false;
+ }
+
+# if defined(_M_X64)
+ // Currently this function is used only in x64.
+ bool IsRelativeNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xe9) {
+ *aOutTarget = (*this + 1).ReadDisp32AsAbsolute();
+ return true;
+ }
+ return false;
+ }
+# endif // defined(_M_X64)
+
+ bool IsIndirectNearJump(uintptr_t* aOutTarget) {
+ if ((*this)[0] == 0xff && (*this)[1] == 0x25) {
+# if defined(_M_X64)
+ *aOutTarget = (*this + 2).ChasePointerFromDisp();
+# else
+ *aOutTarget = (*this + 2).template ChasePointer<uintptr_t*>();
+# endif // defined(_M_X64)
+ return true;
+ }
+# if defined(_M_X64)
+ else if ((*this)[0] == 0x48 && (*this)[1] == 0xff && (*this)[2] == 0x25) {
+ // According to Intel SDM, JMP does not have REX.W except JMP m16:64,
+ // but CPU can execute JMP r/m32 with REX.W. We handle it just in case.
+ *aOutTarget = (*this + 3).ChasePointerFromDisp();
+ return true;
+ }
+# endif // defined(_M_X64)
+ return false;
+ }
+
+#endif // defined(_M_ARM64)
+
+ void Rewind() { mOffset = 0; }
+
+ uint32_t GetOffset() const { return mOffset; }
+
+ uintptr_t OffsetToAbsolute(const uint8_t aOffset) const {
+ return mTargetBytes->GetBase() + mOffset + aOffset;
+ }
+
+ uintptr_t GetCurrentAbsolute() const { return OffsetToAbsolute(0); }
+
+ /**
+ * This method promotes the code referenced by this object to be writable.
+ *
+ * @param aLen The length of the function's code to make writable. If set
+ * to zero, this object's current offset is used as the length.
+ * @param aOffset The result's base address will be offset from this
+ * object's base address by |aOffset| bytes. This value may be
+ * negative.
+ */
+ WritableTargetFunction<MMPolicy> Promote(const uint32_t aLen = 0,
+ const int8_t aOffset = 0) const {
+ const uint32_t effectiveLength = aLen ? aLen : mOffset;
+ MOZ_RELEASE_ASSERT(effectiveLength,
+ "Cannot Promote a zero-length function");
+
+ if (!mTargetBytes->IsValidAtOffset(aOffset)) {
+ return WritableTargetFunction<MMPolicy>(mTargetBytes->GetMMPolicy());
+ }
+
+ WritableTargetFunction<MMPolicy> result(mTargetBytes->GetMMPolicy(),
+ mTargetBytes->GetBase() + aOffset,
+ effectiveLength);
+
+ return result;
+ }
+
+ private:
+ template <typename T>
+ struct ChasePointerHelper {
+ template <typename MMPolicy_>
+ static T Result(const MMPolicy_&, T aValue) {
+ return aValue;
+ }
+ };
+
+ template <typename T>
+ struct ChasePointerHelper<T*> {
+ template <typename MMPolicy_>
+ static auto Result(const MMPolicy_& aPolicy, T* aValue) {
+ ReadOnlyTargetFunction<MMPolicy_> ptr(aPolicy, aValue);
+ return ptr.template ChasePointer<T>();
+ }
+ };
+
+ public:
+ // Keep chasing pointers until T is not a pointer type anymore
+ template <typename T>
+ auto ChasePointer() {
+ mTargetBytes->EnsureLimit(mOffset + sizeof(T));
+ const std::remove_cv_t<T> result =
+ *reinterpret_cast<const std::remove_cv_t<T>*>(
+ mTargetBytes->GetLocalBytes() + mOffset);
+ return ChasePointerHelper<std::remove_cv_t<T>>::Result(
+ mTargetBytes->GetMMPolicy(), result);
+ }
+
+ uintptr_t ChasePointerFromDisp() {
+ uintptr_t ptrFromDisp = ReadDisp32AsAbsolute();
+ ReadOnlyTargetFunction<MMPolicy> ptr(
+ mTargetBytes->GetMMPolicy(),
+ reinterpret_cast<const void*>(ptrFromDisp));
+ return ptr.template ChasePointer<uintptr_t>();
+ }
+
+ private:
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther)
+ : mTargetBytes(aOther.mTargetBytes), mOffset(aOther.mOffset) {}
+
+ ReadOnlyTargetFunction(const ReadOnlyTargetFunction& aOther,
+ const uint32_t aOffsetFromOther)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::CopyFromOffset(
+ aOther.mTargetBytes, aOffsetFromOther)),
+ mOffset(0) {}
+
+ private:
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ uint32_t mOffset;
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObject {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+
+ TargetObject(const MMPolicy& aMMPolicy, const void* aBaseAddress)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)) {
+ mTargetBytes->EnsureLimit(sizeof(T));
+ }
+
+ public:
+ explicit TargetObject(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)) {}
+
+ TargetObject(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress)
+ : TargetObject(aMMPolicy, reinterpret_cast<const void*>(aBaseAddress)) {}
+
+ TargetObject(const TargetObject&) = delete;
+ TargetObject(TargetObject&&) = delete;
+ TargetObject& operator=(const TargetObject&) = delete;
+ TargetObject& operator=(TargetObject&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mTargetBytes->GetLocalBytes();
+ }
+
+ const T* operator->() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+};
+
+template <typename MMPolicy, typename T>
+class MOZ_STACK_CLASS TargetObjectArray {
+ mutable typename TargetBytesPtr<MMPolicy>::Type mTargetBytes;
+ size_t mNumOfItems;
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, const void* aBaseAddress,
+ size_t aNumOfItems)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, aBaseAddress)),
+ mNumOfItems(aNumOfItems) {
+ uint32_t itemsRead =
+ mTargetBytes->TryEnsureLimit(sizeof(T) * mNumOfItems) / sizeof(T);
+ // itemsRead may be bigger than the requested amount because of buffering,
+ // but mNumOfItems should not include extra bytes of buffering.
+ if (itemsRead < mNumOfItems) {
+ mNumOfItems = itemsRead;
+ }
+ }
+
+ const T* GetLocalBase() const {
+ return reinterpret_cast<const T*>(mTargetBytes->GetLocalBytes());
+ }
+
+ public:
+ explicit TargetObjectArray(const MMPolicy& aMMPolicy)
+ : mTargetBytes(TargetBytesPtr<MMPolicy>::Make(aMMPolicy, nullptr)),
+ mNumOfItems(0) {}
+
+ TargetObjectArray(const MMPolicy& aMMPolicy, uintptr_t aBaseAddress,
+ size_t aNumOfItems)
+ : TargetObjectArray(aMMPolicy,
+ reinterpret_cast<const void*>(aBaseAddress),
+ aNumOfItems) {}
+
+ TargetObjectArray(const TargetObjectArray&) = delete;
+ TargetObjectArray(TargetObjectArray&&) = delete;
+ TargetObjectArray& operator=(const TargetObjectArray&) = delete;
+ TargetObjectArray& operator=(TargetObjectArray&&) = delete;
+
+ explicit operator bool() const {
+ return mTargetBytes->GetBase() && mNumOfItems;
+ }
+
+ const T* operator[](size_t aIndex) const {
+ if (aIndex >= mNumOfItems) {
+ return nullptr;
+ }
+
+ return &GetLocalBase()[aIndex];
+ }
+
+ template <typename Comparator>
+ bool BinarySearchIf(const Comparator& aCompare,
+ size_t* aMatchOrInsertionPoint) const {
+ return mozilla::BinarySearchIf(GetLocalBase(), 0, mNumOfItems, aCompare,
+ aMatchOrInsertionPoint);
+ }
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_TargetFunction_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h
new file mode 100644
index 0000000000..befbd47215
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/Trampoline.h
@@ -0,0 +1,773 @@
+/* -*- 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_interceptor_Trampoline_h
+#define mozilla_interceptor_Trampoline_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+#include "mozilla/WindowsProcessMitigations.h"
+#include "mozilla/WindowsUnwindInfo.h"
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS Trampoline final {
+ public:
+ Trampoline(const MMPolicy* aMMPolicy, uint8_t* const aLocalBase,
+ const uintptr_t aRemoteBase, const uint32_t aChunkSize)
+ : mMMPolicy(aMMPolicy),
+ mPrevLocalProt(0),
+ mLocalBase(aLocalBase),
+ mRemoteBase(aRemoteBase),
+ mOffset(0),
+ mExeOffset(0),
+#ifdef _M_X64
+ mCopyCodesEndOffset(0),
+ mExeEndOffset(0),
+#endif // _M_X64
+ mMaxOffset(aChunkSize),
+ mAccumulatedStatus(true) {
+ if (!::VirtualProtect(aLocalBase, aChunkSize,
+ MMPolicy::GetTrampWriteProtFlags(),
+ &mPrevLocalProt)) {
+ mPrevLocalProt = 0;
+ }
+ }
+
+ Trampoline(Trampoline&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mPrevLocalProt(aOther.mPrevLocalProt),
+ mLocalBase(aOther.mLocalBase),
+ mRemoteBase(aOther.mRemoteBase),
+ mOffset(aOther.mOffset),
+ mExeOffset(aOther.mExeOffset),
+#ifdef _M_X64
+ mCopyCodesEndOffset(aOther.mCopyCodesEndOffset),
+ mExeEndOffset(aOther.mExeEndOffset),
+#endif // _M_X64
+ mMaxOffset(aOther.mMaxOffset),
+ mAccumulatedStatus(aOther.mAccumulatedStatus) {
+ aOther.mPrevLocalProt = 0;
+ aOther.mAccumulatedStatus = false;
+ }
+
+ MOZ_IMPLICIT Trampoline(decltype(nullptr))
+ : mMMPolicy(nullptr),
+ mPrevLocalProt(0),
+ mLocalBase(nullptr),
+ mRemoteBase(0),
+ mOffset(0),
+ mExeOffset(0),
+#ifdef _M_X64
+ mCopyCodesEndOffset(0),
+ mExeEndOffset(0),
+#endif // _M_X64
+ mMaxOffset(0),
+ mAccumulatedStatus(false) {
+ }
+
+ Trampoline(const Trampoline&) = delete;
+ Trampoline& operator=(const Trampoline&) = delete;
+
+ Trampoline& operator=(Trampoline&& aOther) {
+ Clear();
+
+ mMMPolicy = aOther.mMMPolicy;
+ mPrevLocalProt = aOther.mPrevLocalProt;
+ mLocalBase = aOther.mLocalBase;
+ mRemoteBase = aOther.mRemoteBase;
+ mOffset = aOther.mOffset;
+ mExeOffset = aOther.mExeOffset;
+#ifdef _M_X64
+ mCopyCodesEndOffset = aOther.mCopyCodesEndOffset;
+ mExeEndOffset = aOther.mExeEndOffset;
+#endif // _M_X64
+ mMaxOffset = aOther.mMaxOffset;
+ mAccumulatedStatus = aOther.mAccumulatedStatus;
+
+ aOther.mPrevLocalProt = 0;
+ aOther.mAccumulatedStatus = false;
+
+ return *this;
+ }
+
+ ~Trampoline() { Clear(); }
+
+ explicit operator bool() const {
+ return IsNull() ||
+ (mLocalBase && mRemoteBase && mPrevLocalProt && mAccumulatedStatus);
+ }
+
+ bool IsNull() const { return !mMMPolicy; }
+
+#if defined(_M_ARM64)
+
+ void WriteInstruction(uint32_t aInstruction) {
+ const uint32_t kDelta = sizeof(uint32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<uint32_t*>(mLocalBase + mOffset) = aInstruction;
+ mOffset += kDelta;
+ }
+
+ void WriteLoadLiteral(const uintptr_t aAddress, const uint8_t aReg) {
+ const uint32_t kDelta = sizeof(uint32_t) + sizeof(uintptr_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ // We grow the literal pool from the *end* of the tramp,
+ // so we need to ensure that there is enough room for both an instruction
+ // and a pointer
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mMaxOffset -= sizeof(uintptr_t);
+ *reinterpret_cast<uintptr_t*>(mLocalBase + mMaxOffset) = aAddress;
+
+ CheckedInt<intptr_t> pc(GetCurrentRemoteAddress());
+ if (!pc.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ CheckedInt<intptr_t> literal(reinterpret_cast<uintptr_t>(mLocalBase) +
+ mMaxOffset);
+ if (!literal.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ CheckedInt<intptr_t> ptrOffset = (literal - pc);
+ if (!ptrOffset.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // ptrOffset must be properly aligned
+ MOZ_ASSERT((ptrOffset.value() % 4) == 0);
+ ptrOffset /= 4;
+
+ CheckedInt<int32_t> offset(ptrOffset.value());
+ if (!offset.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // Ensure that offset falls within the range of a signed 19-bit value
+ if (offset.value() < -0x40000 || offset.value() > 0x3FFFF) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ const int32_t kimm19Mask = 0x7FFFF;
+ int32_t masked = offset.value() & kimm19Mask;
+
+ MOZ_ASSERT(aReg < 32);
+ uint32_t loadInstr = 0x58000000 | (masked << 5) | aReg;
+ WriteInstruction(loadInstr);
+ }
+
+#else
+
+ void WriteByte(uint8_t aValue) {
+ const uint32_t kDelta = sizeof(uint8_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset >= mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *(mLocalBase + mOffset) = aValue;
+ ++mOffset;
+ }
+
+ void WriteInteger(int32_t aValue) {
+ const uint32_t kDelta = sizeof(int32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += kDelta;
+ }
+
+ void WriteDisp32(uintptr_t aAbsTarget) {
+ const uint32_t kDelta = sizeof(int32_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ // This needs to be computed from the remote location
+ intptr_t remoteTrampPosition = static_cast<intptr_t>(mRemoteBase + mOffset);
+
+ intptr_t diff =
+ static_cast<intptr_t>(aAbsTarget) - (remoteTrampPosition + kDelta);
+
+ CheckedInt<int32_t> checkedDisp(diff);
+ MOZ_ASSERT(checkedDisp.isValid());
+ if (!checkedDisp.isValid()) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ int32_t disp = checkedDisp.value();
+ *reinterpret_cast<int32_t*>(mLocalBase + mOffset) = disp;
+ mOffset += kDelta;
+ }
+
+ void WriteBytes(void* aAddr, size_t aSize) {
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += aSize;
+ return;
+ }
+
+ if (mOffset + aSize > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ std::memcpy(reinterpret_cast<void*>(mLocalBase + mOffset), aAddr, aSize);
+ mOffset += aSize;
+ }
+
+#endif
+
+ void WritePointer(uintptr_t aValue) {
+ const uint32_t kDelta = sizeof(uintptr_t);
+
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += kDelta;
+ return;
+ }
+
+ if (mOffset + kDelta > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ *reinterpret_cast<uintptr_t*>(mLocalBase + mOffset) = aValue;
+ mOffset += kDelta;
+ }
+
+ void WriteEncodedPointer(void* aValue) {
+ uintptr_t encoded = ReadOnlyTargetFunction<MMPolicy>::EncodePtr(aValue);
+ WritePointer(encoded);
+ }
+
+ Maybe<uintptr_t> ReadPointer() {
+ if (mOffset + sizeof(uintptr_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return Nothing();
+ }
+
+ auto result = Some(*reinterpret_cast<uintptr_t*>(mLocalBase + mOffset));
+ mOffset += sizeof(uintptr_t);
+ return std::move(result);
+ }
+
+ Maybe<uintptr_t> ReadEncodedPointer() {
+ Maybe<uintptr_t> encoded(ReadPointer());
+ if (!encoded) {
+ return encoded;
+ }
+
+ return Some(ReadOnlyTargetFunction<MMPolicy>::DecodePtr(encoded.value()));
+ }
+
+#if defined(_M_IX86)
+ // 32-bit only
+ void AdjustDisp32AtOffset(uint32_t aOffset, uintptr_t aAbsTarget) {
+ uint32_t effectiveOffset = mExeOffset + aOffset;
+
+ if (effectiveOffset + sizeof(int32_t) > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ intptr_t diff = static_cast<intptr_t>(aAbsTarget) -
+ static_cast<intptr_t>(mRemoteBase + mExeOffset);
+ *reinterpret_cast<int32_t*>(mLocalBase + effectiveOffset) += diff;
+ }
+#endif // defined(_M_IX86)
+
+ void CopyFrom(uintptr_t aOrigBytes, uint32_t aNumBytes) {
+ if (!mMMPolicy) {
+ // Null tramp, just track offset
+ mOffset += aNumBytes;
+ return;
+ }
+
+ if (!mMMPolicy || mOffset + aNumBytes > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ if (!mMMPolicy->Read(mLocalBase + mOffset,
+ reinterpret_cast<void*>(aOrigBytes), aNumBytes)) {
+ mAccumulatedStatus = false;
+ return;
+ }
+
+ mOffset += aNumBytes;
+ }
+
+ void CopyCodes(uintptr_t aOrigBytes, uint32_t aNumBytes) {
+#ifdef _M_X64
+ if (mOffset == mCopyCodesEndOffset) {
+ mCopyCodesEndOffset += aNumBytes;
+ }
+#endif // _M_X64
+ CopyFrom(aOrigBytes, aNumBytes);
+ }
+
+ void Rewind() { mOffset = 0; }
+
+ uintptr_t GetCurrentRemoteAddress() const { return mRemoteBase + mOffset; }
+
+ void StartExecutableCode() {
+ MOZ_ASSERT(!mExeOffset);
+ mExeOffset = mOffset;
+#ifdef _M_X64
+ mCopyCodesEndOffset = mOffset;
+#endif // _M_X64
+ }
+
+ void* EndExecutableCode() {
+ if (!mAccumulatedStatus || !mMMPolicy) {
+ return nullptr;
+ }
+
+#ifdef _M_X64
+ mExeEndOffset = mOffset;
+#endif // _M_X64
+
+ // This must always return the start address the executable code
+ // *in the target process*
+ return reinterpret_cast<void*>(mRemoteBase + mExeOffset);
+ }
+
+ uint32_t GetCurrentExecutableCodeLen() const { return mOffset - mExeOffset; }
+
+#ifdef _M_X64
+
+ void Align(uint32_t aAlignment) {
+ // aAlignment should be a power of 2
+ MOZ_ASSERT(!(aAlignment & (aAlignment - 1)));
+
+ uint32_t alignedOffset = (mOffset + aAlignment - 1) & ~(aAlignment - 1);
+ if (alignedOffset > mMaxOffset) {
+ mAccumulatedStatus = false;
+ return;
+ }
+ mOffset = alignedOffset;
+ }
+
+ // We assume that all instructions that are part of the prologue are left
+ // intact by detouring code, i.e. that they are copied using CopyCodes. This
+ // is not true for calls and jumps for example, but calls and jumps cannot be
+ // part of the prologue. This assumption allows us to copy unwind information
+ // as-is, because unwind information only refers to instructions within the
+ // prologue.
+ bool AddUnwindInfo(uintptr_t aOrigFuncAddr, uintptr_t aOrigFuncStopOffset) {
+ if constexpr (!MMPolicy::kSupportsUnwindInfo) {
+ return false;
+ }
+
+ if (!mMMPolicy) {
+ return false;
+ }
+
+ uint32_t origFuncOffsetFromBeginAddr = 0;
+ uint32_t origFuncOffsetToEndAddr = 0;
+ uintptr_t origImageBase = 0;
+ auto unwindInfoData =
+ mMMPolicy->LookupUnwindInfo(aOrigFuncAddr, &origFuncOffsetFromBeginAddr,
+ &origFuncOffsetToEndAddr, &origImageBase);
+ if (!unwindInfoData) {
+ // If the original function does not have unwind info, there is nothing
+ // more to do.
+ return true;
+ }
+
+ // We do not support hooking at a location that isn't the beginning of a
+ // function.
+ MOZ_ASSERT(origFuncOffsetFromBeginAddr == 0);
+ if (origFuncOffsetFromBeginAddr != 0) {
+ return false;
+ }
+
+ IterableUnwindInfo unwindInfoIt(unwindInfoData.get());
+ auto& unwindInfo = unwindInfoIt.Info();
+
+ // The prologue should contain only instructions that we detour using
+ // CopyCodes. If not, there is most likely a mismatch between the unwind
+ // information and the actual code we are detouring, so we stop here. This
+ // is a best-effort safeguard intended to detect situations where e.g.
+ // third-party injected code would have altered the function we are
+ // detouring.
+ if (mCopyCodesEndOffset < aOrigFuncStopOffset &&
+ unwindInfo.size_of_prolog > mCopyCodesEndOffset) {
+ return false;
+ }
+
+ // According to the documentation, the array is sorted by descending order
+ // of offset in the prologue. Let's double check this assumption if in
+ // debug. This also checks that the full unwind information isn't
+ // ill-formed, thanks to all the MOZ_ASSERT in iteration code.
+# ifdef DEBUG
+ uint8_t previousOffset = 0xFF;
+ for (const auto& unwindCode : unwindInfoIt) {
+ MOZ_ASSERT(unwindCode.offset_in_prolog <= previousOffset);
+ previousOffset = unwindCode.offset_in_prolog;
+ }
+# endif // DEBUG
+
+ // We skip entries that are not part of the code we have detoured.
+ // This code relies on the array being sorted by descending order of offset
+ // in the prolog.
+ uint8_t firstRelevantCode = 0;
+ uint8_t countOfCodes = 0;
+ auto it = unwindInfoIt.begin();
+ for (; it != unwindInfoIt.end(); ++it) {
+ const auto& unwindCode = *it;
+ if (unwindCode.offset_in_prolog <= aOrigFuncStopOffset) {
+ // Found a relevant entry
+ firstRelevantCode = it.Index();
+ countOfCodes = unwindInfo.count_of_codes - firstRelevantCode;
+ break;
+ }
+ }
+
+ // Check that we encountered no ill-formed unwind codes.
+ if (!it.IsValid() && !it.IsAtEnd()) {
+ return false;
+ }
+
+ // We do not support chained unwind info. We should add support for chained
+ // unwind info if we ever reach this assert. Since we hook functions at
+ // their start address, this should not happen.
+ if (unwindInfo.flags & UNW_FLAG_CHAININFO) {
+ MOZ_ASSERT(
+ false,
+ "Tried to detour at a location with chained unwind information");
+ return false;
+ }
+
+ // We do not support exception handler info either. This could be a problem
+ // if we detour code that does not belong to the prologue and contains a
+ // call instruction, as this handler would then not be found if unwinding
+ // from callees. The following assert checks that this does not happen.
+ //
+ // Our current assumption is that all the functions we hook either have no
+ // associated exception handlers, or it is __GSHandlerCheck. This handler
+ // is the most commonly found, for example it is present in LdrLoadDll,
+ // SendMessageTimeoutW, GetWindowInfo. It is added to functions that use
+ // stack buffers, in order to mitigate stack buffer overflows. We explain
+ // below why it is not a problem that we do not preserve __GSHandlerCheck
+ // information when we detour code.
+ //
+ // Preserving exception handler information would raise two challenges:
+ //
+ // (1) if the exception handler was not written in a generic way, it may
+ // behave differently when called for our detoured code compared to
+ // what it would do if called from the original location of the code;
+ // (2) the exception handler can be followed by handler-specific data,
+ // which we cannot copy because we do not know its size.
+ //
+ // __GSHandlerCheck checks that the stack cookie value wasn't overwritten
+ // before continuing to unwind and call further handlers. That is a
+ // security feature that we want to preserve. However, since these
+ // functions allocate stack space and write the stack cookie as part of
+ // their prologue, the 13 bytes that we detour are necessarily part of
+ // their prologue, which must contain at least the following instructions:
+ //
+ // 48 81 ec XX XX XX XX sub rsp, 0xXXXXXXXX
+ // 48 8b 05 XX XX XX XX mov rax, qword ptr [rip+__security_cookie]
+ // 48 33 c4 xor rax, rsp
+ // 48 89 84 24 XX XX XX XX mov qword ptr [RSP + 0xXXXXXXXX],RAX
+ //
+ // As a consequence, code associated with __GSHandlerCheck will necessarily
+ // satisfy (aOrigFuncStopOffset <= unwindInfo.size_of_prolog), and it is OK
+ // to not preserve handler info in that case.
+# ifdef DEBUG
+ if (aOrigFuncStopOffset > unwindInfo.size_of_prolog) {
+ MOZ_ASSERT(!(unwindInfo.flags & (UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER)));
+ }
+# endif // DEBUG
+
+ // The unwind info must be DWORD-aligned
+ Align(sizeof(uint32_t));
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+ uintptr_t unwindInfoOffset = mOffset;
+
+ unwindInfo.flags &=
+ ~(UNW_FLAG_CHAININFO | UNW_FLAG_EHANDLER | UNW_FLAG_UHANDLER);
+ unwindInfo.count_of_codes = countOfCodes;
+ if (aOrigFuncStopOffset < unwindInfo.size_of_prolog) {
+ unwindInfo.size_of_prolog = aOrigFuncStopOffset;
+ }
+
+ WriteBytes(reinterpret_cast<void*>(&unwindInfo),
+ offsetof(UnwindInfo, unwind_code));
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+
+ WriteBytes(
+ reinterpret_cast<void*>(&unwindInfo.unwind_code[firstRelevantCode]),
+ countOfCodes * sizeof(UnwindCode));
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+
+ // The function table must be DWORD-aligned
+ Align(sizeof(uint32_t));
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+ uintptr_t functionTableOffset = mOffset;
+
+ WriteInteger(mExeOffset);
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+
+ WriteInteger(mExeEndOffset);
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+
+ WriteInteger(unwindInfoOffset);
+ if (!mAccumulatedStatus) {
+ return false;
+ }
+
+ return mMMPolicy->AddFunctionTable(mRemoteBase + functionTableOffset, 1,
+ mRemoteBase);
+ }
+
+#endif // _M_X64
+
+ Trampoline<MMPolicy>& operator--() {
+ MOZ_ASSERT(mOffset);
+ --mOffset;
+ return *this;
+ }
+
+ private:
+ void Clear() {
+ if (!mLocalBase || !mPrevLocalProt) {
+ return;
+ }
+
+ DebugOnly<bool> ok = !!::VirtualProtect(mLocalBase, mMaxOffset,
+ mPrevLocalProt, &mPrevLocalProt);
+ MOZ_ASSERT(ok);
+
+ mLocalBase = nullptr;
+ mRemoteBase = 0;
+ mPrevLocalProt = 0;
+ mAccumulatedStatus = false;
+ }
+
+ private:
+ const MMPolicy* mMMPolicy;
+ DWORD mPrevLocalProt;
+ uint8_t* mLocalBase;
+ uintptr_t mRemoteBase;
+ uint32_t mOffset;
+ uint32_t mExeOffset;
+#ifdef _M_X64
+ uint32_t mCopyCodesEndOffset;
+ uint32_t mExeEndOffset;
+#endif // _M_X64
+ uint32_t mMaxOffset;
+ bool mAccumulatedStatus;
+};
+
+template <typename MMPolicy>
+class MOZ_STACK_CLASS TrampolineCollection final {
+ public:
+ class MOZ_STACK_CLASS TrampolineIterator final {
+ public:
+ Trampoline<MMPolicy> operator*() {
+ uint32_t offset = mCurTramp * mCollection.mTrampSize;
+ return Trampoline<MMPolicy>(
+ &mCollection.mMMPolicy, mCollection.mLocalBase + offset,
+ mCollection.mRemoteBase + offset, mCollection.mTrampSize);
+ }
+
+ TrampolineIterator& operator++() {
+ ++mCurTramp;
+ return *this;
+ }
+
+ bool operator!=(const TrampolineIterator& aOther) const {
+ return mCurTramp != aOther.mCurTramp;
+ }
+
+ private:
+ explicit TrampolineIterator(
+ const TrampolineCollection<MMPolicy>& aCollection,
+ const uint32_t aCurTramp = 0)
+ : mCollection(aCollection), mCurTramp(aCurTramp) {}
+
+ const TrampolineCollection<MMPolicy>& mCollection;
+ uint32_t mCurTramp;
+
+ friend class TrampolineCollection<MMPolicy>;
+ };
+
+ explicit TrampolineCollection(const MMPolicy& aMMPolicy)
+ : mMMPolicy(aMMPolicy),
+ mLocalBase(0),
+ mRemoteBase(0),
+ mTrampSize(0),
+ mNumTramps(0),
+ mPrevProt(0),
+ mCS(nullptr) {}
+
+ TrampolineCollection(const MMPolicy& aMMPolicy, uint8_t* const aLocalBase,
+ const uintptr_t aRemoteBase, const uint32_t aTrampSize,
+ const uint32_t aNumTramps)
+ : mMMPolicy(aMMPolicy),
+ mLocalBase(aLocalBase),
+ mRemoteBase(aRemoteBase),
+ mTrampSize(aTrampSize),
+ mNumTramps(aNumTramps),
+ mPrevProt(0),
+ mCS(nullptr) {
+ if (!aNumTramps) {
+ return;
+ }
+
+ BOOL ok = mMMPolicy.Protect(aLocalBase, aNumTramps * aTrampSize,
+ PAGE_EXECUTE_READWRITE, &mPrevProt);
+ if (!ok) {
+ // When destroying a sandboxed process that uses
+ // MITIGATION_DYNAMIC_CODE_DISABLE, we won't be allowed to write to our
+ // executable memory so we just do nothing. If we fail to get access
+ // to memory for any other reason, we still don't want to crash but we
+ // do assert.
+ MOZ_ASSERT(IsDynamicCodeDisabled());
+ mNumTramps = 0;
+ mPrevProt = 0;
+ }
+ }
+
+ ~TrampolineCollection() {
+ if (!mPrevProt) {
+ return;
+ }
+
+ mMMPolicy.Protect(mLocalBase, mNumTramps * mTrampSize, mPrevProt,
+ &mPrevProt);
+
+ if (mCS) {
+ ::LeaveCriticalSection(mCS);
+ }
+ }
+
+ void Lock(CRITICAL_SECTION& aCS) {
+ if (!mPrevProt || mCS) {
+ return;
+ }
+
+ mCS = &aCS;
+ ::EnterCriticalSection(&aCS);
+ }
+
+ TrampolineIterator begin() const {
+ if (!mPrevProt) {
+ return end();
+ }
+
+ return TrampolineIterator(*this);
+ }
+
+ TrampolineIterator end() const {
+ return TrampolineIterator(*this, mNumTramps);
+ }
+
+ TrampolineCollection(const TrampolineCollection&) = delete;
+ TrampolineCollection& operator=(const TrampolineCollection&) = delete;
+ TrampolineCollection& operator=(TrampolineCollection&&) = delete;
+
+ TrampolineCollection(TrampolineCollection&& aOther)
+ : mMMPolicy(aOther.mMMPolicy),
+ mLocalBase(aOther.mLocalBase),
+ mRemoteBase(aOther.mRemoteBase),
+ mTrampSize(aOther.mTrampSize),
+ mNumTramps(aOther.mNumTramps),
+ mPrevProt(aOther.mPrevProt),
+ mCS(aOther.mCS) {
+ aOther.mPrevProt = 0;
+ aOther.mCS = nullptr;
+ }
+
+ private:
+ const MMPolicy& mMMPolicy;
+ uint8_t* const mLocalBase;
+ const uintptr_t mRemoteBase;
+ const uint32_t mTrampSize;
+ uint32_t mNumTramps;
+ uint32_t mPrevProt;
+ CRITICAL_SECTION* mCS;
+
+ friend class TrampolineIterator;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_Trampoline_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h b/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h
new file mode 100644
index 0000000000..8f93f5c1ad
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/VMSharingPolicies.h
@@ -0,0 +1,285 @@
+/* -*- 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_interceptor_VMSharingPolicies_h
+#define mozilla_interceptor_VMSharingPolicies_h
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/Types.h"
+
+namespace mozilla {
+namespace interceptor {
+
+/**
+ * This class is an abstraction of a reservation of virtual address space that
+ * has been obtained from a VMSharingPolicy via the policy's |Reserve| method.
+ *
+ * TrampolinePool allows us to obtain a trampoline without needing to concern
+ * ourselves with the underlying implementation of the VM sharing policy.
+ *
+ * For example, VMSharingPolicyShared delegates to VMSharingPolicyUnique, but
+ * also requires taking a lock before doing so. By invoking |GetNextTrampoline|
+ * on a TrampolinePool, the caller does not need to concern themselves with
+ * that detail.
+ *
+ * We accompolish this with a recursive implementation that provides an inner
+ * TrampolinePool that is provided by the delegated VMSharingPolicy.
+ */
+template <typename VMPolicyT, typename InnerT>
+class MOZ_STACK_CLASS TrampolinePool final {
+ public:
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool(VMPolicyT& aVMPolicy, InnerT&& aInner)
+ : mVMPolicy(aVMPolicy), mInner(std::move(aInner)) {}
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline(mInner);
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB(mInner);
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+ InnerT mInner;
+};
+
+/**
+ * This specialization is the base case for TrampolinePool, and is used by
+ * VMSharingPolicyUnique (since that policy does not delegate anything).
+ */
+template <typename VMPolicyT>
+class MOZ_STACK_CLASS TrampolinePool<VMPolicyT, decltype(nullptr)> final {
+ public:
+ explicit TrampolinePool(VMPolicyT& aVMPolicy) : mVMPolicy(aVMPolicy) {}
+
+ TrampolinePool(TrampolinePool&& aOther) = default;
+
+ TrampolinePool& operator=(TrampolinePool&& aOther) = delete;
+ TrampolinePool(const TrampolinePool&) = delete;
+ TrampolinePool& operator=(const TrampolinePool&) = delete;
+
+ using MMPolicyT = typename VMPolicyT::MMPolicyT;
+
+ Maybe<Trampoline<MMPolicyT>> GetNextTrampoline() {
+ return mVMPolicy.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsInLowest2GB() const {
+ return mVMPolicy.IsTrampolineSpaceInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ VMPolicyT& mVMPolicy;
+};
+
+template <typename MMPolicy>
+class VMSharingPolicyUnique : public MMPolicy {
+ using ThisType = VMSharingPolicyUnique<MMPolicy>;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, decltype(nullptr)>;
+
+ template <typename... Args>
+ explicit VMSharingPolicyUnique(Args&&... aArgs)
+ : MMPolicy(std::forward<Args>(aArgs)...), mNextChunkIndex(0) {}
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = MMPolicy::GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds = MMPolicy::SpanFromPivotAndDistance(
+ len, aPivotAddr, aMaxDistanceFromPivot);
+
+ return Reserve(len, maybeBounds);
+ }
+
+ Maybe<PoolType> Reserve(const uint32_t aSize,
+ const Maybe<Span<const uint8_t>>& aBounds) {
+ uint32_t bytesReserved = MMPolicy::Reserve(aSize, aBounds);
+ if (!bytesReserved) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this));
+ }
+
+ TrampolineCollection<MMPolicy> Items() const {
+ return TrampolineCollection<MMPolicy>(*this, this->GetLocalView(),
+ this->GetRemoteView(), kChunkSize,
+ mNextChunkIndex);
+ }
+
+ void Clear() { mNextChunkIndex = 0; }
+
+ ~VMSharingPolicyUnique() = default;
+
+ VMSharingPolicyUnique(const VMSharingPolicyUnique&) = delete;
+ VMSharingPolicyUnique& operator=(const VMSharingPolicyUnique&) = delete;
+
+ VMSharingPolicyUnique(VMSharingPolicyUnique&& aOther)
+ : MMPolicy(std::move(aOther)), mNextChunkIndex(aOther.mNextChunkIndex) {
+ aOther.mNextChunkIndex = 0;
+ }
+
+ VMSharingPolicyUnique& operator=(VMSharingPolicyUnique&& aOther) {
+ static_cast<MMPolicy&>(*this) = std::move(aOther);
+ mNextChunkIndex = aOther.mNextChunkIndex;
+ aOther.mNextChunkIndex = 0;
+ return *this;
+ }
+
+ protected:
+ // In VMSharingPolicyUnique we do not implement the overload that accepts
+ // an inner trampoline pool, as this policy is expected to be the
+ // implementation of the base case.
+ Maybe<Trampoline<MMPolicy>> GetNextTrampoline() {
+ uint32_t offset = mNextChunkIndex * kChunkSize;
+ if (!this->MaybeCommitNextPage(offset, kChunkSize)) {
+ return Nothing();
+ }
+
+ Trampoline<MMPolicy> result(this, this->GetLocalView() + offset,
+ this->GetRemoteView() + offset, kChunkSize);
+ if (!!result) {
+ ++mNextChunkIndex;
+ }
+
+ return Some(std::move(result));
+ }
+
+ private:
+ uint32_t mNextChunkIndex;
+ static const uint32_t kChunkSize = 128;
+
+ template <typename VMPolicyT, typename FriendT>
+ friend class TrampolinePool;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+// We don't include RangeMap.h until this point because it depends on the
+// TrampolinePool definitions from above.
+#include "mozilla/interceptor/RangeMap.h"
+
+namespace mozilla {
+namespace interceptor {
+
+// We only support this policy for in-proc MMPolicy.
+class MOZ_TRIVIAL_CTOR_DTOR VMSharingPolicyShared : public MMPolicyInProcess {
+ typedef VMSharingPolicyUnique<MMPolicyInProcess> UniquePolicyT;
+ typedef VMSharingPolicyShared ThisType;
+
+ public:
+ using PoolType = TrampolinePool<ThisType, UniquePolicyT::PoolType>;
+ using MMPolicyT = MMPolicyInProcess;
+
+ constexpr VMSharingPolicyShared() {}
+
+ bool ShouldUnhookUponDestruction() const { return false; }
+
+ Maybe<PoolType> Reserve(const uintptr_t aPivotAddr,
+ const uint32_t aMaxDistanceFromPivot) {
+ // Win32 allocates VM addresses at a 64KiB granularity, so we might as well
+ // utilize that entire 64KiB reservation.
+ uint32_t len = this->GetAllocGranularity();
+
+ Maybe<Span<const uint8_t>> maybeBounds =
+ MMPolicyInProcess::SpanFromPivotAndDistance(len, aPivotAddr,
+ aMaxDistanceFromPivot);
+
+ AutoCriticalSection lock(GetCS());
+ VMSharingPolicyUnique<MMPolicyT>* uniquePol = sVMMap.GetPolicy(maybeBounds);
+ MOZ_ASSERT(uniquePol);
+ if (!uniquePol) {
+ return Nothing();
+ }
+
+ Maybe<UniquePolicyT::PoolType> maybeUnique =
+ uniquePol->Reserve(len, maybeBounds);
+ if (!maybeUnique) {
+ return Nothing();
+ }
+
+ return Some(PoolType(*this, std::move(maybeUnique.ref())));
+ }
+
+ TrampolineCollection<MMPolicyInProcess> Items() const {
+ // Since ShouldUnhookUponDestruction returns false, this can be empty
+ return TrampolineCollection<MMPolicyInProcess>(*this);
+ }
+
+ void Clear() {
+ // This must be a no-op for shared VM policy; we can't have one interceptor
+ // wiping out trampolines for all interceptors in the process.
+ }
+
+ VMSharingPolicyShared(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared(VMSharingPolicyShared&&) = delete;
+ VMSharingPolicyShared& operator=(const VMSharingPolicyShared&) = delete;
+ VMSharingPolicyShared& operator=(VMSharingPolicyShared&&) = delete;
+
+ private:
+ static CRITICAL_SECTION* GetCS() {
+ static const bool isAlloc = []() -> bool {
+ DWORD flags = 0;
+#if defined(RELEASE_OR_BETA)
+ flags |= CRITICAL_SECTION_NO_DEBUG_INFO;
+#endif // defined(RELEASE_OR_BETA)
+ ::InitializeCriticalSectionEx(&sCS, 4000, flags);
+ return true;
+ }();
+ Unused << isAlloc;
+
+ return &sCS;
+ }
+
+ // In VMSharingPolicyShared, we only implement the overload that accepts
+ // a VMSharingPolicyUnique trampoline pool as |aInner|, since we require the
+ // former policy to wrap the latter.
+ Maybe<Trampoline<MMPolicyInProcess>> GetNextTrampoline(
+ UniquePolicyT::PoolType& aInner) {
+ AutoCriticalSection lock(GetCS());
+ return aInner.GetNextTrampoline();
+ }
+
+#if defined(_M_X64)
+ bool IsTrampolineSpaceInLowest2GB(
+ const UniquePolicyT::PoolType& aInner) const {
+ AutoCriticalSection lock(GetCS());
+ return aInner.IsInLowest2GB();
+ }
+#endif // defined(_M_X64)
+
+ private:
+ template <typename VMPolicyT, typename InnerT>
+ friend class TrampolinePool;
+
+ inline static RangeMap<MMPolicyInProcess> sVMMap;
+ inline static CRITICAL_SECTION sCS;
+};
+
+} // namespace interceptor
+} // namespace mozilla
+
+#endif // mozilla_interceptor_VMSharingPolicies_h
diff --git a/toolkit/xre/dllservices/mozglue/interceptor/moz.build b/toolkit/xre/dllservices/mozglue/interceptor/moz.build
new file mode 100644
index 0000000000..561e33b147
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/interceptor/moz.build
@@ -0,0 +1,26 @@
+# -*- 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/.
+
+EXPORTS.mozilla.interceptor += [
+ "Arm64.h",
+ "MMPolicies.h",
+ "PatcherBase.h",
+ "PatcherDetour.h",
+ "PatcherNopSpace.h",
+ "RangeMap.h",
+ "TargetFunction.h",
+ "Trampoline.h",
+ "VMSharingPolicies.h",
+]
+
+if CONFIG["CPU_ARCH"] == "aarch64":
+ Library("interceptor")
+
+ FINAL_LIBRARY = "mozglue"
+
+ UNIFIED_SOURCES += [
+ "Arm64.cpp",
+ ]
diff --git a/toolkit/xre/dllservices/mozglue/moz.build b/toolkit/xre/dllservices/mozglue/moz.build
new file mode 100644
index 0000000000..ea7b0eb7d5
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/moz.build
@@ -0,0 +1,80 @@
+# -*- 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/.
+
+Library("dllservices_mozglue")
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"]:
+ SOURCES += [
+ # This file contains a |using namespace mozilla;| statement
+ "WindowsDllBlocklist.cpp",
+ ]
+
+ UNIFIED_SOURCES += [
+ "Authenticode.cpp",
+ "LoaderObserver.cpp",
+ "ModuleLoadFrame.cpp",
+ "WindowsFallbackLoaderAPI.cpp",
+ ]
+
+if not CONFIG["JS_STANDALONE"]:
+ UNIFIED_SOURCES += [
+ "CacheNtDllThunk.cpp",
+ ]
+
+OS_LIBS += [
+ "crypt32",
+ "ntdll",
+ "version",
+ "wintrust",
+]
+
+DELAYLOAD_DLLS += [
+ "crypt32.dll",
+ "wintrust.dll",
+]
+
+EXPORTS += [
+ "nsWindowsDllInterceptor.h",
+]
+
+EXPORTS.mozilla += [
+ "Authenticode.h",
+ "CacheNtDllThunk.h",
+ "LoaderAPIInterfaces.h",
+ "ModuleLoadInfo.h",
+ "WindowsDllBlocklist.h",
+ "WindowsDllBlocklistCommon.h",
+ "WindowsDllBlocklistInfo.h",
+]
+
+EXPORTS.mozilla.glue += [
+ "SharedSection.h",
+ "WindowsDllServices.h",
+]
+
+# Generate DLL Blocklists
+blocklist_header_types = ["A11y", "Launcher", "Legacy", "Test"]
+blocklist_file_leaf_tpl = "WindowsDllBlocklist{0}Defs.h"
+blocklist_files = tuple(
+ [blocklist_file_leaf_tpl.format(type) for type in blocklist_header_types]
+)
+
+GeneratedFile(
+ *blocklist_files,
+ script="gen_dll_blocklist_defs.py",
+ entry_point="gen_blocklists",
+ inputs=["WindowsDllBlocklistDefs.in"]
+)
+
+EXPORTS.mozilla += ["!" + hdr for hdr in blocklist_files]
+
+DIRS += [
+ "interceptor",
+]
+
+FINAL_LIBRARY = "mozglue"
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h b/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h
new file mode 100644
index 0000000000..d6afc9bb7f
--- /dev/null
+++ b/toolkit/xre/dllservices/mozglue/nsWindowsDllInterceptor.h
@@ -0,0 +1,819 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef NS_WINDOWS_DLL_INTERCEPTOR_H_
+#define NS_WINDOWS_DLL_INTERCEPTOR_H_
+
+#include <wchar.h>
+#include <windows.h>
+#include <winternl.h>
+
+#include <utility>
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/Atomics.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/CheckedInt.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/Tuple.h"
+#include "mozilla/Types.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/Vector.h"
+#include "mozilla/interceptor/MMPolicies.h"
+#include "mozilla/interceptor/PatcherDetour.h"
+#include "mozilla/interceptor/PatcherNopSpace.h"
+#include "mozilla/interceptor/VMSharingPolicies.h"
+#include "nsWindowsHelpers.h"
+
+/*
+ * Simple function interception.
+ *
+ * We have two separate mechanisms for intercepting a function: We can use the
+ * built-in nop space, if it exists, or we can create a detour.
+ *
+ * Using the built-in nop space works as follows: On x86-32, DLL functions
+ * begin with a two-byte nop (mov edi, edi) and are preceeded by five bytes of
+ * NOP instructions.
+ *
+ * When we detect a function with this prelude, we do the following:
+ *
+ * 1. Write a long jump to our interceptor function into the five bytes of NOPs
+ * before the function.
+ *
+ * 2. Write a short jump -5 into the two-byte nop at the beginning of the
+ * function.
+ *
+ * This mechanism is nice because it's thread-safe. It's even safe to do if
+ * another thread is currently running the function we're modifying!
+ *
+ * When the WindowsDllNopSpacePatcher is destroyed, we overwrite the short jump
+ * but not the long jump, so re-intercepting the same function won't work,
+ * because its prelude won't match.
+ *
+ *
+ * Unfortunately nop space patching doesn't work on functions which don't have
+ * this magic prelude (and in particular, x86-64 never has the prelude). So
+ * when we can't use the built-in nop space, we fall back to using a detour,
+ * which works as follows:
+ *
+ * 1. Save first N bytes of OrigFunction to trampoline, where N is a
+ * number of bytes >= 5 that are instruction aligned.
+ *
+ * 2. Replace first 5 bytes of OrigFunction with a jump to the Hook
+ * function.
+ *
+ * 3. After N bytes of the trampoline, add a jump to OrigFunction+N to
+ * continue original program flow.
+ *
+ * 4. Hook function needs to call the trampoline during its execution,
+ * to invoke the original function (so address of trampoline is
+ * returned).
+ *
+ * When the WindowsDllDetourPatcher object is destructed, OrigFunction is
+ * patched again to jump directly to the trampoline instead of going through
+ * the hook function. As such, re-intercepting the same function won't work, as
+ * jump instructions are not supported.
+ *
+ * Note that this is not thread-safe. Sad day.
+ *
+ */
+
+#if defined(_M_IX86) && defined(__clang__) && __has_declspec_attribute(guard)
+// On x86, nop-space patches return to the second instruction of their target.
+// This is a deliberate violation of Control Flow Guard, so disable the check.
+# define INTERCEPTOR_DISABLE_CFGUARD __declspec(guard(nocf))
+#else
+# define INTERCEPTOR_DISABLE_CFGUARD /* nothing */
+#endif
+
+namespace mozilla {
+namespace interceptor {
+
+template <typename T>
+struct OriginalFunctionPtrTraits;
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R (*)(Args...)> {
+ using ReturnType = R;
+};
+
+#if defined(_M_IX86)
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__stdcall*)(Args...)> {
+ using ReturnType = R;
+};
+
+template <typename R, typename... Args>
+struct OriginalFunctionPtrTraits<R(__fastcall*)(Args...)> {
+ using ReturnType = R;
+};
+#endif // defined(_M_IX86)
+
+template <typename InterceptorT, typename FuncPtrT>
+class FuncHook final {
+ public:
+ using ThisType = FuncHook<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook() : mOrigFunc(nullptr), mInitOnce(INIT_ONCE_STATIC_INIT) {}
+
+ ~FuncHook() = default;
+
+ bool Set(InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, false);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ bool SetDetour(InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, &aInterceptor, aName, aHookDest, true);
+
+ return ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ INTERCEPTOR_DISABLE_CFGUARD ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, InterceptorT* aInterceptor,
+ const char* aName, FuncPtrT aHookDest, bool aForceDetour)
+ : mHook(aHook),
+ mInterceptor(aInterceptor),
+ mName(aName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)),
+ mForceDetour(aForceDetour) {}
+
+ ThisType* mHook;
+ InterceptorT* mInterceptor;
+ const char* mName;
+ void* mHookDest;
+ bool mForceDetour;
+ };
+
+ private:
+ bool Apply(InterceptorT* aInterceptor, const char* aName, void* aHookDest) {
+ return aInterceptor->AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ bool ApplyDetour(InterceptorT* aInterceptor, const char* aName,
+ void* aHookDest) {
+ return aInterceptor->AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ bool result;
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ if (ctx->mForceDetour) {
+ result = ctx->mHook->ApplyDetour(ctx->mInterceptor, ctx->mName,
+ ctx->mHookDest);
+ } else {
+ result = ctx->mHook->Apply(ctx->mInterceptor, ctx->mName, ctx->mHookDest);
+ }
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+ INIT_ONCE mInitOnce;
+};
+
+template <typename InterceptorT, typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS FuncHookCrossProcess final {
+ public:
+ using ThisType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+#if defined(DEBUG)
+ FuncHookCrossProcess() {}
+#endif // defined(DEBUG)
+
+ bool Set(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName, FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddHook(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ bool SetDetour(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, const char* aName,
+ FuncPtrT aHookDest) {
+ FuncPtrT origFunc;
+ if (!aInterceptor.AddDetour(aName, reinterpret_cast<intptr_t>(aHookDest),
+ reinterpret_cast<void**>(&origFunc))) {
+ return false;
+ }
+
+ return CopyStubToChildProcess(aTransferMgr, aInterceptor, origFunc);
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ /**
+ * NB: This operator is only meaningful when invoked in the target process!
+ */
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+#if defined(DEBUG)
+ FuncHookCrossProcess(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess(FuncHookCrossProcess&&) = delete;
+ FuncHookCrossProcess& operator=(const FuncHookCrossProcess&) = delete;
+ FuncHookCrossProcess& operator=(FuncHookCrossProcess&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ bool CopyStubToChildProcess(nt::CrossExecTransferManager& aTransferMgr,
+ InterceptorT& aInterceptor, FuncPtrT aStub) {
+ LauncherVoidResult writeResult =
+ aTransferMgr.Transfer(&mOrigFunc, &aStub, sizeof(FuncPtrT));
+ if (writeResult.isErr()) {
+#ifdef MOZ_USE_LAUNCHER_ERROR
+ const mozilla::WindowsError& err = writeResult.inspectErr().mError;
+#else
+ const mozilla::WindowsError& err = writeResult.inspectErr();
+#endif
+ aInterceptor.SetLastDetourError(FUNCHOOKCROSSPROCESS_COPYSTUB_ERROR,
+ err.AsHResult());
+ return false;
+ }
+ return true;
+ }
+
+ private:
+ FuncPtrT mOrigFunc;
+};
+
+template <typename MMPolicyT, typename InterceptorT>
+struct TypeResolver;
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyInProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<InterceptorT, FuncPtrT>;
+};
+
+template <typename InterceptorT>
+struct TypeResolver<mozilla::interceptor::MMPolicyOutOfProcess, InterceptorT> {
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHookCrossProcess<InterceptorT, FuncPtrT>;
+};
+
+template <typename VMPolicy = mozilla::interceptor::VMSharingPolicyShared>
+class WindowsDllInterceptor final
+ : public TypeResolver<typename VMPolicy::MMPolicyT,
+ WindowsDllInterceptor<VMPolicy>> {
+ typedef WindowsDllInterceptor<VMPolicy> ThisType;
+
+ interceptor::WindowsDllDetourPatcher<VMPolicy> mDetourPatcher;
+#if defined(_M_IX86)
+ interceptor::WindowsDllNopSpacePatcher<typename VMPolicy::MMPolicyT>
+ mNopSpacePatcher;
+#endif // defined(_M_IX86)
+
+ HMODULE mModule;
+
+ public:
+ template <typename... Args>
+ explicit WindowsDllInterceptor(Args&&... aArgs)
+ : mDetourPatcher(std::forward<Args>(aArgs)...)
+#if defined(_M_IX86)
+ ,
+ mNopSpacePatcher(std::forward<Args>(aArgs)...)
+#endif // defined(_M_IX86)
+ ,
+ mModule(nullptr) {
+ }
+
+ WindowsDllInterceptor(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor(WindowsDllInterceptor&&) = delete;
+ WindowsDllInterceptor& operator=(const WindowsDllInterceptor&) = delete;
+ WindowsDllInterceptor& operator=(WindowsDllInterceptor&&) = delete;
+
+ ~WindowsDllInterceptor() { Clear(); }
+
+ template <size_t N>
+ void Init(const char (&aModuleName)[N]) {
+ wchar_t moduleName[N];
+
+ for (size_t i = 0; i < N; ++i) {
+ MOZ_ASSERT(!(aModuleName[i] & 0x80),
+ "Use wide-character overload for non-ASCII module names");
+ moduleName[i] = aModuleName[i];
+ }
+
+ Init(moduleName);
+ }
+
+ void Init(const wchar_t* aModuleName) {
+ if (mModule) {
+ return;
+ }
+
+ mModule = ::LoadLibraryW(aModuleName);
+ }
+
+ /** Force a specific configuration for testing purposes. NOT to be used in
+ production code! **/
+ void TestOnlyDetourInit(const wchar_t* aModuleName, DetourFlags aFlags) {
+ Init(aModuleName);
+ mDetourPatcher.Init(aFlags);
+ }
+
+ void Clear() {
+ if (!mModule) {
+ return;
+ }
+
+#if defined(_M_IX86)
+ mNopSpacePatcher.Clear();
+#endif // defined(_M_IX86)
+ mDetourPatcher.Clear();
+
+ // NB: We intentionally leak mModule
+ }
+
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& GetLastDetourError() const {
+ return mDetourPatcher.GetLastDetourError();
+ }
+#endif // defined(NIGHTLY_BUILD)
+ template <typename... Args>
+ void SetLastDetourError(Args&&... aArgs) {
+ return mDetourPatcher.SetLastDetourError(std::forward<Args>(aArgs)...);
+ }
+
+ constexpr static uint32_t GetWorstCaseRequiredBytesToPatch() {
+ return WindowsDllDetourPatcherPrimitive<
+ typename VMPolicy::MMPolicyT>::GetWorstCaseRequiredBytesToPatch();
+ }
+
+ private:
+ /**
+ * Hook/detour the method aName from the DLL we set in Init so that it calls
+ * aHookDest instead. Returns the original method pointer in aOrigFunc
+ * and returns true if successful.
+ *
+ * IMPORTANT: If you use this method, please add your case to the
+ * TestDllInterceptor in order to detect future failures. Even if this
+ * succeeds now, updates to the hooked DLL could cause it to fail in
+ * the future.
+ */
+ bool AddHook(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
+ // Use a nop space patch if possible, otherwise fall back to a detour.
+ // This should be the preferred method for adding hooks.
+ if (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+#if defined(_M_IX86)
+ if (mNopSpacePatcher.AddHook(proc, aHookDest, aOrigFunc)) {
+ return true;
+ }
+#endif // defined(_M_IX86)
+
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+ /**
+ * Detour the method aName from the DLL we set in Init so that it calls
+ * aHookDest instead. Returns the original method pointer in aOrigFunc
+ * and returns true if successful.
+ *
+ * IMPORTANT: If you use this method, please add your case to the
+ * TestDllInterceptor in order to detect future failures. Even if this
+ * succeeds now, updates to the detoured DLL could cause it to fail in
+ * the future.
+ */
+ bool AddDetour(const char* aName, intptr_t aHookDest, void** aOrigFunc) {
+ // Generally, code should not call this method directly. Use AddHook unless
+ // there is a specific need to avoid nop space patches.
+ if (!mModule) {
+ mDetourPatcher.SetLastDetourError(DetourResultCode::INTERCEPTOR_MOD_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(
+ nt::PEHeaders::HModuleToBaseAddr<uintptr_t>(mModule))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_MOD_INACCESSIBLE);
+ return false;
+ }
+
+ FARPROC proc = mDetourPatcher.GetProcAddress(mModule, aName);
+ if (!proc) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_NULL);
+ return false;
+ }
+
+ if (!mDetourPatcher.IsPageAccessible(reinterpret_cast<uintptr_t>(proc))) {
+ mDetourPatcher.SetLastDetourError(
+ DetourResultCode::INTERCEPTOR_PROC_INACCESSIBLE);
+ return false;
+ }
+
+ return AddDetour(proc, aHookDest, aOrigFunc);
+ }
+
+ bool AddDetour(FARPROC aProc, intptr_t aHookDest, void** aOrigFunc) {
+ MOZ_ASSERT(mModule && aProc);
+
+ if (!mDetourPatcher.Initialized()) {
+ DetourFlags flags = DetourFlags::eDefault;
+#if defined(_M_X64)
+ // NTDLL hooks should attempt to use a 10-byte patch because some
+ // injected DLLs do the same and interfere with our stuff.
+ bool needs10BytePatch = (mModule == ::GetModuleHandleW(L"ntdll.dll"));
+
+ bool isWin8Or81 = IsWin8OrLater() && (!IsWin10OrLater());
+ bool isWin8 = IsWin8OrLater() && (!IsWin8Point1OrLater());
+
+ bool isKernel32Dll = (mModule == ::GetModuleHandleW(L"kernel32.dll"));
+
+ bool isDuplicateHandle = (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::DuplicateHandle));
+
+ // CloseHandle on Windows 8/8.1 only accomodates 10-byte patches.
+ needs10BytePatch |= isWin8Or81 && isKernel32Dll &&
+ (reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&CloseHandle));
+
+ // CreateFileA and DuplicateHandle on Windows 8 require 10-byte patches.
+ needs10BytePatch |= isWin8 && isKernel32Dll &&
+ ((reinterpret_cast<void*>(aProc) ==
+ reinterpret_cast<void*>(&::CreateFileA)) ||
+ isDuplicateHandle);
+
+ if (needs10BytePatch) {
+ flags |= DetourFlags::eEnable10BytePatch;
+ }
+
+ if (isWin8 && isDuplicateHandle) {
+ // Because we can't detour Win8's KERNELBASE!DuplicateHandle,
+ // we detour kernel32!DuplicateHandle (See bug 1659398).
+ flags |= DetourFlags::eDontResolveRedirection;
+ }
+#endif // defined(_M_X64)
+
+ mDetourPatcher.Init(flags);
+ }
+
+ return mDetourPatcher.AddHook(aProc, aHookDest, aOrigFunc);
+ }
+
+ private:
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHookCrossProcess;
+};
+
+/**
+ * IAT patching is intended for use when we only want to intercept a function
+ * call originating from a specific module.
+ */
+class WindowsIATPatcher final {
+ public:
+ template <typename FuncPtrT>
+ using FuncHookType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+
+ private:
+ static bool CheckASCII(const char* aInStr) {
+ while (*aInStr) {
+ if (*aInStr & 0x80) {
+ return false;
+ }
+ ++aInStr;
+ }
+ return true;
+ }
+
+ static bool AddHook(HMODULE aFromModule, const char* aToModuleName,
+ const char* aTargetFnName, void* aHookDest,
+ Atomic<void*>* aOutOrigFunc) {
+ if (!aFromModule || !aToModuleName || !aTargetFnName || !aOutOrigFunc) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported module names
+ const bool isModuleNameAscii = CheckASCII(aToModuleName);
+ MOZ_ASSERT(isModuleNameAscii);
+ if (!isModuleNameAscii) {
+ return false;
+ }
+
+ // PE Spec requires ASCII names for imported function names
+ const bool isTargetFnNameAscii = CheckASCII(aTargetFnName);
+ MOZ_ASSERT(isTargetFnNameAscii);
+ if (!isTargetFnNameAscii) {
+ return false;
+ }
+
+ nt::PEHeaders headers(aFromModule);
+ if (!headers) {
+ return false;
+ }
+
+ PIMAGE_IMPORT_DESCRIPTOR impDesc =
+ headers.GetImportDescriptor(aToModuleName);
+ if (!nt::PEHeaders::IsValid(impDesc)) {
+ // Either aFromModule does not import aToModuleName at load-time, or
+ // aToModuleName is a (currently unsupported) delay-load import.
+ return false;
+ }
+
+ // Resolve the import name table (INT).
+ auto firstINTThunk = headers.template RVAToPtr<PIMAGE_THUNK_DATA>(
+ impDesc->OriginalFirstThunk);
+ if (!nt::PEHeaders::IsValid(firstINTThunk)) {
+ return false;
+ }
+
+ Maybe<ptrdiff_t> thunkIndex;
+
+ // Scan the INT for the location of the thunk for the function named
+ // 'aTargetFnName'.
+ for (PIMAGE_THUNK_DATA curINTThunk = firstINTThunk;
+ nt::PEHeaders::IsValid(curINTThunk); ++curINTThunk) {
+ if (IMAGE_SNAP_BY_ORDINAL(curINTThunk->u1.Ordinal)) {
+ // Currently not supporting import by ordinal; this isn't hard to add,
+ // but we won't bother unless necessary.
+ continue;
+ }
+
+ PIMAGE_IMPORT_BY_NAME curThunkFnName =
+ headers.template RVAToPtr<PIMAGE_IMPORT_BY_NAME>(
+ curINTThunk->u1.AddressOfData);
+ MOZ_ASSERT(curThunkFnName);
+ if (!curThunkFnName) {
+ // Looks like we have a bad name descriptor. Try to continue.
+ continue;
+ }
+
+ // Function name checks MUST be case-sensitive!
+ if (!strcmp(aTargetFnName, curThunkFnName->Name)) {
+ // We found the thunk. Save the index of this thunk, as the IAT thunk
+ // is located at the same index in that table as in the INT.
+ thunkIndex = Some(curINTThunk - firstINTThunk);
+ break;
+ }
+ }
+
+ if (thunkIndex.isNothing()) {
+ // We never found a thunk for that function. Perhaps it's not imported?
+ return false;
+ }
+
+ if (thunkIndex.value() < 0) {
+ // That's just wrong.
+ return false;
+ }
+
+ auto firstIATThunk =
+ headers.template RVAToPtr<PIMAGE_THUNK_DATA>(impDesc->FirstThunk);
+ if (!nt::PEHeaders::IsValid(firstIATThunk)) {
+ return false;
+ }
+
+ // Resolve the IAT thunk for the function we want
+ PIMAGE_THUNK_DATA targetThunk = &firstIATThunk[thunkIndex.value()];
+ if (!nt::PEHeaders::IsValid(targetThunk)) {
+ return false;
+ }
+
+ auto fnPtr = reinterpret_cast<Atomic<void*>*>(&targetThunk->u1.Function);
+
+ // Now we can just change out its pointer with our hook function.
+ AutoVirtualProtect prot(fnPtr, sizeof(void*), PAGE_EXECUTE_READWRITE);
+ if (!prot) {
+ return false;
+ }
+
+ // We do the exchange this way to ensure that *aOutOrigFunc is always valid
+ // once the atomic exchange has taken place.
+ void* tmp;
+
+ do {
+ tmp = *fnPtr;
+ *aOutOrigFunc = tmp;
+ } while (!fnPtr->compareExchange(tmp, aHookDest));
+
+ return true;
+ }
+
+ template <typename InterceptorT, typename FuncPtrT>
+ friend class FuncHook;
+};
+
+template <typename FuncPtrT>
+class MOZ_ONLY_USED_TO_AVOID_STATIC_CONSTRUCTORS
+ FuncHook<WindowsIATPatcher, FuncPtrT>
+ final {
+ public:
+ using ThisType = FuncHook<WindowsIATPatcher, FuncPtrT>;
+ using ReturnType = typename OriginalFunctionPtrTraits<FuncPtrT>::ReturnType;
+
+ constexpr FuncHook()
+ : mInitOnce(INIT_ONCE_STATIC_INIT),
+ mFromModule(nullptr),
+ mOrigFunc(nullptr) {}
+
+#if defined(DEBUG)
+ ~FuncHook() = default;
+#endif // defined(DEBUG)
+
+ bool Set(const wchar_t* aFromModuleName, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ nsModuleHandle fromModule(::LoadLibraryW(aFromModuleName));
+ if (!fromModule) {
+ return false;
+ }
+
+ return Set(fromModule, aToModuleName, aFnName, aHookDest);
+ }
+
+ // We offer this overload in case the client wants finer-grained control over
+ // loading aFromModule.
+ bool Set(nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, FuncPtrT aHookDest) {
+ LPVOID addHookOk = nullptr;
+ InitOnceContext ctx(this, aFromModule, aToModuleName, aFnName, aHookDest);
+
+ bool result = ::InitOnceExecuteOnce(&mInitOnce, &InitOnceCallback, &ctx,
+ &addHookOk) &&
+ addHookOk;
+ if (!result) {
+ return result;
+ }
+
+ // If we successfully set the hook then we must retain a strong reference
+ // to the module that we modified.
+ mFromModule = aFromModule.disown();
+ return result;
+ }
+
+ explicit operator bool() const { return !!mOrigFunc; }
+
+ template <typename... ArgsType>
+ ReturnType operator()(ArgsType&&... aArgs) const {
+ return mOrigFunc(std::forward<ArgsType>(aArgs)...);
+ }
+
+ FuncPtrT GetStub() const { return mOrigFunc; }
+
+#if defined(DEBUG)
+ // One-time init stuff cannot be moved or copied
+ FuncHook(const FuncHook&) = delete;
+ FuncHook(FuncHook&&) = delete;
+ FuncHook& operator=(const FuncHook&) = delete;
+ FuncHook& operator=(FuncHook&& aOther) = delete;
+#endif // defined(DEBUG)
+
+ private:
+ struct MOZ_RAII InitOnceContext final {
+ InitOnceContext(ThisType* aHook, const nsModuleHandle& aFromModule,
+ const char* aToModuleName, const char* aFnName,
+ FuncPtrT aHookDest)
+ : mHook(aHook),
+ mFromModule(aFromModule),
+ mToModuleName(aToModuleName),
+ mFnName(aFnName),
+ mHookDest(reinterpret_cast<void*>(aHookDest)) {}
+
+ ThisType* mHook;
+ const nsModuleHandle& mFromModule;
+ const char* mToModuleName;
+ const char* mFnName;
+ void* mHookDest;
+ };
+
+ private:
+ bool Apply(const nsModuleHandle& aFromModule, const char* aToModuleName,
+ const char* aFnName, void* aHookDest) {
+ return WindowsIATPatcher::AddHook(
+ aFromModule, aToModuleName, aFnName, aHookDest,
+ reinterpret_cast<Atomic<void*>*>(&mOrigFunc));
+ }
+
+ static BOOL CALLBACK InitOnceCallback(PINIT_ONCE aInitOnce, PVOID aParam,
+ PVOID* aOutContext) {
+ MOZ_ASSERT(aOutContext);
+
+ auto ctx = reinterpret_cast<InitOnceContext*>(aParam);
+ bool result = ctx->mHook->Apply(ctx->mFromModule, ctx->mToModuleName,
+ ctx->mFnName, ctx->mHookDest);
+
+ *aOutContext =
+ result ? reinterpret_cast<PVOID>(1U << INIT_ONCE_CTX_RESERVED_BITS)
+ : nullptr;
+ return TRUE;
+ }
+
+ private:
+ INIT_ONCE mInitOnce;
+ HMODULE mFromModule; // never freed
+ FuncPtrT mOrigFunc;
+};
+
+/**
+ * This class applies an irreversible patch to jump to a target function
+ * without backing up the original function.
+ */
+class WindowsDllEntryPointInterceptor final {
+ using DllMainFn = BOOL(WINAPI*)(HINSTANCE, DWORD, LPVOID);
+ using MMPolicyT = MMPolicyInProcessEarlyStage;
+
+ MMPolicyT mMMPolicy;
+
+ public:
+ explicit WindowsDllEntryPointInterceptor(
+ const MMPolicyT::Kernel32Exports& aK32Exports)
+ : mMMPolicy(aK32Exports) {}
+
+ bool Set(const nt::PEHeaders& aHeaders, DllMainFn aDestination) {
+ if (!aHeaders) {
+ return false;
+ }
+
+ WindowsDllDetourPatcherPrimitive<MMPolicyT> patcher;
+ return patcher.AddIrreversibleHook(
+ mMMPolicy, aHeaders.GetEntryPoint(),
+ reinterpret_cast<uintptr_t>(aDestination));
+ }
+};
+
+} // namespace interceptor
+
+using WindowsDllInterceptor = interceptor::WindowsDllInterceptor<>;
+
+using CrossProcessDllInterceptor = interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyOutOfProcess>>;
+
+using WindowsIATPatcher = interceptor::WindowsIATPatcher;
+
+} // namespace mozilla
+
+#endif /* NS_WINDOWS_DLL_INTERCEPTOR_H_ */
diff --git a/toolkit/xre/dllservices/tests/AssemblyPayloads.h b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
new file mode 100644
index 0000000000..811bf88412
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/AssemblyPayloads.h
@@ -0,0 +1,241 @@
+/* -*- 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/. */
+
+/* These assembly functions represent patterns that were already hooked by
+ * another application before our detour.
+ */
+
+#ifndef mozilla_AssemblyPayloads_h
+#define mozilla_AssemblyPayloads_h
+
+#include <cstdint>
+
+#define PADDING_256_NOP \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;" \
+ "nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;nop;"
+
+extern "C" {
+
+#if defined(__clang__)
+# if defined(_M_X64)
+constexpr uintptr_t JumpDestination = 0x7fff00000000;
+
+__declspec(dllexport) __attribute__((naked)) void MovPushRet() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "push %%rax;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovRaxJump() {
+ asm volatile(
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void NearJump() {
+ asm volatile(
+ "jae label3;"
+ "je label3;"
+ "jne label3;"
+
+ "label4:"
+ "mov %0, %%rax;"
+ "jmpq *%%rax;"
+
+ // 0x100 bytes padding to generate jae rel32 instead of jae rel8
+ PADDING_256_NOP
+
+ "label3:"
+ "jmp label4;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void OpcodeFF() {
+ // Skip PUSH (FF /6) because clang prefers Opcode 50+rd
+ // to translate PUSH r64 rather than Opcode FF.
+ asm volatile(
+ "incl %eax;"
+ "decl %ebx;"
+ "call *%rcx;"
+ "jmp *(%rip);" // Indirect jump to 0xcccccccc`cccccccc
+ "int $3;int $3;int $3;int $3;"
+ "int $3;int $3;int $3;int $3;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void IndirectCall() {
+ asm volatile(
+ "call *(%rip);" // Indirect call to 0x90909090`90909090
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "ret;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovImm64() {
+ asm volatile(
+ "mov $0x1234567812345678, %r10;"
+ "nop;nop;nop");
+}
+
+# if !defined(MOZ_CODE_COVERAGE)
+// This code reproduces bug 1798787: it uses the same prologue, the same unwind
+// info, and it has a call instruction that starts within the 13 first bytes.
+__attribute__((naked)) void DetouredCallCode(uintptr_t aCallee) {
+ asm volatile(
+ "subq $0x28, %rsp;"
+ "testq %rcx, %rcx;"
+ "jz exit;"
+ "callq *%rcx;"
+ "exit:"
+ "addq $0x28, %rsp;"
+ "retq;");
+}
+constexpr uint8_t gDetouredCallCodeSize = 16; // size of function in bytes
+alignas(uint32_t) uint8_t gDetouredCallUnwindInfo[] = {
+ 0x01, // Version (1), Flags (0)
+ 0x04, // SizeOfProlog (4)
+ 0x01, // CountOfUnwindCodes (1)
+ 0x00, // FrameRegister (0), FrameOffset (0)
+ // UnwindCodes[0]
+ 0x04, // .OffsetInProlog (4)
+ 0x42, // .UnwindOpCode(UWOP_ALLOC_SMALL=2), .UnwindInfo (4)
+};
+
+// This points to the same code as DetouredCallCode, but dynamically generated
+// so that it can have custom unwinding info. See TestDllInterceptor.cpp.
+extern decltype(&DetouredCallCode) gDetouredCall;
+
+// This is just a jumper: our hooking code will thus detour the jump target
+// DetouredCall -- it will not detour DetouredCallJumper. We need to do this to
+// point our hooking code to the dynamic code, because our hooking API needs an
+// exported function name.
+//
+// guard(nocf) ensures that our generated code is a recognized jumper:
+// jmp qword ptr [rip+offset DetouredCall]
+// rather than:
+// mov rax, qword ptr [rip+offset DetouredCall]
+// mov rdx, qword ptr [rip+offset __guard_dispatch_icall_fptr]
+// jmp rdx
+__declspec(dllexport noinline guard(nocf)) void DetouredCallJumper(
+ uintptr_t aCallee) {
+ gDetouredCall(aCallee);
+}
+# endif // !defined(MOZ_CODE_COVERAGE)
+
+# elif defined(_M_IX86)
+constexpr uintptr_t JumpDestination = 0x7fff0000;
+
+__declspec(dllexport) __attribute__((naked)) void PushRet() {
+ asm volatile(
+ "push %0;"
+ "ret;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void MovEaxJump() {
+ asm volatile(
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+ :
+ : "i"(JumpDestination));
+}
+
+__declspec(dllexport) __attribute__((naked)) void Opcode83() {
+ asm volatile(
+ "xor $0x42, %eax;"
+ "cmpl $1, 0xc(%ebp);");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LockPrefix() {
+ // Test an instruction with a LOCK prefix (0xf0) at a non-zero offset
+ asm volatile(
+ "push $0x7c;"
+ "lock push $0x7c;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void LooksLikeLockPrefix() {
+ // This is for a regression scenario of bug 1625452, where we double-counted
+ // the offset in CountPrefixBytes. When we count prefix bytes in front of
+ // the 2nd PUSH located at offset 2, we mistakenly started counting from
+ // the byte 0xf0 at offset 4, which is considered as LOCK, thus we try to
+ // detour the next byte 0xcc and it fails.
+ //
+ // 0: 6a7c push 7Ch
+ // 2: 68ccf00000 push 0F0CCh
+ //
+ asm volatile(
+ "push $0x7c;"
+ "push $0x0000f0cc;");
+}
+
+__declspec(dllexport) __attribute__((naked)) void DoubleJump() {
+ asm volatile(
+ "jmp label1;"
+
+ "label2:"
+ "mov %0, %%eax;"
+ "jmp *%%eax;"
+
+ // 0x100 bytes padding to generate jmp rel32 instead of jmp rel8
+ PADDING_256_NOP
+
+ "label1:"
+ "jmp label2;"
+ :
+ : "i"(JumpDestination));
+}
+# endif
+
+# if !defined(_M_ARM64)
+__declspec(dllexport) __attribute__((naked)) void UnsupportedOp() {
+ asm volatile(
+ "ud2;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;"
+ "nop;nop;nop;nop;nop;nop;nop;nop;");
+}
+# endif // !defined(_M_ARM64)
+
+#endif // defined(__clang__)
+
+} // extern "C"
+
+#endif // mozilla_AssemblyPayloads_h
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
new file mode 100644
index 0000000000..3c58132a34
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.cpp
@@ -0,0 +1,1416 @@
+/* -*- 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 <shlobj.h>
+#include <stdio.h>
+#include <commdlg.h>
+#define SECURITY_WIN32
+#include <security.h>
+#include <wininet.h>
+#include <schnlsp.h>
+#include <winternl.h>
+#include <processthreadsapi.h>
+
+#include "AssemblyPayloads.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/WindowsVersion.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include "mozilla/MozProcessMitigationDynamicCodePolicy.h"
+
+NTSTATUS NTAPI NtFlushBuffersFile(HANDLE, PIO_STATUS_BLOCK);
+NTSTATUS NTAPI NtReadFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtReadFileScatter(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtWriteFile(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PVOID, ULONG, PLARGE_INTEGER,
+ PULONG);
+NTSTATUS NTAPI NtWriteFileGather(HANDLE, HANDLE, PIO_APC_ROUTINE, PVOID,
+ PIO_STATUS_BLOCK, PFILE_SEGMENT_ELEMENT, ULONG,
+ PLARGE_INTEGER, PULONG);
+NTSTATUS NTAPI NtQueryFullAttributesFile(POBJECT_ATTRIBUTES, PVOID);
+NTSTATUS NTAPI LdrLoadDll(PWCHAR filePath, PULONG flags,
+ PUNICODE_STRING moduleFileName, PHANDLE handle);
+NTSTATUS NTAPI LdrUnloadDll(HMODULE);
+
+NTSTATUS NTAPI NtMapViewOfSection(
+ HANDLE aSection, HANDLE aProcess, PVOID* aBaseAddress, ULONG_PTR aZeroBits,
+ SIZE_T aCommitSize, PLARGE_INTEGER aSectionOffset, PSIZE_T aViewSize,
+ SECTION_INHERIT aInheritDisposition, ULONG aAllocationType,
+ ULONG aProtectionFlags);
+
+// These pointers are disguised as PVOID to avoid pulling in obscure headers
+PVOID NTAPI LdrResolveDelayLoadedAPI(PVOID, PVOID, PVOID, PVOID, PVOID, ULONG);
+void CALLBACK ProcessCaretEvents(HWINEVENTHOOK, DWORD, HWND, LONG, LONG, DWORD,
+ DWORD);
+void __fastcall BaseThreadInitThunk(BOOL aIsInitialThread, void* aStartAddress,
+ void* aThreadParam);
+
+BOOL WINAPI ApiSetQueryApiSetPresence(PCUNICODE_STRING, PBOOLEAN);
+
+#if (_WIN32_WINNT < 0x0602)
+BOOL WINAPI
+SetProcessMitigationPolicy(PROCESS_MITIGATION_POLICY aMitigationPolicy,
+ PVOID aBuffer, SIZE_T aBufferLen);
+#endif // (_WIN32_WINNT < 0x0602)
+
+using namespace mozilla;
+
+struct payload {
+ UINT64 a;
+ UINT64 b;
+ UINT64 c;
+
+ bool operator==(const payload& other) const {
+ return (a == other.a && b == other.b && c == other.c);
+ }
+};
+
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ rotatePayload(payload p) {
+ UINT64 tmp = p.a;
+ p.a = p.b;
+ p.b = p.c;
+ p.c = tmp;
+ return p;
+}
+
+// payloadNotHooked is a target function for a test to expect a negative result.
+// We cannot use rotatePayload for that purpose because our detour cannot hook
+// a function detoured already. Please keep this function always unhooked.
+extern "C" __declspec(dllexport) __declspec(noinline) payload
+ payloadNotHooked(payload p) {
+ // Do something different from rotatePayload to avoid ICF.
+ p.a ^= p.b;
+ p.b ^= p.c;
+ p.c ^= p.a;
+ return p;
+}
+
+// Declared as volatile to prevent optimizers from incorrectly eliding accesses
+// to it. (See bug 1769001 for a motivating example.)
+static volatile bool patched_func_called = false;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&rotatePayload)>
+ orig_rotatePayload;
+
+static WindowsDllInterceptor::FuncHookType<decltype(&payloadNotHooked)>
+ orig_payloadNotHooked;
+
+static payload patched_rotatePayload(payload p) {
+ patched_func_called = true;
+ return orig_rotatePayload(p);
+}
+
+// Invoke aFunc by taking aArg's contents and using them as aFunc's arguments
+template <typename OrigFuncT, typename... Args,
+ typename ArgTuple = Tuple<Args...>, size_t... Indices>
+decltype(auto) Apply(OrigFuncT& aFunc, ArgTuple&& aArgs,
+ std::index_sequence<Indices...>) {
+ return aFunc(Get<Indices>(std::forward<ArgTuple>(aArgs))...);
+}
+
+#define DEFINE_TEST_FUNCTION(calling_convention) \
+ template <typename R, typename... Args, typename... TestArgs> \
+ bool TestFunction(R(calling_convention* aFunc)(Args...), bool (*aPred)(R), \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = Tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ return aPred(Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices())) && \
+ patched_func_called; \
+ } \
+ \
+ /* Specialization for functions returning void */ \
+ template <typename PredT, typename... Args, typename... TestArgs> \
+ bool TestFunction(void(calling_convention * aFunc)(Args...), PredT, \
+ TestArgs&&... aArgs) { \
+ using ArgTuple = Tuple<Args...>; \
+ using Indices = std::index_sequence_for<Args...>; \
+ ArgTuple fakeArgs{std::forward<TestArgs>(aArgs)...}; \
+ patched_func_called = false; \
+ Apply(aFunc, std::forward<ArgTuple>(fakeArgs), Indices()); \
+ return patched_func_called; \
+ }
+
+// C++11 allows empty arguments to macros. clang works just fine. MSVC does the
+// right thing, but it also throws up warning C4003.
+#if defined(_MSC_VER) && !defined(__clang__)
+DEFINE_TEST_FUNCTION(__cdecl)
+#else
+DEFINE_TEST_FUNCTION()
+#endif
+
+#ifdef _M_IX86
+DEFINE_TEST_FUNCTION(__stdcall)
+DEFINE_TEST_FUNCTION(__fastcall)
+#endif // _M_IX86
+
+// Test the hooked function against the supplied predicate
+template <typename OrigFuncT, typename PredicateT, typename... Args>
+bool CheckHook(OrigFuncT& aOrigFunc, const char* aDllName,
+ const char* aFuncName, PredicateT&& aPred, Args&&... aArgs) {
+ if (TestFunction(aOrigFunc, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Executed hooked function %s from %s\n",
+ aFuncName, aDllName);
+ fflush(stdout);
+ return true;
+ }
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to execute hooked function %s from %s\n",
+ aFuncName, aDllName);
+ return false;
+}
+
+struct InterceptorFunction {
+ static const size_t EXEC_MEMBLOCK_SIZE = 64 * 1024; // 64K
+
+ static InterceptorFunction& Create() {
+ // Make sure the executable memory is allocated
+ if (!sBlock) {
+ Init();
+ }
+ MOZ_ASSERT(sBlock);
+
+ // Make sure we aren't making more functions than we allocated room for
+ MOZ_RELEASE_ASSERT((sNumInstances + 1) * sizeof(InterceptorFunction) <=
+ EXEC_MEMBLOCK_SIZE);
+
+ // Grab the next InterceptorFunction from executable memory
+ InterceptorFunction& ret = *reinterpret_cast<InterceptorFunction*>(
+ sBlock + (sNumInstances++ * sizeof(InterceptorFunction)));
+
+ // Set the InterceptorFunction to the code template.
+ auto funcCode = &ret[0];
+ memcpy(funcCode, sInterceptorTemplate, TemplateLength);
+
+ // Fill in the patched_func_called pointer in the template.
+ auto pfPtr =
+ reinterpret_cast<volatile bool**>(&ret[PatchedFuncCalledIndex]);
+ *pfPtr = &patched_func_called;
+ return ret;
+ }
+
+ uint8_t& operator[](size_t i) { return mFuncCode[i]; }
+
+ uint8_t* GetFunction() { return mFuncCode; }
+
+ void SetStub(uintptr_t aStub) {
+ auto pfPtr = reinterpret_cast<uintptr_t*>(&mFuncCode[StubFuncIndex]);
+ *pfPtr = aStub;
+ }
+
+ private:
+ // We intercept functions with short machine-code functions that set a boolean
+ // and run the stub that launches the original function. Each entry in the
+ // array is the code for one of those interceptor functions. We cannot
+ // free this memory until the test shuts down.
+ // The templates have spots for the address of patched_func_called
+ // and for the address of the stub function. Their indices in the byte
+ // array are given as constants below and they appear as blocks of
+ // 0xff bytes in the templates.
+#if defined(_M_X64)
+ // 0: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &patched_func_called
+ // a: c6 00 01 mov BYTE PTR [rax],0x1
+ // d: 48 b8 ff ff ff ff ff ff ff ff movabs rax, &stub_func_ptr
+ // 17: ff e0 jmp rax
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x48, 0xB8, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xC6, 0x00, 0x01, 0x48, 0xB8, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xE0};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0xf;
+#elif defined(_M_IX86)
+ // 0: c6 05 ff ff ff ff 01 mov BYTE PTR &patched_func_called, 0x1
+ // 7: 68 ff ff ff ff push &stub_func_ptr
+ // c: c3 ret
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0xC6, 0x05, 0xFF, 0xFF, 0xFF, 0xFF, 0x01,
+ 0x68, 0xFF, 0xFF, 0xFF, 0xFF, 0xC3};
+ static const size_t PatchedFuncCalledIndex = 0x2;
+ static const size_t StubFuncIndex = 0x8;
+#elif defined(_M_ARM64)
+ // 0: 31 00 80 52 movz w17, #0x1
+ // 4: 90 00 00 58 ldr x16, #16
+ // 8: 11 02 00 39 strb w17, [x16]
+ // c: 90 00 00 58 ldr x16, #16
+ // 10: 00 02 1F D6 br x16
+ // 14: &patched_func_called
+ // 1c: &stub_func_ptr
+ static constexpr uint8_t sInterceptorTemplate[] = {
+ 0x31, 0x00, 0x80, 0x52, 0x90, 0x00, 0x00, 0x58, 0x11, 0x02, 0x00, 0x39,
+ 0x90, 0x00, 0x00, 0x58, 0x00, 0x02, 0x1F, 0xD6, 0xFF, 0xFF, 0xFF, 0xFF,
+ 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF};
+ static const size_t PatchedFuncCalledIndex = 0x14;
+ static const size_t StubFuncIndex = 0x1c;
+#else
+# error "Missing template for architecture"
+#endif
+
+ static const size_t TemplateLength = sizeof(sInterceptorTemplate);
+ uint8_t mFuncCode[TemplateLength];
+
+ InterceptorFunction() = delete;
+ InterceptorFunction(const InterceptorFunction&) = delete;
+ InterceptorFunction& operator=(const InterceptorFunction&) = delete;
+
+ static void Init() {
+ MOZ_ASSERT(!sBlock);
+ sBlock = reinterpret_cast<uint8_t*>(
+ ::VirtualAlloc(nullptr, EXEC_MEMBLOCK_SIZE, MEM_RESERVE | MEM_COMMIT,
+ PAGE_EXECUTE_READWRITE));
+ }
+
+ static uint8_t* sBlock;
+ static size_t sNumInstances;
+};
+
+uint8_t* InterceptorFunction::sBlock = nullptr;
+size_t InterceptorFunction::sNumInstances = 0;
+
+constexpr uint8_t InterceptorFunction::sInterceptorTemplate[];
+
+// Hook the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool TestHook(const char (&dll)[N], const char* func, PredicateT&& aPred,
+ Args&&... aArgs) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ auto stub = reinterpret_cast<uintptr_t>(orig_func->GetStub());
+ interceptorFunc.SetStub(stub);
+ printf("TEST-PASS | WindowsDllInterceptor | Could hook %s from %s\n", func,
+ dll);
+ fflush(stdout);
+
+ // Test the DLL function we just hooked.
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ return false;
+ }
+
+// Check that unwind information has been added if and only if it was present
+// for the original function.
+#ifdef _M_X64
+
+ auto funcBytes = reinterpret_cast<uint8_t*>(funcAddr);
+
+ // If the function we are hooking is a jumper, we need to lookup the
+ // destination of the jump to find the unwind information.
+ auto realFuncAddr = reinterpret_cast<uintptr_t>(funcAddr);
+ // jmp qword ptr[rip+offset]
+ if (funcBytes[0] == 0xff && funcBytes[1] == 0x25) {
+ realFuncAddr = *reinterpret_cast<uintptr_t*>(
+ realFuncAddr + 6 + *reinterpret_cast<int32_t*>(realFuncAddr + 2));
+ }
+ // rex.jmp qword ptr[rip+offset]
+ else if (funcBytes[0] == 0x48 && funcBytes[1] == 0xff &&
+ funcBytes[2] == 0x25) {
+ realFuncAddr = *reinterpret_cast<uintptr_t*>(
+ realFuncAddr + 7 + *reinterpret_cast<int32_t*>(realFuncAddr + 3));
+ }
+
+ uintptr_t funcImageBase = 0;
+ auto funcEntry =
+ RtlLookupFunctionEntry(realFuncAddr, &funcImageBase, nullptr);
+ bool funcHasUnwindInfo = bool(funcEntry);
+
+ uintptr_t stubImageBase = 0;
+ auto stubEntry = RtlLookupFunctionEntry(stub, &stubImageBase, nullptr);
+ bool stubHasUnwindInfo = bool(stubEntry);
+
+ if (funcHasUnwindInfo == stubHasUnwindInfo) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s and "
+ "the original function are coherent with respect to unwind info: "
+ "funcHasUnwindInfo (%d) == stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook for %s from %s "
+ "and the original function are not coherent with respect to unwind "
+ "info: "
+ "funcHasUnwindInfo (%d) != stubHasUnwindInfo (%d).\n",
+ func, dll, funcHasUnwindInfo, stubHasUnwindInfo);
+ fflush(stdout);
+ return false;
+ }
+
+ if (stubHasUnwindInfo) {
+ if (stub == (stubImageBase + stubEntry->BeginAddress)) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | The hook for %s from %s has "
+ "coherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | The hook for %s "
+ " from %s has incoherent unwind info.\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+ }
+
+#endif // _M_X64
+
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to hook %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+
+ // Print out the function's bytes so that we can easily analyze the error.
+ nsModuleHandle mod(::LoadLibraryW(dllW));
+ FARPROC funcAddr = ::GetProcAddress(mod, func);
+ if (funcAddr) {
+ const uint32_t kNumBytesToDump =
+ WindowsDllInterceptor::GetWorstCaseRequiredBytesToPatch();
+
+ printf("\tFirst %u bytes of function:\n\t", kNumBytesToDump);
+
+ auto code = reinterpret_cast<const uint8_t*>(funcAddr);
+ for (uint32_t i = 0; i < kNumBytesToDump; ++i) {
+ char suffix = (i < (kNumBytesToDump - 1)) ? ' ' : '\n';
+ printf("%02hhX%c", code[i], suffix);
+ }
+
+ fflush(stdout);
+ }
+ return false;
+ }
+}
+
+// Detour the function and optionally attempt calling it
+template <typename OrigFuncT, size_t N, typename PredicateT>
+bool TestDetour(const char (&dll)[N], const char* func, PredicateT&& aPred) {
+ auto orig_func(
+ mozilla::MakeUnique<WindowsDllInterceptor::FuncHookType<OrigFuncT>>());
+ wchar_t dllW[N];
+ std::copy(std::begin(dll), std::end(dll), std::begin(dllW));
+
+ bool successful = false;
+ WindowsDllInterceptor TestIntercept;
+ TestIntercept.Init(dll);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ successful = orig_func->Set(
+ TestIntercept, func,
+ reinterpret_cast<OrigFuncT>(interceptorFunc.GetFunction()));
+
+ if (successful) {
+ interceptorFunc.SetStub(reinterpret_cast<uintptr_t>(orig_func->GetStub()));
+ printf("TEST-PASS | WindowsDllInterceptor | Could detour %s from %s\n",
+ func, dll);
+ fflush(stdout);
+ if (!aPred) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | "
+ "Will not attempt to execute patched %s.\n",
+ func);
+ fflush(stdout);
+ return true;
+ }
+
+ // Test the DLL function we just hooked.
+ HMODULE module = ::LoadLibraryW(dllW);
+ FARPROC funcAddr = ::GetProcAddress(module, func);
+ if (!funcAddr) {
+ return false;
+ }
+
+ return CheckHook(reinterpret_cast<OrigFuncT&>(funcAddr), dll, func,
+ std::forward<PredicateT>(aPred));
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to detour %s "
+ "from %s\n",
+ func, dll);
+ fflush(stdout);
+ return false;
+ }
+}
+
+// If a function pointer's type returns void*, this template converts that type
+// to return uintptr_t instead, for the purposes of predicates.
+template <typename FuncT>
+struct SubstituteForVoidPtr {
+ using Type = FuncT;
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void* (*)(Args...)> {
+ using Type = uintptr_t (*)(Args...);
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__stdcall*)(Args...)> {
+ using Type = uintptr_t(__stdcall*)(Args...);
+};
+
+template <typename... Args>
+struct SubstituteForVoidPtr<void*(__fastcall*)(Args...)> {
+ using Type = uintptr_t(__fastcall*)(Args...);
+};
+#endif // _M_IX86
+
+// Determines the function's return type
+template <typename FuncT>
+struct ReturnType;
+
+template <typename R, typename... Args>
+struct ReturnType<R (*)(Args...)> {
+ using Type = R;
+};
+
+#ifdef _M_IX86
+template <typename R, typename... Args>
+struct ReturnType<R(__stdcall*)(Args...)> {
+ using Type = R;
+};
+
+template <typename R, typename... Args>
+struct ReturnType<R(__fastcall*)(Args...)> {
+ using Type = R;
+};
+#endif // _M_IX86
+
+// Predicates that may be supplied during tests
+template <typename FuncT>
+struct Predicates {
+ using ArgType = typename ReturnType<FuncT>::Type;
+
+ template <ArgType CompVal>
+ static bool Equals(ArgType aValue) {
+ return CompVal == aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool NotEquals(ArgType aValue) {
+ return CompVal != aValue;
+ }
+
+ template <ArgType CompVal>
+ static bool Ignore(ArgType aValue) {
+ return true;
+ }
+};
+
+// Functions that return void should be ignored, so we specialize the
+// Ignore predicate for that case. Use nullptr as the value to compare against.
+template <typename... Args>
+struct Predicates<void (*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+#ifdef _M_IX86
+template <typename... Args>
+struct Predicates<void(__stdcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+
+template <typename... Args>
+struct Predicates<void(__fastcall*)(Args...)> {
+ template <nullptr_t DummyVal>
+ static bool Ignore() {
+ return true;
+ }
+};
+#endif // _M_IX86
+
+// The standard test. Hook |func|, and then try executing it with all zero
+// arguments, using |pred| and |comp| to determine whether the call successfully
+// executed. In general, you want set pred and comp such that they return true
+// when the function is returning whatever value is expected with all-zero
+// arguments.
+//
+// Note: When |func| returns void, you must supply |Ignore| and |nullptr| as the
+// |pred| and |comp| arguments, respectively.
+#define TEST_HOOK(dll, func, pred, comp) \
+ TestHook<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+// We need to special-case functions that return INVALID_HANDLE_VALUE
+// (ie, CreateFile). Our template machinery for comparing values doesn't work
+// with integer constants passed as pointers (well, it works on MSVC, but not
+// clang, because that is not standard-compliant).
+#define TEST_HOOK_FOR_INVALID_HANDLE_VALUE(dll, func) \
+ TestHook<SubstituteForVoidPtr<decltype(&func)>::Type>( \
+ dll, #func, \
+ &Predicates<SubstituteForVoidPtr<decltype(&func)>::Type>::Equals< \
+ uintptr_t(-1)>)
+
+// This variant allows you to explicitly supply arguments to the hooked function
+// during testing. You want to provide arguments that produce the conditions
+// that induce the function to return a value that is accepted by your
+// predicate.
+#define TEST_HOOK_PARAMS(dll, func, pred, comp, ...) \
+ TestHook<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+// This is for cases when we want to hook |func|, but it is unsafe to attempt
+// to execute the function in the context of a test.
+#define TEST_HOOK_SKIP_EXEC(dll, func) \
+ TestHook<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+// The following three variants are identical to the previous macros,
+// however the forcibly use a Detour on 32-bit Windows. On 64-bit Windows,
+// these macros are identical to their TEST_HOOK variants.
+#define TEST_DETOUR(dll, func, pred, comp) \
+ TestDetour<decltype(&func)>(dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define TEST_DETOUR_PARAMS(dll, func, pred, comp, ...) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define TEST_DETOUR_SKIP_EXEC(dll, func) \
+ TestDetour<decltype(&func)>( \
+ dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+template <typename OrigFuncT, size_t N, typename PredicateT, typename... Args>
+bool MaybeTestHook(const bool cond, const char (&dll)[N], const char* func,
+ PredicateT&& aPred, Args&&... aArgs) {
+ if (!cond) {
+ printf(
+ "TEST-SKIPPED | WindowsDllInterceptor | Skipped hook test for %s from "
+ "%s\n",
+ func, dll);
+ fflush(stdout);
+ return true;
+ }
+
+ return TestHook<OrigFuncT>(dll, func, std::forward<PredicateT>(aPred),
+ std::forward<Args>(aArgs)...);
+}
+
+// Like TEST_HOOK, but the test is only executed when cond is true.
+#define MAYBE_TEST_HOOK(cond, dll, func, pred, comp) \
+ MaybeTestHook<decltype(&func)>(cond, dll, #func, \
+ &Predicates<decltype(&func)>::pred<comp>)
+
+#define MAYBE_TEST_HOOK_PARAMS(cond, dll, func, pred, comp, ...) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, &Predicates<decltype(&func)>::pred<comp>, __VA_ARGS__)
+
+#define MAYBE_TEST_HOOK_SKIP_EXEC(cond, dll, func) \
+ MaybeTestHook<decltype(&func)>( \
+ cond, dll, #func, \
+ reinterpret_cast<bool (*)(typename ReturnType<decltype(&func)>::Type)>( \
+ NULL))
+
+bool ShouldTestTipTsf() {
+ if (!IsWin8OrLater()) {
+ return false;
+ }
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SHGetKnownFolderPath)>
+ pSHGetKnownFolderPath(L"shell32.dll", "SHGetKnownFolderPath");
+ if (!pSHGetKnownFolderPath) {
+ return false;
+ }
+
+ PWSTR commonFilesPath = nullptr;
+ if (FAILED(pSHGetKnownFolderPath(FOLDERID_ProgramFilesCommon, 0, nullptr,
+ &commonFilesPath))) {
+ return false;
+ }
+
+ wchar_t fullPath[MAX_PATH + 1] = {};
+ wcscpy(fullPath, commonFilesPath);
+ wcscat(fullPath, L"\\Microsoft Shared\\Ink\\tiptsf.dll");
+ CoTaskMemFree(commonFilesPath);
+
+ if (!LoadLibraryW(fullPath)) {
+ return false;
+ }
+
+ // Leak the module so that it's loaded for the interceptor test
+ return true;
+}
+
+static const wchar_t gEmptyUnicodeStringLiteral[] = L"";
+static UNICODE_STRING gEmptyUnicodeString;
+static BOOLEAN gIsPresent;
+
+bool HasApiSetQueryApiSetPresence() {
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&ApiSetQueryApiSetPresence)>
+ func(L"Api-ms-win-core-apiquery-l1-1-0.dll", "ApiSetQueryApiSetPresence");
+ if (!func) {
+ return false;
+ }
+
+ // Prepare gEmptyUnicodeString for the test
+ ::RtlInitUnicodeString(&gEmptyUnicodeString, gEmptyUnicodeStringLiteral);
+
+ return true;
+}
+
+// Set this to true to test function unhooking (currently broken).
+const bool ShouldTestUnhookFunction = false;
+
+#if defined(_M_X64) || defined(_M_ARM64)
+
+// Use VMSharingPolicyUnique for the ShortInterceptor, as it needs to
+// reserve its trampoline memory in a special location.
+using ShortInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+static ShortInterceptor::FuncHookType<decltype(&::NtMapViewOfSection)>
+ orig_NtMapViewOfSection;
+
+#endif // defined(_M_X64) || defined(_M_ARM64)
+
+bool TestShortDetour() {
+#if defined(_M_X64) || defined(_M_ARM64)
+ auto pNtMapViewOfSection = reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ ::GetProcAddress(::GetModuleHandleW(L"ntdll.dll"), "NtMapViewOfSection"));
+ if (!pNtMapViewOfSection) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to resolve ntdll!NtMapViewOfSection\n");
+ fflush(stdout);
+ return false;
+ }
+
+ { // Scope for shortInterceptor
+ ShortInterceptor shortInterceptor;
+ shortInterceptor.TestOnlyDetourInit(
+ L"ntdll.dll",
+ mozilla::interceptor::DetourFlags::eTestOnlyForceShortPatch);
+
+ InterceptorFunction& interceptorFunc = InterceptorFunction::Create();
+ if (!orig_NtMapViewOfSection.SetDetour(
+ shortInterceptor, "NtMapViewOfSection",
+ reinterpret_cast<decltype(&::NtMapViewOfSection)>(
+ interceptorFunc.GetFunction()))) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to hook ntdll!NtMapViewOfSection via 10-byte patch\n");
+ fflush(stdout);
+ return false;
+ }
+
+ interceptorFunc.SetStub(
+ reinterpret_cast<uintptr_t>(orig_NtMapViewOfSection.GetStub()));
+
+ auto pred =
+ &Predicates<decltype(&::NtMapViewOfSection)>::Ignore<((NTSTATUS)0)>;
+
+ if (!CheckHook(pNtMapViewOfSection, "ntdll.dll", "NtMapViewOfSection",
+ pred)) {
+ // CheckHook has already printed the error message for us
+ return false;
+ }
+ }
+
+ // Now ensure that our hook cleanup worked
+ if (ShouldTestUnhookFunction) {
+ NTSTATUS status =
+ pNtMapViewOfSection(nullptr, nullptr, nullptr, 0, 0, nullptr, nullptr,
+ ((SECTION_INHERIT)0), 0, 0);
+ if (NT_SUCCESS(status)) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpected successful call to ntdll!NtMapViewOfSection after "
+ "removing short-patched hook\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully unhooked ntdll!NtMapViewOfSection via short patch\n");
+ fflush(stdout);
+ }
+
+ return true;
+#else
+ return true;
+#endif
+}
+
+constexpr uintptr_t NoStubAddressCheck = 0;
+constexpr uintptr_t ExpectedFail = 1;
+struct TestCase {
+ const char* mFunctionName;
+ uintptr_t mExpectedStub;
+ bool mPatchedOnce;
+ explicit TestCase(const char* aFunctionName, uintptr_t aExpectedStub)
+ : mFunctionName(aFunctionName),
+ mExpectedStub(aExpectedStub),
+ mPatchedOnce(false) {}
+} g_AssemblyTestCases[] = {
+#if defined(__clang__)
+// We disable these testcases because the code coverage instrumentation injects
+// code in a way that WindowsDllInterceptor doesn't understand.
+# ifndef MOZ_CODE_COVERAGE
+# if defined(_M_X64)
+ // Since we have PatchIfTargetIsRecognizedTrampoline for x64, we expect the
+ // original jump destination is returned as a stub.
+ TestCase("MovPushRet", JumpDestination),
+ TestCase("MovRaxJump", JumpDestination),
+ TestCase("DoubleJump", JumpDestination),
+
+ // Passing NoStubAddressCheck as the following testcases return
+ // a trampoline address instead of the original destination.
+ TestCase("NearJump", NoStubAddressCheck),
+ TestCase("OpcodeFF", NoStubAddressCheck),
+ TestCase("IndirectCall", NoStubAddressCheck),
+ TestCase("MovImm64", NoStubAddressCheck),
+# elif defined(_M_IX86)
+ // Skip the stub address check as we always generate a trampoline for x86.
+ TestCase("PushRet", NoStubAddressCheck),
+ TestCase("MovEaxJump", NoStubAddressCheck),
+ TestCase("DoubleJump", NoStubAddressCheck),
+ TestCase("Opcode83", NoStubAddressCheck),
+ TestCase("LockPrefix", NoStubAddressCheck),
+ TestCase("LooksLikeLockPrefix", NoStubAddressCheck),
+# endif
+# if !defined(DEBUG)
+ // Skip on Debug build because it hits MOZ_ASSERT_UNREACHABLE.
+ TestCase("UnsupportedOp", ExpectedFail),
+# endif // !defined(DEBUG)
+# endif // MOZ_CODE_COVERAGE
+#endif // defined(__clang__)
+};
+
+template <typename InterceptorType>
+bool TestAssemblyFunctions() {
+ static const auto patchedFunction = []() { patched_func_called = true; };
+
+ InterceptorType interceptor;
+ interceptor.Init("TestDllInterceptor.exe");
+
+ for (auto& testCase : g_AssemblyTestCases) {
+ if (testCase.mExpectedStub == NoStubAddressCheck && testCase.mPatchedOnce) {
+ // For the testcases with NoStubAddressCheck, we revert a hook by
+ // jumping into the original stub, which is not detourable again.
+ continue;
+ }
+
+ typename InterceptorType::template FuncHookType<void (*)()> hook;
+ bool result =
+ hook.Set(interceptor, testCase.mFunctionName, patchedFunction);
+ if (testCase.mExpectedStub == ExpectedFail) {
+ if (result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Unexpectedly succeeded to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+#if defined(NIGHTLY_BUILD)
+ const Maybe<DetourError>& maybeError = interceptor.GetLastDetourError();
+ if (maybeError.isNothing()) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "DetourError was not set on detour error.\n");
+ return false;
+ }
+ if (maybeError.ref().mErrorCode !=
+ DetourResultCode::DETOUR_PATCHER_CREATE_TRAMPOLINE_ERROR) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "A wrong detour errorcode was set on detour error.\n");
+ return false;
+ }
+#endif // defined(NIGHTLY_BUILD)
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n",
+ testCase.mFunctionName);
+ continue;
+ }
+
+ if (!result) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Failed to detour %s.\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ testCase.mPatchedOnce = true;
+
+ const auto actualStub = reinterpret_cast<uintptr_t>(hook.GetStub());
+ if (testCase.mExpectedStub != NoStubAddressCheck &&
+ actualStub != testCase.mExpectedStub) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Wrong stub was backed up for %s: %zx\n",
+ testCase.mFunctionName, actualStub);
+ return false;
+ }
+
+ patched_func_called = false;
+
+ auto originalFunction = reinterpret_cast<void (*)()>(
+ GetProcAddress(GetModuleHandleW(nullptr), testCase.mFunctionName));
+ originalFunction();
+
+ if (!patched_func_called) {
+ printf(
+ "TEST-FAILED | WindowsDllInterceptor | "
+ "Hook from %s was not called\n",
+ testCase.mFunctionName);
+ return false;
+ }
+
+ printf("TEST-PASS | WindowsDllInterceptor | %s\n", testCase.mFunctionName);
+ }
+
+ return true;
+}
+
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+// We want to test hooking and unhooking with unwind information, so we need:
+// - a VMSharingPolicy such that ShouldUnhookUponDestruction() is true and
+// Items() is implemented;
+// - a MMPolicy such that ShouldUnhookUponDestruction() is true and
+// kSupportsUnwindInfo is true.
+using DetouredCallInterceptor = mozilla::interceptor::WindowsDllInterceptor<
+ mozilla::interceptor::VMSharingPolicyUnique<
+ mozilla::interceptor::MMPolicyInProcess>>;
+
+struct DetouredCallChunk {
+ alignas(uint32_t) RUNTIME_FUNCTION functionTable[1];
+ alignas(uint32_t) uint8_t unwindInfo[sizeof(gDetouredCallUnwindInfo)];
+ uint8_t code[gDetouredCallCodeSize];
+};
+
+// Unfortunately using RtlAddFunctionTable for static code that lives within
+// a module doesn't seem to work. Presumably it conflicts with the static
+// function tables. So we recreate gDetouredCall as dynamic code to be able to
+// associate it with unwind information.
+decltype(&DetouredCallCode) gDetouredCall =
+ []() -> decltype(&DetouredCallCode) {
+ auto detouredCallChunk = reinterpret_cast<DetouredCallChunk*>(
+ VirtualAlloc(nullptr, sizeof(DetouredCallChunk), MEM_RESERVE | MEM_COMMIT,
+ PAGE_READWRITE));
+ if (!detouredCallChunk) {
+ return nullptr;
+ }
+
+ detouredCallChunk->functionTable[0].BeginAddress =
+ offsetof(DetouredCallChunk, code);
+ detouredCallChunk->functionTable[0].EndAddress =
+ offsetof(DetouredCallChunk, code) + gDetouredCallCodeSize;
+ detouredCallChunk->functionTable[0].UnwindData =
+ offsetof(DetouredCallChunk, unwindInfo);
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->unwindInfo),
+ reinterpret_cast<void*>(gDetouredCallUnwindInfo),
+ sizeof(detouredCallChunk->unwindInfo));
+ memcpy(reinterpret_cast<void*>(&detouredCallChunk->code[0]),
+ reinterpret_cast<void*>(DetouredCallCode),
+ sizeof(detouredCallChunk->code));
+
+ DWORD oldProtect = 0;
+ if (!VirtualProtect(reinterpret_cast<void*>(detouredCallChunk),
+ sizeof(DetouredCallChunk), PAGE_EXECUTE_READ,
+ &oldProtect)) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ if (!RtlAddFunctionTable(detouredCallChunk->functionTable, 1,
+ reinterpret_cast<uintptr_t>(detouredCallChunk))) {
+ VirtualFree(detouredCallChunk, 0, MEM_RELEASE);
+ return nullptr;
+ }
+
+ return reinterpret_cast<decltype(&DetouredCallCode)>(detouredCallChunk->code);
+}();
+
+// We use our own variable instead of patched_func_called because the callee of
+// gDetouredCall could end up calling other, already hooked functions could
+// change patched_func_called.
+static volatile bool sCalledPatchedDetouredCall = false;
+static volatile bool sCalledDetouredCallCallee = false;
+static volatile bool sCouldUnwindFromDetouredCallCallee = false;
+void DetouredCallCallee() {
+ sCalledDetouredCallCallee = true;
+
+ // Check that we can fully unwind the stack
+ CONTEXT contextRecord{};
+ RtlCaptureContext(&contextRecord);
+ if (!contextRecord.Rip) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get an initial context to work "
+ "with\n");
+ fflush(stdout);
+ return;
+ }
+ while (contextRecord.Rip) {
+ DWORD64 imageBase = 0;
+ auto FunctionEntry =
+ RtlLookupFunctionEntry(contextRecord.Rip, &imageBase, nullptr);
+ if (!FunctionEntry) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "DetouredCallCallee was unable to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ return;
+ }
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "DetouredCallCallee was able to get unwind info for ControlPc=%p\n",
+ reinterpret_cast<void*>(contextRecord.Rip));
+ fflush(stdout);
+ void* handlerData = nullptr;
+ DWORD64 establisherFrame = 0;
+ RtlVirtualUnwind(UNW_FLAG_NHANDLER, imageBase, contextRecord.Rip,
+ FunctionEntry, &contextRecord, &handlerData,
+ &establisherFrame, nullptr);
+ }
+ sCouldUnwindFromDetouredCallCallee = true;
+}
+
+static DetouredCallInterceptor::FuncHookType<decltype(&DetouredCallCode)>
+ orig_DetouredCall;
+
+static void patched_DetouredCall(uintptr_t aCallee) {
+ sCalledPatchedDetouredCall = true;
+ return orig_DetouredCall(aCallee);
+}
+
+bool TestCallingDetouredCall(const char* aTestDescription,
+ bool aExpectCalledPatchedDetouredCall) {
+ sCalledPatchedDetouredCall = false;
+ sCalledDetouredCallCallee = false;
+ sCouldUnwindFromDetouredCallCallee = false;
+ DetouredCallJumper(reinterpret_cast<uintptr_t>(DetouredCallCallee));
+
+ if (aExpectCalledPatchedDetouredCall != sCalledPatchedDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) differs from "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: expectCalledPatchedDetouredCall (%d) matches with "
+ "sCalledPatchedDetouredCall (%d)\n",
+ aTestDescription, aExpectCalledPatchedDetouredCall,
+ sCalledPatchedDetouredCall);
+ fflush(stdout);
+
+ if (!sCalledDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: gDetouredCall failed to call its callee\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: gDetouredCall successfully called its callee\n",
+ aTestDescription);
+ fflush(stdout);
+
+ if (!sCouldUnwindFromDetouredCallCallee) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall failed to unwind\n",
+ aTestDescription);
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "%s: the callee of gDetouredCall successfully unwinded\n",
+ aTestDescription);
+ fflush(stdout);
+ return true;
+}
+
+// Test that detouring a call preserves unwind information (bug 1798787).
+bool TestDetouredCallUnwindInfo() {
+ if (!gDetouredCall) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to generate dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ uintptr_t imageBase = 0;
+ if (!RtlLookupFunctionEntry(reinterpret_cast<uintptr_t>(gDetouredCall),
+ &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for dynamic gDetouredCall code\n");
+ fflush(stdout);
+ return false;
+ }
+
+ // We first double check that we manage to unwind when we *do not* detour
+ if (!TestCallingDetouredCall("Before hooking", false)) {
+ return false;
+ }
+
+ uintptr_t StubAddress = 0;
+
+ // The real test starts here: let's detour and check if we can still unwind
+ {
+ DetouredCallInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (!orig_DetouredCall.Set(ExeIntercept, "DetouredCallJumper",
+ &patched_DetouredCall)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to hook the detoured call jumper.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully hooked the detoured call jumper.\n");
+ fflush(stdout);
+
+ StubAddress = reinterpret_cast<uintptr_t>(orig_DetouredCall.GetStub());
+ if (!RtlLookupFunctionEntry(StubAddress, &imageBase, nullptr)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Failed to find unwind information for detoured code of "
+ "gDetouredCall\n");
+ fflush(stdout);
+ return false;
+ }
+
+ TestCallingDetouredCall("After hooking", true);
+ }
+
+ // Check that we can still unwind after clearing the hook.
+ return TestCallingDetouredCall("After unhooking", false);
+}
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+
+bool TestDynamicCodePolicy() {
+ if (!IsWin8Point1OrLater()) {
+ // Skip if a platform does not support this policy.
+ return true;
+ }
+
+ MOZ_PROCESS_MITIGATION_DYNAMIC_CODE_POLICY policy = {};
+ policy.ProhibitDynamicCode = true;
+
+ mozilla::DynamicallyLinkedFunctionPtr<decltype(&SetProcessMitigationPolicy)>
+ pSetProcessMitigationPolicy(L"kernel32.dll",
+ "SetProcessMitigationPolicy");
+ if (!pSetProcessMitigationPolicy) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "SetProcessMitigationPolicy does not exist.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ if (!pSetProcessMitigationPolicy(ProcessDynamicCodePolicy, &policy,
+ sizeof(policy))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "Fail to enable ProcessDynamicCodePolicy.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+
+ // Make sure we fail to hook a function if ProcessDynamicCodePolicy is on
+ // because we cannot create an executable trampoline region.
+ if (orig_payloadNotHooked.Set(ExeIntercept, "payloadNotHooked",
+ &patched_rotatePayload)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | "
+ "ProcessDynamicCodePolicy is not working.\n");
+ fflush(stdout);
+ return false;
+ }
+
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | "
+ "Successfully passed TestDynamicCodePolicy.\n");
+ fflush(stdout);
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ LARGE_INTEGER start;
+ QueryPerformanceCounter(&start);
+
+ // We disable this part of the test because the code coverage instrumentation
+ // injects code in rotatePayload in a way that WindowsDllInterceptor doesn't
+ // understand.
+#ifndef MOZ_CODE_COVERAGE
+ payload initial = {0x12345678, 0xfc4e9d31, 0x87654321};
+ payload p0, p1;
+ ZeroMemory(&p0, sizeof(p0));
+ ZeroMemory(&p1, sizeof(p1));
+
+ p0 = rotatePayload(initial);
+
+ {
+ WindowsDllInterceptor ExeIntercept;
+ ExeIntercept.Init("TestDllInterceptor.exe");
+ if (orig_rotatePayload.Set(ExeIntercept, "rotatePayload",
+ &patched_rotatePayload)) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook added\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Failed to add "
+ "hook\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ p1 = rotatePayload(initial);
+
+ if (patched_func_called) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook called\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was not "
+ "called\n");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf("TEST-PASS | WindowsDllInterceptor | Hook works properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook didn't return "
+ "the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+ }
+
+ patched_func_called = false;
+ ZeroMemory(&p1, sizeof(p1));
+
+ p1 = rotatePayload(initial);
+
+ if (ShouldTestUnhookFunction != patched_func_called) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Hook was %scalled after "
+ "unregistration\n",
+ ShouldTestUnhookFunction ? "not " : "");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Hook was %scalled "
+ "after unregistration\n",
+ ShouldTestUnhookFunction ? "" : "not ");
+ fflush(stdout);
+ return 1;
+ }
+
+ if (p0 == p1) {
+ printf(
+ "TEST-PASS | WindowsDllInterceptor | Original function worked "
+ "properly\n");
+ fflush(stdout);
+ } else {
+ printf(
+ "TEST-UNEXPECTED-FAIL | WindowsDllInterceptor | Original function "
+ "didn't return the right information\n");
+ fflush(stdout);
+ return 1;
+ }
+#endif
+
+ CredHandle credHandle;
+ memset(&credHandle, 0, sizeof(CredHandle));
+ OBJECT_ATTRIBUTES attributes = {};
+
+ // NB: These tests should be ordered such that lower-level APIs are tested
+ // before higher-level APIs.
+ if (TestShortDetour() &&
+ // Run <ShortInterceptor> first because <WindowsDllInterceptor>
+ // does not clean up hooks.
+#if defined(_M_X64)
+ TestAssemblyFunctions<ShortInterceptor>() &&
+#endif
+ TestAssemblyFunctions<WindowsDllInterceptor>() &&
+#ifdef _M_IX86
+ // We keep this test to hook complex code on x86. (Bug 850957)
+ TEST_HOOK("ntdll.dll", NtFlushBuffersFile, NotEquals, 0) &&
+#endif
+ TEST_HOOK("ntdll.dll", NtCreateFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtReadFileScatter, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFile, NotEquals, 0) &&
+ TEST_HOOK("ntdll.dll", NtWriteFileGather, NotEquals, 0) &&
+ TEST_HOOK_PARAMS("ntdll.dll", NtQueryFullAttributesFile, NotEquals, 0,
+ &attributes, nullptr) &&
+ TEST_DETOUR_SKIP_EXEC("ntdll.dll", LdrLoadDll) &&
+ TEST_HOOK("ntdll.dll", LdrUnloadDll, NotEquals, 0) &&
+ MAYBE_TEST_HOOK_SKIP_EXEC(IsWin8OrLater(), "ntdll.dll",
+ LdrResolveDelayLoadedAPI) &&
+ MAYBE_TEST_HOOK_PARAMS(HasApiSetQueryApiSetPresence(),
+ "Api-ms-win-core-apiquery-l1-1-0.dll",
+ ApiSetQueryApiSetPresence, Equals, FALSE,
+ &gEmptyUnicodeString, &gIsPresent) &&
+ TEST_HOOK("kernelbase.dll", QueryDosDeviceW, Equals, 0) &&
+ TEST_HOOK("kernel32.dll", GetFileAttributesW, Equals,
+ INVALID_FILE_ATTRIBUTES) &&
+#if !defined(_M_ARM64)
+# ifndef MOZ_ASAN
+ // Bug 733892: toolkit/crashreporter/nsExceptionHandler.cpp
+ // This fails on ASan because the ASan runtime already hooked this
+ // function
+ TEST_HOOK("kernel32.dll", SetUnhandledExceptionFilter, Ignore, nullptr) &&
+# endif
+#endif // !defined(_M_ARM64)
+#ifdef _M_IX86
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileW) &&
+#endif
+#if !defined(_M_ARM64)
+ TEST_HOOK_FOR_INVALID_HANDLE_VALUE("kernel32.dll", CreateFileA) &&
+#endif // !defined(_M_ARM64)
+#if !defined(_M_ARM64)
+ TEST_HOOK("kernel32.dll", TlsAlloc, NotEquals, TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK_PARAMS("kernel32.dll", TlsFree, Equals, FALSE,
+ TLS_OUT_OF_INDEXES) &&
+ TEST_HOOK("kernel32.dll", CloseHandle, Equals, FALSE) &&
+ TEST_HOOK("kernel32.dll", DuplicateHandle, Equals, FALSE) &&
+#endif // !defined(_M_ARM64)
+ TEST_DETOUR_SKIP_EXEC("kernel32.dll", BaseThreadInitThunk) &&
+#if defined(_M_X64) || defined(_M_ARM64)
+ MAYBE_TEST_HOOK(!IsWin8OrLater(), "kernel32.dll",
+ RtlInstallFunctionTableCallback, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", GetKeyState, Ignore, 0) && // see Bug 1316415
+#endif
+ TEST_HOOK("user32.dll", GetWindowInfo, Equals, FALSE) &&
+ TEST_HOOK("user32.dll", TrackPopupMenu, Equals, FALSE) &&
+ TEST_DETOUR("user32.dll", CreateWindowExW, Equals, nullptr) &&
+ TEST_HOOK("user32.dll", InSendMessageEx, Equals, ISMEX_NOSEND) &&
+ TEST_HOOK("user32.dll", SendMessageTimeoutW, Equals, 0) &&
+ TEST_HOOK("user32.dll", SetCursorPos, NotEquals, FALSE) &&
+#if !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetContext, Equals, nullptr) &&
+#endif // !defined(_M_ARM64)
+ TEST_HOOK("imm32.dll", ImmGetCompositionStringW, Ignore, 0) &&
+ TEST_HOOK_SKIP_EXEC("imm32.dll", ImmSetCandidateWindow) &&
+ TEST_HOOK("imm32.dll", ImmNotifyIME, Equals, 0) &&
+ TEST_HOOK("comdlg32.dll", GetSaveFileNameW, Ignore, FALSE) &&
+ TEST_HOOK("comdlg32.dll", GetOpenFileNameW, Ignore, FALSE) &&
+#if defined(_M_X64)
+ TEST_HOOK("comdlg32.dll", PrintDlgW, Ignore, 0) &&
+#endif
+ MAYBE_TEST_HOOK(ShouldTestTipTsf(), "tiptsf.dll", ProcessCaretEvents,
+ Ignore, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetOpenA, NotEquals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetCloseHandle, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetConnectA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", InternetQueryDataAvailable, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetReadFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetWriteFile, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetSetOptionA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpAddRequestHeadersA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpOpenRequestA, Equals, nullptr) &&
+ TEST_HOOK("wininet.dll", HttpQueryInfoA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpSendRequestExA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", HttpEndRequestA, Equals, FALSE) &&
+ TEST_HOOK("wininet.dll", InternetQueryOptionA, Equals, FALSE) &&
+ TEST_HOOK("sspicli.dll", AcquireCredentialsHandleA, NotEquals,
+ SEC_E_OK) &&
+ TEST_HOOK_PARAMS("sspicli.dll", QueryCredentialsAttributesA, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle, 0, nullptr) &&
+ TEST_HOOK_PARAMS("sspicli.dll", FreeCredentialsHandle, Equals,
+ SEC_E_INVALID_HANDLE, &credHandle) &&
+#if defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ TestDetouredCallUnwindInfo() &&
+#endif // defined(_M_X64) && !defined(MOZ_CODE_COVERAGE)
+ // Run TestDynamicCodePolicy() at the end because the policy is
+ // irreversible.
+ TestDynamicCodePolicy()) {
+ printf("TEST-PASS | WindowsDllInterceptor | all checks passed\n");
+
+ LARGE_INTEGER end, freq;
+ QueryPerformanceCounter(&end);
+
+ QueryPerformanceFrequency(&freq);
+
+ LARGE_INTEGER result;
+ result.QuadPart = end.QuadPart - start.QuadPart;
+ result.QuadPart *= 1000000;
+ result.QuadPart /= freq.QuadPart;
+
+ printf("Elapsed time: %lld microseconds\n", result.QuadPart);
+
+ return 0;
+ }
+
+ return 1;
+}
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
new file mode 100644
index 0000000000..11287012c5
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptor.exe.manifest
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+<assembly xmlns="urn:schemas-microsoft-com:asm.v1"
+ manifestVersion="1.0"
+ xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
+ <assemblyIdentity type="win32"
+ name="TestDllInterceptor"
+ version="1.0.0.0" />
+ <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+ <application>
+ <!-- Need this to use functions in WindowsVersion.h -->
+ <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}"/> <!-- Win10 -->
+ <supportedOS Id="{1f676c76-80e1-4239-95bb-83d0f6d0da78}"/> <!-- Win8.1 -->
+ <supportedOS Id="{4a2f28e3-53b9-4441-ba9c-d69d4a4a6e38}"/> <!-- Win8 -->
+ <supportedOS Id="{35138b9a-5d96-4fbd-8e2d-a2440225f93a}"/> <!-- Win7 -->
+ </application>
+ </compatibility>
+</assembly>
diff --git a/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
new file mode 100644
index 0000000000..32cf90bac1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestDllInterceptorCrossProcess.cpp
@@ -0,0 +1,159 @@
+/* -*- 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/Attributes.h"
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <string>
+
+using std::wstring;
+
+extern "C" __declspec(dllexport) int ReturnResult() { return 2; }
+
+static mozilla::CrossProcessDllInterceptor::FuncHookType<
+ decltype(&ReturnResult)>
+ gOrigReturnResult;
+
+static int ReturnResultHook() {
+ if (gOrigReturnResult() != 2) {
+ return 3;
+ }
+
+ return 0;
+}
+
+int ParentMain(int argc, wchar_t* argv[]) {
+ mozilla::SetArgv0ToFullBinaryPath(argv);
+
+ // We'll add the child process to a job so that, in the event of a failure in
+ // this parent process, the child process will be automatically terminated.
+ nsAutoHandle job(::CreateJobObjectW(nullptr, nullptr));
+ if (!job) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job creation "
+ "failed\n");
+ return 1;
+ }
+
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION jobInfo = {};
+ jobInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
+
+ if (!::SetInformationJobObject(job.get(), JobObjectExtendedLimitInformation,
+ &jobInfo, sizeof(jobInfo))) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Job config "
+ "failed\n");
+ return 1;
+ }
+
+ wchar_t childArgv_1[] = L"-child";
+
+ wchar_t* childArgv[] = {argv[0], childArgv_1};
+
+ mozilla::UniquePtr<wchar_t[]> cmdLine(
+ mozilla::MakeCommandLine(mozilla::ArrayLength(childArgv), childArgv));
+
+ STARTUPINFOW si = {sizeof(si)};
+ PROCESS_INFORMATION pi;
+ if (!::CreateProcessW(argv[0], cmdLine.get(), nullptr, nullptr, FALSE,
+ CREATE_SUSPENDED, nullptr, nullptr, &si, &pi)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to spawn "
+ "child process\n");
+ return 1;
+ }
+
+ nsAutoHandle childProcess(pi.hProcess);
+ nsAutoHandle childMainThread(pi.hThread);
+
+ if (!::AssignProcessToJobObject(job.get(), childProcess.get())) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to assign "
+ "child process to job\n");
+ ::TerminateProcess(childProcess.get(), 1);
+ return 1;
+ }
+
+ mozilla::nt::CrossExecTransferManager transferMgr(childProcess);
+ if (!transferMgr) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | "
+ "CrossExecTransferManager instantiation failed.\n");
+ return 1;
+ }
+
+ mozilla::CrossProcessDllInterceptor intcpt(childProcess.get());
+ intcpt.Init("TestDllInterceptorCrossProcess.exe");
+
+ if (!gOrigReturnResult.Set(transferMgr, intcpt, "ReturnResult",
+ &ReturnResultHook)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to add "
+ "hook\n");
+ return 1;
+ }
+
+ printf("TEST-PASS | DllInterceptorCrossProcess | Hook added\n");
+
+ if (::ResumeThread(childMainThread.get()) == static_cast<DWORD>(-1)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to resume "
+ "child thread\n");
+ return 1;
+ }
+
+ BOOL remoteDebugging;
+ bool debugging =
+ ::IsDebuggerPresent() ||
+ (::CheckRemoteDebuggerPresent(childProcess.get(), &remoteDebugging) &&
+ remoteDebugging);
+
+ DWORD waitResult =
+ ::WaitForSingleObject(childProcess.get(), debugging ? INFINITE : 60000);
+ if (waitResult != WAIT_OBJECT_0) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "failed to finish\n");
+ return 1;
+ }
+
+ DWORD childExitCode;
+ if (!::GetExitCodeProcess(childProcess.get(), &childExitCode)) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Failed to obtain "
+ "child process exit code\n");
+ return 1;
+ }
+
+ if (childExitCode) {
+ printf(
+ "TEST-UNEXPECTED-FAIL | DllInterceptorCrossProcess | Child process "
+ "exit code is %lu instead of 0\n",
+ childExitCode);
+ return 1;
+ }
+
+ printf(
+ "TEST-PASS | DllInterceptorCrossProcess | Child process exit code is "
+ "zero\n");
+ return 0;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ if (argc > 1) {
+ // clang keeps inlining this call despite every attempt to force it to do
+ // otherwise. We'll use GetProcAddress and call its function pointer
+ // instead.
+ auto pReturnResult = reinterpret_cast<decltype(&ReturnResult)>(
+ ::GetProcAddress(::GetModuleHandleW(nullptr), "ReturnResult"));
+ return pReturnResult();
+ }
+
+ return ParentMain(argc, argv);
+}
diff --git a/toolkit/xre/dllservices/tests/TestIATPatcher.cpp b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
new file mode 100644
index 0000000000..2f84c0541a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestIATPatcher.cpp
@@ -0,0 +1,121 @@
+/* -*- 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/Assertions.h"
+#include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "nsWindowsDllInterceptor.h"
+#include "nsWindowsHelpers.h"
+
+#include <shlwapi.h>
+
+static int NormalImport() { return ::GetSystemMetrics(SM_CYCAPTION); }
+
+static bool DelayLoadImport() {
+ return !!::UrlIsW(L"http://example.com/", URLIS_FILEURL);
+}
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::GetSystemMetrics)>
+ gGetSystemMetricsHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::MessageBoxA)>
+ gMessageBoxAHook;
+
+static mozilla::WindowsIATPatcher::FuncHookType<decltype(&::UrlIsW)> gUrlIsHook;
+
+static bool gGetSystemMetricsHookCalled = false;
+
+static int WINAPI GetSystemMetricsHook(int aIndex) {
+ MOZ_DIAGNOSTIC_ASSERT(aIndex == SM_CYCAPTION);
+ gGetSystemMetricsHookCalled = true;
+ return 0;
+}
+
+static bool gUrlIsHookCalled = false;
+
+static BOOL WINAPI UrlIsWHook(PCWSTR aUrl, URLIS aFlags) {
+ gUrlIsHookCalled = true;
+ return TRUE;
+}
+
+static HMODULE GetStrongReferenceToExeModule() {
+ HMODULE result;
+ if (!::GetModuleHandleExW(0, nullptr, &result)) {
+ return nullptr;
+ }
+
+ return result;
+}
+
+#define PRINT_FAIL(msg) printf("TEST-UNEXPECTED-FAIL | IATPatcher | " msg "\n")
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ nsModuleHandle ourModule1(GetStrongReferenceToExeModule());
+ if (!ourModule1) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ if (!gGetSystemMetricsHook.Set(ourModule1, "user32.dll", "GetSystemMetrics",
+ &GetSystemMetricsHook)) {
+ PRINT_FAIL("Failed setting GetSystemMetrics hook");
+ return 1;
+ }
+
+ if (NormalImport() || !gGetSystemMetricsHookCalled) {
+ PRINT_FAIL("GetSystemMetrics hook was not called");
+ return 1;
+ }
+
+ static const mozilla::StaticDynamicallyLinkedFunctionPtr<
+ decltype(&::GetSystemMetrics)>
+ pRealGetSystemMetrics(L"user32.dll", "GetSystemMetrics");
+ if (!pRealGetSystemMetrics) {
+ PRINT_FAIL("Failed resolving real GetSystemMetrics pointer");
+ return 1;
+ }
+
+ if (gGetSystemMetricsHook.GetStub() != pRealGetSystemMetrics) {
+ PRINT_FAIL(
+ "GetSystemMetrics hook stub pointer does not match real "
+ "GetSystemMetrics pointer");
+ return 1;
+ }
+
+ nsModuleHandle ourModule2(GetStrongReferenceToExeModule());
+ if (!ourModule2) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // This should fail becuase the test never calls, and thus never imports,
+ // MessageBoxA
+ if (gMessageBoxAHook.Set(ourModule2, "user32.dll", "MessageBoxA", nullptr)) {
+ PRINT_FAIL("Setting MessageBoxA hook succeeded when it should have failed");
+ return 1;
+ }
+
+ nsModuleHandle ourModule3(GetStrongReferenceToExeModule());
+ if (!ourModule3) {
+ PRINT_FAIL("Failed obtaining HMODULE for executable");
+ return 1;
+ }
+
+ // These tests involve a delay-loaded import, which are not supported; we
+ // expect these tests to FAIL.
+
+ if (gUrlIsHook.Set(ourModule3, "shlwapi.dll", "UrlIsW", &UrlIsWHook)) {
+ PRINT_FAIL("gUrlIsHook.Set should have failed");
+ return 1;
+ }
+
+ if (DelayLoadImport() || gUrlIsHookCalled) {
+ PRINT_FAIL("gUrlIsHook should not have been called");
+ return 1;
+ }
+
+ printf("TEST-PASS | IATPatcher | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/TestMMPolicy.cpp b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
new file mode 100644
index 0000000000..1ae93a4ed1
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/TestMMPolicy.cpp
@@ -0,0 +1,204 @@
+/* -*- 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 "nsWindowsDllInterceptor.h"
+
+#include <functional>
+
+mozilla::interceptor::MMPolicyInProcess gPolicy;
+
+void DepleteVirtualAddress(
+ uint8_t* aStart, size_t aSize,
+ const std::function<void(void*)>& aPostAllocCallback) {
+ const DWORD granularity = gPolicy.GetAllocGranularity();
+ if (aStart == 0 || aSize < granularity) {
+ return;
+ }
+
+ uint8_t* alignedStart = reinterpret_cast<uint8_t*>(
+ (((reinterpret_cast<uintptr_t>(aStart) - 1) / granularity) + 1) *
+ granularity);
+ aSize -= (alignedStart - aStart);
+ if (auto p = VirtualAlloc(alignedStart, aSize, MEM_RESERVE, PAGE_NOACCESS)) {
+ aPostAllocCallback(p);
+ return;
+ }
+
+ uintptr_t mask = ~(static_cast<uintptr_t>(granularity) - 1);
+ size_t halfSize = (aSize >> 1) & mask;
+ if (halfSize == 0) {
+ return;
+ }
+
+ DepleteVirtualAddress(aStart, halfSize, aPostAllocCallback);
+ DepleteVirtualAddress(aStart + halfSize, aSize - halfSize,
+ aPostAllocCallback);
+}
+
+bool ValidateFreeRegion(LPVOID aRegion, size_t aDesiredLen) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(aRegion, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (mbi.State != MEM_FREE) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not within a free region\n",
+ aRegion);
+ return false;
+ }
+
+ if (aRegion != mbi.BaseAddress ||
+ reinterpret_cast<uintptr_t>(mbi.BaseAddress) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "%p is not a region's start address\n",
+ aRegion);
+ return false;
+ }
+
+ LPVOID allocated = VirtualAlloc(aRegion, aDesiredLen,
+ MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
+ if (!allocated) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualAlloc(%p) failed - %08lx\n",
+ aRegion, GetLastError());
+ return false;
+ }
+
+ if (!VirtualFree(allocated, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ allocated, GetLastError());
+ return false;
+ }
+
+ return true;
+}
+
+bool TestFindRegion() {
+ // Skip the near-null addresses
+ uint8_t* minAddr = reinterpret_cast<uint8_t*>(
+ std::max(gPolicy.GetAllocGranularity(), 0x1000000ul));
+ // 64bit address space is too large to deplete. 32bit space is enough.
+ uint8_t* maxAddr = reinterpret_cast<uint8_t*>(std::min(
+ gPolicy.GetMaxUserModeAddress(), static_cast<uintptr_t>(0xffffffff)));
+
+ // Keep one of the regions we allocate so that we can release it later.
+ void* lastResort = nullptr;
+
+ // Reserve all free regions in the range [minAddr, maxAddr]
+ for (uint8_t* address = minAddr; address <= maxAddr;) {
+ MEMORY_BASIC_INFORMATION mbi;
+ if (VirtualQuery(address, &mbi, sizeof(mbi)) != sizeof(mbi)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualQuery(%p) failed - %08lx\n",
+ address, GetLastError());
+ break;
+ }
+
+ address = reinterpret_cast<uint8_t*>(mbi.BaseAddress);
+ if (mbi.State == MEM_FREE) {
+ DepleteVirtualAddress(address, mbi.RegionSize,
+ [&lastResort](void* aAllocated) {
+ // Pick the first address we allocate to make sure
+ // FindRegion scans the full range.
+ if (!lastResort) {
+ lastResort = aAllocated;
+ }
+ });
+ }
+
+ address += mbi.RegionSize;
+ }
+
+ if (!lastResort) {
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "No free region in [%p - %p]. Skipping the testcase.\n",
+ minAddr, maxAddr);
+ return true;
+ }
+
+ // Make sure there are no free regions
+ PVOID freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), 1, minAddr, maxAddr);
+ if (freeRegion) {
+ if (reinterpret_cast<uintptr_t>(freeRegion) %
+ gPolicy.GetAllocGranularity()) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "MMPolicyBase::FindRegion returned an unaligned address %p.\n",
+ freeRegion);
+ return false;
+ }
+
+ printf(
+ "TEST-SKIPPED | TestMMPolicy | "
+ "%p was freed after depletion. Skipping the testcase.\n",
+ freeRegion);
+ return true;
+ }
+
+ // Free one region, and thus we can expect FindRegion finds this region
+ if (!VirtualFree(lastResort, 0, MEM_RELEASE)) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "VirtualFree(%p) failed - %08lx\n",
+ lastResort, GetLastError());
+ return false;
+ }
+ printf("The region starting from %p has been freed.\n", lastResort);
+
+ // Run the function several times because it uses a randon number inside
+ // and its result is nondeterministic.
+ for (int i = 0; i < 50; ++i) {
+ // Because one region was freed, a desire up to one region
+ // should be fulfilled.
+ const size_t desiredLengths[] = {1, gPolicy.GetAllocGranularity()};
+
+ for (auto desiredLen : desiredLengths) {
+ freeRegion =
+ gPolicy.FindRegion(GetCurrentProcess(), desiredLen, minAddr, maxAddr);
+ if (!freeRegion) {
+ printf(
+ "TEST-FAILED | TestMMPolicy | "
+ "Failed to find a free region.\n");
+ return false;
+ }
+
+ if (!ValidateFreeRegion(freeRegion, desiredLen)) {
+ return false;
+ }
+ }
+ }
+
+ return true;
+}
+
+extern "C" int wmain(int argc, wchar_t* argv[]) {
+ // Preload delayload modules (e.g. advapi32.dll, bcryptPrimitives.dll, etc.)
+ // by calling rand_s(), which is used in MMPolicy::FindRegion, before
+ // depleting the process memory during the test.
+ unsigned int rnd = 0;
+ rand_s(&rnd);
+
+ if (!TestFindRegion()) {
+ return 1;
+ }
+
+ printf("TEST-PASS | TestMMPolicy | All tests passed.\n");
+ return 0;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
new file mode 100644
index 0000000000..5f141b22ae
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDLLBlocklist.cpp
@@ -0,0 +1,223 @@
+/* -*- 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 <windows.h>
+#include <winternl.h>
+
+#include <process.h>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Char16.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "nsDirectoryServiceDefs.h"
+#include "nsDirectoryServiceUtils.h"
+#include "nsString.h"
+#include "nsTArray.h"
+#include "nsWindowsHelpers.h"
+
+static nsString GetFullPath(const nsAString& aLeaf) {
+ nsCOMPtr<nsIFile> f;
+
+ EXPECT_TRUE(NS_SUCCEEDED(
+ NS_GetSpecialDirectory(NS_OS_CURRENT_WORKING_DIR, getter_AddRefs(f))));
+
+ EXPECT_NS_SUCCEEDED(f->Append(aLeaf));
+
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(f->Exists(&exists)) && exists);
+
+ nsString ret;
+ EXPECT_NS_SUCCEEDED(f->GetPath(ret));
+ return ret;
+}
+
+TEST(TestDllBlocklist, BlockDllByName)
+{
+ // The DLL name has capital letters, so this also tests that the comparison
+ // is case-insensitive.
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByName.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, BlockDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+
+ hDll.own(
+ ::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_IMAGE_RESOURCE));
+ // Mapped as MEM_IMAGE + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+
+TEST(TestDllBlocklist, AllowDllByVersion)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_AllowByVersion.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, SocketProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_SocketProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+TEST(TestDllBlocklist, UtilityProcessOnly_AllowInMainProcess)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UtilityProcessOnly.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+}
+
+// RedirectToNoOpEntryPoint needs the launcher process.
+#if defined(MOZ_LAUNCHER_PROCESS)
+TEST(TestDllBlocklist, NoOpEntryPoint)
+{
+ // DllMain of this dll has MOZ_RELEASE_ASSERT. This test makes sure we load
+ // the module successfully without running DllMain.
+ constexpr auto kLeafName = u"TestDllBlocklist_NoOpEntryPoint.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+# if defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // REDIRECT_TO_NOOP_ENTRYPOINT is ignored. So LoadLibraryW
+ // is expected to fail.
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# else
+ EXPECT_TRUE(!!hDll);
+ EXPECT_TRUE(!!::GetModuleHandleW(kLeafName.get()));
+# endif
+}
+
+// User blocklist needs the launcher process
+TEST(TestDllBlocklist, UserBlocked)
+{
+ constexpr auto kLeafName = u"TestDllBlocklist_UserBlocked.dll"_ns;
+ nsString dllPath = GetFullPath(kLeafName);
+
+ nsModuleHandle hDll(::LoadLibraryW(dllPath.get()));
+
+// With ASAN, the test uses mozglue's blocklist where
+// the user blocklist is not used.
+# if !defined(MOZ_ASAN)
+ EXPECT_TRUE(!hDll);
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafName.get()));
+# endif
+ hDll.own(::LoadLibraryExW(dllPath.get(), nullptr, LOAD_LIBRARY_AS_DATAFILE));
+ // Mapped as MEM_MAPPED + PAGE_READONLY
+ EXPECT_TRUE(hDll);
+}
+#endif // defined(MOZ_LAUNCHER_PROCESS)
+
+#define DLL_BLOCKLIST_ENTRY(name, ...) {name, __VA_ARGS__},
+#define DLL_BLOCKLIST_STRING_TYPE const char*
+#include "mozilla/WindowsDllBlocklistLegacyDefs.h"
+
+TEST(TestDllBlocklist, BlocklistIntegrity)
+{
+ nsTArray<DLL_BLOCKLIST_STRING_TYPE> dupes;
+ DECLARE_POINTER_TO_FIRST_DLL_BLOCKLIST_ENTRY(pFirst);
+ DECLARE_POINTER_TO_LAST_DLL_BLOCKLIST_ENTRY(pLast);
+
+ EXPECT_FALSE(pLast->mName || pLast->mMaxVersion || pLast->mFlags);
+
+ for (size_t i = 0; i < mozilla::ArrayLength(gWindowsDllBlocklist) - 1; ++i) {
+ auto pEntry = pFirst + i;
+
+ // Validate name
+ EXPECT_TRUE(!!pEntry->mName);
+ EXPECT_GT(strlen(pEntry->mName), 3U);
+
+ // Check the filename for valid characters.
+ for (auto pch = pEntry->mName; *pch != 0; ++pch) {
+ EXPECT_FALSE(*pch >= 'A' && *pch <= 'Z');
+ }
+
+ // Check for duplicate entries
+ for (auto&& dupe : dupes) {
+ EXPECT_NE(stricmp(dupe, pEntry->mName), 0);
+ }
+
+ dupes.AppendElement(pEntry->mName);
+ }
+}
+
+TEST(TestDllBlocklist, BlockThreadWithLoadLibraryEntryPoint)
+{
+ // Only supported on Nightly
+#if defined(NIGHTLY_BUILD)
+ using ThreadProc = unsigned(__stdcall*)(void*);
+
+ constexpr auto kLeafNameW = u"TestDllBlocklist_MatchByVersion.dll"_ns;
+
+ nsString fullPathW = GetFullPath(kLeafNameW);
+ EXPECT_FALSE(fullPathW.IsEmpty());
+
+ nsAutoHandle threadW(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryW),
+ (void*)fullPathW.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadW);
+ EXPECT_EQ(::WaitForSingleObject(threadW, INFINITE), WAIT_OBJECT_0);
+
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ DWORD exitCode;
+ EXPECT_TRUE(::GetExitCodeThread(threadW, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+
+ const NS_LossyConvertUTF16toASCII fullPathA(fullPathW);
+ EXPECT_FALSE(fullPathA.IsEmpty());
+
+ nsAutoHandle threadA(reinterpret_cast<HANDLE>(
+ _beginthreadex(nullptr, 0, reinterpret_cast<ThreadProc>(&::LoadLibraryA),
+ (void*)fullPathA.get(), 0, nullptr)));
+
+ EXPECT_TRUE(!!threadA);
+ EXPECT_EQ(::WaitForSingleObject(threadA, INFINITE), WAIT_OBJECT_0);
+# if !defined(MOZ_ASAN)
+ // ASAN builds under Windows 11 can have unexpected thread exit codes.
+ // See bug 1798796
+ EXPECT_TRUE(::GetExitCodeThread(threadA, &exitCode) && !exitCode);
+# endif // !defined(MOZ_ASAN)
+ EXPECT_TRUE(!::GetModuleHandleW(kLeafNameW.get()));
+#endif // defined(NIGHTLY_BUILD)
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
new file mode 100644
index 0000000000..f56aa099ff
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/TestDllBlocklist_AllowByVersion.rc
@@ -0,0 +1,42 @@
+/* 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 5,5,5,6
+ PRODUCTVERSION 5,5,5,1
+ 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.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_AllowByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/moz.build
new file mode 100644
index 0000000000..0987cdde1a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_AllowByVersion/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("TestDllBlocklist_AllowByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_AllowByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_AllowByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_AllowByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/TestDllBlocklist_MatchByName.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/moz.build
new file mode 100644
index 0000000000..f34931898a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByName/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("TestDllBlocklist_MatchByName")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByName.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByName.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
new file mode 100644
index 0000000000..7390c1cb34
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/TestDllBlocklist_MatchByVersion.rc
@@ -0,0 +1,42 @@
+/* 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 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ 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.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_MatchByVersion.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/moz.build
new file mode 100644
index 0000000000..38e10524c7
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_MatchByVersion/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("TestDllBlocklist_MatchByVersion")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_MatchByVersion.cpp",
+]
+
+RCFILE = "TestDllBlocklist_MatchByVersion.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_MatchByVersion.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
new file mode 100644
index 0000000000..2505b8b700
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.cpp
@@ -0,0 +1,12 @@
+/* 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>
+
+#include "mozilla/Assertions.h"
+
+BOOL WINAPI DllMain(HINSTANCE hInstance, DWORD aReason, LPVOID) {
+ MOZ_RELEASE_ASSERT(0);
+ return TRUE;
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
new file mode 100644
index 0000000000..7c79dac373
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/TestDllBlocklist_NoOpEntryPoint.rc
@@ -0,0 +1,42 @@
+/* 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 5,5,5,5
+ PRODUCTVERSION 5,5,5,1
+ 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.org"
+ VALUE "FileDescription", L"Test DLL"
+ VALUE "FileVersion", "1.0"
+ VALUE "InternalName", "Test DLL"
+ VALUE "OriginalFilename", "TestDllBlocklist_NoOpEntryPoint.dll"
+ VALUE "ProductName", "Test DLL"
+ VALUE "ProductVersion", "1.0"
+ END
+ END
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x0409, 1252
+ END
+END
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
new file mode 100644
index 0000000000..e9a10a150a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_NoOpEntryPoint/moz.build
@@ -0,0 +1,21 @@
+# -*- 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("TestDllBlocklist_NoOpEntryPoint")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_NoOpEntryPoint.cpp",
+]
+
+RCFILE = "TestDllBlocklist_NoOpEntryPoint.rc"
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_NoOpEntryPoint.dll"]
+
+OS_LIBS += [
+ "uuid",
+]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/TestDllBlocklist_SocketProcessOnly.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/moz.build
new file mode 100644
index 0000000000..dc93544e1b
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_SocketProcessOnly/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("TestDllBlocklist_SocketProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_SocketProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_SocketProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/TestDllBlocklist_UserBlocked.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/moz.build
new file mode 100644
index 0000000000..31996c5cb2
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UserBlocked/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("TestDllBlocklist_UserBlocked")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UserBlocked.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UserBlocked.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.cpp
new file mode 100644
index 0000000000..7bd936296e
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/TestDllBlocklist_UtilityProcessOnly.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 hInstance, DWORD aReason, LPVOID) { return TRUE; }
diff --git a/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/moz.build
new file mode 100644
index 0000000000..913d0f155c
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestDllBlocklist_UtilityProcessOnly/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("TestDllBlocklist_UtilityProcessOnly")
+
+UNIFIED_SOURCES = [
+ "TestDllBlocklist_UtilityProcessOnly.cpp",
+]
+
+if CONFIG["COMPILE_ENVIRONMENT"]:
+ TEST_HARNESS_FILES.gtest += ["!TestDllBlocklist_UtilityProcessOnly.dll"]
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
new file mode 100644
index 0000000000..f7865a2ed0
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules.cpp
@@ -0,0 +1,461 @@
+/* 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/BinarySearch.h"
+#include "mozilla/gtest/MozAssertions.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"
+
+using namespace mozilla;
+
+class ModuleLoadCounter final {
+ nsTHashMap<nsStringCaseInsensitiveHashKey, int> mCounters;
+
+ public:
+ template <size_t N>
+ ModuleLoadCounter(const nsString (&aNames)[N], const int (&aCounts)[N])
+ : mCounters(N) {
+ for (size_t i = 0; i < N; ++i) {
+ mCounters.InsertOrUpdate(aNames[i], aCounts[i]);
+ }
+ }
+
+ template <size_t 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 (size_t i = 0; i < N; ++i) {
+ auto entry = mCounters.Lookup(aNames[i]);
+ if (!entry) {
+ wprintf(L"%s is not registered.\n",
+ static_cast<const wchar_t*>(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", static_cast<const wchar_t*>(aNames[i].get()),
+ *entry);
+ result = false;
+ }
+ }
+ return result;
+ }
+
+ bool IsDone() const {
+ bool allZero = true;
+ for (const auto& data : mCounters.Values()) {
+ if (data < 0) {
+ // If any counter is negative, we know the test fails.
+ // No need to continue.
+ return true;
+ }
+ if (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 (auto entry = mCounters.Lookup(aName)) {
+ --(*entry);
+ }
+ }
+};
+
+class UntrustedModulesCollector {
+ static constexpr int kMaximumPendingQueries = 500;
+ 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(
+ "xre:UntrustedModulesCollector"_ns,
+ [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 (auto item : aResult.ref().mEvents) {
+ aChecker.Decrement(item->mEvent.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(
+ "xre:UntrustedModulesCollector(pendingQueries)"_ns,
+ [&pendingQueries]() { return pendingQueries <= 0; }));
+
+ return rv;
+ }
+};
+
+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_NS_SUCCEEDED(file->Append(aLeaf));
+ bool exists;
+ EXPECT_TRUE(NS_SUCCEEDED(file->Exists(&exists)) && exists);
+ nsString fullPath;
+ EXPECT_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 ValidateUntrustedModules(const UntrustedModulesData& aData,
+ bool aIsTruncatedData = false);
+
+ 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_NS_SUCCEEDED(collector.Collect(waitForOne));
+ EXPECT_TRUE(waitForOne.Remains({kTestModules[0]}, {0}));
+ EXPECT_EQ(collector.Data().length(), 1U);
+
+ // 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::Rooted<JS::Value> jsval(cx.GetJSContext());
+ serializer.GetObject(&jsval);
+
+ nsAutoString json;
+ EXPECT_TRUE(nsContentUtils::StringifyJSON(cx.GetJSContext(), &jsval, json));
+
+ JS::Rooted<JSObject*> re(
+ cx.GetJSContext(),
+ JS::NewUCRegExpObject(cx.GetJSContext(), aPattern, aPatternLength,
+ JS::RegExpFlag::Global));
+ EXPECT_TRUE(!!re);
+
+ JS::Rooted<JS::Value> 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.isBoolean() || !matchResult.toBoolean()) {
+ // If match failed, print out the actual JSON kindly.
+ wprintf(L"JSON: %s\n", static_cast<const wchar_t*>(json.get()));
+ wprintf(L"RE: %s\n", aPattern);
+ }
+ }
+};
+
+const nsString UntrustedModulesFixture::kTestModules[] = {
+ // Sorted for binary-search
+ u"TestUntrustedModules_Dll1.dll"_ns,
+ u"TestUntrustedModules_Dll2.dll"_ns,
+};
+
+INIT_ONCE UntrustedModulesFixture::sInitLoadOnce = INIT_ONCE_STATIC_INIT;
+UntrustedModulesCollector UntrustedModulesFixture::sInitLoadDataCollector;
+
+void UntrustedModulesFixture::ValidateUntrustedModules(
+ const UntrustedModulesData& aData, bool aIsTruncatedData) {
+ // This defines a list of modules which are listed on our blocklist and
+ // thus its loading status is not expected to be Status::Loaded.
+ // Although the UntrustedModulesFixture test does not touch any of them,
+ // the current process might have run a test like TestDllBlocklist where
+ // we try to load and block them.
+ const struct {
+ const wchar_t* mName;
+ ModuleLoadInfo::Status mStatus;
+ } kKnownModules[] = {
+ // Sorted by mName for binary-search
+ {L"TestDllBlocklist_MatchByName.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_MatchByVersion.dll", ModuleLoadInfo::Status::Blocked},
+ {L"TestDllBlocklist_NoOpEntryPoint.dll",
+ ModuleLoadInfo::Status::Redirected},
+#if !defined(MOZ_ASAN)
+ // With ASAN, the test uses mozglue's blocklist where
+ // the user blocklist is not used. So only check for this
+ // DLL in the non-ASAN case.
+ {L"TestDllBlocklist_UserBlocked.dll", ModuleLoadInfo::Status::Blocked},
+#endif // !defined(MOZ_ASAN)
+ };
+
+ EXPECT_EQ(aData.mProcessType, GeckoProcessType_Default);
+ EXPECT_EQ(aData.mPid, ::GetCurrentProcessId());
+
+ nsTHashtable<nsPtrHashKey<void>> moduleSet;
+ for (const RefPtr<ModuleRecord>& module : aData.mModules.Values()) {
+ moduleSet.PutEntry(module);
+ }
+
+ size_t numBlockedEvents = 0;
+ for (auto item : aData.mEvents) {
+ const auto& evt = item->mEvent;
+ const nsDependentSubstring leafName =
+ nt::GetLeafName(evt.mModule->mResolvedNtName);
+ const nsAutoString leafNameStr(leafName.Data(), leafName.Length());
+ const ModuleLoadInfo::Status loadStatus =
+ static_cast<ModuleLoadInfo::Status>(evt.mLoadStatus);
+ if (loadStatus == ModuleLoadInfo::Status::Blocked) {
+ ++numBlockedEvents;
+ }
+
+ size_t match;
+ if (BinarySearchIf(
+ kKnownModules, 0, ArrayLength(kKnownModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.mName);
+ },
+ &match)) {
+ EXPECT_EQ(loadStatus, kKnownModules[match].mStatus);
+ } else {
+ EXPECT_EQ(evt.mLoadStatus, 0U);
+ }
+
+ if (BinarySearchIf(
+ kTestModules, 0, ArrayLength(kTestModules),
+ [&leafNameStr](const auto& aVal) {
+ return _wcsicmp(leafNameStr.get(), aVal.get());
+ },
+ &match)) {
+ // We know the test modules are loaded in the main thread,
+ // but we don't know about other modules.
+ 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);
+ }
+
+ // 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_EQ(aData.mNumEvents, aData.mEvents.length());
+ EXPECT_GT(aData.mNumEvents, 0U);
+ if (aIsTruncatedData) {
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ EXPECT_LE(aData.mNumEvents, UntrustedModulesData::kMaxEvents);
+ } else if (numBlockedEvents == aData.mNumEvents) {
+ // If all loading events were blocked or aData is truncated,
+ // the stacks are empty.
+ EXPECT_EQ(aData.mStacks.GetModuleCount(), 0U);
+ } else {
+ EXPECT_GT(aData.mStacks.GetModuleCount(), 0U);
+ }
+ EXPECT_EQ(aData.mSanitizationFailures, 0U);
+ EXPECT_EQ(aData.mTrustTestFailures, 0U);
+}
+
+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(true);
+
+ 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\":\\[\\[\\[(-1|\\d+),\\d+\\]" \
+ u"(,\\[(-1|\\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"\"blockedModules\":\\[.*?\\]," // allow for the case where there are some blocked modules
+ 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_NS_SUCCEEDED(aSerializer.Add(backup1));
+ EXPECT_NS_SUCCEEDED(aSerializer.Add(backup2));
+ });
+}
+
+TEST_F(UntrustedModulesFixture, Backup) {
+ RefPtr<UntrustedModulesBackupService> backupSvc(
+ UntrustedModulesBackupService::Get());
+ for (int i = 0; i < 100; ++i) {
+ backupSvc->Backup(CollectSingleData());
+ }
+
+ backupSvc->SettleAllStagingData();
+ EXPECT_TRUE(backupSvc->Staging().IsEmpty());
+
+ for (const auto& entry : backupSvc->Settled()) {
+ const RefPtr<UntrustedModulesDataContainer>& container = entry.GetData();
+ EXPECT_TRUE(!!container);
+ const UntrustedModulesData& data = container->mData;
+ EXPECT_EQ(entry.GetKey(), ProcessHashKey(data.mProcessType, data.mPid));
+ ValidateUntrustedModules(data, /*aIsTruncatedData*/ true);
+ }
+}
diff --git a/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/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/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/TestUntrustedModules_Dll1.rc
new file mode 100644
index 0000000000..2358b88b93
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/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/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll1/moz.build
new file mode 100644
index 0000000000..57fc59ca8a
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/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/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/TestUntrustedModules_Dll2.cpp
new file mode 100644
index 0000000000..4f6ce877eb
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/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/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build b/toolkit/xre/dllservices/tests/gtest/TestUntrustedModules_Dll2/moz.build
new file mode 100644
index 0000000000..fcefe41329
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/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/dllservices/tests/gtest/moz.build b/toolkit/xre/dllservices/tests/gtest/moz.build
new file mode 100644
index 0000000000..525eba0c38
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/gtest/moz.build
@@ -0,0 +1,35 @@
+# 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("dllservicestest")
+
+UNIFIED_SOURCES += [
+ "TestDLLBlocklist.cpp",
+]
+
+if CONFIG["CPU_ARCH"] != "x86":
+ UNIFIED_SOURCES += [
+ "TestUntrustedModules.cpp",
+ ]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/telemetry/other",
+ "/toolkit/components/telemetry/tests/gtest",
+]
+
+TEST_DIRS += [
+ "TestDllBlocklist_AllowByVersion",
+ "TestDllBlocklist_MatchByName",
+ "TestDllBlocklist_MatchByVersion",
+ "TestDllBlocklist_NoOpEntryPoint",
+ "TestDllBlocklist_SocketProcessOnly",
+ "TestDllBlocklist_UserBlocked",
+ "TestDllBlocklist_UtilityProcessOnly",
+ "TestUntrustedModules_Dll1",
+ "TestUntrustedModules_Dll2",
+]
+
+include("/ipc/chromium/chromium-config.mozbuild")
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/dllservices/tests/moz.build b/toolkit/xre/dllservices/tests/moz.build
new file mode 100644
index 0000000000..eee1b22ae3
--- /dev/null
+++ b/toolkit/xre/dllservices/tests/moz.build
@@ -0,0 +1,46 @@
+# -*- 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(
+ [
+ "TestDllInterceptor",
+ "TestIATPatcher",
+ "TestMMPolicy",
+ ],
+ linkage=None,
+)
+
+if CONFIG["CPU_ARCH"] in ("x86", "x86_64"):
+ # Cross-process interceptors not yet supported on aarch64
+ GeckoCppUnitTests(
+ [
+ "TestDllInterceptorCrossProcess",
+ ],
+ linkage=None,
+ )
+
+OS_LIBS += [
+ "advapi32",
+ "ntdll",
+ "ole32",
+ "shlwapi",
+ "user32",
+ "uuid",
+]
+
+DELAYLOAD_DLLS += [
+ "shlwapi.dll",
+]
+
+if CONFIG["CC_TYPE"] in ("gcc", "clang"):
+ # This allows us to use wmain as the entry point on mingw
+ LDFLAGS += [
+ "-municode",
+ ]
+
+TEST_DIRS += [
+ "gtest",
+]
diff --git a/toolkit/xre/glxtest.cpp b/toolkit/xre/glxtest.cpp
new file mode 100644
index 0000000000..e2b8c09e14
--- /dev/null
+++ b/toolkit/xre/glxtest.cpp
@@ -0,0 +1,1281 @@
+/* -*- 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>
+
+#if defined(MOZ_ASAN) || defined(FUZZING)
+# include <signal.h>
+#endif
+
+#include "mozilla/Unused.h"
+#include "nsAppRunner.h" // for IsWaylandEnabled on IsX11EGLEnabled
+#include "stdint.h"
+
+#ifdef __SUNPRO_CC
+# include <stdio.h>
+#endif
+
+#ifdef MOZ_X11
+# include "X11/Xlib.h"
+# include "X11/Xutil.h"
+# include <X11/extensions/Xrandr.h>
+#endif
+
+#ifdef MOZ_WAYLAND
+# include "mozilla/widget/mozwayland.h"
+# include "mozilla/widget/xdg-output-unstable-v1-client-protocol.h"
+# include "prlink.h"
+# include "va/va.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
+# define GLX_DOUBLEBUFFER 5
+#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 unsigned int EGLenum;
+typedef int EGLint;
+typedef void* EGLNativeDisplayType;
+typedef void* EGLSurface;
+typedef void* (*PFNEGLGETPROCADDRESS)(const char*);
+
+#define EGL_NO_CONTEXT nullptr
+#define EGL_NO_SURFACE nullptr
+#define EGL_FALSE 0
+#define EGL_TRUE 1
+#define EGL_OPENGL_ES2_BIT 0x0004
+#define EGL_BLUE_SIZE 0x3022
+#define EGL_GREEN_SIZE 0x3023
+#define EGL_RED_SIZE 0x3024
+#define EGL_NONE 0x3038
+#define EGL_RENDERABLE_TYPE 0x3040
+#define EGL_VENDOR 0x3053
+#define EGL_EXTENSIONS 0x3055
+#define EGL_CONTEXT_MAJOR_VERSION 0x3098
+#define EGL_OPENGL_ES_API 0x30A0
+#define EGL_OPENGL_API 0x30A2
+#define EGL_DEVICE_EXT 0x322C
+#define EGL_DRM_DEVICE_FILE_EXT 0x3233
+#define EGL_DRM_RENDER_NODE_FILE_EXT 0x3377
+
+// stuff from xf86drm.h
+#define DRM_NODE_RENDER 2
+#define DRM_NODE_MAX 3
+
+typedef struct _drmPciDeviceInfo {
+ uint16_t vendor_id;
+ uint16_t device_id;
+ uint16_t subvendor_id;
+ uint16_t subdevice_id;
+ uint8_t revision_id;
+} drmPciDeviceInfo, *drmPciDeviceInfoPtr;
+
+typedef struct _drmDevice {
+ char** nodes;
+ int available_nodes;
+ int bustype;
+ union {
+ void* pci;
+ void* usb;
+ void* platform;
+ void* host1x;
+ } businfo;
+ union {
+ drmPciDeviceInfoPtr 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
+
+// bits to use decoding childvaapitest() return values.
+constexpr int CODEC_HW_H264 = 1 << 4;
+constexpr int CODEC_HW_VP8 = 1 << 5;
+constexpr int CODEC_HW_VP9 = 1 << 6;
+constexpr int CODEC_HW_AV1 = 1 << 7;
+
+// 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;
+
+static char* glxtest_render_device_path = nullptr;
+
+// 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() {
+ if (access("/sys/bus/pci/", F_OK) != 0 &&
+ access("/sys/bus/pci_express/", F_OK) != 0) {
+ record_warning("cannot access /sys/bus/pci");
+ return;
+ }
+
+ 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 void set_render_device_path(const char* render_device_path) {
+ record_value("DRM_RENDERDEVICE\n%s\n", render_device_path);
+ glxtest_render_device_path = strdup(render_device_path);
+}
+
+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 bool get_render_name(const char* name) {
+ void* libdrm = dlopen(LIBDRM_FILENAME, RTLD_LAZY);
+ if (!libdrm) {
+ record_warning("Failed to open libdrm");
+ return false;
+ }
+
+ 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 false;
+ }
+
+ uint32_t flags = 0;
+ int devices_len = drmGetDevices2(flags, nullptr, 0);
+ if (devices_len < 0) {
+ record_warning("drmGetDevices2 failed");
+ dlclose(libdrm);
+ return false;
+ }
+ drmDevice** devices = (drmDevice**)calloc(devices_len, sizeof(drmDevice*));
+ if (!devices) {
+ record_warning("Allocation error");
+ dlclose(libdrm);
+ return false;
+ }
+ devices_len = drmGetDevices2(flags, devices, devices_len);
+ if (devices_len < 0) {
+ free(devices);
+ record_warning("drmGetDevices2 failed");
+ dlclose(libdrm);
+ return false;
+ }
+
+ const drmDevice* match = nullptr;
+ for (int i = 0; i < devices_len; i++) {
+ if (device_has_name(devices[i], name)) {
+ match = devices[i];
+ break;
+ }
+ }
+
+ // Fallback path for split kms/render devices - if only one drm render node
+ // exists it's most likely the one we're looking for.
+ if (match && !(match->available_nodes & (1 << DRM_NODE_RENDER))) {
+ match = nullptr;
+ for (int i = 0; i < devices_len; i++) {
+ if (devices[i]->available_nodes & (1 << DRM_NODE_RENDER)) {
+ if (!match) {
+ match = devices[i];
+ } else {
+ // more than one candidate found, stop trying.
+ match = nullptr;
+ break;
+ }
+ }
+ }
+ if (match) {
+ record_warning(
+ "DRM render node not clearly detectable. Falling back to using the "
+ "only one that was found.");
+ } else {
+ record_warning("DRM device has no render node");
+ }
+ }
+
+ bool result = false;
+ if (!match) {
+ record_warning("Cannot find DRM device");
+ } else {
+ set_render_device_path(match->nodes[DRM_NODE_RENDER]);
+ record_value(
+ "MESA_VENDOR_ID\n0x%04x\n"
+ "MESA_DEVICE_ID\n0x%04x\n",
+ match->deviceinfo.pci->vendor_id, match->deviceinfo.pci->device_id);
+ result = true;
+ }
+
+ for (int i = 0; i < devices_len; i++) {
+ drmFreeDevice(&devices[i]);
+ }
+ free(devices);
+
+ dlclose(libdrm);
+ return result;
+}
+#endif
+
+static bool get_egl_gl_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 EGLBoolean (*PFNEGLBINDAPIPROC)(EGLint api);
+ PFNEGLBINDAPIPROC eglBindAPI =
+ cast<PFNEGLBINDAPIPROC>(eglGetProcAddress("eglBindAPI"));
+
+ typedef EGLContext (*PFNEGLCREATECONTEXTPROC)(
+ EGLDisplay dpy, EGLConfig config, EGLContext share_context,
+ EGLint const* attrib_list);
+ PFNEGLCREATECONTEXTPROC eglCreateContext =
+ cast<PFNEGLCREATECONTEXTPROC>(eglGetProcAddress("eglCreateContext"));
+
+ 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 || !eglMakeCurrent ||
+ !eglQueryDeviceStringEXT) {
+ record_warning("libEGL missing methods for GL test");
+ return false;
+ }
+
+ typedef GLubyte* (*PFNGLGETSTRING)(GLenum);
+ PFNGLGETSTRING glGetString =
+ cast<PFNGLGETSTRING>(eglGetProcAddress("glGetString"));
+
+#if defined(__aarch64__)
+ bool useGles = true;
+#else
+ bool useGles = false;
+#endif
+
+ std::vector<EGLint> attribs;
+ attribs.push_back(EGL_RED_SIZE);
+ attribs.push_back(8);
+ attribs.push_back(EGL_GREEN_SIZE);
+ attribs.push_back(8);
+ attribs.push_back(EGL_BLUE_SIZE);
+ attribs.push_back(8);
+ if (useGles) {
+ attribs.push_back(EGL_RENDERABLE_TYPE);
+ attribs.push_back(EGL_OPENGL_ES2_BIT);
+ }
+ attribs.push_back(EGL_NONE);
+
+ EGLConfig config;
+ EGLint num_config;
+ if (eglChooseConfig(dpy, attribs.data(), &config, 1, &num_config) ==
+ EGL_FALSE) {
+ record_warning("eglChooseConfig returned an error");
+ return false;
+ }
+
+ EGLenum api = useGles ? EGL_OPENGL_ES_API : EGL_OPENGL_API;
+ if (eglBindAPI(api) == EGL_FALSE) {
+ record_warning("eglBindAPI returned an error");
+ return false;
+ }
+
+ EGLint ctx_attrs[] = {EGL_CONTEXT_MAJOR_VERSION, 3, EGL_NONE};
+ EGLContext ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs);
+ if (!ectx) {
+ EGLint ctx_attrs_fallback[] = {EGL_CONTEXT_MAJOR_VERSION, 2, EGL_NONE};
+ ectx = eglCreateContext(dpy, config, EGL_NO_CONTEXT, ctx_attrs_fallback);
+ if (!ectx) {
+ record_warning("eglCreateContext returned an error");
+ return false;
+ }
+ }
+
+ if (eglMakeCurrent(dpy, EGL_NO_SURFACE, EGL_NO_SURFACE, ectx) == EGL_FALSE) {
+ record_warning("eglMakeCurrent returned an error");
+ return false;
+ }
+
+ // 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_warning(LIBGL_FILENAME " and " LIBGLES_FILENAME " missing");
+ return false;
+ }
+ }
+
+ glGetString = cast<PFNGLGETSTRING>(dlsym(libgl, "glGetString"));
+ if (!glGetString) {
+ dlclose(libgl);
+ record_warning("libEGL, libGL and libGLESv2 are missing glGetString");
+ return false;
+ }
+ }
+
+ 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_warning("EGL glGetString returned null");
+ if (libgl) {
+ dlclose(libgl);
+ }
+ return false;
+ }
+
+ EGLDeviceEXT device;
+ if (eglQueryDisplayAttribEXT(dpy, EGL_DEVICE_EXT, (EGLAttrib*)&device) ==
+ EGL_TRUE) {
+ const char* deviceExtensions =
+ eglQueryDeviceStringEXT(device, EGL_EXTENSIONS);
+ if (deviceExtensions &&
+ strstr(deviceExtensions, "EGL_MESA_device_software")) {
+ record_value("MESA_ACCELERATED\nFALSE\n");
+ } else {
+#ifdef MOZ_WAYLAND
+ const char* deviceString =
+ eglQueryDeviceStringEXT(device, EGL_DRM_DEVICE_FILE_EXT);
+ if (!deviceString || !get_render_name(deviceString)) {
+ const char* renderNodeString =
+ eglQueryDeviceStringEXT(device, EGL_DRM_RENDER_NODE_FILE_EXT);
+ if (renderNodeString) {
+ set_render_device_path(renderNodeString);
+ }
+ }
+#endif
+ }
+ }
+
+ if (libgl) {
+ dlclose(libgl);
+ }
+ return true;
+}
+
+static bool get_egl_status(EGLNativeDisplayType native_dpy) {
+ void* libegl = dlopen(LIBEGL_FILENAME, RTLD_LAZY);
+ if (!libegl) {
+ record_warning("libEGL missing");
+ return false;
+ }
+
+ PFNEGLGETPROCADDRESS eglGetProcAddress =
+ cast<PFNEGLGETPROCADDRESS>(dlsym(libegl, "eglGetProcAddress"));
+
+ if (!eglGetProcAddress) {
+ dlclose(libegl);
+ record_warning("no eglGetProcAddress");
+ return false;
+ }
+
+ 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_warning("libEGL missing methods");
+ return false;
+ }
+
+ EGLDisplay dpy = eglGetDisplay(native_dpy);
+ if (!dpy) {
+ dlclose(libegl);
+ record_warning("libEGL no display");
+ return false;
+ }
+
+ EGLint major, minor;
+ if (!eglInitialize(dpy, &major, &minor)) {
+ dlclose(libegl);
+ record_warning("libEGL initialize failed");
+ return false;
+ }
+
+ 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);
+ }
+ }
+
+ if (!get_egl_gl_status(dpy, eglGetProcAddress)) {
+ eglTerminate(dpy);
+ dlclose(libegl);
+ return false;
+ }
+
+ eglTerminate(dpy);
+ dlclose(libegl);
+ return true;
+}
+
+#ifdef MOZ_X11
+static void get_xrandr_info(Display* dpy) {
+ // When running on remote X11 the xrandr version may be stuck on an ancient
+ // version. There are still setups using remote X11 out there, so make sure we
+ // don't crash.
+ int eventBase, errorBase, major, minor;
+ if (!XRRQueryExtension(dpy, &eventBase, &errorBase) ||
+ !XRRQueryVersion(dpy, &major, &minor) ||
+ !(major > 1 || (major == 1 && minor >= 4))) {
+ return;
+ }
+
+ Window root = RootWindow(dpy, DefaultScreen(dpy));
+ XRRProviderResources* pr = XRRGetProviderResources(dpy, root);
+ XRRScreenResources* res = XRRGetScreenResourcesCurrent(dpy, root);
+ if (pr->nproviders != 0) {
+ record_value("DDX_DRIVER\n");
+ for (int i = 0; i < pr->nproviders; i++) {
+ XRRProviderInfo* info = XRRGetProviderInfo(dpy, res, pr->providers[i]);
+ record_value("%s%s", info->name, i == pr->nproviders - 1 ? ";\n" : ";");
+ }
+ }
+}
+
+static void glxtest() {
+ 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) {
+ int attribs2[] = {GLX_RGBA, GLX_RED_SIZE, 1, GLX_GREEN_SIZE,
+ 1, GLX_BLUE_SIZE, 1, GLX_DOUBLEBUFFER,
+ None};
+ vInfo = glXChooseVisual(dpy, DefaultScreen(dpy), attribs2);
+ 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");
+ } 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) {
+ record_value("DRI_DRIVER\n%s\n", driDriver);
+ }
+ }
+
+ // Get monitor and DDX driver information
+ get_xrandr_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);
+
+ record_value("TEST_TYPE\nGLX\n");
+}
+
+static bool x11_egltest() {
+ Display* dpy = XOpenDisplay(nullptr);
+ if (!dpy) {
+ return false;
+ }
+ XSetErrorHandler(x_error_handler);
+
+ if (!get_egl_status(dpy)) {
+ return false;
+ }
+
+ // Bug 1667621: 30bit "Deep Color" is broken on EGL on Mesa (as of 2021/10).
+ // Disable all non-standard depths for the initial EGL roleout.
+ int screenCount = ScreenCount(dpy);
+ for (int idx = 0; idx < screenCount; idx++) {
+ if (DefaultDepth(dpy, idx) != 24) {
+ return false;
+ }
+ }
+
+ // Get monitor and DDX driver information
+ get_xrandr_info(dpy);
+
+ // Bug 1715245: Closing the display connection here crashes on NV prop.
+ // drivers. Just leave it open, the process will exit shortly after anyway.
+ // XCloseDisplay(dpy);
+
+ record_value("TEST_TYPE\nEGL\n");
+ return true;
+}
+#endif
+
+#ifdef MOZ_WAYLAND
+static void 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) {
+ record_error("Could not connect to wayland socket");
+ return;
+ }
+
+ if (!get_egl_status((EGLNativeDisplayType)dpy)) {
+ record_error("EGL test failed");
+ }
+
+ // This is enough to crash some broken NVIDIA prime + Wayland setups, see
+ // https://github.com/NVIDIA/egl-wayland/issues/41 and bug 1768260.
+ wl_display_roundtrip(dpy);
+
+ wl_display_disconnect(dpy);
+ record_value("TEST_TYPE\nEGL\n");
+}
+
+static constexpr struct {
+ VAProfile mVAProfile;
+ nsLiteralCString mName;
+} kVAAPiProfileName[] = {
+# define MAP(v) \
+ { VAProfile##v, nsLiteralCString(#v) }
+ MAP(H264ConstrainedBaseline),
+ MAP(H264Main),
+ MAP(H264High),
+ MAP(VP8Version0_3),
+ MAP(VP9Profile0),
+ MAP(VP9Profile2),
+ MAP(AV1Profile0),
+ MAP(AV1Profile1),
+# undef MAP
+};
+
+static const char* VAProfileName(VAProfile aVAProfile) {
+ for (const auto& profile : kVAAPiProfileName) {
+ if (profile.mVAProfile == aVAProfile) {
+ return profile.mName.get();
+ }
+ }
+ return nullptr;
+}
+
+int childvaapitest() {
+ int renderDeviceFD = -1;
+ VAProfile* profiles = nullptr;
+ VAEntrypoint* entryPoints = nullptr;
+ PRLibrary* libDrm = nullptr;
+ VADisplay display = nullptr;
+
+ auto autoRelease = mozilla::MakeScopeExit([&] {
+ if (renderDeviceFD > -1) {
+ close(renderDeviceFD);
+ }
+ delete[] profiles;
+ delete[] entryPoints;
+ if (display) {
+ vaTerminate(display);
+ }
+ if (libDrm) {
+ PR_UnloadLibrary(libDrm);
+ }
+ });
+
+ renderDeviceFD = open(glxtest_render_device_path, O_RDWR);
+ if (renderDeviceFD == -1) {
+ return 3;
+ }
+
+ PRLibSpec lspec;
+ lspec.type = PR_LibSpec_Pathname;
+ const char* libName = "libva-drm.so.2";
+ lspec.value.pathname = libName;
+ libDrm = PR_LoadLibraryWithFlags(lspec, PR_LD_NOW | PR_LD_LOCAL);
+ if (!libDrm) {
+ return 4;
+ }
+
+ static auto sVaGetDisplayDRM =
+ (void* (*)(int fd))PR_FindSymbol(libDrm, "vaGetDisplayDRM");
+ if (!sVaGetDisplayDRM) {
+ return 5;
+ }
+
+ display = sVaGetDisplayDRM(renderDeviceFD);
+ if (!display) {
+ return 6;
+ }
+
+ int major, minor;
+ VAStatus status = vaInitialize(display, &major, &minor);
+ if (status != VA_STATUS_SUCCESS) {
+ return 7;
+ }
+
+ int maxProfiles = vaMaxNumProfiles(display);
+ int maxEntryPoints = vaMaxNumEntrypoints(display);
+ if (MOZ_UNLIKELY(maxProfiles <= 0 || maxEntryPoints <= 0)) {
+ return 8;
+ }
+
+ profiles = new VAProfile[maxProfiles];
+ int numProfiles = 0;
+ status = vaQueryConfigProfiles(display, profiles, &numProfiles);
+ if (status != VA_STATUS_SUCCESS) {
+ return 9;
+ }
+ numProfiles = std::min(numProfiles, maxProfiles);
+
+ entryPoints = new VAEntrypoint[maxEntryPoints];
+ int codecs = 0;
+ bool foundProfile = false;
+ for (int p = 0; p < numProfiles; p++) {
+ VAProfile profile = profiles[p];
+
+ // Check only supported profiles
+ if (!VAProfileName(profile)) {
+ continue;
+ }
+
+ int numEntryPoints = 0;
+ status = vaQueryConfigEntrypoints(display, profile, entryPoints,
+ &numEntryPoints);
+ if (status != VA_STATUS_SUCCESS) {
+ continue;
+ }
+ numEntryPoints = std::min(numEntryPoints, maxEntryPoints);
+
+ for (int entry = 0; entry < numEntryPoints; entry++) {
+ if (entryPoints[entry] != VAEntrypointVLD) {
+ continue;
+ }
+ VAConfigID config = VA_INVALID_ID;
+ status = vaCreateConfig(display, profile, entryPoints[entry], nullptr, 0,
+ &config);
+ if (status == VA_STATUS_SUCCESS) {
+ const char* profstr = VAProfileName(profile);
+ // VAProfileName returns null on failure, making the below calls safe
+ if (!strncmp(profstr, "H264", 4)) {
+ codecs |= CODEC_HW_H264;
+ } else if (!strncmp(profstr, "VP8", 3)) {
+ codecs |= CODEC_HW_VP8;
+ } else if (!strncmp(profstr, "VP9", 3)) {
+ codecs |= CODEC_HW_VP9;
+ } else if (!strncmp(profstr, "AV1", 3)) {
+ codecs |= CODEC_HW_AV1;
+ } else {
+ char warnbuf[128];
+ SprintfBuf(warnbuf, 128, "VA-API test unknown profile: %s", profstr);
+ record_warning(warnbuf);
+ }
+ vaDestroyConfig(display, config);
+ foundProfile = true;
+ }
+ }
+ }
+ if (foundProfile) {
+ return codecs;
+ }
+ return 10;
+}
+
+static void vaapitest() {
+ if (!glxtest_render_device_path) {
+ return;
+ }
+
+ pid_t vaapitest_pid = fork();
+ if (vaapitest_pid == 0) {
+# if defined(MOZ_ASAN) || defined(FUZZING)
+ // If handle_segv=1 (default), then glxtest crash will print a sanitizer
+ // report which can confuse the harness in fuzzing automation.
+ signal(SIGSEGV, SIG_DFL);
+# endif
+ int vaapirv = childvaapitest();
+ _exit(vaapirv);
+ } else if (vaapitest_pid > 0) {
+ int vaapitest_status = 0;
+ bool wait_for_vaapitest_process = true;
+
+ while (wait_for_vaapitest_process) {
+ if (waitpid(vaapitest_pid, &vaapitest_status, 0) == -1) {
+ wait_for_vaapitest_process = false;
+ record_warning(
+ "VA-API test failed: waiting for VA-API process failed.");
+ } else if (WIFEXITED(vaapitest_status) || WIFSIGNALED(vaapitest_status)) {
+ wait_for_vaapitest_process = false;
+ }
+ }
+
+ if (WIFEXITED(vaapitest_status)) {
+ // Note that WEXITSTATUS only returns least significant 8 bits
+ // of the exit code, despite returning type int.
+ int exitcode = WEXITSTATUS(vaapitest_status);
+ int codecs = exitcode & 0b1111'0000;
+ exitcode &= 0b0000'1111;
+ switch (exitcode) {
+ case 0:
+ char codecstr[80];
+ record_value("VAAPI_SUPPORTED\nTRUE\n");
+ SprintfBuf(codecstr, 80, "%d", codecs);
+ record_value("VAAPI_HWCODECS\n");
+ record_value(codecstr);
+ record_value("\n");
+ break;
+ case 3:
+ record_warning(
+ "VA-API test failed: opening render device path failed.");
+ break;
+ case 4:
+ record_warning(
+ "VA-API test failed: missing or old libva-drm library.");
+ break;
+ case 5:
+ record_warning("VA-API test failed: missing vaGetDisplayDRM.");
+ break;
+ case 6:
+ record_warning("VA-API test failed: failed to get vaGetDisplayDRM.");
+ break;
+ case 7:
+ record_warning(
+ "VA-API test failed: failed to initialise VAAPI connection.");
+ break;
+ case 8:
+ record_warning(
+ "VA-API test failed: wrong VAAPI profiles/entry point nums.");
+ break;
+ case 9:
+ record_warning("VA-API test failed: vaQueryConfigProfiles() failed.");
+ break;
+ case 10:
+ record_warning(
+ "VA-API test failed: no supported VAAPI profile found.");
+ break;
+ default:
+ record_warning(
+ "VA-API test failed: Something unexpected went wrong.");
+ break;
+ }
+ } else {
+ record_warning(
+ "VA-API test failed: process crashed. Please check your VA-API "
+ "drivers.");
+ }
+ } else {
+ record_warning("VA-API test failed: Could not fork process.");
+ }
+}
+#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();
+
+#ifdef MOZ_WAYLAND
+ if (IsWaylandEnabled()) {
+ wayland_egltest();
+ } else
+#endif
+ {
+#ifdef MOZ_X11
+ // TODO: --display command line argument is not properly handled
+ if (!x11_egltest()) {
+ glxtest();
+ }
+#endif
+ }
+
+#ifdef MOZ_WAYLAND
+ vaapitest();
+#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();
+#if defined(MOZ_ASAN) || defined(FUZZING)
+ // If handle_segv=1 (default), then glxtest crash will print a sanitizer
+ // report which can confuse the harness in fuzzing automation.
+ signal(SIGSEGV, SIG_DFL);
+#endif
+ 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/metrics.yaml b/toolkit/xre/metrics.yaml
new file mode 100644
index 0000000000..c53ee95ecd
--- /dev/null
+++ b/toolkit/xre/metrics.yaml
@@ -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/.
+
+# Adding a new metric? We have docs for that!
+# https://firefox-source-docs.mozilla.org/toolkit/components/glean/user/new_definitions_file.html
+
+---
+$schema: moz://mozilla.org/schemas/glean/metrics/2-0-0
+$tags:
+ - "Toolkit :: Startup and Profile System"
diff --git a/toolkit/xre/moz.build b/toolkit/xre/moz.build
new file mode 100644
index 0000000000..057daff46b
--- /dev/null
+++ b/toolkit/xre/moz.build
@@ -0,0 +1,293 @@
+# -*- 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",
+ "GeckoArgs.h",
+ "MultiInstanceLock.h",
+ "SafeMode.h",
+]
+
+if CONFIG["MOZ_INSTRUMENT_EVENT_LOOP"]:
+ EXPORTS += ["EventTracer.h"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "windows":
+ EXPORTS.mozilla += [
+ "AssembleCmdLine.h",
+ "DllPrefetchExperimentRegistryInfo.h",
+ "PolicyChecks.h",
+ "WinTokenUtils.h",
+ ]
+ UNIFIED_SOURCES += [
+ "/toolkit/mozapps/update/common/pathhash.cpp",
+ "/toolkit/mozapps/update/common/updateutils_win.cpp",
+ "DllPrefetchExperimentRegistryInfo.cpp",
+ "nsNativeAppSupportWin.cpp",
+ "WinTokenUtils.cpp",
+ ]
+ DEFINES["PROXY_PRINTING"] = 1
+ LOCAL_INCLUDES += [
+ "../components/printingui",
+ ]
+ if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ EXPORTS.mozilla += [
+ "LauncherRegistryInfo.h",
+ ]
+ UNIFIED_SOURCES += [
+ "LauncherRegistryInfo.cpp",
+ ]
+ DIRS += [
+ "dllservices",
+ ]
+elif CONFIG["MOZ_WIDGET_TOOLKIT"] == "cocoa":
+ EXPORTS.mozilla += [
+ "MacRunFromDmgUtils.h",
+ ]
+ UNIFIED_SOURCES += [
+ "MacApplicationDelegate.mm",
+ "MacAutoreleasePool.mm",
+ "MacLaunchHelper.mm",
+ "MacRunFromDmgUtils.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",
+ ]
+ CXXFLAGS += CONFIG["MOZ_X11_SM_CFLAGS"]
+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",
+ "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"] or CONFIG["MOZ_WAYLAND"]:
+ UNIFIED_SOURCES += [
+ "glxtest.cpp",
+ ]
+
+if CONFIG["MOZ_WAYLAND"]:
+ LOCAL_INCLUDES += [
+ "/media/mozva",
+ ]
+ USE_LIBS += [
+ "mozva",
+ ]
+
+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"] or CONFIG["MOZ_WAYLAND"]:
+ 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"]
+
+DEFINES["MOZ_DISTRIBUTION_ID"] = '"%s"' % CONFIG["MOZ_DISTRIBUTION_ID"]
+
+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["MOZ_DBUS_CFLAGS"]
+CXXFLAGS += CONFIG["MOZ_DBUS_GLIB_CFLAGS"]
+
+if CONFIG["MOZ_WIDGET_TOOLKIT"] == "gtk":
+ CXXFLAGS += CONFIG["MOZ_GTK3_CFLAGS"]
+ CXXFLAGS += CONFIG["MOZ_PANGO_CFLAGS"]
+
+DEFINES["TOPOBJDIR"] = TOPOBJDIR
+FINAL_TARGET_PP_FILES += ["platform.ini"]
+
+if CONFIG["ENABLE_TESTS"]:
+ DIRS += ["test/gtest"]
+
+REQUIRES_UNIFIED_BUILD = True
diff --git a/toolkit/xre/nsAndroidStartup.cpp b/toolkit/xre/nsAndroidStartup.cpp
new file mode 100644
index 0000000000..d581dc47a4
--- /dev/null
+++ b/toolkit/xre/nsAndroidStartup.cpp
@@ -0,0 +1,57 @@
+/* -*- 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 "mozilla/Bootstrap.h"
+#include "XREShellData.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,
+ bool xpcshell, const char* outFilePath) {
+ mozilla::jni::SetGeckoThreadEnv(env);
+
+ if (!argv) {
+ LOG("Failed to get arguments for GeckoStart\n");
+ return;
+ }
+
+ if (xpcshell) {
+ XREShellData shellData;
+ FILE* outFile = fopen(outFilePath, "w");
+ if (!outFile) {
+ LOG("XRE_XPCShellMain cannot open %s", outFilePath);
+ return;
+ }
+ // We redirect both stdout and stderr to the same file, to conform with
+ // what runxpcshell.py does on Desktop.
+ shellData.outFile = outFile;
+ shellData.errFile = outFile;
+ int result = XRE_XPCShellMain(argc, argv, nullptr, &shellData);
+ fclose(shellData.outFile);
+ if (result) LOG("XRE_XPCShellMain returned %d", result);
+ } else {
+ 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..4f11400140
--- /dev/null
+++ b/toolkit/xre/nsAppRunner.cpp
@@ -0,0 +1,6273 @@
+/* -*- 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/ProcessType.h"
+#include "mozilla/ResultExtensions.h"
+#include "mozilla/RuntimeExceptionModule.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/StaticPrefs_browser.h"
+#include "mozilla/StaticPrefs_fission.h"
+#include "mozilla/StaticPrefs_webgl.h"
+#include "mozilla/StaticPrefs_widget.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/Utf8.h"
+#include "mozilla/intl/LocaleService.h"
+#include "mozilla/JSONWriter.h"
+#include "mozilla/gfx/gfxVars.h"
+#include "mozilla/glean/GleanPings.h"
+#include "mozilla/widget/TextRecognition.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"
+# include "nsUpdateSyncManager.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"
+# include "MacRunFromDmgUtils.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 "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 "PDMFactory.h"
+#ifdef XP_MACOSX
+# include "gfxPlatformMac.h"
+#endif
+
+#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 "detect_win32k_conflicts.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"
+# include "mozilla/mscom/ProfilerMarkers.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"
+#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 "nsIDUtils.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 "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"
+#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"
+# endif
+#endif
+
+#ifdef MOZ_CODE_COVERAGE
+# include "mozilla/CodeCoverageHandler.h"
+#endif
+
+#include "SafeMode.h"
+
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+# include "nsIPowerManagerService.h"
+# include "nsIStringBundle.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";
+static const char kPrefSetDefaultBrowserUserChoicePref[] =
+ "browser.shell.setDefaultBrowserUserChoice";
+#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";
+#endif // defined(XP_WIN)
+
+#if defined(MOZ_WIDGET_GTK)
+constexpr nsLiteralCString kStartupTokenNames[] = {
+ "XDG_ACTIVATION_TOKEN"_ns,
+ "DESKTOP_STARTUP_ID"_ns,
+};
+#endif
+
+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;
+#if defined(MOZ_HAS_REMOTE)
+static nsRemoteService* gRemoteService;
+bool gRestartWithoutRemote = false;
+#endif
+
+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(XP_WIN)
+nsString gProcessStartupShortcut;
+#endif
+
+#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 "mozilla/WidgetUtilsGtk.h"
+# 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);
+ }
+#elif defined(MOZ_WIDGET_ANDROID)
+ SmprintfPointer msg = mozilla::Vsmprintf(fmt, ap);
+ if (msg) {
+ __android_log_print(isError ? ANDROID_LOG_ERROR : ANDROID_LOG_INFO,
+ "GeckoRuntime", "%s", msg.get());
+ }
+#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;
+
+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
+
+// Win32k Infrastructure ==============================================
+
+// This bool tells us if we have initialized the following two statics
+// upon startup.
+
+static bool gWin32kInitialized = false;
+
+// Win32k Lockdown for the current session is determined once, at startup,
+// and then remains the same for the duration of the session.
+
+static nsIXULRuntime::ContentWin32kLockdownState gWin32kStatus;
+
+// The status of the win32k experiment. It is determined once, at startup,
+// and then remains the same for the duration of the session.
+
+static nsIXULRuntime::ExperimentStatus gWin32kExperimentStatus =
+ nsIXULRuntime::eExperimentStatusUnenrolled;
+
+#ifdef XP_WIN
+// The states for win32k lockdown can be generalized to:
+// - User doesn't meet requirements (bad OS, webrender, remotegl) aka
+// PersistentRequirement
+// - User has Safe Mode enabled, Win32k Disabled by Env Var, or E10S disabled
+// by Env Var aka TemporaryRequirement
+// - User has set the win32k pref to something non-default aka NonDefaultPref
+// - User has been enrolled in the experiment and does what it says aka
+// Enrolled
+// - User does the default aka Default
+//
+// We expect the below behvaior. In the code, there may be references to these
+// behaviors (e.g. [A]) but not always.
+//
+// [A] Becoming enrolled in the experiment while NonDefaultPref disqualifies
+// you upon next browser start
+// [B] Becoming enrolled in the experiment while PersistentRequirement
+// disqualifies you upon next browser start
+// [C] Becoming enrolled in the experiment while TemporaryRequirement does not
+// disqualify you
+// [D] Becoming enrolled in the experiment will only take effect after restart
+// [E] While enrolled, becoming PersistentRequirement will not disqualify you
+// until browser restart, but if the state is present at browser start,
+// will disqualify you. Additionally, it will not affect the session value
+// of gWin32kStatus.
+// XXX(bobowen): I hope both webrender and wbgl.out-of-process require a
+// restart to take effect!
+// [F] While enrolled, becoming NonDefaultPref will disqualify you upon next
+// browser start
+// [G] While enrolled, becoming TemporaryRequirement will _not_ disqualify you,
+// but the session status will prevent win32k lockdown from taking effect.
+
+// The win32k pref
+static const char kPrefWin32k[] = "security.sandbox.content.win32k-disable";
+
+// 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 kPrefWin32kExperimentEnrollmentStatus[] =
+ "security.sandbox.content.win32k-experiment.enrollmentStatus";
+
+// The enrollment status to be used at browser startup. This automatically
+// synced from the above enrollmentStatus preference whenever the latter is
+// changed. We reused the Fission experiment enum - it can have any of the
+// values defined in the `nsIXULRuntime_ExperimentStatus` enum _except_ rollout.
+// Meanings are documented in the declaration of
+// `nsIXULRuntime.fissionExperimentStatus`
+static const char kPrefWin32kExperimentStartupEnrollmentStatus[] =
+ "security.sandbox.content.win32k-experiment.startupEnrollmentStatus";
+
+namespace mozilla {
+
+bool Win32kExperimentEnrolled() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return gWin32kExperimentStatus == nsIXULRuntime::eExperimentStatusControl ||
+ gWin32kExperimentStatus == nsIXULRuntime::eExperimentStatusTreatment;
+}
+
+} // namespace mozilla
+
+static bool Win32kRequirementsUnsatisfied(
+ nsIXULRuntime::ContentWin32kLockdownState aStatus) {
+ return aStatus == nsIXULRuntime::ContentWin32kLockdownState::
+ OperatingSystemNotSupported ||
+ aStatus ==
+ nsIXULRuntime::ContentWin32kLockdownState::MissingWebRender ||
+ aStatus ==
+ nsIXULRuntime::ContentWin32kLockdownState::MissingRemoteWebGL ||
+ aStatus ==
+ nsIXULRuntime::ContentWin32kLockdownState::DecodersArentRemote;
+}
+
+static void Win32kExperimentDisqualify() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ Preferences::SetUint(kPrefWin32kExperimentEnrollmentStatus,
+ nsIXULRuntime::eExperimentStatusDisqualified);
+}
+
+static void OnWin32kEnrollmentStatusChanged(const char* aPref, void* aData) {
+ auto newStatusInt =
+ Preferences::GetUint(kPrefWin32kExperimentEnrollmentStatus,
+ nsIXULRuntime::eExperimentStatusUnenrolled);
+ auto newStatus = newStatusInt < nsIXULRuntime::eExperimentStatusCount
+ ? nsIXULRuntime::ExperimentStatus(newStatusInt)
+ : nsIXULRuntime::eExperimentStatusDisqualified;
+
+ // Set the startup pref for next browser start [D]
+ Preferences::SetUint(kPrefWin32kExperimentStartupEnrollmentStatus, newStatus);
+}
+
+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 win32k was initialized.
+class Win32kEnrollmentStatusShutdownObserver 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));
+ OnWin32kEnrollmentStatusChanged(kPrefWin32kExperimentEnrollmentStatus,
+ nullptr);
+ return NS_OK;
+ }
+
+ private:
+ ~Win32kEnrollmentStatusShutdownObserver() = default;
+};
+NS_IMPL_ISUPPORTS(Win32kEnrollmentStatusShutdownObserver, nsIObserver)
+} // namespace
+
+static void OnWin32kChanged(const char* aPref, void* aData) {
+ // If we're actively enrolled in the Win32k experiment, disqualify the user
+ // from the experiment if the Win32k pref is modified. [F]
+ if (Win32kExperimentEnrolled() && Preferences::HasUserValue(kPrefWin32k)) {
+ Win32kExperimentDisqualify();
+ }
+}
+
+#endif // XP_WIN
+
+namespace mozilla {
+void EnsureWin32kInitialized();
+}
+
+nsIXULRuntime::ContentWin32kLockdownState GetLiveWin32kLockdownState() {
+#ifdef XP_WIN
+
+ // HasUserValue The Pref functions can only be called on main thread
+ MOZ_ASSERT(NS_IsMainThread());
+ mozilla::EnsureWin32kInitialized();
+ gfxPlatform::GetPlatform();
+
+ if (gSafeMode) {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledBySafeMode;
+ }
+
+ if (EnvHasValue("MOZ_ENABLE_WIN32K")) {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledByEnvVar;
+ }
+
+ if (!mozilla::BrowserTabsRemoteAutostart()) {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledByE10S;
+ }
+
+ // Win32k lockdown is available on Win8+, but we are initially limiting it to
+ // Windows 10 v1709 (build 16299) or later. Before this COM initialization
+ // currently fails if user32.dll has loaded before it is called.
+ if (!IsWin10FallCreatorsUpdateOrLater()) {
+ return nsIXULRuntime::ContentWin32kLockdownState::
+ OperatingSystemNotSupported;
+ }
+
+ {
+ ConflictingMitigationStatus conflictingMitigationStatus = {};
+ if (!detect_win32k_conflicting_mitigations(&conflictingMitigationStatus)) {
+ return nsIXULRuntime::ContentWin32kLockdownState::
+ IncompatibleMitigationPolicy;
+ }
+ if (conflictingMitigationStatus.caller_check ||
+ conflictingMitigationStatus.sim_exec ||
+ conflictingMitigationStatus.stack_pivot) {
+ return nsIXULRuntime::ContentWin32kLockdownState::
+ IncompatibleMitigationPolicy;
+ }
+ }
+
+ // Non-native theming is required as well
+ if (!StaticPrefs::widget_non_native_theme_enabled()) {
+ return nsIXULRuntime::ContentWin32kLockdownState::MissingNonNativeTheming;
+ }
+
+ // Win32k Lockdown requires Remote WebGL, but it may be disabled on
+ // certain hardware or virtual machines.
+ if (!gfx::gfxVars::AllowWebglOop() || !StaticPrefs::webgl_out_of_process()) {
+ return nsIXULRuntime::ContentWin32kLockdownState::MissingRemoteWebGL;
+ }
+
+ // Some (not sure exactly which) decoders are not compatible
+ if (!PDMFactory::AllDecodersAreRemote()) {
+ return nsIXULRuntime::ContentWin32kLockdownState::DecodersArentRemote;
+ }
+
+ bool prefSetByUser =
+ Preferences::HasUserValue("security.sandbox.content.win32k-disable");
+ bool prefValue = Preferences::GetBool(
+ "security.sandbox.content.win32k-disable", false, PrefValueKind::User);
+ bool defaultValue = Preferences::GetBool(
+ "security.sandbox.content.win32k-disable", false, PrefValueKind::Default);
+
+ if (prefSetByUser) {
+ if (prefValue) {
+ return nsIXULRuntime::ContentWin32kLockdownState::EnabledByUserPref;
+ } else {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledByUserPref;
+ }
+ }
+
+ if (gWin32kExperimentStatus ==
+ nsIXULRuntime::ExperimentStatus::eExperimentStatusControl) {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledByControlGroup;
+ } else if (gWin32kExperimentStatus ==
+ nsIXULRuntime::ExperimentStatus::eExperimentStatusTreatment) {
+ return nsIXULRuntime::ContentWin32kLockdownState::EnabledByTreatmentGroup;
+ }
+
+ if (defaultValue) {
+ return nsIXULRuntime::ContentWin32kLockdownState::EnabledByDefault;
+ } else {
+ return nsIXULRuntime::ContentWin32kLockdownState::DisabledByDefault;
+ }
+
+#else
+
+ return nsIXULRuntime::ContentWin32kLockdownState::OperatingSystemNotSupported;
+
+#endif
+}
+
+namespace mozilla {
+
+void EnsureWin32kInitialized() {
+ if (gWin32kInitialized) {
+ return;
+ }
+ gWin32kInitialized = true;
+
+#ifdef XP_WIN
+
+ // Initialize the Win32k experiment, configuring win32k's
+ // default, before checking other overrides. This allows opting-out of a
+ // Win32k experiment through about:preferences or about:config from a
+ // safemode session.
+ uint32_t experimentRaw =
+ Preferences::GetUint(kPrefWin32kExperimentStartupEnrollmentStatus,
+ nsIXULRuntime::eExperimentStatusUnenrolled);
+ gWin32kExperimentStatus =
+ 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(&OnWin32kEnrollmentStatusChanged,
+ kPrefWin32kExperimentEnrollmentStatus);
+ if (nsCOMPtr<nsIObserverService> observerService =
+ mozilla::services::GetObserverService()) {
+ nsCOMPtr<nsIObserver> shutdownObserver =
+ new Win32kEnrollmentStatusShutdownObserver();
+ observerService->AddObserver(shutdownObserver, "profile-before-change",
+ false);
+ }
+
+ // If the user no longer qualifies because they edited a required pref, check
+ // that. [B] [E]
+ auto tmpStatus = GetLiveWin32kLockdownState();
+ if (Win32kExperimentEnrolled() && Win32kRequirementsUnsatisfied(tmpStatus)) {
+ Win32kExperimentDisqualify();
+ gWin32kExperimentStatus = nsIXULRuntime::eExperimentStatusDisqualified;
+ }
+
+ // If the user has overridden an active experiment by setting a user value for
+ // "security.sandbox.content.win32k-disable", disqualify the user from the
+ // experiment. [A] [F]
+ if (Preferences::HasUserValue(kPrefWin32k) && Win32kExperimentEnrolled()) {
+ Win32kExperimentDisqualify();
+ gWin32kExperimentStatus = nsIXULRuntime::eExperimentStatusDisqualified;
+ }
+
+ // Unlike Fission, we do not configure the default branch based on experiment
+ // enrollment status. Instead we check the startupEnrollmentPref in
+ // GetContentWin32kLockdownState()
+
+ Preferences::RegisterCallback(&OnWin32kChanged, kPrefWin32k);
+
+ // Set the state
+ gWin32kStatus = GetLiveWin32kLockdownState();
+
+#else
+ gWin32kStatus =
+ nsIXULRuntime::ContentWin32kLockdownState::OperatingSystemNotSupported;
+ gWin32kExperimentStatus = nsIXULRuntime::eExperimentStatusUnenrolled;
+
+#endif // XP_WIN
+}
+
+nsIXULRuntime::ContentWin32kLockdownState GetWin32kLockdownState() {
+#ifdef XP_WIN
+
+ mozilla::EnsureWin32kInitialized();
+ return gWin32kStatus;
+
+#else
+
+ return nsIXULRuntime::ContentWin32kLockdownState::OperatingSystemNotSupported;
+
+#endif
+}
+
+} // namespace mozilla
+
+// End Win32k Infrastructure ==========================================
+
+// Fission Infrastructure =============================================
+
+// 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;
+
+namespace mozilla {
+
+bool FissionExperimentEnrolled() {
+ MOZ_ASSERT(XRE_IsParentProcess());
+ return gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusControl ||
+ gFissionExperimentStatus ==
+ nsIXULRuntime::eExperimentStatusTreatment ||
+ gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusRollout;
+}
+
+} // namespace mozilla
+
+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 ||
+ gFissionExperimentStatus == nsIXULRuntime::eExperimentStatusRollout;
+ Preferences::SetBool(kPrefFissionAutostart, isTreatment,
+ PrefValueKind::Default);
+ }
+
+ if (!BrowserTabsRemoteAutostart()) {
+ gFissionAutostart = false;
+ if (gBrowserTabsRemoteStatus == kE10sForceDisabled) {
+ gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledByE10sEnv;
+ } else {
+ gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledByE10sOther;
+ }
+ } else if (EnvHasValue("MOZ_FORCE_ENABLE_FISSION")) {
+ gFissionAutostart = true;
+ gFissionDecisionStatus = nsIXULRuntime::eFissionEnabledByEnv;
+ } else if (EnvHasValue("MOZ_FORCE_DISABLE_FISSION")) {
+ gFissionAutostart = false;
+ gFissionDecisionStatus = nsIXULRuntime::eFissionDisabledByEnv;
+ } 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 (gFissionExperimentStatus ==
+ nsIXULRuntime::eExperimentStatusRollout) {
+ gFissionDecisionStatus = nsIXULRuntime::eFissionEnabledByRollout;
+ } 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;
+}
+
+} // namespace mozilla
+
+// End Fission Infrastructure =========================================
+
+namespace mozilla {
+
+bool SessionHistoryInParent() {
+ return FissionAutostart() ||
+ StaticPrefs::
+ fission_sessionHistoryInParent_AtStartup_DoNotUseDirectly();
+}
+
+bool BFCacheInParent() {
+ return SessionHistoryInParent() &&
+ StaticPrefs::fission_bfcacheInParent_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 GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ static_assert(nsIXULRuntime::PROCESS_TYPE_##allcaps_name == \
+ static_cast<int>(GeckoProcessType_##enum_name), \
+ "GeckoProcessType in nsXULAppAPI.h not synchronized with " \
+ "nsIXULRuntime.idl");
+#include "mozilla/GeckoProcessTypes.h"
+#undef GECKO_PROCESS_TYPE
+
+// .. and ensure that that is all of them:
+static_assert(GeckoProcessType_Utility + 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::GetWin32kExperimentStatus(ExperimentStatus* aResult) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ EnsureWin32kInitialized();
+ *aResult = gWin32kExperimentStatus;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetWin32kLiveStatusTestingOnly(
+ nsIXULRuntime::ContentWin32kLockdownState* aResult) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ EnsureWin32kInitialized();
+ *aResult = GetLiveWin32kLockdownState();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetWin32kSessionStatus(
+ nsIXULRuntime::ContentWin32kLockdownState* aResult) {
+ if (!XRE_IsParentProcess()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ EnsureWin32kInitialized();
+ *aResult = gWin32kStatus;
+ 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 eFissionDisabledByEnv:
+ aResult = "disabledByEnv";
+ 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;
+ case eFissionEnabledByRollout:
+ aResult = "enabledByRollout";
+ 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::GetIsTextRecognitionSupported(bool* aResult) {
+ *aResult = widget::TextRecognition::IsSupported();
+ 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::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;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetChromeColorSchemeIsDark(bool* aResult) {
+ LookAndFeel::EnsureColorSchemesInitialized();
+ *aResult = LookAndFeel::ColorSchemeForChrome() == ColorScheme::Dark;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetContentThemeDerivedColorSchemeIsDark(bool* aResult) {
+ *aResult =
+ LookAndFeel::ThemeDerivedColorSchemeForContent() == ColorScheme::Dark;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetDrawInTitlebar(bool* aResult) {
+ *aResult = LookAndFeel::DrawInTitlebar();
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsXULAppInfo::GetProcessStartupShortcut(nsAString& aShortcut) {
+#if defined(XP_WIN)
+ if (XRE_IsParentProcess()) {
+ aShortcut.Assign(gProcessStartupShortcut);
+ return NS_OK;
+ }
+#endif
+ return NS_ERROR_NOT_AVAILABLE;
+}
+
+#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::GetCrashReporterEnabled(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::IsAnnotationAllowlistedForPing(const nsACString& aValue,
+ bool* aIsAllowlisted) {
+ CrashReporter::Annotation annotation;
+
+ if (!AnnotationFromString(annotation, PromiseFlatCString(aValue).get())) {
+ return NS_ERROR_INVALID_ARG;
+ }
+
+ *aIsAllowlisted = CrashReporter::IsAnnotationAllowlistedForPing(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(REFNSIID aIID, void** aResult) {
+ 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;
+ }
+}
+
+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(const nsIID& aIID, void** aResult) {
+ return mSingleton->QueryInterface(aIID, aResult);
+}
+
+/**
+ * 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 && *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 && *gAppData->copyright) {
+ printf(", %s", (const char*)gAppData->copyright);
+ }
+ printf("\n");
+}
+
+static inline void DumpFullVersion() {
+ if (gAppData->vendor && *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 && *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) &&
+ LookAndFeel::DrawInTitlebar();
+ if (shouldBeEnabled && Preferences::HasUserValue(kPrefThemeId)) {
+ nsCString themeId;
+ Preferences::GetCString(kPrefThemeId, themeId);
+ if (themeId.EqualsLiteral("default-theme@mozilla.org")) {
+ Unused << SetPreXULSkeletonUIThemeId(ThemeMode::Default);
+ } else if (themeId.EqualsLiteral("firefox-compact-dark@mozilla.org")) {
+ Unused << SetPreXULSkeletonUIThemeId(ThemeMode::Dark);
+ } else if (themeId.EqualsLiteral("firefox-compact-light@mozilla.org")) {
+ Unused << SetPreXULSkeletonUIThemeId(ThemeMode::Light);
+ } else {
+ shouldBeEnabled = false;
+ }
+ } else if (shouldBeEnabled) {
+ Unused << SetPreXULSkeletonUIThemeId(ThemeMode::Default);
+ }
+
+ if (GetPreXULSkeletonUIEnabled() != shouldBeEnabled) {
+ Unused << SetPreXULSkeletonUIEnabledIfAllowed(shouldBeEnabled);
+ }
+}
+
+static void SetupSkeletonUIPrefs() {
+ ReflectSkeletonUIPrefToRegistry(nullptr, nullptr);
+ Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry,
+ kPrefPreXulSkeletonUI);
+ Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry,
+ kPrefBrowserStartupBlankWindow);
+ Preferences::RegisterCallback(&ReflectSkeletonUIPrefToRegistry, kPrefThemeId);
+ Preferences::RegisterCallback(
+ &ReflectSkeletonUIPrefToRegistry,
+ nsDependentCString(StaticPrefs::GetPrefName_browser_tabs_inTitlebar()));
+}
+
+# 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 OnSetDefaultBrowserUserChoicePrefChanged(const char* aPref,
+ void* aData) {
+ nsresult rv;
+ if (strcmp(aPref, kPrefSetDefaultBrowserUserChoicePref) != 0) {
+ return;
+ }
+ nsAutoString valueName;
+ valueName.AssignLiteral("SetDefaultBrowserUserChoice");
+ 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);
+
+ rv = regKey->WriteIntValue(valueName, prefVal);
+ 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();
+ }
+}
+
+nsresult LaunchChild(bool aBlankCommandLine, bool aTryExec) {
+ // 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;
+ }
+
+#if defined(MOZ_HAS_REMOTE)
+ if (gRestartWithoutRemote) {
+ SaveToEnv("MOZ_NO_REMOTE=1");
+ }
+#endif
+
+ 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 (aTryExec) {
+ execv(exePath.get(), gRestartArgv);
+
+ // If execv returns we know it's because it failed.
+ return NS_ERROR_FAILURE;
+ }
+# endif
+ if (PR_CreateProcessDetached(exePath.get(), gRestartArgv, nullptr, nullptr) ==
+ PR_FAILURE) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Note that we don't know if the child process starts okay, if it
+ // immediately returns non-zero then we may mask that by returning a zero
+ // exit status.
+
+# 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;
+ }
+#ifdef MOZ_BACKGROUNDTASKS
+ // A background task that fails to lock its profile will return
+ // NS_ERROR_UNEXPECTED and this will allow the task to exit with a
+ // non-zero exit code.
+ if (aRv == NS_ERROR_UNEXPECTED && BackgroundTasks::IsBackgroundTaskMode()) {
+ return aRv;
+ }
+#endif
+ return NS_ERROR_ABORT;
+ }
+
+ nsresult mRv;
+};
+
+} // namespace
+
+static nsresult ProfileMissingDialog(nsINativeAppSupport* aNative) {
+#ifdef MOZ_WIDGET_ANDROID
+ // We cannot really do anything this early during initialization, so we just
+ // return as this is likely the effect of misconfiguration on the test side.
+ // Non-test code paths cannot set the profile folder, which is always the
+ // default one.
+ Output(true, "Could not find profile folder.\n");
+ return NS_ERROR_ABORT;
+#else
+# ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // We should never get to this point in background task mode.
+ Output(false,
+ "Could not determine any profile running in backgroundtask mode!\n");
+ return NS_ERROR_ABORT;
+ }
+# endif // MOZ_BACKGROUNDTASKS
+
+ 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::components::StringBundle::Service();
+ 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;
+ }
+#endif // MOZ_WIDGET_ANDROID
+}
+
+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::components::StringBundle::Service();
+ 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);
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // This error is handled specially to exit with a non-zero exit code.
+ printf_stderr("%s\n", NS_LossyConvertUTF16toASCII(killMessage).get());
+ return NS_ERROR_UNEXPECTED;
+ }
+#endif
+
+ 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
+ // On Android we always kill the process if the lock is still being held
+ 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);
+
+#if defined(MOZ_HAS_REMOTE)
+ if (gRemoteService) {
+ gRemoteService->UnlockStartup();
+ gRemoteService = nullptr;
+ }
+#endif
+ return LaunchChild(false, true);
+ }
+ } else {
+ rv = ps->Alert(nullptr, killTitle.get(), killMessage.get());
+ NS_ENSURE_SUCCESS_LOG(rv, rv);
+ }
+
+ 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);
+
+ nsAutoCString features("centerscreen,chrome,modal,titlebar");
+ // If we're launching a private browsing window make sure to set that
+ // feature for the Profile Manager window as well, so it groups correctly
+ // on the Windows taskbar.
+ if (CheckArgExists("private-window") == ARG_FOUND) {
+ features.AppendLiteral(",private");
+ }
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = windowWatcher->OpenWindow(
+ nullptr, nsDependentCString(kProfileManagerURL), "_blank"_ns,
+ features, 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;
+ }
+#if defined(MOZ_HAS_REMOTE)
+ if (gRemoteService) {
+ gRemoteService->UnlockStartup();
+ gRemoteService = nullptr;
+ }
+#endif
+ return LaunchChild(false, true);
+}
+
+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;
+ }
+
+ // 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 final : public JSONWriteFunc {
+ FILE* mFile;
+ explicit FileWriteFunc(FILE* aFile) : mFile(aFile) {}
+
+ void Write(const Span<const char>& aStr) final {
+ 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;
+ rv = nsID::GenerateUUIDInPlace(uuid);
+ NS_ENSURE_SUCCESS_VOID(rv);
+
+ 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_TrimBracketsASCII pingId(uuid);
+ 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);
+
+ nsAutoCString features("centerscreen,chrome,modal,titlebar");
+ // If we're launching a private browsing window make sure to set that
+ // feature for the Profile Manager window as well, so it groups correctly
+ // on the Windows taskbar.
+ if (CheckArgExists("private-window") == ARG_FOUND) {
+ features.AppendLiteral(",private");
+ }
+ nsCOMPtr<mozIDOMWindowProxy> newWindow;
+ rv = windowWatcher->OpenWindow(
+ nullptr, nsDependentCString(kProfileDowngradeURL), "_blank"_ns,
+ features, 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, true);
+ }
+
+ // 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_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"
+#include "ProfilerControl.h"
+
+// Encapsulates startup and shutdown state for XRE_main
+class XREMain {
+ public:
+ XREMain() = default;
+
+ ~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;
+
+#ifdef MOZ_WIDGET_GTK
+ nsAutoCString mXDGActivationToken;
+ nsAutoCString mDesktopStartupID;
+#endif
+
+ bool mStartOffline = false;
+ bool mShuttingDown = false;
+#if defined(MOZ_HAS_REMOTE)
+ bool mDisableRemoteClient = false;
+ bool mDisableRemoteServer = false;
+#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
+
+void mozilla::startup::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
+}
+
+#ifdef XP_WIN
+
+static uint32_t GetMicrocodeVersionByVendor(HKEY key, DWORD upper,
+ DWORD lower) {
+ WCHAR data[13]; // The CPUID vendor string is 12 characters long plus null
+ DWORD len = sizeof(data);
+ DWORD vtype;
+
+ if (RegQueryValueExW(key, L"VendorIdentifier", nullptr, &vtype,
+ reinterpret_cast<LPBYTE>(data), &len) == ERROR_SUCCESS) {
+ if (wcscmp(L"GenuineIntel", data) == 0) {
+ // Intel reports the microcode version in the upper 32 bits of the MSR
+ return upper;
+ }
+
+ if (wcscmp(L"AuthenticAMD", data) == 0) {
+ // AMD reports the microcode version in the lower 32 bits of the MSR
+ return lower;
+ }
+
+ // Unknown CPU vendor, return whatever half is non-zero
+ return lower ? lower : upper;
+ }
+
+ return 0; // No clue
+}
+
+#endif // XP_WIN
+
+static void MaybeAddCPUMicrocodeCrashAnnotation() {
+#ifdef XP_WIN
+ // Add CPU microcode version to the crash report as "CPUMicrocodeVersion".
+ // It feels like this code may belong in nsSystemInfo instead.
+ uint32_t cpuUpdateRevision = 0;
+ 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 (const auto& oneChoice : choices) {
+ if (RegQueryValueExW(key, oneChoice, nullptr, &vtype,
+ reinterpret_cast<LPBYTE>(updateRevision),
+ &len) == ERROR_SUCCESS) {
+ if (vtype == REG_BINARY && len == sizeof(updateRevision)) {
+ cpuUpdateRevision = GetMicrocodeVersionByVendor(
+ key, updateRevision[1], updateRevision[0]);
+ break;
+ }
+
+ 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%" PRIx32, cpuUpdateRevision));
+ }
+#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
+
+#ifdef MOZ_BACKGROUNDTASKS
+ Maybe<nsCString> backgroundTask = Nothing();
+ const char* backgroundTaskName = nullptr;
+ if (ARG_FOUND ==
+ CheckArg("backgroundtask", &backgroundTaskName, CheckArgFlag::None)) {
+ backgroundTask = Some(backgroundTaskName);
+
+ if (BackgroundTasks::IsNoOutputTaskName(backgroundTask.ref()) &&
+ !CheckArgExists("attach-console") &&
+ !EnvHasValue("MOZ_BACKGROUNDTASKS_IGNORE_NO_OUTPUT")) {
+ // Suppress output, somewhat crudely. We need to suppress stderr as well
+ // as stdout because assertions, of which there are many, write to stderr.
+# ifdef XP_WIN
+ Unused << freopen("nul:", "w", stdout);
+ Unused << freopen("nul:", "w", stderr);
+# else
+ Unused << freopen("/dev/null", "w", stdout);
+ Unused << freopen("/dev/null", "w", stderr);
+# endif
+ } else {
+ printf_stderr("*** You are running in background task mode. ***\n");
+ }
+ }
+
+ BackgroundTasks::Init(backgroundTask);
+#endif
+
+#ifndef ANDROID
+ if (PR_GetEnv("MOZ_RUN_GTEST")
+# ifdef FUZZING
+ || PR_GetEnv("FUZZER")
+# endif
+# ifdef MOZ_BACKGROUNDTASKS
+ || BackgroundTasks::IsBackgroundTaskMode()
+# 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
+
+ mozilla::startup::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));
+ }
+
+ nsString packageFamilyName = widget::WinUtils::GetPackageFamilyName();
+ if (StringBeginsWith(packageFamilyName, u"Mozilla."_ns) ||
+ StringBeginsWith(packageFamilyName, u"MozillaCorporation."_ns)) {
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::WindowsPackageFamilyName,
+ NS_ConvertUTF16toUTF8(packageFamilyName));
+ }
+#endif
+
+ bool isBackgroundTaskMode = false;
+#ifdef MOZ_BACKGROUNDTASKS
+ Maybe<nsCString> backgroundTasks = BackgroundTasks::GetBackgroundTasks();
+ if (backgroundTasks.isSome()) {
+ isBackgroundTaskMode = true;
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::BackgroundTaskName, backgroundTasks.ref());
+ }
+#endif
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::BackgroundTaskMode, isBackgroundTaskMode);
+
+ CrashReporter::AnnotateCrashReport(CrashReporter::Annotation::HeadlessMode,
+ gfxPlatform::IsHeadless());
+
+ 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);
+ }
+ }
+ } else {
+ // We might have registered a runtime exception module very early in process
+ // startup to catch early crashes. This is before we have access to ini file
+ // data, so unregister here if it turns out the crash reporter is disabled.
+ CrashReporter::UnregisterRuntimeExceptionModule();
+ }
+
+#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.");
+ }
+#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;
+ }
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ safeModeRequested = Some(false);
+
+ // Remove the --backgroundtask arg now that it has been saved in
+ // gRestartArgv.
+ const char* tmpBackgroundTaskName = nullptr;
+ Unused << CheckArg("backgroundtask", &tmpBackgroundTaskName,
+ CheckArgFlag::RemoveArg);
+ }
+#endif
+
+ gSafeMode = safeModeRequested.value();
+
+ MaybeAddCPUMicrocodeCrashAnnotation();
+ 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;
+ gRestartWithoutRemote = true;
+ // We don't want to propagate MOZ_NO_REMOTE to potential child
+ // process.
+ SaveToEnv("MOZ_NO_REMOTE=");
+ }
+
+ 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;
+ }
+
+ // On Windows, to get working console arrangements so help/version/etc
+ // print something, we need to initialize the native app support.
+ rv = NS_CreateNativeAppSupport(getter_AddRefs(mNativeApp));
+ if (NS_FAILED(rv)) return 1;
+
+ // 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;
+}
+
+#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 IsWaylandEnabled() {
+ const char* waylandDisplay = PR_GetEnv("WAYLAND_DISPLAY");
+ if (!waylandDisplay) {
+ return false;
+ }
+ if (!PR_GetEnv("DISPLAY")) {
+ // No X11 display, so try to run wayland.
+ return true;
+ }
+ // MOZ_ENABLE_WAYLAND is our primary Wayland on/off switch.
+ if (const char* waylandPref = PR_GetEnv("MOZ_ENABLE_WAYLAND")) {
+ return *waylandPref == '1';
+ }
+ if (const char* backendPref = PR_GetEnv("GDK_BACKEND")) {
+ if (!strncmp(backendPref, "wayland", 7)) {
+ NS_WARNING(
+ "Wayland backend should be enabled by MOZ_ENABLE_WAYLAND=1."
+ "GDK_BACKEND is a Gtk3 debug variable and may cause issues.");
+ return true;
+ }
+ }
+# ifdef EARLY_BETA_OR_EARLIER
+ // Enable by default when we're running on a recent enough GTK version. We'd
+ // like to check further details like compositor version and so on ideally
+ // to make sure we don't enable it on old Mutter or what not, but we can't,
+ // so let's assume that if the user is running on a Wayland session by
+ // default we're ok, since either the distro has enabled Wayland by default,
+ // or the user has gone out of their way to use Wayland.
+ //
+ // TODO(emilio): If users hit problems, we might be able to restrict it to
+ // GNOME / KDE / known-good-desktop environments by checking
+ // XDG_CURRENT_DESKTOP or so...
+ return !gtk_check_version(3, 24, 30);
+# else
+ return false;
+# endif
+}
+#endif
+
+#if defined(MOZ_UPDATER) && !defined(MOZ_WIDGET_ANDROID)
+enum struct ShouldNotProcessUpdatesReason {
+ DevToolsLaunching,
+ NotAnUpdatingTask,
+ OtherInstanceRunning,
+};
+
+const char* ShouldNotProcessUpdatesReasonAsString(
+ ShouldNotProcessUpdatesReason aReason) {
+ switch (aReason) {
+ case ShouldNotProcessUpdatesReason::DevToolsLaunching:
+ return "DevToolsLaunching";
+ case ShouldNotProcessUpdatesReason::NotAnUpdatingTask:
+ return "NotAnUpdatingTask";
+ case ShouldNotProcessUpdatesReason::OtherInstanceRunning:
+ return "OtherInstanceRunning";
+ default:
+ MOZ_CRASH("impossible value for ShouldNotProcessUpdatesReason");
+ }
+}
+
+Maybe<ShouldNotProcessUpdatesReason> ShouldNotProcessUpdates(
+ nsXREDirProvider& aDirProvider) {
+ // Do not process updates if we're launching devtools, as evidenced by
+ // "--chrome ..." with the browser toolbox chrome document URL.
+
+ // Keep this synchronized with the value of the same name in
+ // devtools/client/framework/browser-toolbox/Launcher.sys.mjs. Or, for bonus
+ // points, lift this value to nsIXulRuntime or similar, so that it can be
+ // accessed in both locations. (The prefs service isn't available at this
+ // point so the simplest manner of sharing the value is not available to us.)
+ const char* BROWSER_TOOLBOX_WINDOW_URL =
+ "chrome://devtools/content/framework/browser-toolbox/window.html";
+
+ const char* chromeParam = nullptr;
+ if (ARG_FOUND == CheckArg("chrome", &chromeParam, CheckArgFlag::None)) {
+ if (!chromeParam || !strcmp(BROWSER_TOOLBOX_WINDOW_URL, chromeParam)) {
+ NS_WARNING("ShouldNotProcessUpdates(): DevToolsLaunching");
+ return Some(ShouldNotProcessUpdatesReason::DevToolsLaunching);
+ }
+ }
+
+# ifdef MOZ_BACKGROUNDTASKS
+ // Do not process updates if we're running a background task mode and another
+ // instance is already running. This avoids periodic maintenance updating
+ // underneath a browsing session.
+ Maybe<nsCString> backgroundTasks = BackgroundTasks::GetBackgroundTasks();
+ if (backgroundTasks.isSome()) {
+ // Only process updates for specific tasks: at this time, the
+ // `backgroundupdate` task and the test-only `shouldprocessupdates` task.
+ //
+ // Background tasks can be sparked by Firefox instances that are shutting
+ // down, which can cause races between the task startup trying to update and
+ // Firefox trying to invoke the updater. This happened when converting
+ // `pingsender` to a background task, since it is launched to send pings at
+ // shutdown: Bug 1736373.
+ //
+ // We'd prefer to have this be a property of the task definition sibling to
+ // `backgroundTaskTimeoutSec`, but when we reach this code we're well before
+ // we can load the task JSM.
+ if (!BackgroundTasks::IsUpdatingTaskName(backgroundTasks.ref())) {
+ NS_WARNING("ShouldNotProcessUpdates(): NotAnUpdatingTask");
+ return Some(ShouldNotProcessUpdatesReason::NotAnUpdatingTask);
+ }
+
+ // At this point we have a dir provider but no XPCOM directory service. We
+ // launch the update sync manager using that information so that it doesn't
+ // need to ask for (and fail to find) the directory service.
+ nsCOMPtr<nsIFile> anAppFile;
+ bool persistent;
+ nsresult rv = aDirProvider.GetFile(XRE_EXECUTABLE_FILE, &persistent,
+ getter_AddRefs(anAppFile));
+ if (NS_FAILED(rv) || !anAppFile) {
+ // Strange, but not a reason to skip processing updates.
+ return Nothing();
+ }
+
+ auto updateSyncManager = new nsUpdateSyncManager(anAppFile);
+
+ bool otherInstance = false;
+ updateSyncManager->IsOtherInstanceRunning(&otherInstance);
+ if (otherInstance) {
+ NS_WARNING("ShouldNotProcessUpdates(): OtherInstanceRunning");
+ return Some(ShouldNotProcessUpdatesReason::OtherInstanceRunning);
+ }
+ }
+# endif
+
+ return Nothing();
+}
+#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 and for ANDROID.
+#ifndef FUZZING
+# ifndef ANDROID
+# 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 /* ANDROID */
+#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 */
+
+#ifdef MOZ_WIDGET_GTK
+ // Stash startup token in owned memory because gtk_init will clear
+ // DESKTOP_STARTUP_ID it.
+ if (const char* v = PR_GetEnv("DESKTOP_STARTUP_ID")) {
+ mDesktopStartupID.Assign(v);
+ }
+ if (const char* v = PR_GetEnv("XDG_ACTIVATION_TOKEN")) {
+ mXDGActivationToken.Assign(v);
+ }
+#endif
+
+#if defined(XP_WIN)
+ {
+ // Save the shortcut path before lpTitle is replaced by an AUMID,
+ // such as by WinTaskbar
+ STARTUPINFOW si;
+ GetStartupInfoW(&si);
+ if (si.dwFlags & STARTF_TITLEISAPPID) {
+ NS_WARNING("AUMID was already set, shortcut may have been lost.");
+ } else if ((si.dwFlags & STARTF_TITLEISLINKNAME) && si.lpTitle) {
+ gProcessStartupShortcut.Assign(si.lpTitle);
+ }
+ }
+#endif /* XP_WIN */
+
+#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.
+ g_set_prgname(gAppData->remotingName);
+
+ // 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 waylandEnabled = false;
+# if defined(MOZ_WAYLAND)
+ waylandEnabled = IsWaylandEnabled();
+# endif
+ // On Wayland disabled builds read X11 DISPLAY env exclusively
+ // and don't care about different displays.
+ if (!waylandEnabled && !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) {
+ GdkDisplay* disp = gdk_display_open(display_name);
+ if (!disp) {
+ PR_fprintf(PR_STDERR, "Error: cannot open display: %s\n", display_name);
+ return 1;
+ }
+ if (saveDisplayArg) {
+ if (GdkIsX11Display(disp)) {
+ SaveWordToEnv("DISPLAY", nsDependentCString(display_name));
+ }
+# ifdef MOZ_WAYLAND
+ else if (GdkIsWaylandDisplay(disp)) {
+ SaveWordToEnv("WAYLAND_DISPLAY", nsDependentCString(display_name));
+ }
+# endif
+ }
+ }
+# ifdef MOZ_WIDGET_GTK
+ else {
+ 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();
+ gRemoteService = mRemoteService;
+ }
+#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
+
+ bool canRun = false;
+ rv = mNativeApp->Start(&canRun);
+ if (NS_FAILED(rv) || !canRun) {
+ return 1;
+ }
+
+#ifdef MOZ_WIDGET_GTK
+ // startup token might be cleared now, we recover it in case we need a
+ // restart.
+ if (!mDesktopStartupID.IsEmpty()) {
+ // Leak it with extreme prejudice!
+ PR_SetEnv(ToNewCString("DESKTOP_STARTUP_ID="_ns + mDesktopStartupID));
+ }
+
+ if (!mXDGActivationToken.IsEmpty()) {
+ // Leak it with extreme prejudice!
+ PR_SetEnv(ToNewCString("XDG_ACTIVATION_TOKEN="_ns + mXDGActivationToken));
+ }
+#endif
+
+ // Support exiting early for testing startup sequence. Bug 1360493
+ if (CheckArg("test-launch-without-hang")) {
+ *aExitFlag = true;
+ return 0;
+ }
+
+ mProfileSvc = NS_GetToolkitProfileService();
+ if (!mProfileSvc) {
+ // 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.
+# ifdef MOZ_WIDGET_GTK
+ const auto& startupToken =
+ GdkIsWaylandDisplay() ? mXDGActivationToken : mDesktopStartupID;
+# else
+ const nsCString startupToken;
+# endif
+ RemoteResult rr = mRemoteService->StartClient(
+ startupToken.IsEmpty() ? nullptr : startupToken.get());
+ 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)
+ Maybe<ShouldNotProcessUpdatesReason> shouldNotProcessUpdatesReason =
+ ShouldNotProcessUpdates(mDirProvider);
+ if (shouldNotProcessUpdatesReason.isNothing()) {
+ // 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;
+ }
+ } else {
+ if (CheckArg("test-process-updates") ||
+ EnvHasValue("MOZ_TEST_PROCESS_UPDATES")) {
+ // Support for testing *not* processing an update. The launched process
+ // can witness this environment variable and conclude that its runtime
+ // environment resulted in not processing updates.
+
+ SaveToEnv(nsPrintfCString(
+ "MOZ_TEST_PROCESS_UPDATES=ShouldNotProcessUpdates(): %s",
+ ShouldNotProcessUpdatesReasonAsString(
+ shouldNotProcessUpdatesReason.value()))
+ .get());
+ }
+ }
+#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) {
+ if (EnvHasValue("MOZ_RESET_PROFILE_RESTART")) {
+ 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");
+ }
+ // 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);
+ }
+
+ // Flush any pending page load events.
+ mozilla::glean_pings::Pageload.Submit("startup"_ns);
+
+ 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);
+ }
+
+ {
+ int level = GetEffectiveGpuSandboxLevel();
+
+ nsAutoCString levelString;
+ levelString.AppendInt(level);
+
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::GpuSandboxLevel, 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(false);
+ auto dllServicesDisable =
+ MakeScopeExit([&dllServices]() { dllServices->DisableFull(); });
+
+ mozilla::mscom::InitProfilerMarkers();
+#endif // defined(XP_WIN)
+
+ // We need the appStartup pointer to span multiple scopes, so we declare
+ // it here.
+ nsCOMPtr<nsIAppStartup> appStartup;
+ // Ditto with the command line.
+ nsCOMPtr<nsICommandLineRunner> cmdLine;
+
+ {
+#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.
+ bool includeContextHeap = Preferences::GetBool(
+ "toolkit.crashreporter.include_context_heap", false);
+ CrashReporter::SetIncludeContextHeap(includeContextHeap);
+
+#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();
+
+ 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;
+ }
+
+ cmdLine = new nsCommandLine();
+
+ rv = cmdLine->Init(gArgc, gArgv, workingDir,
+ nsICommandLine::STATE_INITIAL_LAUNCH);
+ NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE);
+
+ nsAppStartupNotifier::NotifyObservers(APPSTARTUP_CATEGORY, cmdLine);
+
+ appStartup = components::AppStartup::Service();
+ NS_ENSURE_TRUE(appStartup, NS_ERROR_FAILURE);
+
+ mDirProvider.DoStartup();
+
+#ifdef XP_WIN
+ // It needs to be called on the main thread because it has to use
+ // nsObserverService.
+ EnsureWin32kInitialized();
+#endif
+
+ // As FilePreferences need the profile directory, we must initialize right
+ // here.
+ mozilla::FilePreferences::InitDirectoriesAllowlist();
+ mozilla::FilePreferences::InitPrefs();
+
+ OverrideDefaultLocaleIfNeeded();
+
+ nsCString userAgentLocale;
+ LocaleService::GetInstance()->GetAppLocaleAsBCP47(userAgentLocale);
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::useragent_locale, userAgentLocale);
+
+ mShuttingDown =
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
+
+ if (!mShuttingDown) {
+ /* 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
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // Background tasks aren't going to load a chrome XUL document.
+ lazyHiddenWindow = true;
+ }
+#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)
+# if defined(MOZ_BACKGROUNDTASKS)
+ // The backgroundtask profile is not a browsing profile, let alone the new
+ // default profile, so don't mirror its properties into the registry.
+ if (!BackgroundTasks::IsBackgroundTaskMode())
+# endif // defined(MOZ_BACKGROUNDTASKS)
+ {
+ Preferences::RegisterCallbackAndCall(
+ &OnDefaultAgentTelemetryPrefChanged,
+ kPrefHealthReportUploadEnabled);
+ Preferences::RegisterCallbackAndCall(
+ &OnDefaultAgentTelemetryPrefChanged, kPrefDefaultAgentEnabled);
+
+ Preferences::RegisterCallbackAndCall(
+ &OnDefaultAgentRemoteSettingsPrefChanged,
+ kPrefServicesSettingsServer);
+ Preferences::RegisterCallbackAndCall(
+ &OnDefaultAgentRemoteSettingsPrefChanged,
+ kPrefSecurityContentSignatureRootHash);
+
+ Preferences::RegisterCallbackAndCall(
+ &OnSetDefaultBrowserUserChoicePrefChanged,
+ kPrefSetDefaultBrowserUserChoicePref);
+
+ SetDefaultAgentLastRunTime();
+ }
+# endif // defined(MOZ_DEFAULT_BROWSER_AGENT)
+#endif
+
+#if defined(MOZ_WIDGET_GTK)
+ // Clear the environment variables so they won't be inherited by child
+ // processes and confuse things.
+ for (const auto& name : kStartupTokenNames) {
+ g_unsetenv(name.get());
+ }
+#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);
+
+# ifdef MOZILLA_OFFICIAL
+ // Check if we're running from a DMG or an app translocated location and
+ // allow the user to install to the Applications directory.
+ if (MacRunFromDmgUtils::MaybeInstallAndRelaunch()) {
+ bool userAllowedQuit = true;
+ appStartup->Quit(nsIAppStartup::eForceQuit, 0, &userAllowedQuit);
+ }
+# endif
+#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);
+
+ mShuttingDown =
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
+ }
+
+ if (!mShuttingDown) {
+ rv = cmdLine->Run();
+ NS_ENSURE_SUCCESS_LOG(rv, NS_ERROR_FAILURE);
+
+ mShuttingDown =
+ AppShutdown::IsInOrBeyond(ShutdownPhase::AppShutdownConfirmed);
+ }
+
+ 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();
+ gRemoteService = nullptr;
+ }
+#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();
+ }
+
+#ifdef MOZ_BACKGROUNDTASKS
+ if (BackgroundTasks::IsBackgroundTaskMode()) {
+ // In background task mode, we don't fire various delayed initialization
+ // notifications, which in the regular browser is how startup crash tracking
+ // is marked as finished. Here, getting this far means we don't have a
+ // startup crash.
+ rv = appStartup->TrackStartupCrashEnd();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // We never open a window, but don't want to exit immediately.
+ rv = appStartup->EnterLastWindowClosingSurvivalArea();
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ // Avoid some small differences in initialization order across platforms.
+ nsCOMPtr<nsIPowerManagerService> powerManagerService =
+ do_GetService(POWERMANAGERSERVICE_CONTRACTID);
+ nsCOMPtr<nsIStringBundleService> stringBundleService =
+ do_GetService(NS_STRINGBUNDLE_CONTRACTID);
+
+ rv = BackgroundTasks::RunBackgroundTask(cmdLine);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+#endif
+
+ {
+ 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(int argc, char** argv) {
+ nsresult rv;
+
+ const char* path = nullptr;
+ ArgResult ar = CheckArg(argc, argv, "greomni", &path, CheckArgFlag::None);
+ 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 XP_MACOSX
+ // We call this early because it will kick off a background-thread task
+ // to register the fonts, and we'd like it to have a chance to complete
+ // before gfxPlatform initialization actually requires it.
+ gfxPlatformMac::RegisterSupplementalFonts();
+#endif
+
+#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(argc, argv);
+ 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;
+#endif
+
+ // Once we unset the exception handler, we lose the ability to properly
+ // detect hangs -- they show up as crashes. We do this as late as possible.
+ // In particular, after ProcessRuntime is destroyed on Windows.
+ auto unsetExceptionHandler = MakeScopeExit([&] {
+ if (mAppData->flags & NS_XRE_ENABLE_CRASH_REPORTER)
+ return CrashReporter::UnsetExceptionHandler();
+ return NS_OK;
+ });
+
+ mozilla::IOInterposerInit ioInterposerGuard;
+
+#if defined(XP_WIN)
+ // We should have already done this when we created the skeleton UI. However,
+ // there is code in here which needs xul in order to work, like EnsureMTA. It
+ // should be setup that running it again is safe.
+ 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>();
+
+ rv = mScopedXPCOM->Initialize(/* aInitJSContext = */ false);
+ NS_ENSURE_SUCCESS(rv, 1);
+
+ // run!
+ rv = XRE_mainRun();
+
+#ifdef MOZ_X11
+ XRE_CleanupX11ErrorHandler();
+#endif
+
+#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;
+
+ // 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
+
+ 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();
+#ifdef MOZ_BACKGROUNDTASKS
+ // This is well after the profile has been unlocked, so it's okay if this does
+ // delete this background task's temporary profile.
+ mozilla::BackgroundTasks::Shutdown();
+#endif
+ 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(aArgc, aArgv);
+ 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 GetGeckoProcessType(); }
+
+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_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ bool XRE_Is##proc_typename##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() || XRE_IsUtilityProcess()) {
+ 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_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ case GeckoProcessType_##enum_name: \
+ return BinPathType::process_bin_type;
+#include "mozilla/GeckoProcessTypes.h"
+#undef GECKO_PROCESS_TYPE
+ default:
+ return BinPathType::PluginContainer;
+ }
+}
+
+// From mozglue/static/rust/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..87d51c8653
--- /dev/null
+++ b/toolkit/xre/nsAppRunner.h
@@ -0,0 +1,168 @@
+/* -*- 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(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();
+
+/**
+ * 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;
+
+// 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.
+// If aTryExec is true then we use exec on platforms that support it to
+// remain in the foreground.
+nsresult LaunchChild(bool aBlankCommandLine, bool aTryExec = false);
+
+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);
+
+void IncreaseDescriptorLimits();
+} // 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 IsWaylandEnabled();
+#endif
+
+#endif // nsAppRunner_h__
diff --git a/toolkit/xre/nsAppStartupNotifier.cpp b/toolkit/xre/nsAppStartupNotifier.cpp
new file mode 100644
index 0000000000..b8205a91e6
--- /dev/null
+++ b/toolkit/xre/nsAppStartupNotifier.cpp
@@ -0,0 +1,72 @@
+/* -*- 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,
+ nsISupports* aSubject) {
+ 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(aSubject, 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..f21c15deb1
--- /dev/null
+++ b/toolkit/xre/nsAppStartupNotifier.h
@@ -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/. */
+
+#ifndef nsAppStartupNotifier_h___
+#define nsAppStartupNotifier_h___
+
+#include "nsIAppStartupNotifier.h"
+#include "nsError.h"
+
+class nsAppStartupNotifier final {
+ public:
+ static nsresult NotifyObservers(const char* aCategory,
+ nsISupports* aSubject = nullptr);
+};
+
+#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..d99b077b0d
--- /dev/null
+++ b/toolkit/xre/nsEmbedFunctions.cpp
@@ -0,0 +1,852 @@
+/* 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"
+# ifdef MOZ_SANDBOX
+# include "mozilla/RandomNum.h"
+# endif
+# include "mozilla/ScopeExit.h"
+# include "mozilla/WinDllServices.h"
+# include "WinUtils.h"
+# ifdef ACCESSIBILITY
+# include "mozilla/GeckoArgs.h"
+# include "mozilla/mscom/ActCtxResource.h"
+# endif
+#endif
+
+#include "nsAppRunner.h"
+#include "nsExceptionHandler.h"
+#include "mozilla/RuntimeExceptionModule.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 <mach/mach.h>
+# include <servers/bootstrap.h>
+# include "nsVersionComparator.h"
+# include "chrome/common/mach_ipc_mac.h"
+# include "gfxPlatformMac.h"
+#endif
+#include "nsX11ErrorHandler.h"
+#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 "mozilla/ipc/ProcessUtils.h"
+#endif // defined(MOZ_WIDGET_ANDROID)
+
+#include "mozilla/AbstractThread.h"
+#include "mozilla/FilePreferences.h"
+#include "mozilla/IOInterposer.h"
+#include "mozilla/ProcessType.h"
+#include "mozilla/RDDProcessImpl.h"
+#include "mozilla/ipc/UtilityProcessImpl.h"
+#include "mozilla/UniquePtr.h"
+
+#include "mozilla/ipc/BrowserProcessSubThread.h"
+#include "mozilla/ipc/IOThreadChild.h"
+#include "mozilla/ipc/ProcessChild.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 "ProfilerControl.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_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
+
+#if defined(MOZ_X11)
+# include <X11/Xlib.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::dom::ContentParent;
+using mozilla::dom::ContentProcess;
+
+using mozilla::gmp::GMPProcessChild;
+
+using mozilla::ipc::TestShellCommandParent;
+using mozilla::ipc::TestShellParent;
+
+namespace mozilla::_ipdltest {
+// Set in IPDLUnitTest.cpp when running gtests.
+UniquePtr<mozilla::ipc::ProcessChild> (*gMakeIPDLUnitTestProcessChild)(
+ base::ProcessId, const nsID&) = nullptr;
+} // namespace mozilla::_ipdltest
+
+static NS_DEFINE_CID(kAppShellCID, NS_APPSHELL_CID);
+
+const char* XRE_GeckoProcessTypeToString(GeckoProcessType aProcessType) {
+ switch (aProcessType) {
+#define GECKO_PROCESS_TYPE(enum_value, enum_name, string_name, proc_typename, \
+ process_bin_type, procinfo_typename, \
+ webidl_typename, allcaps_name) \
+ case GeckoProcessType::GeckoProcessType_##enum_name: \
+ return string_name;
+#include "mozilla/GeckoProcessTypes.h"
+#undef GECKO_PROCESS_TYPE
+ default:
+ return "invalid";
+ }
+}
+
+const char* XRE_ChildProcessTypeToAnnotation(GeckoProcessType aProcessType) {
+ switch (aProcessType) {
+ case GeckoProcessType_GMPlugin:
+ return "plugin";
+ case GeckoProcessType_Default:
+ return "";
+ case GeckoProcessType_Content:
+ return "content";
+ default:
+ return XRE_GeckoProcessTypeToString(aProcessType);
+ }
+}
+
+#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) {
+ SetGeckoProcessType(aProcessTypeString);
+}
+
+#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);
+ } else if (XRE_GetProcessType() == GeckoProcessType_GPU) {
+ int level = GetEffectiveGpuSandboxLevel();
+ CrashReporter::AnnotateCrashReport(
+ CrashReporter::Annotation::GpuSandboxLevel, 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);
+
+#ifdef XP_MACOSX
+ gfxPlatformMac::RegisterSupplementalFonts();
+#endif
+
+ // 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 */
+
+ // Acquire the mach bootstrap port name from our command line, and send our
+ // task_t to the parent process.
+ const char* const mach_port_name = aArgv[--aArgc];
+
+ const int kTimeoutMs = 1000;
+
+ UniqueMachSendRight task_sender;
+ kern_return_t kr = bootstrap_look_up(bootstrap_port, mach_port_name,
+ getter_Transfers(task_sender));
+ if (kr != KERN_SUCCESS) {
+ NS_WARNING(nsPrintfCString("child bootstrap_look_up failed: %s",
+ mach_error_string(kr))
+ .get());
+ return NS_ERROR_FAILURE;
+ }
+
+ kr = MachSendPortSendRight(task_sender.get(), mach_task_self(),
+ Some(kTimeoutMs));
+ if (kr != KERN_SUCCESS) {
+ NS_WARNING(nsPrintfCString("child MachSendPortSendRight failed: %s",
+ mach_error_string(kr))
+ .get());
+ 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()) {
+ CrashReporter::FileHandle crashTimeAnnotationFile =
+ CrashReporter::kInvalidFileHandle;
+#if defined(XP_WIN)
+ if (aArgc < 1) {
+ return NS_ERROR_FAILURE;
+ }
+ // Pop the first argument, this is used by the WER runtime exception module
+ // which reads it from the command-line so we can just discard it here.
+ --aArgc;
+
+ const char* const crashTimeAnnotationArg = aArgv[--aArgc];
+ crashTimeAnnotationFile = reinterpret_cast<CrashReporter::FileHandle>(
+ std::stoul(std::string(crashTimeAnnotationArg)));
+#endif
+
+ if (aArgc < 1) return NS_ERROR_FAILURE;
+ const char* const crashReporterArg = aArgv[--aArgc];
+
+ if (IsCrashReporterEnabled(crashReporterArg)) {
+ exceptionHandlerIsSet = CrashReporter::SetRemoteExceptionHandler(
+ crashReporterArg, crashTimeAnnotationFile);
+
+ if (!exceptionHandlerIsSet) {
+ // Bug 684322 will add better visibility into this condition
+ NS_WARNING("Could not setup crash reporting\n");
+ }
+ } else {
+ // We might have registered a runtime exception module very early in
+ // process startup to catch early crashes. This is before we process the
+ // crash reporter arg, so unregister here if it turns out the crash
+ // reporter is disabled.
+ CrashReporter::UnregisterRuntimeExceptionModule();
+ }
+ }
+
+ 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 @ %lu\n\n",
+ XRE_GetProcessTypeString(), base::GetCurrentProcId());
+ ::Sleep(GetDebugChildPauseTime());
+ }
+#endif
+
+#ifdef MOZ_WIDGET_ANDROID
+ // The parent process already did this, but Gecko child processes on
+ // Android aren't descendants of the parent process, so they don't
+ // inherit its rlimits.
+ mozilla::startup::IncreaseDescriptorLimits();
+#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");
+
+ // They also get the initial message channel ID passed in the same manner.
+ const char* const messageChannelIdString = aArgv[aArgc - 1];
+ MOZ_ASSERT(messageChannelIdString, "NULL MessageChannel Id");
+ --aArgc;
+
+ nsID messageChannelId{};
+ if (!messageChannelId.Parse(messageChannelIdString)) {
+ return NS_ERROR_FAILURE;
+ }
+
+#if defined(XP_WIN)
+ // On Win7+, when not running as an MSIX package, register the application
+ // user model id passed in by parent. This ensures windows created by the
+ // container properly group with the parent app on the Win7 taskbar.
+ // MSIX packages explicitly do not support setting the appid from within
+ // the app, as it is set in the package manifest instead.
+ const char* const appModelUserId = aArgv[--aArgc];
+ if (appModelUserId && !mozilla::widget::WinUtils::HasPackageIdentity()) {
+ // '-' 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_IPDLUnitTest:
+ case GeckoProcessType_VR:
+ case GeckoProcessType_RDD:
+ case GeckoProcessType_Socket:
+ case GeckoProcessType_Utility:
+ // 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();
+ }
+
+ // Call BCryptGenRandom() to pre-load bcryptPrimitives.dll while the current
+ // thread still has an unrestricted impersonation token. We need to perform
+ // that operation to warmup the BCryptGenRandom() call that is used by
+ // others, especially rust. See bug 1746524, bug 1751094, bug 1751177
+ UCHAR buffer[32];
+ NTSTATUS status = BCryptGenRandom(NULL, // hAlgorithm
+ buffer, // pbBuffer
+ sizeof(buffer), // cbBuffer
+ BCRYPT_USE_SYSTEM_PREFERRED_RNG // dwFlags
+ );
+ MOZ_RELEASE_ASSERT(status == STATUS_SUCCESS);
+#endif // defined(MOZ_SANDBOX) && defined(XP_WIN)
+
+ {
+ // 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);
+ {
+#if defined(XP_WIN) && defined(ACCESSIBILITY)
+ // The accessibility resource ID is passed down on the command line
+ // because its retrieval causes issues with the sandbox. When it is set,
+ // it is required for ProcessRuntime construction within ProcessChild.
+ auto a11yResourceId = geckoargs::sA11yResourceId.Get(aArgc, aArgv);
+ if (a11yResourceId.isSome()) {
+ mscom::ActCtxResource::SetAccessibilityResourceId(*a11yResourceId);
+ }
+#endif
+
+ UniquePtr<ProcessChild> process;
+ switch (XRE_GetProcessType()) {
+ case GeckoProcessType_Default:
+ MOZ_CRASH("This makes no sense");
+ break;
+
+ case GeckoProcessType_Content:
+ ioInterposerGuard.emplace();
+ process = MakeUnique<ContentProcess>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_IPDLUnitTest:
+ MOZ_RELEASE_ASSERT(mozilla::_ipdltest::gMakeIPDLUnitTestProcessChild,
+ "xul-gtest not loaded!");
+ process = mozilla::_ipdltest::gMakeIPDLUnitTestProcessChild(
+ parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_GMPlugin:
+ process =
+ MakeUnique<gmp::GMPProcessChild>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_GPU:
+ process =
+ MakeUnique<gfx::GPUProcessImpl>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_VR:
+ process =
+ MakeUnique<gfx::VRProcessChild>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_RDD:
+ process = MakeUnique<RDDProcessImpl>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_Socket:
+ ioInterposerGuard.emplace();
+ process =
+ MakeUnique<net::SocketProcessImpl>(parentPID, messageChannelId);
+ break;
+
+ case GeckoProcessType_Utility:
+ process =
+ MakeUnique<ipc::UtilityProcessImpl>(parentPID, messageChannelId);
+ break;
+
+#if defined(MOZ_SANDBOX) && defined(XP_WIN)
+ case GeckoProcessType_RemoteSandboxBroker:
+ process = MakeUnique<RemoteSandboxBrokerProcessChild>(
+ parentPID, messageChannelId);
+ 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::InitDirectoriesAllowlist();
+ 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 (exceptionHandlerIsSet) {
+ CrashReporter::UnsetRemoteExceptionHandler();
+ }
+
+ return XRE_DeinitCommandLine();
+}
+
+MessageLoop* XRE_GetIOMessageLoop() {
+ if (GetGeckoProcessType() == GeckoProcessType_Default) {
+ return BrowserProcessSubThread::GetMessageLoop(BrowserProcessSubThread::IO);
+ }
+ return IOThreadChild::message_loop();
+}
+
+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::Rooted<JSString*> 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();
+# endif
+
+ // Ensure our X11 error handler overrides the default GDK error handler such
+ // that errors are ignored by default. GDK will install its own error handler
+ // temporarily when pushing error traps internally as needed. This avoids us
+ // otherwise having to frequently override the error handler merely to trap
+ // errors in multiple places that would otherwise contend with GDK or other
+ // libraries that might also override the handler.
+ InstallX11ErrorHandler();
+}
+
+void XRE_CleanupX11ErrorHandler() { CleanupX11ErrorHandler(); }
+#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/nsGDKErrorHandler.cpp b/toolkit/xre/nsGDKErrorHandler.cpp
new file mode 100644
index 0000000000..19895cff0a
--- /dev/null
+++ b/toolkit/xre/nsGDKErrorHandler.cpp
@@ -0,0 +1,117 @@
+/* -*- 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>
+#ifdef MOZ_X11
+# include <gdk/gdkx.h>
+#endif
+#include <errno.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "nsDebug.h"
+#include "nsString.h"
+#ifdef MOZ_X11
+# include "nsX11ErrorHandler.h"
+#endif
+
+#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) {
+#ifdef MOZ_X11
+ 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, 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
+#endif
+ {
+ 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);
+#ifdef MOZ_X11
+ if (PR_GetEnv("MOZ_X_SYNC")) {
+ XSynchronize(GDK_DISPLAY_XDISPLAY(gdk_display_get_default()), X11True);
+ }
+#endif
+}
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..eead6cd9fa
--- /dev/null
+++ b/toolkit/xre/nsNativeAppSupportCocoa.mm
@@ -0,0 +1,177 @@
+/* -*- 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_BLOCK_RETURN;
+
+ // 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_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+NS_IMETHODIMP
+nsNativeAppSupportCocoa::ReOpen() {
+ NS_OBJC_BEGIN_TRY_BLOCK_RETURN;
+
+ 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 || !widget->IsVisible()) {
+ 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) {
+ // Prioritize browser windows for deminiaturization
+ nsCOMPtr<mozIDOMWindowProxy> mru;
+ wm->GetMostRecentBrowserWindow(getter_AddRefs(mru));
+
+ // Failing that, deminiaturize the most recently used window
+ if (!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_BLOCK_RETURN(NS_ERROR_FAILURE);
+}
+
+#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..e8f7b67c35
--- /dev/null
+++ b/toolkit/xre/nsNativeAppSupportUnix.cpp
@@ -0,0 +1,658 @@
+/* -*- 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/Logging.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..8c4b43b3f6
--- /dev/null
+++ b/toolkit/xre/nsNativeAppSupportWin.cpp
@@ -0,0 +1,66 @@
+/* -*- 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/CmdLineAndEnvUtils.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() {
+ if (CheckArg(gArgc, gArgv, "attach-console") == ARG_FOUND) {
+ UseParentConsole();
+ }
+
+ if (CheckArg(gArgc, gArgv, "console") == ARG_FOUND) {
+ 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);
+ }
+ }
+ }
+}
+
+// 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..4bf01e1bb5
--- /dev/null
+++ b/toolkit/xre/nsNativeAppSupportWin.h
@@ -0,0 +1,29 @@
+/* -*- 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
+// If IDI_PBMODE's index changes, PRIVATE_BROWSING_ICON_INDEX
+// in BrowserContentHandler.sys.mjs must also be updated.
+#define IDI_PBMODE 5
+#define IDI_DOCUMENT_PDF 6
+#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..f28a21fce4
--- /dev/null
+++ b/toolkit/xre/nsSigHandlers.cpp
@@ -0,0 +1,402 @@
+/* -*- 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 "prenv.h"
+# include "nsDebug.h"
+# include "nsString.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/Attributes.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 common_crap_handler(int signum, const void* aFirstFramePC) {
+ printf("\nProgram %s (pid = %d) received signal %d.\n", gProgname, getpid(),
+ signum);
+
+ printf("Stack:\n");
+ MozStackWalk(PrintStackFrame, aFirstFramePC, /* 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);
+}
+
+MOZ_NEVER_INLINE void ah_crap_handler(int signum) {
+ common_crap_handler(signum, CallerPC());
+}
+
+MOZ_NEVER_INLINE void child_ah_crap_handler(int signum) {
+ if (!getenv("MOZ_DONT_UNBLOCK_PARENT_ON_CHILD_CRASH"))
+ close(kClientChannelFd);
+ common_crap_handler(signum, CallerPC());
+}
+
+# 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 glib_log_func(const gchar* log_domain, GLogLevelFlags log_level,
+ const gchar* message, gpointer user_data);
+}
+
+// GDK sometimes avoids calling exit handlers, but we still want to know when we
+// crash, see https://gitlab.gnome.org/GNOME/gtk/-/issues/4514 and bug 1743144.
+static bool IsCrashyGtkMessage(const nsACString& aMessage) {
+ if (aMessage.EqualsLiteral("Lost connection to Wayland compositor.")) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/wayland/gdkeventsource.c#L210
+ return true;
+ }
+ if (StringBeginsWith(aMessage, "Error flushing display: "_ns)) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/wayland/gdkeventsource.c#L68
+ return true;
+ }
+ if (StringBeginsWith(aMessage, "Error reading events from display: "_ns)) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/wayland/gdkeventsource.c#L97
+ return true;
+ }
+ if (StringBeginsWith(aMessage, "Error "_ns) &&
+ StringEndsWith(aMessage, " dispatching to Wayland display."_ns)) {
+ // https://gitlab.gnome.org/GNOME/gtk/-/blob/gtk-3-24/gdk/wayland/gdkeventsource.c#L205
+ return true;
+ }
+ return false;
+}
+
+/* static */ void glib_log_func(const gchar* log_domain,
+ GLogLevelFlags log_level, const gchar* message,
+ gpointer user_data) {
+ if (MOZ_UNLIKELY(IsCrashyGtkMessage(nsDependentCString(message)))) {
+ MOZ_CRASH_UNSAFE(strdup(message));
+ }
+
+ 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)
+ if (aProgname) {
+ const char* tmp = 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))
+ // Override the default glib logging function to intercept some crashes that
+ // are uninterceptable otherwise.
+ // Also, when XPCOM_DEBUG_BREAK is set, we can also get stacks for them.
+ // so we get stacks for it too.
+ orig_log_func = g_log_set_default_handler(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..b491b91861
--- /dev/null
+++ b/toolkit/xre/nsUpdateDriver.cpp
@@ -0,0 +1,952 @@
+/* -*- 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 "nsDebug.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/ErrorNames.h"
+#include "mozilla/Printf.h"
+#include "mozilla/UniquePtr.h"
+#include "nsIObserverService.h"
+#include "nsNetCID.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Services.h"
+#include "mozilla/dom/Promise.h"
+
+#ifdef XP_MACOSX
+# include "nsILocalFileMac.h"
+# include "nsCommandLineServiceMac.h"
+# include "MacLaunchHelper.h"
+# include "updaterfileutils_osx.h"
+# include "mozilla/Monitor.h"
+# include "gfxPlatformMac.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"
+# include "pathhash.h"
+# include "WinUtils.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_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 MOZ_UNANNOTATED("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);
+}
+
+enum UpdateStatus {
+ eNoUpdateAction,
+ ePendingUpdate,
+ ePendingService,
+ ePendingElevate,
+ eAppliedUpdate,
+ eAppliedService,
+};
+
+/**
+ * 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;
+ }
+
+#if defined(XP_MACOSX)
+ // If we're going to do a restart, we need to make sure the font registration
+ // thread has finished before this process exits (bug 1777332).
+ if (restart) {
+ gfxPlatformMac::WaitForFontRegistration();
+ }
+
+ // 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).
+ // We only check if we need elevation if we are restarting. We don't attempt
+ // to stage if elevation is required. Staging happens without the user knowing
+ // about it, and we don't want to ask for elevation for seemingly no reason.
+ bool needElevation = false;
+ if (restart) {
+ needElevation = !IsRecursivelyWritable(installDirPath.get());
+ if (needElevation) {
+ // Normally we would check this via nsIAppStartup::wasSilentlyStarted,
+ // but nsIAppStartup isn't available yet.
+ char* mozAppSilentStart = PR_GetEnv("MOZ_APP_SILENT_START");
+ bool wasSilentlyStarted =
+ mozAppSilentStart && (strcmp(mozAppSilentStart, "") != 0);
+ if (wasSilentlyStarted) {
+ // Elevation always requires prompting for credentials on macOS. If we
+ // are trying to restart silently, we must not display UI such as this
+ // prompt.
+ // We make this check here rather than in the updater, because it is
+ // actually Firefox that shows the elevation prompt (via
+ // InstallPrivilegedHelper), not the updater.
+ return;
+ }
+ }
+ }
+#endif
+
+ 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);
+if (restart && needElevation) {
+ 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);
+ }
+}
+
+#if !defined(XP_WIN)
+/**
+ * Wait briefly to see if a process terminates, then return true if it has.
+ *
+ * (Not implemented on Windows, where HandleWatcher is used instead.)
+ */
+static bool ProcessHasTerminated(ProcessType pt) {
+# if 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
+}
+#endif
+
+nsresult ProcessUpdates(nsIFile* greDir, nsIFile* appDir, nsIFile* updRootDir,
+ int argc, char** argv, const char* appVersion,
+ bool restart, ProcessType* pid) {
+ nsresult rv;
+
+#ifdef XP_WIN
+ // If we're in a package, we know any updates that we find are not for us.
+ if (mozilla::widget::WinUtils::HasPackageIdentity()) {
+ return NS_OK;
+ }
+#endif
+
+ 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) {}
+
+#ifdef XP_WIN
+nsUpdateProcessor::~nsUpdateProcessor() { mProcessWatcher.Stop(); }
+#else
+nsUpdateProcessor::~nsUpdateProcessor() = default;
+#endif
+
+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
+ // worker 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("UpdateProcessor", getter_AddRefs(mWorkerThread), r);
+}
+
+void nsUpdateProcessor::StartStagedUpdate() {
+ MOZ_ASSERT(!NS_IsMainThread(), "main thread");
+
+ // If we fail to launch the updater process or its monitor for some reason, we
+ // need to shut down the worker thread, as there isn't anything more for us to
+ // do.
+ auto onExitStopThread = mozilla::MakeScopeExit([&] {
+ nsresult rv = NS_DispatchToMainThread(
+ NewRunnableMethod("nsUpdateProcessor::ShutdownWorkerThread", this,
+ &nsUpdateProcessor::ShutdownWorkerThread));
+ NS_ENSURE_SUCCESS_VOID(rv);
+ });
+
+ // Launch updater. (We do this on a worker thread to avoid blocking the main
+ // thread with file I/O.)
+ nsresult rv = ProcessUpdates(mInfo.mGREDir, mInfo.mAppDir, mInfo.mUpdateRoot,
+ mInfo.mArgc, mInfo.mArgv,
+ mInfo.mAppVersion.get(), false, &mUpdaterPID);
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
+ ("could not start updater process: %s", GetStaticErrorName(rv)));
+ return;
+ }
+
+ if (!mUpdaterPID) {
+ // not an error
+ MOZ_LOG(sUpdateLog, mozilla::LogLevel::Verbose,
+ ("ProcessUpdates() indicated nothing to do"));
+ return;
+ }
+
+#ifdef WIN32
+ // Set up a HandleWatcher to report to the main thread when we're done.
+ RefPtr<nsIThread> mainThread;
+ NS_GetMainThread(getter_AddRefs(mainThread));
+ mProcessWatcher.Watch(mUpdaterPID, mainThread,
+ NewRunnableMethod("nsUpdateProcessor::UpdateDone", this,
+ &nsUpdateProcessor::UpdateDone));
+
+// On Windows, that's all we need the worker thread for. Let
+// `onExitStopThread` shut us down.
+#else
+ // Monitor the state of the updater process while it is staging an update.
+ rv = NS_DispatchToCurrentThread(
+ NewRunnableMethod("nsUpdateProcessor::WaitForProcess", this,
+ &nsUpdateProcessor::WaitForProcess));
+ if (NS_FAILED(rv)) {
+ MOZ_LOG(sUpdateLog, mozilla::LogLevel::Error,
+ ("could not start updater process poll: error %s",
+ GetStaticErrorName(rv)));
+ return;
+ }
+
+ // Leave the worker thread alive to run WaitForProcess. Either it or its
+ // successors will be responsible for shutting down the worker thread.
+ onExitStopThread.release();
+#endif
+}
+
+void nsUpdateProcessor::ShutdownWorkerThread() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+ mWorkerThread->Shutdown();
+ mWorkerThread = nullptr;
+}
+
+#ifndef WIN32
+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));
+ }
+}
+#endif
+
+void nsUpdateProcessor::UpdateDone() {
+ MOZ_ASSERT(NS_IsMainThread(), "not main thread");
+
+ nsCOMPtr<nsIUpdateManager> um =
+ do_GetService("@mozilla.org/updates/update-manager;1");
+ if (um) {
+ // This completes asynchronously, but nothing else that we are doing in this
+ // function requires waiting for this to complete.
+ RefPtr<mozilla::dom::Promise> outPromise;
+ um->RefreshUpdateStatus(getter_AddRefs(outPromise));
+ }
+
+// On Windows, shutting down the worker thread is taken care of by another task.
+// (Which may not have run yet, so we can't assert.)
+#ifndef XP_WIN
+ ShutdownWorkerThread();
+#endif
+}
+
+NS_IMETHODIMP
+nsUpdateProcessor::GetServiceRegKeyExists(bool* aResult) {
+#ifndef XP_WIN
+ return NS_ERROR_NOT_IMPLEMENTED;
+#else // #ifdef XP_WIN
+ nsCOMPtr<nsIProperties> dirSvc(
+ do_GetService("@mozilla.org/file/directory_service;1"));
+ NS_ENSURE_TRUE(dirSvc, NS_ERROR_SERVICE_NOT_AVAILABLE);
+
+ nsCOMPtr<nsIFile> installBin;
+ nsresult rv = dirSvc->Get(XRE_EXECUTABLE_FILE, NS_GET_IID(nsIFile),
+ getter_AddRefs(installBin));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsCOMPtr<nsIFile> installDir;
+ rv = installBin->GetParent(getter_AddRefs(installDir));
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ nsAutoString installPath;
+ rv = installDir->GetPath(installPath);
+ NS_ENSURE_SUCCESS(rv, rv);
+
+ wchar_t maintenanceServiceKey[MAX_PATH + 1];
+ BOOL success = CalculateRegistryPathFromFilePath(
+ PromiseFlatString(installPath).get(), maintenanceServiceKey);
+ NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
+
+ HKEY regHandle;
+ LSTATUS ls = RegOpenKeyExW(HKEY_LOCAL_MACHINE, maintenanceServiceKey, 0,
+ KEY_QUERY_VALUE | KEY_WOW64_64KEY, &regHandle);
+ if (ls == ERROR_SUCCESS) {
+ RegCloseKey(regHandle);
+ *aResult = true;
+ return NS_OK;
+ }
+ if (ls == ERROR_FILE_NOT_FOUND) {
+ *aResult = false;
+ return NS_OK;
+ }
+ // We got an error we weren't expecting reading the registry.
+ return NS_ERROR_NOT_AVAILABLE;
+#endif // #ifdef XP_WIN
+}
diff --git a/toolkit/xre/nsUpdateDriver.h b/toolkit/xre/nsUpdateDriver.h
new file mode 100644
index 0000000000..24a20f3f76
--- /dev/null
+++ b/toolkit/xre/nsUpdateDriver.h
@@ -0,0 +1,112 @@
+/* -*- 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>
+# include "mozilla/WinHandleWatcher.h"
+typedef HANDLE ProcessType;
+#elif defined(XP_UNIX)
+typedef pid_t ProcessType;
+#else
+# include "prproces.h"
+typedef PRProcess* ProcessType;
+#endif
+
+#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
+
+/**
+ * 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 UpdateDone();
+ void ShutdownWorkerThread();
+
+#ifndef XP_WIN
+ void WaitForProcess();
+#endif
+
+ private:
+ ProcessType mUpdaterPID;
+ nsCOMPtr<nsIThread> mWorkerThread;
+#ifdef XP_WIN
+ mozilla::HandleWatcher mProcessWatcher;
+#endif
+ StagedUpdateInfo mInfo;
+};
+#endif // nsUpdateDriver_h__
diff --git a/toolkit/xre/nsUpdateSyncManager.cpp b/toolkit/xre/nsUpdateSyncManager.cpp
new file mode 100644
index 0000000000..b2f6d4247b
--- /dev/null
+++ b/toolkit/xre/nsUpdateSyncManager.cpp
@@ -0,0 +1,129 @@
+/* -*- 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(nsIFile* anAppFile /* = nullptr */) {
+ gUpdateSyncManager = this;
+ OpenLock(anAppFile);
+}
+
+nsUpdateSyncManager::~nsUpdateSyncManager() {
+ ReleaseLock();
+ gUpdateSyncManager = nullptr;
+}
+
+already_AddRefed<nsUpdateSyncManager> nsUpdateSyncManager::GetSingleton() {
+ if (!gUpdateSyncManager) {
+ new nsUpdateSyncManager(); // This sets gUpdateSyncManager.
+ }
+ 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(nsIFile* anAppFile) {
+ nsresult rv;
+ if (mLock != MULTI_INSTANCE_LOCK_HANDLE_ERROR) {
+ // Lock is already open.
+ return NS_OK;
+ }
+
+ // Our component registration should already have made sure of this.
+ if (NS_WARN_IF(XRE_GetProcessType() != GeckoProcessType_Default)) {
+ return NS_OK;
+ }
+
+ nsCOMPtr<nsIFile> appFile = mozilla::GetNormalizedAppFile(anAppFile);
+ if (!appFile) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ 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, 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(nsIFile* anAppFile = nullptr) {
+ ReleaseLock();
+ return OpenLock(anAppFile);
+}
diff --git a/toolkit/xre/nsUpdateSyncManager.h b/toolkit/xre/nsUpdateSyncManager.h
new file mode 100644
index 0000000000..4c24cde8d4
--- /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:
+ explicit nsUpdateSyncManager(nsIFile* anAppFile = nullptr);
+
+ 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(nsIFile* anAppFile = nullptr);
+ 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..869ca7c92c
--- /dev/null
+++ b/toolkit/xre/nsWindowsRestart.cpp
@@ -0,0 +1,197 @@
+/* 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;
+}
+
+/**
+ * Return true if we are in a job with JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE and
+ * we can break away from it.
+ * CreateProcess fails if we try to break away from a job but it's not allowed.
+ * So if we cannot determine the result due to a failure, we assume we don't
+ * need to break away and this returns false.
+ */
+static bool NeedToBreakAwayFromJob() {
+ // If we can't determine if we are in a job, we assume we're not in a job.
+ BOOL inJob = FALSE;
+ if (!::IsProcessInJob(::GetCurrentProcess(), nullptr, &inJob)) {
+ return false;
+ }
+
+ // If there is no job, there is nothing to worry about.
+ if (!inJob) {
+ return false;
+ }
+
+ // If we can't get the job object flags, we assume no need to break away from
+ // it.
+ JOBOBJECT_EXTENDED_LIMIT_INFORMATION job_info = {};
+ if (!::QueryInformationJobObject(nullptr, JobObjectExtendedLimitInformation,
+ &job_info, sizeof(job_info), nullptr)) {
+ return false;
+ }
+
+ // If JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE is not set, no need to worry about
+ // the job.
+ if (!(job_info.BasicLimitInformation.LimitFlags &
+ JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE)) {
+ return false;
+ }
+
+ // If we can't break away from the current job, there is nothing we can do.
+ if (!(job_info.BasicLimitInformation.LimitFlags &
+ JOB_OBJECT_LIMIT_BREAKAWAY_OK)) {
+ return false;
+ }
+
+ // We can and need to break away from the job.
+ return true;
+}
+
+/**
+ * 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;
+ }
+
+ DWORD creationFlags =
+ NeedToBreakAwayFromJob() ? CREATE_BREAKAWAY_FROM_JOB : 0;
+
+ 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
+ creationFlags,
+ 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
+ creationFlags, 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..ea14a59b80
--- /dev/null
+++ b/toolkit/xre/nsWindowsWMain.cpp
@@ -0,0 +1,176 @@
+/* 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 "nsWindowsHelpers.h"
+
+#include <windows.h>
+#include <versionhelpers.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
+
+// SanitizeEnvironmentVariables()
+//
+// Mitigate CVE-2007-6753 (binary planting via unexpanded environment variable
+// references) by forcibly expanding all environment variable references in
+// %PATH%.
+//
+// CVE-2007-6753 is documented to have affected all active mainline Windows
+// versions at the time of its announcement (i.e., up to Windows 7). Microsoft
+// has never formally stated that later versions of Windows are free of this
+// issue; out of an abundance of caution, we continue to mitigate it on Windows
+// 8 and beyond.
+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);
+ // The maximum length of a Windows environment variable and the maximum
+ // length of a string returned by ExpandEnvironmentStringsW are both
+ // documented to be 32,767 characters. Under some versions of Windows,
+ // however, both may be longer, with delayed but deleterious consequences.
+ //
+ // We therefore cap the size manually, in case the user has a sufficiently
+ // problematic %PATH%. (See bug 1753910.) This is unlikely to occur unless
+ // there is some form of recursive referencing involved, in which case
+ // expansion is no mitigation anyway.
+ if (bufferSize && bufferSize < 32768) {
+ 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) {
+ // In Windows 7 32-bit, user32.dll must be mapped to the same virtual
+ // address in all processes. Otherwise, win32k's user-mode callback causes
+ // crash. Since we delayload user32.dll, if our code or injected code
+ // reserves the user32 address before user32.dll is loaded, it is loaded
+ // to a new address and we crash. To mitigate this problem, we explicitly
+ // load user32.dll as early as possible. See bug 1730033 for details.
+ if (!IsWindows8OrGreater()) {
+ SYSTEM_INFO sysInfo;
+ ::GetNativeSystemInfo(&sysInfo);
+ if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
+ LoadLibrarySystem32(L"user32.dll");
+ }
+ }
+
+ 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..3d36e8484c
--- /dev/null
+++ b/toolkit/xre/nsX11ErrorHandler.cpp
@@ -0,0 +1,154 @@
+/* -*- 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 "nsString.h"
+#include "nsTArray.h"
+
+#include "mozilla/X11Util.h"
+#include <X11/Xlib.h>
+
+#define BUFSIZE 2048 // What Xlib uses with XGetErrorDatabaseText
+
+struct XExtension {
+ nsCString name;
+ int major_opcode;
+
+ XExtension(const char* aName, int aCode) : name(aName), major_opcode(aCode) {}
+};
+
+static nsTArray<XExtension> sXExtensions;
+
+// 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 query the
+// extension list early to avoid problems.
+static void QueryXExtensions(Display* aDisplay) {
+ if (!sXExtensions.IsEmpty() || !aDisplay) {
+ return;
+ }
+ int nExts = 0;
+ char** extNames = XListExtensions(aDisplay, &nExts);
+ if (!extNames) {
+ return;
+ }
+ for (int i = 0; i < nExts; ++i) {
+ int major_opcode, first_event, first_error;
+ if (XQueryExtension(aDisplay, extNames[i], &major_opcode, &first_event,
+ &first_error)) {
+ sXExtensions.EmplaceBack(extNames[i], major_opcode);
+ }
+ }
+ XFreeExtensionList(extNames);
+}
+
+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
+ for (XExtension& ext : sXExtensions) {
+ if (ext.major_opcode == event->request_code) {
+ message.Append(ext.name);
+ message.Append('.');
+ message.AppendInt(event->minor_code);
+ break;
+ }
+ }
+ }
+
+ 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");
+ }
+ }
+
+#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
+
+ NS_WARNING(notes.get());
+ return 0;
+}
+}
+
+void InstallX11ErrorHandler() {
+ XSetErrorHandler(X11Error);
+
+ if (Display* display = mozilla::DefaultXDisplay()) {
+ QueryXExtensions(display);
+ if (PR_GetEnv("MOZ_X_SYNC")) {
+ XSynchronize(display, X11True);
+ }
+ }
+}
+
+void CleanupX11ErrorHandler() { sXExtensions.Clear(); }
diff --git a/toolkit/xre/nsX11ErrorHandler.h b/toolkit/xre/nsX11ErrorHandler.h
new file mode 100644
index 0000000000..bd7ac59154
--- /dev/null
+++ b/toolkit/xre/nsX11ErrorHandler.h
@@ -0,0 +1,20 @@
+/* -*- 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();
+
+void CleanupX11ErrorHandler();
+
+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..e8a4f547fd
--- /dev/null
+++ b/toolkit/xre/nsXREDirProvider.cpp
@@ -0,0 +1,1660 @@
+/* -*- 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 "SpecialSystemDirectory.h"
+
+#include "mozilla/dom/ScriptSettings.h"
+
+#include "mozilla/AppShutdown.h"
+#include "mozilla/AutoRestore.h"
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+#include "mozilla/Components.h"
+#include "mozilla/Services.h"
+#include "mozilla/Omnijar.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/ProfilerLabels.h"
+#include "mozilla/Telemetry.h"
+#include "mozilla/XREAppData.h"
+#include "nsPrintfCString.h"
+
+#ifdef MOZ_THUNDERBIRD
+# include "nsIPK11TokenDB.h"
+# include "nsIPK11Token.h"
+#endif
+
+#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 "nsID.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_UNIX)
+static const char* GetAppName() {
+ if (gAppData) {
+ return gAppData->name;
+ }
+ return nullptr;
+}
+#endif
+
+#ifdef XP_MACOSX
+static const char* GetAppVendor() {
+ if (gAppData) {
+ return gAppData->vendor;
+ }
+ return nullptr;
+}
+#endif
+
+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;
+ nsCOMPtr<nsIFile> binaryPath;
+ nsresult rv = XRE_GetBinaryPath(getter_AddRefs(binaryPath));
+ if (NS_FAILED(rv)) return rv;
+ rv = binaryPath->GetParent(getter_AddRefs(mGREBinDir));
+ if (NS_FAILED(rv)) return rv;
+
+ 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;
+ 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;
+}
+
+#ifdef MOZ_BACKGROUNDTASKS
+nsresult nsXREDirProvider::GetBackgroundTasksProfilesRootDir(
+ nsIFile** aResult) {
+ nsCOMPtr<nsIFile> file;
+ nsresult rv = GetUserDataDirectory(getter_AddRefs(file), false);
+
+ if (NS_SUCCEEDED(rv)) {
+# if !defined(XP_UNIX) || defined(XP_MACOSX)
+ // Sibling to regular user "Profiles" directory.
+ rv = file->AppendNative("Background Tasks Profiles"_ns);
+# endif
+ // We must create the directory here if it does not exist.
+ nsresult tmp = EnsureDirectoryExists(file);
+ if (NS_FAILED(tmp)) {
+ rv = tmp;
+ }
+ }
+ file.swap(*aResult);
+ return rv;
+}
+#endif
+
+#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, 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)) {
+ return mGREBinDir->Clone(aFile);
+ } 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_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)
+ 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;
+ }
+ }
+
+ 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);
+
+ const char* prefKey = "security.sandbox.content.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 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);
+
+ // Get (and create if blank) temp directory suffix pref.
+ const char* pref = "security.sandbox.content.tempDirSuffix";
+
+ nsresult rv;
+ nsAutoString tempDirSuffix;
+ mozilla::Preferences::GetString(pref, tempDirSuffix);
+
+ if (tempDirSuffix.IsEmpty()) {
+ nsID uuid;
+ rv = nsID::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.
+ nsresult rv = dir->Remove(/* aRecursive */ true);
+ if (NS_FAILED(rv) && rv != NS_ERROR_FILE_NOT_FOUND) {
+ return rv;
+ }
+ }
+ return NS_OK;
+}
+
+#endif // defined(MOZ_SANDBOX)
+
+static const char* const kAppendPrefDir[] = {"defaults", "preferences",
+ nullptr};
+#ifdef MOZ_BACKGROUNDTASKS
+static const char* const kAppendBackgroundTasksPrefDir[] = {
+ "defaults", "backgroundtasks", nullptr};
+#endif
+
+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);
+#ifdef MOZ_BACKGROUNDTASKS
+ if (mozilla::BackgroundTasks::IsBackgroundTaskMode()) {
+ LoadDirIntoArray(mGREDir, kAppendBackgroundTasksPrefDir, directories);
+ LoadDirIntoArray(mXULAppDir, kAppendBackgroundTasksPrefDir, directories);
+ }
+#endif
+
+ 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
+
+#ifdef MOZ_THUNDERBIRD
+ bool bgtaskMode = false;
+# ifdef MOZ_BACKGROUNDTASKS
+ bgtaskMode = mozilla::BackgroundTasks::IsBackgroundTaskMode();
+# endif
+ if (!bgtaskMode &&
+ mozilla::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");
+ if (db) {
+ nsCOMPtr<nsIPK11Token> token;
+ if (NS_SUCCEEDED(db->GetInternalKeyToken(getter_AddRefs(token)))) {
+ mozilla::Unused << token->Login(false);
+ }
+ } else {
+ NS_WARNING("Failed to get nsIPK11TokenDB service.");
+ }
+ }
+#endif
+
+ bool initExtensionManager =
+#ifdef MOZ_BACKGROUNDTASKS
+ !mozilla::BackgroundTasks::IsBackgroundTaskMode();
+#else
+ true;
+#endif
+ if (initExtensionManager) {
+ // 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);
+
+ 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
+ }
+ return NS_OK;
+}
+
+void nsXREDirProvider::DoShutdown() {
+ AUTO_PROFILER_LABEL("nsXREDirProvider::DoShutdown", OTHER);
+
+ if (mProfileNotified) {
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownNetTeardown, nullptr);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownTeardown, nullptr);
+
+#ifdef DEBUG
+ // Not having this causes large intermittent leaks. See bug 1340425.
+ if (JSContext* cx = mozilla::dom::danger::GetJSContext()) {
+ JS_GC(cx);
+ }
+#endif
+
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdown, nullptr);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownQM, nullptr);
+ mozilla::AppShutdown::AdvanceShutdownPhase(
+ mozilla::ShutdownPhase::AppShutdownTelemetry, nullptr);
+ mProfileNotified = false;
+ }
+
+ gDataDirProfileLocal = nullptr;
+ gDataDirProfile = nullptr;
+
+ if (XRE_IsParentProcess()) {
+#if defined(MOZ_SANDBOX)
+ mozilla::Unused << DeleteDirIfExists(mContentProcessSandboxTempDir);
+#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) {
+ mozilla::UniquePtr<NS_tchar[]> hash;
+ bool success = ::GetInstallHash(PromiseFlatString(aInstallPath).get(), 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) {
+ nsAutoString stringToHash;
+
+#ifdef XP_WIN
+ if (mozilla::widget::WinUtils::HasPackageIdentity()) {
+ // For packages, the install path includes the version number, so it isn't
+ // a stable or consistent identifier for the installation. The package
+ // family name is though, so use that instead of the path.
+ stringToHash = mozilla::widget::WinUtils::GetPackageFamilyName();
+ } else
+#endif
+ {
+ 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.
+
+ rv = installDir->GetPath(stringToHash);
+ NS_ENSURE_SUCCESS(rv, rv);
+ }
+
+ // If we somehow failed to get an actual value, hashing an empty string could
+ // potentially cause some serious problems given all the things this hash is
+ // used for. So we don't allow that.
+ if (stringToHash.IsEmpty()) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return HashInstallPath(stringToHash, 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(u".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) {
+ hrv =
+ GetOldUpdateDirectory(PromiseFlatString(installPath).get(), updatePath);
+ } else {
+ hrv = GetCommonUpdateDirectory(PromiseFlatString(installPath).get(),
+ 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);
+
+ 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::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..da89eb11b2
--- /dev/null
+++ b/toolkit/xre/nsXREDirProvider.h
@@ -0,0 +1,168 @@
+/* -*- 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"
+#ifdef MOZ_BACKGROUNDTASKS
+# include "mozilla/BackgroundTasks.h"
+#endif
+
+// {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);
+#ifdef MOZ_BACKGROUNDTASKS
+ // A special location for non-ephemeral background tasks profiles,
+ // distinct from user profiles.
+ nsresult GetBackgroundTasksProfilesRootDir(nsIFile** aResult);
+#endif
+
+ 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);
+#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);
+
+ // 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();
+#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;
+#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/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..e36c62e0db
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
@@ -0,0 +1,227 @@
+/* -*- 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/gtest/MozAssertions.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
+ {{"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"\""},
+
+ // Empty string cases
+ {{"", nullptr}, L"\"\""},
+ {{"foo", "", nullptr}, L"foo \"\""},
+ {{"", "bar", nullptr}, L"\"\" bar"},
+ {{"foo", "", "bar", nullptr}, L"foo \"\" bar"},
+};
+
+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);
+
+ 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_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t 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_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t 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_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+
+ receiver.Parse(v2.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t 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_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+}
+
+TEST(WinRemoteMessage, NonNullTerminatedBuffer)
+{
+ // Reserve two pages and commit the first one
+ const uint32_t kPageSize = 4096;
+ UniquePtr<void, VirtualFreeDeleter> pages(
+ ::VirtualAlloc(nullptr, kPageSize * 2, MEM_RESERVE, PAGE_NOACCESS));
+ EXPECT_TRUE(pages);
+ EXPECT_TRUE(
+ ::VirtualAlloc(pages.get(), kPageSize, MEM_COMMIT, PAGE_READWRITE));
+
+ // Test strings with lengths between 0 and |kMaxBufferSize| bytes
+ const int kMaxBufferSize = 10;
+
+ // Set a string just before the boundary between the two pages.
+ uint8_t* bufferEnd = reinterpret_cast<uint8_t*>(pages.get()) + kPageSize;
+ memset(bufferEnd - kMaxBufferSize, '$', kMaxBufferSize);
+
+ nsCOMPtr<nsIFile> workingDir;
+ COPYDATASTRUCT copyData = {};
+ for (int i = 0; i < kMaxBufferSize; ++i) {
+ WinRemoteMessageReceiver receiver;
+
+ copyData.cbData = i;
+ copyData.lpData = bufferEnd - i;
+
+ copyData.dwData =
+ static_cast<ULONG_PTR>(WinRemoteMessageVersion::CommandLineOnly);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDir);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
new file mode 100644
index 0000000000..a5f8a19a9c
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
@@ -0,0 +1,372 @@
+/* -*- 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 <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+
+// Insulate these test-classes from the outside world.
+//
+// (In particular, distinguish this `CommandLine` from the one in ipc/chromium.
+// The compiler has no issues here, but the MSVC debugger gets somewhat
+// confused.)
+namespace testzilla {
+
+// Auxiliary class to simplify the declaration of test-cases for
+// EnsureCommandlineSafe.
+class CommandLine {
+ const size_t size;
+ std::vector<std::string> _data_8;
+ std::vector<std::wstring> _data_16;
+ // _should_ be const all the way through, but...
+ char const** argv_8;
+ wchar_t const** argv_16;
+
+ public:
+ inline int argc() const { return (int)(unsigned int)(size); }
+
+ template <typename CharT>
+ CharT const** argv() const;
+
+ CommandLine(CommandLine const&) = delete;
+ CommandLine(CommandLine&& that) = delete;
+
+ template <typename Container>
+ explicit CommandLine(Container const& container)
+ : size{std::size(container) + 1}, // plus one for argv[0]
+ argv_8(new const char*[size + 1]), // plus one again for argv[argc]
+ argv_16(new const wchar_t*[size + 1]) {
+ _data_8.reserve(size + 1);
+ _data_16.reserve(size + 1);
+ size_t pos = 0;
+
+ const auto append = [&](const char* val) {
+ size_t const len = ::strlen(val);
+ _data_8.emplace_back(val, val + len);
+ _data_16.emplace_back(val, val + len);
+ argv_8[pos] = _data_8.back().data();
+ argv_16[pos] = _data_16.back().data();
+ ++pos;
+ };
+
+ append("GECKOAPP.COM"); // argv[0] may be anything
+ for (const char* item : container) {
+ append(item);
+ }
+ assert(pos == size);
+
+ // C99 §5.1.2.2.1 states "argv[argc] shall be a null pointer", and
+ // `CheckArg` implicitly relies on this.
+ argv_8[pos] = nullptr;
+ argv_16[pos] = nullptr;
+ }
+
+ // debug output
+ friend std::ostream& operator<<(std::ostream& o, CommandLine const& cl) {
+ for (const auto& item : cl._data_8) {
+ o << '"';
+ for (const char c : item) {
+ if (c == '"') {
+ o << "\\\"";
+ } else {
+ o << c;
+ }
+ }
+ o << "\", ";
+ }
+ return o;
+ }
+};
+
+template <>
+char const** CommandLine::argv<char>() const {
+ return argv_8;
+}
+template <>
+wchar_t const** CommandLine::argv<wchar_t>() const {
+ return argv_16;
+}
+
+enum TestCaseState : bool {
+ FAIL = false,
+ PASS = true,
+};
+constexpr TestCaseState operator!(TestCaseState s) {
+ return TestCaseState(!bool(s));
+}
+
+#ifdef XP_WIN
+constexpr static const TestCaseState WIN_ONLY = PASS;
+#else
+constexpr static const TestCaseState WIN_ONLY = FAIL;
+#endif
+
+using NarrowTestCase = std::pair<const char*, const char*>;
+constexpr std::pair<TestCaseState, NarrowTestCase> kStrMatches8[] = {
+ {PASS, {"", ""}},
+
+ {PASS, {"i", "i"}},
+ {PASS, {"i", "I"}},
+
+ {FAIL, {"", "i"}},
+ {FAIL, {"i", ""}},
+ {FAIL, {"i", "j"}},
+
+ {PASS, {"mozilla", "mozilla"}},
+ {PASS, {"mozilla", "Mozilla"}},
+ {PASS, {"mozilla", "MOZILLA"}},
+ {PASS, {"mozilla", "mOZILLA"}},
+ {PASS, {"mozilla", "mOZIlLa"}},
+ {PASS, {"mozilla", "MoZiLlA"}},
+
+ {FAIL, {"mozilla", ""}},
+ {FAIL, {"mozilla", "mozill"}},
+ {FAIL, {"mozilla", "mozillo"}},
+ {FAIL, {"mozilla", "mozillam"}},
+ {FAIL, {"mozilla", "mozilla-"}},
+ {FAIL, {"mozilla", "-mozilla"}},
+ {FAIL, {"-mozilla", "mozilla"}},
+
+ // numbers are permissible
+ {PASS, {"m0zi11a", "m0zi11a"}},
+ {PASS, {"m0zi11a", "M0ZI11A"}},
+ {PASS, {"m0zi11a", "m0Zi11A"}},
+
+ {FAIL, {"mozilla", "m0zi11a"}},
+ {FAIL, {"m0zi11a", "mozilla"}},
+
+ // capital letters are not accepted in the left comparand
+ {FAIL, {"I", "i"}},
+ {FAIL, {"I", "i"}},
+ {FAIL, {"Mozilla", "mozilla"}},
+ {FAIL, {"Mozilla", "Mozilla"}},
+
+ // punctuation other than `-` is rejected
+ {FAIL, {"*", "*"}},
+ {FAIL, {"*", "word"}},
+ {FAIL, {".", "."}},
+ {FAIL, {".", "a"}},
+ {FAIL, {"_", "_"}},
+
+ // spaces are rejected
+ {FAIL, {" ", " "}},
+ {FAIL, {"two words", "two words"}},
+
+ // non-ASCII characters are rejected
+ //
+ // (the contents of this test case may differ depending on the source and
+ // execution character sets, but the result should always be failure)
+ {FAIL, {"à", "a"}},
+ {FAIL, {"a", "à"}},
+ {FAIL, {"à", "à"}},
+};
+
+#ifdef XP_WIN
+using WideTestCase = std::pair<const char*, const wchar_t*>;
+std::pair<TestCaseState, WideTestCase> kStrMatches16[] = {
+ // (Turkish 'İ' may lowercase to 'i' in some locales, but
+ // we explicitly prevent that from being relevant)
+ {FAIL, {"i", L"İ"}},
+ {FAIL, {"mozilla", L"ṁozilla"}},
+};
+#endif
+
+constexpr const char* kRequiredArgs[] = {"aleph", "beth"};
+
+std::pair<TestCaseState, std::vector<const char*>> const kCommandLines[] = {
+ // the basic admissible forms
+ {PASS, {"-osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-beth", "http://www.example.com/"}},
+
+ // GNU-style double-dashes are also allowed
+ {PASS, {"--osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "-beth", "http://www.example.com/"}},
+ {PASS, {"--osint", "--aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "--beth", "http://www.example.com/"}},
+
+ // on Windows, slashes are also permitted
+ {WIN_ONLY, {"-osint", "/aleph", "http://www.example.com/"}},
+ {WIN_ONLY, {"--osint", "/aleph", "http://www.example.com/"}},
+ // - These should pass on Windows because they're well-formed.
+ // - These should pass elsewhere because the first parameter is
+ // just a local filesystem path, not an option.
+ {PASS, {"/osint", "/aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "--aleph", "http://www.example.com/"}},
+
+ // the required argument's argument may not itself be a flag
+ {FAIL, {"-osint", "-aleph", "-anything"}},
+ {FAIL, {"-osint", "-aleph", "--anything"}},
+ // - On Windows systems this is a switch and should be rejected.
+ // - On non-Windows systems this is potentially a valid local path.
+ {!WIN_ONLY, {"-osint", "-aleph", "/anything"}},
+
+ // wrong order
+ {FAIL, {"-osint", "http://www.example.com/", "-aleph"}},
+ {FAIL, {"-aleph", "-osint", "http://www.example.com/"}},
+ {FAIL, {"-aleph", "http://www.example.com/", "-osint"}},
+ {FAIL, {"http://www.example.com/", "-osint", "-aleph"}},
+ {FAIL, {"http://www.example.com/", "-aleph", "-osint"}},
+
+ // missing arguments
+ {FAIL, {"-osint", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph"}},
+ {FAIL, {"-osint"}},
+
+ // improper arguments
+ {FAIL, {"-osint", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "", "http://www.example.com/"}},
+
+ // additional arguments
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-other-argument"}},
+ {FAIL, {"-osint", "-other-argument", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-a", "-b"}},
+ {FAIL, {"-osint", "-aleph", "-a", "http://www.example.com/", "-b"}},
+ {FAIL, {"-osint", "-a", "-aleph", "http://www.example.com/", "-b"}},
+
+ // multiply-occurring otherwise-licit arguments
+ {FAIL, {"-osint", "-aleph", "-beth", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-aleph", "http://www.example.com/"}},
+
+ // if -osint is not supplied, anything goes
+ {PASS, {}},
+ {PASS, {"http://www.example.com"}},
+ {PASS, {"-aleph", "http://www.example.com/"}},
+ {PASS, {"-beth", "http://www.example.com/"}},
+ {PASS, {"-aleph", "http://www.example.com/", "-other-argument"}},
+ {PASS, {"-aleph", "-aleph", "http://www.example.com/"}},
+};
+
+constexpr static char const* const kOptionalArgs[] = {"mozilla", "allizom"};
+
+// Additional test cases for optional parameters.
+//
+// Test cases marked PASS should pass iff the above optional parameters are
+// permitted. (Test cases marked FAIL should fail regardless, and are only
+// grouped here for convenience and semantic coherence.)
+std::pair<TestCaseState, std::vector<const char*>> kCommandLinesOpt[] = {
+ // one permitted optional argument
+ {PASS, {"-osint", "-mozilla", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+
+ // multiple permitted optional arguments
+ {PASS,
+ {"-osint", "-mozilla", "-allizom", "-aleph", "http://www.example.com/"}},
+ {PASS,
+ {"-osint", "-allizom", "-mozilla", "-aleph", "http://www.example.com/"}},
+
+ // optional arguments in the wrong place
+ {FAIL, {"-mozilla", "-osint", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-mozilla"}},
+
+ // optional arguments in both right and wrong places
+ {FAIL,
+ {"-mozilla", "-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "http://www.example.com/", "-mozilla"}},
+};
+
+enum WithOptionalState : bool {
+ NoOptionalArgs = false,
+ WithOptionalArgs = true,
+};
+
+template <typename CharT>
+bool TestCommandLineImpl(CommandLine const& cl,
+ WithOptionalState withOptional) {
+ int argc = cl.argc();
+ // EnsureCommandlineSafe's signature isn't const-correct here for annoying
+ // reasons, but it is indeed non-mutating.
+ CharT** argv = const_cast<CharT**>(cl.argv<CharT>());
+ if (withOptional) {
+ return mozilla::internal::EnsureCommandlineSafeImpl(
+ argc, argv, kRequiredArgs, kOptionalArgs);
+ }
+ return mozilla::internal::EnsureCommandlineSafeImpl(argc, argv,
+ kRequiredArgs);
+}
+
+// Test that `args` produces `expectation`. On Windows, test against both
+// wide-character and narrow-character implementations.
+void TestCommandLine(TestCaseState expectation, CommandLine const& cl,
+ WithOptionalState withOptional) {
+ EXPECT_EQ(TestCommandLineImpl<char>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#ifdef XP_WIN
+ EXPECT_EQ(TestCommandLineImpl<wchar_t>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#endif
+}
+} // namespace testzilla
+
+/***********************
+ * Test cases
+ */
+
+TEST(CmdLineAndEnvUtils, strimatch)
+{
+ using namespace testzilla;
+ using mozilla::strimatch;
+ for (auto const& [result, data] : kStrMatches8) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ <" << right << '>';
+
+#ifdef XP_WIN
+ wchar_t right_wide[200];
+ ::mbstowcs(right_wide, right, 200);
+ EXPECT_EQ(strimatch(left, right_wide), result)
+ << '<' << left << "> !~ L<" << right << '>';
+#endif
+ }
+
+#ifdef XP_WIN
+ for (auto const& [result, data] : kStrMatches16) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ L<" << right << '>';
+ }
+#endif
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafe)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, NoOptionalArgs);
+ }
+ for (auto const& [_unused, data] : kCommandLinesOpt) {
+ MOZ_UNUSED(_unused); // silence gcc
+ CommandLine const cl(data);
+ TestCommandLine(FAIL, cl, NoOptionalArgs);
+ }
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafeWithOptional)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+ for (auto const& [result, data] : kCommandLinesOpt) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+}
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/TestGeckoArgs.cpp b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
new file mode 100644
index 0000000000..42c7a41b2b
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
@@ -0,0 +1,179 @@
+/* -*- 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/ArrayUtils.h"
+#include "mozilla/GeckoArgs.h"
+
+using namespace mozilla;
+using namespace mozilla::geckoargs;
+
+static CommandLineArg<const char*> kCharParam{"-charParam", "charparam"};
+static CommandLineArg<uint64_t> kUint64Param{"-Uint64Param", "uint64param"};
+static CommandLineArg<bool> kFlag{"-Flag", "flag"};
+
+template <size_t N>
+bool CheckArgv(char** aArgv, const char* const (&aExpected)[N]) {
+ for (size_t i = 0; i < N; ++i) {
+ if (aArgv[i] == nullptr && aExpected[i] == nullptr) {
+ return true;
+ }
+ if (aArgv[i] == nullptr || aExpected[i] == nullptr) {
+ return false;
+ }
+ if (strcmp(aArgv[i], aExpected[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+char kFirefox[] = "$HOME/bin/firefox/firefox-bin";
+
+TEST(GeckoArgs, const_char_ptr)
+{
+ char kCharParamStr[] = "-charParam";
+ char kCharParamValue[] = "paramValue";
+
+ {
+ char* argv[] = {kFirefox, kCharParamStr, kCharParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isSome());
+ EXPECT_EQ(*charParam, kCharParamValue);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kBlahBlah[] = "-blahblah";
+ char* argv[] = {kFirefox, kCharParamStr, kBlahBlah, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isNothing());
+
+ const char* const expArgv[] = {kFirefox, kBlahBlah, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kCharParam.Put("ParamValue", extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-charParam");
+ EXPECT_EQ(extraArgs[1], "ParamValue");
+ }
+ { EXPECT_EQ(kCharParam.Name(), "-charParam"); }
+}
+
+TEST(GeckoArgs, uint64)
+{
+ char kUint64ParamStr[] = "-Uint64Param";
+
+ {
+ char* argv[] = {kFirefox, kUint64ParamStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "42";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isSome());
+ EXPECT_EQ(*uint64Param, 42U);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "aa";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kUint64Param.Put(1234, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-Uint64Param");
+ EXPECT_EQ(extraArgs[1], "1234");
+ }
+ { EXPECT_EQ(kUint64Param.Name(), "-Uint64Param"); }
+}
+
+TEST(GeckoArgs, bool)
+{
+ char kFlagStr[] = "-Flag";
+
+ {
+ char* argv[] = {kFirefox, kFlagStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isSome());
+ EXPECT_TRUE(*Flag);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(true, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 1U);
+ EXPECT_EQ(extraArgs[0], "-Flag");
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(false, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 0U);
+ }
+ { EXPECT_EQ(kFlag.Name(), "-Flag"); }
+}
diff --git a/toolkit/xre/test/gtest/moz.build b/toolkit/xre/test/gtest/moz.build
new file mode 100644
index 0000000000..bec881fa72
--- /dev/null
+++ b/toolkit/xre/test/gtest/moz.build
@@ -0,0 +1,24 @@
+# -*- 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 = [
+ "TestCmdLineAndEnvUtils.cpp",
+ "TestCompatVersionCompare.cpp",
+ "TestGeckoArgs.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/remote",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestAssembleCommandLineWin.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/test/marionette/gen_win32k_tests.py b/toolkit/xre/test/marionette/gen_win32k_tests.py
new file mode 100644
index 0000000000..e0a39776db
--- /dev/null
+++ b/toolkit/xre/test/marionette/gen_win32k_tests.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+
+import re
+
+RE_DEFAULT = re.compile(r"\[D=([TF])\] ")
+RE_TRANSITION = re.compile(r"([a-zA-Z0-9 \[\]=#_-]+) *->(.*)")
+RE_ASSERTION = re.compile(
+ r"\[A S=([a-zA-Z01_]+) SS=([a-zA-Z01_]+) ES=([a-zA-Z01_]+) P=([a-zA-Z_]+) ESP=([a-zA-Z_]+)\]"
+)
+RE_ASSERTION_SHORTHAND = re.compile(r"\[A#([0-9T]+)\]")
+
+# ======================================================================
+# ======================================================================
+
+testnum = 1
+
+
+def start_test(line):
+ global testnum
+ output.write(
+ """
+ def test_{0}(self):
+ # {1}...\n""".format(
+ testnum, line[0:80]
+ )
+ )
+ testnum += 1
+
+
+def set_default(d):
+ output.write(
+ """
+ if self.default_is is not {0}:
+ return\n""".format(
+ "True" if d == "T" else "False"
+ )
+ )
+
+
+def enroll(e):
+ if e.endswith("-C"):
+ e = e[:-2]
+ output.write("\n # Re-set enrollment pref, like Normandy would do\n")
+ output.write(
+ " self.set_enrollment_status(ExperimentStatus.ENROLLED_{0})\n".format(
+ e.upper()
+ )
+ )
+
+
+def set_pref(enabled):
+ output.write(
+ "\n self.marionette.set_pref(Prefs.WIN32K, {0})\n".format(str(enabled))
+ )
+
+
+def set_e10s(enable):
+ if enable:
+ output.write(
+ """
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")\n"""
+ )
+ else:
+ raise Exception("Not implemented")
+
+
+def set_header(enable):
+ if enable:
+ output.write(
+ """
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})\n"""
+ )
+ else:
+ output.write(
+ """
+ self.set_env(ENV_DISABLE_WIN32K, "")\n"""
+ )
+
+
+def set_bad_requirements(enabled):
+ output.write(
+ """
+ self.marionette.set_pref(Prefs.WEBGL, {0})\n""".format(
+ "False" if enabled else "True"
+ )
+ )
+
+
+def restart():
+ output.write("\n self.restart()\n")
+
+
+def print_assertion(assertion):
+ if not assertion:
+ return
+ output.write(
+ """
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.{0},
+ sessionStatus=ContentWin32kLockdownState.{1},
+ experimentStatus=ExperimentStatus.{2},
+ pref={3},
+ enrollmentStatusPref=ExperimentStatus.{4},
+ )\n""".format(
+ *assertion
+ )
+ )
+
+
+# ======================================================================
+# ======================================================================
+
+TESTS = open("win32k_tests.txt", "r").readlines()
+
+output = open("test_win32k_enrollment.py", "w", newline="\n")
+header = open("test_win32k_enrollment.template.py", "r")
+for l in header:
+ output.write(l)
+
+mappings = {}
+for line in TESTS:
+ line = line.strip()
+ if not line:
+ continue
+
+ if line.startswith("#"):
+ continue
+ elif line.startswith("> "):
+ line = line[2:]
+ key, value = line.split(":")
+ mappings[key.strip()] = value.strip()
+ continue
+ elif line.startswith("-"):
+ line = line[1:].strip()
+ elif line.startswith("+"):
+ line = line[1:].strip()
+ import pdb
+
+ pdb.set_trace()
+ else:
+ raise Exception("unknown line type: " + line)
+
+ # We can't handle Safe Mode right now
+ if "Safe Mode" in line:
+ continue
+
+ # If we have no assertions defined, skip the test entirely
+ if "[A" not in line:
+ continue
+
+ if not RE_DEFAULT.match(line):
+ raise Exception("'{0}' does not match the default regex".format(line))
+ default = RE_DEFAULT.search(line).groups(1)[0]
+
+ start_test(line)
+
+ set_default(default)
+
+ line = line[6:]
+
+ while line:
+ # this is a horrible hack for the line ending to avoid making the
+ # regex more complicated and having to fix it
+ if not line.endswith(" ->"):
+ line += " ->"
+
+ groups = RE_TRANSITION.search(line).groups()
+ start = groups[0].strip()
+ end = groups[1].strip()
+
+ if RE_ASSERTION.search(start):
+ assertion = RE_ASSERTION.search(start).groups()
+ start = start[0 : start.index("[")].strip()
+ elif RE_ASSERTION_SHORTHAND.search(start):
+ key = RE_ASSERTION_SHORTHAND.search(start).groups()[0]
+ assertion = RE_ASSERTION.search(mappings[key]).groups()
+ start = start[0 : start.index("[")].strip()
+ else:
+ assertion = ""
+
+ if start == "Nothing":
+ pass
+ elif start.startswith("Enrolled "):
+ enroll(start[9:])
+ elif start == "E10S":
+ set_e10s(True)
+ elif start == "Header-On":
+ set_header(True)
+ elif start == "Header-Off":
+ set_header(False)
+ elif start == "Bad Requirements":
+ set_bad_requirements(True)
+ elif start == "Restart":
+ restart()
+ elif start == "On":
+ set_pref(True)
+ elif start == "Off":
+ set_pref(False)
+ else:
+ raise Exception("Unknown Action: " + start)
+
+ print_assertion(assertion)
+
+ line = end.strip()
+
+ if RE_ASSERTION.match(line):
+ print_assertion(RE_ASSERTION.search(line).groups())
+ elif RE_ASSERTION_SHORTHAND.search(line):
+ key = RE_ASSERTION_SHORTHAND.search(line).groups()[0]
+ print_assertion(RE_ASSERTION.search(mappings[key]).groups())
diff --git a/toolkit/xre/test/marionette/marionette.ini b/toolkit/xre/test/marionette/marionette.ini
new file mode 100644
index 0000000000..bb49d6c5de
--- /dev/null
+++ b/toolkit/xre/test/marionette/marionette.ini
@@ -0,0 +1,6 @@
+[test_fission_autostart.py]
+[test_win32k_enrollment.py]
+skip-if =
+ os != 'win'
+ ccov # Bug 1757102
+[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..4ce7306122
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_exitcode.py
@@ -0,0 +1,31 @@
+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(
+ """
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+ """,
+ sandbox="system",
+ )
+
+ self.marionette.quit(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(
+ """
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit, 5);
+ """,
+ sandbox="system",
+ )
+
+ self.marionette.quit(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..b830c0eaca
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_fission_autostart.py
@@ -0,0 +1,410 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+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_FISSION = "MOZ_FORCE_DISABLE_FISSION"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+DECISION_STATUS = {
+ "experimentControl": 1,
+ "experimentTreatment": 2,
+ "disabledByE10sEnv": 3,
+ "enabledByEnv": 4,
+ "disabledByEnv": 5,
+ "enabledByDefault": 7,
+ "disabledByDefault": 8,
+ "enabledByUserPref": 9,
+ "disabledByUserPref": 10,
+ "disabledByE10sOther": 11,
+}
+
+
+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 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.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestFissionAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `fission.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.fissionRequired = None
+ if Prefs.FISSION_AUTOSTART in self.marionette.instance.required_prefs:
+ self.fissionRequired = self.marionette.instance.required_prefs[
+ Prefs.FISSION_AUTOSTART
+ ]
+ del self.marionette.instance.required_prefs[Prefs.FISSION_AUTOSTART]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Fission status must start out with `enabledByDefault`
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ def tearDown(self):
+ if self.fissionRequired is not None:
+ self.marionette.instance.required_prefs[
+ Prefs.FISSION_AUTOSTART
+ ] = self.fissionRequired
+ self.marionette.restart(in_app=False, 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."""
+
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ self.restart(prefs={Prefs.FISSION_AUTOSTART: False})
+
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.marionette.set_pref(Prefs.FISSION_AUTOSTART, True)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ dynamic=True,
+ )
+
+ self.marionette.clear_pref(Prefs.FISSION_AUTOSTART)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ dynamic=True,
+ )
+
+ 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):
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ 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_DISABLE_FISSION: "1", ENV_ENABLE_FISSION: ""},
+ )
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByEnv",
+ dynamic=True,
+ )
+
+ self.restart(
+ prefs={Prefs.FISSION_AUTOSTART: False}, env={ENV_DISABLE_FISSION: ""}
+ )
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.restart(prefs={Prefs.FISSION_AUTOSTART: None})
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ 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):
+ # 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=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ # 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/marionette/test_win32k_enrollment.py b/toolkit/xre/test/marionette/test_win32k_enrollment.py
new file mode 100644
index 0000000000..d09331319b
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_win32k_enrollment.py
@@ -0,0 +1,2506 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+class ExperimentStatus:
+ UNENROLLED = 0
+ ENROLLED_CONTROL = 1
+ ENROLLED_TREATMENT = 2
+ DISQUALIFIED = 3
+
+
+class ContentWin32kLockdownState:
+ LockdownEnabled = 1
+ MissingWebRender = 2
+ OperatingSystemNotSupported = 3
+ PrefNotSet = 4
+ MissingRemoteWebGL = 5
+ MissingNonNativeTheming = 6
+ DisabledByEnvVar = 7
+ DisabledBySafeMode = 8
+ DisabledByE10S = 9
+ DisabledByUserPref = 10
+ EnabledByUserPref = 11
+ DisabledByControlGroup = 12
+ EnabledByTreatmentGroup = 13
+ DisabledByDefault = 14
+ EnabledByDefault = 15
+
+
+class Prefs:
+ ENROLLMENT_STATUS = "security.sandbox.content.win32k-experiment.enrollmentStatus"
+ STARTUP_ENROLLMENT_STATUS = (
+ "security.sandbox.content.win32k-experiment.startupEnrollmentStatus"
+ )
+ WIN32K = "security.sandbox.content.win32k-disable"
+ WEBGL = "webgl.out-of-process"
+
+
+ENV_DISABLE_WIN32K = "MOZ_ENABLE_WIN32K"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+class TestWin32kAutostart(MarionetteTestCase):
+ SANDBOX_NAME = "win32k-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_win32k_status(self):
+ return self.execute_script(
+ r"""
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let ses = "security.sandbox.content.win32k-experiment.startupEnrollmentStatus";
+ return {
+ win32kSessionStatus: Services.appinfo.win32kSessionStatus,
+ win32kStatus: Services.appinfo.win32kLiveStatusTestingOnly,
+ win32kExperimentStatus: Services.appinfo.win32kExperimentStatus,
+ win32kPref: Services.prefs.getBoolPref("security.sandbox.content.win32k-disable"),
+ win32kStartupEnrollmentStatusPref: Services.prefs.getIntPref(ses),
+ };
+ """
+ )
+
+ def check_win32k_status(
+ self, status, sessionStatus, experimentStatus, pref, enrollmentStatusPref
+ ):
+ # We CANNOT check win32kEnrollmentStatusPref after a restart because we only set this
+ # pref on the default branch, and it goes away after a restart, so we only check
+ # the startupEnrollmentStatusPref
+ expected = {
+ "win32kSessionStatus": sessionStatus,
+ "win32kStatus": status,
+ "win32kExperimentStatus": experimentStatus,
+ "win32kPref": pref,
+ "win32kStartupEnrollmentStatusPref": enrollmentStatusPref,
+ }
+
+ status = self.get_win32k_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 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)
+
+ updated_status = self.marionette.get_pref(Prefs.ENROLLMENT_STATUS)
+ self.assertTrue(
+ status == updated_status or updated_status == ExperimentStatus.DISQUALIFIED
+ )
+ startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS)
+ self.assertEqual(
+ startup_status,
+ updated_status,
+ "Startup enrollment status (%r) should match "
+ "session status (%r)" % (startup_status, updated_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.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestWin32kAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `win32k.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.win32kRequired = None
+ if Prefs.WIN32K in self.marionette.instance.required_prefs:
+ self.win32kRequired = self.marionette.instance.required_prefs[Prefs.WIN32K]
+ del self.marionette.instance.required_prefs[Prefs.WIN32K]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Marionette doesn't let you set preferences on the default branch before startup
+ # so we can't test the default=False and default=True scenarios in one test
+ # What we can do is generate all the tests, and then only run the runs for which
+ # the default is. (And run the other ones locally to make sure they work before
+ # we land it.)
+ prefJS = 'return Services.prefs.getBoolPref("security.sandbox.content.win32k-disable");'
+ self.default_is = self.execute_script(prefJS)
+
+ if self.default_is is False:
+ # Win32k status must start out with `disabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ else:
+ # Win32k status must start out with `enabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def tearDown(self):
+ if self.win32kRequired is not None:
+ self.marionette.instance.required_prefs[Prefs.WIN32K] = self.win32kRequired
+ self.marionette.restart(in_app=False, clean=True)
+
+ super(TestWin32kAutostart, self).tearDown()
+
+ def test_1(self):
+ # [D=F] Nothing [A#1] -> Enrolled Control [A S=DisabledByDefault SS=DisabledByDefa...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_2(self):
+ # [D=F] Nothing [A#1] -> Enrolled Treatment [A S=DisabledByDefault SS=DisabledByDe...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_3(self):
+ # [D=F] Nothing [A#1] -> On [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROL...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_4(self):
+ # [D=F] Nothing [A#1] -> Off [A#1] -> Restart [A#1]...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_5(self):
+ # [D=F] Nothing [A#1] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=Disable...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_6(self):
+ # [D=F] Nothing [A#1] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENR...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_7(self):
+ # [D=F] Nothing [A#1] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_8(self):
+ # [D=T] Nothing [A#1T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefau...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_9(self):
+ # [D=T] Nothing [A#1T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDef...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_10(self):
+ # [D=T] Nothing [A#1T] -> On [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLL...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_11(self):
+ # [D=T] Nothing [A#1T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENR...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_12(self):
+ # [D=T] Nothing [A#1T] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=Enable...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_13(self):
+ # [D=T] Nothing [A#1T] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNEN...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_14(self):
+ # [D=T] Nothing [A#1T] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVa...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_15(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Enrolled Control [A S=EnabledByUserPref SS=En...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_16(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Enrolled Treatment [A S=EnabledByUserPref SS=...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_17(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Off [A S=DisabledByDefault SS=EnabledByUserPr...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_18(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Bad Requirements [A S=MissingRemoteWebGL SS=E...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_19(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_20(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Header-On [A S=DisabledByEnvVar SS=DisabledBy...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_21(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Control [A S=EnabledByDefault SS=E...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_22(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Treatment [A S=EnabledByDefault SS...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_23(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Off [A S=DisabledByUserPref SS=EnabledByDef...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_24(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Bad Requirements [A S=MissingRemoteWebGL SS...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_25(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_26(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Header-On [A S=DisabledByEnvVar SS=Disabled...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_27(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> On [A S=E...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_28(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Off [A#6]...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_29(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Bad Requi...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_30(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> E10S [A S...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_31(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Header-On...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_32(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> On [A S...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_33(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Off [A S=DisabledByUserPref S...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_34(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Bad Req...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_35(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> E10S [A...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_36(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Header-...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_37(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> On [A...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_38(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Off [...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_39(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Bad R...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_40(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> E10S ...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_41(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Heade...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_42(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> On ...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_43(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Off...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_44(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Bad...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_45(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> E10...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_46(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Hea...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_47(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Control [A S=MissingR...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_48(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Treatment [A S=Missin...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_49(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> On [A S=MissingRemoteWebGL SS=...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_50(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Off [A S=MissingRemoteWebGL SS...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_51(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Control [A S=Missin...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_52(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Treatment [A S=Miss...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_53(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> On [A S=MissingRemoteWebGL S...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_54(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Off [A S=MissingRemoteWebGL ...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
diff --git a/toolkit/xre/test/marionette/test_win32k_enrollment.template.py b/toolkit/xre/test/marionette/test_win32k_enrollment.template.py
new file mode 100644
index 0000000000..30dc7fed33
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_win32k_enrollment.template.py
@@ -0,0 +1,204 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+class ExperimentStatus:
+ UNENROLLED = 0
+ ENROLLED_CONTROL = 1
+ ENROLLED_TREATMENT = 2
+ DISQUALIFIED = 3
+
+
+class ContentWin32kLockdownState:
+ LockdownEnabled = 1
+ MissingWebRender = 2
+ OperatingSystemNotSupported = 3
+ PrefNotSet = 4
+ MissingRemoteWebGL = 5
+ MissingNonNativeTheming = 6
+ DisabledByEnvVar = 7
+ DisabledBySafeMode = 8
+ DisabledByE10S = 9
+ DisabledByUserPref = 10
+ EnabledByUserPref = 11
+ DisabledByControlGroup = 12
+ EnabledByTreatmentGroup = 13
+ DisabledByDefault = 14
+ EnabledByDefault = 15
+
+
+class Prefs:
+ ENROLLMENT_STATUS = "security.sandbox.content.win32k-experiment.enrollmentStatus"
+ STARTUP_ENROLLMENT_STATUS = (
+ "security.sandbox.content.win32k-experiment.startupEnrollmentStatus"
+ )
+ WIN32K = "security.sandbox.content.win32k-disable"
+ WEBGL = "webgl.out-of-process"
+
+
+ENV_DISABLE_WIN32K = "MOZ_ENABLE_WIN32K"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+class TestWin32kAutostart(MarionetteTestCase):
+ SANDBOX_NAME = "win32k-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_win32k_status(self):
+ return self.execute_script(
+ r"""
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let ses = "security.sandbox.content.win32k-experiment.startupEnrollmentStatus";
+ return {
+ win32kSessionStatus: Services.appinfo.win32kSessionStatus,
+ win32kStatus: Services.appinfo.win32kLiveStatusTestingOnly,
+ win32kExperimentStatus: Services.appinfo.win32kExperimentStatus,
+ win32kPref: Services.prefs.getBoolPref("security.sandbox.content.win32k-disable"),
+ win32kStartupEnrollmentStatusPref: Services.prefs.getIntPref(ses),
+ };
+ """
+ )
+
+ def check_win32k_status(
+ self, status, sessionStatus, experimentStatus, pref, enrollmentStatusPref
+ ):
+ # We CANNOT check win32kEnrollmentStatusPref after a restart because we only set this
+ # pref on the default branch, and it goes away after a restart, so we only check
+ # the startupEnrollmentStatusPref
+ expected = {
+ "win32kSessionStatus": sessionStatus,
+ "win32kStatus": status,
+ "win32kExperimentStatus": experimentStatus,
+ "win32kPref": pref,
+ "win32kStartupEnrollmentStatusPref": enrollmentStatusPref,
+ }
+
+ status = self.get_win32k_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 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)
+
+ updated_status = self.marionette.get_pref(Prefs.ENROLLMENT_STATUS)
+ self.assertTrue(
+ status == updated_status or updated_status == ExperimentStatus.DISQUALIFIED
+ )
+ startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS)
+ self.assertEqual(
+ startup_status,
+ updated_status,
+ "Startup enrollment status (%r) should match "
+ "session status (%r)" % (startup_status, updated_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.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestWin32kAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `win32k.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.win32kRequired = None
+ if Prefs.WIN32K in self.marionette.instance.required_prefs:
+ self.win32kRequired = self.marionette.instance.required_prefs[Prefs.WIN32K]
+ del self.marionette.instance.required_prefs[Prefs.WIN32K]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Marionette doesn't let you set preferences on the default branch before startup
+ # so we can't test the default=False and default=True scenarios in one test
+ # What we can do is generate all the tests, and then only run the runs for which
+ # the default is. (And run the other ones locally to make sure they work before
+ # we land it.)
+ prefJS = 'return Services.prefs.getBoolPref("security.sandbox.content.win32k-disable");'
+ self.default_is = self.execute_script(prefJS)
+
+ if self.default_is is False:
+ # Win32k status must start out with `disabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ else:
+ # Win32k status must start out with `enabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def tearDown(self):
+ if self.win32kRequired is not None:
+ self.marionette.instance.required_prefs[Prefs.WIN32K] = self.win32kRequired
+ self.marionette.restart(in_app=False, clean=True)
+
+ super(TestWin32kAutostart, self).tearDown()
diff --git a/toolkit/xre/test/marionette/win32k_tests.txt b/toolkit/xre/test/marionette/win32k_tests.txt
new file mode 100644
index 0000000000..76068de268
--- /dev/null
+++ b/toolkit/xre/test/marionette/win32k_tests.txt
@@ -0,0 +1,167 @@
+#############################################
+# This file serves as the test case list for gen_win32k_tests.py
+
+#############################################
+# Only here for reference:
+
+# MissingWebRender = 2
+# OperatingSystemNotSupported = 3
+# MissingRemoteWebGL = 5
+# DisabledByEnvVar = 7
+# DisabledBySafeMode = 8
+# DisabledByE10S = 9
+# DisabledByUserPref = 10
+# EnabledByUserPref = 11
+# DisabledByControlGroup = 12
+# EnabledByTreatmentGroup = 13
+# DisabledByDefault = 14
+# EnabledByDefault = 15
+
+# UNENROLLED
+# ENROLLED_CONTROL
+# ENROLLED_TREATMENT
+# DISQUALIFIED
+
+#############################################
+# Assertion Mappings
+# Shorthand values for the test cases below. Instead of writing out the whole thing
+# you can just do [A#5]
+
+ > 1 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 1T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 2 : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 3 : [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 3T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 4 : [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 4T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 5 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_CONTROL]
+ > 5T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL]
+ > 6 : [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL]
+ > 6T : [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ > 7 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT]
+ > 7T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT]
+ > 8 : [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT]
+ > 8T : [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ > 9 : [A S=MissingRemoteWebGL SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 9T : [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 10 : [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 10T: [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+
+
+# TODO
+# - win32k env var tests
+
+
+#############################################
+# Tests
+
+# Syntax:
+# [D=T] - is the default value for win32k lockdown pref 'false' or 'true'
+# Action -> Action
+# Valid Actions:
+# Nothing
+# Enrolled Control/Treatment
+# On/Off (changes win32k pref)
+# Restart
+# E10s (Restarts with E10s disabled)
+# Header-On - Restart with MOZ_ENABLE_WIN32K set
+# Header-Off - unset MOZ_ENABLE_WIN32K but do not restart
+# Bad Requirements - sets webg out of process to false, invalidating win32k
+# Enrolled /Control/Treatment]-C:
+# The enrollment pref is set by normandy on _every_ startup on the
+# default branch, where it doesn't persist.
+# This action represents normandy doing its normal set that
+# occurs after a restart. If Normandy doesn't do it, the
+# startupEnrollmentPref is set back to 0 after restart, which is expected.
+# [A S= SS= ES= P= ESP=] - trigger an assertion check for the listed values
+# S - 'Status' or the current value of calculating GetWin32kLockdownState()
+# SS - 'Session Status' or the current value of set-once-at-startup static variable gWin32kStatus
+# ES - 'Experiment Status' or the value of gWin32kExperimentStatus
+# P - 'Pref' or the value of security.sandbox.content.win32k-disable
+# ESP- 'Enrollment Status Pref' or the value of security.sandbox.content.win32k-experiment.startupEnrollmentStatus
+# [A#5] - trigger an assertion check from the mapping table above
+
+
+# Safe Mode tests are currently not generated, as I don't know how to make marionette restart in safe mode
+
+#################
+ - [D=F] Nothing [A#1] -> Enrolled Control [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL]
+ - [D=F] Nothing [A#1] -> Enrolled Treatment [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT]
+ - [D=F] Nothing [A#1] -> On [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> Off [A#1] -> Restart [A#1]
+ - [D=F] Nothing [A#1] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off [A S=EnabledByUserPref SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> Safe Mode -> Restart
+
+#################
+ - [D=T] Nothing [A#1T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] Nothing [A#1T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] Nothing [A#1T] -> On [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off [A S=EnabledByDefault SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> Safe Mode -> Restart
+
+#################
+ - [D=F] On [A#3] -> Restart [A#4] -> Enrolled Control [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Enrolled Treatment [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Off [A S=DisabledByDefault SS=EnabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] On [A#3] -> Restart [A#4] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A#4]
+ - [D=F] On [A#3] -> Restart [A#4] -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off -> Restart [A#4]
+ - [D=F] On [A#3] -> Restart [A#4] -> Safe Mode
+
+#################
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A#4T]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off -> Restart [A#4T]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Safe Mode
+
+#################
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> On [A S=EnabledByUserPref SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=DISQUALIFIED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Off [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Enrolled Control-C -> Restart [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Header-Off -> Enrolled Control-C -> Restart [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Safe Mode ->
+
+#################
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> On [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Off [A S=DisabledByUserPref SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=DISQUALIFIED]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Enrolled Control-C -> Restart [A#6T]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Header-Off -> Enrolled Control-C -> Restart [A#6T]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Safe Mode ->
+
+#################
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> On [A S=EnabledByUserPref SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=DISQUALIFIED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Off [A#8] -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Enrolled Treatment-C -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Header-Off -> Enrolled Treatment-C -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Safe Mode ->
+
+#################
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> On [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Off [A S=DisabledByUserPref SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=DISQUALIFIED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Enrolled Treatment-C -> Restart [A#8T]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Header-Off -> Enrolled Treatment-C -> Restart [A#8T]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Safe Mode ->
+
+#################
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Control [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Treatment [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> On [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Off [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
+
+#################
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Control [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Treatment [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> On [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Off [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
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..75154b27bd
--- /dev/null
+++ b/toolkit/xre/test/test_install_hash.js
@@ -0,0 +1,138 @@
+/* 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 { Subprocess } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+);
+
+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..feacae36d4
--- /dev/null
+++ b/toolkit/xre/test/test_launch_without_hang.js
@@ -0,0 +1,256 @@
+// 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 { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+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 = [];
+ for (let i = 0; i < newVals.length; ++i) {
+ let key = newVals[i].key;
+ let value = newVals[i].value;
+ let oldObj = { key };
+ if (Services.env.exists(key)) {
+ oldObj.value = Services.env.get(key);
+ } else {
+ oldObj.value = null;
+ }
+
+ Services.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..ab5edcf1ca
--- /dev/null
+++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
@@ -0,0 +1,783 @@
+/* -*- 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);
+}
+
+bool GetInstallHash(const char16_t*, mozilla::UniquePtr<NS_tchar[]>& result) {
+ return true;
+}
+
+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/mochitest/browser_env_path_long.ini b/toolkit/xre/test/win/mochitest/browser_env_path_long.ini
new file mode 100644
index 0000000000..9eb5a7b6e4
--- /dev/null
+++ b/toolkit/xre/test/win/mochitest/browser_env_path_long.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+# This path should expand to >32K characters. See bug 1753910.
+environment =
+ PATH="C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%"
+
+[browser_env_path_long.js]
+skip-if = os != "win"
diff --git a/toolkit/xre/test/win/mochitest/browser_env_path_long.js b/toolkit/xre/test/win/mochitest/browser_env_path_long.js
new file mode 100644
index 0000000000..f720d8426b
--- /dev/null
+++ b/toolkit/xre/test/win/mochitest/browser_env_path_long.js
@@ -0,0 +1,15 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Test that the browser starts even when PATH would expand to a detrimentally
+// long value.
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function(
+ browser
+ ) {
+ ok(
+ true,
+ "Browser should start even with potentially pathologically long PATH."
+ );
+ });
+});
diff --git a/toolkit/xre/test/win/moz.build b/toolkit/xre/test/win/moz.build
new file mode 100644
index 0000000000..8744f70698
--- /dev/null
+++ b/toolkit/xre/test/win/moz.build
@@ -0,0 +1,55 @@
+# -*- 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/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "mochitest/browser_env_path_long.ini",
+]
+
+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 += [
+ "advapi32",
+ "comctl32",
+ "ole32",
+ "shell32",
+ "uuid",
+ "userenv",
+ "ws2_32",
+]
+
+if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ LOCAL_INCLUDES += [
+ "/toolkit/mozapps/update/common",
+ ]
+ 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..0efc83d30c
--- /dev/null
+++ b/toolkit/xre/test/xpcshell.ini
@@ -0,0 +1,20 @@
+# 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'
+requesttimeoutfactor = 2
+[test_install_hash.js]
+# Android doesn't ship Subprocess.sys.mjs and debug builds output garbage that the
+# test cannot handle.
+skip-if =
+ toolkit == 'android'
+ debug
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807927
+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;
+}