Source code

Revision control

Copy as Markdown

Other Tools

/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
/* vim:set ts=2 sts=2 sw=2 et cin: */
/* 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/. */
/*
* nsWindow - Native window management and event handling.
*
* nsWindow is organized into a set of major blocks and
* block subsections. The layout is as follows:
*
* Includes
* Variables
* nsIWidget impl.
* nsIWidget methods and utilities
* nsSwitchToUIThread impl.
* nsSwitchToUIThread methods and utilities
* Moz events
* Event initialization
* Event dispatching
* Native events
* Wndproc(s)
* Event processing
* OnEvent event handlers
* IME management and accessibility
* Transparency
* Popup hook handling
* Misc. utilities
* Child window impl.
*
* Search for "BLOCK:" to find major blocks.
* Search for "SECTION:" to find specific sections.
*
* Blocks should be split out into separate files if they
* become unmanageable.
*
* Notable related sources:
*
* nsWindowDefs.h - Definitions, macros, structs, enums
* and general setup.
* nsWindowDbg.h/.cpp - Debug related code and directives.
* nsWindowGfx.h/.cpp - Graphics and painting.
*
*/
/**************************************************************
**************************************************************
**
** BLOCK: Includes
**
** Include headers.
**
**************************************************************
**************************************************************/
#include "gfx2DGlue.h"
#include "gfxEnv.h"
#include "gfxPlatform.h"
#include "mozilla/AppShutdown.h"
#include "mozilla/AutoRestore.h"
#include "mozilla/Likely.h"
#include "mozilla/PreXULSkeletonUI.h"
#include "mozilla/Logging.h"
#include "mozilla/MathAlgorithms.h"
#include "mozilla/MiscEvents.h"
#include "mozilla/MouseEvents.h"
#include "mozilla/PresShell.h"
#include "mozilla/ScopeExit.h"
#include "mozilla/StaticPrefs_browser.h"
#include "mozilla/SwipeTracker.h"
#include "mozilla/TouchEvents.h"
#include "mozilla/TimeStamp.h"
#include "mozilla/ipc/MessageChannel.h"
#include <algorithm>
#include <limits>
#include "mozilla/widget/WinMessages.h"
#include "nsWindow.h"
#include "nsWindowTaskbarConcealer.h"
#include "nsAppRunner.h"
#include <shellapi.h>
#include <windows.h>
#include <wtsapi32.h>
#include <process.h>
#include <commctrl.h>
#include <dbt.h>
#include <unknwn.h>
#include <psapi.h>
#include <rpc.h>
#include <propvarutil.h>
#include <propkey.h>
#include "mozilla/Logging.h"
#include "prtime.h"
#include "prenv.h"
#include "mozilla/WidgetTraceEvent.h"
#include "nsContentUtils.h"
#include "nsISupportsPrimitives.h"
#include "nsITheme.h"
#include "nsIObserverService.h"
#include "nsIScreenManager.h"
#include "imgIContainer.h"
#include "nsIFile.h"
#include "nsIRollupListener.h"
#include "nsIClipboard.h"
#include "WinMouseScrollHandler.h"
#include "nsFontMetrics.h"
#include "nsIFontEnumerator.h"
#include "nsFont.h"
#include "nsRect.h"
#include "nsThreadUtils.h"
#include "nsNativeCharsetUtils.h"
#include "nsGkAtoms.h"
#include "nsCRT.h"
#include "nsAppDirectoryServiceDefs.h"
#include "nsWidgetsCID.h"
#include "nsTHashtable.h"
#include "nsHashKeys.h"
#include "nsString.h"
#include "mozilla/Components.h"
#include "nsNativeThemeWin.h"
#include "nsXULPopupManager.h"
#include "nsWindowsDllInterceptor.h"
#include "nsLayoutUtils.h"
#include "nsView.h"
#include "nsWindowGfx.h"
#include "gfxWindowsPlatform.h"
#include "gfxDWriteFonts.h"
#include "nsPrintfCString.h"
#include "mozilla/Preferences.h"
#include "SystemTimeConverter.h"
#include "WinTaskbar.h"
#include "WidgetUtils.h"
#include "WinWindowOcclusionTracker.h"
#include "nsIWidgetListener.h"
#include "mozilla/dom/Document.h"
#include "mozilla/dom/MouseEventBinding.h"
#include "mozilla/dom/Touch.h"
#include "mozilla/gfx/2D.h"
#include "mozilla/gfx/GPUProcessManager.h"
#include "mozilla/intl/LocaleService.h"
#include "mozilla/layers/WebRenderLayerManager.h"
#include "mozilla/WindowsVersion.h"
#include "mozilla/TextEvents.h" // For WidgetKeyboardEvent
#include "mozilla/TextEventDispatcherListener.h"
#include "mozilla/widget/nsAutoRollup.h"
#include "mozilla/widget/PlatformWidgetTypes.h"
#include "mozilla/widget/Screen.h"
#include "nsStyleConsts.h"
#include "nsBidiKeyboard.h"
#include "nsStyleConsts.h"
#include "gfxConfig.h"
#include "InProcessWinCompositorWidget.h"
#include "InputDeviceUtils.h"
#include "ScreenHelperWin.h"
#include "mozilla/StaticPrefs_apz.h"
#include "mozilla/StaticPrefs_dom.h"
#include "mozilla/StaticPrefs_gfx.h"
#include "mozilla/StaticPrefs_layout.h"
#include "mozilla/StaticPrefs_ui.h"
#include "mozilla/StaticPrefs_widget.h"
#include "nsNativeAppSupportWin.h"
#include "mozilla/browser/NimbusFeatures.h"
#include "nsIGfxInfo.h"
#include "nsUXThemeConstants.h"
#include "KeyboardLayout.h"
#include "nsNativeDragTarget.h"
#include <mmsystem.h> // needed for WIN32_LEAN_AND_MEAN
#include <zmouse.h>
#include <richedit.h>
#if defined(ACCESSIBILITY)
# ifdef DEBUG
# include "mozilla/a11y/Logging.h"
# endif
# include "oleidl.h"
# include <winuser.h>
# include "nsAccessibilityService.h"
# include "mozilla/a11y/DocAccessible.h"
# include "mozilla/a11y/LazyInstantiator.h"
# include "mozilla/a11y/Platform.h"
# if !defined(WINABLEAPI)
# include <winable.h>
# endif // !defined(WINABLEAPI)
#endif // defined(ACCESSIBILITY)
#include "WindowsUIUtils.h"
#include "nsWindowDefs.h"
#include "nsCrashOnException.h"
#include "nsIContent.h"
#include "mozilla/BackgroundHangMonitor.h"
#include "WinIMEHandler.h"
#include "npapi.h"
#include <d3d11.h>
// ERROR from wingdi.h (below) gets undefined by some code.
// #define ERROR 0
// #define RGN_ERROR ERROR
#define ERROR 0
#if !defined(SM_CONVERTIBLESLATEMODE)
# define SM_CONVERTIBLESLATEMODE 0x2003
#endif
#include "mozilla/gfx/DeviceManagerDx.h"
#include "mozilla/layers/APZInputBridge.h"
#include "mozilla/layers/InputAPZContext.h"
#include "mozilla/layers/KnowsCompositor.h"
#include "InputData.h"
#include "mozilla/TaskController.h"
#include "mozilla/Telemetry.h"
#include "mozilla/webrender/WebRenderAPI.h"
#include "mozilla/layers/IAPZCTreeManager.h"
#include "DirectManipulationOwner.h"
using namespace mozilla;
using namespace mozilla::dom;
using namespace mozilla::gfx;
using namespace mozilla::layers;
using namespace mozilla::widget;
using namespace mozilla::plugins;
/**************************************************************
**************************************************************
**
** BLOCK: Variables
**
** nsWindow Class static initializations and global variables.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: nsWindow statics
*
**************************************************************/
static const wchar_t kUser32LibName[] = L"user32.dll";
uint32_t nsWindow::sInstanceCount = 0;
bool nsWindow::sIsOleInitialized = false;
nsIWidget::Cursor nsWindow::sCurrentCursor = {};
nsWindow* nsWindow::sCurrentWindow = nullptr;
bool nsWindow::sJustGotDeactivate = false;
bool nsWindow::sJustGotActivate = false;
bool nsWindow::sIsInMouseCapture = false;
// Urgent-message reentrancy depth for the static `WindowProc` callback.
//
// Three unfortunate facts collide:
//
// 𝛼) Some messages must be processed promptly. If not, Windows will leave the
// receiving window in an intermediate, and potentially unusable, state until
// the WindowProc invocation that is handling it returns.
//
// 𝛽) Some messages have indefinitely long processing time. These are mostly
// messages which may cause us to enter a nested modal loop (via
// `SpinEventLoopUntil` or similar).
//
// 𝛾) Sometimes, messages skip the queue entirely. Our `WindowProc` may be
// reentrantly reinvoked from the kernel while we're blocking _on_ the
// kernel, even briefly, during processing of other messages. (Relevant
// search term: `KeUserModeCallback`.)
//
// The nightmare scenario, then, is that during processing of an 𝛼-message, we
// briefly become blocked (e.g., by calling `::SendMessageW()`), and the kernel
// takes that opportunity to use 𝛾 to hand us a 𝛽-message. (Concretely, see
//
// There is little we can do to prevent the first half of this scenario. 𝛼) and
// 𝛾) are effectively immutable facts of Windows, and we sometimes legitimately
// need to make blocking calls to process 𝛼-messages. (We may not even be aware
// that we're making such calls, if they're undocumented implementation details
// of another API.)
//
// In an ideal world, WindowProc would always return promptly (or at least in
// bounded time), and 𝛽-messages would not _per se_ exist; long-running modal
// states would instead be implemented in async fashion. In practice, that's far
// easier said than done -- replacing existing uses of `SpinEventLoopUntil` _et
// al._ with asynchronous mechanisms is a collection of mostly-unrelated cross-
// cutting architectural tasks, each of potentially unbounded scope. For now,
// and for the foreseeable future, we're stuck with them.
//
// We therefore simply punt. More specifically: if a known 𝛽-message jumps the
// queue to come in while we're in the middle of processing a known 𝛼-message,
// we:
// * properly queue the message for processing later;
// * respond to the 𝛽-message as though we actually had processed it; and
// * just hope that it can wait until we get around to it.
//
// The word "known" requires a bit of justification. There is no canonical set
// of 𝛼-messages, nor is the set of 𝛽-messages fixed (or even demarcable). We
// can't safely assume that all messages are 𝛼-messages, as that could cause
// 𝛽-messages to be arbitrarily and surprisingly delayed whenever any nested
// event loop is active. We also can't assume all messages are 𝛽-messages,
// since one 𝛼-message jumping the queue while processing another 𝛼-message is
// part of normal and required operation for windowed Windows applications.
//
// So we simply add messages to those sets as we identify them. (Or, preferably,
// rework the 𝛽-message's handling to make it no longer 𝛽. But see above.)
//
// ---
//
// The actual value of `sDepth` is the number of active invocations of
// `WindowProc` that are processing known 𝛼-messages.
size_t nsWindow::WndProcUrgentInvocation::sDepth = 0;
// Hook Data Members for Dropdowns. sProcessHook Tells the
// hook methods whether they should be processing the hook
// messages.
HHOOK nsWindow::sMsgFilterHook = nullptr;
HHOOK nsWindow::sCallProcHook = nullptr;
HHOOK nsWindow::sCallMouseHook = nullptr;
bool nsWindow::sProcessHook = false;
UINT nsWindow::sRollupMsgId = 0;
HWND nsWindow::sRollupMsgWnd = nullptr;
UINT nsWindow::sHookTimerId = 0;
// Used to prevent dispatching mouse events that do not originate from user
// input.
POINT nsWindow::sLastMouseMovePoint = {0};
bool nsWindow::sIsRestoringSession = false;
bool nsWindow::sTouchInjectInitialized = false;
InjectTouchInputPtr nsWindow::sInjectTouchFuncPtr;
static SystemTimeConverter<DWORD>& TimeConverter() {
static SystemTimeConverter<DWORD> timeConverterSingleton;
return timeConverterSingleton;
}
// Global event hook for window cloaking. Never deregistered.
// - `Nothing` if not yet set.
// - `Some(nullptr)` if no attempt should be made to set it.
static mozilla::Maybe<HWINEVENTHOOK> sWinCloakEventHook = Nothing();
static mozilla::LazyLogModule sCloakingLog("DWMCloaking");
namespace mozilla {
class CurrentWindowsTimeGetter {
public:
explicit CurrentWindowsTimeGetter(HWND aWnd) : mWnd(aWnd) {}
DWORD GetCurrentTime() const { return ::GetTickCount(); }
void GetTimeAsyncForPossibleBackwardsSkew(const TimeStamp& aNow) {
DWORD currentTime = GetCurrentTime();
if (sBackwardsSkewStamp && currentTime == sLastPostTime) {
// There's already one inflight with this timestamp. Don't
// send a duplicate.
return;
}
sBackwardsSkewStamp = Some(aNow);
sLastPostTime = currentTime;
static_assert(sizeof(WPARAM) >= sizeof(DWORD),
"Can't fit a DWORD in a WPARAM");
::PostMessage(mWnd, MOZ_WM_SKEWFIX, sLastPostTime, 0);
}
static bool GetAndClearBackwardsSkewStamp(DWORD aPostTime,
TimeStamp* aOutSkewStamp) {
if (aPostTime != sLastPostTime) {
// The SKEWFIX message is stale; we've sent a new one since then.
// Ignore this one.
return false;
}
MOZ_ASSERT(sBackwardsSkewStamp);
*aOutSkewStamp = sBackwardsSkewStamp.value();
sBackwardsSkewStamp = Nothing();
return true;
}
private:
static Maybe<TimeStamp> sBackwardsSkewStamp;
static DWORD sLastPostTime;
HWND mWnd;
};
Maybe<TimeStamp> CurrentWindowsTimeGetter::sBackwardsSkewStamp;
DWORD CurrentWindowsTimeGetter::sLastPostTime = 0;
} // namespace mozilla
/**************************************************************
*
* SECTION: globals variables
*
**************************************************************/
static const char* sScreenManagerContractID =
"@mozilla.org/gfx/screenmanager;1";
extern mozilla::LazyLogModule gWindowsLog;
static NS_DEFINE_CID(kCClipboardCID, NS_CLIPBOARD_CID);
// General purpose user32.dll hook object
static WindowsDllInterceptor sUser32Intercept;
// When the client area is extended out into the default window frame area,
// this is the minimum amount of space along the edge of resizable windows
// we will always display a resize cursor in, regardless of the underlying
// content.
static const int32_t kResizableBorderMinSize = 3;
// Getting this object from the window server can be expensive. Keep it
// around, also get it off the main thread. (See bug 1640852)
StaticRefPtr<IVirtualDesktopManager> gVirtualDesktopManager;
static bool gInitializedVirtualDesktopManager = false;
// We should never really try to accelerate windows bigger than this. In some
// cases this might lead to no D3D9 acceleration where we could have had it
// but D3D9 does not reliably report when it supports bigger windows. 8192
// is as safe as we can get, we know at least D3D10 hardware always supports
// this, other hardware we expect to report correctly in D3D9.
#define MAX_ACCELERATED_DIMENSION 8192
// On window open (as well as after), Windows has an unfortunate habit of
// sending rather a lot of WM_NCHITTEST messages. Because we have to do point
// to DOM target conversions for these, we cache responses for a given
// coordinate this many milliseconds:
#define HITTEST_CACHE_LIFETIME_MS 50
#if defined(ACCESSIBILITY)
namespace mozilla {
/**
* Windows touchscreen code works by setting a global WH_GETMESSAGE hook and
* injecting tiptsf.dll. The touchscreen process then posts registered messages
* to our main thread. The tiptsf hook picks up those registered messages and
* uses them as commands, some of which call into UIA, which then calls into
* MSAA, which then sends WM_GETOBJECT to us.
*
* We can get ahead of this by installing our own thread-local WH_GETMESSAGE
* hook. Since thread-local hooks are called ahead of global hooks, we will
* see these registered messages before tiptsf does. At this point we can then
* raise a flag that blocks a11y before invoking CallNextHookEx which will then
* invoke the global tiptsf hook. Then when we see WM_GETOBJECT, we check the
* flag by calling TIPMessageHandler::IsA11yBlocked().
*
* For Windows 8, we also hook tiptsf!ProcessCaretEvents, which is an a11y hook
* function that also calls into UIA.
*/
class TIPMessageHandler {
public:
~TIPMessageHandler() {
if (mHook) {
::UnhookWindowsHookEx(mHook);
}
}
static void Initialize() {
if (sInstance) {
return;
}
sInstance = new TIPMessageHandler();
ClearOnShutdown(&sInstance);
}
static bool IsA11yBlocked() {
if (!sInstance) {
return false;
}
return sInstance->mA11yBlockCount > 0;
}
private:
TIPMessageHandler() : mHook(nullptr), mA11yBlockCount(0) {
MOZ_ASSERT(NS_IsMainThread());
// Registered messages used by tiptsf
mMessages[0] = ::RegisterWindowMessage(L"ImmersiveFocusNotification");
mMessages[1] = ::RegisterWindowMessage(L"TipCloseMenus");
mMessages[2] = ::RegisterWindowMessage(L"TabletInputPanelOpening");
mMessages[3] = ::RegisterWindowMessage(L"IHM Pen or Touch Event noticed");
mMessages[4] = ::RegisterWindowMessage(L"ProgrammabilityCaretVisibility");
mMessages[5] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPHidden");
mMessages[6] = ::RegisterWindowMessage(L"CaretTrackingUpdateIPInfo");
mHook = ::SetWindowsHookEx(WH_GETMESSAGE, &TIPHook, nullptr,
::GetCurrentThreadId());
MOZ_ASSERT(mHook);
if (!sSendMessageTimeoutWStub) {
sUser32Intercept.Init("user32.dll");
DebugOnly<bool> hooked = sSendMessageTimeoutWStub.Set(
sUser32Intercept, "SendMessageTimeoutW", &SendMessageTimeoutWHook);
MOZ_ASSERT(hooked);
}
}
class MOZ_RAII A11yInstantiationBlocker {
public:
A11yInstantiationBlocker() {
if (!TIPMessageHandler::sInstance) {
return;
}
++TIPMessageHandler::sInstance->mA11yBlockCount;
}
~A11yInstantiationBlocker() {
if (!TIPMessageHandler::sInstance) {
return;
}
MOZ_ASSERT(TIPMessageHandler::sInstance->mA11yBlockCount > 0);
--TIPMessageHandler::sInstance->mA11yBlockCount;
}
};
friend class A11yInstantiationBlocker;
static LRESULT CALLBACK TIPHook(int aCode, WPARAM aWParam, LPARAM aLParam) {
if (aCode < 0 || !sInstance) {
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
MSG* msg = reinterpret_cast<MSG*>(aLParam);
UINT& msgCode = msg->message;
for (uint32_t i = 0; i < ArrayLength(sInstance->mMessages); ++i) {
if (msgCode == sInstance->mMessages[i]) {
A11yInstantiationBlocker block;
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
}
return ::CallNextHookEx(nullptr, aCode, aWParam, aLParam);
}
static LRESULT WINAPI SendMessageTimeoutWHook(HWND aHwnd, UINT aMsgCode,
WPARAM aWParam, LPARAM aLParam,
UINT aFlags, UINT aTimeout,
PDWORD_PTR aMsgResult) {
// We don't want to handle this unless the message is a WM_GETOBJECT that we
// want to block, and the aHwnd is a nsWindow that belongs to the current
// (i.e., main) thread.
if (!aMsgResult || aMsgCode != WM_GETOBJECT ||
static_cast<LONG>(aLParam) != OBJID_CLIENT || !::NS_IsMainThread() ||
!WinUtils::GetNSWindowPtr(aHwnd) || !IsA11yBlocked()) {
return sSendMessageTimeoutWStub(aHwnd, aMsgCode, aWParam, aLParam, aFlags,
aTimeout, aMsgResult);
}
// In this case we want to fake the result that would happen if we had
// decided not to handle WM_GETOBJECT in our WndProc. We hand the message
// off to DefWindowProc to accomplish this.
*aMsgResult = static_cast<DWORD_PTR>(
::DefWindowProcW(aHwnd, aMsgCode, aWParam, aLParam));
return static_cast<LRESULT>(TRUE);
}
static WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
sSendMessageTimeoutWStub;
static StaticAutoPtr<TIPMessageHandler> sInstance;
HHOOK mHook;
UINT mMessages[7];
uint32_t mA11yBlockCount;
};
WindowsDllInterceptor::FuncHookType<decltype(&SendMessageTimeoutW)>
TIPMessageHandler::sSendMessageTimeoutWStub;
StaticAutoPtr<TIPMessageHandler> TIPMessageHandler::sInstance;
} // namespace mozilla
#endif // defined(ACCESSIBILITY)
namespace mozilla {
// This task will get the VirtualDesktopManager from the generic thread pool
// since doing this on the main thread on startup causes performance issues.
//
// See bug 1640852.
//
// This should be fine and should not require any locking, as when the main
// thread will access it, if it races with this function it will either find
// it to be null or to have a valid value.
class InitializeVirtualDesktopManagerTask : public Task {
public:
InitializeVirtualDesktopManagerTask()
: Task(Kind::OffMainThreadOnly, kDefaultPriorityValue) {}
#ifdef MOZ_COLLECTING_RUNNABLE_TELEMETRY
bool GetName(nsACString& aName) override {
aName.AssignLiteral("InitializeVirtualDesktopManagerTask");
return true;
}
#endif
virtual TaskResult Run() override {
RefPtr<IVirtualDesktopManager> desktopManager;
HRESULT hr = ::CoCreateInstance(
CLSID_VirtualDesktopManager, NULL, CLSCTX_INPROC_SERVER,
__uuidof(IVirtualDesktopManager), getter_AddRefs(desktopManager));
if (FAILED(hr)) {
return TaskResult::Complete;
}
gVirtualDesktopManager = desktopManager;
return TaskResult::Complete;
}
};
// Ground-truth query: does Windows claim the window is cloaked right now?
static bool IsCloaked(HWND hwnd) {
DWORD cloakedState;
HRESULT hr = ::DwmGetWindowAttribute(hwnd, DWMWA_CLOAKED, &cloakedState,
sizeof(cloakedState));
if (FAILED(hr)) {
MOZ_LOG(sCloakingLog, LogLevel::Warning,
("failed (%08lX) to query cloaking state for HWND %p", hr, hwnd));
return false;
}
return cloakedState != 0;
}
} // namespace mozilla
/**************************************************************
**************************************************************
**
** BLOCK: nsIWidget impl.
**
** nsIWidget interface implementation, broken down into
** sections.
**
**************************************************************
**************************************************************/
/**************************************************************
*
* SECTION: nsWindow construction and destruction
*
**************************************************************/
nsWindow::nsWindow(bool aIsChildWindow)
: nsBaseWidget(BorderStyle::Default),
mBrush(::CreateSolidBrush(NSRGB_2_COLOREF(::GetSysColor(COLOR_BTNFACE)))),
mFrameState(std::in_place, this),
mIsChildWindow(aIsChildWindow),
mLastPaintEndTime(TimeStamp::Now()),
mCachedHitTestTime(TimeStamp::Now()),
mSizeConstraintsScale(GetDefaultScale().scale),
mDesktopId("DesktopIdMutex") {
MOZ_ASSERT(mWindowType == WindowType::Child);
if (!gInitializedVirtualDesktopManager) {
TaskController::Get()->AddTask(
MakeAndAddRef<InitializeVirtualDesktopManagerTask>());
gInitializedVirtualDesktopManager = true;
}
// Global initialization
if (!sInstanceCount) {
// Global app registration id for Win7 and up. See
// WinTaskbar.cpp for details.
// MSIX packages explicitly do not support setting the appid from within
// the app, as it is set in the package manifest instead.
if (!WinUtils::HasPackageIdentity()) {
mozilla::widget::WinTaskbar::RegisterAppUserModelID();
}
if (!StaticPrefs::ui_key_layout_load_when_first_needed()) {
KeyboardLayout::GetInstance()->OnLayoutChange(::GetKeyboardLayout(0));
}
#if defined(ACCESSIBILITY)
mozilla::TIPMessageHandler::Initialize();
#endif // defined(ACCESSIBILITY)
if (SUCCEEDED(::OleInitialize(nullptr))) {
sIsOleInitialized = true;
}
NS_ASSERTION(sIsOleInitialized, "***** OLE is not initialized!\n");
MouseScrollHandler::Initialize();
// Init theme data
nsUXThemeData::UpdateNativeThemeInfo();
RedirectedKeyDownMessageManager::Forget();
} // !sInstanceCount
sInstanceCount++;
}
nsWindow::~nsWindow() {
mInDtor = true;
// If the widget was released without calling Destroy() then the native window
// still exists, and we need to destroy it. Destroy() will early-return if it
// was already called. In any case it is important to call it before
// destroying mPresentLock (cf. 1156182).
Destroy();
// Free app icon resources. This must happen after `OnDestroy` (see bug
// 708033).
if (mIconSmall) ::DestroyIcon(mIconSmall);
if (mIconBig) ::DestroyIcon(mIconBig);
sInstanceCount--;
// Global shutdown
if (sInstanceCount == 0) {
IMEHandler::Terminate();
sCurrentCursor = {};
if (sIsOleInitialized) {
::OleFlushClipboard();
::OleUninitialize();
sIsOleInitialized = false;
}
}
NS_IF_RELEASE(mNativeDragTarget);
}
/**************************************************************
*
* SECTION: nsIWidget::Create, nsIWidget::Destroy
*
* Creating and destroying windows for this widget.
*
**************************************************************/
// Allow Derived classes to modify the height that is passed
// when the window is created or resized.
int32_t nsWindow::GetHeight(int32_t aProposedHeight) { return aProposedHeight; }
void nsWindow::SendAnAPZEvent(InputData& aEvent) {
LRESULT popupHandlingResult;
if (DealWithPopups(mWnd, MOZ_WM_DMANIP, 0, 0, &popupHandlingResult)) {
// We need to consume the event after using it to roll up the popup(s).
return;
}
if (mSwipeTracker && aEvent.mInputType == PANGESTURE_INPUT) {
// Give the swipe tracker a first pass at the event. If a new pan gesture
// has been started since the beginning of the swipe, the swipe tracker
// will know to ignore the event.
nsEventStatus status =
mSwipeTracker->ProcessEvent(aEvent.AsPanGestureInput());
if (status == nsEventStatus_eConsumeNoDefault) {
return;
}
}
APZEventResult result;
if (mAPZC) {
result = mAPZC->InputBridge()->ReceiveInputEvent(aEvent);
}
if (result.GetStatus() == nsEventStatus_eConsumeNoDefault) {
return;
}
MOZ_ASSERT(aEvent.mInputType == PANGESTURE_INPUT ||
aEvent.mInputType == PINCHGESTURE_INPUT);
if (aEvent.mInputType == PANGESTURE_INPUT) {
PanGestureInput& panInput = aEvent.AsPanGestureInput();
WidgetWheelEvent event = panInput.ToWidgetEvent(this);
if (!mAPZC) {
if (MayStartSwipeForNonAPZ(panInput)) {
return;
}
} else {
event = MayStartSwipeForAPZ(panInput, result);
}
ProcessUntransformedAPZEvent(&event, result);
return;
}
PinchGestureInput& pinchInput = aEvent.AsPinchGestureInput();
WidgetWheelEvent event = pinchInput.ToWidgetEvent(this);
ProcessUntransformedAPZEvent(&event, result);
}
void nsWindow::RecreateDirectManipulationIfNeeded() {
DestroyDirectManipulation();
if (mWindowType != WindowType::TopLevel && mWindowType != WindowType::Popup) {
return;
}
if (!(StaticPrefs::apz_allow_zooming() ||
StaticPrefs::apz_windows_use_direct_manipulation()) ||
StaticPrefs::apz_windows_force_disable_direct_manipulation()) {
return;
}
mDmOwner = MakeUnique<DirectManipulationOwner>(this);
LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
GetHeight(mBounds.Height()));
mDmOwner->Init(bounds);
}
void nsWindow::ResizeDirectManipulationViewport() {
if (mDmOwner) {
LayoutDeviceIntRect bounds(mBounds.X(), mBounds.Y(), mBounds.Width(),
GetHeight(mBounds.Height()));
mDmOwner->ResizeViewport(bounds);
}
}
void nsWindow::DestroyDirectManipulation() {
if (mDmOwner) {
mDmOwner->Destroy();
mDmOwner.reset();
}
}
// Create the proper widget
nsresult nsWindow::Create(nsIWidget* aParent, nsNativeWidget aNativeParent,
const LayoutDeviceIntRect& aRect,
widget::InitData* aInitData) {
// Historical note: there was once some belief and/or intent that nsWindows
// could be created on arbitrary threads, and this may still be reflected in
// some comments.
MOZ_ASSERT(NS_IsMainThread());
widget::InitData defaultInitData;
if (!aInitData) aInitData = &defaultInitData;
nsIWidget* baseParent =
aInitData->mWindowType == WindowType::Dialog ||
aInitData->mWindowType == WindowType::TopLevel ||
aInitData->mWindowType == WindowType::Invisible
? nullptr
: aParent;
mIsTopWidgetWindow = (nullptr == baseParent);
mBounds = aRect;
// Ensure that the toolkit is created.
nsToolkit::GetToolkit();
BaseCreate(baseParent, aInitData);
HWND parent;
if (aParent) { // has a nsIWidget parent
parent = aParent ? (HWND)aParent->GetNativeData(NS_NATIVE_WINDOW) : nullptr;
mParent = aParent;
} else { // has a nsNative parent
parent = (HWND)aNativeParent;
mParent =
aNativeParent ? WinUtils::GetNSWindowPtr((HWND)aNativeParent) : nullptr;
}
mIsRTL = aInitData->mRTL;
mOpeningAnimationSuppressed = aInitData->mIsAnimationSuppressed;
mAlwaysOnTop = aInitData->mAlwaysOnTop;
mIsAlert = aInitData->mIsAlert;
mResizable = aInitData->mResizable;
DWORD style = WindowStyle();
DWORD extendedStyle = WindowExStyle();
if (mWindowType == WindowType::Popup) {
if (!aParent) {
parent = nullptr;
}
} else if (mWindowType == WindowType::Invisible) {
// Make sure CreateWindowEx succeeds at creating a toplevel window
style &= ~0x40000000; // WS_CHILDWINDOW
} else {
// See if the caller wants to explictly set clip children and clip siblings
if (aInitData->mClipChildren) {
style |= WS_CLIPCHILDREN;
} else {
style &= ~WS_CLIPCHILDREN;
}
if (aInitData->mClipSiblings) {
style |= WS_CLIPSIBLINGS;
}
}
const wchar_t* className = ChooseWindowClass(mWindowType);
// Take specific actions when creating the first top-level window
static bool sFirstTopLevelWindowCreated = false;
if (aInitData->mWindowType == WindowType::TopLevel && !aParent &&
!sFirstTopLevelWindowCreated) {
sFirstTopLevelWindowCreated = true;
mWnd = ConsumePreXULSkeletonUIHandle();
auto skeletonUIError = GetPreXULSkeletonUIErrorReason();
if (skeletonUIError) {
nsAutoString errorString(
GetPreXULSkeletonUIErrorString(skeletonUIError.value()));
Telemetry::ScalarSet(
Telemetry::ScalarID::STARTUP_SKELETON_UI_DISABLED_REASON,
errorString);
}
if (mWnd) {
MOZ_ASSERT(style == kPreXULSkeletonUIWindowStyle,
"The skeleton UI window style should match the expected "
"style for the first window created");
MOZ_ASSERT(extendedStyle == kPreXULSkeletonUIWindowStyleEx,
"The skeleton UI window extended style should match the "
"expected extended style for the first window created");
MOZ_ASSERT(
::GetWindowThreadProcessId(mWnd, nullptr) == ::GetCurrentThreadId(),
"The skeleton UI window should be created on the same thread as "
"other windows");
mIsShowingPreXULSkeletonUI = true;
// If we successfully consumed the pre-XUL skeleton UI, just update
// our internal state to match what is currently being displayed.
mIsVisible = true;
mIsCloaked = mozilla::IsCloaked(mWnd);
mFrameState->ConsumePreXULSkeletonState(WasPreXULSkeletonUIMaximized());
// These match the margins set in browser-tabsintitlebar.js with
// default prefs on Windows. Bug 1673092 tracks lining this up with
// that more correctly instead of hard-coding it.
SetNonClientMargins(LayoutDeviceIntMargin(0, 2, 2, 2));
// Reset the WNDPROC for this window and its whole class, as we had
// to use our own WNDPROC when creating the the skeleton UI window.
::SetWindowLongPtrW(mWnd, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>(
WinUtils::NonClientDpiScalingDefWindowProcW));
::SetClassLongPtrW(mWnd, GCLP_WNDPROC,
reinterpret_cast<LONG_PTR>(
WinUtils::NonClientDpiScalingDefWindowProcW));
}
}
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.");
return NS_ERROR_FAILURE;
}
if (!sWinCloakEventHook) {
MOZ_LOG(sCloakingLog, LogLevel::Info, ("Registering cloaking event hook"));
// C++03 lambda approximation until P2173R1 is available (-std=c++2b)
struct StdcallLambda {
static void CALLBACK OnCloakUncloakHook(HWINEVENTHOOK hWinEventHook,
DWORD event, HWND hwnd,
LONG idObject, LONG idChild,
DWORD idEventThread,
DWORD dwmsEventTime) {
const bool isCloaked = event == EVENT_OBJECT_CLOAKED ? true : false;
nsWindow::OnCloakEvent(hwnd, isCloaked);
}
};
const HWINEVENTHOOK hook = ::SetWinEventHook(
EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED, HMODULE(nullptr),
&StdcallLambda::OnCloakUncloakHook, ::GetCurrentProcessId(),
::GetCurrentThreadId(), WINEVENT_OUTOFCONTEXT);
sWinCloakEventHook = Some(hook);
if (!hook) {
const DWORD err = ::GetLastError();
MOZ_LOG(sCloakingLog, LogLevel::Error,
("Failed to register cloaking event hook! GLE = %lu (0x%lX)", err,
err));
}
}
if (aInitData->mIsPrivate) {
if (NimbusFeatures::GetBool("majorRelease2022"_ns,
"feltPrivacyWindowSeparation"_ns, true) &&
// Although permanent Private Browsing mode is indeed Private Browsing,
// we choose to make it look like regular Firefox in terms of the icon
// it uses (which also means we shouldn't use the Private Browsing
// AUMID).
!StaticPrefs::browser_privatebrowsing_autostart()) {
RefPtr<IPropertyStore> pPropStore;
if (!FAILED(SHGetPropertyStoreForWindow(mWnd, IID_IPropertyStore,
getter_AddRefs(pPropStore)))) {
PROPVARIANT pv;
nsAutoString aumid;
// make sure we're using the private browsing AUMID so that taskbar
// grouping works properly
Unused << NS_WARN_IF(
!mozilla::widget::WinTaskbar::GenerateAppUserModelID(aumid, true));
if (!FAILED(InitPropVariantFromString(aumid.get(), &pv))) {
if (!FAILED(pPropStore->SetValue(PKEY_AppUserModel_ID, pv))) {
pPropStore->Commit();
}
PropVariantClear(&pv);
}
}
HICON icon = ::LoadIconW(::GetModuleHandleW(nullptr),
MAKEINTRESOURCEW(IDI_PBMODE));
SetBigIcon(icon);
SetSmallIcon(icon);
}
}
mDeviceNotifyHandle = InputDeviceUtils::RegisterNotification(mWnd);
// If mDefaultScale is set before mWnd has been set, it will have the scale of
// the primary monitor, rather than the monitor that the window is actually
// on. For non-popup windows this gets corrected by the WM_DPICHANGED message
// which resets mDefaultScale, but for popup windows we don't reset
// mDefaultScale on that message. In order to ensure that popup windows
// spawned on a non-primary monitor end up with the correct scale, we reset
// mDefaultScale here so that it gets recomputed using the correct monitor now
// that we have a mWnd.
mDefaultScale = -1.0;
if (mIsRTL) {
DWORD dwAttribute = TRUE;
DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
sizeof dwAttribute);
}
UpdateDarkModeToolbar();
if (mOpeningAnimationSuppressed) {
SuppressAnimation(true);
}
if (mAlwaysOnTop) {
::SetWindowPos(mWnd, HWND_TOPMOST, 0, 0, 0, 0,
SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
}
if (mWindowType != WindowType::Invisible &&
MouseScrollHandler::Device::IsFakeScrollableWindowNeeded()) {
// Ugly Thinkpad Driver Hack (Bugs 507222 and 594977)
//
// We create two zero-sized windows as descendants of the top-level window,
// like so:
//
// Top-level window (MozillaWindowClass)
// FAKETRACKPOINTSCROLLCONTAINER (MozillaWindowClass)
// FAKETRACKPOINTSCROLLABLE (MozillaWindowClass)
//
// We need to have the middle window, otherwise the Trackpoint driver
// will fail to deliver scroll messages. WM_MOUSEWHEEL messages are
// sent to the FAKETRACKPOINTSCROLLABLE, which then propagate up the
// window hierarchy until they are handled by nsWindow::WindowProc.
// WM_HSCROLL messages are also sent to the FAKETRACKPOINTSCROLLABLE,
// but these do not propagate automatically, so we have the window
// procedure pretend that they were dispatched to the top-level window
// instead.
//
// The FAKETRACKPOINTSCROLLABLE needs to have the specific window styles it
// is given below so that it catches the Trackpoint driver's heuristics.
HWND scrollContainerWnd = ::CreateWindowW(
className, L"FAKETRACKPOINTSCROLLCONTAINER", WS_CHILD | WS_VISIBLE, 0,
0, 0, 0, mWnd, nullptr, nsToolkit::mDllInstance, nullptr);
HWND scrollableWnd = ::CreateWindowW(
className, L"FAKETRACKPOINTSCROLLABLE",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP | 0x30, 0, 0, 0, 0,
scrollContainerWnd, nullptr, nsToolkit::mDllInstance, nullptr);
// Give the FAKETRACKPOINTSCROLLABLE window a specific ID so that
// WindowProcInternal can distinguish it from the top-level window
// easily.
::SetWindowLongPtrW(scrollableWnd, GWLP_ID, eFakeTrackPointScrollableID);
// Make FAKETRACKPOINTSCROLLABLE use nsWindow::WindowProc, and store the
// old window procedure in its "user data".
WNDPROC oldWndProc = (WNDPROC)::SetWindowLongPtrW(
scrollableWnd, GWLP_WNDPROC, (LONG_PTR)nsWindow::WindowProc);
::SetWindowLongPtrW(scrollableWnd, GWLP_USERDATA, (LONG_PTR)oldWndProc);
}
// We will start receiving native events after associating with our native
// window. We will also become the output of WinUtils::GetNSWindowPtr for that
// window.
if (!AssociateWithNativeWindow()) {
return NS_ERROR_FAILURE;
}
// Starting with Windows XP, a process always runs within a terminal services
// session. In order to play nicely with RDP, fast user switching, and the
// lock screen, we should be handling WM_WTSSESSION_CHANGE. We must register
// our HWND in order to receive this message.
DebugOnly<BOOL> wtsRegistered =
::WTSRegisterSessionNotification(mWnd, NOTIFY_FOR_THIS_SESSION);
NS_ASSERTION(wtsRegistered, "WTSRegisterSessionNotification failed!\n");
mDefaultIMC.Init(this);
IMEHandler::InitInputContext(this, mInputContext);
static bool a11yPrimed = false;
if (!a11yPrimed && mWindowType == WindowType::TopLevel) {
a11yPrimed = true;
if (Preferences::GetInt("accessibility.force_disabled", 0) == -1) {
::PostMessage(mWnd, MOZ_WM_STARTA11Y, 0, 0);
}
}
RecreateDirectManipulationIfNeeded();
return NS_OK;
}
void nsWindow::LocalesChanged() {
bool isRTL = intl::LocaleService::GetInstance()->IsAppLocaleRTL();
if (mIsRTL != isRTL) {
DWORD dwAttribute = isRTL;
DwmSetWindowAttribute(mWnd, DWMWA_NONCLIENT_RTL_LAYOUT, &dwAttribute,
sizeof dwAttribute);
mIsRTL = isRTL;
}
}
// Close this nsWindow
void nsWindow::Destroy() {
// WM_DESTROY has already fired, avoid calling it twice
if (mOnDestroyCalled) return;
// Don't destroy windows that have file pickers open, we'll tear these down
// later once the picker is closed.
mDestroyCalled = true;
if (mPickerDisplayCount) return;
// During the destruction of all of our children, make sure we don't get
// deleted.
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
DestroyDirectManipulation();
/**
* On windows the LayerManagerOGL destructor wants the widget to be around for
* cleanup. It also would like to have the HWND intact, so we nullptr it here.
*/
DestroyLayerManager();
InputDeviceUtils::UnregisterNotification(mDeviceNotifyHandle);
mDeviceNotifyHandle = nullptr;
// The DestroyWindow function destroys the specified window. The function
// sends WM_DESTROY and WM_NCDESTROY messages to the window to deactivate it
// and remove the keyboard focus from it. The function also destroys the
// window's menu, flushes the thread message queue, destroys timers, removes
// clipboard ownership, and breaks the clipboard viewer chain (if the window
// is at the top of the viewer chain).
//
// If the specified window is a parent or owner window, DestroyWindow
// automatically destroys the associated child or owned windows when it
// destroys the parent or owner window. The function first destroys child or
// owned windows, and then it destroys the parent or owner window.
VERIFY(::DestroyWindow(mWnd));
// Our windows can be subclassed which may prevent us receiving WM_DESTROY. If
// OnDestroy() didn't get called, call it now.
if (false == mOnDestroyCalled) {
MSGResult msgResult;
mWindowHook.Notify(mWnd, WM_DESTROY, 0, 0, msgResult);
OnDestroy();
}
}
/**************************************************************
*
* SECTION: Window class utilities
*
* Utilities for calculating the proper window class name for
* Create window.
*
**************************************************************/
/* static */
const wchar_t* nsWindow::RegisterWindowClass(const wchar_t* aClassName,
UINT aExtraStyle, LPWSTR aIconID) {
WNDCLASSW wc;
if (::GetClassInfoW(nsToolkit::mDllInstance, aClassName, &wc)) {
// already registered
return aClassName;
}
wc.style = CS_DBLCLKS | aExtraStyle;
wc.lpfnWndProc = WinUtils::NonClientDpiScalingDefWindowProcW;
wc.cbClsExtra = 0;
wc.cbWndExtra = 0;
wc.hInstance = nsToolkit::mDllInstance;
wc.hIcon =
aIconID ? ::LoadIconW(::GetModuleHandleW(nullptr), aIconID) : nullptr;
wc.hCursor = nullptr;
wc.hbrBackground = nullptr;
wc.lpszMenuName = nullptr;
wc.lpszClassName = aClassName;
if (!::RegisterClassW(&wc)) {
// For older versions of Win32 (i.e., not XP), the registration may
// fail with aExtraStyle, so we have to re-register without it.
wc.style = CS_DBLCLKS;
::RegisterClassW(&wc);
}
return aClassName;
}
static LPWSTR const gStockApplicationIcon = MAKEINTRESOURCEW(32512);
/* static */
const wchar_t* nsWindow::ChooseWindowClass(WindowType aWindowType) {
const wchar_t* className = [aWindowType] {
switch (aWindowType) {
case WindowType::Invisible:
return kClassNameHidden;
case WindowType::Dialog:
return kClassNameDialog;
case WindowType::Popup:
return kClassNameDropShadow;
default:
return GetMainWindowClass();
}
}();
return RegisterWindowClass(className, 0, gStockApplicationIcon);
}
/**************************************************************
*
* SECTION: Window styles utilities
*
* Return the proper windows styles and extended styles.
*
**************************************************************/
// Return nsWindow styles
DWORD nsWindow::WindowStyle() {
DWORD style;
switch (mWindowType) {
case WindowType::Child:
style = WS_OVERLAPPED;
break;
case WindowType::Dialog:
style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU | DS_3DLOOK |
DS_MODALFRAME | WS_CLIPCHILDREN;
if (mBorderStyle != BorderStyle::Default)
style |= WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX;
break;
case WindowType::Popup:
style = WS_POPUP | WS_OVERLAPPED;
break;
default:
NS_ERROR("unknown border style");
[[fallthrough]];
case WindowType::TopLevel:
case WindowType::Invisible:
style = WS_OVERLAPPED | WS_BORDER | WS_DLGFRAME | WS_SYSMENU |
WS_THICKFRAME | WS_MINIMIZEBOX | WS_MAXIMIZEBOX | WS_CLIPCHILDREN;
break;
}
if (mBorderStyle != BorderStyle::Default &&
mBorderStyle != BorderStyle::All) {
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::Border))
style &= ~WS_BORDER;
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::Title)) {
style &= ~WS_DLGFRAME;
}
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::Close))
style &= ~0;
// XXX The close box can only be removed by changing the window class,
// as far as I know --- roc+moz@cs.cmu.edu
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & (BorderStyle::Menu | BorderStyle::Close)))
style &= ~WS_SYSMENU;
// Looks like getting rid of the system menu also does away with the
// close box. So, we only get rid of the system menu if you want neither it
// nor the close box. How does the Windows "Dialog" window class get just
// closebox and no sysmenu? Who knows.
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::ResizeH))
style &= ~WS_THICKFRAME;
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::Minimize))
style &= ~WS_MINIMIZEBOX;
if (mBorderStyle == BorderStyle::None ||
!(mBorderStyle & BorderStyle::Maximize))
style &= ~WS_MAXIMIZEBOX;
if (IsPopupWithTitleBar()) {
style |= WS_CAPTION;
if (mBorderStyle & BorderStyle::Close) {
style |= WS_SYSMENU;
}
}
}
if (mIsChildWindow) {
style |= WS_CLIPCHILDREN;
if (!(style & WS_POPUP)) {
style |= WS_CHILD; // WS_POPUP and WS_CHILD are mutually exclusive.
}
}
VERIFY_WINDOW_STYLE(style);
return style;
}
// Return nsWindow extended styles
DWORD nsWindow::WindowExStyle() {
switch (mWindowType) {
case WindowType::Child:
return 0;
case WindowType::Popup: {
DWORD extendedStyle = WS_EX_TOOLWINDOW;
if (mPopupLevel == PopupLevel::Top) {
extendedStyle |= WS_EX_TOPMOST;
}
return extendedStyle;
}
case WindowType::Sheet:
MOZ_FALLTHROUGH_ASSERT("Sheets are macOS specific");
case WindowType::Dialog:
case WindowType::TopLevel:
case WindowType::Invisible:
break;
}
if (mIsAlert) {
MOZ_ASSERT(mWindowType == WindowType::Dialog,
"Expect alert windows to have type=dialog");
return WS_EX_TOOLWINDOW;
}
return WS_EX_WINDOWEDGE;
}
/**************************************************************
*
* SECTION: Native window association utilities
*
* Used in Create and Destroy. A nsWindow can associate with its
* underlying native window mWnd. Once a native window is
* associated with a nsWindow, its native events will be handled
* by the static member function nsWindow::WindowProc. Moreover,
* the association will be registered in the WinUtils association
* list, that is, calling WinUtils::GetNSWindowPtr on the native
* window will return the associated nsWindow. This is used in
* nsWindow::WindowProc to correctly dispatch native events to
* the handler methods defined in nsWindow, even though it is a
* static member function.
*
* After dissociation, the native events of the native window will
* no longer be handled by nsWindow::WindowProc, and will thus not
* be dispatched to the nsWindow native event handler methods.
* Moreover, the association will no longer be registered in the
* WinUtils association list, so calling WinUtils::GetNSWindowPtr
* on the native window will return nullptr.
*
**************************************************************/
bool nsWindow::AssociateWithNativeWindow() {
if (!mWnd || !IsWindow(mWnd)) {
NS_ERROR("Invalid window handle");
return false;
}
// Connect the this pointer to the native window handle.
// This should be done before SetWindowLongPtrW, because nsWindow::WindowProc
// uses WinUtils::GetNSWindowPtr internally.
WinUtils::SetNSWindowPtr(mWnd, this);
::SetLastError(ERROR_SUCCESS);
const auto prevWndProc = reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(nsWindow::WindowProc)));
if (!prevWndProc && GetLastError() != ERROR_SUCCESS) {
NS_ERROR("Failure in SetWindowLongPtrW");
WinUtils::SetNSWindowPtr(mWnd, nullptr);
return false;
}
mPrevWndProc.emplace(prevWndProc);
return true;
}
void nsWindow::DissociateFromNativeWindow() {
if (!mWnd || !IsWindow(mWnd) || mPrevWndProc.isNothing()) {
return;
}
DebugOnly<WNDPROC> wndProcBeforeDissociate =
reinterpret_cast<WNDPROC>(::SetWindowLongPtrW(
mWnd, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(*mPrevWndProc)));
NS_ASSERTION(wndProcBeforeDissociate == nsWindow::WindowProc,
"Unstacked an unexpected native window procedure");
WinUtils::SetNSWindowPtr(mWnd, nullptr);
mPrevWndProc.reset();
}
/**************************************************************
*
* SECTION: nsIWidget::SetParent, nsIWidget::GetParent
*
* Set or clear the parent widgets using window properties, and
* handles calculating native parent handles.
*
**************************************************************/
// Get and set parent widgets
void nsWindow::SetParent(nsIWidget* aNewParent) {
nsCOMPtr<nsIWidget> kungFuDeathGrip(this);
nsIWidget* parent = GetParent();
if (parent) {
parent->RemoveChild(this);
}
mParent = aNewParent;
if (aNewParent) {
ReparentNativeWidget(aNewParent);
aNewParent->AddChild(this);
return;
}
if (mWnd) {
// If we have no parent, SetParent should return the desktop.
VERIFY(::SetParent(mWnd, nullptr));
RecreateDirectManipulationIfNeeded();
}
}
void nsWindow::ReparentNativeWidget(nsIWidget* aNewParent) {
MOZ_ASSERT(aNewParent, "null widget");
mParent = aNewParent;
if (mWindowType == WindowType::Popup) {
return;
}
HWND newParent = (HWND)aNewParent->GetNativeData(NS_NATIVE_WINDOW);
NS_ASSERTION(newParent, "Parent widget has a null native window handle");
if (newParent && mWnd) {
::SetParent(mWnd, newParent);
RecreateDirectManipulationIfNeeded();
}
}
nsIWidget* nsWindow::GetParent(void) {
if (mIsTopWidgetWindow) {
return nullptr;
}
if (mInDtor || mOnDestroyCalled) {
return nullptr;
}
return mParent;
}
static int32_t RoundDown(double aDouble) {
return aDouble > 0 ? static_cast<int32_t>(floor(aDouble))
: static_cast<int32_t>(ceil(aDouble));
}
float nsWindow::GetDPI() {
float dpi = 96.0f;
nsCOMPtr<nsIScreen> screen = GetWidgetScreen();
if (screen) {
screen->GetDpi(&dpi);
}
return dpi;
}
double nsWindow::GetDefaultScaleInternal() {
if (mDefaultScale <= 0.0) {
mDefaultScale = WinUtils::LogToPhysFactor(mWnd);
}
return mDefaultScale;
}
int32_t nsWindow::LogToPhys(double aValue) {
return WinUtils::LogToPhys(
::MonitorFromWindow(mWnd, MONITOR_DEFAULTTOPRIMARY), aValue);
}
nsWindow* nsWindow::GetParentWindow(bool aIncludeOwner) {
return static_cast<nsWindow*>(GetParentWindowBase(aIncludeOwner));
}
nsWindow* nsWindow::GetParentWindowBase(bool aIncludeOwner) {
if (mIsTopWidgetWindow) {
// Must use a flag instead of mWindowType to tell if the window is the
// owned by the topmost widget, because a child window can be embedded
// inside a HWND which is not associated with a nsIWidget.
return nullptr;
}
// If this widget has already been destroyed, pretend we have no parent.
// This corresponds to code in Destroy which removes the destroyed
// widget from its parent's child list.
if (mInDtor || mOnDestroyCalled) return nullptr;
// aIncludeOwner set to true implies walking the parent chain to retrieve the
// root owner. aIncludeOwner set to false implies the search will stop at the
// true parent (default).
nsWindow* widget = nullptr;
if (mWnd) {
HWND parent = nullptr;
if (aIncludeOwner)
parent = ::GetParent(mWnd);
else
parent = ::GetAncestor(mWnd, GA_PARENT);
if (parent) {
widget = WinUtils::GetNSWindowPtr(parent);
if (widget) {
// If the widget is in the process of being destroyed then
// do NOT return it
if (widget->mInDtor) {
widget = nullptr;
}
}
}
}
return widget;
}
/**************************************************************
*
* SECTION: nsIWidget::Show
*
* Hide or show this component.
*
**************************************************************/
void nsWindow::Show(bool bState) {
if (bState && mIsShowingPreXULSkeletonUI) {
// The first time we decide to actually show the window is when we decide
// that we've taken over the window from the skeleton UI, and we should
// no longer treat resizes / moves specially.
mIsShowingPreXULSkeletonUI = false;
#if defined(ACCESSIBILITY)
// If our HWND has focus and the a11y engine hasn't started yet, fire a
// focus win event. Windows already did this when the skeleton UI appeared,
// but a11y wouldn't have been able to start at that point even if a client
// responded. Firing this now gives clients the chance to respond with
// WM_GETOBJECT, which will trigger the a11y engine. We don't want to do
// this if the a11y engine has already started because it has probably
// already fired focus on a descendant.
if (::GetFocus() == mWnd && !GetAccService()) {
::NotifyWinEvent(EVENT_OBJECT_FOCUS, mWnd, OBJID_CLIENT, CHILDID_SELF);
}
#endif // defined(ACCESSIBILITY)
}
if (mWindowType == WindowType::Popup) {
MOZ_ASSERT(ChooseWindowClass(mWindowType) == kClassNameDropShadow);
// WS_EX_COMPOSITED conflicts with the WS_EX_LAYERED style and causes
// some popup menus to become invisible.
LONG_PTR exStyle = ::GetWindowLongPtrW(mWnd, GWL_EXSTYLE);
if (exStyle & WS_EX_LAYERED) {
::SetWindowLongPtrW(mWnd, GWL_EXSTYLE, exStyle & ~WS_EX_COMPOSITED);
}
}
bool syncInvalidate = false;
bool wasVisible = mIsVisible;
// Set the status now so that anyone asking during ShowWindow or
// SetWindowPos would get the correct answer.
mIsVisible = bState;
// We may have cached an out of date visible state. This can happen
// when session restore sets the full screen mode.
if (mIsVisible)
mOldStyle |= WS_VISIBLE;
else
mOldStyle &= ~WS_VISIBLE;
if (mWnd) {
if (bState) {
if (!wasVisible && mWindowType == WindowType::TopLevel) {
// speed up the initial paint after show for
// top level windows:
syncInvalidate = true;
// Set the cursor before showing the window to avoid the default wait
// cursor.
SetCursor(Cursor{eCursor_standard});
switch (mFrameState->GetSizeMode()) {
case nsSizeMode_Fullscreen:
::ShowWindow(mWnd, SW_SHOW);
break;
case nsSizeMode_Maximized:
::ShowWindow(mWnd, SW_SHOWMAXIMIZED);
break;
case nsSizeMode_Minimized:
::ShowWindow(mWnd, SW_SHOWMINIMIZED);
break;
default:
if (CanTakeFocus() && !mAlwaysOnTop) {
::ShowWindow(mWnd, SW_SHOWNORMAL);
} else {
::ShowWindow(mWnd, SW_SHOWNOACTIVATE);
// Don't flicker the window if we're restoring session
if (!sIsRestoringSession) {
Unused << GetAttention(2);
}
}
break;
}
} else {
DWORD flags = SWP_NOSIZE | SWP_NOMOVE | SWP_SHOWWINDOW;
if (wasVisible) {
flags |= SWP_NOZORDER;
}
if (mAlwaysOnTop || mIsAlert) {
flags |= SWP_NOACTIVATE;
}
if (mWindowType == WindowType::Popup) {
// ensure popups are the topmost of the TOPMOST
// layer. Remember not to set the SWP_NOZORDER
// flag as that might allow the taskbar to overlap
// the popup.