Bug 1665455 - Animate the Skeleton UI r=agashlin

For a better user experience during slow startups (and to match the design
by Markus Jaritz), we want to animate the placeholder elements (the grey
rectangles which hold the place of text and icons) with a light moving
gradient.

A question may arise regarding whether the performance cost of running this
animation is worth the improved user experience. My claim is yes, hinging
mostly on the observation that the performance cost is small.

On my machine, one frame of the animation takes at most 0.15ms, and runs
every 16.15ms. This means we eat less than 1% CPU time on one core of the
system. It should also be noted that this animation runs primarily during
the window wherein we are prefetching dlls, AKA while we are blocked on IO
and the CPU is more likely to be idle.

On slower systems, one frame may take longer - however, on slower systems
we should also be blocked *more* by IO, making the trade favorable.

For further anecdotal evidence of this being okay, when I run this on slow
reference hardware shortly after OS startup, I do not see any dropped frames,
indicating that this has very little trouble completing, and is thus quite
cheap.

Regarding testing, I will invoke the same logic as for the rest of the
skeleton UI patches - it would involve quite a bit of work to test this in
automation given our existing frameworks. It may be worth it at some point,
but certainly not while this is a feature that we are just experimenting
with and which is not enabled by default.

Differential Revision: https://phabricator.services.mozilla.com/D91453
This commit is contained in:
Doug Thayer 2020-10-29 19:03:59 +00:00
Родитель d11c215f61
Коммит 620dfb6899
1 изменённых файлов: 378 добавлений и 79 удалений

Просмотреть файл

