/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

#include "PreXULSkeletonUI.h"

#include <algorithm>
#include <math.h>
#include <limits.h>
#include <cmath>
#include <locale>
#include <string>
#include <objbase.h>
#include <shlobj.h>

#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/BaseProfilerMarkers.h"
#include "mozilla/CacheNtDllThunk.h"
#include "mozilla/FStream.h"
#include "mozilla/GetKnownFolderPath.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/HelperMacros.h"
#include "mozilla/glue/Debug.h"
#include "mozilla/Maybe.h"
#include "mozilla/mscom/ProcessRuntime.h"
#include "mozilla/ResultVariant.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Unused.h"
#include "mozilla/WindowsDpiAwareness.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/WindowsProcessMitigations.h"

namespace mozilla {

// ColorRect defines an optionally-rounded, optionally-bordered rectangle of a
// particular color that we will draw.
struct ColorRect {
  uint32_t color;
  uint32_t borderColor;
  int x;
  int y;
  int width;
  int height;
  int borderWidth;
  int borderRadius;
  bool flipIfRTL;
};

// DrawRect is mostly the same as ColorRect, but exists as an implementation
// detail to simplify drawing borders. We draw borders as a strokeOnly rect
// underneath an inner rect of a particular color. We also need to keep
// track of the backgroundColor for rounding rects, in order to correctly
// anti-alias.
struct DrawRect {
  uint32_t color;
  uint32_t backgroundColor;
  int x;
  int y;
  int width;
  int height;
  int borderRadius;
  int borderWidth;
  bool strokeOnly;
};

struct NormalizedRGB {
  double r;
  double g;
  double b;
};

NormalizedRGB UintToRGB(uint32_t color) {
  double r = static_cast<double>(color >> 16 & 0xff) / 255.0;
  double g = static_cast<double>(color >> 8 & 0xff) / 255.0;
  double b = static_cast<double>(color >> 0 & 0xff) / 255.0;
  return NormalizedRGB{r, g, b};
}

uint32_t RGBToUint(const NormalizedRGB& rgb) {
  return (static_cast<uint32_t>(rgb.r * 255.0) << 16) |
         (static_cast<uint32_t>(rgb.g * 255.0) << 8) |
         (static_cast<uint32_t>(rgb.b * 255.0) << 0);
}

double Lerp(double a, double b, double x) { return a + x * (b - a); }

NormalizedRGB Lerp(const NormalizedRGB& a, const NormalizedRGB& b, double x) {
  return NormalizedRGB{Lerp(a.r, b.r, x), Lerp(a.g, b.g, x), Lerp(a.b, b.b, x)};
}

// Produces a smooth curve in [0,1] based on a linear input in [0,1]
double SmoothStep3(double x) { return x * x * (3.0 - 2.0 * x); }

static const wchar_t kPreXULSkeletonUIKeyPath[] =
    L"SOFTWARE"
    L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\PreXULSkeletonUISettings";

static bool sPreXULSkeletonUIShown = false;
static bool sPreXULSkeletonUIEnabled = false;
static HWND sPreXULSkeletonUIWindow;
static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
static HANDLE sPreXULSKeletonUIAnimationThread;
static HANDLE sPreXULSKeletonUILockFile = INVALID_HANDLE_VALUE;

static mozilla::mscom::ProcessRuntime* sProcessRuntime;
static uint32_t* sPixelBuffer = nullptr;
static Vector<ColorRect>* sAnimatedRects = nullptr;
static int sTotalChromeHeight = 0;
static volatile LONG sAnimationControlFlag = 0;
static bool sMaximized = false;
static int sNonClientVerticalMargins = 0;
static int sNonClientHorizontalMargins = 0;
static uint32_t sDpi = 0;

// Color values needed by the animation loop
static uint32_t sAnimationColor;
static uint32_t sToolbarForegroundColor;

static ThemeMode sTheme = ThemeMode::Invalid;

typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
typedef int(WINAPI* GetSystemMetricsForDpiProc)(int, UINT);
GetSystemMetricsForDpiProc sGetSystemMetricsForDpi = NULL;
typedef UINT(WINAPI* GetDpiForWindowProc)(HWND);
GetDpiForWindowProc sGetDpiForWindow = NULL;
typedef ATOM(WINAPI* RegisterClassWProc)(const WNDCLASSW*);
RegisterClassWProc sRegisterClassW = NULL;
typedef HICON(WINAPI* LoadIconWProc)(HINSTANCE, LPCWSTR);
LoadIconWProc sLoadIconW = NULL;
typedef HICON(WINAPI* LoadCursorWProc)(HINSTANCE, LPCWSTR);
LoadCursorWProc sLoadCursorW = NULL;
typedef HWND(WINAPI* CreateWindowExWProc)(DWORD, LPCWSTR, LPCWSTR, DWORD, int,
                                          int, int, int, HWND, HMENU, HINSTANCE,
                                          LPVOID);
CreateWindowExWProc sCreateWindowExW = NULL;
typedef BOOL(WINAPI* ShowWindowProc)(HWND, int);
ShowWindowProc sShowWindow = NULL;
typedef BOOL(WINAPI* SetWindowPosProc)(HWND, HWND, int, int, int, int, UINT);
SetWindowPosProc sSetWindowPos = NULL;
typedef HDC(WINAPI* GetWindowDCProc)(HWND);
GetWindowDCProc sGetWindowDC = NULL;
typedef int(WINAPI* FillRectProc)(HDC, const RECT*, HBRUSH);
FillRectProc sFillRect = NULL;
typedef BOOL(WINAPI* DeleteObjectProc)(HGDIOBJ);
DeleteObjectProc sDeleteObject = NULL;
typedef int(WINAPI* ReleaseDCProc)(HWND, HDC);
ReleaseDCProc sReleaseDC = NULL;
typedef HMONITOR(WINAPI* MonitorFromWindowProc)(HWND, DWORD);
MonitorFromWindowProc sMonitorFromWindow = NULL;
typedef BOOL(WINAPI* GetMonitorInfoWProc)(HMONITOR, LPMONITORINFO);
GetMonitorInfoWProc sGetMonitorInfoW = NULL;
typedef LONG_PTR(WINAPI* SetWindowLongPtrWProc)(HWND, int, LONG_PTR);
SetWindowLongPtrWProc sSetWindowLongPtrW = NULL;
typedef int(WINAPI* StretchDIBitsProc)(HDC, int, int, int, int, int, int, int,
                                       int, const VOID*, const BITMAPINFO*,
                                       UINT, DWORD);
StretchDIBitsProc sStretchDIBits = NULL;
typedef HBRUSH(WINAPI* CreateSolidBrushProc)(COLORREF);
CreateSolidBrushProc sCreateSolidBrush = NULL;

static int sWindowWidth;
static int sWindowHeight;
static double sCSSToDevPixelScaling;

static Maybe<PreXULSkeletonUIError> sErrorReason;

static const int kAnimationCSSPixelsPerFrame = 11;
static const int kAnimationCSSExtraWindowSize = 300;

// NOTE: these values were pulled out of thin air as round numbers that are
// likely to be too big to be seen in practice. If we legitimately see windows
// this big, we probably don't want to be drawing them on the CPU anyway.
static const uint32_t kMaxWindowWidth = 1 << 16;
static const uint32_t kMaxWindowHeight = 1 << 16;

static const wchar_t* sEnabledRegSuffix = L"|Enabled";
static const wchar_t* sScreenXRegSuffix = L"|ScreenX";
static const wchar_t* sScreenYRegSuffix = L"|ScreenY";
static const wchar_t* sWidthRegSuffix = L"|Width";
static const wchar_t* sHeightRegSuffix = L"|Height";
static const wchar_t* sMaximizedRegSuffix = L"|Maximized";
static const wchar_t* sUrlbarCSSRegSuffix = L"|UrlbarCSSSpan";
static const wchar_t* sCssToDevPixelScalingRegSuffix = L"|CssToDevPixelScaling";
static const wchar_t* sSearchbarRegSuffix = L"|SearchbarCSSSpan";
static const wchar_t* sSpringsCSSRegSuffix = L"|SpringsCSSSpan";
static const wchar_t* sThemeRegSuffix = L"|Theme";
static const wchar_t* sFlagsRegSuffix = L"|Flags";
static const wchar_t* sProgressSuffix = L"|Progress";

std::wstring GetRegValueName(const wchar_t* prefix, const wchar_t* suffix) {
  std::wstring result(prefix);
  result.append(suffix);
  return result;
}

// This is paraphrased from WinHeaderOnlyUtils.h. The fact that this file is
// included in standalone SpiderMonkey builds prohibits us from including that
// file directly, and it hardly warrants its own header. Bug 1674920 tracks
// only including this file for gecko-related builds.
Result<UniquePtr<wchar_t[]>, PreXULSkeletonUIError> GetBinaryPath() {
  DWORD bufLen = MAX_PATH;
  UniquePtr<wchar_t[]> buf;
  while (true) {
    buf = MakeUnique<wchar_t[]>(bufLen);
    DWORD retLen = ::GetModuleFileNameW(nullptr, buf.get(), bufLen);
    if (!retLen) {
      return Err(PreXULSkeletonUIError::FilesystemFailure);
    }

    if (retLen == bufLen && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
      bufLen *= 2;
      continue;
    }

    break;
  }

  return buf;
}

// PreXULSkeletonUIDisallowed means that we don't even have the capacity to
// enable the skeleton UI, whether because we're on a platform that doesn't
// support it or because we launched with command line arguments that we don't
// support. Some of these situations are transient, so we want to make sure we
// don't mess with registry values in these scenarios that we may use in
// other scenarios in which the skeleton UI is actually enabled.
static bool PreXULSkeletonUIDisallowed() {
  return sErrorReason.isSome() &&
         (*sErrorReason == PreXULSkeletonUIError::Cmdline ||
          *sErrorReason == PreXULSkeletonUIError::EnvVars);
}

// Note: this is specifically *not* a robust, multi-locale lowercasing
// operation. It is not intended to be such. It is simply intended to match the
// way in which we look for other instances of firefox to remote into.
// See
// https://searchfox.org/mozilla-central/rev/71621bfa47a371f2b1ccfd33c704913124afb933/toolkit/components/remote/nsRemoteService.cpp#56
static void MutateStringToLowercase(wchar_t* ptr) {
  while (*ptr) {
    wchar_t ch = *ptr;
    if (ch >= L'A' && ch <= L'Z') {
      *ptr = ch + (L'a' - L'A');
    }
    ++ptr;
  }
}

static Result<Ok, PreXULSkeletonUIError> GetSkeletonUILock() {
  auto localAppDataPath = GetKnownFolderPath(FOLDERID_LocalAppData);
  if (!localAppDataPath) {
    return Err(PreXULSkeletonUIError::FilesystemFailure);
  }

  if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
    return Ok();
  }

  // Note: because we're in mozglue, we cannot easily access things from
  // toolkit, like `GetInstallHash`. We could move `GetInstallHash` into
  // mozglue, and rip out all of its usage of types defined in toolkit headers.
  // However, it seems cleaner to just hash the bin path ourselves. We don't
  // get quite the same robustness that `GetInstallHash` might provide, but
  // we already don't have that with how we key our registry values, so it
  // probably makes sense to just match those.
  UniquePtr<wchar_t[]> binPath;
  MOZ_TRY_VAR(binPath, GetBinaryPath());

