Bug 1931425 - Limit how often moz-label's #setStyles runs r=reusable-components-revie...
[gecko.git] / mozglue / misc / PreXULSkeletonUI.cpp
blobabde018dddf5267109316b0af3785f9e22746879
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 <dwmapi.h>
11 #include <math.h>
12 #include <limits.h>
13 #include <cmath>
14 #include <locale>
15 #include <string>
16 #include <objbase.h>
17 #include <shlobj.h>
19 #include "mozilla/Assertions.h"
20 #include "mozilla/Attributes.h"
21 #include "mozilla/BaseProfilerMarkers.h"
22 #include "mozilla/CacheNtDllThunk.h"
23 #include "mozilla/FStream.h"
24 #include "mozilla/GetKnownFolderPath.h"
25 #include "mozilla/HashFunctions.h"
26 #include "mozilla/HelperMacros.h"
27 #include "mozilla/glue/Debug.h"
28 #include "mozilla/Maybe.h"
29 #include "mozilla/mscom/ProcessRuntime.h"
30 #include "mozilla/ResultVariant.h"
31 #include "mozilla/ScopeExit.h"
32 #include "mozilla/Try.h"
33 #include "mozilla/UniquePtr.h"
34 #include "mozilla/UniquePtrExtensions.h"
35 #include "mozilla/Unused.h"
36 #include "mozilla/WindowsDpiAwareness.h"
37 #include "mozilla/WindowsProcessMitigations.h"
39 namespace mozilla {
41 // ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
42 // particular color that we will draw.
43 struct ColorRect {
44 uint32_t color;
45 uint32_t borderColor;
46 int x;
47 int y;
48 int width;
49 int height;
50 int borderWidth;
51 int borderRadius;
52 bool flipIfRTL;
55 // DrawRect is mostly the same as ColorRect, but exists as an implementation
56 // detail to simplify drawing borders. We draw borders as a strokeOnly rect
57 // underneath an inner rect of a particular color. We also need to keep
58 // track of the backgroundColor for rounding rects, in order to correctly
59 // anti-alias.
60 struct DrawRect {
61 uint32_t color;
62 uint32_t backgroundColor;
63 int x;
64 int y;
65 int width;
66 int height;
67 int borderRadius;
68 int borderWidth;
69 bool strokeOnly;
72 struct NormalizedRGB {
73 double r;
74 double g;
75 double b;
78 NormalizedRGB UintToRGB(uint32_t color) {
79 double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
80 double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
81 double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
82 return NormalizedRGB{r, g, b};
85 uint32_t RGBToUint(const NormalizedRGB& rgb) {
86 return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
87 (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
88 (static_cast<uint32_t>(rgb.b * 255.0) << 0);
91 double Lerp(double a, double b, double x) { return a + x * (b - a); }
93 NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
94 return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
97 // Produces a smooth curve in [0,1] based on a linear input in [0,1]
98 double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }
100 struct Margin {
101 int top = 0;
102 int right = 0;
103 int bottom = 0;
104 int left = 0;
107 static const wchar_t kPreXULSkeletonUIKeyPath[] =
108 L"SOFTWARE"
109 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";
111 static bool sPreXULSkeletonUIShown = false;
112 static bool sPreXULSkeletonUIEnabled = false;
113 static HWND sPreXULSkeletonUIWindow;
114 static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
115 static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
116 static HANDLE sPreXULSKeletonUIAnimationThread;
117 static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE;
119 static mozilla::mscom::ProcessRuntime* sProcessRuntime;
120 static uint32_t* sPixelBuffer = nullptr;
121 static Vector<ColorRect>* sAnimatedRects = nullptr;
122 static int sTotalChromeHeight = 0;
123 static volatile LONG sAnimationControlFlag = 0;
124 static bool sMaximized = false;
125 static uint32_t sDpi = 0;
126 // See nsWindow::mNonClientOffset
127 static Margin sNonClientOffset;
128 static int sCaptionHeight = 0;
129 static int sHorizontalResizeMargin = 0;
130 static int sVerticalResizeMargin = 0;
132 // See nsWindow::NonClientSizeMargin()
133 static Margin NonClientSizeMargin() {
134 return Margin{sCaptionHeight + sVerticalResizeMargin - sNonClientOffset.top,
135 sHorizontalResizeMargin - sNonClientOffset.right,
136 sVerticalResizeMargin - sNonClientOffset.bottom,
137 sHorizontalResizeMargin - sNonClientOffset.left};
140 // Color values needed by the animation loop
141 static uint32_t sAnimationColor;
142 static uint32_t sToolbarForegroundColor;
144 static ThemeMode sTheme = ThemeMode::Invalid;
146 #define MOZ_DECL_IMPORTED_WIN32_FN(name) \
147 static decltype(&::name) s##name = nullptr
148 MOZ_DECL_IMPORTED_WIN32_FN(EnableNonClientDpiScaling);
149 MOZ_DECL_IMPORTED_WIN32_FN(GetSystemMetricsForDpi);
150 MOZ_DECL_IMPORTED_WIN32_FN(GetDpiForWindow);
151 MOZ_DECL_IMPORTED_WIN32_FN(RegisterClassW);
152 MOZ_DECL_IMPORTED_WIN32_FN(LoadIconW);
153 MOZ_DECL_IMPORTED_WIN32_FN(LoadCursorW);
154 MOZ_DECL_IMPORTED_WIN32_FN(CreateWindowExW);
155 MOZ_DECL_IMPORTED_WIN32_FN(ShowWindow);
156 MOZ_DECL_IMPORTED_WIN32_FN(SetWindowPos);
157 MOZ_DECL_IMPORTED_WIN32_FN(GetWindowDC);
158 MOZ_DECL_IMPORTED_WIN32_FN(GetWindowRect);
159 MOZ_DECL_IMPORTED_WIN32_FN(MapWindowPoints);
160 MOZ_DECL_IMPORTED_WIN32_FN(FillRect);
161 MOZ_DECL_IMPORTED_WIN32_FN(DeleteObject);
162 MOZ_DECL_IMPORTED_WIN32_FN(ReleaseDC);
163 MOZ_DECL_IMPORTED_WIN32_FN(MonitorFromWindow);
164 MOZ_DECL_IMPORTED_WIN32_FN(GetMonitorInfoW);
165 MOZ_DECL_IMPORTED_WIN32_FN(SetWindowLongPtrW);
166 MOZ_DECL_IMPORTED_WIN32_FN(StretchDIBits);
167 MOZ_DECL_IMPORTED_WIN32_FN(CreateSolidBrush);
168 MOZ_DECL_IMPORTED_WIN32_FN(DwmGetWindowAttribute);
169 MOZ_DECL_IMPORTED_WIN32_FN(DwmSetWindowAttribute);
170 #undef MOZ_DECL_IMPORTED_WIN32_FN
172 static int sWindowWidth;
173 static int sWindowHeight;
174 static double sCSSToDevPixelScaling;
176 static Maybe<PreXULSkeletonUIError> sErrorReason;
178 static const int kAnimationCSSPixelsPerFrame = 11;
179 static const int kAnimationCSSExtraWindowSize = 300;
181 // NOTE: these values were pulled out of thin air as round numbers that are
182 // likely to be too big to be seen in practice. If we legitimately see windows
183 // this big, we probably don't want to be drawing them on the CPU anyway.
184 static const uint32_t kMaxWindowWidth = 1 << 16;
185 static const uint32_t kMaxWindowHeight = 1 << 16;
187 static const wchar_t* sEnabledRegSuffix = L"|Enabled";
188 static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
189 static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
190 static const wchar_t* sWidthRegSuffix = L"|Width";
191 static const wchar_t* sHeightRegSuffix = L"|Height";
192 static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
193 static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
194 static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
195 static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
196 static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
197 static const wchar_t* sThemeRegSuffix = L"|Theme";
198 static const wchar_t* sFlagsRegSuffix = L"|Flags";
199 static const wchar_t* sProgressSuffix = L"|Progress";
201 std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
202 std::wstring result(prefix);
203 result.append(suffix);
204 return result;
207 // This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
208 // included in standalone SpiderMonkey builds prohibits us from including that
209 // file directly, and it hardly warrants its own header. Bug 1674920 tracks
210 // only including this file for gecko-related builds.
211 Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() {
212 DWORD bufLen = MAX_PATH;
213 UniquePtr<wchar_t[]> buf;
214 while (true) {
215 buf = MakeUnique<wchar_t[]>(bufLen);
216 DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
217 if (!retLen) {
218 return Err(PreXULSkeletonUIError::FilesystemFailure);
221 if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
222 bufLen *= 2;
223 continue;
226 break;
229 return buf;
232 // PreXULSkeletonUIDisallowed means that we don't even have the capacity to
233 // enable the skeleton UI, whether because we're on a platform that doesn't
234 // support it or because we launched with command line arguments that we don't
235 // support. Some of these situations are transient, so we want to make sure we
236 // don't mess with registry values in these scenarios that we may use in
237 // other scenarios in which the skeleton UI is actually enabled.
238 static bool PreXULSkeletonUIDisallowed() {
239 return sErrorReason.isSome() &&
240 (*sErrorReason == PreXULSkeletonUIError::Cmdline ||
241 *sErrorReason == PreXULSkeletonUIError::EnvVars);
244 // Note: this is specifically *not* a robust, multi-locale lowercasing
245 // operation. It is not intended to be such. It is simply intended to match the
246 // way in which we look for other instances of firefox to remote into.
247 // See
248 // https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
249 static void MutateStringToLowercase(wchar_t* ptr) {
250 while (*ptr) {
251 wchar_t ch = *ptr;
252 if (ch >= L'A' && ch <= L'Z') {
253 *ptr = ch + (L'a' - L'A');
255 ++ptr;
259 static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() {
260 auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
261 if (!localAppDataPath) {
262 return Err(PreXULSkeletonUIError::FilesystemFailure);
265 if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
266 return Ok();
269 // Note: because we're in mozglue, we cannot easily access things from
270 // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
271 // mozglue, and rip out all of its usage of types defined in toolkit headers.
272 // However, it seems cleaner to just hash the bin path ourselves. We don't
273 // get quite the same robustness that `GetInstallHash` might provide, but
274 // we already don't have that with how we key our registry values, so it
275 // probably makes sense to just match those.
276 UniquePtr<wchar_t[]> binPath;
277 MOZ_TRY_VAR(binPath, GetBinaryPath());
279 // Lowercase the binpath to match how we look for remote instances.
280 MutateStringToLowercase(binPath.get());
282 // The number of bytes * 2 characters per byte + 1 for the null terminator
283 uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
284 UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
285 // This isn't perfect - it's a 32-bit hash of the path to our executable. It
286 // could reasonably collide, or casing could potentially affect things, but
287 // the theory is that that should be uncommon enough and the failure case
288 // mild enough that this is fine.
289 uint32_t binPathHash = HashString(binPath.get());
290 swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);
292 std::wstring lockFilePath;
293 lockFilePath.append(localAppDataPath.get());
294 lockFilePath.append(
295 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
296 lockFilePath.append(installHash.get());
298 // We intentionally leak this file - that is okay, and (kind of) the point.
299 // We want to hold onto this handle until the application exits, and hold
300 // onto it with exclusive rights. If this check fails, then we assume that
301 // another instance of the executable is holding it, and thus return false.
302 sPreXULSKeletonUILockFile =
303 ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
304 0, // No sharing - this is how the lock works
305 nullptr, CREATE_ALWAYS,
306 FILE_FLAG_DELETE_ON_CLOSE, // Don't leave this lying around
307 nullptr);
308 if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) {
309 return Err(PreXULSkeletonUIError::FailedGettingLock);
312 return Ok();
315 const char kGeneralSection[] = "[General]";
316 const char kStartWithLastProfile[] = "StartWithLastProfile=";
318 static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
319 bool inGeneral = false;
320 std::string line;
321 while (std::getline(iniContents, line)) {
322 size_t whitespace = 0;
323 while (line.length() > whitespace &&
324 (line[whitespace] == ' ' || line[whitespace] == '\t')) {
325 whitespace++;
327 line.erase(0, whitespace);
329 if (line.compare(kGeneralSection) == 0) {
330 inGeneral = true;
331 } else if (inGeneral) {
332 if (line[0] == '[') {
333 inGeneral = false;
334 } else {
335 if (line.find(kStartWithLastProfile) == 0) {
336 char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
337 if (val == '0') {
338 return false;
339 } else if (val == '1') {
340 return true;
347 // If we don't find it in the .ini file, we interpret that as true
348 return true;
351 static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() {
352 auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
353 if (!roamingAppData) {
354 return Err(PreXULSkeletonUIError::FilesystemFailure);
356 std::wstring profileDbPath(roamingAppData.get());
357 profileDbPath.append(
358 L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
359 IFStream profileDb(profileDbPath.c_str());
360 if (profileDb.fail()) {
361 return Err(PreXULSkeletonUIError::FilesystemFailure);
364 if (!ProfileDbHasStartWithLastProfile(profileDb)) {
365 return Err(PreXULSkeletonUIError::NoStartWithLastProfile);
368 return Ok();
371 // We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
372 // failures in random places because we're in mozglue. Overall it should be
373 // simpler and cleaner to just step around that issue with this class:
374 class MOZ_RAII AutoCloseRegKey {
375 public:
376 explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
377 ~AutoCloseRegKey() { ::RegCloseKey(mKey); }
379 private:
380 HKEY mKey;
383 int CSSToDevPixels(double cssPixels, double scaling) {
384 return floor(cssPixels * scaling + 0.5);
387 int CSSToDevPixels(int cssPixels, double scaling) {
388 return CSSToDevPixels((double)cssPixels, scaling);
391 int CSSToDevPixelsFloor(double cssPixels, double scaling) {
392 return floor(cssPixels * scaling);
395 // Some things appear to floor to device pixels rather than rounding. A good
396 // example of this is border widths.
397 int CSSToDevPixelsFloor(int cssPixels, double scaling) {
398 return CSSToDevPixelsFloor((double)cssPixels, scaling);
401 double SignedDistanceToCircle(double x, double y, double radius) {
402 return sqrt(x * x + y * y) - radius;
405 // For more details, see
406 // https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
407 // which was a reference for this function.
408 double DistanceAntiAlias(double signedDistance) {
409 // Distance assumed to be in device pixels. We use an aa range of 0.5 for
410 // reasons detailed in the linked code above.
411 const double aaRange = 0.5;
412 double dist = 0.5 * signedDistance / aaRange;
413 if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
414 if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
415 return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
418 void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
419 if (rect.height <= 2 * rect.borderRadius) {
420 MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
421 return;
423 if (rect.width <= 2 * rect.borderRadius) {
424 MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
425 return;
428 NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
429 NormalizedRGB rgbBlend = UintToRGB(rect.color);
431 for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
432 int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
433 int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;
435 uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
436 uint32_t* innermostPixelTopLeft =
437 lineStartTop + rect.x + rect.borderRadius - 1;
438 uint32_t* innermostPixelTopRight =
439 lineStartTop + rect.x + rect.width - rect.borderRadius;
440 uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
441 uint32_t* innermostPixelBottomLeft =
442 lineStartBottom + rect.x + rect.borderRadius - 1;
443 uint32_t* innermostPixelBottomRight =
444 lineStartBottom + rect.x + rect.width - rect.borderRadius;
446 // Add 0.5 to x and y to get the pixel center.
447 double pixelY = (double)rowIndex + 0.5;
448 for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
449 double pixelX = (double)columnIndex + 0.5;
450 double distance =
451 SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
452 double alpha = DistanceAntiAlias(distance);
453 NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
454 uint32_t color = RGBToUint(rgb);
456 innermostPixelTopLeft[-columnIndex] = color;
457 innermostPixelTopRight[columnIndex] = color;
458 innermostPixelBottomLeft[-columnIndex] = color;
459 innermostPixelBottomRight[columnIndex] = color;
462 std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
463 std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
464 rect.color);
468 void RasterizeAnimatedRoundedRectTopAndBottom(
469 const ColorRect& colorRect, const uint32_t* animationLookup,
470 int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
471 int currentUpdateAreaMax, int animationMin) {
472 // We iterate through logical pixel rows here, from inside to outside, which
473 // for the top of the rounded rect means from bottom to top, and for the
474 // bottom of the rect means top to bottom. We paint pixels from left to
475 // right on the top and bottom rows at the same time for the entire animation
476 // window. (If the animation window does not overlap any rounded corners,
477 // however, we won't be called at all)
478 for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
479 int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
480 int yBottom =
481 colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;
483 uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
484 uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
486 // Add 0.5 to x and y to get the pixel center.
487 double pixelY = (double)rowIndex + 0.5;
488 for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
489 // The column index is the distance from the innermost pixel, which
490 // is different depending on whether we're on the left or right
491 // side of the rect. It will always be the max here, and if it's
492 // negative that just means we're outside the rounded area.
493 int columnIndex =
494 std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
495 x - ((int)colorRect.x + (int)colorRect.width -
496 (int)colorRect.borderRadius));
498 double alpha = 1.0;
499 if (columnIndex >= 0) {
500 double pixelX = (double)columnIndex + 0.5;
501 double distance = SignedDistanceToCircle(
502 pixelX, pixelY, (double)colorRect.borderRadius);
503 alpha = DistanceAntiAlias(distance);
505 // We don't do alpha blending for the antialiased pixels at the
506 // shape's border. It is not noticeable in the animation.
507 if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
508 // Overwrite the tail end of last frame's animation with the
509 // rect's normal, unanimated color.
510 uint32_t color = x < priorUpdateAreaMax
511 ? colorRect.color
512 : animationLookup[x - animationMin];
513 lineStartTop[x] = color;
514 lineStartBottom[x] = color;
520 void RasterizeColorRect(const ColorRect& colorRect) {
521 // We sometimes split our rect into two, to simplify drawing borders. If we
522 // have a border, we draw a stroke-only rect first, and then draw the smaller
523 // inner rect on top of it.
524 Vector<DrawRect, 2> drawRects;
525 Unused << drawRects.reserve(2);
526 if (colorRect.borderWidth == 0) {
527 DrawRect rect = {};
528 rect.color = colorRect.color;
529 rect.backgroundColor =
530 sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
531 rect.x = colorRect.x;
532 rect.y = colorRect.y;
533 rect.width = colorRect.width;
534 rect.height = colorRect.height;
535 rect.borderRadius = colorRect.borderRadius;
536 rect.strokeOnly = false;
537 drawRects.infallibleAppend(rect);
538 } else {
539 DrawRect borderRect = {};
540 borderRect.color = colorRect.borderColor;
541 borderRect.backgroundColor =
542 sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
543 borderRect.x = colorRect.x;
544 borderRect.y = colorRect.y;
545 borderRect.width = colorRect.width;
546 borderRect.height = colorRect.height;
547 borderRect.borderRadius = colorRect.borderRadius;
548 borderRect.borderWidth = colorRect.borderWidth;
549 borderRect.strokeOnly = true;
550 drawRects.infallibleAppend(borderRect);
552 DrawRect baseRect = {};
553 baseRect.color = colorRect.color;
554 baseRect.backgroundColor = borderRect.color;
555 baseRect.x = colorRect.x + colorRect.borderWidth;
556 baseRect.y = colorRect.y + colorRect.borderWidth;
557 baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
558 baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
559 baseRect.borderRadius =
560 std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
561 baseRect.borderWidth = 0;
562 baseRect.strokeOnly = false;
563 drawRects.infallibleAppend(baseRect);
566 for (const DrawRect& rect : drawRects) {
567 if (rect.height <= 0 || rect.width <= 0) {
568 continue;
571 // For rounded rectangles, the first thing we do is draw the top and
572 // bottom of the rectangle, with the more complicated logic below. After
573 // that we can just draw the vertically centered part of the rect like
574 // normal.
575 RasterizeRoundedRectTopAndBottom(rect);
577 // We then draw the flat, central portion of the rect (which in the case of
578 // non-rounded rects, is just the entire thing.)
579 int solidRectStartY =
580 std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
581 int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
582 sTotalChromeHeight);
583 for (int y = solidRectStartY; y < solidRectEndY; ++y) {
584 // For strokeOnly rects (used to draw borders), we just draw the left
585 // and right side here. Looping down a column of pixels is not the most
586 // cache-friendly thing, but it shouldn't be a big deal given the height
587 // of the urlbar.
588 // Also, if borderRadius is less than borderWidth, we need to ensure
589 // that we fully draw the top and bottom lines, so we make sure to check
590 // that we're inside the middle range range before excluding pixels.
591 if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
592 rect.y + rect.height - y > rect.borderWidth) {
593 int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
594 int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
595 int startXRight =
596 std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
597 int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);
599 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
600 uint32_t* dataStartLeft = lineStart + startXLeft;
601 uint32_t* dataEndLeft = lineStart + endXLeft;
602 uint32_t* dataStartRight = lineStart + startXRight;
603 uint32_t* dataEndRight = lineStart + endXRight;
604 std::fill(dataStartLeft, dataEndLeft, rect.color);
605 std::fill(dataStartRight, dataEndRight, rect.color);
606 } else {
607 int startX = std::clamp(rect.x, 0, sWindowWidth);
608 int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
609 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
610 uint32_t* dataStart = lineStart + startX;
611 uint32_t* dataEnd = lineStart + endX;
612 std::fill(dataStart, dataEnd, rect.color);
618 // Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
619 // gradient which moves from left to right across the grey placeholder rects).
620 // Takes in the rect to draw, together with a lookup table for the gradient,
621 // and the bounds of the previous and current frame of the animation.
622 bool RasterizeAnimatedRect(const ColorRect& colorRect,
623 const uint32_t* animationLookup,
624 int priorAnimationMin, int animationMin,
625 int animationMax) {
626 int rectMin = colorRect.x;
627 int rectMax = colorRect.x + colorRect.width;
628 bool animationWindowOverlaps =
629 rectMax >= priorAnimationMin && rectMin < animationMax;
631 int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
632 int priorUpdateAreaMax = std::min(rectMax, animationMin);
633 int currentUpdateAreaMin = std::max(rectMin, animationMin);
634 int currentUpdateAreaMax = std::min(rectMax, animationMax);
636 if (!animationWindowOverlaps) {
637 return false;
640 bool animationWindowOverlapsBorderRadius =
641 rectMin + colorRect.borderRadius > priorAnimationMin ||
642 rectMax - colorRect.borderRadius <= animationMax;
644 // If we don't overlap the left or right side of the rounded rectangle,
645 // just pretend it's not rounded. This is a small optimization but
646 // there's no point in doing all of this rounded rectangle checking if
647 // we aren't even overlapping
648 int borderRadius =
649 animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;
651 if (borderRadius > 0) {
652 // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
653 // first draw the rounded top and bottom, and then we draw the center
654 // rect.
655 RasterizeAnimatedRoundedRectTopAndBottom(
656 colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
657 currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
660 for (int y = colorRect.y + borderRadius;
661 y < colorRect.y + colorRect.height - borderRadius; ++y) {
662 uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
663 // Overwrite the tail end of last frame's animation with the rect's
664 // normal, unanimated color.
665 for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
666 lineStart[x] = colorRect.color;
668 // Then apply the animated color
669 for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
670 lineStart[x] = animationLookup[x - animationMin];
674 return true;
677 bool FillRectWithColor(HDC hdc, LPCRECT rect, uint32_t mozColor) {
678 HBRUSH brush = sCreateSolidBrush(RGB((mozColor & 0xff0000) >> 16,
679 (mozColor & 0x00ff00) >> 8,
680 (mozColor & 0x0000ff) >> 0));
681 int fillRectResult = sFillRect(hdc, rect, brush);
683 sDeleteObject(brush);
685 return !!fillRectResult;
688 Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI(
689 HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan,
690 Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme,
691 const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
692 // NOTE: we opt here to paint a pixel buffer for the application chrome by
693 // hand, without using native UI library methods. Why do we do this?
695 // 1) It gives us a little bit more control, especially if we want to animate
696 // any of this.
697 // 2) It's actually more portable. We can do this on any platform where we
698 // can blit a pixel buffer to the screen, and it only has to change
699 // insofar as the UI is different on those platforms (and thus would have
700 // to change anyway.)
702 // The performance impact of this ought to be negligible. As far as has been
703 // observed, on slow reference hardware this might take up to a millisecond,
704 // for a startup which otherwise takes 30 seconds.
706 // The readability and maintainability are a greater concern. When the
707 // silhouette of Firefox's core UI changes, this code will likely need to
708 // change. However, for the foreseeable future, our skeleton UI will be mostly
709 // axis-aligned geometric shapes, and the thought is that any code which is
710 // manipulating raw pixels should not be *too* hard to maintain and
711 // understand so long as it is only painting such simple shapes.
713 sAnimationColor = currentTheme.animationColor;
714 sToolbarForegroundColor = currentTheme.toolbarForegroundColor;
716 bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
717 bool verticalTabs = flags.contains(SkeletonUIFlag::VerticalTabs);
718 bool bookmarksToolbarShown =
719 flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
720 bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);
722 int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
723 int verticalOffset = sMaximized ? sVerticalResizeMargin : 0;
724 int horizontalOffset =
725 sHorizontalResizeMargin - (sMaximized ? 0 : chromeHorMargin);
727 // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
728 int tabBarHeight =
729 verticalTabs ? 0 : CSSToDevPixels(44, sCSSToDevPixelScaling);
730 int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling);
731 // found in tabs.inc.css, "--tab-block-margin"
732 int titlebarSpacerWidth = horizontalOffset +
733 CSSToDevPixels(2, sCSSToDevPixelScaling) -
734 selectedTabBorderWidth;
735 if (!sMaximized && !menubarShown) {
736 // found in tabs.inc.css, ".titlebar-spacer"
737 titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
739 // found in tabs.inc.css, "--tab-block-margin"
740 int selectedTabMarginTop =
741 CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
742 int selectedTabMarginBottom =
743 CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
744 int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
745 int selectedTabWidth =
746 CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth;
747 int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling);
748 // found in browser.css, "#PersonalToolbar"
749 int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
750 if (bookmarksToolbarShown) {
751 toolbarHeight += bookmarkToolbarHeight;
753 // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
754 int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling);
755 int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling);
756 // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
757 int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);
759 int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling);
760 int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
761 int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
762 int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);
764 int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
765 int toolbarPlaceholderMarginRight =
766 rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
767 : CSSToDevPixels(9, sCSSToDevPixelScaling);
768 int toolbarPlaceholderMarginLeft =
769 rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
770 : CSSToDevPixels(11, sCSSToDevPixelScaling);
771 int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);
773 int menubarHeightDevPixels =
774 menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;
776 // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px
777 int urlbarMargin =
778 CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;
780 int urlbarTextPlaceholderMarginTop =
781 CSSToDevPixels(12, sCSSToDevPixelScaling);
782 int urlbarTextPlaceholderMarginLeft =
783 CSSToDevPixels(12, sCSSToDevPixelScaling);
784 int urlbarTextPlaceHolderWidth = CSSToDevPixels(
785 std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
786 sCSSToDevPixelScaling);
787 int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
789 int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);
791 auto scopeExit = MakeScopeExit([&] {
792 delete sAnimatedRects;
793 sAnimatedRects = nullptr;
796 Vector<ColorRect> rects;
798 ColorRect menubar = {};
799 menubar.color = currentTheme.titlebarColor;
800 menubar.x = 0;
801 menubar.y = verticalOffset;
802 menubar.width = sWindowWidth;
803 menubar.height = menubarHeightDevPixels;
804 menubar.flipIfRTL = false;
805 if (!rects.append(menubar)) {
806 return Err(PreXULSkeletonUIError::OOM);
809 int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
810 // found in browser.css "--toolbarbutton-border-radius"
811 int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
813 // The (traditionally dark blue on Windows) background of the tab bar.
814 ColorRect tabBar = {};
815 tabBar.color = currentTheme.titlebarColor;
816 tabBar.x = 0;
817 tabBar.y = menubar.y + menubar.height;
818 tabBar.width = sWindowWidth;
819 tabBar.height = tabBarHeight;
820 tabBar.flipIfRTL = false;
821 if (!rects.append(tabBar)) {
822 return Err(PreXULSkeletonUIError::OOM);
825 if (!verticalTabs) {
826 // The initial selected tab
827 ColorRect selectedTab = {};
828 selectedTab.color = currentTheme.tabColor;
829 selectedTab.x = titlebarSpacerWidth;
830 selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop;
831 selectedTab.width = selectedTabWidth;
832 selectedTab.height =
833 tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom;
834 selectedTab.borderColor = currentTheme.tabOutlineColor;
835 selectedTab.borderWidth = selectedTabBorderWidth;
836 selectedTab.borderRadius = selectedTabBorderRadius;
837 selectedTab.flipIfRTL = true;
838 if (!rects.append(selectedTab)) {
839 return Err(PreXULSkeletonUIError::OOM);
842 // A placeholder rect representing text that will fill the selected tab
843 // title
844 ColorRect tabTextPlaceholder = {};
845 tabTextPlaceholder.color = currentTheme.toolbarForegroundColor;
846 tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
847 tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
848 tabTextPlaceholder.width = tabPlaceholderBarWidth;
849 tabTextPlaceholder.height = tabPlaceholderBarHeight;
850 tabTextPlaceholder.borderRadius = placeholderBorderRadius;
851 tabTextPlaceholder.flipIfRTL = true;
852 if (!rects.append(tabTextPlaceholder)) {
853 return Err(PreXULSkeletonUIError::OOM);
856 if (!sAnimatedRects->append(tabTextPlaceholder)) {
857 return Err(PreXULSkeletonUIError::OOM);
861 // The toolbar background
862 ColorRect toolbar = {};
863 // In the vertical tabs case the main toolbar is in the titlebar:
864 toolbar.color =
865 verticalTabs ? currentTheme.titlebarColor : currentTheme.backgroundColor;
866 toolbar.x = 0;
867 toolbar.y = tabBar.y + tabBarHeight;
868 toolbar.width = sWindowWidth;
869 toolbar.height = toolbarHeight;
870 toolbar.flipIfRTL = false;
871 if (!rects.append(toolbar)) {
872 return Err(PreXULSkeletonUIError::OOM);
875 // The single-pixel divider line below the toolbar
876 ColorRect chromeContentDivider = {};
877 chromeContentDivider.color = currentTheme.chromeContentDividerColor;
878 chromeContentDivider.x = 0;
879 chromeContentDivider.y = toolbar.y + toolbar.height;
880 chromeContentDivider.width = sWindowWidth;
881 chromeContentDivider.height = chromeContentDividerHeight;
882 chromeContentDivider.flipIfRTL = false;
883 if (!rects.append(chromeContentDivider)) {
884 return Err(PreXULSkeletonUIError::OOM);
887 // The urlbar
888 ColorRect urlbar = {};
889 urlbar.color = currentTheme.urlbarColor;
890 urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
891 horizontalOffset;
892 urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
893 urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
894 sCSSToDevPixelScaling);
895 urlbar.height = urlbarHeight;
896 urlbar.borderColor = currentTheme.urlbarBorderColor;
897 urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
898 urlbar.borderRadius = urlbarBorderRadius;
899 urlbar.flipIfRTL = false;
900 if (!rects.append(urlbar)) {
901 return Err(PreXULSkeletonUIError::OOM);
904 // The urlbar placeholder rect representating text that will fill the urlbar
905 // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
906 // sWindowWidth.
907 ColorRect urlbarTextPlaceholder = {};
908 urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
909 urlbarTextPlaceholder.x =
910 rtlEnabled
911 ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
912 urlbarTextPlaceHolderWidth)
913 : (urlbar.x + urlbarTextPlaceholderMarginLeft);
914 urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
915 urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
916 urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
917 urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
918 urlbarTextPlaceholder.flipIfRTL = false;
919 if (!rects.append(urlbarTextPlaceholder)) {
920 return Err(PreXULSkeletonUIError::OOM);
923 // The searchbar and placeholder text, if present
924 // This is y-aligned with the urlbar
925 bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
926 ColorRect searchbarRect = {};
927 if (hasSearchbar == true) {
928 searchbarRect.color = currentTheme.urlbarColor;
929 searchbarRect.x =
930 CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
931 horizontalOffset;
932 searchbarRect.y = urlbar.y;
933 searchbarRect.width = CSSToDevPixels(
934 searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
935 searchbarRect.height = urlbarHeight;
936 searchbarRect.borderRadius = urlbarBorderRadius;
937 searchbarRect.borderColor = currentTheme.urlbarBorderColor;
938 searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
939 searchbarRect.flipIfRTL = false;
940 if (!rects.append(searchbarRect)) {
941 return Err(PreXULSkeletonUIError::OOM);
944 // The placeholder rect representating text that will fill the searchbar
945 // This uses the same margins as the urlbarTextPlaceholder
946 // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
947 // not sWindowWidth.
948 ColorRect searchbarTextPlaceholder = {};
949 searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
950 searchbarTextPlaceholder.x =
951 rtlEnabled
952 ? ((searchbarRect.x + searchbarRect.width) -
953 urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
954 : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
955 searchbarTextPlaceholder.y =
956 searchbarRect.y + urlbarTextPlaceholderMarginTop;
957 searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
958 searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
959 searchbarTextPlaceholder.flipIfRTL = false;
960 if (!rects.append(searchbarTextPlaceholder) ||
961 !sAnimatedRects->append(searchbarTextPlaceholder)) {
962 return Err(PreXULSkeletonUIError::OOM);
966 // Determine where the placeholder rectangles should not go. This is
967 // anywhere occupied by a spring, urlbar, or searchbar
968 Vector<DevPixelSpan> noPlaceholderSpans;
970 DevPixelSpan urlbarSpan;
971 urlbarSpan.start = urlbar.x - urlbarMargin;
972 urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;
974 DevPixelSpan searchbarSpan;
975 if (hasSearchbar) {
976 searchbarSpan.start = searchbarRect.x - urlbarMargin;
977 searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
980 DevPixelSpan marginLeftPlaceholder;
981 marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
982 marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
983 if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
984 return Err(PreXULSkeletonUIError::OOM);
987 if (rtlEnabled) {
988 // If we're RTL, then the springs as ordered in the DOM will be from right
989 // to left, which will break our comparison logic below
990 springs.reverse();
993 for (auto spring : springs) {
994 DevPixelSpan springDevPixels;
995 springDevPixels.start =
996 CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
997 springDevPixels.end =
998 CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
999 if (!noPlaceholderSpans.append(springDevPixels)) {
1000 return Err(PreXULSkeletonUIError::OOM);
1004 DevPixelSpan marginRightPlaceholder;
1005 marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
1006 marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
1007 if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
1008 return Err(PreXULSkeletonUIError::OOM);
1011 Vector<DevPixelSpan, 2> spansToAdd;
1012 Unused << spansToAdd.reserve(2);
1013 spansToAdd.infallibleAppend(urlbarSpan);
1014 if (hasSearchbar) {
1015 spansToAdd.infallibleAppend(searchbarSpan);
1018 for (auto& toAdd : spansToAdd) {
1019 for (auto& span : noPlaceholderSpans) {
1020 if (span.start > toAdd.start) {
1021 if (!noPlaceholderSpans.insert(&span, toAdd)) {
1022 return Err(PreXULSkeletonUIError::OOM);
1024 break;
1029 for (size_t i = 1; i < noPlaceholderSpans.length(); i++) {
1030 int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
1031 int end = noPlaceholderSpans[i].start - placeholderMargin;
1032 if (start + 2 * placeholderBorderRadius >= end) {
1033 continue;
1036 // The placeholder rects should all be y-aligned.
1037 ColorRect placeholderRect = {};
1038 placeholderRect.color = currentTheme.toolbarForegroundColor;
1039 placeholderRect.x = start;
1040 placeholderRect.y = urlbarTextPlaceholder.y;
1041 placeholderRect.width = end - start;
1042 placeholderRect.height = toolbarPlaceholderHeight;
1043 placeholderRect.borderRadius = placeholderBorderRadius;
1044 placeholderRect.flipIfRTL = false;
1045 if (!rects.append(placeholderRect) ||
1046 !sAnimatedRects->append(placeholderRect)) {
1047 return Err(PreXULSkeletonUIError::OOM);
1051 sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
1052 if (sTotalChromeHeight > sWindowHeight) {
1053 return Err(PreXULSkeletonUIError::BadWindowDimensions);
1056 if (!sAnimatedRects->append(urlbarTextPlaceholder)) {
1057 return Err(PreXULSkeletonUIError::OOM);
1060 sPixelBuffer =
1061 (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
1063 for (auto& rect : *sAnimatedRects) {
1064 if (rtlEnabled && rect.flipIfRTL) {
1065 rect.x = sWindowWidth - rect.x - rect.width;
1067 rect.x = std::clamp(rect.x, 0, sWindowWidth);
1068 rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
1069 rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
1070 rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
1073 for (auto& rect : rects) {
1074 if (rtlEnabled && rect.flipIfRTL) {
1075 rect.x = sWindowWidth - rect.x - rect.width;
1077 rect.x = std::clamp(rect.x, 0, sWindowWidth);
1078 rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
1079 rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
1080 rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
1081 RasterizeColorRect(rect);
1084 HDC hdc = sGetWindowDC(hWnd);
1085 if (!hdc) {
1086 return Err(PreXULSkeletonUIError::FailedGettingDC);
1088 auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); });
1090 BITMAPINFO chromeBMI = {};
1091 chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
1092 chromeBMI.bmiHeader.biWidth = sWindowWidth;
1093 chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
1094 chromeBMI.bmiHeader.biPlanes = 1;
1095 chromeBMI.bmiHeader.biBitCount = 32;
1096 chromeBMI.bmiHeader.biCompression = BI_RGB;
1098 // First, we just paint the chrome area with our pixel buffer
1099 int scanLinesCopied = sStretchDIBits(
1100 hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth,
1101 sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY);
1102 if (scanLinesCopied == 0) {
1103 return Err(PreXULSkeletonUIError::FailedBlitting);
1106 // Then, we just fill the rest with FillRect
1107 RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
1108 bool const fillRectOk =
1109 FillRectWithColor(hdc, &rect, currentTheme.backgroundColor);
1111 if (!fillRectOk) {
1112 return Err(PreXULSkeletonUIError::FailedFillingBottomRect);
1115 scopeExit.release();
1116 return Ok();
1119 DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
1120 if (!sPixelBuffer || sAnimatedRects->empty()) {
1121 return 0;
1124 // See the comments above the InterlockedIncrement calls below here - we
1125 // atomically flip this up and down around sleep so the main thread doesn't
1126 // have to wait for us if we're just sleeping.
1127 if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
1128 return 0;
1130 // Sleep for two seconds - startups faster than this don't really benefit
1131 // from an animation, and we don't want to take away cycles from them.
1132 // Startups longer than this, however, are more likely to be blocked on IO,
1133 // and thus animating does not substantially impact startup times for them.
1134 ::Sleep(2000);
1135 if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
1136 return 0;
1139 // On each of the animated rects (which happen to all be placeholder UI
1140 // rects sharing the same color), we want to animate a gradient moving across
1141 // the screen from left to right. The gradient starts as the rect's color on,
1142 // the left side, changes to the background color of the window by the middle
1143 // of the gradient, and then goes back down to the rect's color. To make this
1144 // faster than interpolating between the two colors for each pixel for each
1145 // frame, we simply create a lookup buffer in which we can look up the color
1146 // for a particular offset into the gradient.
1148 // To do this we just interpolate between the two values, and to give the
1149 // gradient a smoother transition between colors, we transform the linear
1150 // blend amount via the cubic smooth step function (SmoothStep3) to produce
1151 // a smooth start and stop for the gradient. We do this for the first half
1152 // of the gradient, and then simply copy that backwards for the second half.
1154 // The CSS width of 80 chosen here is effectively is just to match the size
1155 // of the animation provided in the design mockup. We define it in CSS pixels
1156 // simply because the rest of our UI is based off of CSS scalings.
1157 int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
1158 UniquePtr<uint32_t[]> animationLookup =
1159 MakeUnique<uint32_t[]>(animationWidth);
1160 uint32_t animationColor = sAnimationColor;
1161 NormalizedRGB rgbBlend = UintToRGB(animationColor);
1163 // Build the first half of the lookup table
1164 for (int i = 0; i < animationWidth / 2; ++i) {
1165 uint32_t baseColor = sToolbarForegroundColor;
1166 double blendAmountLinear =
1167 static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
1168 double blendAmount = SmoothStep3(blendAmountLinear);
1170 NormalizedRGB rgbBase = UintToRGB(baseColor);
1171 NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
1172 animationLookup[i] = RGBToUint(rgb);
1175 // Copy the first half of the lookup table into the second half backwards
1176 for (int i = animationWidth / 2; i < animationWidth; ++i) {
1177 int j = animationWidth - 1 - i;
1178 if (j == animationWidth / 2) {
1179 // If animationWidth is odd, we'll be left with one pixel at the center.
1180 // Just color that as the animation color.
1181 animationLookup[i] = animationColor;
1182 } else {
1183 animationLookup[i] = animationLookup[j];
1187 // The bitmap info remains unchanged throughout the animation - this just
1188 // effectively describes the contents of sPixelBuffer
1189 BITMAPINFO chromeBMI = {};
1190 chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
1191 chromeBMI.bmiHeader.biWidth = sWindowWidth;
1192 chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
1193 chromeBMI.bmiHeader.biPlanes = 1;
1194 chromeBMI.bmiHeader.biBitCount = 32;
1195 chromeBMI.bmiHeader.biCompression = BI_RGB;
1197 uint32_t animationIteration = 0;
1199 int devPixelsPerFrame =
1200 CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
1201 int devPixelsExtraWindowSize =
1202 CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);
1204 if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
1205 // The window got consumed before we were able to draw anything.
1206 return 0;
1209 while (true) {
1210 // The gradient will move across the screen at devPixelsPerFrame at
1211 // 60fps, and then loop back to the beginning. However, we add a buffer of
1212 // devPixelsExtraWindowSize around the edges so it doesn't immediately
1213 // jump back, giving it a more pulsing feel.
1214 int animationMin = ((animationIteration * devPixelsPerFrame) %
1215 (sWindowWidth + devPixelsExtraWindowSize)) -
1216 devPixelsExtraWindowSize / 2;
1217 int animationMax = animationMin + animationWidth;
1218 // The priorAnimationMin is the beginning of the previous frame's animation.
1219 // Since we only want to draw the bits of the image that we updated, we need
1220 // to overwrite the left bit of the animation we drew last frame with the
1221 // default color.
1222 int priorAnimationMin = animationMin - devPixelsPerFrame;
1223 animationMin = std::max(0, animationMin);
1224 priorAnimationMin = std::max(0, priorAnimationMin);
1225 animationMax = std::min((int)sWindowWidth, animationMax);
1227 // The gradient only affects the specific rects that we put into
1228 // sAnimatedRects. So we simply update those rects, and maintain a flag
1229 // to avoid drawing when we don't need to.
1230 bool updatedAnything = false;
1231 for (ColorRect rect : *sAnimatedRects) {
1232 bool hadUpdates =
1233 RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
1234 animationMin, animationMax);
1235 updatedAnything = updatedAnything || hadUpdates;
1238 if (updatedAnything) {
1239 HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
1240 if (!hdc) {
1241 return 0;
1244 sStretchDIBits(hdc, priorAnimationMin, 0,
1245 animationMax - priorAnimationMin, sTotalChromeHeight,
1246 priorAnimationMin, 0, animationMax - priorAnimationMin,
1247 sTotalChromeHeight, sPixelBuffer, &chromeBMI,
1248 DIB_RGB_COLORS, SRCCOPY);
1250 sReleaseDC(sPreXULSkeletonUIWindow, hdc);
1253 animationIteration++;
1255 // We coordinate around our sleep here to ensure that the main thread does
1256 // not wait on us if we're sleeping. If we don't get 1 here, it means the
1257 // window has been consumed and we don't need to sleep. If in
1258 // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
1259 // incrementing, it means we're sleeping, and that function can assume that
1260 // we will safely exit after the sleep because of the observed value of
1261 // sAnimationControlFlag.
1262 if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
1263 return 0;
1266 // Note: Sleep does not guarantee an exact time interval. If the system is
1267 // busy, for instance, we could easily end up taking several frames longer,
1268 // and really we could be left unscheduled for an arbitrarily long time.
1269 // This is fine, and we don't really care. We could track how much time this
1270 // actually took and jump the animation forward the appropriate amount, but
1271 // its not even clear that that's a better user experience. So we leave this
1272 // as simple as we can.
1273 ::Sleep(16);
1275 // Here we bring sAnimationControlFlag back down - again, if we don't get a
1276 // 0 here it means we consumed the skeleton UI window in the mean time, so
1277 // we can simply exit.
1278 if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
1279 return 0;
1284 LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
1285 LPARAM lParam) {
1286 // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause
1287 // screen readers to report spurious information when the skeleton appears.
1288 if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) {
1289 return E_FAIL;
1292 // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
1293 // sync.
1294 if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
1295 sEnableNonClientDpiScaling(hWnd);
1298 // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
1299 // nsWindow.cpp, and will need to be kept in sync.
1300 if (msg == WM_NCCALCSIZE) {
1301 RECT* clientRect =
1302 wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
1303 : (reinterpret_cast<RECT*>(lParam));
1305 Margin margin = NonClientSizeMargin();
1306 clientRect->top += margin.top;
1307 clientRect->left += margin.left;
1308 clientRect->right -= margin.right;
1309 clientRect->bottom -= margin.bottom;
1311 return 0;
1314 return ::DefWindowProcW(hWnd, msg, wParam, lParam);
1317 bool IsSystemDarkThemeEnabled() {
1318 DWORD result;
1319 HKEY themeKey;
1320 DWORD dataLen = sizeof(uint32_t);
1321 LPCWSTR keyName =
1322 L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
1324 result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
1325 if (result != ERROR_SUCCESS) {
1326 return false;
1328 AutoCloseRegKey closeKey(themeKey);
1330 uint32_t lightThemeEnabled;
1331 result = ::RegGetValueW(
1332 themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
1333 reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
1334 if (result != ERROR_SUCCESS) {
1335 return false;
1337 return !lightThemeEnabled;
1340 ThemeColors GetTheme(ThemeMode themeId) {
1341 ThemeColors theme = {};
1342 switch (themeId) {
1343 case ThemeMode::Dark:
1344 // Dark theme or default theme when in dark mode
1346 // controlled by css variable --toolbar-bgcolor
1347 theme.backgroundColor = 0x2b2a33;
1348 theme.tabColor = 0x42414d;
1349 theme.toolbarForegroundColor = 0x6a6a6d;
1350 theme.tabOutlineColor = 0x1c1b22;
1351 // controlled by css variable --lwt-accent-color
1352 theme.titlebarColor = 0x1c1b22;
1353 // controlled by --toolbar-color in browser.css
1354 theme.chromeContentDividerColor = 0x0c0c0d;
1355 // controlled by css variable --toolbar-field-background-color
1356 theme.urlbarColor = 0x42414d;
1357 theme.urlbarBorderColor = 0x42414d;
1358 theme.animationColor = theme.urlbarColor;
1359 return theme;
1360 case ThemeMode::Light:
1361 case ThemeMode::Default:
1362 default:
1363 // --toolbar-bgcolor in browser.css
1364 theme.backgroundColor = 0xf9f9fb;
1365 theme.tabColor = 0xf9f9fb;
1366 theme.toolbarForegroundColor = 0xdddde1;
1367 theme.tabOutlineColor = 0xdddde1;
1368 // found in browser-aero.css ":root[customtitlebar]:not(:-moz-lwtheme)"
1369 // (set to "hsl(235,33%,19%)")
1370 theme.titlebarColor = 0xf0f0f4;
1371 // --chrome-content-separator-color in browser.css
1372 theme.chromeContentDividerColor = 0xe1e1e2;
1373 // controlled by css variable --toolbar-color
1374 theme.urlbarColor = 0xffffff;
1375 theme.urlbarBorderColor = 0xdddde1;
1376 theme.animationColor = theme.backgroundColor;
1377 return theme;
1381 Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() {
1382 HKEY key;
1383 DWORD disposition;
1384 LSTATUS result =
1385 ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
1386 0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
1388 if (result != ERROR_SUCCESS) {
1389 return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
1392 if (disposition == REG_CREATED_NEW_KEY ||
1393 disposition == REG_OPENED_EXISTING_KEY) {
1394 return key;
1397 ::RegCloseKey(key);
1398 return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
1401 Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() {
1402 HMODULE user32Dll = ::LoadLibraryW(L"user32");
1403 HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
1404 HMODULE dwmapiDll = ::LoadLibraryW(L"dwmapi.dll");
1406 if (!user32Dll || !gdi32Dll || !dwmapiDll) {
1407 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
1410 #define MOZ_LOAD_OR_FAIL(dll_handle, name) \
1411 do { \
1412 s##name = (decltype(&::name))::GetProcAddress(dll_handle, #name); \
1413 if (!s##name) { \
1414 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs); \
1416 } while (0)
1418 auto getThreadDpiAwarenessContext =
1419 (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
1420 user32Dll, "GetThreadDpiAwarenessContext");
1421 auto areDpiAwarenessContextsEqual =
1422 (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
1423 user32Dll, "AreDpiAwarenessContextsEqual");
1424 if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
1425 areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
1426 DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
1427 // EnableNonClientDpiScaling is first available in Win10 Build 1607, but
1428 // it's optional - we can handle not having it.
1429 Unused << [&]() -> Result<Ok, PreXULSkeletonUIError> {
1430 MOZ_LOAD_OR_FAIL(user32Dll, EnableNonClientDpiScaling);
1431 return Ok{};
1432 }();
1435 MOZ_LOAD_OR_FAIL(user32Dll, GetSystemMetricsForDpi);
1436 MOZ_LOAD_OR_FAIL(user32Dll, GetDpiForWindow);
1437 MOZ_LOAD_OR_FAIL(user32Dll, RegisterClassW);
1438 MOZ_LOAD_OR_FAIL(user32Dll, CreateWindowExW);
1439 MOZ_LOAD_OR_FAIL(user32Dll, ShowWindow);
1440 MOZ_LOAD_OR_FAIL(user32Dll, SetWindowPos);
1441 MOZ_LOAD_OR_FAIL(user32Dll, GetWindowDC);
1442 MOZ_LOAD_OR_FAIL(user32Dll, GetWindowRect);
1443 MOZ_LOAD_OR_FAIL(user32Dll, MapWindowPoints);
1444 MOZ_LOAD_OR_FAIL(user32Dll, FillRect);
1445 MOZ_LOAD_OR_FAIL(user32Dll, ReleaseDC);
1446 MOZ_LOAD_OR_FAIL(user32Dll, LoadIconW);
1447 MOZ_LOAD_OR_FAIL(user32Dll, LoadCursorW);
1448 MOZ_LOAD_OR_FAIL(user32Dll, MonitorFromWindow);
1449 MOZ_LOAD_OR_FAIL(user32Dll, GetMonitorInfoW);
1450 MOZ_LOAD_OR_FAIL(user32Dll, SetWindowLongPtrW);
1451 MOZ_LOAD_OR_FAIL(gdi32Dll, StretchDIBits);
1452 MOZ_LOAD_OR_FAIL(gdi32Dll, CreateSolidBrush);
1453 MOZ_LOAD_OR_FAIL(gdi32Dll, DeleteObject);
1454 MOZ_LOAD_OR_FAIL(dwmapiDll, DwmGetWindowAttribute);
1455 MOZ_LOAD_OR_FAIL(dwmapiDll, DwmSetWindowAttribute);
1457 #undef MOZ_LOAD_OR_FAIL
1459 return Ok();
1462 // Strips "--", "-", and "/" from the front of the arg if one of those exists,
1463 // returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
1464 // prefixes are found, the argument is not a flag, and nullptr is returned.
1465 const char* NormalizeFlag(const char* arg) {
1466 if (strstr(arg, "--") == arg) {
1467 return arg + 2;
1470 if (arg[0] == '-') {
1471 return arg + 1;
1474 if (arg[0] == '/') {
1475 return arg + 1;
1478 return nullptr;
1481 static bool EnvHasValue(const char* name) {
1482 const char* val = getenv(name);
1483 return (val && *val);
1486 // Ensures that we only see arguments in the command line which are acceptable.
1487 // This is based on manual inspection of the list of arguments listed in the MDN
1488 // page for Gecko/Firefox commandline options:
1489 // https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
1490 // Broadly speaking, we want to reject any argument which causes us to show
1491 // something other than the default window at its normal size. Here is a non-
1492 // exhaustive list of command line options we want to *exclude*:
1494 // -ProfileManager : This will display the profile manager window, which does
1495 // not match the skeleton UI at all.
1497 // -CreateProfile : This will display a firefox window with the default
1498 // screen position and size, and not the position and size
1499 // which we have recorded in the registry.
1501 // -P <profile> : This could cause us to display firefox with a position
1502 // and size of a different profile than that in which we
1503 // were previously running.
1505 // -width, -height : This will cause the width and height values in the
1506 // registry to be incorrect.
1508 // -kiosk : See above.
1510 // -headless : This one should be rather obvious.
1512 // -migration : This will start with the import wizard, which of course
1513 // does not match the skeleton UI.
1515 // -private-window : This is tricky, but the colors of the main content area
1516 // make this not feel great with the white content of the
1517 // default skeleton UI.
1519 // NOTE: we generally want to skew towards erroneous rejections of the command
1520 // line rather than erroneous approvals. The consequence of a bad rejection
1521 // is that we don't show the skeleton UI, which is business as usual. The
1522 // consequence of a bad approval is that we show it when we're not supposed to,
1523 // which is visually jarring and can also be unpredictable - there's no
1524 // guarantee that the code which handles the non-default window is set up to
1525 // properly handle the transition from the skeleton UI window.
1526 static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments(
1527 int argc, char** argv, bool* explicitProfile) {
1528 const char* approvedArgumentsArray[] = {
1529 // These won't cause the browser to be visualy different in any way
1530 "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
1531 "attach-console", "wait-for-browser", "osint",
1533 // These will cause the chrome to be a bit different or extra windows to
1534 // be created, but overall the skeleton UI should still be broadly
1535 // correct enough.
1536 "new-tab", "new-window",
1538 // To the extent possible, we want to ensure that existing tests cover
1539 // the skeleton UI, so we need to allow marionette
1540 "marionette",
1542 // These will cause the content area to appear different, but won't
1543 // meaningfully affect the chrome
1544 "preferences", "search", "url",
1546 #ifndef MOZILLA_OFFICIAL
1547 // On local builds, we want to allow -profile, because it's how `mach run`
1548 // operates, and excluding that would create an unnecessary blind spot for
1549 // Firefox devs.
1550 "profile"
1551 #endif
1553 // There are other arguments which are likely okay. However, they are
1554 // not included here because this list is not intended to be
1555 // exhaustive - it only intends to green-light some somewhat commonly
1556 // used arguments. We want to err on the side of an unnecessary
1557 // rejection of the command line.
1560 int approvedArgumentsArraySize =
1561 sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
1562 Vector<const char*> approvedArguments;
1563 if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
1564 return Err(PreXULSkeletonUIError::OOM);
1567 for (int i = 0; i < approvedArgumentsArraySize; ++i) {
1568 approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
1571 #ifdef MOZILLA_OFFICIAL
1572 int profileArgIndex = -1;
1573 // If we're running mochitests or direct marionette tests, those specify a
1574 // temporary profile, and we want to ensure that we get the added coverage
1575 // from those.
1576 for (int i = 1; i < argc; ++i) {
1577 const char* flag = NormalizeFlag(argv[i]);
1578 if (flag && !strcmp(flag, "marionette")) {
1579 if (!approvedArguments.append("profile")) {
1580 return Err(PreXULSkeletonUIError::OOM);
1582 profileArgIndex = approvedArguments.length() - 1;
1584 break;
1587 #else
1588 int profileArgIndex = approvedArguments.length() - 1;
1589 #endif
1591 for (int i = 1; i < argc; ++i) {
1592 const char* flag = NormalizeFlag(argv[i]);
1593 if (!flag) {
1594 // If this is not a flag, then we interpret it as a URL, similar to
1595 // BrowserContentHandler.sys.mjs. Some command line options take
1596 // additional arguments, which may or may not be URLs. We don't need to
1597 // know this, because we don't need to parse them out; we just rely on the
1598 // assumption that if arg X is actually a parameter for the preceding
1599 // arg Y, then X must not look like a flag (starting with "--", "-",
1600 // or "/").
1602 // The most important thing here is the assumption that if something is
1603 // going to meaningfully alter the appearance of the window itself, it
1604 // must be a flag.
1605 continue;
1608 bool approved = false;
1609 for (const char* approvedArg : approvedArguments) {
1610 // We do a case-insensitive compare here with _stricmp. Even though some
1611 // of these arguments are *not* read as case-insensitive, others *are*.
1612 // Similar to the flag logic above, we don't really care about this
1613 // distinction, because we don't need to parse the arguments - we just
1614 // rely on the assumption that none of the listed flags in our
1615 // approvedArguments are overloaded in such a way that a different
1616 // casing would visually alter the firefox window.
1617 if (!_stricmp(flag, approvedArg)) {
1618 approved = true;
1620 if (i == profileArgIndex) {
1621 *explicitProfile = true;
1623 break;
1627 if (!approved) {
1628 return Err(PreXULSkeletonUIError::Cmdline);
1632 return Ok();
1635 static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() {
1636 if (EnvHasValue("MOZ_SAFE_MODE_RESTART") ||
1637 EnvHasValue("MOZ_APP_SILENT_START") ||
1638 EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") ||
1639 (EnvHasValue("XRE_PROFILE_PATH") &&
1640 !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) {
1641 return Err(PreXULSkeletonUIError::EnvVars);
1644 return Ok();
1647 static bool VerifyWindowDimensions(uint32_t windowWidth,
1648 uint32_t windowHeight) {
1649 return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
1652 static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans(
1653 HKEY regKey, const std::wstring& valueName) {
1654 DWORD dataLen = 0;
1655 LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr,
1656 nullptr, nullptr, &dataLen);
1657 if (result != ERROR_SUCCESS) {
1658 return Err(PreXULSkeletonUIError::RegistryError);
1661 if (dataLen % (2 * sizeof(double)) != 0) {
1662 return Err(PreXULSkeletonUIError::CorruptData);
1665 auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
1666 if (!buffer) {
1667 return Err(PreXULSkeletonUIError::OOM);
1669 result =
1670 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
1671 nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen);
1672 if (result != ERROR_SUCCESS) {
1673 return Err(PreXULSkeletonUIError::RegistryError);
1676 Vector<CSSPixelSpan> resultVector;
1677 double* asDoubles = reinterpret_cast<double*>(buffer.get());
1678 for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) {
1679 CSSPixelSpan span = {};
1680 span.start = *(asDoubles++);
1681 span.end = *(asDoubles++);
1682 if (!resultVector.append(span)) {
1683 return Err(PreXULSkeletonUIError::OOM);
1687 return resultVector;
1690 static Result<double, PreXULSkeletonUIError> ReadRegDouble(
1691 HKEY regKey, const std::wstring& valueName) {
1692 double value = 0;
1693 DWORD dataLen = sizeof(double);
1694 LSTATUS result =
1695 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
1696 nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
1697 if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
1698 return Err(PreXULSkeletonUIError::RegistryError);
1701 return value;
1704 static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint(
1705 HKEY regKey, const std::wstring& valueName) {
1706 DWORD value = 0;
1707 DWORD dataLen = sizeof(uint32_t);
1708 LSTATUS result =
1709 ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD,
1710 nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
1711 if (result != ERROR_SUCCESS) {
1712 return Err(PreXULSkeletonUIError::RegistryError);
1715 return value;
1718 static Result<bool, PreXULSkeletonUIError> ReadRegBool(
1719 HKEY regKey, const std::wstring& valueName) {
1720 uint32_t value;
1721 MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName));
1722 return !!value;
1725 static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans(
1726 HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans,
1727 int spansLength) {
1728 // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's
1729 // also trivial to just copy them into a buffer of doubles.
1730 auto doubles = MakeUnique<double[]>(spansLength * 2);
1731 for (int i = 0; i < spansLength; ++i) {
1732 doubles[i * 2] = spans[i].start;
1733 doubles[i * 2 + 1] = spans[i].end;
1736 LSTATUS result =
1737 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
1738 reinterpret_cast<const BYTE*>(doubles.get()),
1739 spansLength * sizeof(double) * 2);
1740 if (result != ERROR_SUCCESS) {
1741 return Err(PreXULSkeletonUIError::RegistryError);
1743 return Ok();
1746 static Result<Ok, PreXULSkeletonUIError> WriteRegDouble(
1747 HKEY regKey, const std::wstring& valueName, double value) {
1748 LSTATUS result =
1749 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
1750 reinterpret_cast<const BYTE*>(&value), sizeof(value));
1751 if (result != ERROR_SUCCESS) {
1752 return Err(PreXULSkeletonUIError::RegistryError);
1755 return Ok();
1758 static Result<Ok, PreXULSkeletonUIError> WriteRegUint(
1759 HKEY regKey, const std::wstring& valueName, uint32_t value) {
1760 LSTATUS result =
1761 ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD,
1762 reinterpret_cast<PBYTE>(&value), sizeof(value));
1763 if (result != ERROR_SUCCESS) {
1764 return Err(PreXULSkeletonUIError::RegistryError);
1767 return Ok();
1770 static Result<Ok, PreXULSkeletonUIError> WriteRegBool(
1771 HKEY regKey, const std::wstring& valueName, bool value) {
1772 return WriteRegUint(regKey, valueName, value ? 1 : 0);
1775 static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl(
1776 HINSTANCE hInstance, int argc, char** argv) {
1777 // Initializing COM below may load modules via SetWindowHookEx, some of
1778 // which may modify the executable's IAT for ntdll.dll. If that happens,
1779 // this browser process fails to launch sandbox processes because we cannot
1780 // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp).
1781 // To prevent that, we cache the intact IAT before COM initialization.
1782 // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will
1783 // also prevent an injected module from parsing the PE headers and modifying
1784 // the IAT. Therefore, we can skip CacheNtDllThunk().
1785 if (!mozilla::IsEafPlusEnabled()) {
1786 CacheNtDllThunk();
1789 // NOTE: it's important that we initialize sProcessRuntime before showing a
1790 // window. Historically we ran into issues where showing the window would
1791 // cause an accessibility win event to fire, which could cause in-process
1792 // system or third party components to initialize COM and prevent us from
1793 // initializing it with important settings we need.
1795 // Some COM settings are global to the process and must be set before any non-
1796 // trivial COM is run in the application. Since these settings may affect
1797 // stability, we should instantiate COM ASAP so that we can ensure that these
1798 // global settings are configured before anything can interfere.
1799 sProcessRuntime = new mscom::ProcessRuntime(
1800 mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent);
1802 const TimeStamp skeletonStart = TimeStamp::Now();
1804 HKEY regKey;
1805 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
1806 AutoCloseRegKey closeKey(regKey);
1808 UniquePtr<wchar_t[]> binPath;
1809 MOZ_TRY_VAR(binPath, GetBinaryPath());
1811 std::wstring regProgressName =
1812 GetRegValueName(binPath.get(), sProgressSuffix);
1813 auto progressResult = ReadRegUint(regKey, regProgressName);
1814 if (!progressResult.isErr() &&
1815 progressResult.unwrap() !=
1816 static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) {
1817 return Err(PreXULSkeletonUIError::CrashedOnce);
1820 MOZ_TRY(
1821 WriteRegUint(regKey, regProgressName,
1822 static_cast<uint32_t>(PreXULSkeletonUIProgress::Started)));
1823 auto writeCompletion = MakeScopeExit([&] {
1824 Unused << WriteRegUint(
1825 regKey, regProgressName,
1826 static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed));
1829 MOZ_TRY(GetSkeletonUILock());
1831 bool explicitProfile = false;
1832 MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile));
1833 MOZ_TRY(ValidateEnvVars());
1835 auto enabledResult =
1836 ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix));
1837 if (enabledResult.isErr()) {
1838 return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist);
1840 if (!enabledResult.unwrap()) {
1841 return Err(PreXULSkeletonUIError::Disabled);
1843 sPreXULSkeletonUIEnabled = true;
1845 MOZ_ASSERT(!sAnimatedRects);
1846 sAnimatedRects = new Vector<ColorRect>();
1848 MOZ_TRY(LoadGdi32AndUser32Procedures());
1850 if (!explicitProfile) {
1851 MOZ_TRY(CheckForStartWithLastProfile());
1854 WNDCLASSW wc;
1855 wc.style = CS_DBLCLKS;
1856 wc.lpfnWndProc = PreXULSkeletonUIProc;
1857 wc.cbClsExtra = 0;
1858 wc.cbWndExtra = 0;
1859 wc.hInstance = hInstance;
1860 wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
1861 wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
1862 wc.hbrBackground = nullptr;
1863 wc.lpszMenuName = nullptr;
1865 // TODO: just ensure we disable this if we've overridden the window class
1866 wc.lpszClassName = L"MozillaWindowClass";
1868 if (!sRegisterClassW(&wc)) {
1869 return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass);
1872 uint32_t screenX;
1873 MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1874 sScreenXRegSuffix)));
1875 uint32_t screenY;
1876 MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1877 sScreenYRegSuffix)));
1878 uint32_t windowWidth;
1879 MOZ_TRY_VAR(
1880 windowWidth,
1881 ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix)));
1882 uint32_t windowHeight;
1883 MOZ_TRY_VAR(
1884 windowHeight,
1885 ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix)));
1886 MOZ_TRY_VAR(
1887 sMaximized,
1888 ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix)));
1889 MOZ_TRY_VAR(
1890 sCSSToDevPixelScaling,
1891 ReadRegDouble(regKey, GetRegValueName(binPath.get(),
1892 sCssToDevPixelScalingRegSuffix)));
1893 Vector<CSSPixelSpan> urlbar;
1894 MOZ_TRY_VAR(urlbar,
1895 ReadRegCSSPixelSpans(
1896 regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix)));
1897 Vector<CSSPixelSpan> searchbar;
1898 MOZ_TRY_VAR(searchbar,
1899 ReadRegCSSPixelSpans(
1900 regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix)));
1901 Vector<CSSPixelSpan> springs;
1902 MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans(
1903 regKey, GetRegValueName(binPath.get(),
1904 sSpringsCSSRegSuffix)));
1906 if (urlbar.empty() || searchbar.empty()) {
1907 return Err(PreXULSkeletonUIError::CorruptData);
1910 EnumSet<SkeletonUIFlag, uint32_t> flags;
1911 uint32_t flagsUint;
1912 MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1913 sFlagsRegSuffix)));
1914 flags.deserialize(flagsUint);
1916 if (flags.contains(SkeletonUIFlag::TouchDensity) ||
1917 flags.contains(SkeletonUIFlag::CompactDensity)) {
1918 return Err(PreXULSkeletonUIError::BadUIDensity);
1921 uint32_t theme;
1922 MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(),
1923 sThemeRegSuffix)));
1924 ThemeMode themeMode = static_cast<ThemeMode>(theme);
1925 if (themeMode == ThemeMode::Default) {
1926 if (IsSystemDarkThemeEnabled()) {
1927 themeMode = ThemeMode::Dark;
1930 ThemeColors currentTheme = GetTheme(themeMode);
1932 if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
1933 return Err(PreXULSkeletonUIError::BadWindowDimensions);
1936 int showCmd = SW_SHOWNORMAL;
1937 DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
1938 if (sMaximized) {
1939 showCmd = SW_SHOWMAXIMIZED;
1940 windowStyle |= WS_MAXIMIZE;
1943 sPreXULSkeletonUIWindow =
1944 sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
1945 L"", windowStyle, screenX, screenY, windowWidth,
1946 windowHeight, nullptr, nullptr, hInstance, nullptr);
1947 if (!sPreXULSkeletonUIWindow) {
1948 return Err(PreXULSkeletonUIError::CreateWindowFailed);
1951 // DWM displays garbage immediately on Show(), and that garbage is usually
1952 // mostly #FFFFFF. To avoid a bright flash when the window is first created,
1953 // cloak the window while showing it, and fill it with the appropriate
1954 // background color before uncloaking it.
1956 constexpr static auto const CloakWindow = [](HWND hwnd, BOOL state) {
1957 sDwmSetWindowAttribute(sPreXULSkeletonUIWindow, DWMWA_CLOAK, &state,
1958 sizeof(state));
1960 // Equivalent to ::OffsetRect, with no dynamic-symbol resolution needed.
1961 constexpr static auto const OffsetRect = [](LPRECT rect, int dx, int dy) {
1962 rect->left += dx;
1963 rect->top += dy;
1964 rect->right += dx;
1965 rect->bottom += dy;
1968 CloakWindow(sPreXULSkeletonUIWindow, TRUE);
1969 auto const _uncloak =
1970 MakeScopeExit([&]() { CloakWindow(sPreXULSkeletonUIWindow, FALSE); });
1971 sShowWindow(sPreXULSkeletonUIWindow, showCmd);
1973 HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
1974 if (!hdc) {
1975 return Err(PreXULSkeletonUIError::FailedGettingDC);
1977 auto const _cleanupDC =
1978 MakeScopeExit([&] { sReleaseDC(sPreXULSkeletonUIWindow, hdc); });
1980 // This should match the related code in nsWindow::Show.
1981 RECT rect;
1982 sGetWindowRect(sPreXULSkeletonUIWindow, &rect); // includes non-client area
1983 // screen-to-client (handling RTL if necessary)
1984 sMapWindowPoints(HWND_DESKTOP, sPreXULSkeletonUIWindow, (LPPOINT)&rect, 2);
1985 // client-to-window (no RTL handling needed)
1986 OffsetRect(&rect, -rect.left, -rect.top);
1987 FillRectWithColor(hdc, &rect, currentTheme.backgroundColor);
1990 sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
1991 sHorizontalResizeMargin = sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
1992 sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
1993 sVerticalResizeMargin = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
1994 sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
1995 sCaptionHeight = sGetSystemMetricsForDpi(SM_CYCAPTION, sDpi);
1997 // These match the offsets that we get with default prefs. We don't use the
1998 // skeleton ui if tabsInTitlebar is disabled, see bug 1673092.
1999 if (sMaximized) {
2000 sNonClientOffset = Margin{sCaptionHeight, 0, 0, 0};
2001 } else {
2002 // See nsWindow::NormalWindowNonClientOffset()
2003 sNonClientOffset = Margin{sCaptionHeight + sVerticalResizeMargin, 0, 0, 0};
2006 if (sMaximized) {
2007 HMONITOR monitor =
2008 sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
2009 if (!monitor) {
2010 // NOTE: we specifically don't clean up the window here. If we're unable
2011 // to finish setting up the window how we want it, we still need to keep
2012 // it around and consume it with the first real toplevel window we
2013 // create, to avoid flickering.
2014 return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
2016 MONITORINFO mi = {sizeof(MONITORINFO)};
2017 if (!sGetMonitorInfoW(monitor, &mi)) {
2018 return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
2021 sWindowWidth =
2022 mi.rcWork.right - mi.rcWork.left + sHorizontalResizeMargin * 2;
2023 sWindowHeight =
2024 mi.rcWork.bottom - mi.rcWork.top + sVerticalResizeMargin * 2;
2025 } else {
2026 sWindowWidth = static_cast<int>(windowWidth);
2027 sWindowHeight = static_cast<int>(windowHeight);
2030 sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
2031 SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
2032 SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
2033 MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0],
2034 springs, currentTheme, flags));
2035 if (sAnimatedRects) {
2036 sPreXULSKeletonUIAnimationThread = ::CreateThread(
2037 nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
2040 BASE_PROFILER_MARKER_UNTYPED(
2041 "CreatePreXULSkeletonUI", OTHER,
2042 MarkerTiming::IntervalUntilNowFrom(skeletonStart));
2044 return Ok();
2047 void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
2048 char** argv) {
2049 auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv);
2051 if (result.isErr()) {
2052 sErrorReason.emplace(result.unwrapErr());
2056 void CleanupProcessRuntime() {
2057 delete sProcessRuntime;
2058 sProcessRuntime = nullptr;
2061 bool WasPreXULSkeletonUIMaximized() { return sMaximized; }
2063 bool GetPreXULSkeletonUIWasShown() {
2064 return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow;
2067 HWND ConsumePreXULSkeletonUIHandle() {
2068 // NOTE: we need to make sure that everything that runs here is a no-op if
2069 // it failed to be set, which is a possibility. If anything fails to be set
2070 // we don't want to clean everything up right away, because if we have a
2071 // blank window up, we want that to stick around and get consumed by nsWindow
2072 // as normal, otherwise the window will flicker in and out, which we imagine
2073 // is unpleasant.
2075 // If we don't get 1 here, it means the thread is actually just sleeping, so
2076 // we don't need to worry about giving out ownership of the window, because
2077 // the thread will simply exit after its sleep. However, if it is 1, we need
2078 // to wait for the thread to exit to be safe, as it could be doing anything.
2079 if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
2080 ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
2082 ::CloseHandle(sPreXULSKeletonUIAnimationThread);
2083 sPreXULSKeletonUIAnimationThread = nullptr;
2084 HWND result = sPreXULSkeletonUIWindow;
2085 sPreXULSkeletonUIWindow = nullptr;
2086 free(sPixelBuffer);
2087 sPixelBuffer = nullptr;
2088 delete sAnimatedRects;
2089 sAnimatedRects = nullptr;
2091 return result;
2094 Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
2095 const SkeletonUISettings& settings) {
2096 if (!sPreXULSkeletonUIEnabled) {
2097 return Err(PreXULSkeletonUIError::Disabled);
2100 HKEY regKey;
2101 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2102 AutoCloseRegKey closeKey(regKey);
2104 UniquePtr<wchar_t[]> binPath;
2105 MOZ_TRY_VAR(binPath, GetBinaryPath());
2107 MOZ_TRY(WriteRegUint(regKey,
2108 GetRegValueName(binPath.get(), sScreenXRegSuffix),
2109 settings.screenX));
2110 MOZ_TRY(WriteRegUint(regKey,
2111 GetRegValueName(binPath.get(), sScreenYRegSuffix),
2112 settings.screenY));
2113 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix),
2114 settings.width));
2115 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix),
2116 settings.height));
2118 MOZ_TRY(WriteRegBool(regKey,
2119 GetRegValueName(binPath.get(), sMaximizedRegSuffix),
2120 settings.maximized));
2122 EnumSet<SkeletonUIFlag, uint32_t> flags;
2123 if (settings.menubarShown) {
2124 flags += SkeletonUIFlag::MenubarShown;
2126 if (settings.bookmarksToolbarShown) {
2127 flags += SkeletonUIFlag::BookmarksToolbarShown;
2129 if (settings.rtlEnabled) {
2130 flags += SkeletonUIFlag::RtlEnabled;
2132 if (settings.uiDensity == SkeletonUIDensity::Touch) {
2133 flags += SkeletonUIFlag::TouchDensity;
2135 if (settings.uiDensity == SkeletonUIDensity::Compact) {
2136 flags += SkeletonUIFlag::CompactDensity;
2138 if (settings.verticalTabs) {
2139 flags += SkeletonUIFlag::VerticalTabs;
2142 uint32_t flagsUint = flags.serialize();
2143 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix),
2144 flagsUint));
2146 MOZ_TRY(WriteRegDouble(
2147 regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix),
2148 settings.cssToDevPixelScaling));
2149 MOZ_TRY(WriteRegCSSPixelSpans(
2150 regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix),
2151 &settings.urlbarSpan, 1));
2152 MOZ_TRY(WriteRegCSSPixelSpans(
2153 regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix),
2154 &settings.searchbarSpan, 1));
2155 MOZ_TRY(WriteRegCSSPixelSpans(
2156 regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix),
2157 settings.springs.begin(), settings.springs.length()));
2159 return Ok();
2162 MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }
2164 MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
2165 bool value) {
2166 // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
2167 // ignore changes to the registry. An example of how things could be bad if
2168 // we didn't: someone running firefox with the -profile argument could
2169 // turn the skeleton UI on or off for the default profile. Turning it off
2170 // maybe isn't so bad (though it's likely still incorrect), but turning it
2171 // on could be bad if the user had specifically disabled it for a profile for
2172 // some reason. Ultimately there's no correct decision here, and the
2173 // messiness of this is just a consequence of sharing the registry values
2174 // across profiles. However, whatever ill effects we observe should be
2175 // correct themselves after one session.
2176 if (PreXULSkeletonUIDisallowed()) {
2177 return Err(PreXULSkeletonUIError::Disabled);
2180 HKEY regKey;
2181 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2182 AutoCloseRegKey closeKey(regKey);
2184 UniquePtr<wchar_t[]> binPath;
2185 MOZ_TRY_VAR(binPath, GetBinaryPath());
2186 MOZ_TRY(WriteRegBool(
2187 regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value));
2189 if (!sPreXULSkeletonUIEnabled && value) {
2190 // We specifically don't care if we fail to get this lock. We just want to
2191 // do our best effort to lock it so that future instances don't create
2192 // skeleton UIs while we're still running, since they will immediately exit
2193 // and tell us to open a new window.
2194 Unused << GetSkeletonUILock();
2197 sPreXULSkeletonUIEnabled = value;
2199 return Ok();
2202 MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
2203 ThemeMode theme) {
2204 if (theme == sTheme) {
2205 return Ok();
2207 sTheme = theme;
2209 // If we fail below, invalidate sTheme
2210 auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; });
2212 HKEY regKey;
2213 MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
2214 AutoCloseRegKey closeKey(regKey);
2216 UniquePtr<wchar_t[]> binPath;
2217 MOZ_TRY_VAR(binPath, GetBinaryPath());
2218 MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix),
2219 static_cast<uint32_t>(theme)));
2221 invalidateTheme.release();
2222 return Ok();
2225 MFBT_API void PollPreXULSkeletonUIEvents() {
2226 if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
2227 MSG outMsg = {};
2228 PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
2232 Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() {
2233 if (!sPreXULSkeletonUIEnabled) {
2234 return Err(PreXULSkeletonUIError::Disabled);
2237 ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1");
2239 // We assume that we are going to exit the application very shortly after
2240 // this. It should thus be fine to release this lock, and we'll need to,
2241 // since during a restart we launch the new instance before closing this
2242 // one.
2243 if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
2244 ::CloseHandle(sPreXULSKeletonUILockFile);
2246 return Ok();
2249 } // namespace mozilla