@ -8,15 +8,55 @@
#include <algorithm>
#include <math.h>
#include <limits.h>
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/glue/Debug.h"
#include "mozilla/UniquePtr.h"
#include "mozilla/Vector.h"
#include "mozilla/WindowsDpiAwareness.h"
#include "mozilla/WindowsVersion.h"
#include "prthread.h"
namespace mozilla {
struct ColorRect {
uint32_t color;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
};
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";
@ -25,6 +65,16 @@ static bool sPreXULSkeletonUIEnabled = false;
static HWND sPreXULSkeletonUIWindow;
static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
static LPWSTR const gIDCWait = MAKEINTRESOURCEW(32514);
static HANDLE sPreXULSKeletonUIAnimationThread;
static uint32_t* sPixelBuffer = nullptr;
static Vector<ColorRect>* sAnimatedRects = nullptr;
static int sTotalChromeHeight = 0;
static volatile LONG sAnimationControlFlag = 0;
// Color values needed by the animation loop
static uint32_t sBackgroundColor;
static uint32_t sToolbarForegroundColor;
typedef BOOL(WINAPI* EnableNonClientDpiScalingProc)(HWND);
static EnableNonClientDpiScalingProc sEnableNonClientDpiScaling = NULL;
@ -84,6 +134,9 @@ static DWORD sWindowStyle = WS_POPUP;
// entry only so long as we are displaying a skeleton UI.
static DWORD sWindowStyleEx = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW;
static const int kAnimationCSSPixelsPerFrame = 21;
static const int kAnimationCSSExtraWindowSize = 300;
// 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:
@ -104,20 +157,8 @@ int CSSToDevPixels(int cssPixels, double scaling) {
return CSSToDevPixels((double)cssPixels, scaling);
}
struct ColorRect {
uint32_t color;
uint32_t x;
uint32_t y;
uint32_t width;
uint32_t height;
};
void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
double urlbarWidthCSS) {
if (!sGetSystemMetricsForDpi || !sGetDpiForWindow) {
return;
}
// 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?
//
@ -139,15 +180,18 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// manipulating raw pixels should not be *too* hard to maintain and
// understand so long as it is only painting such simple shapes.
// NOTE: these could be constants, but eventually they won't be, and they will
// need to be set here.
// --toolbar-non-lwt-bgcolor in browser.css
sBackgroundColor = 0xf9f9fa;
// We define this, but it will need to differ based on theme
sToolbarForegroundColor = 0xe5e5e5;
// found in browser-aero.css ":root[tabsintitlebar]:not(:-moz-lwtheme)"
// (set to "hsl(235,33%,19%)")
uint32_t tabBarColor = 0x202340;
// --toolbar-non-lwt-bgcolor in browser.css
uint32_t backgroundColor = 0xf9f9fa;
// --chrome-content-separator-color in browser.css
uint32_t chromeContentDividerColor = 0xe2e1e3;
// We define this, but it will need to differ based on theme
uint32_t toolbarForegroundColor = 0xe5e5e5;
// controlled by css variable --tab-line-color
uint32_t tabLineColor = 0x0a75d3;
// controlled by css variable --toolbar-color
@ -208,7 +252,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// The initial selected tab
ColorRect selectedTab = {};
selectedTab.color = backgroundColor;
selectedTab.color = sBackgroundColor;
selectedTab.x = titlebarSpacerWidth;
selectedTab.y = tabLineHeight;
selectedTab.width = selectedTabWidth;
@ -216,7 +260,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// A placeholder rect representing text that will fill the selected tab title
ColorRect tabTextPlaceholder = {};
tabTextPlaceholder.color = toolbarForegroundColor;
tabTextPlaceholder.color = sToolbarForegroundColor;
tabTextPlaceholder.x = selectedTab.x + tabPlaceholderBarMarginLeft;
tabTextPlaceholder.y = selectedTab.y + tabPlaceholderBarMarginTop;
tabTextPlaceholder.width = tabPlaceholderBarWidth;
@ -224,7 +268,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// The toolbar background
ColorRect toolbar = {};
toolbar.color = backgroundColor;
toolbar.color = sBackgroundColor;
toolbar.x = 0;
toolbar.y = tabBarHeight;
toolbar.width = sWindowWidth;
@ -233,7 +277,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// A placeholder rect representing UI elements that will fill the left part
// of the toolbar
ColorRect leftToolbarPlaceholder = {};
leftToolbarPlaceholder.color = toolbarForegroundColor;
leftToolbarPlaceholder.color = sToolbarForegroundColor;
leftToolbarPlaceholder.x =
toolbar.x + toolbarPlaceholderMarginLeft + horizontalOffset;
leftToolbarPlaceholder.y = toolbar.y + toolbarPlaceholderMarginTop;
@ -243,7 +287,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// A placeholder rect representing UI elements that will fill the right part
// of the toolbar
ColorRect rightToolbarPlaceholder = {};
rightToolbarPlaceholder.color = toolbarForegroundColor;
rightToolbarPlaceholder.color = sToolbarForegroundColor;
rightToolbarPlaceholder.x = sWindowWidth - horizontalOffset -
toolbarPlaceholderMarginRight -
toolbarPlaceholderWidth;
@ -271,7 +315,7 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
// The urlbar placeholder rect representating text that will fill the urlbar
// The placeholder rects should all be y-aligned.
ColorRect urlbarTextPlaceholder = {};
urlbarTextPlaceholder.color = toolbarForegroundColor;
urlbarTextPlaceholder.color = sToolbarForegroundColor;
urlbarTextPlaceholder.x = urlbar.x + urlbarTextPlaceholderMarginLeft;
// This is equivalent to rightToolbarPlaceholder.y and
// leftToolbarPlaceholder.y
@ -292,17 +336,26 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
urlbarTextPlaceholder,
};
int totalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
if (!sAnimatedRects->append(tabTextPlaceholder) ||
!sAnimatedRects->append(leftToolbarPlaceholder) ||
!sAnimatedRects->append(rightToolbarPlaceholder) ||
!sAnimatedRects->append(urlbarTextPlaceholder)) {
delete sAnimatedRects;
sAnimatedRects = nullptr;
return;
}
uint32_t* pixelBuffer =
(uint32_t*)calloc(sWindowWidth * totalChromeHeight, sizeof(uint32_t));
sTotalChromeHeight = chromeContentDivider.y + chromeContentDivider.height;
sPixelBuffer =
(uint32_t*)calloc(sWindowWidth * sTotalChromeHeight, sizeof(uint32_t));
for (int i = 0; i < sizeof(rects) / sizeof(rects[0]); ++i) {
ColorRect rect = rects[i];
for (int y = rect.y; y < rect.y + rect.height; ++y) {
uint32_t* lineStart = &pixelBuffer[y * sWindowWidth];
uint32_t* lineStart = &sPixelBuffer[y * sWindowWidth];
uint32_t* dataStart = lineStart + rect.x;
std::fill(dataStart, dataStart + rect.width, rect.color);
std::fill_n(dataStart, rect.width, rect.color);
}
}
@ -311,28 +364,195 @@ void DrawSkeletonUI(HWND hWnd, double urlbarHorizontalOffsetCSS,
BITMAPINFO chromeBMI = {};
chromeBMI.bmiHeader.biSize = sizeof(chromeBMI.bmiHeader);
chromeBMI.bmiHeader.biWidth = sWindowWidth;
chromeBMI.bmiHeader.biHeight = -totalChromeHeight;
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
sStretchDIBits(hdc, 0, 0, sWindowWidth, totalChromeHeight, 0, 0, sWindowWidth,
totalChromeHeight, pixelBuffer, &chromeBMI, DIB_RGB_COLORS,
SRCCOPY);
sStretchDIBits(hdc, 0, 0, sWindowWidth, sTotalChromeHeight, 0, 0,
sWindowWidth, sTotalChromeHeight, sPixelBuffer, &chromeBMI,
DIB_RGB_COLORS, SRCCOPY);
// Then, we just fill the rest with FillRect
RECT rect = {0, totalChromeHeight, (LONG)sWindowWidth, (LONG)sWindowHeight};
HBRUSH brush = sCreateSolidBrush(backgroundColor);
RECT rect = {0, sTotalChromeHeight, (LONG)sWindowWidth, (LONG)sWindowHeight};
HBRUSH brush = sCreateSolidBrush(sBackgroundColor);
sFillRect(hdc, &rect, brush);
sReleaseDC(hWnd, hdc);
free(pixelBuffer);
sDeleteObject(brush);
}
DWORD WINAPI AnimateSkeletonUI(void* aUnused) {
if (!sPixelBuffer || sAnimatedRects->empty()) {
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 = sBackgroundColor;
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) {
int rectMin = rect.x;
int rectMax = rect.x + rect.width;
bool animationWindowOverlaps =
rectMax >= priorAnimationMin && rectMin < animationMax;
int priorUpdateAreaMin = std::max(rectMin, priorAnimationMin);
int currentUpdateAreaMin = std::max(rectMin, animationMin);
int priorUpdateAreaMax = std::min(rectMax, animationMin);
int currentUpdateAreaMax = std::min(rectMax, animationMax);
if (animationWindowOverlaps) {
updatedAnything = true;
for (int y = rect.y; y < rect.y + rect.height; ++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] = rect.color;
}
// Then apply the animated color
for (int x = currentUpdateAreaMin; x < currentUpdateAreaMax; ++x) {
lineStart[x] = animationLookup[x - animationMin];
}
}
}
}
if (updatedAnything) {
HDC hdc = sGetWindowDC(sPreXULSkeletonUIWindow);
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;
}
}
return 0;
}
LRESULT WINAPI PreXULSkeletonUIProc(HWND hWnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
@ -364,6 +584,100 @@ bool OpenPreXULSkeletonUIRegKey(HKEY& key) {
return false;
}
bool LoadGdi32AndUser32Procedures() {
HMODULE user32Dll = ::LoadLibraryW(L"user32");
HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
if (!user32Dll || !gdi32Dll) {
return false;
}
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 false;
}
sGetDpiForWindow =
(GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
if (!sGetDpiForWindow) {
return false;
}
sRegisterClassW =
(RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
if (!sRegisterClassW) {
return false;
}
sCreateWindowExW =
(CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
if (!sCreateWindowExW) {
return false;
}
sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
if (!sShowWindow) {
return false;
}
sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
if (!sSetWindowPos) {
return false;
}
sRedrawWindow = (RedrawWindowProc)::GetProcAddress(user32Dll, "RedrawWindow");
if (!sRedrawWindow) {
return false;
}
sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
if (!sGetWindowDC) {
return false;
}
sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
if (!sFillRect) {
return false;
}
sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
if (!sReleaseDC) {
return false;
}
sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
if (!sLoadIconW) {
return false;
}
sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
if (!sLoadCursorW) {
return false;
}
sStretchDIBits =
(StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
if (!sStretchDIBits) {
return false;
}
sCreateSolidBrush =
(CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
if (!sCreateSolidBrush) {
return false;
}
sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
if (!sDeleteObject) {
return false;
}
return true;
}
void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance) {
HKEY regKey;
if (!IsWin10OrLater() || !OpenPreXULSkeletonUIRegKey(regKey)) {
@ -381,53 +695,13 @@ void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance) {
}
sPreXULSkeletonUIEnabled = true;
// EnableNonClientDpiScaling must be called during the initialization of
// the window, so we have to find it and store it before we create our
// window in order to run it in our WndProc.
HMODULE user32Dll = ::LoadLibraryW(L"user32");
HMODULE gdi32Dll = ::LoadLibraryW(L"gdi32");
MOZ_ASSERT(!sAnimatedRects);
sAnimatedRects = new Vector<ColorRect>();
if (!user32Dll || !gdi32Dll) {
if (!LoadGdi32AndUser32Procedures()) {
return;
}
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)) {
// Only per-monitor v1 requires these workarounds.
sEnableNonClientDpiScaling =
(EnableNonClientDpiScalingProc)::GetProcAddress(
user32Dll, "EnableNonClientDpiScaling");
}
sGetSystemMetricsForDpi = (GetSystemMetricsForDpiProc)::GetProcAddress(
user32Dll, "GetSystemMetricsForDpi");
sGetDpiForWindow =
(GetDpiForWindowProc)::GetProcAddress(user32Dll, "GetDpiForWindow");
sRegisterClassW =
(RegisterClassWProc)::GetProcAddress(user32Dll, "RegisterClassW");
sCreateWindowExW =
(CreateWindowExWProc)::GetProcAddress(user32Dll, "CreateWindowExW");
sShowWindow = (ShowWindowProc)::GetProcAddress(user32Dll, "ShowWindow");
sSetWindowPos = (SetWindowPosProc)::GetProcAddress(user32Dll, "SetWindowPos");
sRedrawWindow = (RedrawWindowProc)::GetProcAddress(user32Dll, "RedrawWindow");
sGetWindowDC = (GetWindowDCProc)::GetProcAddress(user32Dll, "GetWindowDC");
sFillRect = (FillRectProc)::GetProcAddress(user32Dll, "FillRect");
sDeleteObject = (DeleteObjectProc)::GetProcAddress(gdi32Dll, "DeleteObject");
sReleaseDC = (ReleaseDCProc)::GetProcAddress(user32Dll, "ReleaseDC");
sLoadIconW = (LoadIconWProc)::GetProcAddress(user32Dll, "LoadIconW");
sLoadCursorW = (LoadCursorWProc)::GetProcAddress(user32Dll, "LoadCursorW");
sStretchDIBits =
(StretchDIBitsProc)::GetProcAddress(gdi32Dll, "StretchDIBits");
sCreateSolidBrush =
(CreateSolidBrushProc)::GetProcAddress(gdi32Dll, "CreateSolidBrush");
WNDCLASSW wc;
wc.style = CS_DBLCLKS;
wc.lpfnWndProc = PreXULSkeletonUIProc;
@ -517,11 +791,36 @@ void CreateAndStorePreXULSkeletonUI(HINSTANCE hInstance) {
DrawSkeletonUI(sPreXULSkeletonUIWindow, urlbarHorizontalOffsetCSS,
urlbarWidthCSS);
sRedrawWindow(sPreXULSkeletonUIWindow, NULL, NULL, RDW_INVALIDATE);
if (sAnimatedRects) {
sPreXULSKeletonUIAnimationThread = ::CreateThread(
nullptr, 256 * 1024, AnimateSkeletonUI, nullptr, 0, nullptr);
}
}
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;
}