  // Lowercase the binpath to match how we look for remote instances.
  MutateStringToLowercase(binPath.get());

  // The number of bytes * 2 characters per byte + 1 for the null terminator
  uint32_t hexHashSize = sizeof(uint32_t) * 2 + 1;
  UniquePtr<wchar_t[]> installHash = MakeUnique<wchar_t[]>(hexHashSize);
  // This isn't perfect - it's a 32-bit hash of the path to our executable. It
  // could reasonably collide, or casing could potentially affect things, but
  // the theory is that that should be uncommon enough and the failure case
  // mild enough that this is fine.
  uint32_t binPathHash = HashString(binPath.get());
  swprintf(installHash.get(), hexHashSize, L"%08x", binPathHash);

  std::wstring lockFilePath;
  lockFilePath.append(localAppDataPath.get());
  lockFilePath.append(
      L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\SkeletonUILock-");
  lockFilePath.append(installHash.get());

  // We intentionally leak this file - that is okay, and (kind of) the point.
  // We want to hold onto this handle until the application exits, and hold
  // onto it with exclusive rights. If this check fails, then we assume that
  // another instance of the executable is holding it, and thus return false.
  sPreXULSKeletonUILockFile =
      ::CreateFileW(lockFilePath.c_str(), GENERIC_READ | GENERIC_WRITE,
                    0,  // No sharing - this is how the lock works
                    nullptr, CREATE_ALWAYS,
                    FILE_FLAG_DELETE_ON_CLOSE,  // Don't leave this lying around
                    nullptr);
  if (sPreXULSKeletonUILockFile == INVALID_HANDLE_VALUE) {
    return Err(PreXULSkeletonUIError::FailedGettingLock);
  }

  return Ok();
}

const char kGeneralSection[] = "[General]";
const char kStartWithLastProfile[] = "StartWithLastProfile=";

static bool ProfileDbHasStartWithLastProfile(IFStream& iniContents) {
  bool inGeneral = false;
  std::string line;
  while (std::getline(iniContents, line)) {
    size_t whitespace = 0;
    while (line.length() > whitespace &&
           (line[whitespace] == ' ' || line[whitespace] == '\t')) {
      whitespace++;
    }
    line.erase(0, whitespace);

    if (line.compare(kGeneralSection) == 0) {
      inGeneral = true;
    } else if (inGeneral) {
      if (line[0] == '[') {
        inGeneral = false;
      } else {
        if (line.find(kStartWithLastProfile) == 0) {
          char val = line.c_str()[sizeof(kStartWithLastProfile) - 1];
          if (val == '0') {
            return false;
          } else if (val == '1') {
            return true;
          }
        }
      }
    }
  }

  // If we don't find it in the .ini file, we interpret that as true
  return true;
}

static Result<Ok, PreXULSkeletonUIError> CheckForStartWithLastProfile() {
  auto roamingAppData = GetKnownFolderPath(FOLDERID_RoamingAppData);
  if (!roamingAppData) {
    return Err(PreXULSkeletonUIError::FilesystemFailure);
  }
  std::wstring profileDbPath(roamingAppData.get());
  profileDbPath.append(
      L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\profiles.ini");
  IFStream profileDb(profileDbPath.c_str());
  if (profileDb.fail()) {
    return Err(PreXULSkeletonUIError::FilesystemFailure);
  }

  if (!ProfileDbHasStartWithLastProfile(profileDb)) {
    return Err(PreXULSkeletonUIError::NoStartWithLastProfile);
  }

  return Ok();
}

// We could use nsAutoRegKey, but including nsWindowsHelpers.h causes build
// failures in random places because we're in mozglue. Overall it should be
// simpler and cleaner to just step around that issue with this class:
class MOZ_RAII AutoCloseRegKey {
 public:
  explicit AutoCloseRegKey(HKEY key) : mKey(key) {}
  ~AutoCloseRegKey() { ::RegCloseKey(mKey); }

 private:
  HKEY mKey;
};

int CSSToDevPixels(double cssPixels, double scaling) {
  return floor(cssPixels * scaling + 0.5);
}

int CSSToDevPixels(int cssPixels, double scaling) {
  return CSSToDevPixels((double)cssPixels, scaling);
}

int CSSToDevPixelsFloor(double cssPixels, double scaling) {
  return floor(cssPixels * scaling);
}

// Some things appear to floor to device pixels rather than rounding. A good
// example of this is border widths.
int CSSToDevPixelsFloor(int cssPixels, double scaling) {
  return CSSToDevPixelsFloor((double)cssPixels, scaling);
}

double SignedDistanceToCircle(double x, double y, double radius) {
  return sqrt(x * x + y * y) - radius;
}

// For more details, see
// https://searchfox.org/mozilla-central/rev/a5d9abfda1e26b1207db9549549ab0bdd73f735d/gfx/wr/webrender/res/shared.glsl#141-187
// which was a reference for this function.
double DistanceAntiAlias(double signedDistance) {
  // Distance assumed to be in device pixels. We use an aa range of 0.5 for
  // reasons detailed in the linked code above.
  const double aaRange = 0.5;
  double dist = 0.5 * signedDistance / aaRange;
  if (dist <= -0.5 + std::numeric_limits<double>::epsilon()) return 1.0;
  if (dist >= 0.5 - std::numeric_limits<double>::epsilon()) return 0.0;
  return 0.5 + dist * (0.8431027 * dist * dist - 1.14453603);
}

void RasterizeRoundedRectTopAndBottom(const DrawRect& rect) {
  if (rect.height <= 2 * rect.borderRadius) {
    MOZ_ASSERT(false, "Skeleton UI rect height too small for border radius.");
    return;
  }
  if (rect.width <= 2 * rect.borderRadius) {
    MOZ_ASSERT(false, "Skeleton UI rect width too small for border radius.");
    return;
  }

  NormalizedRGB rgbBase = UintToRGB(rect.backgroundColor);
  NormalizedRGB rgbBlend = UintToRGB(rect.color);

  for (int rowIndex = 0; rowIndex < rect.borderRadius; ++rowIndex) {
    int yTop = rect.y + rect.borderRadius - 1 - rowIndex;
    int yBottom = rect.y + rect.height - rect.borderRadius + rowIndex;

    uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
    uint32_t* innermostPixelTopLeft =
        lineStartTop + rect.x + rect.borderRadius - 1;
    uint32_t* innermostPixelTopRight =
        lineStartTop + rect.x + rect.width - rect.borderRadius;
    uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];
    uint32_t* innermostPixelBottomLeft =
        lineStartBottom + rect.x + rect.borderRadius - 1;
    uint32_t* innermostPixelBottomRight =
        lineStartBottom + rect.x + rect.width - rect.borderRadius;

    // Add 0.5 to x and y to get the pixel center.
    double pixelY = (double)rowIndex + 0.5;
    for (int columnIndex = 0; columnIndex < rect.borderRadius; ++columnIndex) {
      double pixelX = (double)columnIndex + 0.5;
      double distance =
          SignedDistanceToCircle(pixelX, pixelY, (double)rect.borderRadius);
      double alpha = DistanceAntiAlias(distance);
      NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, alpha);
      uint32_t color = RGBToUint(rgb);

      innermostPixelTopLeft[-columnIndex] = color;
      innermostPixelTopRight[columnIndex] = color;
      innermostPixelBottomLeft[-columnIndex] = color;
      innermostPixelBottomRight[columnIndex] = color;
    }

    std::fill(innermostPixelTopLeft + 1, innermostPixelTopRight, rect.color);
    std::fill(innermostPixelBottomLeft + 1, innermostPixelBottomRight,
              rect.color);
  }
}

void RasterizeAnimatedRoundedRectTopAndBottom(
    const ColorRect& colorRect, const uint32_t* animationLookup,
    int priorUpdateAreaMin, int priorUpdateAreaMax, int currentUpdateAreaMin,
    int currentUpdateAreaMax, int animationMin) {
  // We iterate through logical pixel rows here, from inside to outside, which
  // for the top of the rounded rect means from bottom to top, and for the
  // bottom of the rect means top to bottom. We paint pixels from left to
  // right on the top and bottom rows at the same time for the entire animation
  // window. (If the animation window does not overlap any rounded corners,
  // however, we won't be called at all)
  for (int rowIndex = 0; rowIndex < colorRect.borderRadius; ++rowIndex) {
    int yTop = colorRect.y + colorRect.borderRadius - 1 - rowIndex;
    int yBottom =
        colorRect.y + colorRect.height - colorRect.borderRadius + rowIndex;

    uint32_t* lineStartTop = &sPixelBuffer[yTop * sWindowWidth];
    uint32_t* lineStartBottom = &sPixelBuffer[yBottom * sWindowWidth];

    // Add 0.5 to x and y to get the pixel center.
    double pixelY = (double)rowIndex + 0.5;
    for (int x = priorUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
      // The column index is the distance from the innermost pixel, which
      // is different depending on whether we're on the left or right
      // side of the rect. It will always be the max here, and if it's
      // negative that just means we're outside the rounded area.
      int columnIndex =
          std::max((int)colorRect.x + (int)colorRect.borderRadius - x - 1,
                   x - ((int)colorRect.x + (int)colorRect.width -
                        (int)colorRect.borderRadius));

      double alpha = 1.0;
      if (columnIndex >= 0) {
        double pixelX = (double)columnIndex + 0.5;
        double distance = SignedDistanceToCircle(
            pixelX, pixelY, (double)colorRect.borderRadius);
        alpha = DistanceAntiAlias(distance);
      }
      // We don't do alpha blending for the antialiased pixels at the
      // shape's border. It is not noticeable in the animation.
      if (alpha > 1.0 - std::numeric_limits<double>::epsilon()) {
        // Overwrite the tail end of last frame's animation with the
        // rect's normal, unanimated color.
        uint32_t color = x < priorUpdateAreaMax
                             ? colorRect.color
                             : animationLookup[x - animationMin];
        lineStartTop[x] = color;
        lineStartBottom[x] = color;
      }
    }
  }
}

