Bug 1852545 [wpt PR 41834] - [FedCM] Update wpt tests for AccountAutoSelectedFlag...
[gecko.git] / mozglue / misc / PreXULSkeletonUI.cpp
blob1ed0a18a14c04cc6f9dedcd5d4fe02db97602052
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "PreXULSkeletonUI.h"
9 #include <algorithm>
10 #include <math.h>
11 #include <limits.h>
12 #include <cmath>
13 #include <locale>
14 #include <string>
15 #include <objbase.h>
16 #include <shlobj.h>
18 #include "mozilla/Assertions.h"
19 #include "mozilla/Attributes.h"
20 #include "mozilla/BaseProfilerMarkers.h"
21 #include "mozilla/CacheNtDllThunk.h"
22 #include "mozilla/FStream.h"
23 #include "mozilla/GetKnownFolderPath.h"
24 #include "mozilla/HashFunctions.h"
25 #include "mozilla/HelperMacros.h"
26 #include "mozilla/glue/Debug.h"
27 #include "mozilla/Maybe.h"
28 #include "mozilla/mscom/ProcessRuntime.h"
29 #include "mozilla/ResultVariant.h"
30 #include "mozilla/ScopeExit.h"
31 #include "mozilla/UniquePtr.h"
32 #include "mozilla/UniquePtrExtensions.h"
33 #include "mozilla/Unused.h"
34 #include "mozilla/WindowsDpiAwareness.h"
35 #include "mozilla/WindowsProcessMitigations.h"
37 namespace mozilla {
39 // ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
40 // particular color that we will draw.
41 struct ColorRect {
42 uint32_t color;
43 uint32_t borderColor;
44 int x;
45 int y;
46 int width;
47 int height;
48 int borderWidth;
49 int borderRadius;
50 bool flipIfRTL;
53 // DrawRect is mostly the same as ColorRect, but exists as an implementation
54 // detail to simplify drawing borders. We draw borders as a strokeOnly rect
55 // underneath an inner rect of a particular color. We also need to keep
56 // track of the backgroundColor for rounding rects, in order to correctly
57 // anti-alias.
58 struct DrawRect {
59 uint32_t color;
60 uint32_t backgroundColor;
61 int x;
62 int y;
63 int width;
64 int height;
65 int borderRadius;
66 int borderWidth;
67 bool strokeOnly;
70 struct NormalizedRGB {
71 double r;
72 double g;
73 double b;
76 NormalizedRGB UintToRGB(uint32_t color) {
77 double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
78 double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
79 double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
80 return NormalizedRGB{r, g, b};
83 uint32_t RGBToUint(const NormalizedRGB& rgb) {
84 return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
85 (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
86 (static_cast<uint32_t>(rgb.b * 255.0) << 0);
89 double Lerp(double a, double b, double x) { return a + x * (b - a); }
91 NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
92 return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
95 // Produces a smooth curve in [0,1] based on a linear input in [0,1]
96 double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }
98 static const wchar_t kPreXULSkeletonUIKeyPath[] =
99 L"SOFTWARE"
100 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";
102 static bool sPreXULSkeletonUIShown = false;
103 static bool sPreXULSkeletonUIEnabled = false;
104 static HWND sPreXULSkeletonUIWindow;
105 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
106 static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
107 static HANDLE sPreXULSKeletonUIAnimationThread;
108 static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE;
110 static mozilla::mscom::ProcessRuntime* sProcessRuntime;
111 static uint32_t* sPixelBuffer = nullptr;
112 static Vector<ColorRect>* sAnimatedRects = nullptr;
113 static int sTotalChromeHeight = 0;
114 static volatile LONG sAnimationControlFlag = 0;
115 static bool sMaximized = false;
116 static int sNonClientVerticalMargins = 0;
117 static int sNonClientHorizontalMargins = 0;
118 static uint32_t sDpi = 0;
120 // Color values needed by the animation loop
121 static uint32_t sAnimationColor;
122 static uint32_t sToolbarForegroundColor;
124 static ThemeMode sTheme = ThemeMode::Invalid;
126 typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
127 static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
128 typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
129 GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL;
130 typedef UINT(WINAPI* GetDpiForWindowProc)(HWND);
131 GetDpiForWindowProc sGetDpiForWindow = NULL;
132 typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*);
133 RegisterClassWProc sRegisterClassW = NULL;
134 typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR);
135 LoadIconWProc sLoadIconW = NULL;
136 typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR);
137 LoadCursorWProc sLoadCursorW = NULL;
138 typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int,
139 int, int, int, HWND, HMENU, HINSTANCE,
140 LPVOID);
141 CreateWindowExWProc sCreateWindowExW = NULL;
142 typedef BOOL(WINAPI* ShowWindowProc)(HWND, int);
143 ShowWindowProc sShowWindow = NULL;
144 typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT);
145 SetWindowPosProc sSetWindowPos = NULL;
146 typedef HDC(WINAPI* GetWindowDCProc)(HWND);
147 GetWindowDCProc sGetWindowDC = NULL;
148 typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH);
149 FillRectProc sFillRect = NULL;
150 typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ);
151 DeleteObjectProc sDeleteObject = NULL;
152 typedef int(WINAPI* ReleaseDCProc)(HWND, HDC);
153 ReleaseDCProc sReleaseDC = NULL;
154 typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD);
155 MonitorFromWindowProc sMonitorFromWindow = NULL;
156 typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO);
157 GetMonitorInfoWProc sGetMonitorInfoW = NULL;
158 typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR);
159 SetWindowLongPtrWProc sSetWindowLongPtrW = NULL;
160 typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int,
161 int, const VOID*, const BITMAPINFO*,
162 UINT, DWORD);
163 StretchDIBitsProc sStretchDIBits = NULL;
164 typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF);
165 CreateSolidBrushProc sCreateSolidBrush = NULL;
167 static int sWindowWidth;
168 static int sWindowHeight;
169 static double sCSSToDevPixelScaling;
171 static Maybe<PreXULSkeletonUIError> sErrorReason;
173 static const int kAnimationCSSPixelsPerFrame = 11;
174 static const int kAnimationCSSExtraWindowSize = 300;
176 // NOTE: these values were pulled out of thin air as round numbers that are
177 // likely to be too big to be seen in practice. If we legitimately see windows
178 // this big, we probably don't want to be drawing them on the CPU anyway.
179 static const uint32_t kMaxWindowWidth = 1 << 16;
180 static const uint32_t kMaxWindowHeight = 1 << 16;
182 static const wchar_t* sEnabledRegSuffix = L"|Enabled";
183 static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
184 static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
185 static const wchar_t* sWidthRegSuffix = L"|Width";
186 static const wchar_t* sHeightRegSuffix = L"|Height";
187 static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
188 static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
189 static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
190 static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
191 static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
192 static const wchar_t* sThemeRegSuffix = L"|Theme";
193 static const wchar_t* sFlagsRegSuffix = L"|Flags";
194 static const wchar_t* sProgressSuffix = L"|Progress";
196 std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
197 std::wstring result(prefix);
198 result.append(suffix);
199 return result;
202 // This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
203 // included in standalone SpiderMonkey builds prohibits us from including that
204 // file directly, and it hardly warrants its own header. Bug 1674920 tracks
205 // only including this file for gecko-related builds.
206 Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() {
207 DWORD bufLen = MAX_PATH;
208 UniquePtr<wchar_t[]> buf;
209 while (true) {
210 buf = MakeUnique<wchar_t[]>(bufLen);
211 DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
212 if (!retLen) {
213 return Err(PreXULSkeletonUIError::FilesystemFailure);
216 if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
217 bufLen *= 2;
218 continue;
221 break;
224 return buf;
227 // PreXULSkeletonUIDisallowed means that we don't even have the capacity to
228 // enable the skeleton UI, whether because we're on a platform that doesn't
229 // support it or because we launched with command line arguments that we don't
230 // support. Some of these situations are transient, so we want to make sure we
231 // don't mess with registry values in these scenarios that we may use in
232 // other scenarios in which the skeleton UI is actually enabled.
233 static bool PreXULSkeletonUIDisallowed() {
234 return sErrorReason.isSome() &&
235 (*sErrorReason == PreXULSkeletonUIError::Cmdline ||
236 *sErrorReason == PreXULSkeletonUIError::EnvVars);
239 // Note: this is specifically *not* a robust, multi-locale lowercasing
240 // operation. It is not intended to be such. It is simply intended to match the
241 // way in which we look for other instances of firefox to remote into.
242 // See
243 // https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
244 static void MutateStringToLowercase(wchar_t* ptr) {
245 while (*ptr) {
246 wchar_t ch = *ptr;
247 if (ch >= L'A' && ch <= L'Z') {
248 *ptr = ch + (L'a' - L'A');
250 ++ptr;
254 static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() {
255 auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
256 if (!localAppDataPath) {
257 return Err(PreXULSkeletonUIError::FilesystemFailure);
260 if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
261 return Ok();
264 // Note: because we're in mozglue, we cannot easily access things from
265 // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
266 // mozglue, and rip out all of its usage of types defined in toolkit headers.
267 // However, it seems cleaner to just hash the bin path ourselves. We don't
268 // get quite the same robustness that `GetInstallHash` might provide, but
269 // we already don't have that with how we key our registry values, so it
270 // probably makes sense to just match those.
271 UniquePtr<wchar_t[]> binPath;
272 MOZ_TRY_VAR(binPath, GetBinaryPath());
274 // Lowercase the binpath to match how we look for remote instances.
275 MutateStringToLowercase(binPath.get());
277 // The number of bytes * 2 characters per byte + 1 for the null terminator
278 uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
279 UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
280 // This isn't perfect - it's a 32-bit hash of the path to our executable. It
281 // could reasonably collide, or casing could potentially affect things, but
282 // the theory is that that should be uncommon enough and the failure case
283 // mild enough that this is fine.
284 uint32_t binPathHash = HashString(binPath.get());
285 swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);
287 std::wstring lockFilePath;
288 lockFilePath.append(localAppDataPath.get());
289 lockFilePath.append(
290 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
291 lockFilePath.append(installHash.get());
293 // We intentionally leak this file - that is okay, and (kind of) the point.
294 // We want to hold onto this handle until the application exits, and hold
295 // onto it with exclusive rights. If this check fails, then we assume that
296 // another instance of the executable is holding it, and thus return false.
297 sPreXULSKeletonUILockFile =
298 ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
299 0, // No sharing - this is how the lock works
300 nullptr, CREATE_ALWAYS,
301 FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around
302 nullptr);
303 if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) {
304 return Err(PreXULSkeletonUIError::FailedGettingLock);
307 return Ok();
310 const char kGeneralSection[] = "[General]";
311 const char kStartWithLastProfile[] = "StartWithLastProfile=";
313 static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
314 bool inGeneral = false;
315 std::string line;
316 while (std::getline(iniContents, line)) {
317 size_t whitespace = 0;
318 while (line.length() > whitespace &&
319 (line[whitespace] == ' ' || line[whitespace] == '\t')) {
320 whitespace++;
322 line.erase(0, whitespace);
324 if (line.compare(kGeneralSection) == 0) {
325 inGeneral = true;
326 } else if (inGeneral) {
327 if (line[0] == '[') {
328 inGeneral = false;
329 } else {
330 if (line.find(kStartWithLastProfile) == 0) {
331 char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
332 if (val == '0') {
333 return false;
334 } else if (val == '1') {
335 return true;
342 // If we don't find it in the .ini file, we interpret that as true
343 return true;
346 static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() {
347 auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
348 if (!roamingAppData) {
349 return Err(PreXULSkeletonUIError::FilesystemFailure);
351 std::wstring profileDbPath(roamingAppData.get());
352 profileDbPath.append(
353 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
354 IFStream profileDb(profileDbPath.c_str());
355 if (profileDb.fail()) {
356 return Err(PreXULSkeletonUIError::FilesystemFailure);
359 if (!ProfileDbHasStartWithLastProfile(profileDb)) {
360 return Err(PreXULSkeletonUIError::NoStartWithLastProfile);
363 return Ok();
366 // We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
367 // failures in random places because we're in mozglue. Overall it should be
368 // simpler and cleaner to just step around that issue with this class:
369 class MOZ_RAII AutoCloseRegKey {
370 public:
371 explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
372 ~AutoCloseRegKey() { ::RegCloseKey(mKey); }
374 private:
375 HKEY mKey;
378 int CSSToDevPixels(double cssPixels, double scaling) {
379 return floor(cssPixels * scaling + 0.5);
382 int CSSToDevPixels(int cssPixels, double scaling) {
383 return CSSToDevPixels((double)cssPixels, scaling);
386 int CSSToDevPixelsFloor(double cssPixels, double scaling) {
387 return floor(cssPixels * scaling);
390 // Some things appear to floor to device pixels rather than rounding. A good
391 // example of this is border widths.
392 int CSSToDevPixelsFloor(int cssPixels, double scaling) {
393 return CSSToDevPixelsFloor((double)cssPixels, scaling);
396 double SignedDistanceToCircle(double x, double y, double radius) {
397 return sqrt(x * x + y * y) - radius;
400 // For more details, see
401 // https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
402 // which was a reference for this function.
403 double DistanceAntiAlias(double signedDistance) {
404 // Distance assumed to be in device pixels. We use an aa range of 0.5 for
405 // reasons detailed in the linked code above.
406 const double aaRange = 0.5;
407 double dist = 0.5 * signedDistance / aaRange;
408 if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
409 if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
410 return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
413 void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
414 if (rect.height <= 2 * rect.borderRadius) {
415 MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
416 return;
418 if (rect.width <= 2 * rect.borderRadius) {
419 MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
420 return;
423 NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
424 NormalizedRGB rgbBlend = UintToRGB(rect.color);
426 for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
427 int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
428 int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;
430 uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
431 uint32_t* innermostPixelTopLeft =
432 lineStartTop + rect.x + rect.borderRadius - 1;
433 uint32_t* innermostPixelTopRight =
434 lineStartTop + rect.x + rect.width - rect.borderRadius;
435 uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
436 uint32_t* innermostPixelBottomLeft =
437 lineStartBottom + rect.x + rect.borderRadius - 1;
438 uint32_t* innermostPixelBottomRight =
439 lineStartBottom + rect.x + rect.width - rect.borderRadius;
441 // Add 0.5 to x and y to get the pixel center.
442 double pixelY = (double)rowIndex + 0.5;
443 for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
444 double pixelX = (double)columnIndex + 0.5;
445 double distance =
446 SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
447 double alpha = DistanceAntiAlias(distance);
448 NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
449 uint32_t color = RGBToUint(rgb);
451 innermostPixelTopLeft[-columnIndex] = color;
452 innermostPixelTopRight[columnIndex] = color;
453 innermostPixelBottomLeft[-columnIndex] = color;
454 innermostPixelBottomRight[columnIndex] = color;
457 std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
458 std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
459 rect.color);
463 void RasterizeAnimatedRoundedRectTopAndBottom(
464 const ColorRect& colorRect, const uint32_t* animationLookup,
465 int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
466 int currentUpdateAreaMax, int animationMin) {
467 // We iterate through logical pixel rows here, from inside to outside, which
468 // for the top of the rounded rect means from bottom to top, and for the
469 // bottom of the rect means top to bottom. We paint pixels from left to
470 // right on the top and bottom rows at the same time for the entire animation
471 // window. (If the animation window does not overlap any rounded corners,
472 // however, we won't be called at all)
473 for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
474 int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
475 int yBottom =
476 colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;
478 uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
479 uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
481 // Add 0.5 to x and y to get the pixel center.
482 double pixelY = (double)rowIndex + 0.5;
483 for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
484 // The column index is the distance from the innermost pixel, which
485 // is different depending on whether we're on the left or right
486 // side of the rect. It will always be the max here, and if it's
487 // negative that just means we're outside the rounded area.
488 int columnIndex =
489 std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
490 x - ((int)colorRect.x + (int)colorRect.width -
491 (int)colorRect.borderRadius));
493 double alpha = 1.0;
494 if (columnIndex >= 0) {
495 double pixelX = (double)columnIndex + 0.5;
496 double distance = SignedDistanceToCircle(
497 pixelX, pixelY, (double)colorRect.borderRadius);
498 alpha = DistanceAntiAlias(distance);
500 // We don't do alpha blending for the antialiased pixels at the
501 // shape's border. It is not noticeable in the animation.
502 if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
503 // Overwrite the tail end of last frame's animation with the
504 // rect's normal, unanimated color.
505 uint32_t color = x < priorUpdateAreaMax
506 ? colorRect.color
507 : animationLookup[x - animationMin];
508 lineStartTop[x] = color;
509 lineStartBottom[x] = color;
515 void RasterizeColorRect(const ColorRect& colorRect) {
516 // We sometimes split our rect into two, to simplify drawing borders. If we
517 // have a border, we draw a stroke-only rect first, and then draw the smaller
518 // inner rect on top of it.
519 Vector<DrawRect, 2> drawRects;
520 Unused << drawRects.reserve(2);
521 if (colorRect.borderWidth == 0) {
522 DrawRect rect = {};
523 rect.color = colorRect.color;
524 rect.backgroundColor =
525 sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
526 rect.x = colorRect.x;
527 rect.y = colorRect.y;
528 rect.width = colorRect.width;
529 rect.height = colorRect.height;
530 rect.borderRadius = colorRect.borderRadius;
531 rect.strokeOnly = false;
532 drawRects.infallibleAppend(rect);
533 } else {
534 DrawRect borderRect = {};
535 borderRect.color = colorRect.borderColor;
536 borderRect.backgroundColor =
537 sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
538 borderRect.x = colorRect.x;
539 borderRect.y = colorRect.y;
540 borderRect.width = colorRect.width;
541 borderRect.height = colorRect.height;
542 borderRect.borderRadius = colorRect.borderRadius;
543 borderRect.borderWidth = colorRect.borderWidth;
544 borderRect.strokeOnly = true;
545 drawRects.infallibleAppend(borderRect);
547 DrawRect baseRect = {};
548 baseRect.color = colorRect.color;
549 baseRect.backgroundColor = borderRect.color;
550 baseRect.x = colorRect.x + colorRect.borderWidth;
551 baseRect.y = colorRect.y + colorRect.borderWidth;
552 baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
553 baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
554 baseRect.borderRadius =
555 std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
556 baseRect.borderWidth = 0;
557 baseRect.strokeOnly = false;
558 drawRects.infallibleAppend(baseRect);
561 for (const DrawRect& rect : drawRects) {
562 if (rect.height <= 0 || rect.width <= 0) {
563 continue;
566 // For rounded rectangles, the first thing we do is draw the top and
567 // bottom of the rectangle, with the more complicated logic below. After
568 // that we can just draw the vertically centered part of the rect like
569 // normal.
570 RasterizeRoundedRectTopAndBottom(rect);
572 // We then draw the flat, central portion of the rect (which in the case of
573 // non-rounded rects, is just the entire thing.)
574 int solidRectStartY =
575 std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
576 int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
577 sTotalChromeHeight);
578 for (int y = solidRectStartY; y < solidRectEndY; ++y) {
579 // For strokeOnly rects (used to draw borders), we just draw the left
580 // and right side here. Looping down a column of pixels is not the most
581 // cache-friendly thing, but it shouldn't be a big deal given the height
582 // of the urlbar.
583 // Also, if borderRadius is less than borderWidth, we need to ensure
584 // that we fully draw the top and bottom lines, so we make sure to check
585 // that we're inside the middle range range before excluding pixels.
586 if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
587 rect.y + rect.height - y > rect.borderWidth) {
588 int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
589 int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
590 int startXRight =
591 std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
592 int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);
594 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
595 uint32_t* dataStartLeft = lineStart + startXLeft;
596 uint32_t* dataEndLeft = lineStart + endXLeft;
597 uint32_t* dataStartRight = lineStart + startXRight;
598 uint32_t* dataEndRight = lineStart + endXRight;
599 std::fill(dataStartLeft, dataEndLeft, rect.color);
600 std::fill(dataStartRight, dataEndRight, rect.color);
601 } else {
602 int startX = std::clamp(rect.x, 0, sWindowWidth);
603 int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
604 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
605 uint32_t* dataStart = lineStart + startX;
606 uint32_t* dataEnd = lineStart + endX;
607 std::fill(dataStart, dataEnd, rect.color);
613 // Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
614 // gradient which moves from left to right across the grey placeholder rects).
615 // Takes in the rect to draw, together with a lookup table for the gradient,
616 // and the bounds of the previous and current frame of the animation.
617 bool RasterizeAnimatedRect(const ColorRect& colorRect,
618 const uint32_t* animationLookup,
619 int priorAnimationMin, int animationMin,
620 int animationMax) {
621 int rectMin = colorRect.x;
622 int rectMax = colorRect.x + colorRect.width;
623 bool animationWindowOverlaps =
624 rectMax >= priorAnimationMin && rectMin < animationMax;
626 int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
627 int priorUpdateAreaMax = std::min(rectMax, animationMin);
628 int currentUpdateAreaMin = std::max(rectMin, animationMin);
629 int currentUpdateAreaMax = std::min(rectMax, animationMax);
631 if (!animationWindowOverlaps) {
632 return false;
635 bool animationWindowOverlapsBorderRadius =
636 rectMin + colorRect.borderRadius > priorAnimationMin ||
637 rectMax - colorRect.borderRadius <= animationMax;
639 // If we don't overlap the left or right side of the rounded rectangle,
640 // just pretend it's not rounded. This is a small optimization but
641 // there's no point in doing all of this rounded rectangle checking if
642 // we aren't even overlapping
643 int borderRadius =
644 animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;
646 if (borderRadius > 0) {
647 // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
648 // first draw the rounded top and bottom, and then we draw the center
649 // rect.
650 RasterizeAnimatedRoundedRectTopAndBottom(
651 colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
652 currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
655 for (int y = colorRect.y + borderRadius;
656 y < colorRect.y + colorRect.height - borderRadius; ++y) {
657 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
658 // Overwrite the tail end of last frame's animation with the rect's
659 // normal, unanimated color.
660 for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
661 lineStart[x] = colorRect.color;
663 // Then apply the animated color
664 for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
665 lineStart[x] = animationLookup[x - animationMin];
669 return true;
672 Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI(
673 HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan,
674 Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme,
675 const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
676 // NOTE: we opt here to paint a pixel buffer for the application chrome by
677 // hand, without using native UI library methods. Why do we do this?
679 // 1) It gives us a little bit more control, especially if we want to animate
680 // any of this.
681 // 2) It's actually more portable. We can do this on any platform where we
682 // can blit a pixel buffer to the screen, and it only has to change
683 // insofar as the UI is different on those platforms (and thus would have
684 // to change anyway.)
686 // The performance impact of this ought to be negligible. As far as has been
687 // observed, on slow reference hardware this might take up to a millisecond,
688 // for a startup which otherwise takes 30 seconds.
690 // The readability and maintainability are a greater concern. When the
691 // silhouette of Firefox's core UI changes, this code will likely need to
692 // change. However, for the foreseeable future, our skeleton UI will be mostly
693 // axis-aligned geometric shapes, and the thought is that any code which is
694 // manipulating raw pixels should not be *too* hard to maintain and
695 // understand so long as it is only painting such simple shapes.
697 sAnimationColor = currentTheme.animationColor;
698 sToolbarForegroundColor = currentTheme.toolbarForegroundColor;
700 bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
701 bool bookmarksToolbarShown =
702 flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
703 bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);
705 int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
706 int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0;
707 int horizontalOffset =
708 sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin);
710 // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
711 int tabBarHeight = CSSToDevPixels(44, sCSSToDevPixelScaling);
712 int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling);
713 // found in tabs.inc.css, "--tab-block-margin"
714 int titlebarSpacerWidth = horizontalOffset +
715 CSSToDevPixels(2, sCSSToDevPixelScaling) -
716 selectedTabBorderWidth;
717 if (!sMaximized && !menubarShown) {
718 // found in tabs.inc.css, ".titlebar-spacer"
719 titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
721 // found in tabs.inc.css, "--tab-block-margin"
722 int selectedTabMarginTop =
723 CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
724 int selectedTabMarginBottom =
725 CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
726 int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
727 int selectedTabWidth =
728 CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth;
729 int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling);
730 // found in browser.css, "#PersonalToolbar"
731 int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
732 if (bookmarksToolbarShown) {
733 toolbarHeight += bookmarkToolbarHeight;
735 // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
736 int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling);
737 int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling);
738 // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
739 int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);
741 int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling);
742 int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
743 int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
744 int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);
746 int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
747 int toolbarPlaceholderMarginRight =
748 rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
749 : CSSToDevPixels(9, sCSSToDevPixelScaling);
750 int toolbarPlaceholderMarginLeft =
751 rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
752 : CSSToDevPixels(11, sCSSToDevPixelScaling);
753 int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);
755 int menubarHeightDevPixels =
756 menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;
758 // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px
759 int urlbarMargin =
760 CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;
762 int urlbarTextPlaceholderMarginTop =
763 CSSToDevPixels(12, sCSSToDevPixelScaling);
764 int urlbarTextPlaceholderMarginLeft =
765 CSSToDevPixels(12, sCSSToDevPixelScaling);
766 int urlbarTextPlaceHolderWidth = CSSToDevPixels(
767 std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
768 sCSSToDevPixelScaling);
769 int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
771 int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);
773 auto scopeExit = MakeScopeExit([&] {
774 delete sAnimatedRects;
775 sAnimatedRects = nullptr;
778 Vector<ColorRect> rects;
780 ColorRect menubar = {};
781 menubar.color = currentTheme.tabBarColor;
782 menubar.x = 0;
783 menubar.y = verticalOffset;
784 menubar.width = sWindowWidth;
785 menubar.height = menubarHeightDevPixels;
786 menubar.flipIfRTL = false;
787 if (!rects.append(menubar)) {
788 return Err(PreXULSkeletonUIError::OOM);
791 int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
792 // found in browser.css "--toolbarbutton-border-radius"
793 int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
795 // The (traditionally dark blue on Windows) background of the tab bar.
796 ColorRect tabBar = {};
797 tabBar.color = currentTheme.tabBarColor;
798 tabBar.x = 0;
799 tabBar.y = menubar.y + menubar.height;
800 tabBar.width = sWindowWidth;
801 tabBar.height = tabBarHeight;
802 tabBar.flipIfRTL = false;
803 if (!rects.append(tabBar)) {
804 return Err(PreXULSkeletonUIError::OOM);
807 // The initial selected tab
808 ColorRect selectedTab = {};
809 selectedTab.color = currentTheme.tabColor;
810 selectedTab.x = titlebarSpacerWidth;
811 selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop;
812 selectedTab.width = selectedTabWidth;
813 selectedTab.height =
814 tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom;
815 selectedTab.borderColor = currentTheme.tabOutlineColor;
816 selectedTab.borderWidth = selectedTabBorderWidth;
817 selectedTab.borderRadius = selectedTabBorderRadius;
818 selectedTab.flipIfRTL = true;
819 if (!rects.append(selectedTab)) {
820 return Err(PreXULSkeletonUIError::OOM);
823 // A placeholder rect representing text that will fill the selected tab title
824 ColorRect tabTextPlaceholder = {};
825 tabTextPlaceholder.color = currentTheme.toolbarForegroundColor;
826 tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
827 tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
828 tabTextPlaceholder.width = tabPlaceholderBarWidth;
829 tabTextPlaceholder.height = tabPlaceholderBarHeight;
830 tabTextPlaceholder.borderRadius = placeholderBorderRadius;
831 tabTextPlaceholder.flipIfRTL = true;
832 if (!rects.append(tabTextPlaceholder)) {
833 return Err(PreXULSkeletonUIError::OOM);
836 // The toolbar background
837 ColorRect toolbar = {};
838 toolbar.color = currentTheme.backgroundColor;
839 toolbar.x = 0;
840 toolbar.y = tabBar.y + tabBarHeight;
841 toolbar.width = sWindowWidth;
842 toolbar.height = toolbarHeight;
843 toolbar.flipIfRTL = false;
844 if (!rects.append(toolbar)) {
845 return Err(PreXULSkeletonUIError::OOM);
848 // The single-pixel divider line below the toolbar
849 ColorRect chromeContentDivider = {};
850 chromeContentDivider.color = currentTheme.chromeContentDividerColor;
851 chromeContentDivider.x = 0;
852 chromeContentDivider.y = toolbar.y + toolbar.height;
853 chromeContentDivider.width = sWindowWidth;
854 chromeContentDivider.height = chromeContentDividerHeight;
855 chromeContentDivider.flipIfRTL = false;
856 if (!rects.append(chromeContentDivider)) {
857 return Err(PreXULSkeletonUIError::OOM);
860 // The urlbar
861 ColorRect urlbar = {};
862 urlbar.color = currentTheme.urlbarColor;
863 urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
864 horizontalOffset;
865 urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
866 urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
867 sCSSToDevPixelScaling);
868 urlbar.height = urlbarHeight;
869 urlbar.borderColor = currentTheme.urlbarBorderColor;
870 urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
871 urlbar.borderRadius = urlbarBorderRadius;
872 urlbar.flipIfRTL = false;
873 if (!rects.append(urlbar)) {
874 return Err(PreXULSkeletonUIError::OOM);
877 // The urlbar placeholder rect representating text that will fill the urlbar
878 // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
879 // sWindowWidth.
880 ColorRect urlbarTextPlaceholder = {};
881 urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
882 urlbarTextPlaceholder.x =
883 rtlEnabled
884 ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
885 urlbarTextPlaceHolderWidth)
886 : (urlbar.x + urlbarTextPlaceholderMarginLeft);
887 urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
888 urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
889 urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
890 urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
891 urlbarTextPlaceholder.flipIfRTL = false;
892 if (!rects.append(urlbarTextPlaceholder)) {
893 return Err(PreXULSkeletonUIError::OOM);
896 // The searchbar and placeholder text, if present
897 // This is y-aligned with the urlbar
898 bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
899 ColorRect searchbarRect = {};
900 if (hasSearchbar == true) {
901 searchbarRect.color = currentTheme.urlbarColor;
902 searchbarRect.x =
903 CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
904 horizontalOffset;
905 searchbarRect.y = urlbar.y;
906 searchbarRect.width = CSSToDevPixels(
907 searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
908 searchbarRect.height = urlbarHeight;
909 searchbarRect.borderRadius = urlbarBorderRadius;
910 searchbarRect.borderColor = currentTheme.urlbarBorderColor;
911 searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
912 searchbarRect.flipIfRTL = false;
913 if (!rects.append(searchbarRect)) {
914 return Err(PreXULSkeletonUIError::OOM);
917 // The placeholder rect representating text that will fill the searchbar
918 // This uses the same margins as the urlbarTextPlaceholder
919 // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
920 // not sWindowWidth.
921 ColorRect searchbarTextPlaceholder = {};
922 searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
923 searchbarTextPlaceholder.x =
924 rtlEnabled
925 ? ((searchbarRect.x + searchbarRect.width) -
926 urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
927 : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
928 searchbarTextPlaceholder.y =
929 searchbarRect.y + urlbarTextPlaceholderMarginTop;
930 searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
931 searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
932 searchbarTextPlaceholder.flipIfRTL = false;
933 if (!rects.append(searchbarTextPlaceholder) ||
934 !sAnimatedRects->append(searchbarTextPlaceholder)) {
935 return Err(PreXULSkeletonUIError::OOM);
939 // Determine where the placeholder rectangles should not go. This is
940 // anywhere occupied by a spring, urlbar, or searchbar
941 Vector<DevPixelSpan> noPlaceholderSpans;
943 DevPixelSpan urlbarSpan;
944 urlbarSpan.start = urlbar.x - urlbarMargin;
945 urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;
947 DevPixelSpan searchbarSpan;
948 if (hasSearchbar) {
949 searchbarSpan.start = searchbarRect.x - urlbarMargin;
950 searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
953 DevPixelSpan marginLeftPlaceholder;
954 marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
955 marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
956 if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
957 return Err(PreXULSkeletonUIError::OOM);
960 if (rtlEnabled) {
961 // If we're RTL, then the springs as ordered in the DOM will be from right
962 // to left, which will break our comparison logic below
963 springs.reverse();
966 for (auto spring : springs) {
967 DevPixelSpan springDevPixels;
968 springDevPixels.start =
969 CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
970 springDevPixels.end =
971 CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
972 if (!noPlaceholderSpans.append(springDevPixels)) {
973 return Err(PreXULSkeletonUIError::OOM);
977 DevPixelSpan marginRightPlaceholder;
978 marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
979 marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
980 if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
981 return Err(PreXULSkeletonUIError::OOM);
984 Vector<DevPixelSpan, 2> spansToAdd;
985 Unused << spansToAdd.reserve(2);
986 spansToAdd.infallibleAppend(urlbarSpan);
987 if (hasSearchbar) {
988 spansToAdd.infallibleAppend(searchbarSpan);
991 for (auto& toAdd : spansToAdd) {
992 for (auto& span : noPlaceholderSpans) {
993 if (span.start > toAdd.start) {
994 if (!noPlaceholderSpans.insert(&span, toAdd)) {
995 return Err(PreXULSkeletonUIError::OOM);
997 break;
1002 for (size_t i = 1; i < noPlaceholderSpans.length(); i++) {
1003 int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
1004 int end = noPlaceholderSpans[i].start - placeholderMargin;
1005 if (start + 2 * placeholderBorderRadius >= end) {
1006 continue;
1009 // The placeholder rects should all be y-aligned.
1010 ColorRect placeholderRect = {};
1011 placeholderRect.color = currentTheme.toolbarForegroundColor;
1012 placeholderRect.x = start;
1013 placeholderRect.y = urlbarTextPlaceholder.y;
1014 placeholderRect.width = end - start;
1015 placeholderRect.height = toolbarPlaceholderHeight;
1016 placeholderRect.borderRadius = placeholderBorderRadius;
1017 placeholderRect.flipIfRTL = false;
1018 if (!rects.append(placeholderRect) ||
1019 !sAnimatedRects->append(placeholderRect)) {
1020 return Err(PreXULSkeletonUIError::OOM);
1024 sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
1025 if (sTotalChromeHeight > sWindowHeight) {
1026 return Err(PreXULSkeletonUIError::BadWindowDimensions);
1029 if (!sAnimatedRects->append(tabTextPlaceholder) ||
1030 !sAnimatedRects->append(urlbarTextPlaceholder)) {
1031 return Err(PreXULSkeletonUIError::OOM);
1034 sPixelBuffer =
1035 (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
1037 for (auto& rect : *sAnimatedRects) {
1038 if (rtlEnabled && rect.flipIfRTL) {
1039 rect.x = sWindowWidth - rect.x - rect.width;
1041 rect.x = std::clamp(rect.x, 0, sWindowWidth);
1042 rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
1043 rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
1044 rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
1047 for (auto& rect : rects) {
1048 if (rtlEnabled && rect.flipIfRTL) {
1049 rect.x = sWindowWidth - rect.x - rect.width;
1051 rect.x = std::clamp(rect.x, 0, sWindowWidth);
1052 rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
1053 rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
1054 rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
1055 RasterizeColorRect(rect);
1058 HDC hdc = sGetWindowDC(hWnd);
1059 if (!hdc) {
1060 return Err(PreXULSkeletonUIError::FailedGettingDC);
1062 auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); });
1064 BITMAPINFO chromeBMI = {};
1065 chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
1066 chromeBMI.bmiHeader.biWidth = sWindowWidth;
1067 chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
1068 chromeBMI.bmiHeader.biPlanes = 1;
1069 chromeBMI.bmiHeader.biBitCount = 32;
1070 chromeBMI.bmiHeader.biCompression = BI_RGB;
1072 // First, we just paint the chrome area with our pixel buffer
1073 int scanLinesCopied = sStretchDIBits(
1074 hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth,
1075 sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY);
1076 if (scanLinesCopied == 0) {
1077 return Err(PreXULSkeletonUIError::FailedBlitting);
1080 // Then, we just fill the rest with FillRect
1081 RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
1082 HBRUSH brush =
1083 sCreateSolidBrush(RGB((currentTheme.backgroundColor & 0xff0000) >> 16,
1084 (currentTheme.backgroundColor & 0x00ff00) >> 8,
1085 (currentTheme.backgroundColor & 0x0000ff) >> 0));
1086 int fillRectResult = sFillRect(hdc, &rect, brush);
1088 sDeleteObject(brush);
1090 if (fillRectResult == 0) {
1091 return Err(PreXULSkeletonUIError::FailedFillingBottomRect);
1094 scopeExit.release();
1095 return Ok();
1098 DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
1099 if (!sPixelBuffer || sAnimatedRects->empty()) {
1100 return 0;
1103 // See the comments above the InterlockedIncrement calls below here - we
1104 // atomically flip this up and down around sleep so the main thread doesn't
1105 // have to wait for us if we're just sleeping.
1106 if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
1107 return 0;
1109 // Sleep for two seconds - startups faster than this don't really benefit
1110 // from an animation, and we don't want to take away cycles from them.
1111 // Startups longer than this, however, are more likely to be blocked on IO,
1112 // and thus animating does not substantially impact startup times for them.
1113 ::Sleep(2000);
1114 if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
1115 return 0;
1118 // On each of the animated rects (which happen to all be placeholder UI
1119 // rects sharing the same color), we want to animate a gradient moving across
1120 // the screen from left to right. The gradient starts as the rect's color on,
1121 // the left side, changes to the background color of the window by the middle
1122 // of the gradient, and then goes back down to the rect's color. To make this
1123 // faster than interpolating between the two colors for each pixel for each
1124 // frame, we simply create a lookup buffer in which we can look up the color
1125 // for a particular offset into the gradient.
1127 // To do this we just interpolate between the two values, and to give the
1128 // gradient a smoother transition between colors, we transform the linear
1129 // blend amount via the cubic smooth step function (SmoothStep3) to produce
1130 // a smooth start and stop for the gradient. We do this for the first half
1131 // of the gradient, and then simply copy that backwards for the second half.
1133 // The CSS width of 80 chosen here is effectively is just to match the size
1134 // of the animation provided in the design mockup. We define it in CSS pixels
1135 // simply because the rest of our UI is based off of CSS scalings.
1136 int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
1137 UniquePtr<uint32_t[]> animationLookup =
1138 MakeUnique<uint32_t[]>(animationWidth);
1139 uint32_t animationColor = sAnimationColor;
1140 NormalizedRGB rgbBlend = UintToRGB(animationColor);
1142 // Build the first half of the lookup table
1143 for (int i = 0; i < animationWidth / 2; ++i) {
1144 uint32_t baseColor = sToolbarForegroundColor;
1145 double blendAmountLinear =
1146 static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
1147 double blendAmount = SmoothStep3(blendAmountLinear);
1149 NormalizedRGB rgbBase = UintToRGB(baseColor);
1150 NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
1151 animationLookup[i] = RGBToUint(rgb);
1154 // Copy the first half of the lookup table into the second half backwards
1155 for (int i = animationWidth / 2; i < animationWidth; ++i) {
1156 int j = animationWidth - 1 - i;
1157 if (j == animationWidth / 2) {
1158 // If animationWidth is odd, we'll be left with one pixel at the center.
1159 // Just color that as the animation color.
1160 animationLookup[i] = animationColor;
1161 } else {
1162 animationLookup[i] = animationLookup[j];
1166 // The bitmap info remains unchanged throughout the animation - this just
1167 // effectively describes the contents of sPixelBuffer
1168 BITMAPINFO chromeBMI = {};
1169 chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
1170 chromeBMI.bmiHeader.biWidth = sWindowWidth;
1171 chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
1172 chromeBMI.bmiHeader.biPlanes = 1;
1173 chromeBMI.bmiHeader.biBitCount = 32;
1174 chromeBMI.bmiHeader.biCompression = BI_RGB;
1176 uint32_t animationIteration = 0;
1178 int devPixelsPerFrame =
1179 CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
1180 int devPixelsExtraWindowSize =
1181 CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);
1183 if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
1184 // The window got consumed before we were able to draw anything.
1185 return 0;
1188 while (true) {
1189 // The gradient will move across the screen at devPixelsPerFrame at
1190 // 60fps, and then loop back to the beginning. However, we add a buffer of
1191 // devPixelsExtraWindowSize around the edges so it doesn't immediately
1192 // jump back, giving it a more pulsing feel.
1193 int animationMin = ((animationIteration * devPixelsPerFrame) %
1194 (sWindowWidth + devPixelsExtraWindowSize)) -
1195 devPixelsExtraWindowSize / 2;
1196 int animationMax = animationMin + animationWidth;
1197 // The priorAnimationMin is the beginning of the previous frame's animation.
1198 // Since we only want to draw the bits of the image that we updated, we need
1199 // to overwrite the left bit of the animation we drew last frame with the
1200 // default color.
1201 int priorAnimationMin = animationMin - devPixelsPerFrame;
1202 animationMin = std::max(0, animationMin);
1203 priorAnimationMin = std::max(0, priorAnimationMin);
1204 animationMax = std::min((int)sWindowWidth, animationMax);
1206 // The gradient only affects the specific rects that we put into
1207 // sAnimatedRects. So we simply update those rects, and maintain a flag
1208 // to avoid drawing when we don't need to.
1209 bool updatedAnything = false;
1210 for (ColorRect rect : *sAnimatedRects) {
1211 bool hadUpdates =
1212 RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
1213 animationMin, animationMax);
1214 updatedAnything = updatedAnything || hadUpdates;
1217 if (updatedAnything) {
1218 HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
1219 if (!hdc) {
1220 return 0;
1223 sStretchDIBits(hdc, priorAnimationMin, 0,
1224 animationMax - priorAnimationMin, sTotalChromeHeight,
1225 priorAnimationMin, 0, animationMax - priorAnimationMin,
1226 sTotalChromeHeight, sPixelBuffer, &chromeBMI,
1227 DIB_RGB_COLORS, SRCCOPY);
1229 sReleaseDC(sPreXULSkeletonUIWindow, hdc);
1232 animationIteration++;
1234 // We coordinate around our sleep here to ensure that the main thread does
1235 // not wait on us if we're sleeping. If we don't get 1 here, it means the
1236 // window has been consumed and we don't need to sleep. If in
1237 // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
1238 // incrementing, it means we're sleeping, and that function can assume that
1239 // we will safely exit after the sleep because of the observed value of
1240 // sAnimationControlFlag.
1241 if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
1242 return 0;
1245 // Note: Sleep does not guarantee an exact time interval. If the system is
1246 // busy, for instance, we could easily end up taking several frames longer,
1247 // and really we could be left unscheduled for an arbitrarily long time.
1248 // This is fine, and we don't really care. We could track how much time this
1249 // actually took and jump the animation forward the appropriate amount, but
1250 // its not even clear that that's a better user experience. So we leave this
1251 // as simple as we can.
1252 ::Sleep(16);
1254 // Here we bring sAnimationControlFlag back down - again, if we don't get a
1255 // 0 here it means we consumed the skeleton UI window in the mean time, so
1256 // we can simply exit.
1257 if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
1258 return 0;
1263 LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
1264 LPARAM lParam) {
1265 // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause
1266 // screen readers to report spurious information when the skeleton appears.
1267 if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) {
1268 return E_FAIL;
1271 // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
1272 // sync.
1273 if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
1274 sEnableNonClientDpiScaling(hWnd);
1277 // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
1278 // nsWindow.cpp, and will need to be kept in sync.
1279 if (msg == WM_NCCALCSIZE) {
1280 RECT* clientRect =
1281 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
1282 : (reinterpret_cast<RECT*>(lParam));
1284 // These match the margins set in browser-tabsintitlebar.js with
1285 // default prefs on Windows. Bug 1673092 tracks lining this up with
1286 // that more correctly instead of hard-coding it.
1287 int horizontalOffset =
1288 sNonClientHorizontalMargins -
1289 (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
1290 int verticalOffset =
1291 sNonClientHorizontalMargins -
1292 (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
1293 clientRect->top = clientRect->top;
1294 clientRect->left += horizontalOffset;
1295 clientRect->right -= horizontalOffset;
1296 clientRect->bottom -= verticalOffset;
1297 return 0;
1300 return ::DefWindowProcW(hWnd, msg, wParam, lParam);
1303 bool IsSystemDarkThemeEnabled() {
1304 DWORD result;
1305 HKEY themeKey;
1306 DWORD dataLen = sizeof(uint32_t);
1307 LPCWSTR keyName =
1308 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
1310 result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
1311 if (result != ERROR_SUCCESS) {
1312 return false;
1314 AutoCloseRegKey closeKey(themeKey);
1316 uint32_t lightThemeEnabled;
1317 result = ::RegGetValueW(
1318 themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
1319 reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
1320 if (result != ERROR_SUCCESS) {
1321 return false;
1323 return !lightThemeEnabled;
1326 ThemeColors GetTheme(ThemeMode themeId) {
1327 ThemeColors theme = {};
1328 switch (themeId) {
1329 case ThemeMode::Dark:
1330 // Dark theme or default theme when in dark mode
1332 // controlled by css variable --toolbar-bgcolor
1333 theme.backgroundColor = 0x2b2a33;
1334 theme.tabColor = 0x42414d;
1335 theme.toolbarForegroundColor = 0x6a6a6d;
1336 theme.tabOutlineColor = 0x1c1b22;
1337 // controlled by css variable --lwt-accent-color
1338 theme.tabBarColor = 0x1c1b22;
1339 // controlled by --toolbar-non-lwt-textcolor in browser.css
1340 theme.chromeContentDividerColor = 0x0c0c0d;
1341 // controlled by css variable --toolbar-field-background-color
1342 theme.urlbarColor = 0x42414d;
1343 theme.urlbarBorderColor = 0x42414d;
1344 theme.animationColor = theme.urlbarColor;
1345 return theme;
1346 case ThemeMode::Light:
1347 case ThemeMode::Default:
1348 default:
1349 // --toolbar-non-lwt-bgcolor in browser.css
1350 theme.backgroundColor = 0xf9f9fb;
1351 theme.tabColor = 0xf9f9fb;
1352 theme.toolbarForegroundColor = 0xdddde1;
1353 theme.tabOutlineColor = 0xdddde1;
1354 // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
1355 // (set to "hsl(235,33%,19%)")
1356 theme.tabBarColor = 0xf0f0f4;
1357 // --chrome-content-separator-color in browser.css
1358 theme.chromeContentDividerColor = 0xe1e1e2;
1359 // controlled by css variable --toolbar-color
1360 theme.urlbarColor = 0xffffff;
1361 theme.urlbarBorderColor = 0xdddde1;
1362 theme.animationColor = theme.backgroundColor;
1363 return theme;
1367 Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() {
1368 HKEY key;
1369 DWORD disposition;
1370 LSTATUS result =
1371 ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
1372 0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
1374 if (result != ERROR_SUCCESS) {
1375 return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
1378 if (disposition == REG_CREATED_NEW_KEY ||
1379 disposition == REG_OPENED_EXISTING_KEY) {
1380 return key;
1383 ::RegCloseKey(key);
1384 return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
1387 Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() {
1388 HMODULE user32Dll = ::LoadLibraryW(L"user32");
1389 HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
1391 if (!user32Dll || !gdi32Dll) {
1392 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1395 auto getThreadDpiAwarenessContext =
1396 (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
1397 user32Dll, "GetThreadDpiAwarenessContext");
1398 auto areDpiAwarenessContextsEqual =
1399 (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
1400 user32Dll, "AreDpiAwarenessContextsEqual");
1401 if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
1402 areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
1403 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
1404 // EnableNonClientDpiScaling is optional - we can handle not having it.
1405 sEnableNonClientDpiScaling =
1406 (EnableNonClientDpiScalingProc)::GetProcAddress(
1407 user32Dll, "EnableNonClientDpiScaling");
1410 sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
1411 user32Dll, "GetSystemMetricsForDpi");
1412 if (!sGetSystemMetricsForDpi) {
1413 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1415 sGetDpiForWindow =
1416 (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
1417 if (!sGetDpiForWindow) {
1418 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1420 sRegisterClassW =
1421 (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
1422 if (!sRegisterClassW) {
1423 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1425 sCreateWindowExW =
1426 (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
1427 if (!sCreateWindowExW) {
1428 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1430 sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
1431 if (!sShowWindow) {
1432 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1434 sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
1435 if (!sSetWindowPos) {
1436 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1438 sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
1439 if (!sGetWindowDC) {
1440 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1442 sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
1443 if (!sFillRect) {
1444 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1446 sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
1447 if (!sReleaseDC) {
1448 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1450 sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
1451 if (!sLoadIconW) {
1452 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1454 sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
1455 if (!sLoadCursorW) {
1456 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1458 sMonitorFromWindow =
1459 (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow");
1460 if (!sMonitorFromWindow) {
1461 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1463 sGetMonitorInfoW =
1464 (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW");
1465 if (!sGetMonitorInfoW) {
1466 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1468 sSetWindowLongPtrW =
1469 (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW");
1470 if (!sSetWindowLongPtrW) {
1471 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1473 sStretchDIBits =
1474 (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
1475 if (!sStretchDIBits) {
1476 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1478 sCreateSolidBrush =
1479 (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
1480 if (!sCreateSolidBrush) {
1481 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1483 sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
1484 if (!sDeleteObject) {
1485 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1488 return Ok();
1491 // Strips "--", "-", and "/" from the front of the arg if one of those exists,
1492 // returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
1493 // prefixes are found, the argument is not a flag, and nullptr is returned.
1494 const char* NormalizeFlag(const char* arg) {
1495 if (strstr(arg, "--") == arg) {
1496 return arg + 2;
1499 if (arg[0] == '-') {
1500 return arg + 1;
1503 if (arg[0] == '/') {
1504 return arg + 1;
1507 return nullptr;
1510 static bool EnvHasValue(const char* name) {
1511 const char* val = getenv(name);
1512 return (val && *val);
1515 // Ensures that we only see arguments in the command line which are acceptable.
1516 // This is based on manual inspection of the list of arguments listed in the MDN
1517 // page for Gecko/Firefox commandline options:
1518 // https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
1519 // Broadly speaking, we want to reject any argument which causes us to show
1520 // something other than the default window at its normal size. Here is a non-
1521 // exhaustive list of command line options we want to *exclude*:
1523 // -ProfileManager : This will display the profile manager window, which does
1524 // not match the skeleton UI at all.
1526 // -CreateProfile : This will display a firefox window with the default
1527 // screen position and size, and not the position and size
1528 // which we have recorded in the registry.
1530 // -P <profile> : This could cause us to display firefox with a position
1531 // and size of a different profile than that in which we
1532 // were previously running.
1534 // -width, -height : This will cause the width and height values in the
1535 // registry to be incorrect.
1537 // -kiosk : See above.
1539 // -headless : This one should be rather obvious.
1541 // -migration : This will start with the import wizard, which of course
1542 // does not match the skeleton UI.
1544 // -private-window : This is tricky, but the colors of the main content area
1545 // make this not feel great with the white content of the
1546 // default skeleton UI.
1548 // NOTE: we generally want to skew towards erroneous rejections of the command
1549 // line rather than erroneous approvals. The consequence of a bad rejection
1550 // is that we don't show the skeleton UI, which is business as usual. The
1551 // consequence of a bad approval is that we show it when we're not supposed to,
1552 // which is visually jarring and can also be unpredictable - there's no
1553 // guarantee that the code which handles the non-default window is set up to
1554 // properly handle the transition from the skeleton UI window.
1555 static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments(
1556 int argc, char** argv, bool* explicitProfile) {
1557 const char* approvedArgumentsArray[] = {
1558 // These won't cause the browser to be visualy different in any way
1559 "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
1560 "attach-console", "wait-for-browser", "osint",
1562 // These will cause the chrome to be a bit different or extra windows to
1563 // be created, but overall the skeleton UI should still be broadly
1564 // correct enough.
1565 "new-tab", "new-window",
1567 // To the extent possible, we want to ensure that existing tests cover
1568 // the skeleton UI, so we need to allow marionette
1569 "marionette",
1571 // These will cause the content area to appear different, but won't
1572 // meaningfully affect the chrome
1573 "preferences", "search", "url",
1575 #ifndef MOZILLA_OFFICIAL
1576 // On local builds, we want to allow -profile, because it's how `mach run`
1577 // operates, and excluding that would create an unnecessary blind spot for
1578 // Firefox devs.
1579 "profile"
1580 #endif
1582 // There are other arguments which are likely okay. However, they are
1583 // not included here because this list is not intended to be
1584 // exhaustive - it only intends to green-light some somewhat commonly
1585 // used arguments. We want to err on the side of an unnecessary
1586 // rejection of the command line.
1589 int approvedArgumentsArraySize =
1590 sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
1591 Vector<const char*> approvedArguments;
1592 if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
1593 return Err(PreXULSkeletonUIError::OOM);
1596 for (int i = 0; i < approvedArgumentsArraySize; ++i) {
1597 approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
1600 #ifdef MOZILLA_OFFICIAL
1601 int profileArgIndex = -1;
1602 // If we're running mochitests or direct marionette tests, those specify a
1603 // temporary profile, and we want to ensure that we get the added coverage
1604 // from those.
1605 for (int i = 1; i < argc; ++i) {
1606 const char* flag = NormalizeFlag(argv[i]);
1607 if (flag && !strcmp(flag, "marionette")) {
1608 if (!approvedArguments.append("profile")) {
1609 return Err(PreXULSkeletonUIError::OOM);
1611 profileArgIndex = approvedArguments.length() - 1;
1613 break;
1616 #else
1617 int profileArgIndex = approvedArguments.length() - 1;
1618 #endif
1620 for (int i = 1; i < argc; ++i) {
1621 const char* flag = NormalizeFlag(argv[i]);
1622 if (!flag) {
1623 // If this is not a flag, then we interpret it as a URL, similar to
1624 // BrowserContentHandler.sys.mjs. Some command line options take
1625 // additional arguments, which may or may not be URLs. We don't need to
1626 // know this, because we don't need to parse them out; we just rely on the
1627 // assumption that if arg X is actually a parameter for the preceding
1628 // arg Y, then X must not look like a flag (starting with "--", "-",
1629 // or "/").
1631 // The most important thing here is the assumption that if something is
1632 // going to meaningfully alter the appearance of the window itself, it
1633 // must be a flag.
1634 continue;
1637 bool approved = false;
1638 for (const char* approvedArg : approvedArguments) {
1639 // We do a case-insensitive compare here with _stricmp. Even though some
1640 // of these arguments are *not* read as case-insensitive, others *are*.
1641 // Similar to the flag logic above, we don't really care about this
1642 // distinction, because we don't need to parse the arguments - we just
1643 // rely on the assumption that none of the listed flags in our
1644 // approvedArguments are overloaded in such a way that a different
1645 // casing would visually alter the firefox window.
1646 if (!_stricmp(flag, approvedArg)) {
1647 approved = true;
1649 if (i == profileArgIndex) {
1650 *explicitProfile = true;
1652 break;
1656 if (!approved) {
1657 return Err(PreXULSkeletonUIError::Cmdline);
1661 return Ok();
1664 static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() {
1665 if (EnvHasValue("MOZ_SAFE_MODE_RESTART") ||
1666 EnvHasValue("MOZ_APP_SILENT_START") ||
1667 EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") ||
1668 (EnvHasValue("XRE_PROFILE_PATH") &&
1669 !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) {
1670 return Err(PreXULSkeletonUIError::EnvVars);
1673 return Ok();
1676 static bool VerifyWindowDimensions(uint32_t windowWidth,
1677 uint32_t windowHeight) {
1678 return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
1681 static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans(
1682 HKEY regKey, const std::wstring& valueName) {
1683 DWORD dataLen = 0;
1684 LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr,
1685 nullptr, nullptr, &dataLen);
1686 if (result != ERROR_SUCCESS) {
1687 return Err(PreXULSkeletonUIError::RegistryError);
1690 if (dataLen % (2 * sizeof(double)) != 0) {
1691 return Err(PreXULSkeletonUIError::CorruptData);
1694 auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
1695 if (!buffer) {
1696 return Err(PreXULSkeletonUIError::OOM);
1698 result =
1699 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
1700 nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen);
1701 if (result != ERROR_SUCCESS) {
1702 return Err(PreXULSkeletonUIError::RegistryError);
1705 Vector<CSSPixelSpan> resultVector;
1706 double* asDoubles = reinterpret_cast<double*>(buffer.get());
1707 for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) {
1708 CSSPixelSpan span = {};
1709 span.start = *(asDoubles++);
1710 span.end = *(asDoubles++);
1711 if (!resultVector.append(span)) {
1712 return Err(PreXULSkeletonUIError::OOM);
1716 return resultVector;
1719 static Result<double, PreXULSkeletonUIError> ReadRegDouble(
1720 HKEY regKey, const std::wstring& valueName) {
1721 double value = 0;
1722 DWORD dataLen = sizeof(double);
1723 LSTATUS result =
1724 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
1725 nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
1726 if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
1727 return Err(PreXULSkeletonUIError::RegistryError);
1730 return value;
1733 static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint(
1734 HKEY regKey, const std::wstring& valueName) {
1735 DWORD value = 0;
1736 DWORD dataLen = sizeof(uint32_t);
1737 LSTATUS result =
1738 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD,
1739 nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
1740 if (result != ERROR_SUCCESS) {
1741 return Err(PreXULSkeletonUIError::RegistryError);
1744 return value;
1747 static Result<bool, PreXULSkeletonUIError> ReadRegBool(
1748 HKEY regKey, const std::wstring& valueName) {
1749 uint32_t value;
1750 MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName));
1751 return !!value;
1754 static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans(
1755 HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans,
1756 int spansLength) {
1757 // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's
1758 // also trivial to just copy them into a buffer of doubles.
1759 auto doubles = MakeUnique<double[]>(spansLength * 2);
1760 for (int i = 0; i < spansLength; ++i) {
1761 doubles[i * 2] = spans[i].start;
1762 doubles[i * 2 + 1] = spans[i].end;
1765 LSTATUS result =
1766 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
1767 reinterpret_cast<const BYTE*>(doubles.get()),
1768 spansLength * sizeof(double) * 2);
1769 if (result != ERROR_SUCCESS) {
1770 return Err(PreXULSkeletonUIError::RegistryError);
1772 return Ok();
1775 static Result<Ok, PreXULSkeletonUIError> WriteRegDouble(
1776 HKEY regKey, const std::wstring& valueName, double value) {
1777 LSTATUS result =
1778 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
1779 reinterpret_cast<const BYTE*>(&value), sizeof(value));
1780 if (result != ERROR_SUCCESS) {
1781 return Err(PreXULSkeletonUIError::RegistryError);
1784 return Ok();
1787 static Result<Ok, PreXULSkeletonUIError> WriteRegUint(
1788 HKEY regKey, const std::wstring& valueName, uint32_t value) {
1789 LSTATUS result =
1790 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD,
1791 reinterpret_cast<PBYTE>(&value), sizeof(value));
1792 if (result != ERROR_SUCCESS) {
1793 return Err(PreXULSkeletonUIError::RegistryError);
1796 return Ok();
1799 static Result<Ok, PreXULSkeletonUIError> WriteRegBool(
1800 HKEY regKey, const std::wstring& valueName, bool value) {
1801 return WriteRegUint(regKey, valueName, value ? 1 : 0);
1804 static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl(
1805 HINSTANCE hInstance, int argc, char** argv) {
1806 // Initializing COM below may load modules via SetWindowHookEx, some of
1807 // which may modify the executable's IAT for ntdll.dll. If that happens,
1808 // this browser process fails to launch sandbox processes because we cannot
1809 // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp).
1810 // To prevent that, we cache the intact IAT before COM initialization.
1811 // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will
1812 // also prevent an injected module from parsing the PE headers and modifying
1813 // the IAT. Therefore, we can skip CacheNtDllThunk().
1814 if (!mozilla::IsEafPlusEnabled()) {
1815 CacheNtDllThunk();
1818 // NOTE: it's important that we initialize sProcessRuntime before showing a
1819 // window. Historically we ran into issues where showing the window would
1820 // cause an accessibility win event to fire, which could cause in-process
1821 // system or third party components to initialize COM and prevent us from
1822 // initializing it with important settings we need.
1824 // Some COM settings are global to the process and must be set before any non-
1825 // trivial COM is run in the application. Since these settings may affect
1826 // stability, we should instantiate COM ASAP so that we can ensure that these
1827 // global settings are configured before anything can interfere.
1828 sProcessRuntime = new mscom::ProcessRuntime(
1829 mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent);
1831 const TimeStamp skeletonStart = TimeStamp::Now();
1833 HKEY regKey;
1834 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
1835 AutoCloseRegKey closeKey(regKey);
1837 UniquePtr<wchar_t[]> binPath;
1838 MOZ_TRY_VAR(binPath, GetBinaryPath());
1840 std::wstring regProgressName =
1841 GetRegValueName(binPath.get(), sProgressSuffix);
1842 auto progressResult = ReadRegUint(regKey, regProgressName);
1843 if (!progressResult.isErr() &&
1844 progressResult.unwrap() !=
1845 static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) {
1846 return Err(PreXULSkeletonUIError::CrashedOnce);
1849 MOZ_TRY(
1850 WriteRegUint(regKey, regProgressName,
1851 static_cast<uint32_t>(PreXULSkeletonUIProgress::Started)));
1852 auto writeCompletion = MakeScopeExit([&] {
1853 Unused << WriteRegUint(
1854 regKey, regProgressName,
1855 static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed));
1858 MOZ_TRY(GetSkeletonUILock());
1860 bool explicitProfile = false;
1861 MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile));
1862 MOZ_TRY(ValidateEnvVars());
1864 auto enabledResult =
1865 ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix));
1866 if (enabledResult.isErr()) {
1867 return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist);
1869 if (!enabledResult.unwrap()) {
1870 return Err(PreXULSkeletonUIError::Disabled);
1872 sPreXULSkeletonUIEnabled = true;
1874 MOZ_ASSERT(!sAnimatedRects);
1875 sAnimatedRects = new Vector<ColorRect>();
1877 MOZ_TRY(LoadGdi32AndUser32Procedures());
1879 if (!explicitProfile) {
1880 MOZ_TRY(CheckForStartWithLastProfile());
1883 WNDCLASSW wc;
1884 wc.style = CS_DBLCLKS;
1885 wc.lpfnWndProc = PreXULSkeletonUIProc;
1886 wc.cbClsExtra = 0;
1887 wc.cbWndExtra = 0;
1888 wc.hInstance = hInstance;
1889 wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
1890 wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
1891 wc.hbrBackground = nullptr;
1892 wc.lpszMenuName = nullptr;
1894 // TODO: just ensure we disable this if we've overridden the window class
1895 wc.lpszClassName = L"MozillaWindowClass";
1897 if (!sRegisterClassW(&wc)) {
1898 return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass);
1901 uint32_t screenX;
1902 MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1903 sScreenXRegSuffix)));
1904 uint32_t screenY;
1905 MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1906 sScreenYRegSuffix)));
1907 uint32_t windowWidth;
1908 MOZ_TRY_VAR(
1909 windowWidth,
1910 ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix)));
1911 uint32_t windowHeight;
1912 MOZ_TRY_VAR(
1913 windowHeight,
1914 ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix)));
1915 MOZ_TRY_VAR(
1916 sMaximized,
1917 ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix)));
1918 MOZ_TRY_VAR(
1919 sCSSToDevPixelScaling,
1920 ReadRegDouble(regKey, GetRegValueName(binPath.get(),
1921 sCssToDevPixelScalingRegSuffix)));
1922 Vector<CSSPixelSpan> urlbar;
1923 MOZ_TRY_VAR(urlbar,
1924 ReadRegCSSPixelSpans(
1925 regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix)));
1926 Vector<CSSPixelSpan> searchbar;
1927 MOZ_TRY_VAR(searchbar,
1928 ReadRegCSSPixelSpans(
1929 regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix)));
1930 Vector<CSSPixelSpan> springs;
1931 MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans(
1932 regKey, GetRegValueName(binPath.get(),
1933 sSpringsCSSRegSuffix)));
1935 if (urlbar.empty() || searchbar.empty()) {
1936 return Err(PreXULSkeletonUIError::CorruptData);
1939 EnumSet<SkeletonUIFlag, uint32_t> flags;
1940 uint32_t flagsUint;
1941 MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1942 sFlagsRegSuffix)));
1943 flags.deserialize(flagsUint);
1945 if (flags.contains(SkeletonUIFlag::TouchDensity) ||
1946 flags.contains(SkeletonUIFlag::CompactDensity)) {
1947 return Err(PreXULSkeletonUIError::BadUIDensity);
1950 uint32_t theme;
1951 MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1952 sThemeRegSuffix)));
1953 ThemeMode themeMode = static_cast<ThemeMode>(theme);
1954 if (themeMode == ThemeMode::Default) {
1955 if (IsSystemDarkThemeEnabled() == true) {
1956 themeMode = ThemeMode::Dark;
1959 ThemeColors currentTheme = GetTheme(themeMode);
1961 if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
1962 return Err(PreXULSkeletonUIError::BadWindowDimensions);
1965 int showCmd = SW_SHOWNORMAL;
1966 DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
1967 if (sMaximized) {
1968 showCmd = SW_SHOWMAXIMIZED;
1969 windowStyle |= WS_MAXIMIZE;
1972 sPreXULSkeletonUIWindow =
1973 sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
1974 L"", windowStyle, screenX, screenY, windowWidth,
1975 windowHeight, nullptr, nullptr, hInstance, nullptr);
1976 if (!sPreXULSkeletonUIWindow) {
1977 return Err(PreXULSkeletonUIError::CreateWindowFailed);
1980 sShowWindow(sPreXULSkeletonUIWindow, showCmd);
1982 sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
1983 sNonClientHorizontalMargins =
1984 sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
1985 sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
1986 sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
1987 sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
1989 if (sMaximized) {
1990 HMONITOR monitor =
1991 sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
1992 if (!monitor) {
1993 // NOTE: we specifically don't clean up the window here. If we're unable
1994 // to finish setting up the window how we want it, we still need to keep
1995 // it around and consume it with the first real toplevel window we
1996 // create, to avoid flickering.
1997 return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
1999 MONITORINFO mi = {sizeof(MONITORINFO)};
2000 if (!sGetMonitorInfoW(monitor, &mi)) {
2001 return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
2004 sWindowWidth =
2005 mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2;
2006 sWindowHeight =
2007 mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2;
2008 } else {
2009 sWindowWidth = static_cast<int>(windowWidth);
2010 sWindowHeight = static_cast<int>(windowHeight);
2013 sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
2014 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2015 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2016 MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0],
2017 springs, currentTheme, flags));
2018 if (sAnimatedRects) {
2019 sPreXULSKeletonUIAnimationThread = ::CreateThread(
2020 nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
2023 BASE_PROFILER_MARKER_UNTYPED(
2024 "CreatePreXULSkeletonUI", OTHER,
2025 MarkerTiming::IntervalUntilNowFrom(skeletonStart));
2027 return Ok();
2030 void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
2031 char** argv) {
2032 auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv);
2034 if (result.isErr()) {
2035 sErrorReason.emplace(result.unwrapErr());
2039 void CleanupProcessRuntime() {
2040 delete sProcessRuntime;
2041 sProcessRuntime = nullptr;
2044 bool WasPreXULSkeletonUIMaximized() { return sMaximized; }
2046 bool GetPreXULSkeletonUIWasShown() {
2047 return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow;
2050 HWND ConsumePreXULSkeletonUIHandle() {
2051 // NOTE: we need to make sure that everything that runs here is a no-op if
2052 // it failed to be set, which is a possibility. If anything fails to be set
2053 // we don't want to clean everything up right away, because if we have a
2054 // blank window up, we want that to stick around and get consumed by nsWindow
2055 // as normal, otherwise the window will flicker in and out, which we imagine
2056 // is unpleasant.
2058 // If we don't get 1 here, it means the thread is actually just sleeping, so
2059 // we don't need to worry about giving out ownership of the window, because
2060 // the thread will simply exit after its sleep. However, if it is 1, we need
2061 // to wait for the thread to exit to be safe, as it could be doing anything.
2062 if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
2063 ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
2065 ::CloseHandle(sPreXULSKeletonUIAnimationThread);
2066 sPreXULSKeletonUIAnimationThread = nullptr;
2067 HWND result = sPreXULSkeletonUIWindow;
2068 sPreXULSkeletonUIWindow = nullptr;
2069 free(sPixelBuffer);
2070 sPixelBuffer = nullptr;
2071 delete sAnimatedRects;
2072 sAnimatedRects = nullptr;
2074 return result;
2077 Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason() {
2078 return sErrorReason;
2081 Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
2082 const SkeletonUISettings& settings) {
2083 if (!sPreXULSkeletonUIEnabled) {
2084 return Err(PreXULSkeletonUIError::Disabled);
2087 HKEY regKey;
2088 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2089 AutoCloseRegKey closeKey(regKey);
2091 UniquePtr<wchar_t[]> binPath;
2092 MOZ_TRY_VAR(binPath, GetBinaryPath());
2094 MOZ_TRY(WriteRegUint(regKey,
2095 GetRegValueName(binPath.get(), sScreenXRegSuffix),
2096 settings.screenX));
2097 MOZ_TRY(WriteRegUint(regKey,
2098 GetRegValueName(binPath.get(), sScreenYRegSuffix),
2099 settings.screenY));
2100 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix),
2101 settings.width));
2102 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix),
2103 settings.height));
2105 MOZ_TRY(WriteRegBool(regKey,
2106 GetRegValueName(binPath.get(), sMaximizedRegSuffix),
2107 settings.maximized));
2109 EnumSet<SkeletonUIFlag, uint32_t> flags;
2110 if (settings.menubarShown) {
2111 flags += SkeletonUIFlag::MenubarShown;
2113 if (settings.bookmarksToolbarShown) {
2114 flags += SkeletonUIFlag::BookmarksToolbarShown;
2116 if (settings.rtlEnabled) {
2117 flags += SkeletonUIFlag::RtlEnabled;
2119 if (settings.uiDensity == SkeletonUIDensity::Touch) {
2120 flags += SkeletonUIFlag::TouchDensity;
2122 if (settings.uiDensity == SkeletonUIDensity::Compact) {
2123 flags += SkeletonUIFlag::CompactDensity;
2126 uint32_t flagsUint = flags.serialize();
2127 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix),
2128 flagsUint));
2130 MOZ_TRY(WriteRegDouble(
2131 regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix),
2132 settings.cssToDevPixelScaling));
2133 MOZ_TRY(WriteRegCSSPixelSpans(
2134 regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix),
2135 &settings.urlbarSpan, 1));
2136 MOZ_TRY(WriteRegCSSPixelSpans(
2137 regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix),
2138 &settings.searchbarSpan, 1));
2139 MOZ_TRY(WriteRegCSSPixelSpans(
2140 regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix),
2141 settings.springs.begin(), settings.springs.length()));
2143 return Ok();
2146 MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }
2148 MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
2149 bool value) {
2150 // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
2151 // ignore changes to the registry. An example of how things could be bad if
2152 // we didn't: someone running firefox with the -profile argument could
2153 // turn the skeleton UI on or off for the default profile. Turning it off
2154 // maybe isn't so bad (though it's likely still incorrect), but turning it
2155 // on could be bad if the user had specifically disabled it for a profile for
2156 // some reason. Ultimately there's no correct decision here, and the
2157 // messiness of this is just a consequence of sharing the registry values
2158 // across profiles. However, whatever ill effects we observe should be
2159 // correct themselves after one session.
2160 if (PreXULSkeletonUIDisallowed()) {
2161 return Err(PreXULSkeletonUIError::Disabled);
2164 HKEY regKey;
2165 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2166 AutoCloseRegKey closeKey(regKey);
2168 UniquePtr<wchar_t[]> binPath;
2169 MOZ_TRY_VAR(binPath, GetBinaryPath());
2170 MOZ_TRY(WriteRegBool(
2171 regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value));
2173 if (!sPreXULSkeletonUIEnabled && value) {
2174 // We specifically don't care if we fail to get this lock. We just want to
2175 // do our best effort to lock it so that future instances don't create
2176 // skeleton UIs while we're still running, since they will immediately exit
2177 // and tell us to open a new window.
2178 Unused << GetSkeletonUILock();
2181 sPreXULSkeletonUIEnabled = value;
2183 return Ok();
2186 MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
2187 ThemeMode theme) {
2188 if (theme == sTheme) {
2189 return Ok();
2191 sTheme = theme;
2193 // If we fail below, invalidate sTheme
2194 auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; });
2196 HKEY regKey;
2197 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2198 AutoCloseRegKey closeKey(regKey);
2200 UniquePtr<wchar_t[]> binPath;
2201 MOZ_TRY_VAR(binPath, GetBinaryPath());
2202 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix),
2203 static_cast<uint32_t>(theme)));
2205 invalidateTheme.release();
2206 return Ok();
2209 MFBT_API void PollPreXULSkeletonUIEvents() {
2210 if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
2211 MSG outMsg = {};
2212 PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
2216 Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() {
2217 if (!sPreXULSkeletonUIEnabled) {
2218 return Err(PreXULSkeletonUIError::Disabled);
2221 ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1");
2223 // We assume that we are going to exit the application very shortly after
2224 // this. It should thus be fine to release this lock, and we'll need to,
2225 // since during a restart we launch the new instance before closing this
2226 // one.
2227 if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
2228 ::CloseHandle(sPreXULSKeletonUILockFile);
2230 return Ok();
2233 } // namespace mozilla