diff options
Diffstat (limited to 'toolkit/xre/test')
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(©Data)); + 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(©Data)); + 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(©Data)); + 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 |