void RasterizeColorRect(const ColorRect& colorRect) {
  // We sometimes split our rect into two, to simplify drawing borders. If we
  // have a border, we draw a stroke-only rect first, and then draw the smaller
  // inner rect on top of it.
  Vector<DrawRect, 2> drawRects;
  Unused << drawRects.reserve(2);
  if (colorRect.borderWidth == 0) {
    DrawRect rect = {};
    rect.color = colorRect.color;
    rect.backgroundColor =
        sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
    rect.x = colorRect.x;
    rect.y = colorRect.y;
    rect.width = colorRect.width;
    rect.height = colorRect.height;
    rect.borderRadius = colorRect.borderRadius;
    rect.strokeOnly = false;
    drawRects.infallibleAppend(rect);
  } else {
    DrawRect borderRect = {};
    borderRect.color = colorRect.borderColor;
    borderRect.backgroundColor =
        sPixelBuffer[colorRect.y * sWindowWidth + colorRect.x];
    borderRect.x = colorRect.x;
    borderRect.y = colorRect.y;
    borderRect.width = colorRect.width;
    borderRect.height = colorRect.height;
    borderRect.borderRadius = colorRect.borderRadius;
    borderRect.borderWidth = colorRect.borderWidth;
    borderRect.strokeOnly = true;
    drawRects.infallibleAppend(borderRect);

    DrawRect baseRect = {};
    baseRect.color = colorRect.color;
    baseRect.backgroundColor = borderRect.color;
    baseRect.x = colorRect.x + colorRect.borderWidth;
    baseRect.y = colorRect.y + colorRect.borderWidth;
    baseRect.width = colorRect.width - 2 * colorRect.borderWidth;
    baseRect.height = colorRect.height - 2 * colorRect.borderWidth;
    baseRect.borderRadius =
        std::max(0, (int)colorRect.borderRadius - (int)colorRect.borderWidth);
    baseRect.borderWidth = 0;
    baseRect.strokeOnly = false;
    drawRects.infallibleAppend(baseRect);
  }

  for (const DrawRect& rect : drawRects) {
    if (rect.height <= 0 || rect.width <= 0) {
      continue;
    }

    // For rounded rectangles, the first thing we do is draw the top and
    // bottom of the rectangle, with the more complicated logic below. After
    // that we can just draw the vertically centered part of the rect like
    // normal.
    RasterizeRoundedRectTopAndBottom(rect);

    // We then draw the flat, central portion of the rect (which in the case of
    // non-rounded rects, is just the entire thing.)
    int solidRectStartY =
        std::clamp(rect.y + rect.borderRadius, 0, sTotalChromeHeight);
    int solidRectEndY = std::clamp(rect.y + rect.height - rect.borderRadius, 0,
                                   sTotalChromeHeight);
    for (int y = solidRectStartY; y < solidRectEndY; ++y) {
      // For strokeOnly rects (used to draw borders), we just draw the left
      // and right side here. Looping down a column of pixels is not the most
      // cache-friendly thing, but it shouldn't be a big deal given the height
      // of the urlbar.
      // Also, if borderRadius is less than borderWidth, we need to ensure
      // that we fully draw the top and bottom lines, so we make sure to check
      // that we're inside the middle range range before excluding pixels.
      if (rect.strokeOnly && y - rect.y > rect.borderWidth &&
          rect.y + rect.height - y > rect.borderWidth) {
        int startXLeft = std::clamp(rect.x, 0, sWindowWidth);
        int endXLeft = std::clamp(rect.x + rect.borderWidth, 0, sWindowWidth);
        int startXRight =
            std::clamp(rect.x + rect.width - rect.borderWidth, 0, sWindowWidth);
        int endXRight = std::clamp(rect.x + rect.width, 0, sWindowWidth);

        uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
        uint32_t* dataStartLeft = lineStart + startXLeft;
        uint32_t* dataEndLeft = lineStart + endXLeft;
        uint32_t* dataStartRight = lineStart + startXRight;
        uint32_t* dataEndRight = lineStart + endXRight;
        std::fill(dataStartLeft, dataEndLeft, rect.color);
        std::fill(dataStartRight, dataEndRight, rect.color);
      } else {
        int startX = std::clamp(rect.x, 0, sWindowWidth);
        int endX = std::clamp(rect.x + rect.width, 0, sWindowWidth);
        uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
        uint32_t* dataStart = lineStart + startX;
        uint32_t* dataEnd = lineStart + endX;
        std::fill(dataStart, dataEnd, rect.color);
      }
    }
  }
}

// Paints the pixels to sPixelBuffer for the skeleton UI animation (a light
// gradient which moves from left to right across the grey placeholder rects).
// Takes in the rect to draw, together with a lookup table for the gradient,
// and the bounds of the previous and current frame of the animation.
bool RasterizeAnimatedRect(const ColorRect& colorRect,
                           const uint32_t* animationLookup,
                           int priorAnimationMin, int animationMin,
                           int animationMax) {
  int rectMin = colorRect.x;
  int rectMax = colorRect.x + colorRect.width;
  bool animationWindowOverlaps =
      rectMax >= priorAnimationMin && rectMin < animationMax;

  int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
  int priorUpdateAreaMax = std::min(rectMax, animationMin);
  int currentUpdateAreaMin = std::max(rectMin, animationMin);
  int currentUpdateAreaMax = std::min(rectMax, animationMax);

  if (!animationWindowOverlaps) {
    return false;
  }

  bool animationWindowOverlapsBorderRadius =
      rectMin + colorRect.borderRadius > priorAnimationMin ||
      rectMax - colorRect.borderRadius <= animationMax;

  // If we don't overlap the left or right side of the rounded rectangle,
  // just pretend it's not rounded. This is a small optimization but
  // there's no point in doing all of this rounded rectangle checking if
  // we aren't even overlapping
  int borderRadius =
      animationWindowOverlapsBorderRadius ? colorRect.borderRadius : 0;

  if (borderRadius > 0) {
    // Similarly to how we draw the rounded rects in DrawSkeletonUI, we
    // first draw the rounded top and bottom, and then we draw the center
    // rect.
    RasterizeAnimatedRoundedRectTopAndBottom(
        colorRect, animationLookup, priorUpdateAreaMin, priorUpdateAreaMax,
        currentUpdateAreaMin, currentUpdateAreaMax, animationMin);
  }

  for (int y = colorRect.y + borderRadius;
       y < colorRect.y + colorRect.height - borderRadius; ++y) {
    uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
    // Overwrite the tail end of last frame's animation with the rect's
    // normal, unanimated color.
    for (int x = priorUpdateAreaMin; x < priorUpdateAreaMax; ++x) {
      lineStart[x] = colorRect.color;
    }
    // Then apply the animated color
    for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
      lineStart[x] = animationLookup[x - animationMin];
    }
  }

  return true;
}

