Bug 1656526 - Show blank window prior to loading xul on Windows r=mhowell

See bug for justification. This patch aims to display a blank window prior to
loading/prefetching xul.dll. It also has a placeholder for drawing a
skeleton UI into that window. Note that this is disabled by default based on
a registry value, as there are still kinks to work out (for instance, what
happens if we aren't actually going to display a window, because, say, Firefox
is already running.) This just gives a basic implementation to dogfood, and
facilitates distributing work across multiple contributors.

Onto the details. The patch achieves its goal by creating a window and
assigning its handle to a static variable, which will be consumed inside
nsWindow::Create by the first toplevel window we want to make. nsWindow::Create
will take ownership of the window handle, restyle it to its own liking, and
then proceed as if everything is normal and it had created the window itself.

Differential Revision: https://phabricator.services.mozilla.com/D86263
This commit is contained in:
Doug Thayer 2020-09-11 14:12:00 +00:00
Родитель b3e1b43263
Коммит efe700e222
7 изменённых файлов: 391 добавлений и 9 удалений

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

@ -11,6 +11,7 @@
#if defined(XP_WIN)
# include <windows.h>
# include <stdlib.h>
# include "mozilla/EarlyBlankWindow.h"
#elif defined(XP_UNIX)
# include <sys/resource.h>
# include <unistd.h>
@ -311,6 +312,10 @@ int main(int argc, char* argv[], char* envp[]) {
return result;
}
# if defined(XP_WIN)
mozilla::CreateAndStoreEarlyBlankWindow(GetModuleHandle(nullptr));
# endif
#endif
#ifdef HAS_DLL_BLOCKLIST

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

