tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

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