Result<Ok, PreXULSkeletonUIError> DrawSkeletonUI(
    HWND hWnd, CSSPixelSpan urlbarCSSSpan, CSSPixelSpan searchbarCSSSpan,
    Vector<CSSPixelSpan>& springs, const ThemeColors& currentTheme,
    const EnumSet<SkeletonUIFlag, uint32_t>& flags) {
  // NOTE: we opt here to paint a pixel buffer for the application chrome by
  // hand, without using native UI library methods. Why do we do this?
  //
  // 1) It gives us a little bit more control, especially if we want to animate
  //    any of this.
  // 2) It's actually more portable. We can do this on any platform where we
  //    can blit a pixel buffer to the screen, and it only has to change
  //    insofar as the UI is different on those platforms (and thus would have
  //    to change anyway.)
  //
  // The performance impact of this ought to be negligible. As far as has been
  // observed, on slow reference hardware this might take up to a millisecond,
  // for a startup which otherwise takes 30 seconds.
  //
  // The readability and maintainability are a greater concern. When the
  // silhouette of Firefox's core UI changes, this code will likely need to
  // change. However, for the foreseeable future, our skeleton UI will be mostly
  // axis-aligned geometric shapes, and the thought is that any code which is
  // manipulating raw pixels should not be *too* hard to maintain and
  // understand so long as it is only painting such simple shapes.

  sAnimationColor = currentTheme.animationColor;
  sToolbarForegroundColor = currentTheme.toolbarForegroundColor;

  bool menubarShown = flags.contains(SkeletonUIFlag::MenubarShown);
  bool bookmarksToolbarShown =
      flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
  bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);

  int chromeHorMargin = CSSToDevPixels(2, sCSSToDevPixelScaling);
  int verticalOffset = sMaximized ? sNonClientVerticalMargins : 0;
  int horizontalOffset =
      sNonClientHorizontalMargins - (sMaximized ? 0 : chromeHorMargin);

  // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
  int tabBarHeight = CSSToDevPixels(44, sCSSToDevPixelScaling);
  int selectedTabBorderWidth = CSSToDevPixels(2, sCSSToDevPixelScaling);
  // found in tabs.inc.css, "--tab-block-margin"
  int titlebarSpacerWidth = horizontalOffset +
                            CSSToDevPixels(2, sCSSToDevPixelScaling) -
                            selectedTabBorderWidth;
  if (!sMaximized && !menubarShown) {
    // found in tabs.inc.css, ".titlebar-spacer"
    titlebarSpacerWidth += CSSToDevPixels(40, sCSSToDevPixelScaling);
  }
  // found in tabs.inc.css, "--tab-block-margin"
  int selectedTabMarginTop =
      CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
  int selectedTabMarginBottom =
      CSSToDevPixels(4, sCSSToDevPixelScaling) - selectedTabBorderWidth;
  int selectedTabBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
  int selectedTabWidth =
      CSSToDevPixels(221, sCSSToDevPixelScaling) + 2 * selectedTabBorderWidth;
  int toolbarHeight = CSSToDevPixels(40, sCSSToDevPixelScaling);
  // found in browser.css, "#PersonalToolbar"
  int bookmarkToolbarHeight = CSSToDevPixels(28, sCSSToDevPixelScaling);
  if (bookmarksToolbarShown) {
    toolbarHeight += bookmarkToolbarHeight;
  }
  // found in urlbar-searchbar.inc.css, "#urlbar[breakout]"
  int urlbarTopOffset = CSSToDevPixels(4, sCSSToDevPixelScaling);
  int urlbarHeight = CSSToDevPixels(32, sCSSToDevPixelScaling);
  // found in browser-aero.css, "#navigator-toolbox::after" border-bottom
  int chromeContentDividerHeight = CSSToDevPixels(1, sCSSToDevPixelScaling);

  int tabPlaceholderBarMarginTop = CSSToDevPixels(14, sCSSToDevPixelScaling);
  int tabPlaceholderBarMarginLeft = CSSToDevPixels(10, sCSSToDevPixelScaling);
  int tabPlaceholderBarHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
  int tabPlaceholderBarWidth = CSSToDevPixels(120, sCSSToDevPixelScaling);

  int toolbarPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);
  int toolbarPlaceholderMarginRight =
      rtlEnabled ? CSSToDevPixels(11, sCSSToDevPixelScaling)
                 : CSSToDevPixels(9, sCSSToDevPixelScaling);
  int toolbarPlaceholderMarginLeft =
      rtlEnabled ? CSSToDevPixels(9, sCSSToDevPixelScaling)
                 : CSSToDevPixels(11, sCSSToDevPixelScaling);
  int placeholderMargin = CSSToDevPixels(8, sCSSToDevPixelScaling);

  int menubarHeightDevPixels =
      menubarShown ? CSSToDevPixels(28, sCSSToDevPixelScaling) : 0;

  // defined in urlbar-searchbar.inc.css as --urlbar-margin-inline: 5px
  int urlbarMargin =
      CSSToDevPixels(5, sCSSToDevPixelScaling) + horizontalOffset;

  int urlbarTextPlaceholderMarginTop =
      CSSToDevPixels(12, sCSSToDevPixelScaling);
  int urlbarTextPlaceholderMarginLeft =
      CSSToDevPixels(12, sCSSToDevPixelScaling);
  int urlbarTextPlaceHolderWidth = CSSToDevPixels(
      std::clamp(urlbarCSSSpan.end - urlbarCSSSpan.start - 10.0, 0.0, 260.0),
      sCSSToDevPixelScaling);
  int urlbarTextPlaceholderHeight = CSSToDevPixels(10, sCSSToDevPixelScaling);

  int searchbarTextPlaceholderWidth = CSSToDevPixels(62, sCSSToDevPixelScaling);

  auto scopeExit = MakeScopeExit([&] {
    delete sAnimatedRects;
    sAnimatedRects = nullptr;
  });

  Vector<ColorRect> rects;

  ColorRect menubar = {};
  menubar.color = currentTheme.tabBarColor;
  menubar.x = 0;
  menubar.y = verticalOffset;
  menubar.width = sWindowWidth;
  menubar.height = menubarHeightDevPixels;
  menubar.flipIfRTL = false;
  if (!rects.append(menubar)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  int placeholderBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);
  // found in browser.css "--toolbarbutton-border-radius"
  int urlbarBorderRadius = CSSToDevPixels(4, sCSSToDevPixelScaling);

  // The (traditionally dark blue on Windows) background of the tab bar.
  ColorRect tabBar = {};
  tabBar.color = currentTheme.tabBarColor;
  tabBar.x = 0;
  tabBar.y = menubar.y + menubar.height;
  tabBar.width = sWindowWidth;
  tabBar.height = tabBarHeight;
  tabBar.flipIfRTL = false;
  if (!rects.append(tabBar)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The initial selected tab
  ColorRect selectedTab = {};
  selectedTab.color = currentTheme.tabColor;
  selectedTab.x = titlebarSpacerWidth;
  selectedTab.y = menubar.y + menubar.height + selectedTabMarginTop;
  selectedTab.width = selectedTabWidth;
  selectedTab.height =
      tabBar.y + tabBar.height - selectedTab.y - selectedTabMarginBottom;
  selectedTab.borderColor = currentTheme.tabOutlineColor;
  selectedTab.borderWidth = selectedTabBorderWidth;
  selectedTab.borderRadius = selectedTabBorderRadius;
  selectedTab.flipIfRTL = true;
  if (!rects.append(selectedTab)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // A placeholder rect representing text that will fill the selected tab title
  ColorRect tabTextPlaceholder = {};
  tabTextPlaceholder.color = currentTheme.toolbarForegroundColor;
  tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
  tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
  tabTextPlaceholder.width = tabPlaceholderBarWidth;
  tabTextPlaceholder.height = tabPlaceholderBarHeight;
  tabTextPlaceholder.borderRadius = placeholderBorderRadius;
  tabTextPlaceholder.flipIfRTL = true;
  if (!rects.append(tabTextPlaceholder)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The toolbar background
  ColorRect toolbar = {};
  toolbar.color = currentTheme.backgroundColor;
  toolbar.x = 0;
  toolbar.y = tabBar.y + tabBarHeight;
  toolbar.width = sWindowWidth;
  toolbar.height = toolbarHeight;
  toolbar.flipIfRTL = false;
  if (!rects.append(toolbar)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The single-pixel divider line below the toolbar
  ColorRect chromeContentDivider = {};
  chromeContentDivider.color = currentTheme.chromeContentDividerColor;
  chromeContentDivider.x = 0;
  chromeContentDivider.y = toolbar.y + toolbar.height;
  chromeContentDivider.width = sWindowWidth;
  chromeContentDivider.height = chromeContentDividerHeight;
  chromeContentDivider.flipIfRTL = false;
  if (!rects.append(chromeContentDivider)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The urlbar
  ColorRect urlbar = {};
  urlbar.color = currentTheme.urlbarColor;
  urlbar.x = CSSToDevPixels(urlbarCSSSpan.start, sCSSToDevPixelScaling) +
             horizontalOffset;
  urlbar.y = tabBar.y + tabBarHeight + urlbarTopOffset;
  urlbar.width = CSSToDevPixels((urlbarCSSSpan.end - urlbarCSSSpan.start),
                                sCSSToDevPixelScaling);
  urlbar.height = urlbarHeight;
  urlbar.borderColor = currentTheme.urlbarBorderColor;
  urlbar.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
  urlbar.borderRadius = urlbarBorderRadius;
  urlbar.flipIfRTL = false;
  if (!rects.append(urlbar)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The urlbar placeholder rect representating text that will fill the urlbar
  // If rtl is enabled, it is flipped relative to the the urlbar rectangle, not
  // sWindowWidth.
  ColorRect urlbarTextPlaceholder = {};
  urlbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
  urlbarTextPlaceholder.x =
      rtlEnabled
          ? ((urlbar.x + urlbar.width) - urlbarTextPlaceholderMarginLeft -
             urlbarTextPlaceHolderWidth)
          : (urlbar.x + urlbarTextPlaceholderMarginLeft);
  urlbarTextPlaceholder.y = urlbar.y + urlbarTextPlaceholderMarginTop;
  urlbarTextPlaceholder.width = urlbarTextPlaceHolderWidth;
  urlbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
  urlbarTextPlaceholder.borderRadius = placeholderBorderRadius;
  urlbarTextPlaceholder.flipIfRTL = false;
  if (!rects.append(urlbarTextPlaceholder)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  // The searchbar and placeholder text, if present
  // This is y-aligned with the urlbar
  bool hasSearchbar = searchbarCSSSpan.start != 0 && searchbarCSSSpan.end != 0;
  ColorRect searchbarRect = {};
  if (hasSearchbar == true) {
    searchbarRect.color = currentTheme.urlbarColor;
    searchbarRect.x =
        CSSToDevPixels(searchbarCSSSpan.start, sCSSToDevPixelScaling) +
        horizontalOffset;
    searchbarRect.y = urlbar.y;
    searchbarRect.width = CSSToDevPixels(
        searchbarCSSSpan.end - searchbarCSSSpan.start, sCSSToDevPixelScaling);
    searchbarRect.height = urlbarHeight;
    searchbarRect.borderRadius = urlbarBorderRadius;
    searchbarRect.borderColor = currentTheme.urlbarBorderColor;
    searchbarRect.borderWidth = CSSToDevPixels(1, sCSSToDevPixelScaling);
    searchbarRect.flipIfRTL = false;
    if (!rects.append(searchbarRect)) {
      return Err(PreXULSkeletonUIError::OOM);
    }

    // The placeholder rect representating text that will fill the searchbar
    // This uses the same margins as the urlbarTextPlaceholder
    // If rtl is enabled, it is flipped relative to the the searchbar rectangle,
    // not sWindowWidth.
    ColorRect searchbarTextPlaceholder = {};
    searchbarTextPlaceholder.color = currentTheme.toolbarForegroundColor;
    searchbarTextPlaceholder.x =
        rtlEnabled
            ? ((searchbarRect.x + searchbarRect.width) -
               urlbarTextPlaceholderMarginLeft - searchbarTextPlaceholderWidth)
            : (searchbarRect.x + urlbarTextPlaceholderMarginLeft);
    searchbarTextPlaceholder.y =
        searchbarRect.y + urlbarTextPlaceholderMarginTop;
    searchbarTextPlaceholder.width = searchbarTextPlaceholderWidth;
    searchbarTextPlaceholder.height = urlbarTextPlaceholderHeight;
    searchbarTextPlaceholder.flipIfRTL = false;
    if (!rects.append(searchbarTextPlaceholder) ||
        !sAnimatedRects->append(searchbarTextPlaceholder)) {
      return Err(PreXULSkeletonUIError::OOM);
    }
  }

  // Determine where the placeholder rectangles should not go. This is
  // anywhere occupied by a spring, urlbar, or searchbar
  Vector<DevPixelSpan> noPlaceholderSpans;

  DevPixelSpan urlbarSpan;
  urlbarSpan.start = urlbar.x - urlbarMargin;
  urlbarSpan.end = urlbar.width + urlbar.x + urlbarMargin;

  DevPixelSpan searchbarSpan;
  if (hasSearchbar) {
    searchbarSpan.start = searchbarRect.x - urlbarMargin;
    searchbarSpan.end = searchbarRect.width + searchbarRect.x + urlbarMargin;
  }

  DevPixelSpan marginLeftPlaceholder;
  marginLeftPlaceholder.start = toolbarPlaceholderMarginLeft;
  marginLeftPlaceholder.end = toolbarPlaceholderMarginLeft;
  if (!noPlaceholderSpans.append(marginLeftPlaceholder)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  if (rtlEnabled) {
    // If we're RTL, then the springs as ordered in the DOM will be from right
    // to left, which will break our comparison logic below
    springs.reverse();
  }

  for (auto spring : springs) {
    DevPixelSpan springDevPixels;
    springDevPixels.start =
        CSSToDevPixels(spring.start, sCSSToDevPixelScaling) + horizontalOffset;
    springDevPixels.end =
        CSSToDevPixels(spring.end, sCSSToDevPixelScaling) + horizontalOffset;
    if (!noPlaceholderSpans.append(springDevPixels)) {
      return Err(PreXULSkeletonUIError::OOM);
    }
  }

  DevPixelSpan marginRightPlaceholder;
  marginRightPlaceholder.start = sWindowWidth - toolbarPlaceholderMarginRight;
  marginRightPlaceholder.end = sWindowWidth - toolbarPlaceholderMarginRight;
  if (!noPlaceholderSpans.append(marginRightPlaceholder)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  Vector<DevPixelSpan, 2> spansToAdd;
  Unused << spansToAdd.reserve(2);
  spansToAdd.infallibleAppend(urlbarSpan);
  if (hasSearchbar) {
    spansToAdd.infallibleAppend(searchbarSpan);
  }

  for (auto& toAdd : spansToAdd) {
    for (auto& span : noPlaceholderSpans) {
      if (span.start > toAdd.start) {
        if (!noPlaceholderSpans.insert(&span, toAdd)) {
          return Err(PreXULSkeletonUIError::OOM);
        }
        break;
      }
    }
  }

  for (size_t i = 1; i < noPlaceholderSpans.length(); i++) {
    int start = noPlaceholderSpans[i - 1].end + placeholderMargin;
    int end = noPlaceholderSpans[i].start - placeholderMargin;
    if (start + 2 * placeholderBorderRadius >= end) {
      continue;
    }

    // The placeholder rects should all be y-aligned.
    ColorRect placeholderRect = {};
    placeholderRect.color = currentTheme.toolbarForegroundColor;
    placeholderRect.x = start;
    placeholderRect.y = urlbarTextPlaceholder.y;
    placeholderRect.width = end - start;
    placeholderRect.height = toolbarPlaceholderHeight;
    placeholderRect.borderRadius = placeholderBorderRadius;
    placeholderRect.flipIfRTL = false;
    if (!rects.append(placeholderRect) ||
        !sAnimatedRects->append(placeholderRect)) {
      return Err(PreXULSkeletonUIError::OOM);
    }
  }

  sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
  if (sTotalChromeHeight > sWindowHeight) {
    return Err(PreXULSkeletonUIError::BadWindowDimensions);
  }

  if (!sAnimatedRects->append(tabTextPlaceholder) ||
      !sAnimatedRects->append(urlbarTextPlaceholder)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  sPixelBuffer =
      (uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));

  for (auto& rect : *sAnimatedRects) {
    if (rtlEnabled && rect.flipIfRTL) {
      rect.x = sWindowWidth - rect.x - rect.width;
    }
    rect.x = std::clamp(rect.x, 0, sWindowWidth);
    rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
    rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
    rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
  }

  for (auto& rect : rects) {
    if (rtlEnabled && rect.flipIfRTL) {
      rect.x = sWindowWidth - rect.x - rect.width;
    }
    rect.x = std::clamp(rect.x, 0, sWindowWidth);
    rect.width = std::clamp(rect.width, 0, sWindowWidth - rect.x);
    rect.y = std::clamp(rect.y, 0, sTotalChromeHeight);
    rect.height = std::clamp(rect.height, 0, sTotalChromeHeight - rect.y);
    RasterizeColorRect(rect);
  }

  HDC hdc = sGetWindowDC(hWnd);
  if (!hdc) {
    return Err(PreXULSkeletonUIError::FailedGettingDC);
  }
  auto cleanupDC = MakeScopeExit([=] { sReleaseDC(hWnd, hdc); });

  BITMAPINFO chromeBMI = {};
  chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
  chromeBMI.bmiHeader.biWidth = sWindowWidth;
  chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
  chromeBMI.bmiHeader.biPlanes = 1;
  chromeBMI.bmiHeader.biBitCount = 32;
  chromeBMI.bmiHeader.biCompression = BI_RGB;

  // First, we just paint the chrome area with our pixel buffer
  int scanLinesCopied = sStretchDIBits(
      hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0, sWindowWidth,
      sTotalChromeHeight, sPixelBuffer, &chromeBMI, DIB_RGB_COLORS, SRCCOPY);
  if (scanLinesCopied == 0) {
    return Err(PreXULSkeletonUIError::FailedBlitting);
  }

  // Then, we just fill the rest with FillRect
  RECT rect = {0, sTotalChromeHeight, sWindowWidth, sWindowHeight};
  HBRUSH brush =
      sCreateSolidBrush(RGB((currentTheme.backgroundColor & 0xff0000) >> 16,
                            (currentTheme.backgroundColor & 0x00ff00) >> 8,
                            (currentTheme.backgroundColor & 0x0000ff) >> 0));
  int fillRectResult = sFillRect(hdc, &rect, brush);

  sDeleteObject(brush);

  if (fillRectResult == 0) {
    return Err(PreXULSkeletonUIError::FailedFillingBottomRect);
  }

  scopeExit.release();
  return Ok();
}

DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
  if (!sPixelBuffer || sAnimatedRects->empty()) {
    return 0;
  }

  // See the comments above the InterlockedIncrement calls below here - we
  // atomically flip this up and down around sleep so the main thread doesn't
  // have to wait for us if we're just sleeping.
  if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
    return 0;
  }
  // Sleep for two seconds - startups faster than this don't really benefit
  // from an animation, and we don't want to take away cycles from them.
  // Startups longer than this, however, are more likely to be blocked on IO,
  // and thus animating does not substantially impact startup times for them.
  ::Sleep(2000);
  if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
    return 0;
  }

  // On each of the animated rects (which happen to all be placeholder UI
  // rects sharing the same color), we want to animate a gradient moving across
  // the screen from left to right. The gradient starts as the rect's color on,
  // the left side, changes to the background color of the window by the middle
  // of the gradient, and then goes back down to the rect's color. To make this
  // faster than interpolating between the two colors for each pixel for each
  // frame, we simply create a lookup buffer in which we can look up the color
  // for a particular offset into the gradient.
  //
  // To do this we just interpolate between the two values, and to give the
  // gradient a smoother transition between colors, we transform the linear
  // blend amount via the cubic smooth step function (SmoothStep3) to produce
  // a smooth start and stop for the gradient. We do this for the first half
  // of the gradient, and then simply copy that backwards for the second half.
  //
  // The CSS width of 80 chosen here is effectively is just to match the size
  // of the animation provided in the design mockup. We define it in CSS pixels
  // simply because the rest of our UI is based off of CSS scalings.
  int animationWidth = CSSToDevPixels(80, sCSSToDevPixelScaling);
  UniquePtr<uint32_t[]> animationLookup =
      MakeUnique<uint32_t[]>(animationWidth);
  uint32_t animationColor = sAnimationColor;
  NormalizedRGB rgbBlend = UintToRGB(animationColor);

  // Build the first half of the lookup table
  for (int i = 0; i < animationWidth / 2; ++i) {
    uint32_t baseColor = sToolbarForegroundColor;
    double blendAmountLinear =
        static_cast<double>(i) / (static_cast<double>(animationWidth / 2));
    double blendAmount = SmoothStep3(blendAmountLinear);

    NormalizedRGB rgbBase = UintToRGB(baseColor);
    NormalizedRGB rgb = Lerp(rgbBase, rgbBlend, blendAmount);
    animationLookup[i] = RGBToUint(rgb);
  }

  // Copy the first half of the lookup table into the second half backwards
  for (int i = animationWidth / 2; i < animationWidth; ++i) {
    int j = animationWidth - 1 - i;
    if (j == animationWidth / 2) {
      // If animationWidth is odd, we'll be left with one pixel at the center.
      // Just color that as the animation color.
      animationLookup[i] = animationColor;
    } else {
      animationLookup[i] = animationLookup[j];
    }
  }

  // The bitmap info remains unchanged throughout the animation - this just
  // effectively describes the contents of sPixelBuffer
  BITMAPINFO chromeBMI = {};
  chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
  chromeBMI.bmiHeader.biWidth = sWindowWidth;
  chromeBMI.bmiHeader.biHeight = -sTotalChromeHeight;
  chromeBMI.bmiHeader.biPlanes = 1;
  chromeBMI.bmiHeader.biBitCount = 32;
  chromeBMI.bmiHeader.biCompression = BI_RGB;

  uint32_t animationIteration = 0;

  int devPixelsPerFrame =
      CSSToDevPixels(kAnimationCSSPixelsPerFrame, sCSSToDevPixelScaling);
  int devPixelsExtraWindowSize =
      CSSToDevPixels(kAnimationCSSExtraWindowSize, sCSSToDevPixelScaling);

  if (::InterlockedCompareExchange(&sAnimationControlFlag, 0, 0)) {
    // The window got consumed before we were able to draw anything.
    return 0;
  }

  while (true) {
    // The gradient will move across the screen at devPixelsPerFrame at
    // 60fps, and then loop back to the beginning. However, we add a buffer of
    // devPixelsExtraWindowSize around the edges so it doesn't immediately
    // jump back, giving it a more pulsing feel.
    int animationMin = ((animationIteration * devPixelsPerFrame) %
                        (sWindowWidth + devPixelsExtraWindowSize)) -
                       devPixelsExtraWindowSize / 2;
    int animationMax = animationMin + animationWidth;
    // The priorAnimationMin is the beginning of the previous frame's animation.
    // Since we only want to draw the bits of the image that we updated, we need
    // to overwrite the left bit of the animation we drew last frame with the
    // default color.
    int priorAnimationMin = animationMin - devPixelsPerFrame;
    animationMin = std::max(0, animationMin);
    priorAnimationMin = std::max(0, priorAnimationMin);
    animationMax = std::min((int)sWindowWidth, animationMax);

    // The gradient only affects the specific rects that we put into
    // sAnimatedRects. So we simply update those rects, and maintain a flag
    // to avoid drawing when we don't need to.
    bool updatedAnything = false;
    for (ColorRect rect : *sAnimatedRects) {
      bool hadUpdates =
          RasterizeAnimatedRect(rect, animationLookup.get(), priorAnimationMin,
                                animationMin, animationMax);
      updatedAnything = updatedAnything || hadUpdates;
    }

    if (updatedAnything) {
      HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
      if (!hdc) {
        return 0;
      }

      sStretchDIBits(hdc, priorAnimationMin, 0,
                     animationMax - priorAnimationMin, sTotalChromeHeight,
                     priorAnimationMin, 0, animationMax - priorAnimationMin,
                     sTotalChromeHeight, sPixelBuffer, &chromeBMI,
                     DIB_RGB_COLORS, SRCCOPY);

      sReleaseDC(sPreXULSkeletonUIWindow, hdc);
    }

    animationIteration++;

    // We coordinate around our sleep here to ensure that the main thread does
    // not wait on us if we're sleeping. If we don't get 1 here, it means the
    // window has been consumed and we don't need to sleep. If in
    // ConsumePreXULSkeletonUIHandle we get a value other than 1 after
    // incrementing, it means we're sleeping, and that function can assume that
    // we will safely exit after the sleep because of the observed value of
    // sAnimationControlFlag.
    if (InterlockedIncrement(&sAnimationControlFlag) != 1) {
      return 0;
    }

    // Note: Sleep does not guarantee an exact time interval. If the system is
    // busy, for instance, we could easily end up taking several frames longer,
    // and really we could be left unscheduled for an arbitrarily long time.
    // This is fine, and we don't really care. We could track how much time this
    // actually took and jump the animation forward the appropriate amount, but
    // its not even clear that that's a better user experience. So we leave this
    // as simple as we can.
    ::Sleep(16);

    // Here we bring sAnimationControlFlag back down - again, if we don't get a
    // 0 here it means we consumed the skeleton UI window in the mean time, so
    // we can simply exit.
    if (InterlockedDecrement(&sAnimationControlFlag) != 0) {
      return 0;
    }
  }
}

LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
                                    LPARAM lParam) {
  // Exposing a generic oleacc proxy for the skeleton isn't useful and may cause
  // screen readers to report spurious information when the skeleton appears.
  if (msg == WM_GETOBJECT && sPreXULSkeletonUIWindow) {
    return E_FAIL;
  }

  // NOTE: this block was copied from WinUtils.cpp, and needs to be kept in
  // sync.
  if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
    sEnableNonClientDpiScaling(hWnd);
  }

  // NOTE: this block was paraphrased from the WM_NCCALCSIZE handler in
  // nsWindow.cpp, and will need to be kept in sync.
  if (msg == WM_NCCALCSIZE) {
    RECT* clientRect =
        wParam ? &(reinterpret_cast<NCCALCSIZE_PARAMS*>(lParam))->rgrc[0]
               : (reinterpret_cast<RECT*>(lParam));

    // These match the margins set in browser-tabsintitlebar.js with
    // default prefs on Windows. Bug 1673092 tracks lining this up with
    // that more correctly instead of hard-coding it.
    int horizontalOffset =
        sNonClientHorizontalMargins -
        (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
    int verticalOffset =
        sNonClientHorizontalMargins -
        (sMaximized ? 0 : CSSToDevPixels(2, sCSSToDevPixelScaling));
    clientRect->top = clientRect->top;
    clientRect->left += horizontalOffset;
    clientRect->right -= horizontalOffset;
    clientRect->bottom -= verticalOffset;
    return 0;
  }

  return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}

bool IsSystemDarkThemeEnabled() {
  DWORD result;
  HKEY themeKey;
  DWORD dataLen = sizeof(uint32_t);
  LPCWSTR keyName =
      L"SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";

  result = ::RegOpenKeyExW(HKEY_CURRENT_USER, keyName, 0, KEY_READ, &themeKey);
  if (result != ERROR_SUCCESS) {
    return false;
  }
  AutoCloseRegKey closeKey(themeKey);

  uint32_t lightThemeEnabled;
  result = ::RegGetValueW(
      themeKey, nullptr, L"AppsUseLightTheme", RRF_RT_REG_DWORD, nullptr,
      reinterpret_cast<PBYTE>(&lightThemeEnabled), &dataLen);
  if (result != ERROR_SUCCESS) {
    return false;
  }
  return !lightThemeEnabled;
}

ThemeColors GetTheme(ThemeMode themeId) {
  ThemeColors theme = {};
  switch (themeId) {
    case ThemeMode::Dark:
      // Dark theme or default theme when in dark mode

      // controlled by css variable --toolbar-bgcolor
      theme.backgroundColor = 0x2b2a33;
      theme.tabColor = 0x42414d;
      theme.toolbarForegroundColor = 0x6a6a6d;
      theme.tabOutlineColor = 0x1c1b22;
      // controlled by css variable --lwt-accent-color
      theme.tabBarColor = 0x1c1b22;
      // controlled by --toolbar-non-lwt-textcolor in browser.css
      theme.chromeContentDividerColor = 0x0c0c0d;
      // controlled by css variable --toolbar-field-background-color
      theme.urlbarColor = 0x42414d;
      theme.urlbarBorderColor = 0x42414d;
      theme.animationColor = theme.urlbarColor;
      return theme;
    case ThemeMode::Light:
    case ThemeMode::Default:
    default:
      // --toolbar-non-lwt-bgcolor in browser.css
      theme.backgroundColor = 0xf9f9fb;
      theme.tabColor = 0xf9f9fb;
      theme.toolbarForegroundColor = 0xdddde1;
      theme.tabOutlineColor = 0xdddde1;
      // found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
      // (set to "hsl(235,33%,19%)")
      theme.tabBarColor = 0xf0f0f4;
      // --chrome-content-separator-color in browser.css
      theme.chromeContentDividerColor = 0xe1e1e2;
      // controlled by css variable --toolbar-color
      theme.urlbarColor = 0xffffff;
      theme.urlbarBorderColor = 0xdddde1;
      theme.animationColor = theme.backgroundColor;
      return theme;
  }
}

Result<HKEY, PreXULSkeletonUIError> OpenPreXULSkeletonUIRegKey() {
  HKEY key;
  DWORD disposition;
  LSTATUS result =
      ::RegCreateKeyExW(HKEY_CURRENT_USER, kPreXULSkeletonUIKeyPath, 0, nullptr,
                        0, KEY_ALL_ACCESS, nullptr, &key, &disposition);

  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
  }

  if (disposition == REG_CREATED_NEW_KEY ||
      disposition == REG_OPENED_EXISTING_KEY) {
    return key;
  }

  ::RegCloseKey(key);
  return Err(PreXULSkeletonUIError::FailedToOpenRegistryKey);
}

Result<Ok, PreXULSkeletonUIError> LoadGdi32AndUser32Procedures() {
  HMODULE user32Dll = ::LoadLibraryW(L"user32");
  HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");

  if (!user32Dll || !gdi32Dll) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }

  auto getThreadDpiAwarenessContext =
      (decltype(GetThreadDpiAwarenessContext)*)::GetProcAddress(
          user32Dll, "GetThreadDpiAwarenessContext");
  auto areDpiAwarenessContextsEqual =
      (decltype(AreDpiAwarenessContextsEqual)*)::GetProcAddress(
          user32Dll, "AreDpiAwarenessContextsEqual");
  if (getThreadDpiAwarenessContext && areDpiAwarenessContextsEqual &&
      areDpiAwarenessContextsEqual(getThreadDpiAwarenessContext(),
                                   DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE)) {
    // EnableNonClientDpiScaling is optional - we can handle not having it.
    sEnableNonClientDpiScaling =
        (EnableNonClientDpiScalingProc)::GetProcAddress(
            user32Dll, "EnableNonClientDpiScaling");
  }

  sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
      user32Dll, "GetSystemMetricsForDpi");
  if (!sGetSystemMetricsForDpi) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sGetDpiForWindow =
      (GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
  if (!sGetDpiForWindow) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sRegisterClassW =
      (RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
  if (!sRegisterClassW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sCreateWindowExW =
      (CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
  if (!sCreateWindowExW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
  if (!sShowWindow) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
  if (!sSetWindowPos) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
  if (!sGetWindowDC) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
  if (!sFillRect) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
  if (!sReleaseDC) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
  if (!sLoadIconW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
  if (!sLoadCursorW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sMonitorFromWindow =
      (MonitorFromWindowProc)::GetProcAddress(user32Dll, "MonitorFromWindow");
  if (!sMonitorFromWindow) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sGetMonitorInfoW =
      (GetMonitorInfoWProc)::GetProcAddress(user32Dll, "GetMonitorInfoW");
  if (!sGetMonitorInfoW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sSetWindowLongPtrW =
      (SetWindowLongPtrWProc)::GetProcAddress(user32Dll, "SetWindowLongPtrW");
  if (!sSetWindowLongPtrW) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sStretchDIBits =
      (StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
  if (!sStretchDIBits) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sCreateSolidBrush =
      (CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
  if (!sCreateSolidBrush) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }
  sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
  if (!sDeleteObject) {
    return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);
  }

  return Ok();
}

// Strips "--", "-", and "/" from the front of the arg if one of those exists,
// returning `arg + 2`, `arg + 1`, and `arg + 1` respectively. If none of these
// prefixes are found, the argument is not a flag, and nullptr is returned.
const char* NormalizeFlag(const char* arg) {
  if (strstr(arg, "--") == arg) {
    return arg + 2;
  }

  if (arg[0] == '-') {
    return arg + 1;
  }

  if (arg[0] == '/') {
    return arg + 1;
  }

  return nullptr;
}

static bool EnvHasValue(const char* name) {
  const char* val = getenv(name);
  return (val && *val);
}

// Ensures that we only see arguments in the command line which are acceptable.
// This is based on manual inspection of the list of arguments listed in the MDN
// page for Gecko/Firefox commandline options:
// https://developer.mozilla.org/en-US/docs/Mozilla/Command_Line_Options
// Broadly speaking, we want to reject any argument which causes us to show
// something other than the default window at its normal size. Here is a non-
// exhaustive list of command line options we want to *exclude*:
//
//   -ProfileManager : This will display the profile manager window, which does
//                     not match the skeleton UI at all.
//
//   -CreateProfile  : This will display a firefox window with the default
//                     screen position and size, and not the position and size
//                     which we have recorded in the registry.
//
//   -P <profile>    : This could cause us to display firefox with a position
//                     and size of a different profile than that in which we
//                     were previously running.
//
//   -width, -height : This will cause the width and height values in the
//                     registry to be incorrect.
//
//   -kiosk          : See above.
//
//   -headless       : This one should be rather obvious.
//
//   -migration      : This will start with the import wizard, which of course
//                     does not match the skeleton UI.
//
//   -private-window : This is tricky, but the colors of the main content area
//                     make this not feel great with the white content of the
//                     default skeleton UI.
//
// NOTE: we generally want to skew towards erroneous rejections of the command
// line rather than erroneous approvals. The consequence of a bad rejection
// is that we don't show the skeleton UI, which is business as usual. The
// consequence of a bad approval is that we show it when we're not supposed to,
// which is visually jarring and can also be unpredictable - there's no
// guarantee that the code which handles the non-default window is set up to
// properly handle the transition from the skeleton UI window.
static Result<Ok, PreXULSkeletonUIError> ValidateCmdlineArguments(
    int argc, char** argv, bool* explicitProfile) {
  const char* approvedArgumentsArray[] = {
      // These won't cause the browser to be visualy different in any way
      "new-instance", "no-remote", "browser", "foreground", "setDefaultBrowser",
      "attach-console", "wait-for-browser", "osint",

      // These will cause the chrome to be a bit different or extra windows to
      // be created, but overall the skeleton UI should still be broadly
      // correct enough.
      "new-tab", "new-window",

      // To the extent possible, we want to ensure that existing tests cover
      // the skeleton UI, so we need to allow marionette
      "marionette",

      // These will cause the content area to appear different, but won't
      // meaningfully affect the chrome
      "preferences", "search", "url",

#ifndef MOZILLA_OFFICIAL
      // On local builds, we want to allow -profile, because it's how `mach run`
      // operates, and excluding that would create an unnecessary blind spot for
      // Firefox devs.
      "profile"
#endif

      // There are other arguments which are likely okay. However, they are
      // not included here because this list is not intended to be
      // exhaustive - it only intends to green-light some somewhat commonly
      // used arguments. We want to err on the side of an unnecessary
      // rejection of the command line.
  };

  int approvedArgumentsArraySize =
      sizeof(approvedArgumentsArray) / sizeof(approvedArgumentsArray[0]);
  Vector<const char*> approvedArguments;
  if (!approvedArguments.reserve(approvedArgumentsArraySize)) {
    return Err(PreXULSkeletonUIError::OOM);
  }

  for (int i = 0; i < approvedArgumentsArraySize; ++i) {
    approvedArguments.infallibleAppend(approvedArgumentsArray[i]);
  }

#ifdef MOZILLA_OFFICIAL
  int profileArgIndex = -1;
  // If we're running mochitests or direct marionette tests, those specify a
  // temporary profile, and we want to ensure that we get the added coverage
  // from those.
  for (int i = 1; i < argc; ++i) {
    const char* flag = NormalizeFlag(argv[i]);
    if (flag && !strcmp(flag, "marionette")) {
      if (!approvedArguments.append("profile")) {
        return Err(PreXULSkeletonUIError::OOM);
      }
      profileArgIndex = approvedArguments.length() - 1;

      break;
    }
  }
#else
  int profileArgIndex = approvedArguments.length() - 1;
#endif

  for (int i = 1; i < argc; ++i) {
    const char* flag = NormalizeFlag(argv[i]);
    if (!flag) {
      // If this is not a flag, then we interpret it as a URL, similar to
      // BrowserContentHandler.sys.mjs. Some command line options take
      // additional arguments, which may or may not be URLs. We don't need to
      // know this, because we don't need to parse them out; we just rely on the
      // assumption that if arg X is actually a parameter for the preceding
      // arg Y, then X must not look like a flag (starting with "--", "-",
      // or "/").
      //
      // The most important thing here is the assumption that if something is
      // going to meaningfully alter the appearance of the window itself, it
      // must be a flag.
      continue;
    }

    bool approved = false;
    for (const char* approvedArg : approvedArguments) {
      // We do a case-insensitive compare here with _stricmp. Even though some
      // of these arguments are *not* read as case-insensitive, others *are*.
      // Similar to the flag logic above, we don't really care about this
      // distinction, because we don't need to parse the arguments - we just
      // rely on the assumption that none of the listed flags in our
      // approvedArguments are overloaded in such a way that a different
      // casing would visually alter the firefox window.
      if (!_stricmp(flag, approvedArg)) {
        approved = true;

        if (i == profileArgIndex) {
          *explicitProfile = true;
        }
        break;
      }
    }

    if (!approved) {
      return Err(PreXULSkeletonUIError::Cmdline);
    }
  }

  return Ok();
}

static Result<Ok, PreXULSkeletonUIError> ValidateEnvVars() {
  if (EnvHasValue("MOZ_SAFE_MODE_RESTART") ||
      EnvHasValue("MOZ_APP_SILENT_START") ||
      EnvHasValue("MOZ_RESET_PROFILE_RESTART") || EnvHasValue("MOZ_HEADLESS") ||
      (EnvHasValue("XRE_PROFILE_PATH") &&
       !EnvHasValue("MOZ_SKELETON_UI_RESTARTING"))) {
    return Err(PreXULSkeletonUIError::EnvVars);
  }

  return Ok();
}

static bool VerifyWindowDimensions(uint32_t windowWidth,
                                   uint32_t windowHeight) {
  return windowWidth <= kMaxWindowWidth && windowHeight <= kMaxWindowHeight;
}

static Result<Vector<CSSPixelSpan>, PreXULSkeletonUIError> ReadRegCSSPixelSpans(
    HKEY regKey, const std::wstring& valueName) {
  DWORD dataLen = 0;
  LSTATUS result = ::RegQueryValueExW(regKey, valueName.c_str(), nullptr,
                                      nullptr, nullptr, &dataLen);
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  if (dataLen % (2 * sizeof(double)) != 0) {
    return Err(PreXULSkeletonUIError::CorruptData);
  }

  auto buffer = MakeUniqueFallible<wchar_t[]>(dataLen);
  if (!buffer) {
    return Err(PreXULSkeletonUIError::OOM);
  }
  result =
      ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
                     nullptr, reinterpret_cast<PBYTE>(buffer.get()), &dataLen);
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  Vector<CSSPixelSpan> resultVector;
  double* asDoubles = reinterpret_cast<double*>(buffer.get());
  for (size_t i = 0; i < dataLen / (2 * sizeof(double)); i++) {
    CSSPixelSpan span = {};
    span.start = *(asDoubles++);
    span.end = *(asDoubles++);
    if (!resultVector.append(span)) {
      return Err(PreXULSkeletonUIError::OOM);
    }
  }

  return resultVector;
}

static Result<double, PreXULSkeletonUIError> ReadRegDouble(
    HKEY regKey, const std::wstring& valueName) {
  double value = 0;
  DWORD dataLen = sizeof(double);
  LSTATUS result =
      ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_BINARY,
                     nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
  if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  return value;
}

static Result<uint32_t, PreXULSkeletonUIError> ReadRegUint(
    HKEY regKey, const std::wstring& valueName) {
  DWORD value = 0;
  DWORD dataLen = sizeof(uint32_t);
  LSTATUS result =
      ::RegGetValueW(regKey, nullptr, valueName.c_str(), RRF_RT_REG_DWORD,
                     nullptr, reinterpret_cast<PBYTE>(&value), &dataLen);
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  return value;
}

static Result<bool, PreXULSkeletonUIError> ReadRegBool(
    HKEY regKey, const std::wstring& valueName) {
  uint32_t value;
  MOZ_TRY_VAR(value, ReadRegUint(regKey, valueName));
  return !!value;
}

static Result<Ok, PreXULSkeletonUIError> WriteRegCSSPixelSpans(
    HKEY regKey, const std::wstring& valueName, const CSSPixelSpan* spans,
    int spansLength) {
  // No guarantee on the packing of CSSPixelSpan. We could #pragma it, but it's
  // also trivial to just copy them into a buffer of doubles.
  auto doubles = MakeUnique<double[]>(spansLength * 2);
  for (int i = 0; i < spansLength; ++i) {
    doubles[i * 2] = spans[i].start;
    doubles[i * 2 + 1] = spans[i].end;
  }

  LSTATUS result =
      ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
                       reinterpret_cast<const BYTE*>(doubles.get()),
                       spansLength * sizeof(double) * 2);
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }
  return Ok();
}

static Result<Ok, PreXULSkeletonUIError> WriteRegDouble(
    HKEY regKey, const std::wstring& valueName, double value) {
  LSTATUS result =
      ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_BINARY,
                       reinterpret_cast<const BYTE*>(&value), sizeof(value));
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  return Ok();
}

static Result<Ok, PreXULSkeletonUIError> WriteRegUint(
    HKEY regKey, const std::wstring& valueName, uint32_t value) {
  LSTATUS result =
      ::RegSetValueExW(regKey, valueName.c_str(), 0, REG_DWORD,
                       reinterpret_cast<PBYTE>(&value), sizeof(value));
  if (result != ERROR_SUCCESS) {
    return Err(PreXULSkeletonUIError::RegistryError);
  }

  return Ok();
}

static Result<Ok, PreXULSkeletonUIError> WriteRegBool(
    HKEY regKey, const std::wstring& valueName, bool value) {
  return WriteRegUint(regKey, valueName, value ? 1 : 0);
}

static Result<Ok, PreXULSkeletonUIError> CreateAndStorePreXULSkeletonUIImpl(
    HINSTANCE hInstance, int argc, char** argv) {
  // Initializing COM below may load modules via SetWindowHookEx, some of
  // which may modify the executable's IAT for ntdll.dll.  If that happens,
  // this browser process fails to launch sandbox processes because we cannot
  // copy a modified IAT into a remote process (See SandboxBroker::LaunchApp).
  // To prevent that, we cache the intact IAT before COM initialization.
  // If EAF+ is enabled, CacheNtDllThunk() causes a crash, but EAF+ will
  // also prevent an injected module from parsing the PE headers and modifying
  // the IAT.  Therefore, we can skip CacheNtDllThunk().
  if (!mozilla::IsEafPlusEnabled()) {
    CacheNtDllThunk();
  }

  // NOTE: it's important that we initialize sProcessRuntime before showing a
  // window. Historically we ran into issues where showing the window would
  // cause an accessibility win event to fire, which could cause in-process
  // system or third party components to initialize COM and prevent us from
  // initializing it with important settings we need.

  // Some COM settings are global to the process and must be set before any non-
  // trivial COM is run in the application. Since these settings may affect
  // stability, we should instantiate COM ASAP so that we can ensure that these
  // global settings are configured before anything can interfere.
  sProcessRuntime = new mscom::ProcessRuntime(
      mscom::ProcessRuntime::ProcessCategory::GeckoBrowserParent);

  const TimeStamp skeletonStart = TimeStamp::Now();

  if (!IsWin10OrLater()) {
    return Err(PreXULSkeletonUIError::Ineligible);
  }

  HKEY regKey;
  MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
  AutoCloseRegKey closeKey(regKey);

  UniquePtr<wchar_t[]> binPath;
  MOZ_TRY_VAR(binPath, GetBinaryPath());

  std::wstring regProgressName =
      GetRegValueName(binPath.get(), sProgressSuffix);
  auto progressResult = ReadRegUint(regKey, regProgressName);
  if (!progressResult.isErr() &&
      progressResult.unwrap() !=
          static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed)) {
    return Err(PreXULSkeletonUIError::CrashedOnce);
  }

  MOZ_TRY(
      WriteRegUint(regKey, regProgressName,
                   static_cast<uint32_t>(PreXULSkeletonUIProgress::Started)));
  auto writeCompletion = MakeScopeExit([&] {
    Unused << WriteRegUint(
        regKey, regProgressName,
        static_cast<uint32_t>(PreXULSkeletonUIProgress::Completed));
  });

  MOZ_TRY(GetSkeletonUILock());

  bool explicitProfile = false;
  MOZ_TRY(ValidateCmdlineArguments(argc, argv, &explicitProfile));
  MOZ_TRY(ValidateEnvVars());

  auto enabledResult =
      ReadRegBool(regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix));
  if (enabledResult.isErr()) {
    return Err(PreXULSkeletonUIError::EnabledKeyDoesNotExist);
  }
  if (!enabledResult.unwrap()) {
    return Err(PreXULSkeletonUIError::Disabled);
  }
  sPreXULSkeletonUIEnabled = true;

  MOZ_ASSERT(!sAnimatedRects);
  sAnimatedRects = new Vector<ColorRect>();

  MOZ_TRY(LoadGdi32AndUser32Procedures());

  if (!explicitProfile) {
    MOZ_TRY(CheckForStartWithLastProfile());
  }

  WNDCLASSW wc;
  wc.style = CS_DBLCLKS;
  wc.lpfnWndProc = PreXULSkeletonUIProc;
  wc.cbClsExtra = 0;
  wc.cbWndExtra = 0;
  wc.hInstance = hInstance;
  wc.hIcon = sLoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
  wc.hCursor = sLoadCursorW(hInstance, gIDCWait);
  wc.hbrBackground = nullptr;
  wc.lpszMenuName = nullptr;

  // TODO: just ensure we disable this if we've overridden the window class
  wc.lpszClassName = L"MozillaWindowClass";

  if (!sRegisterClassW(&wc)) {
    return Err(PreXULSkeletonUIError::FailedRegisteringWindowClass);
  }

  uint32_t screenX;
  MOZ_TRY_VAR(screenX, ReadRegUint(regKey, GetRegValueName(binPath.get(),
                                                           sScreenXRegSuffix)));
  uint32_t screenY;
  MOZ_TRY_VAR(screenY, ReadRegUint(regKey, GetRegValueName(binPath.get(),
                                                           sScreenYRegSuffix)));
  uint32_t windowWidth;
  MOZ_TRY_VAR(
      windowWidth,
      ReadRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix)));
  uint32_t windowHeight;
  MOZ_TRY_VAR(
      windowHeight,
      ReadRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix)));
  MOZ_TRY_VAR(
      sMaximized,
      ReadRegBool(regKey, GetRegValueName(binPath.get(), sMaximizedRegSuffix)));
  MOZ_TRY_VAR(
      sCSSToDevPixelScaling,
      ReadRegDouble(regKey, GetRegValueName(binPath.get(),
                                            sCssToDevPixelScalingRegSuffix)));
  Vector<CSSPixelSpan> urlbar;
  MOZ_TRY_VAR(urlbar,
              ReadRegCSSPixelSpans(
                  regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix)));
  Vector<CSSPixelSpan> searchbar;
  MOZ_TRY_VAR(searchbar,
              ReadRegCSSPixelSpans(
                  regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix)));
  Vector<CSSPixelSpan> springs;
  MOZ_TRY_VAR(springs, ReadRegCSSPixelSpans(
                           regKey, GetRegValueName(binPath.get(),
                                                   sSpringsCSSRegSuffix)));

  if (urlbar.empty() || searchbar.empty()) {
    return Err(PreXULSkeletonUIError::CorruptData);
  }

  EnumSet<SkeletonUIFlag, uint32_t> flags;
  uint32_t flagsUint;
  MOZ_TRY_VAR(flagsUint, ReadRegUint(regKey, GetRegValueName(binPath.get(),
                                                             sFlagsRegSuffix)));
  flags.deserialize(flagsUint);

  if (flags.contains(SkeletonUIFlag::TouchDensity) ||
      flags.contains(SkeletonUIFlag::CompactDensity)) {
    return Err(PreXULSkeletonUIError::BadUIDensity);
  }

  uint32_t theme;
  MOZ_TRY_VAR(theme, ReadRegUint(regKey, GetRegValueName(binPath.get(),
                                                         sThemeRegSuffix)));
  ThemeMode themeMode = static_cast<ThemeMode>(theme);
  if (themeMode == ThemeMode::Default) {
    if (IsSystemDarkThemeEnabled() == true) {
      themeMode = ThemeMode::Dark;
    }
  }
  ThemeColors currentTheme = GetTheme(themeMode);

  if (!VerifyWindowDimensions(windowWidth, windowHeight)) {
    return Err(PreXULSkeletonUIError::BadWindowDimensions);
  }

  int showCmd = SW_SHOWNORMAL;
  DWORD windowStyle = kPreXULSkeletonUIWindowStyle;
  if (sMaximized) {
    showCmd = SW_SHOWMAXIMIZED;
    windowStyle |= WS_MAXIMIZE;
  }

  sPreXULSkeletonUIWindow =
      sCreateWindowExW(kPreXULSkeletonUIWindowStyleEx, L"MozillaWindowClass",
                       L"", windowStyle, screenX, screenY, windowWidth,
                       windowHeight, nullptr, nullptr, hInstance, nullptr);
  if (!sPreXULSkeletonUIWindow) {
    return Err(PreXULSkeletonUIError::CreateWindowFailed);
  }

  sShowWindow(sPreXULSkeletonUIWindow, showCmd);

  sDpi = sGetDpiForWindow(sPreXULSkeletonUIWindow);
  sNonClientHorizontalMargins =
      sGetSystemMetricsForDpi(SM_CXFRAME, sDpi) +
      sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);
  sNonClientVerticalMargins = sGetSystemMetricsForDpi(SM_CYFRAME, sDpi) +
                              sGetSystemMetricsForDpi(SM_CXPADDEDBORDER, sDpi);

  if (sMaximized) {
    HMONITOR monitor =
        sMonitorFromWindow(sPreXULSkeletonUIWindow, MONITOR_DEFAULTTONULL);
    if (!monitor) {
      // NOTE: we specifically don't clean up the window here. If we're unable
      // to finish setting up the window how we want it, we still need to keep
      // it around and consume it with the first real toplevel window we
      // create, to avoid flickering.
      return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
    }
    MONITORINFO mi = {sizeof(MONITORINFO)};
    if (!sGetMonitorInfoW(monitor, &mi)) {
      return Err(PreXULSkeletonUIError::FailedGettingMonitorInfo);
    }

    sWindowWidth =
        mi.rcWork.right - mi.rcWork.left + sNonClientHorizontalMargins * 2;
    sWindowHeight =
        mi.rcWork.bottom - mi.rcWork.top + sNonClientVerticalMargins * 2;
  } else {
    sWindowWidth = static_cast<int>(windowWidth);
    sWindowHeight = static_cast<int>(windowHeight);
  }

  sSetWindowPos(sPreXULSkeletonUIWindow, 0, 0, 0, 0, 0,
                SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
                    SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
  MOZ_TRY(DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbar[0], searchbar[0],
                         springs, currentTheme, flags));
  if (sAnimatedRects) {
    sPreXULSKeletonUIAnimationThread = ::CreateThread(
        nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
  }

  BASE_PROFILER_MARKER_UNTYPED(
      "CreatePreXULSkeletonUI", OTHER,
      MarkerTiming::IntervalUntilNowFrom(skeletonStart));

  return Ok();
}

