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