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