void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance, int argc,
                                    char** argv) {
  auto result = CreateAndStorePreXULSkeletonUIImpl(hInstance, argc, argv);

  if (result.isErr()) {
    sErrorReason.emplace(result.unwrapErr());
  }
}

void CleanupProcessRuntime() {
  delete sProcessRuntime;
  sProcessRuntime = nullptr;
}

bool WasPreXULSkeletonUIMaximized() { return sMaximized; }

bool GetPreXULSkeletonUIWasShown() {
  return sPreXULSkeletonUIShown || !!sPreXULSkeletonUIWindow;
}

HWND ConsumePreXULSkeletonUIHandle() {
  // NOTE: we need to make sure that everything that runs here is a no-op if
  // it failed to be set, which is a possibility. If anything fails to be set
  // we don't want to clean everything up right away, because if we have a
  // blank window up, we want that to stick around and get consumed by nsWindow
  // as normal, otherwise the window will flicker in and out, which we imagine
  // is unpleasant.

  // If we don't get 1 here, it means the thread is actually just sleeping, so
  // we don't need to worry about giving out ownership of the window, because
  // the thread will simply exit after its sleep. However, if it is 1, we need
  // to wait for the thread to exit to be safe, as it could be doing anything.
  if (InterlockedIncrement(&sAnimationControlFlag) == 1) {
    ::WaitForSingleObject(sPreXULSKeletonUIAnimationThread, INFINITE);
  }
  ::CloseHandle(sPreXULSKeletonUIAnimationThread);
  sPreXULSKeletonUIAnimationThread = nullptr;
  HWND result = sPreXULSkeletonUIWindow;
  sPreXULSkeletonUIWindow = nullptr;
  free(sPixelBuffer);
  sPixelBuffer = nullptr;
  delete sAnimatedRects;
  sAnimatedRects = nullptr;

  return result;
}

