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