summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/test/win
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-07 09:22:09 +0000
commit43a97878ce14b72f0981164f87f2e35e14151312 (patch)
tree620249daf56c0258faa40cbdcf9cfba06de2a846 /toolkit/xre/test/win
parentInitial commit. (diff)
downloadfirefox-upstream.tar.xz
firefox-upstream.zip
Adding upstream version 110.0.1.upstream/110.0.1upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'toolkit/xre/test/win')
-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
7 files changed, 1231 insertions, 0 deletions
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]