Maybe<PreXULSkeletonUIError> GetPreXULSkeletonUIErrorReason() {
  return sErrorReason;
}

Result<Ok, PreXULSkeletonUIError> PersistPreXULSkeletonUIValues(
    const SkeletonUISettings& settings) {
  if (!sPreXULSkeletonUIEnabled) {
    return Err(PreXULSkeletonUIError::Disabled);
  }

  HKEY regKey;
  MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
  AutoCloseRegKey closeKey(regKey);

  UniquePtr<wchar_t[]> binPath;
  MOZ_TRY_VAR(binPath, GetBinaryPath());

  MOZ_TRY(WriteRegUint(regKey,
                       GetRegValueName(binPath.get(), sScreenXRegSuffix),
                       settings.screenX));
  MOZ_TRY(WriteRegUint(regKey,
                       GetRegValueName(binPath.get(), sScreenYRegSuffix),
                       settings.screenY));
  MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sWidthRegSuffix),
                       settings.width));
  MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sHeightRegSuffix),
                       settings.height));

  MOZ_TRY(WriteRegBool(regKey,
                       GetRegValueName(binPath.get(), sMaximizedRegSuffix),
                       settings.maximized));

  EnumSet<SkeletonUIFlag, uint32_t> flags;
  if (settings.menubarShown) {
    flags += SkeletonUIFlag::MenubarShown;
  }
  if (settings.bookmarksToolbarShown) {
    flags += SkeletonUIFlag::BookmarksToolbarShown;
  }
  if (settings.rtlEnabled) {
    flags += SkeletonUIFlag::RtlEnabled;
  }
  if (settings.uiDensity == SkeletonUIDensity::Touch) {
    flags += SkeletonUIFlag::TouchDensity;
  }
  if (settings.uiDensity == SkeletonUIDensity::Compact) {
    flags += SkeletonUIFlag::CompactDensity;
  }

  uint32_t flagsUint = flags.serialize();
  MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sFlagsRegSuffix),
                       flagsUint));

  MOZ_TRY(WriteRegDouble(
      regKey, GetRegValueName(binPath.get(), sCssToDevPixelScalingRegSuffix),
      settings.cssToDevPixelScaling));
  MOZ_TRY(WriteRegCSSPixelSpans(
      regKey, GetRegValueName(binPath.get(), sUrlbarCSSRegSuffix),
      &settings.urlbarSpan, 1));
  MOZ_TRY(WriteRegCSSPixelSpans(
      regKey, GetRegValueName(binPath.get(), sSearchbarRegSuffix),
      &settings.searchbarSpan, 1));
  MOZ_TRY(WriteRegCSSPixelSpans(
      regKey, GetRegValueName(binPath.get(), sSpringsCSSRegSuffix),
      settings.springs.begin(), settings.springs.length()));

  return Ok();
}

