Bug 1511078: Add LauncherRegistryInfo as a temporary mechanism for runtime disabling of launcher process; r=mhowell
☠☠ backed out by b37c6861572a ☠ ☠
authorAaron Klotz <aklotz@mozilla.com>
Tue, 15 Jan 2019 20:19:46 +0000
changeset 511079 006df494925adbd6ad79a3281b78dcb506939b8b
parent 511078 8d104c49d7e2e5063a2cd1027586e39fd5a7f0bb
child 511080 7167385d70ec11e7196a499385b263555ce778ea
push id10547
push userffxbld-merge
push dateMon, 21 Jan 2019 13:03:58 +0000
treeherdermozilla-beta@24ec1916bffe [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersmhowell
bugs1511078
milestone66.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1511078: Add LauncherRegistryInfo as a temporary mechanism for runtime disabling of launcher process; r=mhowell Differential Revision: https://phabricator.services.mozilla.com/D15756
browser/app/winlauncher/DllBlocklistWin.cpp
browser/app/winlauncher/DllBlocklistWin.h
browser/app/winlauncher/ErrorHandler.cpp
browser/app/winlauncher/ErrorHandler.h
browser/app/winlauncher/LaunchUnelevated.cpp
browser/app/winlauncher/LaunchUnelevated.h
browser/app/winlauncher/LauncherProcessWin.cpp
browser/app/winlauncher/LauncherResult.h
browser/app/winlauncher/NativeNt.h
browser/app/winlauncher/SameBinary.h
browser/app/winlauncher/moz.build
browser/app/winlauncher/test/TestNativeNt.cpp
browser/app/winlauncher/test/TestSameBinary.cpp
browser/app/winlauncher/test/moz.build
mozglue/misc/NativeNt.h
mozglue/misc/moz.build
mozglue/tests/TestNativeNt.cpp
mozglue/tests/moz.build
toolkit/xre/CmdLineAndEnvUtils.h
toolkit/xre/LauncherRegistryInfo.cpp
toolkit/xre/LauncherRegistryInfo.h
toolkit/xre/LauncherResult.h
toolkit/xre/moz.build
toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
toolkit/xre/test/win/moz.build
widget/windows/WinHeaderOnlyUtils.h
--- a/browser/app/winlauncher/DllBlocklistWin.cpp
+++ b/browser/app/winlauncher/DllBlocklistWin.cpp
@@ -1,18 +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/. */
 
-#include "NativeNt.h"
 #include "nsWindowsDllInterceptor.h"
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/Types.h"
 #include "mozilla/WindowsDllBlocklist.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 
 #define MOZ_LITERAL_UNICODE_STRING(s)                                        \
   {                                                                          \
     /* Length of the string in bytes, less the null terminator */            \
     sizeof(s) - sizeof(wchar_t), /* Length of the string in bytes, including \
--- a/browser/app/winlauncher/DllBlocklistWin.h
+++ b/browser/app/winlauncher/DllBlocklistWin.h
@@ -4,17 +4,17 @@
  * 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_DllBlocklistWin_h
 #define mozilla_DllBlocklistWin_h
 
 #include <windows.h>
 
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 
 namespace mozilla {
 
 LauncherVoidResult InitializeDllBlocklistOOP(HANDLE aChildProcess);
 
 }  // namespace mozilla
 
 #endif  // mozilla_DllBlocklistWin_h
--- a/browser/app/winlauncher/ErrorHandler.cpp
+++ b/browser/app/winlauncher/ErrorHandler.cpp
@@ -1,21 +1,27 @@
 /* -*- 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 "ErrorHandler.h"
 
+#include "mozilla/LauncherRegistryInfo.h"
+
 namespace mozilla {
 
 void HandleLauncherError(const LauncherError& aError) {
   // This is a placeholder error handler. We'll add telemetry and a fallback
   // error log in future revisions.
+
+  LauncherRegistryInfo regInfo;
+  regInfo.DisableDueToFailure();
+
   WindowsError::UniqueString msg = aError.mError.AsString();
   if (!msg) {
     return;
   }
 
   ::MessageBoxW(nullptr, msg.get(), L"Firefox", MB_OK | MB_ICONERROR);
 }
 
--- a/browser/app/winlauncher/ErrorHandler.h
+++ b/browser/app/winlauncher/ErrorHandler.h
@@ -3,18 +3,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 https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_ErrorHandler_h
 #define mozilla_ErrorHandler_h
 
 #include "mozilla/Assertions.h"
-
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 
 namespace mozilla {
 
 /**
  * All launcher process error handling should live in the implementation of
  * this function.
  */
 void HandleLauncherError(const LauncherError& aError);
--- a/browser/app/winlauncher/LaunchUnelevated.cpp
+++ b/browser/app/winlauncher/LaunchUnelevated.cpp
@@ -3,22 +3,21 @@
 /* This Source Code Form is subject to the terms of 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 "LaunchUnelevated.h"
 
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/mscom/COMApartmentRegion.h"
 #include "mozilla/RefPtr.h"
 #include "nsWindowsHelpers.h"
 
-#include "LauncherResult.h"
-
 // For _bstr_t and _variant_t
 #include <comdef.h>
 #include <comutil.h>
 
 #include <windows.h>
 #include <exdisp.h>
 #include <objbase.h>
 #include <servprov.h>
--- a/browser/app/winlauncher/LaunchUnelevated.h
+++ b/browser/app/winlauncher/LaunchUnelevated.h
@@ -3,17 +3,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 https://mozilla.org/MPL/2.0/. */
 
 #ifndef mozilla_LaunchUnelevated_h
 #define mozilla_LaunchUnelevated_h
 
 #include "LauncherProcessWin.h"
-#include "LauncherResult.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/Maybe.h"
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 enum class ElevationState {
   eNormalUser = 0,
   eElevated = (1 << 0),
--- a/browser/app/winlauncher/LauncherProcessWin.cpp
+++ b/browser/app/winlauncher/LauncherProcessWin.cpp
@@ -8,42 +8,43 @@
 
 #include <io.h>  // For printf_stderr
 #include <string.h>
 
 #include "mozilla/Attributes.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/DebugOnly.h"
 #include "mozilla/DynamicallyLinkedFunctionPtr.h"
+#include "mozilla/LauncherRegistryInfo.h"
+#include "mozilla/LauncherResult.h"
 #include "mozilla/Maybe.h"
 #include "mozilla/SafeMode.h"
 #include "mozilla/Sprintf.h"  // For printf_stderr
 #include "mozilla/UniquePtr.h"
 #include "mozilla/WindowsVersion.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
 #include <windows.h>
 #include <processthreadsapi.h>
 
 #include "DllBlocklistWin.h"
 #include "ErrorHandler.h"
-#include "LauncherResult.h"
 #include "LaunchUnelevated.h"
 #include "ProcThreadAttributes.h"
 
 #if defined(MOZ_LAUNCHER_PROCESS)
 #include "SameBinary.h"
 #endif  // defined(MOZ_LAUNCHER_PROCESS)
 
 /**
  * At this point the child process has been created in a suspended state. Any
  * additional startup work (eg, blocklist setup) should go here.
  *
- * @return true if browser startup should proceed, otherwise false.
+ * @return Ok if browser startup should proceed
  */
 static mozilla::LauncherVoidResult PostCreationSetup(HANDLE aChildProcess,
                                                      HANDLE aChildMainThread,
                                                      const bool aIsSafeMode) {
   // The launcher process's DLL blocking code is incompatible with ASAN because
   // it is able to execute before ASAN itself has even initialized.
   // Also, the AArch64 build doesn't yet have a working interceptor.
 #if defined(MOZ_ASAN) || defined(_M_ARM64)
@@ -139,49 +140,77 @@ static void MaybeBreakForBrowserDebuggin
   }
 
   DWORD pauseLenMs = wcstoul(pauseLenS, nullptr, 10) * 1000;
   printf_stderr("\n\nBROWSERBROWSERBROWSERBROWSER\n  debug me @ %lu\n\n",
                 ::GetCurrentProcessId());
   ::Sleep(pauseLenMs);
 }
 
-namespace mozilla {
-
-bool RunAsLauncherProcess(int& argc, wchar_t** argv) {
+static bool DoLauncherProcessChecks(int& argc, wchar_t** argv) {
   // NB: We run all tests in this function instead of returning early in order
   // to ensure that all side effects take place, such as clearing environment
   // variables.
   bool result = false;
 
 #if defined(MOZ_LAUNCHER_PROCESS)
-  LauncherResult<bool> isSame = IsSameBinaryAsParentProcess();
+  mozilla::LauncherResult<bool> isSame = mozilla::IsSameBinaryAsParentProcess();
   if (isSame.isOk()) {
     result = !isSame.unwrap();
   } else {
     HandleLauncherError(isSame.unwrapErr());
   }
 #endif  // defined(MOZ_LAUNCHER_PROCESS)
 
   if (mozilla::EnvHasValue("MOZ_LAUNCHER_PROCESS")) {
     mozilla::SaveToEnv("MOZ_LAUNCHER_PROCESS=");
     result = true;
   }
 
-  result |=
-      CheckArg(argc, argv, L"launcher", static_cast<const wchar_t**>(nullptr),
-               CheckArgFlag::RemoveArg) == ARG_FOUND;
+  result |= mozilla::CheckArg(
+                argc, argv, L"launcher", static_cast<const wchar_t**>(nullptr),
+                mozilla::CheckArgFlag::RemoveArg) == mozilla::ARG_FOUND;
+
+  return result;
+}
+
+namespace mozilla {
+
+bool RunAsLauncherProcess(int& argc, wchar_t** argv) {
+  LauncherRegistryInfo::ProcessType desiredType =
+      DoLauncherProcessChecks(argc, argv)
+          ? LauncherRegistryInfo::ProcessType::Launcher
+          : LauncherRegistryInfo::ProcessType::Browser;
 
-  if (!result) {
+  // If we're looking at browser, return fast when we're a child process.
+  if (desiredType == LauncherRegistryInfo::ProcessType::Browser &&
+      mozilla::CheckArg(argc, argv, L"contentproc",
+                        static_cast<const wchar_t**>(nullptr),
+                        mozilla::CheckArgFlag::None) == mozilla::ARG_FOUND) {
+    return false;
+  }
+
+  LauncherRegistryInfo regInfo;
+  LauncherResult<LauncherRegistryInfo::ProcessType> runAsType =
+      regInfo.Check(desiredType);
+
+  if (runAsType.isErr()) {
+    HandleLauncherError(runAsType);
+    // If there is an error, we should always fall back to returning false
+    // for safety's sake.
+    return false;
+  }
+
+  if (runAsType.unwrap() == LauncherRegistryInfo::ProcessType::Browser) {
     // In this case, we will be proceeding to run as the browser.
     // We should check MOZ_DEBUG_BROWSER_* env vars.
     MaybeBreakForBrowserDebugging();
   }
 
-  return result;
+  return runAsType.unwrap() == LauncherRegistryInfo::ProcessType::Launcher;
 }
 
 int LauncherMain(int argc, wchar_t* argv[]) {
   // Make sure that the launcher process itself has image load policies set
   if (IsWin10AnniversaryUpdateOrLater()) {
     const DynamicallyLinkedFunctionPtr<decltype(&SetProcessMitigationPolicy)>
         pSetProcessMitigationPolicy(L"kernel32.dll",
                                     "SetProcessMitigationPolicy");
--- a/browser/app/winlauncher/SameBinary.h
+++ b/browser/app/winlauncher/SameBinary.h
@@ -2,18 +2,18 @@
 /* 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_SameBinary_h
 #define mozilla_SameBinary_h
 
-#include "LauncherResult.h"
-#include "NativeNt.h"
+#include "mozilla/LauncherResult.h"
+#include "mozilla/NativeNt.h"
 #include "nsWindowsHelpers.h"
 
 namespace mozilla {
 
 static inline mozilla::LauncherResult<bool> IsSameBinaryAsParentProcess() {
   mozilla::LauncherResult<DWORD> parentPid = mozilla::nt::GetParentProcessId();
   if (parentPid.isErr()) {
     return LAUNCHER_ERROR_FROM_RESULT(parentPid);
--- a/browser/app/winlauncher/moz.build
+++ b/browser/app/winlauncher/moz.build
@@ -4,28 +4,32 @@
 # License, v. 2.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('winlauncher')
 
 FORCE_STATIC_LIB = True
 
 UNIFIED_SOURCES += [
+    '/toolkit/xre/LauncherRegistryInfo.cpp',
     'DllBlocklistWin.cpp',
     'ErrorHandler.cpp',
     'LauncherProcessWin.cpp',
     'LaunchUnelevated.cpp',
 ]
 
 OS_LIBS += [
     'ntdll',
     'oleaut32',
     'ole32',
 ]
 
 TEST_DIRS += [
     'test',
 ]
 
+for var in ('MOZ_APP_BASENAME', 'MOZ_APP_VENDOR'):
+    DEFINES[var] = '"%s"' % CONFIG[var]
+
 DisableStlWrapping()
 
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
--- a/browser/app/winlauncher/test/TestSameBinary.cpp
+++ b/browser/app/winlauncher/test/TestSameBinary.cpp
@@ -1,21 +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 https://mozilla.org/MPL/2.0/. */
 
-#include "NativeNt.h"
 #include "SameBinary.h"
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Assertions.h"
 #include "mozilla/CmdLineAndEnvUtils.h"
 #include "mozilla/Move.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/Unused.h"
 #include "mozilla/Vector.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 #include "nsWindowsHelpers.h"
 
 #include <stdio.h>
 #include <stdlib.h>
 
--- a/browser/app/winlauncher/test/moz.build
+++ b/browser/app/winlauncher/test/moz.build
@@ -3,17 +3,16 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 DisableStlWrapping()
 
 GeckoCppUnitTests(
     [
-        'TestNativeNt',
         'TestSameBinary',
     ],
     linkage=None
 )
 
 LOCAL_INCLUDES += [
     '/browser/app/winlauncher',
 ]
rename from browser/app/winlauncher/NativeNt.h
rename to mozglue/misc/NativeNt.h
--- a/browser/app/winlauncher/NativeNt.h
+++ b/mozglue/misc/NativeNt.h
@@ -2,30 +2,29 @@
 /* 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_NativeNt_h
 #define mozilla_NativeNt_h
 
-#if defined(MOZILLA_INTERNAL_API)
-#error \
-    "This header is for initial process initialization. You don't want to be including this here."
-#endif  // defined(MOZILLA_INTERNAL_API)
-
 #include <stdint.h>
 #include <windows.h>
 #include <winnt.h>
 #include <winternl.h>
 
 #include "mozilla/ArrayUtils.h"
 #include "mozilla/Attributes.h"
+#include "mozilla/LauncherResult.h"
 
-#include "LauncherResult.h"
+// The declarations within this #if block are intended to be used for initial
+// process initialization ONLY. You probably don't want to be using these in
+// normal Gecko code!
+#if !defined(MOZILLA_INTERNAL_API)
 
 extern "C" {
 
 #if !defined(STATUS_ACCESS_DENIED)
 #define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)
 #endif  // !defined(STATUS_ACCESS_DENIED)
 
 #if !defined(STATUS_DLL_NOT_FOUND)
@@ -75,19 +74,23 @@ PVOID NTAPI RtlReAllocateHeap(PVOID aHea
 BOOLEAN NTAPI RtlFreeHeap(PVOID aHeapHandle, ULONG aFlags, PVOID aHeapBase);
 
 VOID NTAPI RtlAcquireSRWLockExclusive(PSRWLOCK aLock);
 
 VOID NTAPI RtlReleaseSRWLockExclusive(PSRWLOCK aLock);
 
 }  // extern "C"
 
+#endif  // !defined(MOZILLA_INTERNAL_API)
+
 namespace mozilla {
 namespace nt {
 
+#if !defined(MOZILLA_INTERNAL_API)
+
 struct MemorySectionNameBuf : public _MEMORY_SECTION_NAME {
   MemorySectionNameBuf() {
     mSectionFileName.Length = 0;
     mSectionFileName.MaximumLength = sizeof(mBuf);
     mSectionFileName.Buffer = mBuf;
   }
 
   WCHAR mBuf[MAX_PATH];
@@ -197,16 +200,18 @@ inline void GetLeafName(PUNICODE_STRING 
 
   // At this point, either cur points to the final backslash, or it points to
   // buf - 1. Either way, we're interested in cur + 1 as the desired buffer.
   aDestString->Buffer = cur + 1;
   aDestString->Length = (end - aDestString->Buffer + 1) * sizeof(WCHAR);
   aDestString->MaximumLength = aDestString->Length;
 }
 
+#endif  // !defined(MOZILLA_INTERNAL_API)
+
 inline char EnsureLowerCaseASCII(char aChar) {
   if (aChar >= 'A' && aChar <= 'Z') {
     aChar -= 'A' - 'a';
   }
 
   return aChar;
 }
 
--- a/mozglue/misc/moz.build
+++ b/mozglue/misc/moz.build
@@ -33,16 +33,17 @@ OS_LIBS += CONFIG['REALTIME_LIBS']
 DEFINES['IMPL_MFBT'] = True
 
 if CONFIG['OS_ARCH'] == 'WINNT':
     EXPORTS += [
         'nsWindowsDllInterceptor.h',
     ]
     EXPORTS.mozilla += [
         'DynamicallyLinkedFunctionPtr.h',
+        'NativeNt.h',
         'WindowsMapRemoteView.h',
     ]
     EXPORTS.mozilla.interceptor += [
         'interceptor/MMPolicies.h',
         'interceptor/PatcherBase.h',
         'interceptor/PatcherDetour.h',
         'interceptor/PatcherNopSpace.h',
         'interceptor/TargetFunction.h',
rename from browser/app/winlauncher/test/TestNativeNt.cpp
rename to mozglue/tests/TestNativeNt.cpp
--- a/browser/app/winlauncher/test/TestNativeNt.cpp
+++ b/mozglue/tests/TestNativeNt.cpp
@@ -1,15 +1,15 @@
 /* -*- 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 "NativeNt.h"
+#include "mozilla/NativeNt.h"
 #include "mozilla/UniquePtr.h"
 
 #include <stdio.h>
 #include <windows.h>
 
 const wchar_t kNormal[] = L"Foo.dll";
 const wchar_t kHex12[] = L"Foo.ABCDEF012345.dll";
 const wchar_t kHex15[] = L"ABCDEF012345678.dll";
--- a/mozglue/tests/moz.build
+++ b/mozglue/tests/moz.build
@@ -10,12 +10,19 @@ GeckoCppUnitTests([
     'ShowSSEConfig',
 ], linkage=None)
 
 CppUnitTests([
     'TestPrintf',
 ])
 
 if CONFIG['OS_ARCH'] == 'WINNT':
+    GeckoCppUnitTests([
+        'TestNativeNt',
+    ], linkage=None)
     TEST_DIRS += [
         'interceptor',
         'gtest',
     ]
+    OS_LIBS += [
+        'mincore',
+        'ntdll',
+    ]
--- a/toolkit/xre/CmdLineAndEnvUtils.h
+++ b/toolkit/xre/CmdLineAndEnvUtils.h
@@ -354,47 +354,56 @@ inline UniquePtr<wchar_t[]> MakeCommandL
     }
   }
 
   *c = '\0';
 
   return s;
 }
 
-inline bool SetArgv0ToFullBinaryPath(wchar_t* aArgv[]) {
-  if (!aArgv) {
-    return false;
-  }
-
+inline UniquePtr<wchar_t[]> GetFullBinaryPath() {
   DWORD bufLen = MAX_PATH;
   mozilla::UniquePtr<wchar_t[]> buf;
   DWORD retLen;
 
   while (true) {
     buf = mozilla::MakeUnique<wchar_t[]>(bufLen);
     retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
     if (!retLen) {
-      return false;
+      return nullptr;
     }
 
     if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
       bufLen *= 2;
       continue;
     }
 
     break;
   }
 
   // Upon success, retLen *excludes* the null character
   ++retLen;
 
   // Since we're likely to have a bunch of unused space in buf, let's reallocate
   // a string to the actual size of the file name.
-  auto newArgv_0 = mozilla::MakeUnique<wchar_t[]>(retLen);
-  if (wcscpy_s(newArgv_0.get(), retLen, buf.get())) {
+  auto result = mozilla::MakeUnique<wchar_t[]>(retLen);
+  if (wcscpy_s(result.get(), retLen, buf.get())) {
+    return nullptr;
+  }
+
+  return result;
+}
+
+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;
 }
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.cpp
@@ -0,0 +1,409 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#include "LauncherRegistryInfo.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/Assertions.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/NativeNt.h"
+
+#include <string>
+
+#define EXPAND_STRING_MACRO2(t) t
+#define EXPAND_STRING_MACRO(t) EXPAND_STRING_MACRO2(t)
+
+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";
+
+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<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  // Always delete the launcher timestamp
+  LauncherResult<bool> clearedLauncherTimestamp =
+      ClearStartTimestamp(ProcessType::Launcher);
+  MOZ_ASSERT(clearedLauncherTimestamp.isOk());
+  if (clearedLauncherTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedLauncherTimestamp);
+  }
+
+  if (!aEnable) {
+    // Set the browser timestamp to 0 to indicate force-disabled
+    return WriteStartTimestamp(ProcessType::Browser, Some(0ULL));
+  }
+
+  // Otherwise we delete the browser timestamp to start over fresh
+  LauncherResult<bool> clearedBrowserTimestamp =
+      ClearStartTimestamp(ProcessType::Browser);
+  MOZ_ASSERT(clearedBrowserTimestamp.isOk());
+  if (clearedBrowserTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedBrowserTimestamp);
+  }
+
+  return Ok();
+}
+
+LauncherResult<LauncherRegistryInfo::ProcessType> LauncherRegistryInfo::Check(
+    const ProcessType aDesiredType) {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  LauncherResult<DWORD> ourImageTimestamp = GetCurrentImageTimestamp();
+  if (ourImageTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(ourImageTimestamp);
+  }
+
+  LauncherResult<DWORD> savedImageTimestamp = GetSavedImageTimestamp();
+  if (savedImageTimestamp.isErr() &&
+      savedImageTimestamp.unwrapErr() !=
+          WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return LAUNCHER_ERROR_FROM_RESULT(savedImageTimestamp);
+  }
+
+  // If we don't have a saved timestamp, or we do but it doesn't match with
+  // our current timestamp, clear previous values.
+  if (savedImageTimestamp.isErr() ||
+      savedImageTimestamp.unwrap() != ourImageTimestamp.unwrap()) {
+    LauncherVoidResult clearResult = ClearStartTimestamps();
+    if (clearResult.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(clearResult);
+    }
+
+    LauncherVoidResult writeResult =
+        WriteImageTimestamp(ourImageTimestamp.unwrap());
+    if (writeResult.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(writeResult);
+    }
+  }
+
+  ProcessType typeToRunAs = aDesiredType;
+
+  if (disposition.unwrap() == Disposition::CreatedNew ||
+      aDesiredType == ProcessType::Browser) {
+    // No existing values to check, or we're going to be running as the browser
+    // process: just write our timestamp and return
+    LauncherVoidResult wroteTimestamp = WriteStartTimestamp(typeToRunAs);
+    if (wroteTimestamp.isErr()) {
+      return LAUNCHER_ERROR_FROM_RESULT(wroteTimestamp);
+    }
+
+    return typeToRunAs;
+  }
+
+  LauncherResult<uint64_t> lastLauncherTimestamp =
+      GetStartTimestamp(ProcessType::Launcher);
+  bool haveLauncherTs = lastLauncherTimestamp.isOk();
+
+  LauncherResult<uint64_t> lastBrowserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+  bool haveBrowserTs = lastBrowserTimestamp.isOk();
+
+  if (haveLauncherTs != haveBrowserTs) {
+    // 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 (haveLauncherTs) {
+    // 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.unwrap() < lastBrowserTimestamp.unwrap();
+      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
+  }
+
+  LauncherVoidResult wroteTimestamp = Ok();
+
+  if (typeToRunAs == ProcessType::Browser && 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.
+    wroteTimestamp = WriteStartTimestamp(typeToRunAs, Some(0ULL));
+  } else {
+    wroteTimestamp = WriteStartTimestamp(typeToRunAs);
+  }
+
+  if (wroteTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(wroteTimestamp);
+  }
+
+  return typeToRunAs;
+}
+
+LauncherVoidResult LauncherRegistryInfo::DisableDueToFailure() {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  return WriteStartTimestamp(ProcessType::Browser, Some(0ULL));
+}
+
+LauncherResult<LauncherRegistryInfo::EnabledState>
+LauncherRegistryInfo::IsEnabled() {
+  LauncherResult<Disposition> disposition = Open();
+  if (disposition.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(disposition);
+  }
+
+  LauncherResult<uint64_t> launcherTimestamp =
+      GetStartTimestamp(ProcessType::Launcher);
+
+  LauncherResult<uint64_t> browserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+
+  // In this function, we'll explictly search for the ForceDisabled and
+  // Enabled conditions. Everything else is FailDisabled.
+
+  bool isBrowserTimestampZero =
+      browserTimestamp.isOk() && browserTimestamp.unwrap() == 0ULL;
+
+  if (isBrowserTimestampZero && launcherTimestamp.isErr()) {
+    return EnabledState::ForceDisabled;
+  }
+
+  if (launcherTimestamp.isOk() && browserTimestamp.isOk() &&
+      launcherTimestamp.unwrap() < browserTimestamp.unwrap()) {
+    return EnabledState::Enabled;
+  }
+
+  if (launcherTimestamp.isErr() && browserTimestamp.isErr()) {
+    return EnabledState::Enabled;
+  }
+
+  return EnabledState::FailDisabled;
+}
+
+LauncherResult<std::wstring> LauncherRegistryInfo::ResolveValueName(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  if (aProcessType == ProcessType::Launcher) {
+    if (mLauncherValueName.empty()) {
+      mLauncherValueName.assign(mBinPath);
+      mLauncherValueName.append(kLauncherSuffix,
+                                ArrayLength(kLauncherSuffix) - 1);
+    }
+
+    return mLauncherValueName;
+  } else if (aProcessType == ProcessType::Browser) {
+    if (mBrowserValueName.empty()) {
+      mBrowserValueName.assign(mBinPath);
+      mBrowserValueName.append(kBrowserSuffix, ArrayLength(kBrowserSuffix) - 1);
+    }
+
+    return mBrowserValueName;
+  }
+
+  return LAUNCHER_ERROR_GENERIC();
+}
+
+std::wstring LauncherRegistryInfo::ResolveImageTimestampValueName() {
+  if (mImageValueName.empty()) {
+    mImageValueName.assign(mBinPath);
+    mImageValueName.append(kImageTimestampSuffix,
+                           ArrayLength(kImageTimestampSuffix) - 1);
+  }
+
+  return mImageValueName;
+}
+
+LauncherVoidResult LauncherRegistryInfo::WriteStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType, Maybe<uint64_t> aValue) {
+  LauncherResult<std::wstring> name = ResolveValueName(aProcessType);
+  if (name.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(name);
+  }
+
+  ULARGE_INTEGER timestamp;
+  if (aValue.isSome()) {
+    timestamp.QuadPart = aValue.value();
+  } else {
+    // We need to use QPC here because millisecond granularity is too coarse
+    // to properly measure the times involved.
+    if (!::QueryPerformanceCounter(
+            reinterpret_cast<LARGE_INTEGER*>(&timestamp))) {
+      return LAUNCHER_ERROR_FROM_LAST();
+    }
+  }
+
+  DWORD len = sizeof(timestamp);
+  LSTATUS result =
+      ::RegSetValueExW(mRegKey.get(), name.unwrap().c_str(), 0, REG_QWORD,
+                       reinterpret_cast<PBYTE>(&timestamp), len);
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  return Ok();
+}
+
+LauncherResult<DWORD> LauncherRegistryInfo::GetCurrentImageTimestamp() {
+  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;
+}
+
+LauncherVoidResult LauncherRegistryInfo::WriteImageTimestamp(DWORD aTimestamp) {
+  std::wstring imageTimestampValueName = ResolveImageTimestampValueName();
+
+  DWORD len = sizeof(aTimestamp);
+  LSTATUS result =
+      ::RegSetValueExW(mRegKey.get(), imageTimestampValueName.c_str(), 0,
+                       REG_DWORD, reinterpret_cast<PBYTE>(&aTimestamp), len);
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  return Ok();
+}
+
+LauncherResult<bool> LauncherRegistryInfo::ClearStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  LauncherResult<std::wstring> timestampName = ResolveValueName(aProcessType);
+  if (timestampName.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(timestampName);
+  }
+
+  LSTATUS result =
+      ::RegDeleteValueW(mRegKey.get(), timestampName.unwrap().c_str());
+  if (result == ERROR_SUCCESS) {
+    return true;
+  }
+
+  if (result == ERROR_FILE_NOT_FOUND) {
+    return false;
+  }
+
+  return LAUNCHER_ERROR_FROM_WIN32(result);
+}
+
+LauncherVoidResult LauncherRegistryInfo::ClearStartTimestamps() {
+  LauncherResult<uint64_t> lastBrowserTimestamp =
+      GetStartTimestamp(ProcessType::Browser);
+  if (lastBrowserTimestamp.isOk() && lastBrowserTimestamp.unwrap() == 0ULL) {
+    // Only proceed when the browser timestamp is non-zero
+    return Ok();
+  }
+
+  LauncherResult<bool> clearedLauncherTimestamp =
+      ClearStartTimestamp(ProcessType::Launcher);
+  if (clearedLauncherTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedLauncherTimestamp);
+  }
+
+  LauncherResult<bool> clearedBrowserTimestamp =
+      ClearStartTimestamp(ProcessType::Browser);
+  if (clearedBrowserTimestamp.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(clearedBrowserTimestamp);
+  }
+
+  return Ok();
+}
+
+LauncherResult<DWORD> LauncherRegistryInfo::GetSavedImageTimestamp() {
+  std::wstring imageTimestampValueName = ResolveImageTimestampValueName();
+
+  DWORD value;
+  DWORD valueLen = sizeof(value);
+  DWORD type;
+  LSTATUS result = ::RegQueryValueExW(
+      mRegKey.get(), imageTimestampValueName.c_str(), nullptr, &type,
+      reinterpret_cast<PBYTE>(&value), &valueLen);
+  // NB: If the value does not exist, result == ERROR_FILE_NOT_FOUND
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  if (type != REG_DWORD) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+  }
+
+  return value;
+}
+
+LauncherResult<uint64_t> LauncherRegistryInfo::GetStartTimestamp(
+    LauncherRegistryInfo::ProcessType aProcessType) {
+  LauncherResult<std::wstring> name = ResolveValueName(aProcessType);
+  if (name.isErr()) {
+    return LAUNCHER_ERROR_FROM_RESULT(name);
+  }
+
+  uint64_t value;
+  DWORD valueLen = sizeof(value);
+  DWORD type;
+  LSTATUS result =
+      ::RegQueryValueExW(mRegKey.get(), name.unwrap().c_str(), nullptr, &type,
+                         reinterpret_cast<PBYTE>(&value), &valueLen);
+  // NB: If the value does not exist, result == ERROR_FILE_NOT_FOUND
+  if (result != ERROR_SUCCESS) {
+    return LAUNCHER_ERROR_FROM_WIN32(result);
+  }
+
+  if (type != REG_QWORD) {
+    return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+  }
+
+  return value;
+}
+
+}  // namespace mozilla
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/LauncherRegistryInfo.h
@@ -0,0 +1,74 @@
+/* -*- 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/CmdLineAndEnvUtils.h"
+#include "mozilla/LauncherResult.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/UniquePtr.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,
+  };
+
+  LauncherRegistryInfo() : mBinPath(GetFullBinaryPath().get()) {}
+
+  LauncherVoidResult ReflectPrefToRegistry(const bool aEnable);
+  LauncherResult<EnabledState> IsEnabled();
+  LauncherResult<ProcessType> Check(const ProcessType aDesiredType);
+  LauncherVoidResult DisableDueToFailure();
+
+ private:
+  enum class Disposition { CreatedNew, OpenedExisting };
+
+ private:
+  LauncherResult<Disposition> Open();
+  LauncherVoidResult WriteStartTimestamp(ProcessType aProcessType,
+                                         Maybe<uint64_t> aValue = Nothing());
+  LauncherResult<DWORD> GetCurrentImageTimestamp();
+  LauncherVoidResult WriteImageTimestamp(DWORD aTimestamp);
+  LauncherResult<bool> ClearStartTimestamp(ProcessType aProcessType);
+  LauncherVoidResult ClearStartTimestamps();
+  LauncherResult<DWORD> GetSavedImageTimestamp();
+  LauncherResult<uint64_t> GetStartTimestamp(ProcessType aProcessType);
+
+  LauncherResult<std::wstring> ResolveValueName(ProcessType aProcessType);
+  std::wstring ResolveImageTimestampValueName();
+
+ private:
+  nsAutoRegKey mRegKey;
+  std::wstring mBinPath;
+  std::wstring mImageValueName;
+  std::wstring mBrowserValueName;
+  std::wstring mLauncherValueName;
+
+  static const wchar_t kLauncherSubKeyPath[];
+  static const wchar_t kLauncherSuffix[];
+  static const wchar_t kBrowserSuffix[];
+  static const wchar_t kImageTimestampSuffix[];
+};
+
+}  // namespace mozilla
+
+#endif  // mozilla_LauncherRegistryInfo_h
rename from browser/app/winlauncher/LauncherResult.h
rename to toolkit/xre/LauncherResult.h
--- a/browser/app/winlauncher/LauncherResult.h
+++ b/toolkit/xre/LauncherResult.h
@@ -7,54 +7,95 @@
 #ifndef mozilla_LauncherResult_h
 #define mozilla_LauncherResult_h
 
 #include "mozilla/Result.h"
 #include "mozilla/WinHeaderOnlyUtils.h"
 
 namespace mozilla {
 
+#if defined(MOZILLA_INTERNAL_API)
+
+template <typename T>
+using LauncherResult = WindowsErrorResult<T>;
+
+#else
+
 struct LauncherError {
   LauncherError(const char* aFile, int aLine, WindowsError aWin32Error)
       : mFile(aFile), mLine(aLine), mError(aWin32Error) {}
 
   const char* mFile;
   int mLine;
   WindowsError mError;
+
+  bool operator==(const LauncherError& aOther) const {
+    return mError == aOther.mError;
+  }
+
+  bool operator!=(const LauncherError& aOther) const {
+    return mError != aOther.mError;
+  }
+
+  bool operator==(const WindowsError& aOther) const { return mError == aOther; }
+
+  bool operator!=(const WindowsError& aOther) const { return mError != aOther; }
 };
 
 template <typename T>
 using LauncherResult = Result<T, LauncherError>;
 
-using LauncherVoidResult = Result<Ok, LauncherError>;
+#endif  // defined(MOZILLA_INTERNAL_API)
+
+using LauncherVoidResult = LauncherResult<Ok>;
 
 }  // namespace mozilla
 
+#if defined(MOZILLA_INTERNAL_API)
+
+#define LAUNCHER_ERROR_GENERIC() \
+  ::mozilla::Err(::mozilla::WindowsError::CreateGeneric())
+
+#define LAUNCHER_ERROR_FROM_WIN32(err) \
+  ::mozilla::Err(::mozilla::WindowsError::FromWin32Error(err))
+
+#define LAUNCHER_ERROR_FROM_LAST() \
+  ::mozilla::Err(::mozilla::WindowsError::FromLastError())
+
+#define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
+  ::mozilla::Err(::mozilla::WindowsError::FromNtStatus(ntstatus))
+
+#define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
+  ::mozilla::Err(::mozilla::WindowsError::FromHResult(hresult))
+
+#else
+
 #define LAUNCHER_ERROR_GENERIC()           \
   ::mozilla::Err(::mozilla::LauncherError( \
       __FILE__, __LINE__, ::mozilla::WindowsError::CreateGeneric()))
 
 #define LAUNCHER_ERROR_FROM_WIN32(err)     \
   ::mozilla::Err(::mozilla::LauncherError( \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromWin32Error(err)))
 
 #define LAUNCHER_ERROR_FROM_LAST()         \
   ::mozilla::Err(::mozilla::LauncherError( \
-      __FILE__, __LINE__,                  \
-      ::mozilla::WindowsError::FromWin32Error(::GetLastError())))
+      __FILE__, __LINE__, ::mozilla::WindowsError::FromLastError()))
 
 #define LAUNCHER_ERROR_FROM_NTSTATUS(ntstatus) \
   ::mozilla::Err(::mozilla::LauncherError(     \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromNtStatus(ntstatus)))
 
 #define LAUNCHER_ERROR_FROM_HRESULT(hresult) \
   ::mozilla::Err(::mozilla::LauncherError(   \
       __FILE__, __LINE__, ::mozilla::WindowsError::FromHResult(hresult)))
 
+// This macro wraps the supplied WindowsError with a LauncherError
+#define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
+  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
+
+#endif  // defined(MOZILLA_INTERNAL_API)
+
 // This macro enables moving of a mozilla::LauncherError from a
 // mozilla::LauncherResult<Foo> into a mozilla::LauncherResult<Bar>
 #define LAUNCHER_ERROR_FROM_RESULT(result) ::mozilla::Err(result.unwrapErr())
 
-// This macro wraps the supplied WindowsError with a LauncherError
-#define LAUNCHER_ERROR_FROM_MOZ_WINDOWS_ERROR(err) \
-  ::mozilla::Err(::mozilla::LauncherError(__FILE__, __LINE__, err))
-
 #endif  // mozilla_LauncherResult_h
--- a/toolkit/xre/moz.build
+++ b/toolkit/xre/moz.build
@@ -54,16 +54,24 @@ if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'wind
         'ModuleVersionInfo_windows.cpp',
         'nsNativeAppSupportWin.cpp',
         'WinDllServices.cpp',
     ]
     DEFINES['PROXY_PRINTING'] = 1
     LOCAL_INCLUDES += [
         '../components/printingui',
     ]
+    if CONFIG['MOZ_LAUNCHER_PROCESS']:
+        EXPORTS.mozilla += [
+          'LauncherRegistryInfo.h',
+          'LauncherResult.h',
+        ]
+        UNIFIED_SOURCES += [
+          'LauncherRegistryInfo.cpp',
+        ]
 elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'cocoa':
     UNIFIED_SOURCES += [
         'MacApplicationDelegate.mm',
         'MacAutoreleasePool.mm',
         'MacLaunchHelper.mm',
         'nsCommandLineServiceMac.cpp',
         'nsNativeAppSupportCocoa.mm',
         'updaterfileutils_osx.mm',
@@ -151,17 +159,18 @@ if CONFIG['MOZ_PDF_PRINTING']:
 include('/ipc/chromium/chromium-config.mozbuild')
 
 FINAL_LIBRARY = 'xul'
 
 if CONFIG['MOZ_X11']:
     DEFINES['USE_GLX_TEST'] = True
 
 for var in ('MOZ_APP_NAME', 'MOZ_APP_BASENAME', 'MOZ_APP_DISPLAYNAME',
-            'MOZ_APP_VERSION', 'OS_TARGET', 'MOZ_WIDGET_TOOLKIT'):
+            'MOZ_APP_VENDOR', 'MOZ_APP_VERSION', 'OS_TARGET',
+            'MOZ_WIDGET_TOOLKIT'):
     DEFINES[var] = '"%s"' % CONFIG[var]
 
 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
new file mode 100644
--- /dev/null
+++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
@@ -0,0 +1,691 @@
+/* -*- 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/LauncherRegistryInfo.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "CmdLineAndEnvUtils.h"
+#include "nsWindowsHelpers.h"
+
+#include "LauncherRegistryInfo.cpp"
+
+#include <string>
+
+const char kFailFmt[] = "TEST-FAILED | LauncherRegistryInfo | %s | %S\n";
+
+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 std::wstring gBrowserValue;
+static std::wstring gLauncherValue;
+static std::wstring gImageValue;
+
+static DWORD gMyImageTimestamp;
+
+using QWordResult = mozilla::Result<DWORD64, mozilla::WindowsError>;
+using DWordResult = mozilla::Result<DWORD, mozilla::WindowsError>;
+using VoidResult = mozilla::Result<mozilla::Ok, mozilla::WindowsError>;
+
+#define RUN_TEST(fn)                                        \
+  if ((vr = fn()).isErr()) {                                \
+    printf(kFailFmt, #fn, vr.unwrapErr().AsString().get()); \
+    return 1;                                               \
+  }
+
+static QWordResult GetBrowserTimestamp() {
+  DWORD64 qword;
+  DWORD size = sizeof(qword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gBrowserValue.c_str(),
+                     RRF_RT_QWORD, nullptr, &qword, &size);
+  if (status == ERROR_SUCCESS) {
+    return qword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetBrowserTimestamp(DWORD64 aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gBrowserValue.c_str(),
+                        REG_QWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteBrowserTimestamp() {
+  LSTATUS status = ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
+                                        gBrowserValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static QWordResult GetLauncherTimestamp() {
+  DWORD64 qword;
+  DWORD size = sizeof(qword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gLauncherValue.c_str(),
+                     RRF_RT_QWORD, nullptr, &qword, &size);
+  if (status == ERROR_SUCCESS) {
+    return qword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetLauncherTimestamp(DWORD64 aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gLauncherValue.c_str(),
+                        REG_QWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteLauncherTimestamp() {
+  LSTATUS status = ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
+                                        gLauncherValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static DWordResult GetImageTimestamp() {
+  DWORD dword;
+  DWORD size = sizeof(dword);
+  LSTATUS status =
+      ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str(),
+                     RRF_RT_DWORD, nullptr, &dword, &size);
+  if (status == ERROR_SUCCESS) {
+    return dword;
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult SetImageTimestamp(DWORD aTimestamp) {
+  LSTATUS status =
+      ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str(),
+                        REG_DWORD, &aTimestamp, sizeof(aTimestamp));
+  if (status == ERROR_SUCCESS) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteImageTimestamp() {
+  LSTATUS status =
+      ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, gImageValue.c_str());
+  if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+    return mozilla::Ok();
+  }
+
+  return mozilla::Err(mozilla::WindowsError::FromWin32Error(status));
+}
+
+static VoidResult DeleteAllTimestamps() {
+  VoidResult vr = DeleteBrowserTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  return DeleteImageTimestamp();
+}
+
+static VoidResult SetupEnabledScenario() {
+  // Reset the registry state to an enabled state. First, we delete all existing
+  // timestamps (if any).
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  // Now we run Check(Launcher)...
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Launcher) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // ...and Check(Browser)
+  result = info.Check(mozilla::LauncherRegistryInfo::ProcessType::Browser);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  // By this point we are considered to be fully enabled.
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestEmptyRegistry() {
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Launcher) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // LauncherRegistryInfo should have created Launcher and Image values
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isErr()) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  DWordResult imageTs = GetImageTimestamp();
+  if (imageTs.isErr()) {
+    return mozilla::Err(imageTs.unwrapErr());
+  }
+
+  if (imageTs.unwrap() != gMyImageTimestamp) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (browserTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  return mozilla::Ok();
+}
+
+// This test depends on the side effects from having just run TestEmptyRegistry
+static VoidResult TestNormal() {
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Browser);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isErr()) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isErr()) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  if (browserTs.unwrap() < launcherTs.unwrap()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::Enabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+// This test depends on the side effects from having just run TestNormal
+static VoidResult TestBrowserNoLauncher() {
+  VoidResult vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // Verify that we still don't have a launcher timestamp
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  if (launcherTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  // Verify that the browser timestamp is now zero
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isErr()) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  if (browserTs.unwrap() != 0ULL) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestLauncherNoBrowser() {
+  VoidResult vr = DeleteAllTimestamps();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetLauncherTimestamp(0x77777777);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetImageTimestamp(gMyImageTimestamp);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::FailDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestBrowserLessThanLauncher() {
+  VoidResult vr = SetLauncherTimestamp(0x77777777);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetBrowserTimestamp(0ULL);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::FailDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestImageTimestampChange() {
+  // This should reset the timestamps and then essentially run like
+  // TestEmptyRegistry
+  VoidResult vr = SetImageTimestamp(0x12345678);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetLauncherTimestamp(1ULL);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetBrowserTimestamp(2ULL);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Launcher) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isErr()) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  DWordResult imageTs = GetImageTimestamp();
+  if (imageTs.isErr()) {
+    return mozilla::Err(imageTs.unwrapErr());
+  }
+
+  if (imageTs.unwrap() != gMyImageTimestamp) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (browserTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestImageTimestampChangeWhenDisabled() {
+  VoidResult vr = SetImageTimestamp(0x12345678);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  vr = SetBrowserTimestamp(0ULL);
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> result =
+      info.Check(mozilla::LauncherRegistryInfo::ProcessType::Launcher);
+  if (result.isErr()) {
+    return mozilla::Err(result.unwrapErr().mError);
+  }
+
+  if (result.unwrap() != mozilla::LauncherRegistryInfo::ProcessType::Browser) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestDisableDueToFailure() {
+  VoidResult vr = SetupEnabledScenario();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  // Check that we are indeed enabled.
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::Enabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // Now call DisableDueToFailure
+  mozilla::LauncherVoidResult lvr = info.DisableDueToFailure();
+  if (lvr.isErr()) {
+    return mozilla::Err(lvr.unwrapErr().mError);
+  }
+
+  // We should now be FailDisabled
+  enabled = info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::FailDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // If we delete the launcher timestamp, IsEnabled should then return
+  // ForceDisabled.
+  vr = DeleteLauncherTimestamp();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  enabled = info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  return mozilla::Ok();
+}
+
+static VoidResult TestPrefReflection() {
+  // Reset the registry to a known good state.
+  VoidResult vr = SetupEnabledScenario();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  // First, test to see what happens when the pref is set to ON.
+  mozilla::LauncherRegistryInfo info;
+  mozilla::LauncherVoidResult reflectOk = info.ReflectPrefToRegistry(true);
+  if (reflectOk.isErr()) {
+    return mozilla::Err(reflectOk.unwrapErr().mError);
+  }
+
+  // Launcher and browser timestamps should be non-existent.
+  QWordResult launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (launcherTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  QWordResult browserTs = GetBrowserTimestamp();
+  if (browserTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (browserTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  // IsEnabled should give us Enabled.
+  mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> enabled =
+      info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::Enabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // Okay, so far so good. Let's reset the board and see what happens when we
+  // flip the pref to OFF.
+  vr = SetupEnabledScenario();
+  if (vr.isErr()) {
+    return vr;
+  }
+
+  reflectOk = info.ReflectPrefToRegistry(false);
+  if (reflectOk.isErr()) {
+    return mozilla::Err(reflectOk.unwrapErr().mError);
+  }
+
+  // Launcher timestamp should be non-existent.
+  launcherTs = GetLauncherTimestamp();
+  if (launcherTs.isOk()) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_UNEXPECTED));
+  }
+
+  if (launcherTs.unwrapErr() !=
+      mozilla::WindowsError::FromWin32Error(ERROR_FILE_NOT_FOUND)) {
+    return mozilla::Err(launcherTs.unwrapErr());
+  }
+
+  // Browser timestamp should be zero
+  browserTs = GetBrowserTimestamp();
+  if (browserTs.isErr()) {
+    return mozilla::Err(browserTs.unwrapErr());
+  }
+
+  if (browserTs.unwrap() != 0ULL) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  // IsEnabled should give us ForceDisabled
+  enabled = info.IsEnabled();
+  if (enabled.isErr()) {
+    return mozilla::Err(enabled.unwrapErr().mError);
+  }
+
+  if (enabled.unwrap() !=
+      mozilla::LauncherRegistryInfo::EnabledState::ForceDisabled) {
+    return mozilla::Err(mozilla::WindowsError::FromHResult(E_FAIL));
+  }
+
+  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;
+
+  mozilla::nt::PEHeaders myHeaders(::GetModuleHandleW(nullptr));
+  if (!myHeaders) {
+    return 1;
+  }
+
+  if (!myHeaders.GetTimeStamp(gMyImageTimestamp)) {
+    return 1;
+  }
+
+  auto onExit = mozilla::MakeScopeExit([] { DeleteAllTimestamps(); });
+
+  VoidResult vr = mozilla::Ok();
+
+  RUN_TEST(TestEmptyRegistry);
+  RUN_TEST(TestNormal);
+  RUN_TEST(TestBrowserNoLauncher);
+  RUN_TEST(TestLauncherNoBrowser);
+  RUN_TEST(TestBrowserLessThanLauncher);
+  RUN_TEST(TestImageTimestampChange);
+  RUN_TEST(TestImageTimestampChangeWhenDisabled);
+  RUN_TEST(TestDisableDueToFailure);
+  RUN_TEST(TestPrefReflection);
+
+  return 0;
+}
--- a/toolkit/xre/test/win/moz.build
+++ b/toolkit/xre/test/win/moz.build
@@ -1,17 +1,21 @@
 # -*- 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/.
 
-SimplePrograms([
-    'TestXREMakeCommandLineWin',
-])
+GeckoCppUnitTests(
+    [
+        'TestLauncherRegistryInfo',
+        'TestXREMakeCommandLineWin',
+    ],
+    linkage=None
+)
 
 DEFINES['NS_NO_XPCOM'] = True
 
 LOCAL_INCLUDES += [
     '/config',
     '/toolkit/xre',
 ]
 
@@ -19,10 +23,14 @@ DisableStlWrapping()
 USE_STATIC_LIBS = True
 
 OS_LIBS += [
     'comctl32',
     'shell32',
     'ws2_32',
 ]
 
+# Needed for TestLauncherRegistryInfo
+for var in ('MOZ_APP_BASENAME', 'MOZ_APP_VENDOR'):
+    DEFINES[var] = '"%s"' % CONFIG[var]
+
 if CONFIG['CC_TYPE'] == 'clang-cl':
     AllowCompilerWarnings()  # workaround for bug 1090497
--- a/widget/windows/WinHeaderOnlyUtils.h
+++ b/widget/windows/WinHeaderOnlyUtils.h
@@ -144,16 +144,24 @@ class WindowsError final {
     // HRESULT_FACILITY and HRESULT_CODE macros.
     if (mHResult & FACILITY_NT_BIT) {
       return Some(static_cast<NTSTATUS>(mHResult & ~FACILITY_NT_BIT));
     }
 
     return Nothing();
   }
 
+  bool operator==(const WindowsError& aOther) const {
+    return mHResult == aOther.mHResult;
+  }
+
+  bool operator!=(const WindowsError& aOther) const {
+    return mHResult != aOther.mHResult;
+  }
+
   static DWORD NtStatusToWin32Error(NTSTATUS aNtStatus) {
     static const DynamicallyLinkedFunctionPtr<decltype(&RtlNtStatusToDosError)>
         pRtlNtStatusToDosError(L"ntdll.dll", "RtlNtStatusToDosError");
 
     MOZ_ASSERT(!!pRtlNtStatusToDosError);
     if (!pRtlNtStatusToDosError) {
       return ERROR_UNIDENTIFIED_ERROR;
     }