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