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"
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"
40 // ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
41 // particular color that we will draw.
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
61 uint32_t backgroundColor
;
71 struct NormalizedRGB
{
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
[] =
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
,
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
*,
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
);
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
;
211 buf
= MakeUnique
<wchar_t[]>(bufLen
);
212 DWORD retLen
= ::GetModuleFileNameW(nullptr, buf
.get(), bufLen
);
214 return Err(PreXULSkeletonUIError::FilesystemFailure
);
217 if (retLen
== bufLen
&& ::GetLastError() == ERROR_INSUFFICIENT_BUFFER
) {
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.
244 // https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
245 static void MutateStringToLowercase(wchar_t* ptr
) {
248 if (ch
>= L
'A' && ch
<= L
'Z') {
249 *ptr
= ch
+ (L
'a' - L
'A');
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
) {
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());
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
304 if (sPreXULSKeletonUILockFile
== INVALID_HANDLE_VALUE
) {
305 return Err(PreXULSkeletonUIError::FailedGettingLock
);
311 const char kGeneralSection
[] = "[General]";
312 const char kStartWithLastProfile
[] = "StartWithLastProfile=";
314 static bool ProfileDbHasStartWithLastProfile(IFStream
& iniContents
) {
315 bool inGeneral
= false;
317 while (std::getline(iniContents
, line
)) {
318 size_t whitespace
= 0;
319 while (line
.length() > whitespace
&&
320 (line
[whitespace
] == ' ' || line
[whitespace
] == '\t')) {
323 line
.erase(0, whitespace
);
325 if (line
.compare(kGeneralSection
) == 0) {
327 } else if (inGeneral
) {
328 if (line
[0] == '[') {
331 if (line
.find(kStartWithLastProfile
) == 0) {
332 char val
= line
.c_str()[sizeof(kStartWithLastProfile
) - 1];
335 } else if (val
== '1') {
343 // If we don't find it in the .ini file, we interpret that as 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
);
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
{
372 explicit AutoCloseRegKey(HKEY key
) : mKey(key
) {}
373 ~AutoCloseRegKey() { ::RegCloseKey(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.");
419 if (rect
.width
<= 2 * rect
.borderRadius
) {
420 MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
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;
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
,
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
;
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.
490 std::max((int)colorRect
.x
+ (int)colorRect
.borderRadius
- x
- 1,
491 x
- ((int)colorRect
.x
+ (int)colorRect
.width
-
492 (int)colorRect
.borderRadius
));
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
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) {
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
);
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) {
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
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,
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
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
);
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
);
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
,
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
) {
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
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
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
];
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
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
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
;
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
;
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
;
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
;
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
);
862 ColorRect urlbar
= {};
863 urlbar
.color
= currentTheme
.urlbarColor
;
864 urlbar
.x
= CSSToDevPixels(urlbarCSSSpan
.start
, sCSSToDevPixelScaling
) +
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
881 ColorRect urlbarTextPlaceholder
= {};
882 urlbarTextPlaceholder
.color
= currentTheme
.toolbarForegroundColor
;
883 urlbarTextPlaceholder
.x
=
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
;
904 CSSToDevPixels(searchbarCSSSpan
.start
, sCSSToDevPixelScaling
) +
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,
922 ColorRect searchbarTextPlaceholder
= {};
923 searchbarTextPlaceholder
.color
= currentTheme
.toolbarForegroundColor
;
924 searchbarTextPlaceholder
.x
=
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
;
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
);
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
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
);
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
);
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
) {
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
);
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
);
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
};
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();
1099 DWORD WINAPI
AnimateSkeletonUI(void* aUnused
) {
1100 if (!sPixelBuffer
|| sAnimatedRects
->empty()) {
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) {
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.
1115 if (InterlockedDecrement(&sAnimationControlFlag
) != 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
;
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.
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
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
) {
1213 RasterizeAnimatedRect(rect
, animationLookup
.get(), priorAnimationMin
,
1214 animationMin
, animationMax
);
1215 updatedAnything
= updatedAnything
|| hadUpdates
;
1218 if (updatedAnything
) {
1219 HDC hdc
= sGetWindowDC(sPreXULSkeletonUIWindow
);
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) {
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.
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) {
1264 LRESULT WINAPI
PreXULSkeletonUIProc(HWND hWnd
, UINT msg
, WPARAM wParam
,
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
) {
1272 // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
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
) {
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
;
1301 return ::DefWindowProcW(hWnd
, msg
, wParam
, lParam
);
1304 bool IsSystemDarkThemeEnabled() {
1307 DWORD dataLen
= sizeof(uint32_t);
1309 L
"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
1311 result
= ::RegOpenKeyExW(HKEY_CURRENT_USER
, keyName
, 0, KEY_READ
, &themeKey
);
1312 if (result
!= ERROR_SUCCESS
) {
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
) {
1324 return !lightThemeEnabled
;
1327 ThemeColors
GetTheme(ThemeMode themeId
) {
1328 ThemeColors theme
= {};
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
;
1347 case ThemeMode::Light
:
1348 case ThemeMode::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
;
1368 Result
<HKEY
, PreXULSkeletonUIError
> OpenPreXULSkeletonUIRegKey() {
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
) {
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
);
1417 (GetDpiForWindowProc
)::GetProcAddress(user32Dll
, "GetDpiForWindow");
1418 if (!sGetDpiForWindow
) {
1419 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
1422 (RegisterClassWProc
)::GetProcAddress(user32Dll
, "RegisterClassW");
1423 if (!sRegisterClassW
) {
1424 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
1427 (CreateWindowExWProc
)::GetProcAddress(user32Dll
, "CreateWindowExW");
1428 if (!sCreateWindowExW
) {
1429 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
1431 sShowWindow
= (ShowWindowProc
)::GetProcAddress(user32Dll
, "ShowWindow");
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");
1445 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
1447 sReleaseDC
= (ReleaseDCProc
)::GetProcAddress(user32Dll
, "ReleaseDC");
1449 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
1451 sLoadIconW
= (LoadIconWProc
)::GetProcAddress(user32Dll
, "LoadIconW");
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
);
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
);
1475 (StretchDIBitsProc
)::GetProcAddress(gdi32Dll
, "StretchDIBits");
1476 if (!sStretchDIBits
) {
1477 return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs
);
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
);
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
) {
1500 if (arg
[0] == '-') {
1504 if (arg
[0] == '/') {
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
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
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
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
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;
1618 int profileArgIndex
= approvedArguments
.length() - 1;
1621 for (int i
= 1; i
< argc
; ++i
) {
1622 const char* flag
= NormalizeFlag(argv
[i
]);
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 "--", "-",
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
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
)) {
1650 if (i
== profileArgIndex
) {
1651 *explicitProfile
= true;
1658 return Err(PreXULSkeletonUIError::Cmdline
);
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
);
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
) {
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
);
1697 return Err(PreXULSkeletonUIError::OOM
);
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
) {
1723 DWORD dataLen
= sizeof(double);
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
);
1734 static Result
<uint32_t, PreXULSkeletonUIError
> ReadRegUint(
1735 HKEY regKey
, const std::wstring
& valueName
) {
1737 DWORD dataLen
= sizeof(uint32_t);
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
);
1748 static Result
<bool, PreXULSkeletonUIError
> ReadRegBool(
1749 HKEY regKey
, const std::wstring
& valueName
) {
1751 MOZ_TRY_VAR(value
, ReadRegUint(regKey
, valueName
));
1755 static Result
<Ok
, PreXULSkeletonUIError
> WriteRegCSSPixelSpans(
1756 HKEY regKey
, const std::wstring
& valueName
, const CSSPixelSpan
* spans
,
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
;
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
);
1776 static Result
<Ok
, PreXULSkeletonUIError
> WriteRegDouble(
1777 HKEY regKey
, const std::wstring
& valueName
, double value
) {
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
);
1788 static Result
<Ok
, PreXULSkeletonUIError
> WriteRegUint(
1789 HKEY regKey
, const std::wstring
& valueName
, uint32_t value
) {
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
);
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()) {
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();
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
);
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());
1885 wc
.style
= CS_DBLCLKS
;
1886 wc
.lpfnWndProc
= PreXULSkeletonUIProc
;
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
);
1903 MOZ_TRY_VAR(screenX
, ReadRegUint(regKey
, GetRegValueName(binPath
.get(),
1904 sScreenXRegSuffix
)));
1906 MOZ_TRY_VAR(screenY
, ReadRegUint(regKey
, GetRegValueName(binPath
.get(),
1907 sScreenYRegSuffix
)));
1908 uint32_t windowWidth
;
1911 ReadRegUint(regKey
, GetRegValueName(binPath
.get(), sWidthRegSuffix
)));
1912 uint32_t windowHeight
;
1915 ReadRegUint(regKey
, GetRegValueName(binPath
.get(), sHeightRegSuffix
)));
1918 ReadRegBool(regKey
, GetRegValueName(binPath
.get(), sMaximizedRegSuffix
)));
1920 sCSSToDevPixelScaling
,
1921 ReadRegDouble(regKey
, GetRegValueName(binPath
.get(),
1922 sCssToDevPixelScalingRegSuffix
)));
1923 Vector
<CSSPixelSpan
> 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
;
1942 MOZ_TRY_VAR(flagsUint
, ReadRegUint(regKey
, GetRegValueName(binPath
.get(),
1944 flags
.deserialize(flagsUint
);
1946 if (flags
.contains(SkeletonUIFlag::TouchDensity
) ||
1947 flags
.contains(SkeletonUIFlag::CompactDensity
)) {
1948 return Err(PreXULSkeletonUIError::BadUIDensity
);
1952 MOZ_TRY_VAR(theme
, ReadRegUint(regKey
, GetRegValueName(binPath
.get(),
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
;
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
);
1992 sMonitorFromWindow(sPreXULSkeletonUIWindow
, MONITOR_DEFAULTTONULL
);
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
);
2006 mi
.rcWork
.right
- mi
.rcWork
.left
+ sNonClientHorizontalMargins
* 2;
2008 mi
.rcWork
.bottom
- mi
.rcWork
.top
+ sNonClientVerticalMargins
* 2;
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
));
2031 void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance
, int argc
,
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
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;
2071 sPixelBuffer
= nullptr;
2072 delete sAnimatedRects
;
2073 sAnimatedRects
= nullptr;
2078 Maybe
<PreXULSkeletonUIError
> GetPreXULSkeletonUIErrorReason() {
2079 return sErrorReason
;
2082 Result
<Ok
, PreXULSkeletonUIError
> PersistPreXULSkeletonUIValues(
2083 const SkeletonUISettings
& settings
) {
2084 if (!sPreXULSkeletonUIEnabled
) {
2085 return Err(PreXULSkeletonUIError::Disabled
);
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
),
2098 MOZ_TRY(WriteRegUint(regKey
,
2099 GetRegValueName(binPath
.get(), sScreenYRegSuffix
),
2101 MOZ_TRY(WriteRegUint(regKey
, GetRegValueName(binPath
.get(), sWidthRegSuffix
),
2103 MOZ_TRY(WriteRegUint(regKey
, GetRegValueName(binPath
.get(), sHeightRegSuffix
),
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
),
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()));
2147 MFBT_API
bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled
; }
2149 MFBT_API Result
<Ok
, PreXULSkeletonUIError
> SetPreXULSkeletonUIEnabledIfAllowed(
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
);
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
;
2187 MFBT_API Result
<Ok
, PreXULSkeletonUIError
> SetPreXULSkeletonUIThemeId(
2189 if (theme
== sTheme
) {
2194 // If we fail below, invalidate sTheme
2195 auto invalidateTheme
= MakeScopeExit([] { sTheme
= ThemeMode::Invalid
; });
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();
2210 MFBT_API
void PollPreXULSkeletonUIEvents() {
2211 if (sPreXULSkeletonUIEnabled
&& sPreXULSkeletonUIWindow
) {
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
2228 if (sPreXULSKeletonUILockFile
!= INVALID_HANDLE_VALUE
) {
2229 ::CloseHandle(sPreXULSKeletonUILockFile
);
2234 } // namespace mozilla