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