Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/mozglue/misc/   (Browser von der Mozilla Stiftung Version 136.0.1©)  Datei vom 10.2.2025 mit Größe 85 kB image not shown  

Quelle  PreXULSkeletonUI.cpp   Sprache: C

 
/* -*- 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 <dwmapi.h>
#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/Try.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/UniquePtrExtensions.h"
#include "mozilla/Unused.h"
#include "mozilla/WindowsDpiAwareness.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); }

struct Margin {
  int top = 0;
  int right = 0;
  int bottom = 0;
  int left = 0;
};

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 uint32_t sDpi = 0;
// See nsWindow::mNonClientOffset
static Margin sNonClientOffset;
static int sCaptionHeight = 0;
static int sHorizontalResizeMargin = 0;
static int sVerticalResizeMargin = 0;

// See nsWindow::NonClientSizeMargin()
static Margin NonClientSizeMargin() {
  return Margin{sCaptionHeight + sVerticalResizeMargin - sNonClientOffset.top,
                sHorizontalResizeMargin - sNonClientOffset.right,
                sVerticalResizeMargin - sNonClientOffset.bottom,
                sHorizontalResizeMargin - sNonClientOffset.left};
}

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

static ThemeMode sTheme = ThemeMode::Invalid;

#define MOZ_DECL_IMPORTED_WIN32_FN(name) \
  static decltype(&::name) s##name = nullptr
MOZ_DECL_IMPORTED_WIN32_FN(EnableNonClientDpiScaling);
MOZ_DECL_IMPORTED_WIN32_FN(GetSystemMetricsForDpi);
MOZ_DECL_IMPORTED_WIN32_FN(GetDpiForWindow);
MOZ_DECL_IMPORTED_WIN32_FN(RegisterClassW);
MOZ_DECL_IMPORTED_WIN32_FN(LoadIconW);
MOZ_DECL_IMPORTED_WIN32_FN(LoadCursorW);
MOZ_DECL_IMPORTED_WIN32_FN(CreateWindowExW);
MOZ_DECL_IMPORTED_WIN32_FN(ShowWindow);
MOZ_DECL_IMPORTED_WIN32_FN(SetWindowPos);
MOZ_DECL_IMPORTED_WIN32_FN(GetWindowDC);
MOZ_DECL_IMPORTED_WIN32_FN(GetWindowRect);
MOZ_DECL_IMPORTED_WIN32_FN(MapWindowPoints);
MOZ_DECL_IMPORTED_WIN32_FN(FillRect);
MOZ_DECL_IMPORTED_WIN32_FN(DeleteObject);
MOZ_DECL_IMPORTED_WIN32_FN(ReleaseDC);
MOZ_DECL_IMPORTED_WIN32_FN(MonitorFromWindow);
MOZ_DECL_IMPORTED_WIN32_FN(GetMonitorInfoW);
MOZ_DECL_IMPORTED_WIN32_FN(SetWindowLongPtrW);
MOZ_DECL_IMPORTED_WIN32_FN(StretchDIBits);
MOZ_DECL_IMPORTED_WIN32_FN(CreateSolidBrush);
MOZ_DECL_IMPORTED_WIN32_FN(DwmGetWindowAttribute);
MOZ_DECL_IMPORTED_WIN32_FN(DwmSetWindowAttribute);
#undef MOZ_DECL_IMPORTED_WIN32_FN

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;
}

bool FillRectWithColor(HDC hdc, LPCRECT rect, uint32_t mozColor) {
  HBRUSH brush = sCreateSolidBrush(RGB((mozColor & 0xff0000) >> 16,
                                       (mozColor & 0x00ff00) >> 8,
                                       (mozColor & 0x0000ff) >> 0));
  int fillRectResult = sFillRect(hdc, rect, brush);

  sDeleteObject(brush);

  return !!fillRectResult;
}

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 verticalTabs = flags.contains(SkeletonUIFlag::VerticalTabs);
  bool bookmarksToolbarShown =
      flags.contains(SkeletonUIFlag::BookmarksToolbarShown);
  bool rtlEnabled = flags.contains(SkeletonUIFlag::RtlEnabled);

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

  // found in tabs.inc.css, "--tab-min-height" + 2 * "--tab-block-margin"
  int tabBarHeight =
      verticalTabs ? 0 : 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.titlebarColor;
  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.titlebarColor;
  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);
  }

  if (!verticalTabs) {
    // 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);
    }

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

  // The toolbar background
  ColorRect toolbar = {};
  // In the vertical tabs case the main toolbar is in the titlebar:
  toolbar.color =
      verticalTabs ? currentTheme.titlebarColor : 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(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};
  bool const fillRectOk =
      FillRectWithColor(hdc, &rect, currentTheme.backgroundColor);

  if (!fillRectOk) {
    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));

    Margin margin = NonClientSizeMargin();
    clientRect->top += margin.top;
    clientRect->left += margin.left;
    clientRect->right -= margin.right;
    clientRect->bottom -= margin.bottom;

    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.titlebarColor = 0x1c1b22;
      // controlled by --toolbar-color 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-bgcolor in browser.css
      theme.backgroundColor = 0xf9f9fb;
      theme.tabColor = 0xf9f9fb;
      theme.toolbarForegroundColor = 0xdddde1;
      theme.tabOutlineColor = 0xdddde1;
      // found in browser-aero.css ":root[customtitlebar]:not(:-moz-lwtheme)"
      // (set to "hsl(235,33%,19%)")
      theme.titlebarColor = 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");
  HMODULE dwmapiDll = ::LoadLibraryW(L"dwmapi.dll");

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

#define MOZ_LOAD_OR_FAIL(dll_handle, name)                            \
  do {                                                                \
    s##name = (decltype(&::name))::GetProcAddress(dll_handle, #name); \
    if (!s##name) {                                                   \
      return Err(PreXULSkeletonUIError::FailedLoadingDynamicProcs);   \
    }                                                                 \
  } while (0)

  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 first available in Win10 Build 1607, but
    // it's optional - we can handle not having it.
    Unused << [&]() -> Result<Ok, PreXULSkeletonUIError> {
      MOZ_LOAD_OR_FAIL(user32Dll, EnableNonClientDpiScaling);
      return Ok{};
    }();
  }

  MOZ_LOAD_OR_FAIL(user32Dll, GetSystemMetricsForDpi);
  MOZ_LOAD_OR_FAIL(user32Dll, GetDpiForWindow);
  MOZ_LOAD_OR_FAIL(user32Dll, RegisterClassW);
  MOZ_LOAD_OR_FAIL(user32Dll, CreateWindowExW);
  MOZ_LOAD_OR_FAIL(user32Dll, ShowWindow);
  MOZ_LOAD_OR_FAIL(user32Dll, SetWindowPos);
  MOZ_LOAD_OR_FAIL(user32Dll, GetWindowDC);
  MOZ_LOAD_OR_FAIL(user32Dll, GetWindowRect);
  MOZ_LOAD_OR_FAIL(user32Dll, MapWindowPoints);
  MOZ_LOAD_OR_FAIL(user32Dll, FillRect);
  MOZ_LOAD_OR_FAIL(user32Dll, ReleaseDC);
  MOZ_LOAD_OR_FAIL(user32Dll, LoadIconW);
  MOZ_LOAD_OR_FAIL(user32Dll, LoadCursorW);
  MOZ_LOAD_OR_FAIL(user32Dll, MonitorFromWindow);
  MOZ_LOAD_OR_FAIL(user32Dll, GetMonitorInfoW);
  MOZ_LOAD_OR_FAIL(user32Dll, SetWindowLongPtrW);
  MOZ_LOAD_OR_FAIL(gdi32Dll, StretchDIBits);
  MOZ_LOAD_OR_FAIL(gdi32Dll, CreateSolidBrush);
  MOZ_LOAD_OR_FAIL(gdi32Dll, DeleteObject);
  MOZ_LOAD_OR_FAIL(dwmapiDll, DwmGetWindowAttribute);
  MOZ_LOAD_OR_FAIL(dwmapiDll, DwmSetWindowAttribute);

#undef MOZ_LOAD_OR_FAIL

  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,
--> --------------------

--> maximum size reached

--> --------------------

Messung V0.5
C=82 H=97 G=89

¤ Dauer der Verarbeitung: 0.21 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

Beweissystem der NASA

Beweissystem Isabelle

NIST Cobol Testsuite

Cephes Mathematical Library

Wiener Entwicklungsmethode

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.