summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/test
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
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 '')
-rw-r--r--toolkit/xre/test/browser.ini4
-rw-r--r--toolkit/xre/test/browser_checkdllblockliststate.js16
-rw-r--r--toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp227
-rw-r--r--toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp372
-rw-r--r--toolkit/xre/test/gtest/TestCompatVersionCompare.cpp53
-rw-r--r--toolkit/xre/test/gtest/TestGeckoArgs.cpp179
-rw-r--r--toolkit/xre/test/gtest/moz.build24
-rw-r--r--toolkit/xre/test/marionette/gen_win32k_tests.py212
-rw-r--r--toolkit/xre/test/marionette/marionette.ini6
-rw-r--r--toolkit/xre/test/marionette/test_exitcode.py31
-rw-r--r--toolkit/xre/test/marionette/test_fission_autostart.py410
-rw-r--r--toolkit/xre/test/marionette/test_win32k_enrollment.py2506
-rw-r--r--toolkit/xre/test/marionette/test_win32k_enrollment.template.py204
-rw-r--r--toolkit/xre/test/marionette/win32k_tests.txt167
-rw-r--r--toolkit/xre/test/mochitest.ini3
-rw-r--r--toolkit/xre/test/show_hash.js4
-rw-r--r--toolkit/xre/test/test_fpuhandler.html36
-rw-r--r--toolkit/xre/test/test_install_hash.js138
-rw-r--r--toolkit/xre/test/test_launch_without_hang.js256
-rw-r--r--toolkit/xre/test/win/Makefile.in11
-rw-r--r--toolkit/xre/test/win/TestLauncherRegistryInfo.cpp783
-rw-r--r--toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp266
-rw-r--r--toolkit/xre/test/win/TestXREMakeCommandLineWin.ini94
-rw-r--r--toolkit/xre/test/win/mochitest/browser_env_path_long.ini7
-rw-r--r--toolkit/xre/test/win/mochitest/browser_env_path_long.js15
-rw-r--r--toolkit/xre/test/win/moz.build55
-rw-r--r--toolkit/xre/test/xpcshell.ini20
27 files changed, 6099 insertions, 0 deletions
diff --git a/toolkit/xre/test/browser.ini b/toolkit/xre/test/browser.ini
new file mode 100644
index 0000000000..7f6eaced59
--- /dev/null
+++ b/toolkit/xre/test/browser.ini
@@ -0,0 +1,4 @@
+[DEFAULT]
+
+[browser_checkdllblockliststate.js]
+skip-if = os != "win" || ccov # Bug 1531789
diff --git a/toolkit/xre/test/browser_checkdllblockliststate.js b/toolkit/xre/test/browser_checkdllblockliststate.js
new file mode 100644
index 0000000000..0054e0a8de
--- /dev/null
+++ b/toolkit/xre/test/browser_checkdllblockliststate.js
@@ -0,0 +1,16 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Tests that the dll blocklist initializes correctly during test runs.
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function(
+ browser
+ ) {
+ ok(
+ Services.appinfo.windowsDLLBlocklistStatus,
+ "Windows dll blocklist status should be true, indicating it is " +
+ "running properly. A failure in this test is considered a " +
+ "release blocker."
+ );
+ });
+});
diff --git a/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
new file mode 100644
index 0000000000..e36c62e0db
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestAssembleCommandLineWin.cpp
@@ -0,0 +1,227 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/AssembleCmdLine.h"
+#include "mozilla/CmdLineAndEnvUtils.h"
+#include "mozilla/gtest/MozAssertions.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "WinRemoteMessage.h"
+
+using namespace mozilla;
+
+template <typename T>
+struct TestCase {
+ const T* mArgs[4];
+ const wchar_t* mExpected;
+};
+
+#define ALPHA_IN_UTF8 "\xe3\x82\xa2\xe3\x83\xab\xe3\x83\x95\xe3\x82\xa1"
+#define OMEGA_IN_UTF8 "\xe3\x82\xaa\xe3\x83\xa1\xe3\x82\xac"
+#define ALPHA_IN_UTF16 L"\u30A2\u30EB\u30D5\u30A1"
+#define OMEGA_IN_UTF16 L"\u30AA\u30E1\u30AC"
+#define UPPER_CYRILLIC_P_IN_UTF8 "\xd0\xa0"
+#define LOWER_CYRILLIC_P_IN_UTF8 "\xd1\x80"
+#define UPPER_CYRILLIC_P_IN_UTF16 L"\u0420"
+#define LOWER_CYRILLIC_P_IN_UTF16 L"\u0440"
+
+TestCase<char> testCases[] = {
+ // Copied from TestXREMakeCommandLineWin.ini
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", nullptr}, L"a:\\\""},
+ {{"a:\\b c", nullptr}, L"\"a:\\b c\""},
+ {{"a:\\b c\"", nullptr}, L"\"a:\\b c\\\"\""},
+ {{"a:\\b c\\d e", nullptr}, L"\"a:\\b c\\d e\""},
+ {{"a:\\b c\\d e\"", nullptr}, L"\"a:\\b c\\d e\\\"\""},
+ {{"a:\\", nullptr}, L"a:\\"},
+ {{"a:\"", "b:\\c d", nullptr}, L"a:\\\" \"b:\\c d\""},
+ {{"a", "b:\" c:\\d", "e", nullptr}, L"a \"b:\\\" c:\\d\" e"},
+ {{"abc", "d", "e", nullptr}, L"abc d e"},
+ {{"a b c", "d", "e", nullptr}, L"\"a b c\" d e"},
+ {{"a\\\\\\b", "de fg", "h", nullptr}, L"a\\\\\\b \"de fg\" h"},
+ {{"a", "b", nullptr}, L"a b"},
+ {{"a\tb", nullptr}, L"\"a\tb\""},
+ {{"a\\\"b", "c", "d", nullptr}, L"a\\\\\\\"b c d"},
+ {{"a\\\"b", "c", nullptr}, L"a\\\\\\\"b c"},
+ {{"a\\\\\\b c", nullptr}, L"\"a\\\\\\b c\""},
+ {{"\"a", nullptr}, L"\\\"a"},
+ {{"\\a", nullptr}, L"\\a"},
+ {{"\\\\\\a", nullptr}, L"\\\\\\a"},
+ {{"\\\\\\\"a", nullptr}, L"\\\\\\\\\\\\\\\"a"},
+ {{"a\\\"b c\" d e", nullptr}, L"\"a\\\\\\\"b c\\\" d e\""},
+ {{"a\\\\\"b", "c d e", nullptr}, L"a\\\\\\\\\\\"b \"c d e\""},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8, OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d"},
+ {{"a:\\b", "c\\" ALPHA_IN_UTF8 " " OMEGA_IN_UTF8 "\\d", nullptr},
+ L"a:\\b \"c\\" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\\d\""},
+ {{ALPHA_IN_UTF8, OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16},
+
+ // More single-argument cases
+ {{"a\fb", nullptr}, L"\"a\fb\""},
+ {{"a\nb", nullptr}, L"\"a\nb\""},
+ {{"a\rb", nullptr}, L"\"a\rb\""},
+ {{"a\vb", nullptr}, L"\"a\vb\""},
+ {{"\"a\" \"b\"", nullptr}, L"\"\\\"a\\\" \\\"b\\\"\""},
+ {{"\"a\\b\" \"c\\d\"", nullptr}, L"\"\\\"a\\b\\\" \\\"c\\d\\\"\""},
+ {{"\\\\ \\\\", nullptr}, L"\"\\\\ \\\\\\\\\""},
+ {{"\"\" \"\"", nullptr}, L"\"\\\"\\\" \\\"\\\"\""},
+ {{ALPHA_IN_UTF8 "\\" OMEGA_IN_UTF8, nullptr},
+ ALPHA_IN_UTF16 L"\\" OMEGA_IN_UTF16},
+ {{ALPHA_IN_UTF8 " " OMEGA_IN_UTF8, nullptr},
+ L"\"" ALPHA_IN_UTF16 L" " OMEGA_IN_UTF16 L"\""},
+
+ // Empty string cases
+ {{"", nullptr}, L"\"\""},
+ {{"foo", "", nullptr}, L"foo \"\""},
+ {{"", "bar", nullptr}, L"\"\" bar"},
+ {{"foo", "", "bar", nullptr}, L"foo \"\" bar"},
+};
+
+TEST(AssembleCommandLineWin, assembleCmdLine)
+{
+ for (const auto& testCase : testCases) {
+ UniqueFreePtr<wchar_t> assembled;
+ wchar_t* assembledRaw = nullptr;
+ EXPECT_EQ(assembleCmdLine(testCase.mArgs, &assembledRaw, CP_UTF8), 0);
+ assembled.reset(assembledRaw);
+
+ EXPECT_STREQ(assembled.get(), testCase.mExpected);
+ }
+}
+
+TEST(CommandLineParserWin, HandleCommandLine)
+{
+ CommandLineParserWin<char> parser;
+ for (const auto& testCase : testCases) {
+ NS_ConvertUTF16toUTF8 utf8(testCase.mExpected);
+ parser.HandleCommandLine(utf8);
+
+ if (utf8.Length() == 0) {
+ EXPECT_EQ(parser.Argc(), 0);
+ continue;
+ }
+
+ for (int i = 0; i < parser.Argc(); ++i) {
+ EXPECT_NE(testCase.mArgs[i], nullptr);
+ EXPECT_STREQ(parser.Argv()[i], testCase.mArgs[i]);
+ }
+ EXPECT_EQ(testCase.mArgs[parser.Argc()], nullptr);
+ }
+}
+
+TEST(WinRemoteMessage, SendReceive)
+{
+ const char kCommandline[] =
+ "dummy.exe /arg1 --arg2 \"3rd arg\" "
+ "4th=\"" UPPER_CYRILLIC_P_IN_UTF8 " " LOWER_CYRILLIC_P_IN_UTF8 "\"";
+ const wchar_t kCommandlineW[] =
+ L"dummy.exe /arg1 --arg2 \"3rd arg\" "
+ L"4th=\"" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16 L"\"";
+ const wchar_t* kExpectedArgsW[] = {
+ L"-arg1", L"-arg2", L"3rd arg",
+ L"4th=" UPPER_CYRILLIC_P_IN_UTF16 L" " LOWER_CYRILLIC_P_IN_UTF16};
+
+ char workingDirA[MAX_PATH];
+ wchar_t workingDirW[MAX_PATH];
+ EXPECT_NE(getcwd(workingDirA, MAX_PATH), nullptr);
+ EXPECT_NE(_wgetcwd(workingDirW, MAX_PATH), nullptr);
+
+ WinRemoteMessageSender v0(kCommandline);
+ WinRemoteMessageSender v1(kCommandline, workingDirA);
+ WinRemoteMessageSender v2(kCommandlineW, workingDirW);
+
+ WinRemoteMessageReceiver receiver;
+ int32_t len;
+ nsAutoString arg;
+ nsCOMPtr<nsIFile> workingDir;
+
+ receiver.Parse(v0.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ receiver.Parse(v1.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+
+ receiver.Parse(v2.CopyData());
+ EXPECT_NS_SUCCEEDED(receiver.CommandLineRunner()->GetLength(&len));
+ EXPECT_EQ(static_cast<size_t>(len), ArrayLength(kExpectedArgsW));
+ for (size_t i = 0; i < ArrayLength(kExpectedArgsW); ++i) {
+ EXPECT_TRUE(
+ NS_SUCCEEDED(receiver.CommandLineRunner()->GetArgument(i, arg)));
+ EXPECT_STREQ(arg.get(), kExpectedArgsW[i]);
+ }
+ EXPECT_TRUE(NS_SUCCEEDED(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir))));
+ EXPECT_NS_SUCCEEDED(workingDir->GetPath(arg));
+ EXPECT_STREQ(arg.get(), workingDirW);
+}
+
+TEST(WinRemoteMessage, NonNullTerminatedBuffer)
+{
+ // Reserve two pages and commit the first one
+ const uint32_t kPageSize = 4096;
+ UniquePtr<void, VirtualFreeDeleter> pages(
+ ::VirtualAlloc(nullptr, kPageSize * 2, MEM_RESERVE, PAGE_NOACCESS));
+ EXPECT_TRUE(pages);
+ EXPECT_TRUE(
+ ::VirtualAlloc(pages.get(), kPageSize, MEM_COMMIT, PAGE_READWRITE));
+
+ // Test strings with lengths between 0 and |kMaxBufferSize| bytes
+ const int kMaxBufferSize = 10;
+
+ // Set a string just before the boundary between the two pages.
+ uint8_t* bufferEnd = reinterpret_cast<uint8_t*>(pages.get()) + kPageSize;
+ memset(bufferEnd - kMaxBufferSize, '$', kMaxBufferSize);
+
+ nsCOMPtr<nsIFile> workingDir;
+ COPYDATASTRUCT copyData = {};
+ for (int i = 0; i < kMaxBufferSize; ++i) {
+ WinRemoteMessageReceiver receiver;
+
+ copyData.cbData = i;
+ copyData.lpData = bufferEnd - i;
+
+ copyData.dwData =
+ static_cast<ULONG_PTR>(WinRemoteMessageVersion::CommandLineOnly);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDir);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+
+ copyData.dwData = static_cast<ULONG_PTR>(
+ WinRemoteMessageVersion::CommandLineAndWorkingDirInUtf16);
+ EXPECT_NS_SUCCEEDED(receiver.Parse(&copyData));
+ EXPECT_EQ(receiver.CommandLineRunner()->GetWorkingDirectory(
+ getter_AddRefs(workingDir)),
+ NS_ERROR_NOT_INITIALIZED);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
new file mode 100644
index 0000000000..a5f8a19a9c
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCmdLineAndEnvUtils.cpp
@@ -0,0 +1,372 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <memory>
+#include <ostream>
+#include <string>
+#include <string_view>
+#include <type_traits>
+#include <vector>
+
+#include "gtest/gtest.h"
+
+#include "mozilla/CmdLineAndEnvUtils.h"
+
+// Insulate these test-classes from the outside world.
+//
+// (In particular, distinguish this `CommandLine` from the one in ipc/chromium.
+// The compiler has no issues here, but the MSVC debugger gets somewhat
+// confused.)
+namespace testzilla {
+
+// Auxiliary class to simplify the declaration of test-cases for
+// EnsureCommandlineSafe.
+class CommandLine {
+ const size_t size;
+ std::vector<std::string> _data_8;
+ std::vector<std::wstring> _data_16;
+ // _should_ be const all the way through, but...
+ char const** argv_8;
+ wchar_t const** argv_16;
+
+ public:
+ inline int argc() const { return (int)(unsigned int)(size); }
+
+ template <typename CharT>
+ CharT const** argv() const;
+
+ CommandLine(CommandLine const&) = delete;
+ CommandLine(CommandLine&& that) = delete;
+
+ template <typename Container>
+ explicit CommandLine(Container const& container)
+ : size{std::size(container) + 1}, // plus one for argv[0]
+ argv_8(new const char*[size + 1]), // plus one again for argv[argc]
+ argv_16(new const wchar_t*[size + 1]) {
+ _data_8.reserve(size + 1);
+ _data_16.reserve(size + 1);
+ size_t pos = 0;
+
+ const auto append = [&](const char* val) {
+ size_t const len = ::strlen(val);
+ _data_8.emplace_back(val, val + len);
+ _data_16.emplace_back(val, val + len);
+ argv_8[pos] = _data_8.back().data();
+ argv_16[pos] = _data_16.back().data();
+ ++pos;
+ };
+
+ append("GECKOAPP.COM"); // argv[0] may be anything
+ for (const char* item : container) {
+ append(item);
+ }
+ assert(pos == size);
+
+ // C99 §5.1.2.2.1 states "argv[argc] shall be a null pointer", and
+ // `CheckArg` implicitly relies on this.
+ argv_8[pos] = nullptr;
+ argv_16[pos] = nullptr;
+ }
+
+ // debug output
+ friend std::ostream& operator<<(std::ostream& o, CommandLine const& cl) {
+ for (const auto& item : cl._data_8) {
+ o << '"';
+ for (const char c : item) {
+ if (c == '"') {
+ o << "\\\"";
+ } else {
+ o << c;
+ }
+ }
+ o << "\", ";
+ }
+ return o;
+ }
+};
+
+template <>
+char const** CommandLine::argv<char>() const {
+ return argv_8;
+}
+template <>
+wchar_t const** CommandLine::argv<wchar_t>() const {
+ return argv_16;
+}
+
+enum TestCaseState : bool {
+ FAIL = false,
+ PASS = true,
+};
+constexpr TestCaseState operator!(TestCaseState s) {
+ return TestCaseState(!bool(s));
+}
+
+#ifdef XP_WIN
+constexpr static const TestCaseState WIN_ONLY = PASS;
+#else
+constexpr static const TestCaseState WIN_ONLY = FAIL;
+#endif
+
+using NarrowTestCase = std::pair<const char*, const char*>;
+constexpr std::pair<TestCaseState, NarrowTestCase> kStrMatches8[] = {
+ {PASS, {"", ""}},
+
+ {PASS, {"i", "i"}},
+ {PASS, {"i", "I"}},
+
+ {FAIL, {"", "i"}},
+ {FAIL, {"i", ""}},
+ {FAIL, {"i", "j"}},
+
+ {PASS, {"mozilla", "mozilla"}},
+ {PASS, {"mozilla", "Mozilla"}},
+ {PASS, {"mozilla", "MOZILLA"}},
+ {PASS, {"mozilla", "mOZILLA"}},
+ {PASS, {"mozilla", "mOZIlLa"}},
+ {PASS, {"mozilla", "MoZiLlA"}},
+
+ {FAIL, {"mozilla", ""}},
+ {FAIL, {"mozilla", "mozill"}},
+ {FAIL, {"mozilla", "mozillo"}},
+ {FAIL, {"mozilla", "mozillam"}},
+ {FAIL, {"mozilla", "mozilla-"}},
+ {FAIL, {"mozilla", "-mozilla"}},
+ {FAIL, {"-mozilla", "mozilla"}},
+
+ // numbers are permissible
+ {PASS, {"m0zi11a", "m0zi11a"}},
+ {PASS, {"m0zi11a", "M0ZI11A"}},
+ {PASS, {"m0zi11a", "m0Zi11A"}},
+
+ {FAIL, {"mozilla", "m0zi11a"}},
+ {FAIL, {"m0zi11a", "mozilla"}},
+
+ // capital letters are not accepted in the left comparand
+ {FAIL, {"I", "i"}},
+ {FAIL, {"I", "i"}},
+ {FAIL, {"Mozilla", "mozilla"}},
+ {FAIL, {"Mozilla", "Mozilla"}},
+
+ // punctuation other than `-` is rejected
+ {FAIL, {"*", "*"}},
+ {FAIL, {"*", "word"}},
+ {FAIL, {".", "."}},
+ {FAIL, {".", "a"}},
+ {FAIL, {"_", "_"}},
+
+ // spaces are rejected
+ {FAIL, {" ", " "}},
+ {FAIL, {"two words", "two words"}},
+
+ // non-ASCII characters are rejected
+ //
+ // (the contents of this test case may differ depending on the source and
+ // execution character sets, but the result should always be failure)
+ {FAIL, {"à", "a"}},
+ {FAIL, {"a", "à"}},
+ {FAIL, {"à", "à"}},
+};
+
+#ifdef XP_WIN
+using WideTestCase = std::pair<const char*, const wchar_t*>;
+std::pair<TestCaseState, WideTestCase> kStrMatches16[] = {
+ // (Turkish 'İ' may lowercase to 'i' in some locales, but
+ // we explicitly prevent that from being relevant)
+ {FAIL, {"i", L"İ"}},
+ {FAIL, {"mozilla", L"ṁozilla"}},
+};
+#endif
+
+constexpr const char* kRequiredArgs[] = {"aleph", "beth"};
+
+std::pair<TestCaseState, std::vector<const char*>> const kCommandLines[] = {
+ // the basic admissible forms
+ {PASS, {"-osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-beth", "http://www.example.com/"}},
+
+ // GNU-style double-dashes are also allowed
+ {PASS, {"--osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "-beth", "http://www.example.com/"}},
+ {PASS, {"--osint", "--aleph", "http://www.example.com/"}},
+ {PASS, {"--osint", "--beth", "http://www.example.com/"}},
+
+ // on Windows, slashes are also permitted
+ {WIN_ONLY, {"-osint", "/aleph", "http://www.example.com/"}},
+ {WIN_ONLY, {"--osint", "/aleph", "http://www.example.com/"}},
+ // - These should pass on Windows because they're well-formed.
+ // - These should pass elsewhere because the first parameter is
+ // just a local filesystem path, not an option.
+ {PASS, {"/osint", "/aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "-aleph", "http://www.example.com/"}},
+ {PASS, {"/osint", "--aleph", "http://www.example.com/"}},
+
+ // the required argument's argument may not itself be a flag
+ {FAIL, {"-osint", "-aleph", "-anything"}},
+ {FAIL, {"-osint", "-aleph", "--anything"}},
+ // - On Windows systems this is a switch and should be rejected.
+ // - On non-Windows systems this is potentially a valid local path.
+ {!WIN_ONLY, {"-osint", "-aleph", "/anything"}},
+
+ // wrong order
+ {FAIL, {"-osint", "http://www.example.com/", "-aleph"}},
+ {FAIL, {"-aleph", "-osint", "http://www.example.com/"}},
+ {FAIL, {"-aleph", "http://www.example.com/", "-osint"}},
+ {FAIL, {"http://www.example.com/", "-osint", "-aleph"}},
+ {FAIL, {"http://www.example.com/", "-aleph", "-osint"}},
+
+ // missing arguments
+ {FAIL, {"-osint", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph"}},
+ {FAIL, {"-osint"}},
+
+ // improper arguments
+ {FAIL, {"-osint", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "", "http://www.example.com/"}},
+
+ // additional arguments
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-other-argument"}},
+ {FAIL, {"-osint", "-other-argument", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-other-argument", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-a", "-b"}},
+ {FAIL, {"-osint", "-aleph", "-a", "http://www.example.com/", "-b"}},
+ {FAIL, {"-osint", "-a", "-aleph", "http://www.example.com/", "-b"}},
+
+ // multiply-occurring otherwise-licit arguments
+ {FAIL, {"-osint", "-aleph", "-beth", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-aleph", "http://www.example.com/"}},
+
+ // if -osint is not supplied, anything goes
+ {PASS, {}},
+ {PASS, {"http://www.example.com"}},
+ {PASS, {"-aleph", "http://www.example.com/"}},
+ {PASS, {"-beth", "http://www.example.com/"}},
+ {PASS, {"-aleph", "http://www.example.com/", "-other-argument"}},
+ {PASS, {"-aleph", "-aleph", "http://www.example.com/"}},
+};
+
+constexpr static char const* const kOptionalArgs[] = {"mozilla", "allizom"};
+
+// Additional test cases for optional parameters.
+//
+// Test cases marked PASS should pass iff the above optional parameters are
+// permitted. (Test cases marked FAIL should fail regardless, and are only
+// grouped here for convenience and semantic coherence.)
+std::pair<TestCaseState, std::vector<const char*>> kCommandLinesOpt[] = {
+ // one permitted optional argument
+ {PASS, {"-osint", "-mozilla", "-aleph", "http://www.example.com/"}},
+ {PASS, {"-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+
+ // multiple permitted optional arguments
+ {PASS,
+ {"-osint", "-mozilla", "-allizom", "-aleph", "http://www.example.com/"}},
+ {PASS,
+ {"-osint", "-allizom", "-mozilla", "-aleph", "http://www.example.com/"}},
+
+ // optional arguments in the wrong place
+ {FAIL, {"-mozilla", "-osint", "-aleph", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL, {"-osint", "-aleph", "http://www.example.com/", "-mozilla"}},
+
+ // optional arguments in both right and wrong places
+ {FAIL,
+ {"-mozilla", "-osint", "-allizom", "-aleph", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "-mozilla", "http://www.example.com/"}},
+ {FAIL,
+ {"-osint", "-allizom", "-aleph", "http://www.example.com/", "-mozilla"}},
+};
+
+enum WithOptionalState : bool {
+ NoOptionalArgs = false,
+ WithOptionalArgs = true,
+};
+
+template <typename CharT>
+bool TestCommandLineImpl(CommandLine const& cl,
+ WithOptionalState withOptional) {
+ int argc = cl.argc();
+ // EnsureCommandlineSafe's signature isn't const-correct here for annoying
+ // reasons, but it is indeed non-mutating.
+ CharT** argv = const_cast<CharT**>(cl.argv<CharT>());
+ if (withOptional) {
+ return mozilla::internal::EnsureCommandlineSafeImpl(
+ argc, argv, kRequiredArgs, kOptionalArgs);
+ }
+ return mozilla::internal::EnsureCommandlineSafeImpl(argc, argv,
+ kRequiredArgs);
+}
+
+// Test that `args` produces `expectation`. On Windows, test against both
+// wide-character and narrow-character implementations.
+void TestCommandLine(TestCaseState expectation, CommandLine const& cl,
+ WithOptionalState withOptional) {
+ EXPECT_EQ(TestCommandLineImpl<char>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#ifdef XP_WIN
+ EXPECT_EQ(TestCommandLineImpl<wchar_t>(cl, withOptional), expectation)
+ << "cl is: " << cl << std::endl
+ << "withOptional is: " << bool(withOptional);
+#endif
+}
+} // namespace testzilla
+
+/***********************
+ * Test cases
+ */
+
+TEST(CmdLineAndEnvUtils, strimatch)
+{
+ using namespace testzilla;
+ using mozilla::strimatch;
+ for (auto const& [result, data] : kStrMatches8) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ <" << right << '>';
+
+#ifdef XP_WIN
+ wchar_t right_wide[200];
+ ::mbstowcs(right_wide, right, 200);
+ EXPECT_EQ(strimatch(left, right_wide), result)
+ << '<' << left << "> !~ L<" << right << '>';
+#endif
+ }
+
+#ifdef XP_WIN
+ for (auto const& [result, data] : kStrMatches16) {
+ auto const& [left, right] = data;
+ EXPECT_EQ(strimatch(left, right), result)
+ << '<' << left << "> !~ L<" << right << '>';
+ }
+#endif
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafe)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, NoOptionalArgs);
+ }
+ for (auto const& [_unused, data] : kCommandLinesOpt) {
+ MOZ_UNUSED(_unused); // silence gcc
+ CommandLine const cl(data);
+ TestCommandLine(FAIL, cl, NoOptionalArgs);
+ }
+}
+
+TEST(CmdLineAndEnvUtils, ensureSafeWithOptional)
+{
+ using namespace testzilla;
+ for (auto const& [result, data] : kCommandLines) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+ for (auto const& [result, data] : kCommandLinesOpt) {
+ CommandLine const cl(data);
+ TestCommandLine(result, cl, WithOptionalArgs);
+ }
+}
diff --git a/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
new file mode 100644
index 0000000000..bce64aacd5
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestCompatVersionCompare.cpp
@@ -0,0 +1,53 @@
+/* vim:set ts=2 sw=2 sts=2 et: */
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/
+ */
+
+#include "gtest/gtest.h"
+#include "nsAppRunner.h"
+#include "nsString.h"
+
+void CheckCompatVersionCompare(const nsCString& aOldCompatVersion,
+ const nsCString& aNewCompatVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ printf("Comparing '%s' to '%s'.\n", aOldCompatVersion.get(),
+ aNewCompatVersion.get());
+
+ int32_t result = CompareCompatVersions(aOldCompatVersion, aNewCompatVersion);
+
+ ASSERT_EQ(aExpectedSame, result == 0)
+ << "Version sameness check should match.";
+ ASSERT_EQ(aExpectedDowngrade, result > 0)
+ << "Version downgrade check should match.";
+}
+
+void CheckExpectedResult(const char* aOldAppVersion, const char* aNewAppVersion,
+ bool aExpectedSame, bool aExpectedDowngrade) {
+ nsCString oldCompatVersion;
+ BuildCompatVersion(aOldAppVersion, "", "", oldCompatVersion);
+
+ nsCString newCompatVersion;
+ BuildCompatVersion(aNewAppVersion, "", "", newCompatVersion);
+
+ CheckCompatVersionCompare(oldCompatVersion, newCompatVersion, aExpectedSame,
+ aExpectedDowngrade);
+}
+
+TEST(CompatVersionCompare, CompareVersionChange)
+{
+ // Identical
+ CheckExpectedResult("67.0", "67.0", true, false);
+
+ // Version changes
+ CheckExpectedResult("67.0", "68.0", false, false);
+ CheckExpectedResult("68.0", "67.0", false, true);
+ CheckExpectedResult("67.0", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0", true, false);
+ CheckExpectedResult("67.0.1", "67.0.1", true, false);
+ CheckExpectedResult("67.0.1", "67.0.2", true, false);
+ CheckExpectedResult("67.0.2", "67.0.1", true, false);
+
+ // Check that if the last run was safe mode then we consider this an upgrade.
+ CheckCompatVersionCompare(
+ "Safe Mode"_ns, "67.0.1_20000000000000/20000000000000"_ns, false, false);
+}
diff --git a/toolkit/xre/test/gtest/TestGeckoArgs.cpp b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
new file mode 100644
index 0000000000..42c7a41b2b
--- /dev/null
+++ b/toolkit/xre/test/gtest/TestGeckoArgs.cpp
@@ -0,0 +1,179 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "gtest/gtest.h"
+
+#include "mozilla/ArrayUtils.h"
+#include "mozilla/GeckoArgs.h"
+
+using namespace mozilla;
+using namespace mozilla::geckoargs;
+
+static CommandLineArg<const char*> kCharParam{"-charParam", "charparam"};
+static CommandLineArg<uint64_t> kUint64Param{"-Uint64Param", "uint64param"};
+static CommandLineArg<bool> kFlag{"-Flag", "flag"};
+
+template <size_t N>
+bool CheckArgv(char** aArgv, const char* const (&aExpected)[N]) {
+ for (size_t i = 0; i < N; ++i) {
+ if (aArgv[i] == nullptr && aExpected[i] == nullptr) {
+ return true;
+ }
+ if (aArgv[i] == nullptr || aExpected[i] == nullptr) {
+ return false;
+ }
+ if (strcmp(aArgv[i], aExpected[i]) != 0) {
+ return false;
+ }
+ }
+ return true;
+}
+
+char kFirefox[] = "$HOME/bin/firefox/firefox-bin";
+
+TEST(GeckoArgs, const_char_ptr)
+{
+ char kCharParamStr[] = "-charParam";
+ char kCharParamValue[] = "paramValue";
+
+ {
+ char* argv[] = {kFirefox, kCharParamStr, kCharParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isSome());
+ EXPECT_EQ(*charParam, kCharParamValue);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kBlahBlah[] = "-blahblah";
+ char* argv[] = {kFirefox, kCharParamStr, kBlahBlah, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<const char*> charParam = kCharParam.Get(argc, argv);
+ EXPECT_TRUE(charParam.isNothing());
+
+ const char* const expArgv[] = {kFirefox, kBlahBlah, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kCharParam.Put("ParamValue", extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-charParam");
+ EXPECT_EQ(extraArgs[1], "ParamValue");
+ }
+ { EXPECT_EQ(kCharParam.Name(), "-charParam"); }
+}
+
+TEST(GeckoArgs, uint64)
+{
+ char kUint64ParamStr[] = "-Uint64Param";
+
+ {
+ char* argv[] = {kFirefox, kUint64ParamStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "42";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isSome());
+ EXPECT_EQ(*uint64Param, 42U);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char kUint64ParamValue[] = "aa";
+ char* argv[] = {kFirefox, kUint64ParamStr, kUint64ParamValue, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 4);
+
+ Maybe<uint64_t> uint64Param = kUint64Param.Get(argc, argv);
+ EXPECT_TRUE(uint64Param.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kUint64Param.Put(1234, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 2U);
+ EXPECT_EQ(extraArgs[0], "-Uint64Param");
+ EXPECT_EQ(extraArgs[1], "1234");
+ }
+ { EXPECT_EQ(kUint64Param.Name(), "-Uint64Param"); }
+}
+
+TEST(GeckoArgs, bool)
+{
+ char kFlagStr[] = "-Flag";
+
+ {
+ char* argv[] = {kFirefox, kFlagStr, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 3);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isSome());
+ EXPECT_TRUE(*Flag);
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ char* argv[] = {kFirefox, nullptr};
+ int argc = ArrayLength(argv);
+ EXPECT_EQ(argc, 2);
+
+ Maybe<bool> Flag = kFlag.Get(argc, argv);
+ EXPECT_TRUE(Flag.isNothing());
+
+ const char* const expArgv[] = {kFirefox, nullptr};
+ EXPECT_TRUE(CheckArgv(argv, expArgv));
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(true, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 1U);
+ EXPECT_EQ(extraArgs[0], "-Flag");
+ }
+ {
+ std::vector<std::string> extraArgs;
+ EXPECT_EQ(extraArgs.size(), 0U);
+ kFlag.Put(false, extraArgs);
+ EXPECT_EQ(extraArgs.size(), 0U);
+ }
+ { EXPECT_EQ(kFlag.Name(), "-Flag"); }
+}
diff --git a/toolkit/xre/test/gtest/moz.build b/toolkit/xre/test/gtest/moz.build
new file mode 100644
index 0000000000..bec881fa72
--- /dev/null
+++ b/toolkit/xre/test/gtest/moz.build
@@ -0,0 +1,24 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, you can obtain one at http://mozilla.org/MPL/2.0/.
+
+Library("xretest")
+
+UNIFIED_SOURCES = [
+ "TestCmdLineAndEnvUtils.cpp",
+ "TestCompatVersionCompare.cpp",
+ "TestGeckoArgs.cpp",
+]
+
+LOCAL_INCLUDES += [
+ "/toolkit/components/remote",
+]
+
+if CONFIG["OS_TARGET"] == "WINNT":
+ UNIFIED_SOURCES += [
+ "TestAssembleCommandLineWin.cpp",
+ ]
+
+FINAL_LIBRARY = "xul-gtest"
diff --git a/toolkit/xre/test/marionette/gen_win32k_tests.py b/toolkit/xre/test/marionette/gen_win32k_tests.py
new file mode 100644
index 0000000000..e0a39776db
--- /dev/null
+++ b/toolkit/xre/test/marionette/gen_win32k_tests.py
@@ -0,0 +1,212 @@
+#!/usr/bin/env python3
+
+import re
+
+RE_DEFAULT = re.compile(r"\[D=([TF])\] ")
+RE_TRANSITION = re.compile(r"([a-zA-Z0-9 \[\]=#_-]+) *->(.*)")
+RE_ASSERTION = re.compile(
+ r"\[A S=([a-zA-Z01_]+) SS=([a-zA-Z01_]+) ES=([a-zA-Z01_]+) P=([a-zA-Z_]+) ESP=([a-zA-Z_]+)\]"
+)
+RE_ASSERTION_SHORTHAND = re.compile(r"\[A#([0-9T]+)\]")
+
+# ======================================================================
+# ======================================================================
+
+testnum = 1
+
+
+def start_test(line):
+ global testnum
+ output.write(
+ """
+ def test_{0}(self):
+ # {1}...\n""".format(
+ testnum, line[0:80]
+ )
+ )
+ testnum += 1
+
+
+def set_default(d):
+ output.write(
+ """
+ if self.default_is is not {0}:
+ return\n""".format(
+ "True" if d == "T" else "False"
+ )
+ )
+
+
+def enroll(e):
+ if e.endswith("-C"):
+ e = e[:-2]
+ output.write("\n # Re-set enrollment pref, like Normandy would do\n")
+ output.write(
+ " self.set_enrollment_status(ExperimentStatus.ENROLLED_{0})\n".format(
+ e.upper()
+ )
+ )
+
+
+def set_pref(enabled):
+ output.write(
+ "\n self.marionette.set_pref(Prefs.WIN32K, {0})\n".format(str(enabled))
+ )
+
+
+def set_e10s(enable):
+ if enable:
+ output.write(
+ """
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")\n"""
+ )
+ else:
+ raise Exception("Not implemented")
+
+
+def set_header(enable):
+ if enable:
+ output.write(
+ """
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})\n"""
+ )
+ else:
+ output.write(
+ """
+ self.set_env(ENV_DISABLE_WIN32K, "")\n"""
+ )
+
+
+def set_bad_requirements(enabled):
+ output.write(
+ """
+ self.marionette.set_pref(Prefs.WEBGL, {0})\n""".format(
+ "False" if enabled else "True"
+ )
+ )
+
+
+def restart():
+ output.write("\n self.restart()\n")
+
+
+def print_assertion(assertion):
+ if not assertion:
+ return
+ output.write(
+ """
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.{0},
+ sessionStatus=ContentWin32kLockdownState.{1},
+ experimentStatus=ExperimentStatus.{2},
+ pref={3},
+ enrollmentStatusPref=ExperimentStatus.{4},
+ )\n""".format(
+ *assertion
+ )
+ )
+
+
+# ======================================================================
+# ======================================================================
+
+TESTS = open("win32k_tests.txt", "r").readlines()
+
+output = open("test_win32k_enrollment.py", "w", newline="\n")
+header = open("test_win32k_enrollment.template.py", "r")
+for l in header:
+ output.write(l)
+
+mappings = {}
+for line in TESTS:
+ line = line.strip()
+ if not line:
+ continue
+
+ if line.startswith("#"):
+ continue
+ elif line.startswith("> "):
+ line = line[2:]
+ key, value = line.split(":")
+ mappings[key.strip()] = value.strip()
+ continue
+ elif line.startswith("-"):
+ line = line[1:].strip()
+ elif line.startswith("+"):
+ line = line[1:].strip()
+ import pdb
+
+ pdb.set_trace()
+ else:
+ raise Exception("unknown line type: " + line)
+
+ # We can't handle Safe Mode right now
+ if "Safe Mode" in line:
+ continue
+
+ # If we have no assertions defined, skip the test entirely
+ if "[A" not in line:
+ continue
+
+ if not RE_DEFAULT.match(line):
+ raise Exception("'{0}' does not match the default regex".format(line))
+ default = RE_DEFAULT.search(line).groups(1)[0]
+
+ start_test(line)
+
+ set_default(default)
+
+ line = line[6:]
+
+ while line:
+ # this is a horrible hack for the line ending to avoid making the
+ # regex more complicated and having to fix it
+ if not line.endswith(" ->"):
+ line += " ->"
+
+ groups = RE_TRANSITION.search(line).groups()
+ start = groups[0].strip()
+ end = groups[1].strip()
+
+ if RE_ASSERTION.search(start):
+ assertion = RE_ASSERTION.search(start).groups()
+ start = start[0 : start.index("[")].strip()
+ elif RE_ASSERTION_SHORTHAND.search(start):
+ key = RE_ASSERTION_SHORTHAND.search(start).groups()[0]
+ assertion = RE_ASSERTION.search(mappings[key]).groups()
+ start = start[0 : start.index("[")].strip()
+ else:
+ assertion = ""
+
+ if start == "Nothing":
+ pass
+ elif start.startswith("Enrolled "):
+ enroll(start[9:])
+ elif start == "E10S":
+ set_e10s(True)
+ elif start == "Header-On":
+ set_header(True)
+ elif start == "Header-Off":
+ set_header(False)
+ elif start == "Bad Requirements":
+ set_bad_requirements(True)
+ elif start == "Restart":
+ restart()
+ elif start == "On":
+ set_pref(True)
+ elif start == "Off":
+ set_pref(False)
+ else:
+ raise Exception("Unknown Action: " + start)
+
+ print_assertion(assertion)
+
+ line = end.strip()
+
+ if RE_ASSERTION.match(line):
+ print_assertion(RE_ASSERTION.search(line).groups())
+ elif RE_ASSERTION_SHORTHAND.search(line):
+ key = RE_ASSERTION_SHORTHAND.search(line).groups()[0]
+ print_assertion(RE_ASSERTION.search(mappings[key]).groups())
diff --git a/toolkit/xre/test/marionette/marionette.ini b/toolkit/xre/test/marionette/marionette.ini
new file mode 100644
index 0000000000..bb49d6c5de
--- /dev/null
+++ b/toolkit/xre/test/marionette/marionette.ini
@@ -0,0 +1,6 @@
+[test_fission_autostart.py]
+[test_win32k_enrollment.py]
+skip-if =
+ os != 'win'
+ ccov # Bug 1757102
+[test_exitcode.py]
diff --git a/toolkit/xre/test/marionette/test_exitcode.py b/toolkit/xre/test/marionette/test_exitcode.py
new file mode 100644
index 0000000000..4ce7306122
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_exitcode.py
@@ -0,0 +1,31 @@
+from marionette_harness import MarionetteTestCase
+
+
+class TestFissionAutostart(MarionetteTestCase):
+ def test_normal_exit(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ def call_quit():
+ self.marionette.execute_script(
+ """
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit);
+ """,
+ sandbox="system",
+ )
+
+ self.marionette.quit(callback=call_quit)
+ self.assertEqual(self.marionette.instance.runner.returncode, 0)
+
+ def test_exit_code(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ def call_quit():
+ self.marionette.execute_script(
+ """
+ Services.startup.quit(Ci.nsIAppStartup.eAttemptQuit, 5);
+ """,
+ sandbox="system",
+ )
+
+ self.marionette.quit(callback=call_quit)
+ self.assertEqual(self.marionette.instance.runner.returncode, 5)
diff --git a/toolkit/xre/test/marionette/test_fission_autostart.py b/toolkit/xre/test/marionette/test_fission_autostart.py
new file mode 100644
index 0000000000..b830c0eaca
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_fission_autostart.py
@@ -0,0 +1,410 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+class ExperimentStatus:
+ UNENROLLED = 0
+ ENROLLED_CONTROL = 1
+ ENROLLED_TREATMENT = 2
+ DISQUALIFIED = 3
+
+
+class Prefs:
+ ENROLLMENT_STATUS = "fission.experiment.enrollmentStatus"
+ STARTUP_ENROLLMENT_STATUS = "fission.experiment.startupEnrollmentStatus"
+ FISSION_AUTOSTART = "fission.autostart"
+ FISSION_AUTOSTART_SESSION = "fission.autostart.session"
+
+
+ENV_ENABLE_FISSION = "MOZ_FORCE_ENABLE_FISSION"
+ENV_DISABLE_FISSION = "MOZ_FORCE_DISABLE_FISSION"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+DECISION_STATUS = {
+ "experimentControl": 1,
+ "experimentTreatment": 2,
+ "disabledByE10sEnv": 3,
+ "enabledByEnv": 4,
+ "disabledByEnv": 5,
+ "enabledByDefault": 7,
+ "disabledByDefault": 8,
+ "enabledByUserPref": 9,
+ "disabledByUserPref": 10,
+ "disabledByE10sOther": 11,
+}
+
+
+class TestFissionAutostart(MarionetteTestCase):
+ SANDBOX_NAME = "fission-autostart"
+
+ def execute_script(self, code, *args, **kwargs):
+ with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
+ return self.marionette.execute_script(
+ code, new_sandbox=False, sandbox=self.SANDBOX_NAME, *args, **kwargs
+ )
+
+ def get_fission_status(self):
+ return self.execute_script(
+ r"""
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ return {
+ fissionAutostart: Services.appinfo.fissionAutostart,
+ fissionExperimentStatus: Services.appinfo.fissionExperimentStatus,
+ decisionStatus: Services.appinfo.fissionDecisionStatus,
+ decisionStatusString: Services.appinfo.fissionDecisionStatusString,
+ useRemoteSubframes: win.docShell.nsILoadContext.useRemoteSubframes,
+ fissionAutostartSession: Services.prefs.getBoolPref("fission.autostart.session"),
+ dynamicFissionAutostart: Services.prefs.getBoolPref("fission.autostart"),
+ };
+ """
+ )
+
+ def check_fission_status(self, enabled, experiment, decision, dynamic=None):
+ if dynamic is None:
+ dynamic = enabled
+
+ expected = {
+ "fissionAutostart": enabled,
+ "fissionExperimentStatus": experiment,
+ "decisionStatus": DECISION_STATUS[decision],
+ "decisionStatusString": decision,
+ "useRemoteSubframes": enabled,
+ "fissionAutostartSession": enabled,
+ "dynamicFissionAutostart": dynamic,
+ }
+
+ status = self.get_fission_status()
+
+ for prop, value in expected.items():
+ self.assertEqual(
+ status[prop],
+ value,
+ "%s should have the value `%r`, but has `%r`"
+ % (prop, value, status[prop]),
+ )
+
+ def set_env(self, env, value):
+ self.execute_script(
+ "env.set(arguments[0], arguments[1]);", script_args=(env, value)
+ )
+
+ def get_env(self, env):
+ return self.execute_script("return env.get(arguments[0]);", script_args=(env,))
+
+ def set_enrollment_status(self, status):
+ self.marionette.set_pref(Prefs.ENROLLMENT_STATUS, status, default_branch=True)
+
+ startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS)
+ self.assertEqual(
+ startup_status,
+ status,
+ "Startup enrollment status (%r) should match new "
+ "session status (%r)" % (startup_status, status),
+ )
+
+ def restart(self, prefs=None, env=None):
+ if prefs:
+ self.marionette.set_prefs(prefs)
+
+ if env:
+ for name, value in env.items():
+ self.set_env(name, value)
+
+ self.marionette.restart(in_app=True, clean=False)
+ self.setUpSession()
+
+ # Sanity check our environment.
+ if prefs:
+ for key, val in prefs.items():
+ if val is not None:
+ self.assertEqual(self.marionette.get_pref(key), val)
+ if env:
+ for key, val in env.items():
+ self.assertEqual(self.get_env(key), val or "")
+
+ def setUpSession(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ self.execute_script(
+ r"""
+ // We're running in a function, in a sandbox, that inherits from an
+ // X-ray wrapped window. Anything we want to be globally available
+ // needs to be defined on that window.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestFissionAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `fission.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.fissionRequired = None
+ if Prefs.FISSION_AUTOSTART in self.marionette.instance.required_prefs:
+ self.fissionRequired = self.marionette.instance.required_prefs[
+ Prefs.FISSION_AUTOSTART
+ ]
+ del self.marionette.instance.required_prefs[Prefs.FISSION_AUTOSTART]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Fission status must start out with `enabledByDefault`
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ def tearDown(self):
+ if self.fissionRequired is not None:
+ self.marionette.instance.required_prefs[
+ Prefs.FISSION_AUTOSTART
+ ] = self.fissionRequired
+ self.marionette.restart(in_app=False, clean=True)
+
+ super(TestFissionAutostart, self).tearDown()
+
+ def test_runtime_changes(self):
+ """Tests that changes to preferences during runtime do not have any
+ effect on the current session."""
+
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ self.restart(prefs={Prefs.FISSION_AUTOSTART: False})
+
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.marionette.set_pref(Prefs.FISSION_AUTOSTART, True)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ dynamic=True,
+ )
+
+ self.marionette.clear_pref(Prefs.FISSION_AUTOSTART)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ dynamic=True,
+ )
+
+ self.restart()
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.ENROLLED_CONTROL,
+ decision="experimentControl",
+ )
+
+ self.marionette.set_pref(
+ Prefs.ENROLLMENT_STATUS, ExperimentStatus.UNENROLLED, default_branch=True
+ )
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.ENROLLED_CONTROL,
+ decision="experimentControl",
+ )
+
+ self.set_env(ENV_ENABLE_FISSION, "1")
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.ENROLLED_CONTROL,
+ decision="experimentControl",
+ )
+
+ def test_fission_precedence(self):
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ self.restart(
+ prefs={Prefs.FISSION_AUTOSTART: False}, env={ENV_ENABLE_FISSION: "1"}
+ )
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByEnv",
+ dynamic=False,
+ )
+
+ self.restart(
+ prefs={Prefs.FISSION_AUTOSTART: True},
+ env={ENV_DISABLE_FISSION: "1", ENV_ENABLE_FISSION: ""},
+ )
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByEnv",
+ dynamic=True,
+ )
+
+ self.restart(
+ prefs={Prefs.FISSION_AUTOSTART: False}, env={ENV_DISABLE_FISSION: ""}
+ )
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="disabledByUserPref",
+ )
+
+ self.restart(prefs={Prefs.FISSION_AUTOSTART: None})
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+ self.restart()
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.ENROLLED_TREATMENT,
+ decision="experimentTreatment",
+ )
+
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+ self.restart()
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.ENROLLED_CONTROL,
+ decision="experimentControl",
+ )
+
+ self.marionette.set_pref(Prefs.FISSION_AUTOSTART, True)
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.ENROLLED_CONTROL,
+ decision="experimentControl",
+ dynamic=True,
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.ENROLLMENT_STATUS),
+ ExperimentStatus.DISQUALIFIED,
+ "Setting fission.autostart should disqualify",
+ )
+
+ self.restart()
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.DISQUALIFIED,
+ decision="enabledByUserPref",
+ )
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.check_fission_status(
+ enabled=False,
+ experiment=ExperimentStatus.DISQUALIFIED,
+ decision="disabledByE10sEnv",
+ dynamic=True,
+ )
+
+ def test_fission_startup(self):
+ # Starting the browser with STARTUP_ENROLLMENT_STATUS set to treatment
+ # should make the current session run under treatment.
+ with self.full_restart() as profile:
+ profile.set_preferences(
+ {
+ Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT,
+ },
+ filename="prefs.js",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.ENROLLMENT_STATUS),
+ ExperimentStatus.UNENROLLED,
+ "Dynamic pref should be unenrolled",
+ )
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS),
+ ExperimentStatus.ENROLLED_TREATMENT,
+ "Startup pref should be in treatment",
+ )
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.ENROLLED_TREATMENT,
+ decision="experimentTreatment",
+ )
+
+ # If normandy doesn't re-set `ENROLLMENT_STATUS` during the session, it
+ # should be cleared back to disabled after a restart.
+ self.marionette.restart(in_app=True, clean=False)
+
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.ENROLLMENT_STATUS),
+ ExperimentStatus.UNENROLLED,
+ "Should unenroll dynamic pref after shutdown",
+ )
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS),
+ ExperimentStatus.UNENROLLED,
+ "Should unenroll startup pref after shutdown",
+ )
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.UNENROLLED,
+ decision="enabledByDefault",
+ )
+
+ # If the browser is started with a customized `fisison.autostart`,
+ # while also enrolled in an experiment, the experiment should be
+ # disqualified at startup.
+ with self.full_restart() as profile:
+ profile.set_preferences(
+ {
+ Prefs.FISSION_AUTOSTART: True,
+ Prefs.STARTUP_ENROLLMENT_STATUS: ExperimentStatus.ENROLLED_TREATMENT,
+ },
+ filename="prefs.js",
+ )
+
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.ENROLLMENT_STATUS),
+ ExperimentStatus.DISQUALIFIED,
+ "Should disqualify dynamic pref on startup",
+ )
+ self.assertEqual(
+ self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS),
+ ExperimentStatus.DISQUALIFIED,
+ "Should disqualify startup pref on startup",
+ )
+
+ self.check_fission_status(
+ enabled=True,
+ experiment=ExperimentStatus.DISQUALIFIED,
+ decision="enabledByUserPref",
+ )
diff --git a/toolkit/xre/test/marionette/test_win32k_enrollment.py b/toolkit/xre/test/marionette/test_win32k_enrollment.py
new file mode 100644
index 0000000000..d09331319b
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_win32k_enrollment.py
@@ -0,0 +1,2506 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+class ExperimentStatus:
+ UNENROLLED = 0
+ ENROLLED_CONTROL = 1
+ ENROLLED_TREATMENT = 2
+ DISQUALIFIED = 3
+
+
+class ContentWin32kLockdownState:
+ LockdownEnabled = 1
+ MissingWebRender = 2
+ OperatingSystemNotSupported = 3
+ PrefNotSet = 4
+ MissingRemoteWebGL = 5
+ MissingNonNativeTheming = 6
+ DisabledByEnvVar = 7
+ DisabledBySafeMode = 8
+ DisabledByE10S = 9
+ DisabledByUserPref = 10
+ EnabledByUserPref = 11
+ DisabledByControlGroup = 12
+ EnabledByTreatmentGroup = 13
+ DisabledByDefault = 14
+ EnabledByDefault = 15
+
+
+class Prefs:
+ ENROLLMENT_STATUS = "security.sandbox.content.win32k-experiment.enrollmentStatus"
+ STARTUP_ENROLLMENT_STATUS = (
+ "security.sandbox.content.win32k-experiment.startupEnrollmentStatus"
+ )
+ WIN32K = "security.sandbox.content.win32k-disable"
+ WEBGL = "webgl.out-of-process"
+
+
+ENV_DISABLE_WIN32K = "MOZ_ENABLE_WIN32K"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+class TestWin32kAutostart(MarionetteTestCase):
+ SANDBOX_NAME = "win32k-autostart"
+
+ def execute_script(self, code, *args, **kwargs):
+ with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
+ return self.marionette.execute_script(
+ code, new_sandbox=False, sandbox=self.SANDBOX_NAME, *args, **kwargs
+ )
+
+ def get_win32k_status(self):
+ return self.execute_script(
+ r"""
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let ses = "security.sandbox.content.win32k-experiment.startupEnrollmentStatus";
+ return {
+ win32kSessionStatus: Services.appinfo.win32kSessionStatus,
+ win32kStatus: Services.appinfo.win32kLiveStatusTestingOnly,
+ win32kExperimentStatus: Services.appinfo.win32kExperimentStatus,
+ win32kPref: Services.prefs.getBoolPref("security.sandbox.content.win32k-disable"),
+ win32kStartupEnrollmentStatusPref: Services.prefs.getIntPref(ses),
+ };
+ """
+ )
+
+ def check_win32k_status(
+ self, status, sessionStatus, experimentStatus, pref, enrollmentStatusPref
+ ):
+ # We CANNOT check win32kEnrollmentStatusPref after a restart because we only set this
+ # pref on the default branch, and it goes away after a restart, so we only check
+ # the startupEnrollmentStatusPref
+ expected = {
+ "win32kSessionStatus": sessionStatus,
+ "win32kStatus": status,
+ "win32kExperimentStatus": experimentStatus,
+ "win32kPref": pref,
+ "win32kStartupEnrollmentStatusPref": enrollmentStatusPref,
+ }
+
+ status = self.get_win32k_status()
+
+ for prop, value in expected.items():
+ self.assertEqual(
+ status[prop],
+ value,
+ "%s should have the value `%r`, but has `%r`"
+ % (prop, value, status[prop]),
+ )
+
+ def set_env(self, env, value):
+ self.execute_script(
+ "env.set(arguments[0], arguments[1]);", script_args=(env, value)
+ )
+
+ def get_env(self, env):
+ return self.execute_script("return env.get(arguments[0]);", script_args=(env,))
+
+ def set_enrollment_status(self, status):
+ self.marionette.set_pref(Prefs.ENROLLMENT_STATUS, status, default_branch=True)
+
+ updated_status = self.marionette.get_pref(Prefs.ENROLLMENT_STATUS)
+ self.assertTrue(
+ status == updated_status or updated_status == ExperimentStatus.DISQUALIFIED
+ )
+ startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS)
+ self.assertEqual(
+ startup_status,
+ updated_status,
+ "Startup enrollment status (%r) should match "
+ "session status (%r)" % (startup_status, updated_status),
+ )
+
+ def restart(self, prefs=None, env=None):
+ if prefs:
+ self.marionette.set_prefs(prefs)
+
+ if env:
+ for name, value in env.items():
+ self.set_env(name, value)
+
+ self.marionette.restart(in_app=True, clean=False)
+ self.setUpSession()
+
+ # Sanity check our environment.
+ if prefs:
+ for key, val in prefs.items():
+ if val is not None:
+ self.assertEqual(self.marionette.get_pref(key), val)
+ if env:
+ for key, val in env.items():
+ self.assertEqual(self.get_env(key), val or "")
+
+ def setUpSession(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ self.execute_script(
+ r"""
+ // We're running in a function, in a sandbox, that inherits from an
+ // X-ray wrapped window. Anything we want to be globally available
+ // needs to be defined on that window.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestWin32kAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `win32k.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.win32kRequired = None
+ if Prefs.WIN32K in self.marionette.instance.required_prefs:
+ self.win32kRequired = self.marionette.instance.required_prefs[Prefs.WIN32K]
+ del self.marionette.instance.required_prefs[Prefs.WIN32K]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Marionette doesn't let you set preferences on the default branch before startup
+ # so we can't test the default=False and default=True scenarios in one test
+ # What we can do is generate all the tests, and then only run the runs for which
+ # the default is. (And run the other ones locally to make sure they work before
+ # we land it.)
+ prefJS = 'return Services.prefs.getBoolPref("security.sandbox.content.win32k-disable");'
+ self.default_is = self.execute_script(prefJS)
+
+ if self.default_is is False:
+ # Win32k status must start out with `disabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ else:
+ # Win32k status must start out with `enabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def tearDown(self):
+ if self.win32kRequired is not None:
+ self.marionette.instance.required_prefs[Prefs.WIN32K] = self.win32kRequired
+ self.marionette.restart(in_app=False, clean=True)
+
+ super(TestWin32kAutostart, self).tearDown()
+
+ def test_1(self):
+ # [D=F] Nothing [A#1] -> Enrolled Control [A S=DisabledByDefault SS=DisabledByDefa...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_2(self):
+ # [D=F] Nothing [A#1] -> Enrolled Treatment [A S=DisabledByDefault SS=DisabledByDe...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_3(self):
+ # [D=F] Nothing [A#1] -> On [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROL...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_4(self):
+ # [D=F] Nothing [A#1] -> Off [A#1] -> Restart [A#1]...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_5(self):
+ # [D=F] Nothing [A#1] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=Disable...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_6(self):
+ # [D=F] Nothing [A#1] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENR...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_7(self):
+ # [D=F] Nothing [A#1] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar...
+
+ if self.default_is is not False:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_8(self):
+ # [D=T] Nothing [A#1T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefau...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_9(self):
+ # [D=T] Nothing [A#1T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDef...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_10(self):
+ # [D=T] Nothing [A#1T] -> On [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLL...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_11(self):
+ # [D=T] Nothing [A#1T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENR...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_12(self):
+ # [D=T] Nothing [A#1T] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=Enable...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_13(self):
+ # [D=T] Nothing [A#1T] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNEN...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_14(self):
+ # [D=T] Nothing [A#1T] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVa...
+
+ if self.default_is is not True:
+ return
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_15(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Enrolled Control [A S=EnabledByUserPref SS=En...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_16(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Enrolled Treatment [A S=EnabledByUserPref SS=...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_17(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Off [A S=DisabledByDefault SS=EnabledByUserPr...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_18(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Bad Requirements [A S=MissingRemoteWebGL SS=E...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_19(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_20(self):
+ # [D=F] On [A#3] -> Restart [A#4] -> Header-On [A S=DisabledByEnvVar SS=DisabledBy...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_21(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Control [A S=EnabledByDefault SS=E...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_22(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Treatment [A S=EnabledByDefault SS...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_23(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Off [A S=DisabledByUserPref SS=EnabledByDef...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_24(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Bad Requirements [A S=MissingRemoteWebGL SS...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_25(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_26(self):
+ # [D=T] On [A#3T] -> Restart [A#4T] -> Header-On [A S=DisabledByEnvVar SS=Disabled...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_27(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> On [A S=E...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_28(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Off [A#6]...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_29(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Bad Requi...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_30(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> E10S [A S...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_31(self):
+ # [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Header-On...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_32(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> On [A S...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_33(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Off [A S=DisabledByUserPref S...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_34(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Bad Req...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_35(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> E10S [A...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_36(self):
+ # [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Header-...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByControlGroup,
+ sessionStatus=ContentWin32kLockdownState.DisabledByControlGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_CONTROL,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ def test_37(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> On [A...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_38(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Off [...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_39(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Bad R...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_40(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> E10S ...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_41(self):
+ # [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Heade...
+
+ if self.default_is is not False:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_42(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> On ...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_43(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Off...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByUserPref,
+ sessionStatus=ContentWin32kLockdownState.DisabledByUserPref,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_44(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Bad...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_45(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> E10...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ app_version = self.execute_script("return Services.appinfo.version")
+ self.restart(env={ENV_DISABLE_E10S: app_version})
+ self.set_env(ENV_DISABLE_E10S, "null")
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByE10S,
+ sessionStatus=ContentWin32kLockdownState.DisabledByE10S,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_46(self):
+ # [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Hea...
+
+ if self.default_is is not True:
+ return
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart(env={ENV_DISABLE_WIN32K: "1"})
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByEnvVar,
+ sessionStatus=ContentWin32kLockdownState.DisabledByEnvVar,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.set_env(ENV_DISABLE_WIN32K, "")
+
+ # Re-set enrollment pref, like Normandy would do
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ sessionStatus=ContentWin32kLockdownState.EnabledByTreatmentGroup,
+ experimentStatus=ExperimentStatus.ENROLLED_TREATMENT,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ def test_47(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Control [A S=MissingR...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_48(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Treatment [A S=Missin...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_49(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> On [A S=MissingRemoteWebGL SS=...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_50(self):
+ # [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Off [A S=MissingRemoteWebGL SS...
+
+ if self.default_is is not False:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_51(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Control [A S=Missin...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_CONTROL)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_CONTROL,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_52(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Treatment [A S=Miss...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ self.set_enrollment_status(ExperimentStatus.ENROLLED_TREATMENT)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.ENROLLED_TREATMENT,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.DISQUALIFIED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.DISQUALIFIED,
+ )
+
+ def test_53(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> On [A S=MissingRemoteWebGL S...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, True)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def test_54(self):
+ # [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Off [A S=MissingRemoteWebGL ...
+
+ if self.default_is is not True:
+ return
+
+ self.marionette.set_pref(Prefs.WEBGL, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.restart()
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ self.marionette.set_pref(Prefs.WIN32K, False)
+
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.MissingRemoteWebGL,
+ sessionStatus=ContentWin32kLockdownState.MissingRemoteWebGL,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
diff --git a/toolkit/xre/test/marionette/test_win32k_enrollment.template.py b/toolkit/xre/test/marionette/test_win32k_enrollment.template.py
new file mode 100644
index 0000000000..30dc7fed33
--- /dev/null
+++ b/toolkit/xre/test/marionette/test_win32k_enrollment.template.py
@@ -0,0 +1,204 @@
+from contextlib import contextmanager
+
+from marionette_harness import MarionetteTestCase
+
+
+class ExperimentStatus:
+ UNENROLLED = 0
+ ENROLLED_CONTROL = 1
+ ENROLLED_TREATMENT = 2
+ DISQUALIFIED = 3
+
+
+class ContentWin32kLockdownState:
+ LockdownEnabled = 1
+ MissingWebRender = 2
+ OperatingSystemNotSupported = 3
+ PrefNotSet = 4
+ MissingRemoteWebGL = 5
+ MissingNonNativeTheming = 6
+ DisabledByEnvVar = 7
+ DisabledBySafeMode = 8
+ DisabledByE10S = 9
+ DisabledByUserPref = 10
+ EnabledByUserPref = 11
+ DisabledByControlGroup = 12
+ EnabledByTreatmentGroup = 13
+ DisabledByDefault = 14
+ EnabledByDefault = 15
+
+
+class Prefs:
+ ENROLLMENT_STATUS = "security.sandbox.content.win32k-experiment.enrollmentStatus"
+ STARTUP_ENROLLMENT_STATUS = (
+ "security.sandbox.content.win32k-experiment.startupEnrollmentStatus"
+ )
+ WIN32K = "security.sandbox.content.win32k-disable"
+ WEBGL = "webgl.out-of-process"
+
+
+ENV_DISABLE_WIN32K = "MOZ_ENABLE_WIN32K"
+ENV_DISABLE_E10S = "MOZ_FORCE_DISABLE_E10S"
+
+
+class TestWin32kAutostart(MarionetteTestCase):
+ SANDBOX_NAME = "win32k-autostart"
+
+ def execute_script(self, code, *args, **kwargs):
+ with self.marionette.using_context(self.marionette.CONTEXT_CHROME):
+ return self.marionette.execute_script(
+ code, new_sandbox=False, sandbox=self.SANDBOX_NAME, *args, **kwargs
+ )
+
+ def get_win32k_status(self):
+ return self.execute_script(
+ r"""
+ let win = Services.wm.getMostRecentWindow("navigator:browser");
+ let ses = "security.sandbox.content.win32k-experiment.startupEnrollmentStatus";
+ return {
+ win32kSessionStatus: Services.appinfo.win32kSessionStatus,
+ win32kStatus: Services.appinfo.win32kLiveStatusTestingOnly,
+ win32kExperimentStatus: Services.appinfo.win32kExperimentStatus,
+ win32kPref: Services.prefs.getBoolPref("security.sandbox.content.win32k-disable"),
+ win32kStartupEnrollmentStatusPref: Services.prefs.getIntPref(ses),
+ };
+ """
+ )
+
+ def check_win32k_status(
+ self, status, sessionStatus, experimentStatus, pref, enrollmentStatusPref
+ ):
+ # We CANNOT check win32kEnrollmentStatusPref after a restart because we only set this
+ # pref on the default branch, and it goes away after a restart, so we only check
+ # the startupEnrollmentStatusPref
+ expected = {
+ "win32kSessionStatus": sessionStatus,
+ "win32kStatus": status,
+ "win32kExperimentStatus": experimentStatus,
+ "win32kPref": pref,
+ "win32kStartupEnrollmentStatusPref": enrollmentStatusPref,
+ }
+
+ status = self.get_win32k_status()
+
+ for prop, value in expected.items():
+ self.assertEqual(
+ status[prop],
+ value,
+ "%s should have the value `%r`, but has `%r`"
+ % (prop, value, status[prop]),
+ )
+
+ def set_env(self, env, value):
+ self.execute_script(
+ "env.set(arguments[0], arguments[1]);", script_args=(env, value)
+ )
+
+ def get_env(self, env):
+ return self.execute_script("return env.get(arguments[0]);", script_args=(env,))
+
+ def set_enrollment_status(self, status):
+ self.marionette.set_pref(Prefs.ENROLLMENT_STATUS, status, default_branch=True)
+
+ updated_status = self.marionette.get_pref(Prefs.ENROLLMENT_STATUS)
+ self.assertTrue(
+ status == updated_status or updated_status == ExperimentStatus.DISQUALIFIED
+ )
+ startup_status = self.marionette.get_pref(Prefs.STARTUP_ENROLLMENT_STATUS)
+ self.assertEqual(
+ startup_status,
+ updated_status,
+ "Startup enrollment status (%r) should match "
+ "session status (%r)" % (startup_status, updated_status),
+ )
+
+ def restart(self, prefs=None, env=None):
+ if prefs:
+ self.marionette.set_prefs(prefs)
+
+ if env:
+ for name, value in env.items():
+ self.set_env(name, value)
+
+ self.marionette.restart(in_app=True, clean=False)
+ self.setUpSession()
+
+ # Sanity check our environment.
+ if prefs:
+ for key, val in prefs.items():
+ if val is not None:
+ self.assertEqual(self.marionette.get_pref(key), val)
+ if env:
+ for key, val in env.items():
+ self.assertEqual(self.get_env(key), val or "")
+
+ def setUpSession(self):
+ self.marionette.set_context(self.marionette.CONTEXT_CHROME)
+
+ self.execute_script(
+ r"""
+ // We're running in a function, in a sandbox, that inherits from an
+ // X-ray wrapped window. Anything we want to be globally available
+ // needs to be defined on that window.
+ window.env = Services.env;
+ """
+ )
+
+ @contextmanager
+ def full_restart(self):
+ profile = self.marionette.instance.profile
+ try:
+ self.marionette.quit()
+ yield profile
+ finally:
+ self.marionette.start_session()
+ self.setUpSession()
+
+ def setUp(self):
+ super(TestWin32kAutostart, self).setUp()
+
+ # If we have configured marionette to require a particular value for
+ # `win32k.autostart`, remove it as a forced pref until `tearDown`, and
+ # perform a clean restart, so we run this test without the pref
+ # pre-configured.
+ self.win32kRequired = None
+ if Prefs.WIN32K in self.marionette.instance.required_prefs:
+ self.win32kRequired = self.marionette.instance.required_prefs[Prefs.WIN32K]
+ del self.marionette.instance.required_prefs[Prefs.WIN32K]
+ self.marionette.restart(in_app=False, clean=True)
+
+ self.setUpSession()
+
+ # Marionette doesn't let you set preferences on the default branch before startup
+ # so we can't test the default=False and default=True scenarios in one test
+ # What we can do is generate all the tests, and then only run the runs for which
+ # the default is. (And run the other ones locally to make sure they work before
+ # we land it.)
+ prefJS = 'return Services.prefs.getBoolPref("security.sandbox.content.win32k-disable");'
+ self.default_is = self.execute_script(prefJS)
+
+ if self.default_is is False:
+ # Win32k status must start out with `disabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.DisabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.DisabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=False,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+ else:
+ # Win32k status must start out with `enabledByDefault`
+ self.check_win32k_status(
+ status=ContentWin32kLockdownState.EnabledByDefault,
+ sessionStatus=ContentWin32kLockdownState.EnabledByDefault,
+ experimentStatus=ExperimentStatus.UNENROLLED,
+ pref=True,
+ enrollmentStatusPref=ExperimentStatus.UNENROLLED,
+ )
+
+ def tearDown(self):
+ if self.win32kRequired is not None:
+ self.marionette.instance.required_prefs[Prefs.WIN32K] = self.win32kRequired
+ self.marionette.restart(in_app=False, clean=True)
+
+ super(TestWin32kAutostart, self).tearDown()
diff --git a/toolkit/xre/test/marionette/win32k_tests.txt b/toolkit/xre/test/marionette/win32k_tests.txt
new file mode 100644
index 0000000000..76068de268
--- /dev/null
+++ b/toolkit/xre/test/marionette/win32k_tests.txt
@@ -0,0 +1,167 @@
+#############################################
+# This file serves as the test case list for gen_win32k_tests.py
+
+#############################################
+# Only here for reference:
+
+# MissingWebRender = 2
+# OperatingSystemNotSupported = 3
+# MissingRemoteWebGL = 5
+# DisabledByEnvVar = 7
+# DisabledBySafeMode = 8
+# DisabledByE10S = 9
+# DisabledByUserPref = 10
+# EnabledByUserPref = 11
+# DisabledByControlGroup = 12
+# EnabledByTreatmentGroup = 13
+# DisabledByDefault = 14
+# EnabledByDefault = 15
+
+# UNENROLLED
+# ENROLLED_CONTROL
+# ENROLLED_TREATMENT
+# DISQUALIFIED
+
+#############################################
+# Assertion Mappings
+# Shorthand values for the test cases below. Instead of writing out the whole thing
+# you can just do [A#5]
+
+ > 1 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 1T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 2 : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 3 : [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 3T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 4 : [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 4T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 5 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_CONTROL]
+ > 5T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL]
+ > 6 : [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL]
+ > 6T : [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ > 7 : [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT]
+ > 7T : [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT]
+ > 8 : [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT]
+ > 8T : [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ > 9 : [A S=MissingRemoteWebGL SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 9T : [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ > 10 : [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
+ > 10T: [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+
+
+# TODO
+# - win32k env var tests
+
+
+#############################################
+# Tests
+
+# Syntax:
+# [D=T] - is the default value for win32k lockdown pref 'false' or 'true'
+# Action -> Action
+# Valid Actions:
+# Nothing
+# Enrolled Control/Treatment
+# On/Off (changes win32k pref)
+# Restart
+# E10s (Restarts with E10s disabled)
+# Header-On - Restart with MOZ_ENABLE_WIN32K set
+# Header-Off - unset MOZ_ENABLE_WIN32K but do not restart
+# Bad Requirements - sets webg out of process to false, invalidating win32k
+# Enrolled /Control/Treatment]-C:
+# The enrollment pref is set by normandy on _every_ startup on the
+# default branch, where it doesn't persist.
+# This action represents normandy doing its normal set that
+# occurs after a restart. If Normandy doesn't do it, the
+# startupEnrollmentPref is set back to 0 after restart, which is expected.
+# [A S= SS= ES= P= ESP=] - trigger an assertion check for the listed values
+# S - 'Status' or the current value of calculating GetWin32kLockdownState()
+# SS - 'Session Status' or the current value of set-once-at-startup static variable gWin32kStatus
+# ES - 'Experiment Status' or the value of gWin32kExperimentStatus
+# P - 'Pref' or the value of security.sandbox.content.win32k-disable
+# ESP- 'Enrollment Status Pref' or the value of security.sandbox.content.win32k-experiment.startupEnrollmentStatus
+# [A#5] - trigger an assertion check from the mapping table above
+
+
+# Safe Mode tests are currently not generated, as I don't know how to make marionette restart in safe mode
+
+#################
+ - [D=F] Nothing [A#1] -> Enrolled Control [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL]
+ - [D=F] Nothing [A#1] -> Enrolled Treatment [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT]
+ - [D=F] Nothing [A#1] -> On [A S=EnabledByUserPref SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> Off [A#1] -> Restart [A#1]
+ - [D=F] Nothing [A#1] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off [A S=EnabledByUserPref SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Nothing [A#1] -> Safe Mode -> Restart
+
+#################
+ - [D=T] Nothing [A#1T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] Nothing [A#1T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] Nothing [A#1T] -> On [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> On -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off [A S=EnabledByDefault SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Nothing [A#1T] -> Safe Mode -> Restart
+
+#################
+ - [D=F] On [A#3] -> Restart [A#4] -> Enrolled Control [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Enrolled Treatment [A S=EnabledByUserPref SS=EnabledByUserPref ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Off [A S=DisabledByDefault SS=EnabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByDefault SS=DisabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=F] On [A#3] -> Restart [A#4] -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByUserPref ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] On [A#3] -> Restart [A#4] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A#4]
+ - [D=F] On [A#3] -> Restart [A#4] -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off -> Restart [A#4]
+ - [D=F] On [A#3] -> Restart [A#4] -> Safe Mode
+
+#################
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Control [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Enrolled Treatment [A S=EnabledByDefault SS=EnabledByDefault ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Off [A S=DisabledByUserPref SS=EnabledByDefault ES=UNENROLLED P=False ESP=UNENROLLED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=UNENROLLED P=False ESP=UNENROLLED]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByDefault ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=UNENROLLED P=True ESP=UNENROLLED] -> Restart [A#4T]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=UNENROLLED P=True ESP=UNENROLLED] -> Header-Off -> Restart [A#4T]
+ - [D=T] On [A#3T] -> Restart [A#4T] -> Safe Mode
+
+#################
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> On [A S=EnabledByUserPref SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=DISQUALIFIED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Off [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Enrolled Control-C -> Restart [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Enrolled Control-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_CONTROL P=False ESP=ENROLLED_CONTROL] -> Header-Off -> Enrolled Control-C -> Restart [A#6]
+ - [D=F] Enrolled Control [A#5] -> Restart [A#6] -> Safe Mode ->
+
+#################
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> On [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Restart [A S=DisabledByControlGroup SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Off [A S=DisabledByUserPref SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=False ESP=DISQUALIFIED]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Bad Requirements [A S=MissingRemoteWebGL SS=DisabledByControlGroup ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Enrolled Control-C -> Restart [A#6T]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Enrolled Control-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_CONTROL P=True ESP=ENROLLED_CONTROL] -> Header-Off -> Enrolled Control-C -> Restart [A#6T]
+ - [D=T] Enrolled Control [A#5T] -> Restart [A#6T] -> Safe Mode ->
+
+#################
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> On [A S=EnabledByUserPref SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=DISQUALIFIED] -> Restart [A S=EnabledByUserPref SS=EnabledByUserPref ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Off [A#8] -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Enrolled Treatment-C -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Enrolled Treatment-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_TREATMENT P=False ESP=ENROLLED_TREATMENT] -> Header-Off -> Enrolled Treatment-C -> Restart [A#8]
+ - [D=F] Enrolled Treatment [A#7] -> Restart [A#8] -> Safe Mode ->
+
+#################
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> On [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=EnabledByTreatmentGroup SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Off [A S=DisabledByUserPref SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=False ESP=DISQUALIFIED] -> Restart [A S=DisabledByUserPref SS=DisabledByUserPref ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Bad Requirements [A S=MissingRemoteWebGL SS=EnabledByTreatmentGroup ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> E10S [A S=DisabledByE10S SS=DisabledByE10S ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Enrolled Treatment-C -> Restart [A#8T]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Enrolled Treatment-C -> Header-On [A S=DisabledByEnvVar SS=DisabledByEnvVar ES=ENROLLED_TREATMENT P=True ESP=ENROLLED_TREATMENT] -> Header-Off -> Enrolled Treatment-C -> Restart [A#8T]
+ - [D=T] Enrolled Treatment [A#7T] -> Restart [A#8T] -> Safe Mode ->
+
+#################
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Control [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Enrolled Treatment [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=False ESP=DISQUALIFIED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> On [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=F] Bad Requirements [A#9] -> Restart [A#10] -> Off [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
+
+#################
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Control [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=ENROLLED_CONTROL] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Enrolled Treatment [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=ENROLLED_TREATMENT] -> Restart [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=DISQUALIFIED P=True ESP=DISQUALIFIED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> On [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=True ESP=UNENROLLED]
+ - [D=T] Bad Requirements [A#9T] -> Restart [A#10T] -> Off [A S=MissingRemoteWebGL SS=MissingRemoteWebGL ES=UNENROLLED P=False ESP=UNENROLLED]
diff --git a/toolkit/xre/test/mochitest.ini b/toolkit/xre/test/mochitest.ini
new file mode 100644
index 0000000000..ccbb08ed97
--- /dev/null
+++ b/toolkit/xre/test/mochitest.ini
@@ -0,0 +1,3 @@
+[DEFAULT]
+
+[test_fpuhandler.html]
diff --git a/toolkit/xre/test/show_hash.js b/toolkit/xre/test/show_hash.js
new file mode 100644
index 0000000000..28674e6840
--- /dev/null
+++ b/toolkit/xre/test/show_hash.js
@@ -0,0 +1,4 @@
+const xre = Cc["@mozilla.org/xre/directory-provider;1"].getService(
+ Ci.nsIXREDirProvider
+);
+dump(`${xre.getInstallHash(false)}\n`);
diff --git a/toolkit/xre/test/test_fpuhandler.html b/toolkit/xre/test/test_fpuhandler.html
new file mode 100644
index 0000000000..a3869f2ecf
--- /dev/null
+++ b/toolkit/xre/test/test_fpuhandler.html
@@ -0,0 +1,36 @@
+<head>
+ <title>Floating-point exception handler test</title>
+ <script src="/tests/SimpleTest/SimpleTest.js"></script>
+
+<body onload="runTest()">
+ <embed id="plugin1" type="application/x-test" width="400" height="400"></embed>
+
+ <script class="testbody" type="application/javascript">
+ SimpleTest.waitForExplicitFinish();
+
+ function doDiv(x, y) {
+ var z;
+ z = x / y;
+
+ for (let i = 0 + x; i < 1000; ++i)
+ z = y / x;
+
+ z = x / y;
+ return z;
+ }
+
+ function runTest() {
+ ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash");
+
+ try {
+ document.getElementById("plugin1").enableFPExceptions();
+ } catch (e) {
+ ok(true, "No special code to set the FPU bit in the testplugin.");
+ SimpleTest.finish();
+ return;
+ }
+
+ ok(isNaN(doDiv(0.0, 0.0)), "Undefined division-by-zero doesn't crash again.");
+ SimpleTest.finish();
+ }
+ </script>
diff --git a/toolkit/xre/test/test_install_hash.js b/toolkit/xre/test/test_install_hash.js
new file mode 100644
index 0000000000..75154b27bd
--- /dev/null
+++ b/toolkit/xre/test/test_install_hash.js
@@ -0,0 +1,138 @@
+/* Any copyright is dedicated to the Public Domain.
+ http://creativecommons.org/publicdomain/zero/1.0/ */
+
+/**
+ * This runs the xpcshell binary with different cases for the executable path.
+ * They should all result in the same installation hash.
+ */
+
+const { Subprocess } = ChromeUtils.importESModule(
+ "resource://gre/modules/Subprocess.sys.mjs"
+);
+
+const XRE = Cc["@mozilla.org/xre/directory-provider;1"].getService(
+ Ci.nsIXREDirProvider
+);
+const HASH = XRE.getInstallHash(false);
+const EXE = Services.dirsvc.get("XREExeF", Ci.nsIFile);
+const SCRIPT = do_get_file("show_hash.js", false);
+
+async function getHash(bin) {
+ try {
+ let proc = await Subprocess.call({
+ command: bin.path,
+ arguments: [SCRIPT.path],
+ });
+
+ let result = "";
+ let string;
+ while ((string = await proc.stdout.readString())) {
+ result += string;
+ }
+
+ return result.trim();
+ } catch (e) {
+ if (e.errorCode == Subprocess.ERROR_BAD_EXECUTABLE) {
+ return null;
+ }
+ throw e;
+ }
+}
+
+// Walks through a path's entries and calls a mutator function to change the
+// case of each.
+function mutatePath(path, mutator) {
+ let parts = [];
+ let index = 0;
+ while (path.parent != null) {
+ parts.push(mutator(path.leafName, index++));
+ path = path.parent;
+ }
+
+ while (parts.length) {
+ path.append(parts.pop());
+ }
+
+ return path;
+}
+
+// Counts how many path parts a mutator will be called for.
+function countParts(path) {
+ let index = 0;
+ while (path.parent != null) {
+ path = path.parent;
+ index++;
+ }
+ return index;
+}
+
+add_task(async function testSameBinary() {
+ // Running with the same binary path should definitely work and give the same
+ // hash.
+ Assert.equal(
+ await getHash(EXE),
+ HASH,
+ "Should have the same hash when running the same binary."
+ );
+});
+
+add_task(async function testUpperCase() {
+ let upper = mutatePath(EXE, p => p.toLocaleUpperCase());
+ let hash = await getHash(upper);
+
+ // We may not get a hash if any part of the filesystem is case sensitive.
+ if (hash) {
+ Assert.equal(
+ hash,
+ HASH,
+ `Should have seen the same hash from ${upper.path}.`
+ );
+ }
+});
+
+add_task(async function testLowerCase() {
+ let lower = mutatePath(EXE, p => p.toLocaleLowerCase());
+ let hash = await getHash(lower);
+
+ // We may not get a hash if any part of the filesystem is case sensitive.
+ if (hash) {
+ Assert.equal(
+ hash,
+ HASH,
+ `Should have seen the same hash from ${lower.path}.`
+ );
+ }
+});
+
+add_task(async function testEachPart() {
+ // We need to check the case where only some of the directories in the path
+ // are case insensitive.
+
+ let count = countParts(EXE);
+ for (let i = 0; i < count; i++) {
+ let upper = mutatePath(EXE, (p, index) =>
+ index == i ? p.toLocaleUpperCase() : p
+ );
+ let lower = mutatePath(EXE, (p, index) =>
+ index == i ? p.toLocaleLowerCase() : p
+ );
+
+ let upperHash = await getHash(upper);
+ if (upperHash) {
+ Assert.equal(
+ upperHash,
+ HASH,
+ `Should have seen the same hash from ${upper.path}.`
+ );
+ }
+
+ let lowerHash = await getHash(lower);
+ if (lowerHash) {
+ Assert.equal(
+ lowerHash,
+ HASH,
+ `Should have seen the same hash from ${lower.path}.`
+ );
+ }
+ }
+});
diff --git a/toolkit/xre/test/test_launch_without_hang.js b/toolkit/xre/test/test_launch_without_hang.js
new file mode 100644
index 0000000000..feacae36d4
--- /dev/null
+++ b/toolkit/xre/test/test_launch_without_hang.js
@@ -0,0 +1,256 @@
+// This Source Code Form is subject to the terms of the Mozilla Public
+// License, v. 2.0. If a copy of the MPL was not distributed with this
+// file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+// bug 1360493
+// Launch the browser a number of times, testing startup hangs.
+
+"use strict";
+
+const { AppConstants } = ChromeUtils.importESModule(
+ "resource://gre/modules/AppConstants.sys.mjs"
+);
+
+const APP_TIMER_TIMEOUT_MS = 1000 * 15;
+const TRY_COUNT = 50;
+
+// Sets a group of environment variables, returning the old values.
+// newVals AND return value is an array of { key: "", value: "" }
+function setEnvironmentVariables(newVals) {
+ let oldVals = [];
+ for (let i = 0; i < newVals.length; ++i) {
+ let key = newVals[i].key;
+ let value = newVals[i].value;
+ let oldObj = { key };
+ if (Services.env.exists(key)) {
+ oldObj.value = Services.env.get(key);
+ } else {
+ oldObj.value = null;
+ }
+
+ Services.env.set(key, value);
+ oldVals.push(oldObj);
+ }
+ return oldVals;
+}
+
+function getFirefoxExecutableFilename() {
+ if (AppConstants.platform === "win") {
+ return AppConstants.MOZ_APP_NAME + ".exe";
+ }
+ return AppConstants.MOZ_APP_NAME;
+}
+
+// Returns a nsIFile to the firefox.exe executable file
+function getFirefoxExecutableFile() {
+ let file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file = Services.dirsvc.get("GreBinD", Ci.nsIFile);
+
+ file.append(getFirefoxExecutableFilename());
+ return file;
+}
+
+// Takes an executable and arguments, and wraps it in a call to the system shell.
+// Technique adapted from \toolkit\mozapps\update\tests\unit_service_updater\xpcshellUtilsAUS.js
+// to avoid child process console output polluting the xpcshell log.
+// returns { file: (nsIFile), args: [] }
+function wrapLaunchInShell(file, args) {
+ let ret = {};
+
+ if (AppConstants.platform === "win") {
+ ret.file = Services.dirsvc.get("WinD", Ci.nsIFile);
+ ret.file.append("System32");
+ ret.file.append("cmd.exe");
+ ret.args = ["/D", "/Q", "/C", file.path].concat(args).concat([">nul"]);
+ } else {
+ ret.file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ ret.file.initWithPath("/usr/bin/env");
+ ret.args = [file.path].concat(args).concat(["> /dev/null"]);
+ }
+
+ Assert.ok(
+ ret.file.exists(),
+ "Executable file should exist: " + ret.file.path
+ );
+
+ return ret;
+}
+
+// Needed because process.kill() kills the console, not its child process, firefox.
+function terminateFirefox(completion) {
+ let executableName = getFirefoxExecutableFilename();
+ let file;
+ let args;
+
+ if (AppConstants.platform === "win") {
+ file = Services.dirsvc.get("WinD", Ci.nsIFile);
+ file.append("System32");
+ file.append("taskkill.exe");
+ args = ["/F", "/IM", executableName];
+ } else {
+ file = Cc["@mozilla.org/file/local;1"].createInstance(Ci.nsIFile);
+ file.initWithPath("/usr/bin/killall");
+ args = [executableName];
+ }
+
+ info("launching application: " + file.path);
+ info(" with args: " + args.join(" "));
+
+ let process = Cc["@mozilla.org/process/util;1"].createInstance(Ci.nsIProcess);
+ process.init(file);
+
+ let processObserver = {
+ observe: function PO_observe(aSubject, aTopic, aData) {
+ info("topic: " + aTopic + ", process exitValue: " + process.exitValue);
+
+ Assert.equal(
+ process.exitValue,
+ 0,
+ "Terminate firefox process exit value should be 0"
+ );
+ Assert.equal(
+ aTopic,
+ "process-finished",
+ "Terminate firefox observer topic should be process-finished"
+ );
+
+ if (completion) {
+ completion();
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ };
+
+ process.runAsync(args, args.length, processObserver);
+
+ info(" with pid: " + process.pid);
+}
+
+// Launches file with args asynchronously, failing if the process did not
+// exit within timeoutMS milliseconds. If a timeout occurs, handler()
+// is called.
+function launchProcess(file, args, env, timeoutMS, handler, attemptCount) {
+ let state = {};
+
+ state.attempt = attemptCount;
+
+ state.processObserver = {
+ observe: function PO_observe(aSubject, aTopic, aData) {
+ if (!state.appTimer) {
+ // the app timer has been canceled; this process has timed out already so don't process further.
+ handler(false);
+ return;
+ }
+
+ info(
+ "topic: " + aTopic + ", process exitValue: " + state.process.exitValue
+ );
+
+ info("Restoring environment variables");
+ setEnvironmentVariables(state.oldEnv);
+
+ state.appTimer.cancel();
+ state.appTimer = null;
+
+ Assert.equal(
+ state.process.exitValue,
+ 0,
+ "the application process exit value should be 0"
+ );
+ Assert.equal(
+ aTopic,
+ "process-finished",
+ "the application process observer topic should be process-finished"
+ );
+
+ handler(true);
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsIObserver"]),
+ };
+
+ // The timer callback to kill the process if it takes too long.
+ state.appTimerCallback = {
+ notify: function TC_notify(aTimer) {
+ state.appTimer = null;
+
+ info("Restoring environment variables");
+ setEnvironmentVariables(state.oldEnv);
+
+ if (state.process.isRunning) {
+ info("attempting to kill process");
+
+ // This will cause the shell process to exit as well, triggering our process observer.
+ terminateFirefox(function terminateFirefoxCompletion() {
+ Assert.ok(false, "Launch application timer expired");
+ });
+ }
+ },
+ QueryInterface: ChromeUtils.generateQI(["nsITimerCallback"]),
+ };
+
+ info("launching application: " + file.path);
+ info(" with args: " + args.join(" "));
+ info(" with environment: ");
+ for (let i = 0; i < env.length; ++i) {
+ info(" " + env[i].key + "=" + env[i].value);
+ }
+
+ state.process = Cc["@mozilla.org/process/util;1"].createInstance(
+ Ci.nsIProcess
+ );
+ state.process.init(file);
+
+ state.appTimer = Cc["@mozilla.org/timer;1"].createInstance(Ci.nsITimer);
+ state.appTimer.initWithCallback(
+ state.appTimerCallback,
+ timeoutMS,
+ Ci.nsITimer.TYPE_ONE_SHOT
+ );
+
+ state.oldEnv = setEnvironmentVariables(env);
+
+ state.process.runAsync(args, args.length, state.processObserver);
+
+ info(" with pid: " + state.process.pid);
+}
+
+function run_test() {
+ do_test_pending();
+
+ let env = [
+ { key: "MOZ_CRASHREPORTER_DISABLE", value: null },
+ { key: "MOZ_CRASHREPORTER", value: "1" },
+ { key: "MOZ_CRASHREPORTER_NO_REPORT", value: "1" },
+ { key: "MOZ_CRASHREPORTER_SHUTDOWN", value: "1" },
+ { key: "XPCOM_DEBUG_BREAK", value: "stack-and-abort" },
+ ];
+
+ let triesStarted = 1;
+
+ let handler = function launchFirefoxHandler(okToContinue) {
+ triesStarted++;
+ if (triesStarted <= TRY_COUNT && okToContinue) {
+ testTry();
+ } else {
+ do_test_finished();
+ }
+ };
+
+ let testTry = function testTry() {
+ let shell = wrapLaunchInShell(getFirefoxExecutableFile(), [
+ "-no-remote",
+ "-test-launch-without-hang",
+ ]);
+ info("Try attempt #" + triesStarted);
+ launchProcess(
+ shell.file,
+ shell.args,
+ env,
+ APP_TIMER_TIMEOUT_MS,
+ handler,
+ triesStarted
+ );
+ };
+
+ testTry();
+}
diff --git a/toolkit/xre/test/win/Makefile.in b/toolkit/xre/test/win/Makefile.in
new file mode 100644
index 0000000000..5a67b8eac8
--- /dev/null
+++ b/toolkit/xre/test/win/Makefile.in
@@ -0,0 +1,11 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+MOZ_WINCONSOLE = 1
+
+include $(topsrcdir)/config/rules.mk
+
+check::
+ @echo 'Running TestXREMakeCommandLineWin tests'
+ @$(RUN_TEST_PROGRAM) $(FINAL_TARGET)/TestXREMakeCommandLineWin.exe
diff --git a/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
new file mode 100644
index 0000000000..ab5edcf1ca
--- /dev/null
+++ b/toolkit/xre/test/win/TestLauncherRegistryInfo.cpp
@@ -0,0 +1,783 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
+
+#define MOZ_USE_LAUNCHER_ERROR
+
+#include "mozilla/LauncherRegistryInfo.h"
+#include "mozilla/NativeNt.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/Unused.h"
+#include "nsWindowsHelpers.h"
+
+#include "LauncherRegistryInfo.cpp"
+
+#include <string>
+
+static const char kMsgStart[] = "TEST-FAILED | LauncherRegistryInfo | ";
+
+static const wchar_t kRegKeyPath[] = L"SOFTWARE\\" EXPAND_STRING_MACRO(
+ MOZ_APP_VENDOR) L"\\" EXPAND_STRING_MACRO(MOZ_APP_BASENAME) L"\\Launcher";
+static const wchar_t kBrowserSuffix[] = L"|Browser";
+static const wchar_t kLauncherSuffix[] = L"|Launcher";
+static const wchar_t kImageSuffix[] = L"|Image";
+static const wchar_t kTelemetrySuffix[] = L"|Telemetry";
+
+static std::wstring gBrowserValue;
+static std::wstring gLauncherValue;
+static std::wstring gImageValue;
+static std::wstring gTelemetryValue;
+
+static DWORD gMyImageTimestamp;
+
+#define RUN_TEST(result, fn) \
+ if ((result = fn()).isErr()) { \
+ const mozilla::LauncherError& err = result.inspectErr(); \
+ printf("%s%s | %08lx (%s:%d)\n", kMsgStart, #fn, err.mError.AsHResult(), \
+ err.mFile, err.mLine); \
+ return 1; \
+ }
+
+#define EXPECT_COMMIT_IS_OK() \
+ do { \
+ mozilla::LauncherVoidResult vr2 = info.Commit(); \
+ if (vr2.isErr()) { \
+ return vr2; \
+ } \
+ } while (0)
+
+#define EXPECT_CHECK_RESULT_IS(desired, expected) \
+ do { \
+ mozilla::LauncherResult<mozilla::LauncherRegistryInfo::ProcessType> \
+ result = info.Check(mozilla::LauncherRegistryInfo::desired); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.unwrap() != mozilla::LauncherRegistryInfo::expected) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL); \
+ } \
+ } while (0)
+
+#define EXPECT_ENABLED_STATE_IS(expected) \
+ do { \
+ mozilla::LauncherResult<mozilla::LauncherRegistryInfo::EnabledState> \
+ enabled = info.IsEnabled(); \
+ if (enabled.isErr()) { \
+ return enabled.propagateErr(); \
+ } \
+ if (enabled.unwrap() != mozilla::LauncherRegistryInfo::expected) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_TELEMETRY_IS_ENABLED(expected) \
+ do { \
+ mozilla::LauncherResult<bool> enabled = info.IsTelemetryEnabled(); \
+ if (enabled.isErr()) { \
+ return enabled.propagateErr(); \
+ } \
+ if (enabled.unwrap() != expected) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_REG_DWORD_EXISTS_AND_EQ(name, expected) \
+ do { \
+ mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \
+ ReadRegistryValueData<DWORD>(name, REG_DWORD); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.inspect().isNothing() || \
+ result.inspect().value() != expected) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_REG_QWORD_EXISTS(name) \
+ do { \
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
+ ReadRegistryValueData<uint64_t>(name, REG_QWORD); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.inspect().isNothing()) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_REG_QWORD_EXISTS_AND_EQ(name, expected) \
+ do { \
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
+ ReadRegistryValueData<uint64_t>(name, REG_QWORD); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.inspect().isNothing() || \
+ result.inspect().value() != expected) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_REG_DWORD_DOES_NOT_EXIST(name) \
+ do { \
+ mozilla::LauncherResult<mozilla::Maybe<DWORD>> result = \
+ ReadRegistryValueData<DWORD>(name, REG_DWORD); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.inspect().isSome()) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+#define EXPECT_REG_QWORD_DOES_NOT_EXIST(name) \
+ do { \
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> result = \
+ ReadRegistryValueData<uint64_t>(name, REG_QWORD); \
+ if (result.isErr()) { \
+ return result.propagateErr(); \
+ } \
+ if (result.inspect().isSome()) { \
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED); \
+ } \
+ } while (0)
+
+template <typename T>
+static mozilla::LauncherResult<mozilla::Maybe<T>> ReadRegistryValueData(
+ const std::wstring& name, DWORD expectedType) {
+ T data;
+ DWORD dataLen = sizeof(data);
+ DWORD type;
+ LSTATUS status = ::RegGetValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str(),
+ RRF_RT_ANY, &type, &data, &dataLen);
+ if (status == ERROR_FILE_NOT_FOUND) {
+ return mozilla::Maybe<T>();
+ }
+
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ if (type != expectedType) {
+ return LAUNCHER_ERROR_FROM_WIN32(ERROR_DATATYPE_MISMATCH);
+ }
+
+ return mozilla::Some(data);
+}
+
+template <typename T>
+static mozilla::LauncherVoidResult WriteRegistryValueData(
+ const std::wstring& name, DWORD type, T data) {
+ LSTATUS status = ::RegSetKeyValueW(HKEY_CURRENT_USER, kRegKeyPath,
+ name.c_str(), type, &data, sizeof(T));
+ if (status != ERROR_SUCCESS) {
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+ }
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult DeleteRegistryValueData(
+ const std::wstring& name) {
+ LSTATUS status =
+ ::RegDeleteKeyValueW(HKEY_CURRENT_USER, kRegKeyPath, name.c_str());
+ if (status == ERROR_SUCCESS || status == ERROR_FILE_NOT_FOUND) {
+ return mozilla::Ok();
+ }
+
+ return LAUNCHER_ERROR_FROM_WIN32(status);
+}
+
+static mozilla::LauncherVoidResult DeleteAllRegstryValues() {
+ // Unblock commit via ReflectPrefToRegistry
+ // (We need to set false, and then true to bypass the early return)
+ mozilla::LauncherRegistryInfo info;
+ mozilla::LauncherVoidResult vr = info.ReflectPrefToRegistry(false);
+ vr = info.ReflectPrefToRegistry(true);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ vr = DeleteRegistryValueData(gImageValue);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ vr = DeleteRegistryValueData(gLauncherValue);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ vr = DeleteRegistryValueData(gBrowserValue);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ return DeleteRegistryValueData(gTelemetryValue);
+}
+
+bool GetInstallHash(const char16_t*, mozilla::UniquePtr<NS_tchar[]>& result) {
+ return true;
+}
+
+static mozilla::LauncherVoidResult SetupEnabledScenario() {
+ // Reset the registry state to an enabled state. First, we delete all existing
+ // registry values (if any).
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Now we run Check(Launcher)...
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ EXPECT_COMMIT_IS_OK();
+ // ...and Check(Browser)
+ EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ // By this point we are considered to be fully enabled.
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestEmptyRegistry() {
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ EXPECT_COMMIT_IS_OK();
+
+ // LauncherRegistryInfo should have created Launcher and Image values
+ EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
+ EXPECT_REG_QWORD_EXISTS(gLauncherValue);
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestNormal() {
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, QPCNowRaw());
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ // Make sure the browser timestamp is newer than the launcher's
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs =
+ ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
+ if (launcherTs.isErr()) {
+ return launcherTs.propagateErr();
+ }
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserTs =
+ ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD);
+ if (browserTs.isErr()) {
+ return browserTs.propagateErr();
+ }
+ if (launcherTs.inspect().isNothing() || browserTs.inspect().isNothing() ||
+ browserTs.inspect().value() <= launcherTs.inspect().value()) {
+ return LAUNCHER_ERROR_FROM_HRESULT(E_FAIL);
+ }
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestBrowserNoLauncher() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = DeleteRegistryValueData(gLauncherValue);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ // Verify that we still don't have a launcher timestamp
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
+ // Verify that the browser timestamp is now zero
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestLauncherNoBrowser() {
+ constexpr uint64_t launcherTs = 0x77777777;
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ // Launcher's timestamps is kept intact while browser's is set to 0.
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestBrowserLessThanLauncher() {
+ constexpr uint64_t launcherTs = 0x77777777, browserTs = 0x66666666;
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, gMyImageTimestamp);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, launcherTs);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, browserTs);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ // Launcher's timestamps is kept intact while browser's is set to 0.
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestImageTimestampChange() {
+ // This should reset the timestamps and then essentially run like
+ // TestEmptyRegistry
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD, 1ULL);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 2ULL);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ EXPECT_COMMIT_IS_OK();
+
+ EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
+ EXPECT_REG_QWORD_EXISTS(gLauncherValue);
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestImageTimestampChangeWhenDisabled() {
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
+ if (vr.isErr()) {
+ return vr;
+ }
+ vr = WriteRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD, 0ULL);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+
+ EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0);
+
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestDisableDueToFailure() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Check that we are indeed enabled.
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);
+
+ // Now call DisableDueToFailure
+ mozilla::LauncherVoidResult lvr = info.DisableDueToFailure();
+ if (lvr.isErr()) {
+ return lvr.propagateErr();
+ }
+
+ // We should now be FailDisabled
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ // If we delete the launcher timestamp, IsEnabled should then return
+ // ForceDisabled.
+ vr = DeleteRegistryValueData(gLauncherValue);
+ if (vr.isErr()) {
+ return vr;
+ }
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestPrefReflection() {
+ // Reset the registry to a known good state.
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Let's see what happens when we flip the pref to OFF.
+ mozilla::LauncherRegistryInfo info;
+ mozilla::LauncherVoidResult reflectOk = info.ReflectPrefToRegistry(false);
+ if (reflectOk.isErr()) {
+ return reflectOk.propagateErr();
+ }
+
+ // Launcher timestamp should be non-existent.
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
+ // Browser timestamp should be zero
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, 0ULL);
+ // IsEnabled should give us ForceDisabled
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ // Now test to see what happens when the pref is set to ON.
+ reflectOk = info.ReflectPrefToRegistry(true);
+ if (reflectOk.isErr()) {
+ return reflectOk.propagateErr();
+ }
+
+ // Launcher and browser timestamps should be non-existent.
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gLauncherValue);
+ EXPECT_REG_QWORD_DOES_NOT_EXIST(gBrowserValue);
+
+ // IsEnabled should give us Enabled.
+ EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestTelemetryConfig() {
+ mozilla::LauncherVoidResult vr = DeleteAllRegstryValues();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_TELEMETRY_IS_ENABLED(false);
+
+ mozilla::LauncherVoidResult reflectOk =
+ info.ReflectTelemetryPrefToRegistry(false);
+ if (reflectOk.isErr()) {
+ return reflectOk.propagateErr();
+ }
+ EXPECT_TELEMETRY_IS_ENABLED(false);
+
+ reflectOk = info.ReflectTelemetryPrefToRegistry(true);
+ if (reflectOk.isErr()) {
+ return reflectOk.propagateErr();
+ }
+ EXPECT_TELEMETRY_IS_ENABLED(true);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestCommitAbort() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Retrieve the current timestamps to compare later
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherValue =
+ ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
+ if (launcherValue.isErr() || launcherValue.inspect().isNothing()) {
+ return launcherValue.propagateErr();
+ }
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> browserValue =
+ ReadRegistryValueData<uint64_t>(gBrowserValue, REG_QWORD);
+ if (browserValue.isErr() || browserValue.inspect().isNothing()) {
+ return browserValue.propagateErr();
+ }
+ uint64_t launcherTs = launcherValue.inspect().value();
+ uint64_t browserTs = browserValue.inspect().value();
+
+ vr = []() -> mozilla::LauncherVoidResult {
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ // No commit
+ return mozilla::Ok();
+ }();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Exiting the scope discards the change.
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs);
+
+ // Commit -> Check -> Abort -> Commit
+ EXPECT_COMMIT_IS_OK();
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ info.Abort();
+ EXPECT_COMMIT_IS_OK();
+
+ // Nothing is changed.
+ EXPECT_REG_DWORD_EXISTS_AND_EQ(gImageValue, gMyImageTimestamp);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gBrowserValue, browserTs);
+ EXPECT_ENABLED_STATE_IS(EnabledState::Enabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestDisableDuringLauncherLaunch() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherResult<mozilla::Maybe<uint64_t>> launcherTs =
+ ReadRegistryValueData<uint64_t>(gLauncherValue, REG_QWORD);
+ if (launcherTs.isErr()) {
+ return launcherTs.propagateErr();
+ }
+ if (launcherTs.inspect().isNothing()) {
+ return LAUNCHER_ERROR_FROM_HRESULT(E_UNEXPECTED);
+ }
+
+ vr = []() -> mozilla::LauncherVoidResult {
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+
+ // Call DisableDueToFailure with a different instance
+ mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult {
+ mozilla::LauncherRegistryInfo info;
+ mozilla::LauncherVoidResult vr = info.DisableDueToFailure();
+ if (vr.isErr()) {
+ return vr.propagateErr();
+ }
+ return mozilla::Ok();
+ }();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Commit after disable.
+ EXPECT_COMMIT_IS_OK();
+
+ return mozilla::Ok();
+ }();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Make sure we're still FailDisabled and the launcher's timestamp is not
+ // updated
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+ EXPECT_REG_QWORD_EXISTS_AND_EQ(gLauncherValue, launcherTs.inspect().value());
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestDisableDuringBrowserLaunch() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ EXPECT_COMMIT_IS_OK();
+
+ vr = []() -> mozilla::LauncherVoidResult {
+ mozilla::LauncherRegistryInfo info;
+ EXPECT_CHECK_RESULT_IS(ProcessType::Browser, ProcessType::Browser);
+
+ // Call DisableDueToFailure with a different instance
+ mozilla::LauncherVoidResult vr = []() -> mozilla::LauncherVoidResult {
+ mozilla::LauncherRegistryInfo info;
+ mozilla::LauncherVoidResult vr = info.DisableDueToFailure();
+ if (vr.isErr()) {
+ return vr.propagateErr();
+ }
+ return mozilla::Ok();
+ }();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Commit after disable.
+ EXPECT_COMMIT_IS_OK();
+
+ return mozilla::Ok();
+ }();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Make sure we're still FailDisabled
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ return mozilla::Ok();
+}
+
+static mozilla::LauncherVoidResult TestReEnable() {
+ mozilla::LauncherVoidResult vr = SetupEnabledScenario();
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Make FailDisabled
+ mozilla::LauncherRegistryInfo info;
+ vr = info.DisableDueToFailure();
+ if (vr.isErr()) {
+ return vr.propagateErr();
+ }
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ // Attempt to launch when FailDisabled: Still be FailDisabled
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+ EXPECT_ENABLED_STATE_IS(EnabledState::FailDisabled);
+
+ // Change the timestamp
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Attempt to launch again: Launcher comes back
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Launcher);
+ EXPECT_COMMIT_IS_OK();
+
+ // Make ForceDisabled
+ vr = info.ReflectPrefToRegistry(false);
+ if (vr.isErr()) {
+ return vr.propagateErr();
+ }
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ // Attempt to launch when ForceDisabled: Still be ForceDisabled
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ // Change the timestamp
+ vr = WriteRegistryValueData<DWORD>(gImageValue, REG_DWORD, 0x12345678);
+ if (vr.isErr()) {
+ return vr;
+ }
+
+ // Attempt to launch again: Still be ForceDisabled
+ EXPECT_CHECK_RESULT_IS(ProcessType::Launcher, ProcessType::Browser);
+ EXPECT_COMMIT_IS_OK();
+ EXPECT_ENABLED_STATE_IS(EnabledState::ForceDisabled);
+
+ return mozilla::Ok();
+}
+
+int main(int argc, char* argv[]) {
+ auto fullPath = mozilla::GetFullBinaryPath();
+ if (!fullPath) {
+ return 1;
+ }
+
+ // Global setup for all tests
+ gBrowserValue = fullPath.get();
+ gBrowserValue += kBrowserSuffix;
+
+ gLauncherValue = fullPath.get();
+ gLauncherValue += kLauncherSuffix;
+
+ gImageValue = fullPath.get();
+ gImageValue += kImageSuffix;
+
+ gTelemetryValue = fullPath.get();
+ gTelemetryValue += kTelemetrySuffix;
+
+ mozilla::LauncherResult<DWORD> timestamp = 0;
+ RUN_TEST(timestamp, GetCurrentImageTimestamp);
+ gMyImageTimestamp = timestamp.unwrap();
+
+ auto onExit = mozilla::MakeScopeExit(
+ []() { mozilla::Unused << DeleteAllRegstryValues(); });
+
+ mozilla::LauncherVoidResult vr = mozilla::Ok();
+
+ // All testcases should call SetupEnabledScenario() or
+ // DeleteAllRegstryValues() to be order-independent
+ RUN_TEST(vr, TestEmptyRegistry);
+ RUN_TEST(vr, TestNormal);
+ RUN_TEST(vr, TestBrowserNoLauncher);
+ RUN_TEST(vr, TestLauncherNoBrowser);
+ RUN_TEST(vr, TestBrowserLessThanLauncher);
+ RUN_TEST(vr, TestImageTimestampChange);
+ RUN_TEST(vr, TestImageTimestampChangeWhenDisabled);
+ RUN_TEST(vr, TestDisableDueToFailure);
+ RUN_TEST(vr, TestPrefReflection);
+ RUN_TEST(vr, TestTelemetryConfig);
+ RUN_TEST(vr, TestCommitAbort);
+ RUN_TEST(vr, TestDisableDuringLauncherLaunch);
+ RUN_TEST(vr, TestDisableDuringBrowserLaunch);
+ RUN_TEST(vr, TestReEnable);
+
+ return 0;
+}
diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp
new file mode 100644
index 0000000000..c12550c3d6
--- /dev/null
+++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.cpp
@@ -0,0 +1,266 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <windows.h>
+// Support for _setmode
+#include <fcntl.h>
+#include <io.h>
+
+#include "nsWindowsRestart.cpp"
+
+// CommandLineToArgvW may return different values for argv[0] since it contains
+// the path to the binary that was executed so we prepend an argument that is
+// quoted with a space to prevent argv[1] being appended to argv[0].
+#define DUMMY_ARG1 L"\"arg 1\" "
+
+#ifndef MAXPATHLEN
+# ifdef PATH_MAX
+# define MAXPATHLEN PATH_MAX
+# elif defined(MAX_PATH)
+# define MAXPATHLEN MAX_PATH
+# elif defined(_MAX_PATH)
+# define MAXPATHLEN _MAX_PATH
+# elif defined(CCHMAXPATH)
+# define MAXPATHLEN CCHMAXPATH
+# else
+# define MAXPATHLEN 1024
+# endif
+#endif
+
+#define TEST_NAME L"XRE MakeCommandLine"
+#define MAX_TESTS 100
+
+// Verbose output can be enabled by defining VERBOSE 1
+#define VERBOSE 0
+
+// Compares compareCmdLine with the output of MakeCommandLine. This is
+// accomplished by converting inCmdLine to an argument list with
+// CommandLineToArgvW and converting it back to a command line with
+// MakeCommandLine.
+static int verifyCmdLineCreation(wchar_t* inCmdLine, wchar_t* compareCmdLine,
+ bool passes, int testNum) {
+ int rv = 0;
+ int i;
+ int inArgc;
+ int outArgc;
+ bool isEqual;
+
+ // When debugging with command lines containing Unicode characters greater
+ // than 255 you can set the mode for stdout to Unicode so the console will
+ // receive the correct characters though it won't display them properly unless
+ // the console's font has been set to one that can display the characters. You
+ // can also redirect the console output to a file that has been saved as
+ // Unicode to view the characters.
+ // _setmode(_fileno(stdout), _O_WTEXT);
+
+ // Prepend an additional argument to the command line. CommandLineToArgvW
+ // handles argv[0] differently than other arguments since argv[0] is the path
+ // to the binary being executed and MakeCommandLine only handles argv[1] and
+ // larger.
+ wchar_t* inCmdLineNew = (wchar_t*)malloc(
+ (wcslen(DUMMY_ARG1) + wcslen(inCmdLine) + 1) * sizeof(wchar_t));
+ wcscpy(inCmdLineNew, DUMMY_ARG1);
+ wcscat(inCmdLineNew, inCmdLine);
+ LPWSTR* inArgv = CommandLineToArgvW(inCmdLineNew, &inArgc);
+
+ auto outCmdLine = mozilla::MakeCommandLine(inArgc - 1, inArgv + 1);
+ wchar_t* outCmdLineNew = (wchar_t*)malloc(
+ (wcslen(DUMMY_ARG1) + wcslen(outCmdLine.get()) + 1) * sizeof(wchar_t));
+ wcscpy(outCmdLineNew, DUMMY_ARG1);
+ wcscat(outCmdLineNew, outCmdLine.get());
+ LPWSTR* outArgv = CommandLineToArgvW(outCmdLineNew, &outArgc);
+
+ if (VERBOSE) {
+ wprintf(L"\n");
+ wprintf(L"Verbose Output\n");
+ wprintf(L"--------------\n");
+ wprintf(L"Input command line : >%s<\n", inCmdLine);
+ wprintf(L"MakeComandLine output: >%s<\n", outCmdLine.get());
+ wprintf(L"Expected command line: >%s<\n", compareCmdLine);
+
+ wprintf(L"input argc : %d\n", inArgc - 1);
+ wprintf(L"output argc: %d\n", outArgc - 1);
+
+ for (i = 1; i < inArgc; ++i) {
+ wprintf(L"input argv[%d] : >%s<\n", i - 1, inArgv[i]);
+ }
+
+ for (i = 1; i < outArgc; ++i) {
+ wprintf(L"output argv[%d]: >%s<\n", i - 1, outArgv[i]);
+ }
+ wprintf(L"\n");
+ }
+
+ isEqual = (inArgc == outArgc);
+ if (!isEqual) {
+ wprintf(L"TEST-%s-FAIL | %s | ARGC Comparison (check %2d)\n",
+ passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum);
+ if (passes) {
+ rv = 1;
+ }
+ LocalFree(inArgv);
+ LocalFree(outArgv);
+ free(inCmdLineNew);
+ free(outCmdLineNew);
+ return rv;
+ }
+
+ for (i = 1; i < inArgc; ++i) {
+ isEqual = (wcscmp(inArgv[i], outArgv[i]) == 0);
+ if (!isEqual) {
+ wprintf(L"TEST-%s-FAIL | %s | ARGV Comparison (check %2d)\n",
+ passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum);
+ if (passes) {
+ rv = 1;
+ }
+ LocalFree(inArgv);
+ LocalFree(outArgv);
+ free(inCmdLineNew);
+ free(outCmdLineNew);
+ return rv;
+ }
+ }
+
+ isEqual = (wcscmp(outCmdLine.get(), compareCmdLine) == 0);
+ if (!isEqual) {
+ wprintf(L"TEST-%s-FAIL | %s | Command Line Comparison (check %2d)\n",
+ passes ? L"UNEXPECTED" : L"KNOWN", TEST_NAME, testNum);
+ if (passes) {
+ rv = 1;
+ }
+ LocalFree(inArgv);
+ LocalFree(outArgv);
+ free(inCmdLineNew);
+ free(outCmdLineNew);
+ return rv;
+ }
+
+ if (rv == 0) {
+ if (passes) {
+ wprintf(L"TEST-PASS | %s | check %2d\n", TEST_NAME, testNum);
+ } else {
+ wprintf(L"TEST-UNEXPECTED-PASS | %s | check %2d\n", TEST_NAME, testNum);
+ rv = 1;
+ }
+ }
+
+ LocalFree(inArgv);
+ LocalFree(outArgv);
+ free(inCmdLineNew);
+ free(outCmdLineNew);
+ return rv;
+}
+
+int wmain(int argc, wchar_t* argv[]) {
+ int i;
+ int rv = 0;
+
+ if (argc > 1 && (_wcsicmp(argv[1], L"-check-one") != 0 || argc != 3)) {
+ fwprintf(stderr,
+ L"Displays and validates output from MakeCommandLine.\n\n");
+ fwprintf(stderr, L"Usage: %s -check-one <test number>\n\n", argv[0]);
+ fwprintf(stderr,
+ L" <test number>\tSpecifies the test number to run from the\n");
+ fwprintf(stderr, L"\t\tTestXREMakeCommandLineWin.ini file.\n");
+ return 255;
+ }
+
+ wchar_t inifile[MAXPATHLEN];
+ if (!::GetModuleFileNameW(0, inifile, MAXPATHLEN)) {
+ wprintf(L"TEST-UNEXPECTED-FAIL | %s | GetModuleFileNameW\n", TEST_NAME);
+ return 2;
+ }
+
+ WCHAR* slash = wcsrchr(inifile, '\\');
+ if (!slash) {
+ wprintf(L"TEST-UNEXPECTED-FAIL | %s | wcsrchr\n", TEST_NAME);
+ return 3;
+ }
+
+ wcscpy(slash + 1, L"TestXREMakeCommandLineWin.ini\0");
+
+ for (i = 0; i < MAX_TESTS; ++i) {
+ wchar_t sInputVal[MAXPATHLEN];
+ wchar_t sOutputVal[MAXPATHLEN];
+ wchar_t sPassesVal[MAXPATHLEN];
+ wchar_t sInputKey[MAXPATHLEN];
+ wchar_t sOutputKey[MAXPATHLEN];
+ wchar_t sPassesKey[MAXPATHLEN];
+
+ if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) {
+ i = _wtoi(argv[2]);
+ }
+
+ _snwprintf(sInputKey, MAXPATHLEN, L"input_%d", i);
+ _snwprintf(sOutputKey, MAXPATHLEN, L"output_%d", i);
+ _snwprintf(sPassesKey, MAXPATHLEN, L"passes_%d", i);
+
+ if (!GetPrivateProfileStringW(L"MakeCommandLineTests", sInputKey, nullptr,
+ sInputVal, MAXPATHLEN, inifile)) {
+ if (i == 0 || (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0)) {
+ wprintf(L"TEST-UNEXPECTED-FAIL | %s | see following explanation:\n",
+ TEST_NAME);
+ wprintf(
+ L"ERROR: Either the TestXREMakeCommandLineWin.ini file doesn't "
+ L"exist\n");
+ if (argc > 1 && _wcsicmp(argv[1], L"-check-one") == 0 && argc == 3) {
+ wprintf(
+ L"ERROR: or the test is not defined in the MakeCommandLineTests "
+ L"section.\n");
+ } else {
+ wprintf(
+ L"ERROR: or it has no tests defined in the MakeCommandLineTests "
+ L"section.\n");
+ }
+ wprintf(L"ERROR: File: %s\n", inifile);
+ return 4;
+ }
+ break;
+ }
+
+ GetPrivateProfileStringW(L"MakeCommandLineTests", sOutputKey, nullptr,
+ sOutputVal, MAXPATHLEN, inifile);
+ GetPrivateProfileStringW(L"MakeCommandLineTests", sPassesKey, nullptr,
+ sPassesVal, MAXPATHLEN, inifile);
+
+ rv |= verifyCmdLineCreation(
+ sInputVal, sOutputVal,
+ (_wcsicmp(sPassesVal, L"false") == 0) ? FALSE : TRUE, i);
+
+ if (argc > 2 && _wcsicmp(argv[1], L"-check-one") == 0) {
+ break;
+ }
+ }
+
+ if (rv == 0) {
+ wprintf(L"TEST-PASS | %s | all checks passed\n", TEST_NAME);
+ } else {
+ wprintf(L"TEST-UNEXPECTED-FAIL | %s | some checks failed\n", TEST_NAME);
+ }
+
+ return rv;
+}
+
+#ifdef __MINGW32__
+
+/* MingW currently does not implement a wide version of the
+ startup routines. Workaround is to implement something like
+ it ourselves. See bug 411826 */
+
+# include <shellapi.h>
+
+int main(int argc, char** argv) {
+ LPWSTR commandLine = GetCommandLineW();
+ int argcw = 0;
+ LPWSTR* argvw = CommandLineToArgvW(commandLine, &argcw);
+ if (!argvw) return 127;
+
+ int result = wmain(argcw, argvw);
+ LocalFree(argvw);
+ return result;
+}
+#endif /* __MINGW32__ */
diff --git a/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini
new file mode 100644
index 0000000000..dbb529d1b1
--- /dev/null
+++ b/toolkit/xre/test/win/TestXREMakeCommandLineWin.ini
@@ -0,0 +1,94 @@
+; A typical MakeCommandLine test will contain an input and an output name value
+; pair. The value for input_xx is the input command line and the value for
+; output_xx is the expected output command line.
+;
+; A test that is known to fail can be added as follows. If the passes_xx name
+; value pair doesn't exist it defaults to true.
+; input_99=yabadaba
+; output_99=doo
+; passes_99=false
+;
+; If a value starts and ends with single or double quotation marks then it must
+; be enclosed in single or double quotation marks due to GetPrivateProfileString
+; discarding the outmost quotation marks. See GetPrivateProfileString on MSDN
+; for more information.
+; http://msdn.microsoft.com/en-us/library/ms724353.aspx
+
+[MakeCommandLineTests]
+input_0=a:\
+output_0=a:\
+
+input_1=""a:\""
+output_1=a:\"
+
+input_2=""a:\b c""
+output_2=""a:\b c""
+
+input_3=""a:\b c\""
+output_3=""a:\b c\"""
+
+input_4=""a:\b c\d e""
+output_4=""a:\b c\d e""
+
+input_5=""a:\b c\d e\""
+output_5=""a:\b c\d e\"""
+
+input_6=""a:\\""
+output_6=a:\
+
+input_7="a:\" "b:\c d"
+output_7=a:\" "b:\c d"
+
+input_8="a "b:\" "c:\d e""
+output_8="a "b:\" c:\d" e"
+
+input_9="abc" d e
+output_9=abc d e
+
+input_10="a b c" d e
+output_10="a b c" d e
+
+input_11=a\\\b d"e f"g h
+output_11=a\\\b "de fg" h
+
+input_12=a b
+output_12=a b
+
+input_13=""a b""
+output_13=""a b""
+
+input_14=a\\\"b c d
+output_14=a\\\"b c d
+
+input_15=a\\\"b c"
+output_15=a\\\"b c
+
+input_16=""a\\\b c"
+output_16=""a\\\b c""
+
+input_17=\"a
+output_17=\"a
+
+input_18=\\"a
+output_18=\a
+
+input_19=\\"\\\\"a
+output_19=\\\a
+
+input_20=\\"\\\\\"a
+output_20=\\\\\\\"a
+
+input_21="a\\\"b c\" d e
+output_21=""a\\\"b c\" d e""
+
+input_22=a\\\\\"b c" d e"
+output_22=a\\\\\"b "c d e"
+
+input_23=a:\b c\アルファ オメガ\d
+output_23=a:\b c\アルファ オメガ\d
+
+input_24=a:\b "c\アルファ オメガ\d"
+output_24=a:\b "c\アルファ オメガ\d"
+
+input_25=アルファ オメガ
+output_25=アルファ オメガ
diff --git a/toolkit/xre/test/win/mochitest/browser_env_path_long.ini b/toolkit/xre/test/win/mochitest/browser_env_path_long.ini
new file mode 100644
index 0000000000..9eb5a7b6e4
--- /dev/null
+++ b/toolkit/xre/test/win/mochitest/browser_env_path_long.ini
@@ -0,0 +1,7 @@
+[DEFAULT]
+# This path should expand to >32K characters. See bug 1753910.
+environment =
+ PATH="C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;C:/loremipsumdolorsitamet/adipiscingconsectetuerelit/;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%;%PATH%"
+
+[browser_env_path_long.js]
+skip-if = os != "win"
diff --git a/toolkit/xre/test/win/mochitest/browser_env_path_long.js b/toolkit/xre/test/win/mochitest/browser_env_path_long.js
new file mode 100644
index 0000000000..f720d8426b
--- /dev/null
+++ b/toolkit/xre/test/win/mochitest/browser_env_path_long.js
@@ -0,0 +1,15 @@
+// Any copyright is dedicated to the Public Domain.
+// http://creativecommons.org/publicdomain/zero/1.0/
+
+// Test that the browser starts even when PATH would expand to a detrimentally
+// long value.
+add_task(async function test() {
+ await BrowserTestUtils.withNewTab({ gBrowser, url: "about:blank" }, function(
+ browser
+ ) {
+ ok(
+ true,
+ "Browser should start even with potentially pathologically long PATH."
+ );
+ });
+});
diff --git a/toolkit/xre/test/win/moz.build b/toolkit/xre/test/win/moz.build
new file mode 100644
index 0000000000..8744f70698
--- /dev/null
+++ b/toolkit/xre/test/win/moz.build
@@ -0,0 +1,55 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+BROWSER_CHROME_MANIFESTS += [
+ "mochitest/browser_env_path_long.ini",
+]
+
+GeckoCppUnitTests(
+ [
+ "TestXREMakeCommandLineWin",
+ ],
+ linkage=None,
+)
+
+# This needs to be installed alongside the above unit test.
+FINAL_TARGET_FILES += [
+ "TestXREMakeCommandLineWin.ini",
+]
+
+DEFINES["NS_NO_XPCOM"] = True
+
+LOCAL_INCLUDES += [
+ "/config",
+ "/toolkit/xre",
+]
+
+DisableStlWrapping()
+USE_STATIC_LIBS = True
+
+OS_LIBS += [
+ "advapi32",
+ "comctl32",
+ "ole32",
+ "shell32",
+ "uuid",
+ "userenv",
+ "ws2_32",
+]
+
+if CONFIG["MOZ_LAUNCHER_PROCESS"]:
+ LOCAL_INCLUDES += [
+ "/toolkit/mozapps/update/common",
+ ]
+ GeckoCppUnitTests(
+ [
+ "TestLauncherRegistryInfo",
+ ],
+ linkage=None,
+ )
+ # Needed for TestLauncherRegistryInfo
+ for var in ("MOZ_APP_BASENAME", "MOZ_APP_VENDOR"):
+ DEFINES[var] = '"%s"' % CONFIG[var]
diff --git a/toolkit/xre/test/xpcshell.ini b/toolkit/xre/test/xpcshell.ini
new file mode 100644
index 0000000000..0efc83d30c
--- /dev/null
+++ b/toolkit/xre/test/xpcshell.ini
@@ -0,0 +1,20 @@
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+
+[DEFAULT]
+tags = native
+
+[test_launch_without_hang.js]
+run-sequentially = Has to launch application binary
+skip-if = toolkit == 'android'
+requesttimeoutfactor = 2
+[test_install_hash.js]
+# Android doesn't ship Subprocess.sys.mjs and debug builds output garbage that the
+# test cannot handle.
+skip-if =
+ toolkit == 'android'
+ debug
+ os == 'win' && msix # https://bugzilla.mozilla.org/show_bug.cgi?id=1807927
+support-files =
+ show_hash.js