MFBT_API bool GetPreXULSkeletonUIEnabled() { return sPreXULSkeletonUIEnabled; }

MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIEnabledIfAllowed(
    bool value) {
  // If the pre-XUL skeleton UI was disallowed for some reason, we just want to
  // ignore changes to the registry. An example of how things could be bad if
  // we didn't: someone running firefox with the -profile argument could
  // turn the skeleton UI on or off for the default profile. Turning it off
  // maybe isn't so bad (though it's likely still incorrect), but turning it
  // on could be bad if the user had specifically disabled it for a profile for
  // some reason. Ultimately there's no correct decision here, and the
  // messiness of this is just a consequence of sharing the registry values
  // across profiles. However, whatever ill effects we observe should be
  // correct themselves after one session.
  if (PreXULSkeletonUIDisallowed()) {
    return Err(PreXULSkeletonUIError::Disabled);
  }

  HKEY regKey;
  MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
  AutoCloseRegKey closeKey(regKey);

  UniquePtr<wchar_t[]> binPath;
  MOZ_TRY_VAR(binPath, GetBinaryPath());
  MOZ_TRY(WriteRegBool(
      regKey, GetRegValueName(binPath.get(), sEnabledRegSuffix), value));

  if (!sPreXULSkeletonUIEnabled && value) {
    // We specifically don't care if we fail to get this lock. We just want to
    // do our best effort to lock it so that future instances don't create
    // skeleton UIs while we're still running, since they will immediately exit
    // and tell us to open a new window.
    Unused << GetSkeletonUILock();
  }

  sPreXULSkeletonUIEnabled = value;

  return Ok();
}

