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