@ -0,0 +1,282 @@
/* -*- 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 "EarlyBlankWindow.h"
#include "mozilla/Assertions.h"
#include "mozilla/Attributes.h"
#include "mozilla/glue/Debug.h"
#include "mozilla/WindowsVersion.h"
namespace mozilla {
static const wchar_t kEarlyBlankWindowKeyPath[] =
L"SOFTWARE"
L"\\" MOZ_APP_VENDOR L"\\" MOZ_APP_BASENAME L"\\EarlyBlankWindowSettings";
static bool sEarlyBlankWindowEnabled = false;
static HWND sEarlyBlankWindowHandle;
static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
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;
#if WINVER < 0x0605
WINUSERAPI DPI_AWARENESS_CONTEXT WINAPI GetThreadDpiAwarenessContext();
WINUSERAPI BOOL WINAPI AreDpiAwarenessContextsEqual(DPI_AWARENESS_CONTEXT,
DPI_AWARENESS_CONTEXT);
#endif /* WINVER < 0x0605 */
static uint32_t sWindowWidth;
static uint32_t sWindowHeight;
static double sCSSToDevPixelScaling;
// We style our initial blank window as a WS_POPUP to eliminate the window
// caption and all that jazz. Alternatively, we could do a big dance in our
// window proc to paint into the nonclient area similarly to what we do in
// nsWindow, but it would be nontrivial code duplication, and the added
// complexity would not be worth it, given that we can just change the
// window style to our liking when we consume sEarlyBlankWindowHandle from
// nsWindow.
static DWORD sWindowStyle = WS_POPUP;
// We add WS_EX_TOOLWINDOW here so that we do not produce a toolbar entry.
// We were not able to avoid flickering in the toolbar without this change,
// as the call to ::SetWindowLongPtrW to restyle the window inside
// nsWindow causes the toolbar entry to momentarily disappear. Not sure of
// the cause of this, but it doesn't feel too wrong to be missing a toolbar
// entry only so long as we are displaying a skeleton UI.
static DWORD sWindowStyleEx = WS_EX_WINDOWEDGE | WS_EX_TOOLWINDOW;
// 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(int cssPixels, double scaling) {
double asDouble = (double)cssPixels * scaling;
return floor(asDouble + 0.5);
}
void DrawSkeletonUI(HWND hWnd) {
// TODO - here is where we will draw a skeleton UI for the application
}
LRESULT WINAPI EarlyBlankWindowProc(HWND hWnd, UINT msg, WPARAM wParam,
LPARAM lParam) {
if (msg == WM_NCCREATE && sEnableNonClientDpiScaling) {
sEnableNonClientDpiScaling(hWnd);
}
return ::DefWindowProcW(hWnd, msg, wParam, lParam);
}
bool OpenEarlyBlankWindowRegKey(HKEY& key) {
DWORD disposition;
LSTATUS result =
::RegCreateKeyExW(HKEY_CURRENT_USER, kEarlyBlankWindowKeyPath, 0, nullptr,
0, KEY_ALL_ACCESS, nullptr, &key, &disposition);
if (result != ERROR_SUCCESS) {
return false;
}
if (disposition == REG_CREATED_NEW_KEY) {
return false;
}
if (disposition == REG_OPENED_EXISTING_KEY) {
return true;
}
::RegCloseKey(key);
return false;
}
void CreateAndStoreEarlyBlankWindow(HINSTANCE hInstance) {
HKEY regKey;
if (!IsWin10OrLater() || !OpenEarlyBlankWindowRegKey(regKey)) {
return;
}
AutoCloseRegKey closeKey(regKey);
DWORD dataLen = sizeof(uint32_t);
uint32_t enabled;
LSTATUS result =
::RegGetValueW(regKey, nullptr, L"enabled", RRF_RT_REG_DWORD, nullptr,
reinterpret_cast<PBYTE>(&enabled), &dataLen);
if (result != ERROR_SUCCESS || enabled == 0) {
return;
}
sEarlyBlankWindowEnabled = 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 = ::GetModuleHandleW(L"user32");
if (user32Dll) {
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");
}
WNDCLASSW wc;
wc.style = CS_DBLCLKS;
wc.lpfnWndProc = EarlyBlankWindowProc;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = hInstance;
wc.hIcon = ::LoadIconW(::GetModuleHandleW(nullptr), gStockApplicationIcon);
wc.hCursor = ::LoadCursor(nullptr, IDC_WAIT);
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
// TODO: just ensure we disable this if we've overridden the window class
wc.lpszClassName = L"MozillaWindowClass";
if (!::RegisterClassW(&wc)) {
printf_stderr("RegisterClassW error %lu\n", GetLastError());
return;
}
uint32_t screenX;
result = ::RegGetValueW(regKey, nullptr, L"screenX", RRF_RT_REG_DWORD,
nullptr, reinterpret_cast<PBYTE>(&screenX), &dataLen);
if (result != ERROR_SUCCESS) {
printf_stderr("Error reading screenX %lu\n", GetLastError());
return;
}
uint32_t screenY;
result = ::RegGetValueW(regKey, nullptr, L"screenY", RRF_RT_REG_DWORD,
nullptr, reinterpret_cast<PBYTE>(&screenY), &dataLen);
if (result != ERROR_SUCCESS) {
printf_stderr("Error reading screenY %lu\n", GetLastError());
return;
}
result = ::RegGetValueW(regKey, nullptr, L"width", RRF_RT_REG_DWORD, nullptr,
reinterpret_cast<PBYTE>(&sWindowWidth), &dataLen);
if (result != ERROR_SUCCESS) {
printf_stderr("Error reading width %lu\n", GetLastError());
return;
}
result = ::RegGetValueW(regKey, nullptr, L"height", RRF_RT_REG_DWORD, nullptr,
reinterpret_cast<PBYTE>(&sWindowHeight), &dataLen);
if (result != ERROR_SUCCESS) {
printf_stderr("Error reading height %lu\n", GetLastError());
return;
}
dataLen = sizeof(double);
result = ::RegGetValueW(
regKey, nullptr, L"cssToDevPixelScaling", RRF_RT_REG_BINARY, nullptr,
reinterpret_cast<PBYTE>(&sCSSToDevPixelScaling), &dataLen);
if (result != ERROR_SUCCESS || dataLen != sizeof(double)) {
printf_stderr("Error reading cssToDevPixelScaling %lu\n", GetLastError());
return;
}
sEarlyBlankWindowHandle =
::CreateWindowExW(sWindowStyleEx, L"MozillaWindowClass", L"",
sWindowStyle, screenX, screenY, sWindowWidth,
sWindowHeight, nullptr, nullptr, hInstance, nullptr);
::ShowWindow(sEarlyBlankWindowHandle, SW_SHOWNORMAL);
::SetWindowPos(sEarlyBlankWindowHandle, 0, 0, 0, 0, 0,
SWP_FRAMECHANGED | SWP_NOACTIVATE | SWP_NOMOVE |
SWP_NOOWNERZORDER | SWP_NOSIZE | SWP_NOZORDER);
DrawSkeletonUI(sEarlyBlankWindowHandle);
::RedrawWindow(sEarlyBlankWindowHandle, NULL, NULL, RDW_INVALIDATE);
}
HWND ConsumeEarlyBlankWindowHandle() {
HWND result = sEarlyBlankWindowHandle;
sEarlyBlankWindowHandle = nullptr;
return result;
}
void PersistEarlyBlankWindowValues(int screenX, int screenY, int width,
int height, double cssToDevPixelScaling) {
if (!sEarlyBlankWindowEnabled) {
return;
}
HKEY regKey;
if (!OpenEarlyBlankWindowRegKey(regKey)) {
return;
}
AutoCloseRegKey closeKey(regKey);
LSTATUS result;
result = ::RegSetValueExW(regKey, L"screenX", 0, REG_DWORD,
reinterpret_cast<PBYTE>(&screenX), sizeof(screenX));
if (result != ERROR_SUCCESS) {
printf_stderr("Failed persisting screenX to Windows registry\n");
return;
}
result = ::RegSetValueExW(regKey, L"screenY", 0, REG_DWORD,
reinterpret_cast<PBYTE>(&screenY), sizeof(screenY));
if (result != ERROR_SUCCESS) {
printf_stderr("Failed persisting screenY to Windows registry\n");
return;
}
result = ::RegSetValueExW(regKey, L"width", 0, REG_DWORD,
reinterpret_cast<PBYTE>(&width), sizeof(width));
if (result != ERROR_SUCCESS) {
printf_stderr("Failed persisting width to Windows registry\n");
return;
}
result = ::RegSetValueExW(regKey, L"height", 0, REG_DWORD,
reinterpret_cast<PBYTE>(&height), sizeof(height));
if (result != ERROR_SUCCESS) {
printf_stderr("Failed persisting height to Windows registry\n");
return;
}
result = ::RegSetValueExW(regKey, L"cssToDevPixelScaling", 0, REG_BINARY,
reinterpret_cast<PBYTE>(&cssToDevPixelScaling),
sizeof(cssToDevPixelScaling));
if (result != ERROR_SUCCESS) {
printf_stderr(
"Failed persisting cssToDevPixelScaling to Windows registry\n");
return;
}
}
} // namespace mozilla

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