MFBT_API Result<Ok, PreXULSkeletonUIError> SetPreXULSkeletonUIThemeId(
    ThemeMode theme) {
  if (theme == sTheme) {
    return Ok();
  }
  sTheme = theme;

  // If we fail below, invalidate sTheme
  auto invalidateTheme = MakeScopeExit([] { sTheme = ThemeMode::Invalid; });

  HKEY regKey;
  MOZ_TRY_VAR(regKey, OpenPreXULSkeletonUIRegKey());
  AutoCloseRegKey closeKey(regKey);

  UniquePtr<wchar_t[]> binPath;
  MOZ_TRY_VAR(binPath, GetBinaryPath());
  MOZ_TRY(WriteRegUint(regKey, GetRegValueName(binPath.get(), sThemeRegSuffix),
                       static_cast<uint32_t>(theme)));

  invalidateTheme.release();
  return Ok();
}

MFBT_API void PollPreXULSkeletonUIEvents() {
  if (sPreXULSkeletonUIEnabled && sPreXULSkeletonUIWindow) {
    MSG outMsg = {};
    PeekMessageW(&outMsg, sPreXULSkeletonUIWindow, 0, 0, 0);
  }
}

Result<Ok, PreXULSkeletonUIError> NotePreXULSkeletonUIRestarting() {
  if (!sPreXULSkeletonUIEnabled) {
    return Err(PreXULSkeletonUIError::Disabled);
  }

  ::SetEnvironmentVariableW(L"MOZ_SKELETON_UI_RESTARTING", L"1");

  // We assume that we are going to exit the application very shortly after
  // this. It should thus be fine to release this lock, and we'll need to,
  // since during a restart we launch the new instance before closing this
  // one.
  if (sPreXULSKeletonUILockFile != INVALID_HANDLE_VALUE) {
    ::CloseHandle(sPreXULSKeletonUILockFile);
  }
  return Ok();
}

}  // namespace mozilla
