summaryrefslogtreecommitdiffstats
path: root/mozglue/misc/PreXULSkeletonUI.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'mozglue/misc/PreXULSkeletonUI.cpp')
-rw-r--r--mozglue/misc/PreXULSkeletonUI.cpp2238
1 files changed, 2238 insertions, 0 deletions
diff --git a/mozglue/misc/PreXULSkeletonUI.cpp b/mozglue/misc/PreXULSkeletonUI.cpp
new file mode 100644
index 0000000000..145a525f85
--- /dev/null
+++ b/mozglue/misc/PreXULSkeletonUI.cpp
@@ -0,0 +1,2238 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "PreXULSkeletonUI.h"
+
+#include <algorithm>
+#include <math.h>
+#include <limits.h>
+#include <cmath>
+#include <locale>
+#include <string>
+#include <objbase.h>
+#include <shlobj.h>
+
+#include "mozilla/Assertions.h"
+#include "mozilla/Attributes.h"
+#include "mozilla/BaseProfilerMarkers.h"
+#include "mozilla/CacheNtDllThunk.h"
+#include "mozilla/FStream.h"
+#include "mozilla/GetKnownFolderPath.h"
+#include "mozilla/HashFunctions.h"
+#include "mozilla/HelperMacros.h"
+#include "mozilla/glue/Debug.h"
+#include "mozilla/Maybe.h"
+#include "mozilla/mscom/ProcessRuntime.h"
+#include "mozilla/ResultVariant.h"
+#include "mozilla/ScopeExit.h"
+#include "mozilla/UniquePtr.h"
+#include "mozilla/UniquePtrExtensions.h"
+#include "mozilla/Unused.h"
+#include "mozilla/WindowsDpiAwareness.h"
+#include "mozilla/WindowsVersion.h"
+#include "mozilla/WindowsProcessMitigations.h"
+
+namespace mozilla {
+
+// ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
+// particular color that we will draw.
+struct ColorRect {
+ uint32_t color;
+ uint32_t borderColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderWidth;
+ int borderRadius;
+ bool flipIfRTL;
+};
+
+// DrawRect is mostly the same as ColorRect, but exists as an implementation
+// detail to simplify drawing borders. We draw borders as a strokeOnly rect
+// underneath an inner rect of a particular color. We also need to keep
+// track of the backgroundColor for rounding rects, in order to correctly
+// anti-alias.
+struct DrawRect {
+ uint32_t color;
+ uint32_t backgroundColor;
+ int x;
+ int y;
+ int width;
+ int height;
+ int borderRadius;
+ int borderWidth;
+ bool strokeOnly;
+};
+
+struct NormalizedRGB {
+ double r;
+ double g;
+ double b;
+};
+
+NormalizedRGB UintToRGB(uint32_t color) {
+ double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
+ double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
+ double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
+ return NormalizedRGB{r, g, b};
+}
+
+uint32_t RGBToUint(const NormalizedRGB& rgb) {
+ return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
+ (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
+ (static_cast<uint32_t>(rgb.b * 255.0) << 0);
+}
+
+double Lerp(double a, double b, double x) { return a + x * (b - a); }
+
+NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
+ return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
+}
+
+// Produces a smooth curve in [0,1] based on a linear input in [0,1]
+double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }
+
+static const wchar_t kPreXULSkeletonUIKeyPath[] =
+ L"SOFTWARE"
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";
+
+static bool sPreXULSkeletonUIShown = false;
+static bool sPreXULSkeletonUIEnabled = false;
+static HWND sPreXULSkeletonUIWindow;
+static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
+static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
+static HANDLE sPreXULSKeletonUIAnimationThread;
+static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE;
+
+static mozilla::mscom::ProcessRuntime* sProcessRuntime;
+static uint32_t* sPixelBuffer = nullptr;
+static Vector<ColorRect>* sAnimatedRects = nullptr;
+static int sTotalChromeHeight = 0;
+static volatile LONG sAnimationControlFlag = 0;
+static bool sMaximized = false;
+static int sNonClientVerticalMargins = 0;
+static int sNonClientHorizontalMargins = 0;
+static uint32_t sDpi = 0;
+
+// Color values needed by the animation loop
+static uint32_t sAnimationColor;
+static uint32_t sToolbarForegroundColor;
+
+static ThemeMode sTheme = ThemeMode::Invalid;
+
+typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
+static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
+typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
+GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL;
+typedef UINT(WINAPI* GetDpiForWindowProc)(HWND);
+GetDpiForWindowProc sGetDpiForWindow = NULL;
+typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*);
+RegisterClassWProc sRegisterClassW = NULL;
+typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR);
+LoadIconWProc sLoadIconW = NULL;
+typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR);
+LoadCursorWProc sLoadCursorW = NULL;
+typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int,
+ int, int, int, HWND, HMENU, HINSTANCE,
+ LPVOID);
+CreateWindowExWProc sCreateWindowExW = NULL;
+typedef BOOL(WINAPI* ShowWindowProc)(HWND, int);
+ShowWindowProc sShowWindow = NULL;
+typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT);
+SetWindowPosProc sSetWindowPos = NULL;
+typedef HDC(WINAPI* GetWindowDCProc)(HWND);
+GetWindowDCProc sGetWindowDC = NULL;
+typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH);
+FillRectProc sFillRect = NULL;
+typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ);
+DeleteObjectProc sDeleteObject = NULL;
+typedef int(WINAPI* ReleaseDCProc)(HWND, HDC);
+ReleaseDCProc sReleaseDC = NULL;
+typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD);
+MonitorFromWindowProc sMonitorFromWindow = NULL;
+typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO);
+GetMonitorInfoWProc sGetMonitorInfoW = NULL;
+typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR);
+SetWindowLongPtrWProc sSetWindowLongPtrW = NULL;
+typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int,
+ int, const VOID*, const BITMAPINFO*,
+ UINT, DWORD);
+StretchDIBitsProc sStretchDIBits = NULL;
+typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF);
+CreateSolidBrushProc sCreateSolidBrush = NULL;
+
+static int sWindowWidth;
+static int sWindowHeight;
+static double sCSSToDevPixelScaling;
+
+static Maybe<PreXULSkeletonUIError> sErrorReason;
+
+static const int kAnimationCSSPixelsPerFrame = 11;
+static const int kAnimationCSSExtraWindowSize = 300;
+
+// NOTE: these values were pulled out of thin air as round numbers that are
+// likely to be too big to be seen in practice. If we legitimately see windows
+// this big, we probably don't want to be drawing them on the CPU anyway.
+static const uint32_t kMaxWindowWidth = 1 << 16;
+static const uint32_t kMaxWindowHeight = 1 << 16;
+
+static const wchar_t* sEnabledRegSuffix = L"|Enabled";
+static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
+static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
+static const wchar_t* sWidthRegSuffix = L"|Width";
+static const wchar_t* sHeightRegSuffix = L"|Height";
+static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
+static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
+static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
+static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
+static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
+static const wchar_t* sThemeRegSuffix = L"|Theme";
+static const wchar_t* sFlagsRegSuffix = L"|Flags";
+static const wchar_t* sProgressSuffix = L"|Progress";
+
+std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
+ std::wstring result(prefix);
+ result.append(suffix);
+ return result;
+}
+
+// This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
+// included in standalone SpiderMonkey builds prohibits us from including that
+// file directly, and it hardly warrants its own header. Bug 1674920 tracks
+// only including this file for gecko-related builds.
+Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() {
+ DWORD bufLen = MAX_PATH;
+ UniquePtr<wchar_t[]> buf;
+ while (true) {
+ buf = MakeUnique<wchar_t[]>(bufLen);
+ DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
+ if (!retLen) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
+ bufLen *= 2;
+ continue;
+ }
+
+ break;
+ }
+
+ return buf;
+}
+
+// PreXULSkeletonUIDisallowed means that we don't even have the capacity to
+// enable the skeleton UI, whether because we're on a platform that doesn't
+// support it or because we launched with command line arguments that we don't
+// support. Some of these situations are transient, so we want to make sure we
+// don't mess with registry values in these scenarios that we may use in
+// other scenarios in which the skeleton UI is actually enabled.
+static bool PreXULSkeletonUIDisallowed() {
+ return sErrorReason.isSome() &&
+ (*sErrorReason == PreXULSkeletonUIError::Cmdline ||
+ *sErrorReason == PreXULSkeletonUIError::EnvVars);
+}
+
+// Note: this is specifically *not* a robust, multi-locale lowercasing
+// operation. It is not intended to be such. It is simply intended to match the
+// way in which we look for other instances of firefox to remote into.
+// See
+// https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
+static void MutateStringToLowercase(wchar_t* ptr) {
+ while (*ptr) {
+ wchar_t ch = *ptr;
+ if (ch >= L'A' && ch <= L'Z') {
+ *ptr = ch + (L'a' - L'A');
+ }
+ ++ptr;
+ }
+}
+
+static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() {
+ auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
+ if (!localAppDataPath) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
+ return Ok();
+ }
+
+ // Note: because we're in mozglue, we cannot easily access things from
+ // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
+ // mozglue, and rip out all of its usage of types defined in toolkit headers.
+ // However, it seems cleaner to just hash the bin path ourselves. We don't
+ // get quite the same robustness that `GetInstallHash` might provide, but
+ // we already don't have that with how we key our registry values, so it
+ // probably makes sense to just match those.
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ // Lowercase the binpath to match how we look for remote instances.
+ MutateStringToLowercase(binPath.get());
+
+ // The number of bytes * 2 characters per byte + 1 for the null terminator
+ uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
+ UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
+ // This isn't perfect - it's a 32-bit hash of the path to our executable. It
+ // could reasonably collide, or casing could potentially affect things, but
+ // the theory is that that should be uncommon enough and the failure case
+ // mild enough that this is fine.
+ uint32_t binPathHash = HashString(binPath.get());
+ swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);
+
+ std::wstring lockFilePath;
+ lockFilePath.append(localAppDataPath.get());
+ lockFilePath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
+ lockFilePath.append(installHash.get());
+
+ // We intentionally leak this file - that is okay, and (kind of) the point.
+ // We want to hold onto this handle until the application exits, and hold
+ // onto it with exclusive rights. If this check fails, then we assume that
+ // another instance of the executable is holding it, and thus return false.
+ sPreXULSKeletonUILockFile =
+ ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
+ 0, // No sharing - this is how the lock works
+ nullptr, CREATE_ALWAYS,
+ FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around
+ nullptr);
+ if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) {
+ return Err(PreXULSkeletonUIError::FailedGettingLock);
+ }
+
+ return Ok();
+}
+
+const char kGeneralSection[] = "[General]";
+const char kStartWithLastProfile[] = "StartWithLastProfile=";
+
+static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
+ bool inGeneral = false;
+ std::string line;
+ while (std::getline(iniContents, line)) {
+ size_t whitespace = 0;
+ while (line.length() > whitespace &&
+ (line[whitespace] == ' ' || line[whitespace] == '\t')) {
+ whitespace++;
+ }
+ line.erase(0, whitespace);
+
+ if (line.compare(kGeneralSection) == 0) {
+ inGeneral = true;
+ } else if (inGeneral) {
+ if (line[0] == '[') {
+ inGeneral = false;
+ } else {
+ if (line.find(kStartWithLastProfile) == 0) {
+ char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
+ if (val == '0') {
+ return false;
+ } else if (val == '1') {
+ return true;
+ }
+ }
+ }
+ }
+ }
+
+ // If we don't find it in the .ini file, we interpret that as true
+ return true;
+}
+
+static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() {
+ auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
+ if (!roamingAppData) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+ std::wstring profileDbPath(roamingAppData.get());
+ profileDbPath.append(
+ L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
+ IFStream profileDb(profileDbPath.c_str());
+ if (profileDb.fail()) {
+ return Err(PreXULSkeletonUIError::FilesystemFailure);
+ }
+
+ if (!ProfileDbHasStartWithLastProfile(profileDb)) {
+ return Err(PreXULSkeletonUIError::NoStartWithLastProfile);
+ }
+
+ return Ok();
+}
+
+// We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
+// failures in random places because we're in mozglue. Overall it should be
+// simpler and cleaner to just step around that issue with this class:
+class MOZ_RAII AutoCloseRegKey {
+ public:
+ explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
+ ~AutoCloseRegKey() { ::RegCloseKey(mKey); }
+
+ private:
+ HKEY mKey;
+};
+
+int CSSToDevPixels(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling + 0.5);
+}
+
+int CSSToDevPixels(int cssPixels, double scaling) {
+ return CSSToDevPixels((double)cssPixels, scaling);
+}
+
+int CSSToDevPixelsFloor(double cssPixels, double scaling) {
+ return floor(cssPixels * scaling);
+}
+
+// Some things appear to floor to device pixels rather than rounding. A good
+// example of this is border widths.
+int CSSToDevPixelsFloor(int cssPixels, double scaling) {
+ return CSSToDevPixelsFloor((double)cssPixels, scaling);
+}
+
+double SignedDistanceToCircle(double x, double y, double radius) {
+ return sqrt(x * x + y * y) - radius;
+}
+
+// For more details, see
+// https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
+// which was a reference for this function.
+double DistanceAntiAlias(double signedDistance) {
+ // Distance assumed to be in device pixels. We use an aa range of 0.5 for
+ // reasons detailed in the linked code above.
+ const double aaRange = 0.5;
+ double dist = 0.5 * signedDistance / aaRange;
+ if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
+ if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
+ return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
+}
+
+void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
+ if (rect.height <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
+ return;
+ }
+ if (rect.width <= 2 * rect.borderRadius) {
+ MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
+ return;
+ }
+
+ NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
+ NormalizedRGB rgbBlend = UintToRGB(rect.color);
+
+ for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
+ int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
+ int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* innermostPixelTopLeft =
+ lineStartTop + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelTopRight =
+ lineStartTop + rect.x + rect.width - rect.borderRadius;
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+ uint32_t* innermostPixelBottomLeft =
+ lineStartBottom + rect.x + rect.borderRadius - 1;
+ uint32_t* innermostPixelBottomRight =
+ lineStartBottom + rect.x + rect.width - rect.borderRadius;
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance =
+ SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
+ double alpha = DistanceAntiAlias(distance);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
+ uint32_t color = RGBToUint(rgb);
+
+ innermostPixelTopLeft[-columnIndex] = color;
+ innermostPixelTopRight[columnIndex] = color;
+ innermostPixelBottomLeft[-columnIndex] = color;
+ innermostPixelBottomRight[columnIndex] = color;
+ }
+
+ std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
+ std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
+ rect.color);
+ }
+}
+
+void RasterizeAnimatedRoundedRectTopAndBottom(
+ const ColorRect& colorRect, const uint32_t* animationLookup,
+ int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
+ int currentUpdateAreaMax, int animationMin) {
+ // We iterate through logical pixel rows here, from inside to outside, which
+ // for the top of the rounded rect means from bottom to top, and for the
+ // bottom of the rect means top to bottom. We paint pixels from left to
+ // right on the top and bottom rows at the same time for the entire animation
+ // window. (If the animation window does not overlap any rounded corners,
+ // however, we won't be called at all)
+ for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
+ int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
+ int yBottom =
+ colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;
+
+ uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
+ uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
+
+ // Add 0.5 to x and y to get the pixel center.
+ double pixelY = (double)rowIndex + 0.5;
+ for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ // The column index is the distance from the innermost pixel, which
+ // is different depending on whether we're on the left or right
+ // side of the rect. It will always be the max here, and if it's
+ // negative that just means we're outside the rounded area.
+ int columnIndex =
+ std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
+ x - ((int)colorRect.x + (int)colorRect.width -
+ (int)colorRect.borderRadius));
+
+ double alpha = 1.0;
+ if (columnIndex >= 0) {
+ double pixelX = (double)columnIndex + 0.5;
+ double distance = SignedDistanceToCircle(
+ pixelX, pixelY, (double)colorRect.borderRadius);
+ alpha = DistanceAntiAlias(distance);
+ }
+ // We don't do alpha blending for the antialiased pixels at the
+ // shape's border. It is not noticeable in the animation.
+ if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
+ // Overwrite the tail end of last frame's animation with the
+ // rect's normal, unanimated color.
+ uint32_t color = x < priorUpdateAreaMax
+ ? colorRect.color
+ : animationLookup[x - animationMin];
+ lineStartTop[x] = color;
+ lineStartBottom[x] = color;
+ }
+ }
+ }
+}
+
+void RasterizeColorRect(const ColorRect& colorRect) {
+ // We sometimes split our rect into two, to simplify drawing borders. If we
+ // have a border, we draw a stroke-only rect first, and then draw the smaller
+ // inner rect on top of it.
+ Vector<DrawRect, 2> drawRects;
+ Unused << drawRects.reserve(2);
+ if (colorRect.borderWidth == 0) {
+ DrawRect rect = {};
+ rect.color = colorRect.color;
+ rect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ rect.x = colorRect.x;
+ rect.y = colorRect.y;
+ rect.width = colorRect.width;
+ rect.height = colorRect.height;
+ rect.borderRadius = colorRect.borderRadius;
+ rect.strokeOnly = false;
+ drawRects.infallibleAppend(rect);
+ } else {
+ DrawRect borderRect = {};
+ borderRect.color = colorRect.borderColor;
+ borderRect.backgroundColor =
+ sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
+ borderRect.x = colorRect.x;
+ borderRect.y = colorRect.y;
+ borderRect.width = colorRect.width;
+ borderRect.height = colorRect.height;
+ borderRect.borderRadius = colorRect.borderRadius;
+ borderRect.borderWidth = colorRect.borderWidth;
+ borderRect.strokeOnly = true;
+ drawRects.infallibleAppend(borderRect);
+
+ DrawRect baseRect = {};
+ baseRect.color = colorRect.color;
+ baseRect.backgroundColor = borderRect.color;
+ baseRect.x = colorRect.x + colorRect.borderWidth;
+ baseRect.y = colorRect.y + colorRect.borderWidth;
+ baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
+ baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
+ baseRect.borderRadius =
+ std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
+ baseRect.borderWidth = 0;
+ baseRect.strokeOnly = false;
+ drawRects.infallibleAppend(baseRect);
+ }
+
+ for (const DrawRect& rect : drawRects) {
+ if (rect.height <= 0 || rect.width <= 0) {
+ continue;
+ }
+
+ // For rounded rectangles, the first thing we do is draw the top and
+ // bottom of the rectangle, with the more complicated logic below. After
+ // that we can just draw the vertically centered part of the rect like
+ // normal.
+ RasterizeRoundedRectTopAndBottom(rect);
+
+ // We then draw the flat, central portion of the rect (which in the case of
+ // non-rounded rects, is just the entire thing.)
+ int solidRectStartY =
+ std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
+ int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
+ sTotalChromeHeight);
+ for (int y = solidRectStartY; y < solidRectEndY; ++y) {
+ // For strokeOnly rects (used to draw borders), we just draw the left
+ // and right side here. Looping down a column of pixels is not the most
+ // cache-friendly thing, but it shouldn't be a big deal given the height
+ // of the urlbar.
+ // Also, if borderRadius is less than borderWidth, we need to ensure
+ // that we fully draw the top and bottom lines, so we make sure to check
+ // that we're inside the middle range range before excluding pixels.
+ if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
+ rect.y + rect.height - y > rect.borderWidth) {
+ int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
+ int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
+ int startXRight =
+ std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
+ int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStartLeft = lineStart + startXLeft;
+ uint32_t* dataEndLeft = lineStart + endXLeft;
+ uint32_t* dataStartRight = lineStart + startXRight;
+ uint32_t* dataEndRight = lineStart + endXRight;
+ std::fill(dataStartLeft, dataEndLeft, rect.color);
+ std::fill(dataStartRight, dataEndRight, rect.color);
+ } else {
+ int startX = std::clamp(rect.x, 0, sWindowWidth);
+ int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ uint32_t* dataStart = lineStart + startX;
+ uint32_t* dataEnd = lineStart + endX;
+ std::fill(dataStart, dataEnd, rect.color);
+ }
+ }
+ }
+}
+
+// Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
+// gradient which moves from left to right across the grey placeholder rects).
+// Takes in the rect to draw, together with a lookup table for the gradient,
+// and the bounds of the previous and current frame of the animation.
+bool RasterizeAnimatedRect(const ColorRect& colorRect,
+ const uint32_t* animationLookup,
+ int priorAnimationMin, int animationMin,
+ int animationMax) {
+ int rectMin = colorRect.x;
+ int rectMax = colorRect.x + colorRect.width;
+ bool animationWindowOverlaps =
+ rectMax >= priorAnimationMin && rectMin < animationMax;
+
+ int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
+ int priorUpdateAreaMax = std::min(rectMax, animationMin);
+ int currentUpdateAreaMin = std::max(rectMin, animationMin);
+ int currentUpdateAreaMax = std::min(rectMax, animationMax);
+
+ if (!animationWindowOverlaps) {
+ return false;
+ }
+
+ bool animationWindowOverlapsBorderRadius =
+ rectMin + colorRect.borderRadius > priorAnimationMin ||
+ rectMax - colorRect.borderRadius <= animationMax;
+
+ // If we don't overlap the left or right side of the rounded rectangle,
+ // just pretend it's not rounded. This is a small optimization but
+ // there's no point in doing all of this rounded rectangle checking if
+ // we aren't even overlapping
+ int borderRadius =
+ animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;
+
+ if (borderRadius > 0) {
+ // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
+ // first draw the rounded top and bottom, and then we draw the center
+ // rect.
+ RasterizeAnimatedRoundedRectTopAndBottom(
+ colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
+ currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
+ }
+
+ for (int y = colorRect.y + borderRadius;
+ y < colorRect.y + colorRect.height - borderRadius; ++y) {
+ uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
+ // Overwrite the tail end of last frame's animation with the rect's
+ // normal, unanimated color.
+ for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
+ lineStart[x] = colorRect.color;
+ }
+ // Then apply the animated color
+ for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
+ lineStart[x] = animationLookup[x - animationMin];
+ }
+ }
+
+ return true;
+}
+
+Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI(
+ HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan,
+ Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme,
+ const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
+ // NOTE: we opt here to paint a pixel buffer for the application chrome by
+ // hand, without using native UI library methods. Why do we do this?
+ //
+ // 1) It gives us a little bit more control, especially if we want to animate
+ // any of this.
+ // 2) It's actually more portable. We can do this on any platform where we
+ // can blit a pixel buffer to the screen, and it only has to change
+ // insofar as the UI is different on those platforms (and thus would have
+ // to change anyway.)
+ //
+ // The performance impact of this ought to be negligible. As far as has been
+ // observed, on slow reference hardware this might take up to a millisecond,
+ // for a startup which otherwise takes 30 seconds.
+ //
+ // The readability and maintainability are a greater concern. When the
+ // silhouette of Firefox's core UI changes, this code will likely need to
+ // change. However, for the foreseeable future, our skeleton UI will be mostly
+ // axis-aligned geometric shapes, and the thought is that any code which is
+ // manipulating raw pixels should not be *too* hard to maintain and
+ // understand so long as it is only painting such simple shapes.
+
+ sAnimationColor = currentTheme.animationColor;
+ sToolbarForegroundColor = currentTheme.toolbarForegroundColor;
+
+ bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
+ bool bookmarksToolbarShown =
+ flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
+ bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);
+
+ int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0;
+ int horizontalOffset =
+ sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin);
+
+ // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
+ int tabBarHeight = CSSToDevPixels(44, sCSSToDevPixelScaling);
+ int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling);
+ // found in tabs.inc.css, "--tab-block-margin"
+ int titlebarSpacerWidth = horizontalOffset +
+ CSSToDevPixels(2, sCSSToDevPixelScaling) -
+ selectedTabBorderWidth;
+ if (!sMaximized && !menubarShown) {
+ // found in tabs.inc.css, ".titlebar-spacer"
+ titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
+ }
+ // found in tabs.inc.css, "--tab-block-margin"
+ int selectedTabMarginTop =
+ CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
+ int selectedTabMarginBottom =
+ CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
+ int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ int selectedTabWidth =
+ CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth;
+ int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling);
+ // found in browser.css, "#PersonalToolbar"
+ int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
+ if (bookmarksToolbarShown) {
+ toolbarHeight += bookmarkToolbarHeight;
+ }
+ // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
+ int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling);
+ // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
+ int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);
+
+ int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling);
+ int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);
+
+ int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginRight =
+ rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
+ : CSSToDevPixels(9, sCSSToDevPixelScaling);
+ int toolbarPlaceholderMarginLeft =
+ rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
+ : CSSToDevPixels(11, sCSSToDevPixelScaling);
+ int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);
+
+ int menubarHeightDevPixels =
+ menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;
+
+ // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px
+ int urlbarMargin =
+ CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;
+
+ int urlbarTextPlaceholderMarginTop =
+ CSSToDevPixels(12, sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderMarginLeft =
+ CSSToDevPixels(12, sCSSToDevPixelScaling);
+ int urlbarTextPlaceHolderWidth = CSSToDevPixels(
+ std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
+ sCSSToDevPixelScaling);
+ int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
+
+ int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);
+
+ auto scopeExit = MakeScopeExit([&] {
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+ });
+
+ Vector<ColorRect> rects;
+
+ ColorRect menubar = {};
+ menubar.color = currentTheme.tabBarColor;
+ menubar.x = 0;
+ menubar.y = verticalOffset;
+ menubar.width = sWindowWidth;
+ menubar.height = menubarHeightDevPixels;
+ menubar.flipIfRTL = false;
+ if (!rects.append(menubar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+ // found in browser.css "--toolbarbutton-border-radius"
+ int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
+
+ // The (traditionally dark blue on Windows) background of the tab bar.
+ ColorRect tabBar = {};
+ tabBar.color = currentTheme.tabBarColor;
+ tabBar.x = 0;
+ tabBar.y = menubar.y + menubar.height;
+ tabBar.width = sWindowWidth;
+ tabBar.height = tabBarHeight;
+ tabBar.flipIfRTL = false;
+ if (!rects.append(tabBar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The initial selected tab
+ ColorRect selectedTab = {};
+ selectedTab.color = currentTheme.tabColor;
+ selectedTab.x = titlebarSpacerWidth;
+ selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop;
+ selectedTab.width = selectedTabWidth;
+ selectedTab.height =
+ tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom;
+ selectedTab.borderColor = currentTheme.tabOutlineColor;
+ selectedTab.borderWidth = selectedTabBorderWidth;
+ selectedTab.borderRadius = selectedTabBorderRadius;
+ selectedTab.flipIfRTL = true;
+ if (!rects.append(selectedTab)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // A placeholder rect representing text that will fill the selected tab title
+ ColorRect tabTextPlaceholder = {};
+ tabTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
+ tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
+ tabTextPlaceholder.width = tabPlaceholderBarWidth;
+ tabTextPlaceholder.height = tabPlaceholderBarHeight;
+ tabTextPlaceholder.borderRadius = placeholderBorderRadius;
+ tabTextPlaceholder.flipIfRTL = true;
+ if (!rects.append(tabTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The toolbar background
+ ColorRect toolbar = {};
+ toolbar.color = currentTheme.backgroundColor;
+ toolbar.x = 0;
+ toolbar.y = tabBar.y + tabBarHeight;
+ toolbar.width = sWindowWidth;
+ toolbar.height = toolbarHeight;
+ toolbar.flipIfRTL = false;
+ if (!rects.append(toolbar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The single-pixel divider line below the toolbar
+ ColorRect chromeContentDivider = {};
+ chromeContentDivider.color = currentTheme.chromeContentDividerColor;
+ chromeContentDivider.x = 0;
+ chromeContentDivider.y = toolbar.y + toolbar.height;
+ chromeContentDivider.width = sWindowWidth;
+ chromeContentDivider.height = chromeContentDividerHeight;
+ chromeContentDivider.flipIfRTL = false;
+ if (!rects.append(chromeContentDivider)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The urlbar
+ ColorRect urlbar = {};
+ urlbar.color = currentTheme.urlbarColor;
+ urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
+ urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
+ sCSSToDevPixelScaling);
+ urlbar.height = urlbarHeight;
+ urlbar.borderColor = currentTheme.urlbarBorderColor;
+ urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
+ urlbar.borderRadius = urlbarBorderRadius;
+ urlbar.flipIfRTL = false;
+ if (!rects.append(urlbar)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The urlbar placeholder rect representating text that will fill the urlbar
+ // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
+ // sWindowWidth.
+ ColorRect urlbarTextPlaceholder = {};
+ urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ urlbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
+ urlbarTextPlaceHolderWidth)
+ : (urlbar.x + urlbarTextPlaceholderMarginLeft);
+ urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
+ urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
+ urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
+ urlbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(urlbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The searchbar and placeholder text, if present
+ // This is y-aligned with the urlbar
+ bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
+ ColorRect searchbarRect = {};
+ if (hasSearchbar == true) {
+ searchbarRect.color = currentTheme.urlbarColor;
+ searchbarRect.x =
+ CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
+ horizontalOffset;
+ searchbarRect.y = urlbar.y;
+ searchbarRect.width = CSSToDevPixels(
+ searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
+ searchbarRect.height = urlbarHeight;
+ searchbarRect.borderRadius = urlbarBorderRadius;
+ searchbarRect.borderColor = currentTheme.urlbarBorderColor;
+ searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
+ searchbarRect.flipIfRTL = false;
+ if (!rects.append(searchbarRect)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ // The placeholder rect representating text that will fill the searchbar
+ // This uses the same margins as the urlbarTextPlaceholder
+ // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
+ // not sWindowWidth.
+ ColorRect searchbarTextPlaceholder = {};
+ searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
+ searchbarTextPlaceholder.x =
+ rtlEnabled
+ ? ((searchbarRect.x + searchbarRect.width) -
+ urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
+ : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
+ searchbarTextPlaceholder.y =
+ searchbarRect.y + urlbarTextPlaceholderMarginTop;
+ searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
+ searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
+ searchbarTextPlaceholder.flipIfRTL = false;
+ if (!rects.append(searchbarTextPlaceholder) ||
+ !sAnimatedRects->append(searchbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ // Determine where the placeholder rectangles should not go. This is
+ // anywhere occupied by a spring, urlbar, or searchbar
+ Vector<DevPixelSpan> noPlaceholderSpans;
+
+ DevPixelSpan urlbarSpan;
+ urlbarSpan.start = urlbar.x - urlbarMargin;
+ urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;
+
+ DevPixelSpan searchbarSpan;
+ if (hasSearchbar) {
+ searchbarSpan.start = searchbarRect.x - urlbarMargin;
+ searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
+ }
+
+ DevPixelSpan marginLeftPlaceholder;
+ marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
+ marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
+ if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ if (rtlEnabled) {
+ // If we're RTL, then the springs as ordered in the DOM will be from right
+ // to left, which will break our comparison logic below
+ springs.reverse();
+ }
+
+ for (auto spring : springs) {
+ DevPixelSpan springDevPixels;
+ springDevPixels.start =
+ CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
+ springDevPixels.end =
+ CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
+ if (!noPlaceholderSpans.append(springDevPixels)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ DevPixelSpan marginRightPlaceholder;
+ marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
+ marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
+ if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ Vector<DevPixelSpan, 2> spansToAdd;
+ Unused << spansToAdd.reserve(2);
+ spansToAdd.infallibleAppend(urlbarSpan);
+ if (hasSearchbar) {
+ spansToAdd.infallibleAppend(searchbarSpan);
+ }
+
+ for (auto& toAdd : spansToAdd) {
+ for (auto& span : noPlaceholderSpans) {
+ if (span.start > toAdd.start) {
+ if (!noPlaceholderSpans.insert(&span, toAdd)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ break;
+ }
+ }
+ }
+
+ for (size_t i = 1; i < noPlaceholderSpans.length(); i++) {
+ int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
+ int end = noPlaceholderSpans[i].start - placeholderMargin;
+ if (start + 2 * placeholderBorderRadius >= end) {
+ continue;
+ }
+
+ // The placeholder rects should all be y-aligned.
+ ColorRect placeholderRect = {};
+ placeholderRect.color = currentTheme.toolbarForegroundColor;
+ placeholderRect.x = start;
+ placeholderRect.y = urlbarTextPlaceholder.y;
+ placeholderRect.width = end - start;
+ placeholderRect.height = toolbarPlaceholderHeight;
+ placeholderRect.borderRadius = placeholderBorderRadius;
+ placeholderRect.flipIfRTL = false;
+ if (!rects.append(placeholderRect) ||
+ !sAnimatedRects->append(placeholderRect)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
+ if (sTotalChromeHeight > sWindowHeight) {
+ return Err(PreXULSkeletonUIError::BadWindowDimensions);
+ }
+
+ if (!sAnimatedRects->append(tabTextPlaceholder) ||
+ !sAnimatedRects->append(urlbarTextPlaceholder)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ sPixelBuffer =
+ (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
+
+ for (auto& rect : *sAnimatedRects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ }
+
+ for (auto& rect : rects) {
+ if (rtlEnabled && rect.flipIfRTL) {
+ rect.x = sWindowWidth - rect.x - rect.width;
+ }
+ rect.x = std::clamp(rect.x, 0, sWindowWidth);
+ rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
+ rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
+ rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
+ RasterizeColorRect(rect);
+ }
+
+ HDC hdc = sGetWindowDC(hWnd);
+ if (!hdc) {
+ return Err(PreXULSkeletonUIError::FailedGettingDC);
+ }
+ auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); });
+
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ // First, we just paint the chrome area with our pixel buffer
+ int scanLinesCopied = sStretchDIBits(
+ hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth,
+ sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY);
+ if (scanLinesCopied == 0) {
+ return Err(PreXULSkeletonUIError::FailedBlitting);
+ }
+
+ // Then, we just fill the rest with FillRect
+ RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
+ HBRUSH brush =
+ sCreateSolidBrush(RGB((currentTheme.backgroundColor & 0xff0000) >> 16,
+ (currentTheme.backgroundColor & 0x00ff00) >> 8,
+ (currentTheme.backgroundColor & 0x0000ff) >> 0));
+ int fillRectResult = sFillRect(hdc, &rect, brush);
+
+ sDeleteObject(brush);
+
+ if (fillRectResult == 0) {
+ return Err(PreXULSkeletonUIError::FailedFillingBottomRect);
+ }
+
+ scopeExit.release();
+ return Ok();
+}
+
+DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
+ if (!sPixelBuffer || sAnimatedRects->empty()) {
+ return 0;
+ }
+
+ // See the comments above the InterlockedIncrement calls below here - we
+ // atomically flip this up and down around sleep so the main thread doesn't
+ // have to wait for us if we're just sleeping.
+ if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
+ return 0;
+ }
+ // Sleep for two seconds - startups faster than this don't really benefit
+ // from an animation, and we don't want to take away cycles from them.
+ // Startups longer than this, however, are more likely to be blocked on IO,
+ // and thus animating does not substantially impact startup times for them.
+ ::Sleep(2000);
+ if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
+ return 0;
+ }
+
+ // On each of the animated rects (which happen to all be placeholder UI
+ // rects sharing the same color), we want to animate a gradient moving across
+ // the screen from left to right. The gradient starts as the rect's color on,
+ // the left side, changes to the background color of the window by the middle
+ // of the gradient, and then goes back down to the rect's color. To make this
+ // faster than interpolating between the two colors for each pixel for each
+ // frame, we simply create a lookup buffer in which we can look up the color
+ // for a particular offset into the gradient.
+ //
+ // To do this we just interpolate between the two values, and to give the
+ // gradient a smoother transition between colors, we transform the linear
+ // blend amount via the cubic smooth step function (SmoothStep3) to produce
+ // a smooth start and stop for the gradient. We do this for the first half
+ // of the gradient, and then simply copy that backwards for the second half.
+ //
+ // The CSS width of 80 chosen here is effectively is just to match the size
+ // of the animation provided in the design mockup. We define it in CSS pixels
+ // simply because the rest of our UI is based off of CSS scalings.
+ int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
+ UniquePtr<uint32_t[]> animationLookup =
+ MakeUnique<uint32_t[]>(animationWidth);
+ uint32_t animationColor = sAnimationColor;
+ NormalizedRGB rgbBlend = UintToRGB(animationColor);
+
+ // Build the first half of the lookup table
+ for (int i = 0; i < animationWidth / 2; ++i) {
+ uint32_t baseColor = sToolbarForegroundColor;
+ double blendAmountLinear =
+ static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
+ double blendAmount = SmoothStep3(blendAmountLinear);
+
+ NormalizedRGB rgbBase = UintToRGB(baseColor);
+ NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
+ animationLookup[i] = RGBToUint(rgb);
+ }
+
+ // Copy the first half of the lookup table into the second half backwards
+ for (int i = animationWidth / 2; i < animationWidth; ++i) {
+ int j = animationWidth - 1 - i;
+ if (j == animationWidth / 2) {
+ // If animationWidth is odd, we'll be left with one pixel at the center.
+ // Just color that as the animation color.
+ animationLookup[i] = animationColor;
+ } else {
+ animationLookup[i] = animationLookup[j];
+ }
+ }
+
+ // The bitmap info remains unchanged throughout the animation - this just
+ // effectively describes the contents of sPixelBuffer
+ BITMAPINFO chromeBMI = {};
+ chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
+ chromeBMI.bmiHeader.biWidth = sWindowWidth;
+ chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
+ chromeBMI.bmiHeader.biPlanes = 1;
+ chromeBMI.bmiHeader.biBitCount = 32;
+ chromeBMI.bmiHeader.biCompression = BI_RGB;
+
+ uint32_t animationIteration = 0;
+
+ int devPixelsPerFrame =
+ CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
+ int devPixelsExtraWindowSize =
+ CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);
+
+ if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
+ // The window got consumed before we were able to draw anything.
+ return 0;
+ }
+
+ while (true) {
+ // The gradient will move across the screen at devPixelsPerFrame at
+ // 60fps, and then loop back to the beginning. However, we add a buffer of
+ // devPixelsExtraWindowSize around the edges so it doesn't immediately
+ // jump back, giving it a more pulsing feel.
+ int animationMin = ((animationIteration * devPixelsPerFrame) %
+ (sWindowWidth + devPixelsExtraWindowSize)) -
+ devPixelsExtraWindowSize / 2;
+ int animationMax = animationMin + animationWidth;
+ // The priorAnimationMin is the beginning of the previous frame's animation.
+ // Since we only want to draw the bits of the image that we updated, we need
+ // to overwrite the left bit of the animation we drew last frame with the
+ // default color.
+ int priorAnimationMin = animationMin - devPixelsPerFrame;
+ animationMin = std::max(0, animationMin);
+ priorAnimationMin = std::max(0, priorAnimationMin);
+ animationMax = std::min((int)sWindowWidth, animationMax);
+
+ // The gradient only affects the specific rects that we put into
+ // sAnimatedRects. So we simply update those rects, and maintain a flag
+ // to avoid drawing when we don't need to.
+ bool updatedAnything = false;
+ for (ColorRect rect : *sAnimatedRects) {
+ bool hadUpdates =
+ RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
+ animationMin, animationMax);
+ updatedAnything = updatedAnything || hadUpdates;
+ }
+
+ if (updatedAnything) {
+ HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
+ if (!hdc) {
+ return 0;
+ }
+
+ sStretchDIBits(hdc, priorAnimationMin, 0,
+ animationMax - priorAnimationMin, sTotalChromeHeight,
+ priorAnimationMin, 0, animationMax - priorAnimationMin,
+ sTotalChromeHeight, sPixelBuffer, &chromeBMI,
+ DIB_RGB_COLORS, SRCCOPY);
+
+ sReleaseDC(sPreXULSkeletonUIWindow, hdc);
+ }
+
+ animationIteration++;
+
+ // We coordinate around our sleep here to ensure that the main thread does
+ // not wait on us if we're sleeping. If we don't get 1 here, it means the
+ // window has been consumed and we don't need to sleep. If in
+ // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
+ // incrementing, it means we're sleeping, and that function can assume that
+ // we will safely exit after the sleep because of the observed value of
+ // sAnimationControlFlag.
+ if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
+ return 0;
+ }
+
+ // Note: Sleep does not guarantee an exact time interval. If the system is
+ // busy, for instance, we could easily end up taking several frames longer,
+ // and really we could be left unscheduled for an arbitrarily long time.
+ // This is fine, and we don't really care. We could track how much time this
+ // actually took and jump the animation forward the appropriate amount, but
+ // its not even clear that that's a better user experience. So we leave this
+ // as simple as we can.
+ ::Sleep(16);
+
+ // Here we bring sAnimationControlFlag back down - again, if we don't get a
+ // 0 here it means we consumed the skeleton UI window in the mean time, so
+ // we can simply exit.
+ if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
+ return 0;
+ }
+ }
+}
+
+LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
+ LPARAM lParam) {
+ // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause
+ // screen readers to report spurious information when the skeleton appears.
+ if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) {
+ return E_FAIL;
+ }
+
+ // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
+ // sync.
+ if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
+ sEnableNonClientDpiScaling(hWnd);
+ }
+
+ // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
+ // nsWindow.cpp, and will need to be kept in sync.
+ if (msg == WM_NCCALCSIZE) {
+ RECT* clientRect =
+ wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
+ : (reinterpret_cast<RECT*>(lParam));
+
+ // These match the margins set in browser-tabsintitlebar.js with
+ // default prefs on Windows. Bug 1673092 tracks lining this up with
+ // that more correctly instead of hard-coding it.
+ int horizontalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ int verticalOffset =
+ sNonClientHorizontalMargins -
+ (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
+ clientRect->top = clientRect->top;
+ clientRect->left += horizontalOffset;
+ clientRect->right -= horizontalOffset;
+ clientRect->bottom -= verticalOffset;
+ return 0;
+ }
+
+ return ::DefWindowProcW(hWnd, msg, wParam, lParam);
+}
+
+bool IsSystemDarkThemeEnabled() {
+ DWORD result;
+ HKEY themeKey;
+ DWORD dataLen = sizeof(uint32_t);
+ LPCWSTR keyName =
+ L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
+
+ result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ AutoCloseRegKey closeKey(themeKey);
+
+ uint32_t lightThemeEnabled;
+ result = ::RegGetValueW(
+ themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
+ reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return false;
+ }
+ return !lightThemeEnabled;
+}
+
+ThemeColors GetTheme(ThemeMode themeId) {
+ ThemeColors theme = {};
+ switch (themeId) {
+ case ThemeMode::Dark:
+ // Dark theme or default theme when in dark mode
+
+ // controlled by css variable --toolbar-bgcolor
+ theme.backgroundColor = 0x2b2a33;
+ theme.tabColor = 0x42414d;
+ theme.toolbarForegroundColor = 0x6a6a6d;
+ theme.tabOutlineColor = 0x1c1b22;
+ // controlled by css variable --lwt-accent-color
+ theme.tabBarColor = 0x1c1b22;
+ // controlled by --toolbar-non-lwt-textcolor in browser.css
+ theme.chromeContentDividerColor = 0x0c0c0d;
+ // controlled by css variable --toolbar-field-background-color
+ theme.urlbarColor = 0x42414d;
+ theme.urlbarBorderColor = 0x42414d;
+ theme.animationColor = theme.urlbarColor;
+ return theme;
+ case ThemeMode::Light:
+ case ThemeMode::Default:
+ default:
+ // --toolbar-non-lwt-bgcolor in browser.css
+ theme.backgroundColor = 0xf9f9fb;
+ theme.tabColor = 0xf9f9fb;
+ theme.toolbarForegroundColor = 0xdddde1;
+ theme.tabOutlineColor = 0xdddde1;
+ // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
+ // (set to "hsl(235,33%,19%)")
+ theme.tabBarColor = 0xf0f0f4;
+ // --chrome-content-separator-color in browser.css
+ theme.chromeContentDividerColor = 0xe1e1e2;
+ // controlled by css variable --toolbar-color
+ theme.urlbarColor = 0xffffff;
+ theme.urlbarBorderColor = 0xdddde1;
+ theme.animationColor = theme.backgroundColor;
+ return theme;
+ }
+}
+
+Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() {
+ HKEY key;
+ DWORD disposition;
+ LSTATUS result =
+ ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
+ 0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
+
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
+ }
+
+ if (disposition == REG_CREATED_NEW_KEY ||
+ disposition == REG_OPENED_EXISTING_KEY) {
+ return key;
+ }
+
+ ::RegCloseKey(key);
+ return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
+}
+
+Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() {
+ HMODULE user32Dll = ::LoadLibraryW(L"user32");
+ HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
+
+ if (!user32Dll || !gdi32Dll) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+
+ auto getThreadDpiAwarenessContext =
+ (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
+ user32Dll, "GetThreadDpiAwarenessContext");
+ auto areDpiAwarenessContextsEqual =
+ (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
+ user32Dll, "AreDpiAwarenessContextsEqual");
+ if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
+ areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
+ DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
+ // EnableNonClientDpiScaling is optional - we can handle not having it.
+ sEnableNonClientDpiScaling =
+ (EnableNonClientDpiScalingProc)::GetProcAddress(
+ user32Dll, "EnableNonClientDpiScaling");
+ }
+
+ sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
+ user32Dll, "GetSystemMetricsForDpi");
+ if (!sGetSystemMetricsForDpi) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetDpiForWindow =
+ (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
+ if (!sGetDpiForWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sRegisterClassW =
+ (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
+ if (!sRegisterClassW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sCreateWindowExW =
+ (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
+ if (!sCreateWindowExW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
+ if (!sShowWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
+ if (!sSetWindowPos) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
+ if (!sGetWindowDC) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
+ if (!sFillRect) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
+ if (!sReleaseDC) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
+ if (!sLoadIconW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
+ if (!sLoadCursorW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sMonitorFromWindow =
+ (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow");
+ if (!sMonitorFromWindow) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sGetMonitorInfoW =
+ (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW");
+ if (!sGetMonitorInfoW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sSetWindowLongPtrW =
+ (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW");
+ if (!sSetWindowLongPtrW) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sStretchDIBits =
+ (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
+ if (!sStretchDIBits) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sCreateSolidBrush =
+ (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
+ if (!sCreateSolidBrush) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+ sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
+ if (!sDeleteObject) {
+ return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
+ }
+
+ return Ok();
+}
+
+// Strips "--", "-", and "/" from the front of the arg if one of those exists,
+// returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
+// prefixes are found, the argument is not a flag, and nullptr is returned.
+const char* NormalizeFlag(const char* arg) {
+ if (strstr(arg, "--") == arg) {
+ return arg + 2;
+ }
+
+ if (arg[0] == '-') {
+ return arg + 1;
+ }
+
+ if (arg[0] == '/') {
+ return arg + 1;
+ }
+
+ return nullptr;
+}
+
+static bool EnvHasValue(const char* name) {
+ const char* val = getenv(name);
+ return (val && *val);
+}
+
+// Ensures that we only see arguments in the command line which are acceptable.
+// This is based on manual inspection of the list of arguments listed in the MDN
+// page for Gecko/Firefox commandline options:
+// https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
+// Broadly speaking, we want to reject any argument which causes us to show
+// something other than the default window at its normal size. Here is a non-
+// exhaustive list of command line options we want to *exclude*:
+//
+// -ProfileManager : This will display the profile manager window, which does
+// not match the skeleton UI at all.
+//
+// -CreateProfile : This will display a firefox window with the default
+// screen position and size, and not the position and size
+// which we have recorded in the registry.
+//
+// -P <profile> : This could cause us to display firefox with a position
+// and size of a different profile than that in which we
+// were previously running.
+//
+// -width, -height : This will cause the width and height values in the
+// registry to be incorrect.
+//
+// -kiosk : See above.
+//
+// -headless : This one should be rather obvious.
+//
+// -migration : This will start with the import wizard, which of course
+// does not match the skeleton UI.
+//
+// -private-window : This is tricky, but the colors of the main content area
+// make this not feel great with the white content of the
+// default skeleton UI.
+//
+// NOTE: we generally want to skew towards erroneous rejections of the command
+// line rather than erroneous approvals. The consequence of a bad rejection
+// is that we don't show the skeleton UI, which is business as usual. The
+// consequence of a bad approval is that we show it when we're not supposed to,
+// which is visually jarring and can also be unpredictable - there's no
+// guarantee that the code which handles the non-default window is set up to
+// properly handle the transition from the skeleton UI window.
+static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments(
+ int argc, char** argv, bool* explicitProfile) {
+ const char* approvedArgumentsArray[] = {
+ // These won't cause the browser to be visualy different in any way
+ "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
+ "attach-console", "wait-for-browser", "osint",
+
+ // These will cause the chrome to be a bit different or extra windows to
+ // be created, but overall the skeleton UI should still be broadly
+ // correct enough.
+ "new-tab", "new-window",
+
+ // To the extent possible, we want to ensure that existing tests cover
+ // the skeleton UI, so we need to allow marionette
+ "marionette",
+
+ // These will cause the content area to appear different, but won't
+ // meaningfully affect the chrome
+ "preferences", "search", "url",
+
+#ifndef MOZILLA_OFFICIAL
+ // On local builds, we want to allow -profile, because it's how `mach run`
+ // operates, and excluding that would create an unnecessary blind spot for
+ // Firefox devs.
+ "profile"
+#endif
+
+ // There are other arguments which are likely okay. However, they are
+ // not included here because this list is not intended to be
+ // exhaustive - it only intends to green-light some somewhat commonly
+ // used arguments. We want to err on the side of an unnecessary
+ // rejection of the command line.
+ };
+
+ int approvedArgumentsArraySize =
+ sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
+ Vector<const char*> approvedArguments;
+ if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+
+ for (int i = 0; i < approvedArgumentsArraySize; ++i) {
+ approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
+ }
+
+#ifdef MOZILLA_OFFICIAL
+ int profileArgIndex = -1;
+ // If we're running mochitests or direct marionette tests, those specify a
+ // temporary profile, and we want to ensure that we get the added coverage
+ // from those.
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (flag && !strcmp(flag, "marionette")) {
+ if (!approvedArguments.append("profile")) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ profileArgIndex = approvedArguments.length() - 1;
+
+ break;
+ }
+ }
+#else
+ int profileArgIndex = approvedArguments.length() - 1;
+#endif
+
+ for (int i = 1; i < argc; ++i) {
+ const char* flag = NormalizeFlag(argv[i]);
+ if (!flag) {
+ // If this is not a flag, then we interpret it as a URL, similar to
+ // BrowserContentHandler.sys.mjs. Some command line options take
+ // additional arguments, which may or may not be URLs. We don't need to
+ // know this, because we don't need to parse them out; we just rely on the
+ // assumption that if arg X is actually a parameter for the preceding
+ // arg Y, then X must not look like a flag (starting with "--", "-",
+ // or "/").
+ //
+ // The most important thing here is the assumption that if something is
+ // going to meaningfully alter the appearance of the window itself, it
+ // must be a flag.
+ continue;
+ }
+
+ bool approved = false;
+ for (const char* approvedArg : approvedArguments) {
+ // We do a case-insensitive compare here with _stricmp. Even though some
+ // of these arguments are *not* read as case-insensitive, others *are*.
+ // Similar to the flag logic above, we don't really care about this
+ // distinction, because we don't need to parse the arguments - we just
+ // rely on the assumption that none of the listed flags in our
+ // approvedArguments are overloaded in such a way that a different
+ // casing would visually alter the firefox window.
+ if (!_stricmp(flag, approvedArg)) {
+ approved = true;
+
+ if (i == profileArgIndex) {
+ *explicitProfile = true;
+ }
+ break;
+ }
+ }
+
+ if (!approved) {
+ return Err(PreXULSkeletonUIError::Cmdline);
+ }
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() {
+ if (EnvHasValue("MOZ_SAFE_MODE_RESTART") ||
+ EnvHasValue("MOZ_APP_SILENT_START") ||
+ EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") ||
+ (EnvHasValue("XRE_PROFILE_PATH") &&
+ !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) {
+ return Err(PreXULSkeletonUIError::EnvVars);
+ }
+
+ return Ok();
+}
+
+static bool VerifyWindowDimensions(uint32_t windowWidth,
+ uint32_t windowHeight) {
+ return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
+}
+
+static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans(
+ HKEY regKey, const std::wstring& valueName) {
+ DWORD dataLen = 0;
+ LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr,
+ nullptr, nullptr, &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ if (dataLen % (2 * sizeof(double)) != 0) {
+ return Err(PreXULSkeletonUIError::CorruptData);
+ }
+
+ auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
+ if (!buffer) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
+ nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ Vector<CSSPixelSpan> resultVector;
+ double* asDoubles = reinterpret_cast<double*>(buffer.get());
+ for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) {
+ CSSPixelSpan span = {};
+ span.start = *(asDoubles++);
+ span.end = *(asDoubles++);
+ if (!resultVector.append(span)) {
+ return Err(PreXULSkeletonUIError::OOM);
+ }
+ }
+
+ return resultVector;
+}
+
+static Result<double, PreXULSkeletonUIError> ReadRegDouble(
+ HKEY regKey, const std::wstring& valueName) {
+ double value = 0;
+ DWORD dataLen = sizeof(double);
+ LSTATUS result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
+ nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
+ if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return value;
+}
+
+static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint(
+ HKEY regKey, const std::wstring& valueName) {
+ DWORD value = 0;
+ DWORD dataLen = sizeof(uint32_t);
+ LSTATUS result =
+ ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD,
+ nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return value;
+}
+
+static Result<bool, PreXULSkeletonUIError> ReadRegBool(
+ HKEY regKey, const std::wstring& valueName) {
+ uint32_t value;
+ MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName));
+ return !!value;
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans(
+ HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans,
+ int spansLength) {
+ // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's
+ // also trivial to just copy them into a buffer of doubles.
+ auto doubles = MakeUnique<double[]>(spansLength * 2);
+ for (int i = 0; i < spansLength; ++i) {
+ doubles[i * 2] = spans[i].start;
+ doubles[i * 2 + 1] = spans[i].end;
+ }
+
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
+ reinterpret_cast<const BYTE*>(doubles.get()),
+ spansLength * sizeof(double) * 2);
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegDouble(
+ HKEY regKey, const std::wstring& valueName, double value) {
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
+ reinterpret_cast<const BYTE*>(&value), sizeof(value));
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegUint(
+ HKEY regKey, const std::wstring& valueName, uint32_t value) {
+ LSTATUS result =
+ ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD,
+ reinterpret_cast<PBYTE>(&value), sizeof(value));
+ if (result != ERROR_SUCCESS) {
+ return Err(PreXULSkeletonUIError::RegistryError);
+ }
+
+ return Ok();
+}
+
+static Result<Ok, PreXULSkeletonUIError> WriteRegBool(
+ HKEY regKey, const std::wstring& valueName, bool value) {
+ return WriteRegUint(regKey, valueName, value ? 1 : 0);
+}
+
+static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl(
+ HINSTANCE hInstance, int argc, char** argv) {
+ // Initializing COM below may load modules via SetWindowHookEx, some of
+ // which may modify the executable's IAT for ntdll.dll. If that happens,
+ // this browser process fails to launch sandbox processes because we cannot
+ // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp).
+ // To prevent that, we cache the intact IAT before COM initialization.
+ // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will
+ // also prevent an injected module from parsing the PE headers and modifying
+ // the IAT. Therefore, we can skip CacheNtDllThunk().
+ if (!mozilla::IsEafPlusEnabled()) {
+ CacheNtDllThunk();
+ }
+
+ // NOTE: it's important that we initialize sProcessRuntime before showing a
+ // window. Historically we ran into issues where showing the window would
+ // cause an accessibility win event to fire, which could cause in-process
+ // system or third party components to initialize COM and prevent us from
+ // initializing it with important settings we need.
+
+ // Some COM settings are global to the process and must be set before any non-
+ // trivial COM is run in the application. Since these settings may affect
+ // stability, we should instantiate COM ASAP so that we can ensure that these
+ // global settings are configured before anything can interfere.
+ sProcessRuntime = new mscom::ProcessRuntime(
+ mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent);
+
+ const TimeStamp skeletonStart = TimeStamp::Now();
+
+ if (!IsWin10OrLater()) {
+ return Err(PreXULSkeletonUIError::Ineligible);
+ }
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ std::wstring regProgressName =
+ GetRegValueName(binPath.get(), sProgressSuffix);
+ auto progressResult = ReadRegUint(regKey, regProgressName);
+ if (!progressResult.isErr() &&
+ progressResult.unwrap() !=
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) {
+ return Err(PreXULSkeletonUIError::CrashedOnce);
+ }
+
+ MOZ_TRY(
+ WriteRegUint(regKey, regProgressName,
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Started)));
+ auto writeCompletion = MakeScopeExit([&] {
+ Unused << WriteRegUint(
+ regKey, regProgressName,
+ static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed));
+ });
+
+ MOZ_TRY(GetSkeletonUILock());
+
+ bool explicitProfile = false;
+ MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile));
+ MOZ_TRY(ValidateEnvVars());
+
+ auto enabledResult =
+ ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix));
+ if (enabledResult.isErr()) {
+ return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist);
+ }
+ if (!enabledResult.unwrap()) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+ sPreXULSkeletonUIEnabled = true;
+
+ MOZ_ASSERT(!sAnimatedRects);
+ sAnimatedRects = new Vector<ColorRect>();
+
+ MOZ_TRY(LoadGdi32AndUser32Procedures());
+
+ if (!explicitProfile) {
+ MOZ_TRY(CheckForStartWithLastProfile());
+ }
+
+ WNDCLASSW wc;
+ wc.style = CS_DBLCLKS;
+ wc.lpfnWndProc = PreXULSkeletonUIProc;
+ wc.cbClsExtra = 0;
+ wc.cbWndExtra = 0;
+ wc.hInstance = hInstance;
+ wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
+ wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
+ wc.hbrBackground = nullptr;
+ wc.lpszMenuName = nullptr;
+
+ // TODO: just ensure we disable this if we've overridden the window class
+ wc.lpszClassName = L"MozillaWindowClass";
+
+ if (!sRegisterClassW(&wc)) {
+ return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass);
+ }
+
+ uint32_t screenX;
+ MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sScreenXRegSuffix)));
+ uint32_t screenY;
+ MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sScreenYRegSuffix)));
+ uint32_t windowWidth;
+ MOZ_TRY_VAR(
+ windowWidth,
+ ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix)));
+ uint32_t windowHeight;
+ MOZ_TRY_VAR(
+ windowHeight,
+ ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix)));
+ MOZ_TRY_VAR(
+ sMaximized,
+ ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix)));
+ MOZ_TRY_VAR(
+ sCSSToDevPixelScaling,
+ ReadRegDouble(regKey, GetRegValueName(binPath.get(),
+ sCssToDevPixelScalingRegSuffix)));
+ Vector<CSSPixelSpan> urlbar;
+ MOZ_TRY_VAR(urlbar,
+ ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix)));
+ Vector<CSSPixelSpan> searchbar;
+ MOZ_TRY_VAR(searchbar,
+ ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix)));
+ Vector<CSSPixelSpan> springs;
+ MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(),
+ sSpringsCSSRegSuffix)));
+
+ if (urlbar.empty() || searchbar.empty()) {
+ return Err(PreXULSkeletonUIError::CorruptData);
+ }
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ uint32_t flagsUint;
+ MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sFlagsRegSuffix)));
+ flags.deserialize(flagsUint);
+
+ if (flags.contains(SkeletonUIFlag::TouchDensity) ||
+ flags.contains(SkeletonUIFlag::CompactDensity)) {
+ return Err(PreXULSkeletonUIError::BadUIDensity);
+ }
+
+ uint32_t theme;
+ MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(),
+ sThemeRegSuffix)));
+ ThemeMode themeMode = static_cast<ThemeMode>(theme);
+ if (themeMode == ThemeMode::Default) {
+ if (IsSystemDarkThemeEnabled() == true) {
+ themeMode = ThemeMode::Dark;
+ }
+ }
+ ThemeColors currentTheme = GetTheme(themeMode);
+
+ if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
+ return Err(PreXULSkeletonUIError::BadWindowDimensions);
+ }
+
+ int showCmd = SW_SHOWNORMAL;
+ DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
+ if (sMaximized) {
+ showCmd = SW_SHOWMAXIMIZED;
+ windowStyle |= WS_MAXIMIZE;
+ }
+
+ sPreXULSkeletonUIWindow =
+ sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
+ L"", windowStyle, screenX, screenY, windowWidth,
+ windowHeight, nullptr, nullptr, hInstance, nullptr);
+ if (!sPreXULSkeletonUIWindow) {
+ return Err(PreXULSkeletonUIError::CreateWindowFailed);
+ }
+
+ sShowWindow(sPreXULSkeletonUIWindow, showCmd);
+
+ sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
+ sNonClientHorizontalMargins =
+ sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+ sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
+ sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
+
+ if (sMaximized) {
+ HMONITOR monitor =
+ sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
+ if (!monitor) {
+ // NOTE: we specifically don't clean up the window here. If we're unable
+ // to finish setting up the window how we want it, we still need to keep
+ // it around and consume it with the first real toplevel window we
+ // create, to avoid flickering.
+ return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
+ }
+ MONITORINFO mi = {sizeof(MONITORINFO)};
+ if (!sGetMonitorInfoW(monitor, &mi)) {
+ return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
+ }
+
+ sWindowWidth =
+ mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2;
+ sWindowHeight =
+ mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2;
+ } else {
+ sWindowWidth = static_cast<int>(windowWidth);
+ sWindowHeight = static_cast<int>(windowHeight);
+ }
+
+ sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
+ SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
+ SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
+ MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0],
+ springs, currentTheme, flags));
+ if (sAnimatedRects) {
+ sPreXULSKeletonUIAnimationThread = ::CreateThread(
+ nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
+ }
+
+ BASE_PROFILER_MARKER_UNTYPED(
+ "CreatePreXULSkeletonUI", OTHER,
+ MarkerTiming::IntervalUntilNowFrom(skeletonStart));
+
+ return Ok();
+}
+
+void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
+ char** argv) {
+ auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv);
+
+ if (result.isErr()) {
+ sErrorReason.emplace(result.unwrapErr());
+ }
+}
+
+void CleanupProcessRuntime() {
+ delete sProcessRuntime;
+ sProcessRuntime = nullptr;
+}
+
+bool WasPreXULSkeletonUIMaximized() { return sMaximized; }
+
+bool GetPreXULSkeletonUIWasShown() {
+ return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow;
+}
+
+HWND ConsumePreXULSkeletonUIHandle() {
+ // NOTE: we need to make sure that everything that runs here is a no-op if
+ // it failed to be set, which is a possibility. If anything fails to be set
+ // we don't want to clean everything up right away, because if we have a
+ // blank window up, we want that to stick around and get consumed by nsWindow
+ // as normal, otherwise the window will flicker in and out, which we imagine
+ // is unpleasant.
+
+ // If we don't get 1 here, it means the thread is actually just sleeping, so
+ // we don't need to worry about giving out ownership of the window, because
+ // the thread will simply exit after its sleep. However, if it is 1, we need
+ // to wait for the thread to exit to be safe, as it could be doing anything.
+ if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
+ ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
+ }
+ ::CloseHandle(sPreXULSKeletonUIAnimationThread);
+ sPreXULSKeletonUIAnimationThread = nullptr;
+ HWND result = sPreXULSkeletonUIWindow;
+ sPreXULSkeletonUIWindow = nullptr;
+ free(sPixelBuffer);
+ sPixelBuffer = nullptr;
+ delete sAnimatedRects;
+ sAnimatedRects = nullptr;
+
+ return result;
+}
+
+Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason() {
+ return sErrorReason;
+}
+
+Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
+ const SkeletonUISettings& settings) {
+ if (!sPreXULSkeletonUIEnabled) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+
+ MOZ_TRY(WriteRegUint(regKey,
+ GetRegValueName(binPath.get(), sScreenXRegSuffix),
+ settings.screenX));
+ MOZ_TRY(WriteRegUint(regKey,
+ GetRegValueName(binPath.get(), sScreenYRegSuffix),
+ settings.screenY));
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix),
+ settings.width));
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix),
+ settings.height));
+
+ MOZ_TRY(WriteRegBool(regKey,
+ GetRegValueName(binPath.get(), sMaximizedRegSuffix),
+ settings.maximized));
+
+ EnumSet<SkeletonUIFlag, uint32_t> flags;
+ if (settings.menubarShown) {
+ flags += SkeletonUIFlag::MenubarShown;
+ }
+ if (settings.bookmarksToolbarShown) {
+ flags += SkeletonUIFlag::BookmarksToolbarShown;
+ }
+ if (settings.rtlEnabled) {
+ flags += SkeletonUIFlag::RtlEnabled;
+ }
+ if (settings.uiDensity == SkeletonUIDensity::Touch) {
+ flags += SkeletonUIFlag::TouchDensity;
+ }
+ if (settings.uiDensity == SkeletonUIDensity::Compact) {
+ flags += SkeletonUIFlag::CompactDensity;
+ }
+
+ uint32_t flagsUint = flags.serialize();
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix),
+ flagsUint));
+
+ MOZ_TRY(WriteRegDouble(
+ regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix),
+ settings.cssToDevPixelScaling));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix),
+ &settings.urlbarSpan, 1));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix),
+ &settings.searchbarSpan, 1));
+ MOZ_TRY(WriteRegCSSPixelSpans(
+ regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix),
+ settings.springs.begin(), settings.springs.length()));
+
+ return Ok();
+}
+
+MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }
+
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
+ bool value) {
+ // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
+ // ignore changes to the registry. An example of how things could be bad if
+ // we didn't: someone running firefox with the -profile argument could
+ // turn the skeleton UI on or off for the default profile. Turning it off
+ // maybe isn't so bad (though it's likely still incorrect), but turning it
+ // on could be bad if the user had specifically disabled it for a profile for
+ // some reason. Ultimately there's no correct decision here, and the
+ // messiness of this is just a consequence of sharing the registry values
+ // across profiles. However, whatever ill effects we observe should be
+ // correct themselves after one session.
+ if (PreXULSkeletonUIDisallowed()) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+ MOZ_TRY(WriteRegBool(
+ regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value));
+
+ if (!sPreXULSkeletonUIEnabled && value) {
+ // We specifically don't care if we fail to get this lock. We just want to
+ // do our best effort to lock it so that future instances don't create
+ // skeleton UIs while we're still running, since they will immediately exit
+ // and tell us to open a new window.
+ Unused << GetSkeletonUILock();
+ }
+
+ sPreXULSkeletonUIEnabled = value;
+
+ return Ok();
+}
+
+MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
+ ThemeMode theme) {
+ if (theme == sTheme) {
+ return Ok();
+ }
+ sTheme = theme;
+
+ // If we fail below, invalidate sTheme
+ auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; });
+
+ HKEY regKey;
+ MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
+ AutoCloseRegKey closeKey(regKey);
+
+ UniquePtr<wchar_t[]> binPath;
+ MOZ_TRY_VAR(binPath, GetBinaryPath());
+ MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix),
+ static_cast<uint32_t>(theme)));
+
+ invalidateTheme.release();
+ return Ok();
+}
+
+MFBT_API void PollPreXULSkeletonUIEvents() {
+ if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
+ MSG outMsg = {};
+ PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
+ }
+}
+
+Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() {
+ if (!sPreXULSkeletonUIEnabled) {
+ return Err(PreXULSkeletonUIError::Disabled);
+ }
+
+ ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1");
+
+ // We assume that we are going to exit the application very shortly after
+ // this. It should thus be fine to release this lock, and we'll need to,
+ // since during a restart we launch the new instance before closing this
+ // one.
+ if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
+ ::CloseHandle(sPreXULSKeletonUILockFile);
+ }
+ return Ok();
+}
+
+} // namespace mozilla