@ -0,0 +1,23 @@
/* -*- 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 https://mozilla.org/MPL/2.0/. */
#ifndef EarlyBlankWindow_h_
#define EarlyBlankWindow_h_
#include <windows.h>
#include "mozilla/Types.h"
namespace mozilla {
MFBT_API void CreateAndStoreEarlyBlankWindow(HINSTANCE hInstance);
MFBT_API HWND ConsumeEarlyBlankWindowHandle();
MFBT_API void PersistEarlyBlankWindowValues(int screenX, int screenY, int width,
int height,
double cssToDevPixelScaling);
} // namespace mozilla
#endif

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

@ -25,6 +25,7 @@ EXPORTS.mozilla.glue += [
if CONFIG['OS_ARCH'] == 'WINNT':
EXPORTS.mozilla += [
'EarlyBlankWindow.h',
'StackWalk_windows.h',
'TimeStamp_windows.h',
]
@ -57,6 +58,7 @@ if CONFIG['OS_ARCH'] == 'WINNT':
'WindowsUnicode.h',
]
SOURCES += [
'EarlyBlankWindow.cpp',
'TimeStamp_windows.cpp',
'WindowsMapRemoteView.cpp',
'WindowsProcessMitigations.cpp',
@ -97,3 +99,7 @@ SOURCES += [
if CONFIG['CC_TYPE'] == 'clang':
# Suppress warnings from third-party V8 Decimal code.
SOURCES['decimal/Decimal.cpp'].flags += ['-Wno-implicit-fallthrough']
for var in ('MOZ_APP_BASENAME', 'MOZ_APP_VENDOR'):
DEFINES[var] = '"%s"' % CONFIG[var]

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

@ -60,6 +60,7 @@
#include "mozilla/AppShutdown.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/EarlyBlankWindow.h"
#include "mozilla/Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MiscEvents.h"
@ -889,9 +890,21 @@ nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
aInitData->mWindowType == eWindowType_plugin_ipc_content) {
style |= WS_DISABLED;
}
mWnd = ::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
parent, nullptr, nsToolkit::mDllInstance, nullptr);
if (aInitData->mWindowType == eWindowType_toplevel && !aParent) {
mWnd = ConsumeEarlyBlankWindowHandle();
if (mWnd) {
::SetWindowLongPtrW(mWnd, GWL_STYLE, style);
::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, extendedStyle);
}
}
if (!mWnd) {
mWnd =
::CreateWindowExW(extendedStyle, className, L"", style, aRect.X(),
aRect.Y(), aRect.Width(), GetHeight(aRect.Height()),
parent, nullptr, nsToolkit::mDllInstance, nullptr);
}
if (!mWnd) {
NS_WARNING("nsWindow CreateWindowEx failed.");

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

@ -62,6 +62,10 @@
#include "mozilla/dom/LoadURIOptionsBinding.h"
#include "mozilla/EventDispatcher.h"
#ifdef XP_WIN
# include "mozilla/EarlyBlankWindow.h"
#endif
#ifdef MOZ_NEW_XULSTORE
# include "mozilla/XULStore.h"
#endif
@ -1744,18 +1748,17 @@ nsresult AppWindow::GetPersistentValue(const nsAtom* aAttr, nsAString& aValue) {
return NS_OK;
}
nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr,
const nsAString& aValue) {
nsresult AppWindow::GetDocXulStoreKeys(nsString& aUriSpec,
nsString& aWindowElementId) {
nsCOMPtr<dom::Element> docShellElement = GetWindowDOMElement();
if (!docShellElement) {
return NS_ERROR_FAILURE;
}
nsAutoString windowElementId;
docShellElement->GetId(windowElementId);
docShellElement->GetId(aWindowElementId);
// Match the behavior of XULPersist and only persist values if the element
// has an ID.
if (windowElementId.IsEmpty()) {
if (aWindowElementId.IsEmpty()) {
return NS_OK;
}
@ -1770,7 +1773,45 @@ nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr,
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
NS_ConvertUTF8toUTF16 uri(utf8uri);
aUriSpec = NS_ConvertUTF8toUTF16(utf8uri);
return NS_OK;
}
nsresult AppWindow::MaybeSaveEarlyWindowPersistentValues(
const LayoutDeviceIntRect& aRect) {
#ifdef XP_WIN
nsAutoString uri;
nsAutoString windowElementId;
nsresult rv = GetDocXulStoreKeys(uri, windowElementId);
if (NS_WARN_IF(NS_FAILED(rv))) {
return rv;
}
if (!windowElementId.EqualsLiteral("main-window") ||
!uri.EqualsLiteral("chrome://browser/content/browser.xhtml")) {
return NS_OK;
}
PersistEarlyBlankWindowValues(aRect.X(), aRect.Y(), aRect.Width(),
aRect.Height(),
mWindow->GetDefaultScale().scale);
#endif
return NS_OK;
}
nsresult AppWindow::SetPersistentValue(const nsAtom* aAttr,
const nsAString& aValue) {
nsAutoString uri;
nsAutoString windowElementId;
nsresult rv = GetDocXulStoreKeys(uri, windowElementId);
if (NS_FAILED(rv) || windowElementId.IsEmpty()) {
return rv;
}
nsAutoString maybeConvertedValue(aValue);
if (aAttr == nsGkAtoms::width || aAttr == nsGkAtoms::height) {
@ -1878,6 +1919,8 @@ NS_IMETHODIMP AppWindow::SavePersistentAttributes() {
}
}
Unused << MaybeSaveEarlyWindowPersistentValues(rect);
if (mPersistentAttributesDirty & PAD_MISC) {
nsSizeMode sizeMode = mWindow->SizeMode();

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

@ -247,6 +247,16 @@ class AppWindow final : public nsIBaseWindow,
nsresult GetPersistentValue(const nsAtom* aAttr, nsAString& aValue);
nsresult SetPersistentValue(const nsAtom* aAttr, const nsAString& aValue);
// Saves window size and positioning values in order to display a very early
// skeleton UI. This has to happen before we can reasonably initialize the
// xulstore (i.e., before even loading libxul), so they have to use a special
// purpose store to do so.
nsresult MaybeSaveEarlyWindowPersistentValues(
const LayoutDeviceIntRect& aRect);
// Gets the uri spec and the window element ID for this window.
nsresult GetDocXulStoreKeys(nsString& aUriSpec, nsString& aWindowElementId);
// Enum for the current state of a fullscreen change.
//
// It is used to ensure that fullscreen change is issued after both