Bug 1950441 - mark windows as (not) fullscreen via different mechanism r=win-reviewers,handyman
As noted in bug 1949079, Windows appears to detect maximized windows with custom titlebars as "full screen", and hides the taskbar behind them when autohide is enabled. (This appears to have been due to a recent change to their explicitly undocumented heuristics, but we may also have triggered it somehow with the recent-ish refactor of the custom non-client area.) To avert this, resurrect another mechanism to mark windows as fullscreen: "NonRudeHWND". This was previously believed -- perhaps correctly, at the time -- to be unusable on Windows 10; but recent testing shows that it _does_ work, even in several of the cases where the existing mechanism fails. Just in case it doesn't and I was right the first time, guard this behind a pref and allow users to switch back to the flaky mechanism. Differential Revision: https://phabricator.services.mozilla.com/D239658
This commit is contained in:
@@ -18415,13 +18415,27 @@
|
||||
value: 0
|
||||
mirror: always
|
||||
|
||||
# Whether to give explorer.exe a delated nudge to recalculate the fullscreenness
|
||||
# of a window after maximizing it.
|
||||
# Whether to give explorer.exe a delayed nudge to recalculate the fullscreenness
|
||||
# of a window after unminimizing it.
|
||||
- name: widget.windows.fullscreen_remind_taskbar
|
||||
type: RelaxedAtomicBool
|
||||
value: true
|
||||
mirror: always
|
||||
|
||||
# Mechanism to use to mark fullscreen windows.
|
||||
#
|
||||
# (If no problems are reported which adjusting this pref solves, it should be
|
||||
# removed in v141, and fixed at `1`.)
|
||||
#
|
||||
# * 0: Default. Use Gecko-internal heuristics. May vary between versions.
|
||||
# * 1: Use only "NonRudeHWND". Historical default on Win7.
|
||||
# * 2: Use only MarkFullscreenWindow. Historical default for Win10+.
|
||||
# * 3: Use both. (Never a default; not well-tested.)
|
||||
- name: widget.windows.fullscreen_marking_method
|
||||
type: RelaxedAtomicUint32
|
||||
value: 0
|
||||
mirror: always
|
||||
|
||||
# Whether to open the Windows file and folder pickers "remotely" (in a utility
|
||||
# process) or "locally" (in the main process).
|
||||
#
|
||||
|
||||
@@ -28,10 +28,30 @@ using namespace mozilla;
|
||||
struct TaskbarConcealerImpl {
|
||||
void MarkAsHidingTaskbar(HWND aWnd, bool aMark);
|
||||
|
||||
// Determination of the mechanism used to set the window state. (Hopefully
|
||||
// temporary: see comments in StaticPrefList.yaml for the relevant pref.)
|
||||
enum class MarkingMethod : uint32_t {
|
||||
NonRudeHwnd = 1,
|
||||
PrepareFullScreen = 2,
|
||||
};
|
||||
static MarkingMethod GetMarkingMethod() {
|
||||
uint32_t const val =
|
||||
StaticPrefs::widget_windows_fullscreen_marking_method();
|
||||
if (val >= 1 && val <= 3) return MarkingMethod(val);
|
||||
|
||||
// By default, just use `NonRudeHWND`.
|
||||
return MarkingMethod::NonRudeHwnd;
|
||||
}
|
||||
|
||||
private:
|
||||
nsCOMPtr<nsIWinTaskbar> mTaskbarInfo;
|
||||
|
||||
// local cache
|
||||
MarkingMethod const mMarkingMethod = GetMarkingMethod();
|
||||
};
|
||||
|
||||
MOZ_MAKE_ENUM_CLASS_BITWISE_OPERATORS(TaskbarConcealerImpl::MarkingMethod);
|
||||
|
||||
/**
|
||||
* nsWindow::TaskbarConcealer
|
||||
*
|
||||
@@ -45,10 +65,10 @@ struct TaskbarConcealerImpl {
|
||||
to show the taskbar when switching focus from a window marked as fullscreen to
|
||||
one not thus marked. [1]
|
||||
|
||||
Experimentation has (so far) suggested that its behavior is reasonable when
|
||||
switching between multiple monitors, or between a set of windows which are all
|
||||
from different processes [2]. This leaves us to handle the same-monitor, same-
|
||||
process case.
|
||||
Experimentation suggests that its behavior has usually been reasonable [2]
|
||||
when switching between multiple monitors, or between a set of windows which
|
||||
are all from different processes [3]. This leaves us to handle the
|
||||
same-monitor, same-process case.
|
||||
|
||||
Rather than do anything subtle here, we take the blanket approach of simply
|
||||
listening for every potentially-relevant state change, and then explicitly
|
||||
@@ -59,14 +79,14 @@ struct TaskbarConcealerImpl {
|
||||
[0] Relevant link:
|
||||
https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist2-markfullscreenwindow
|
||||
|
||||
The "NonRudeHWND" property described therein doesn't help with anything
|
||||
in this comment, unfortunately. (See its use in MarkAsHidingTaskbar for
|
||||
more details.)
|
||||
|
||||
[1] This is an oversimplification; Windows' actual behavior here is...
|
||||
complicated. See bug 1732517 comment 6 for some examples.
|
||||
|
||||
[2] A comment in Chromium asserts that this is actually different threads. For
|
||||
[2] (2025-02-24) Unfortunately, the heuristics appear not to be static. Recent
|
||||
versions of Windows 10, at least, may misinterpret a simple maximized
|
||||
windows with custom titlebar as full-screen.
|
||||
|
||||
[3] A comment in Chromium asserts that this is actually different threads. For
|
||||
us, of course, that makes no difference.
|
||||
https://github.com/chromium/chromium/blob/2b822268bd3/ui/views/win/hwnd_message_handler.cc#L1342
|
||||
*/
|
||||
@@ -250,33 +270,105 @@ void nsWindow::TaskbarConcealer::UpdateAllState(
|
||||
}
|
||||
} // nsWindow::TaskbarConcealer::UpdateAllState()
|
||||
|
||||
// Mark this window as requesting to occlude the taskbar. (The caller is
|
||||
// responsible for keeping any local state up-to-date.)
|
||||
// Mark this window as requesting to occlude, or not occlude, the taskbar. (The
|
||||
// caller is responsible for keeping any local state up-to-date.)
|
||||
void TaskbarConcealerImpl::MarkAsHidingTaskbar(HWND aWnd, bool aMark) {
|
||||
// ## NOTE ON UNDERDOCUMENTED BEHAVIOR:
|
||||
//
|
||||
// A section of the `ITaskbarList2::MarkFullscreenWindow` documentation
|
||||
// follows: [0]
|
||||
//
|
||||
// Setting the value of _fFullscreen_ to **TRUE**, the Shell treats this
|
||||
// window as a full-screen window, and the taskbar is moved to the bottom
|
||||
// of the z-order when this window is active. Setting the value of
|
||||
// _fFullscreen_ to **FALSE** removes the full-screen marking, but does not
|
||||
// cause the Shell to treat the window as though it were definitely not
|
||||
// full-screen. With a **FALSE** _fFullscreen_ value, the Shell depends on
|
||||
// its automatic detection facility to specify how the window should be
|
||||
// treated, possibly still flagging the window as full-screen.
|
||||
//
|
||||
// **Since Windows 7**, call `SetProp(hwnd, L”NonRudeHWND”,
|
||||
// reinterpret_cast<HANDLE>(TRUE))` before showing a window to indicate to
|
||||
// the Shell that the window should not be treated as full-screen.
|
||||
//
|
||||
// This is not entirely accurate. Furthermore, even where accurate, it's
|
||||
// underspecified, and the behavior has differed in important ways.
|
||||
//
|
||||
// * Under Windows 8.1 and early versions of Windows 10, a window will never
|
||||
// be considered fullscreen if the window-property "NonRudeHWND" is set to
|
||||
// `TRUE` before the window is shown, even if that property is later
|
||||
// removed. (See commentary in patch D146635.)
|
||||
//
|
||||
// (Note: no record was made of what happened if the property was only added
|
||||
// after window creation. Presumably it didn't help.)
|
||||
//
|
||||
// * Under Windows 7 and current versions of Windows 10+, a window will not be
|
||||
// considered fullscreen if the window-property "NonRudeHWND" is set to
|
||||
// `TRUE` when a check for fullscreenness is performed, regardless of
|
||||
// whether it was ever previously set. (Again, see commentary in patch
|
||||
// D146635.)
|
||||
//
|
||||
// * Under at least some versions of Windows 10, explicitly calling
|
||||
// `MarkFullscreenWindow(hwnd, FALSE)` on a window _already marked `FALSE`_
|
||||
// will sometimes cause a window improperly detected as fullscreen to no
|
||||
// longer be thus misdetected. (See `TaskbarConcealer::OnWindowMaximized()`,
|
||||
// and commentary in patch D239277.)
|
||||
//
|
||||
// The version of Win10 in which this behavior was adjusted is not presently
|
||||
// known -- indeed, at time of writing, there's no evidence that the developer
|
||||
// responsible for the claims in that first bullet point (also the present
|
||||
// author) didn't simply perform the tests improperly. (See comments in bug
|
||||
// 1950441 for the current known bounds.)
|
||||
//
|
||||
// For now, we implement both methods of marking, and use an `about:config`
|
||||
// pref to select which of them to use.
|
||||
//
|
||||
// [0] https://web.archive.org/web/20211223073250/https://docs.microsoft.com/en-us/windows/win32/api/shobjidl_core/nf-shobjidl_core-itaskbarlist2-markfullscreenwindow
|
||||
|
||||
const char* const sMark = aMark ? "true" : "false";
|
||||
|
||||
if (!mTaskbarInfo) {
|
||||
mTaskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
|
||||
bool const useNonRudeHWND = !!(mMarkingMethod & MarkingMethod::NonRudeHwnd);
|
||||
bool const usePrepareFullScreen =
|
||||
!!(mMarkingMethod & MarkingMethod::PrepareFullScreen);
|
||||
|
||||
// at least one must be set
|
||||
MOZ_ASSERT(useNonRudeHWND || usePrepareFullScreen);
|
||||
|
||||
if (useNonRudeHWND) {
|
||||
MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
|
||||
("Setting %p[L\"NonRudeHWND\"] to %s", aWnd, sMark));
|
||||
|
||||
// (setting the property to `FALSE` is not known to be functionally distinct
|
||||
// from removing it)
|
||||
::SetPropW(aWnd, L"NonRudeHWND", (HANDLE)uintptr_t(aMark ? FALSE : TRUE));
|
||||
} else {
|
||||
::RemovePropW(aWnd, L"NonRudeHWND");
|
||||
}
|
||||
|
||||
if (usePrepareFullScreen) {
|
||||
if (!mTaskbarInfo) {
|
||||
MOZ_LOG(
|
||||
sTaskbarConcealerLog, LogLevel::Warning,
|
||||
("could not acquire IWinTaskbar (aWnd %p, aMark %s)", aWnd, sMark));
|
||||
return;
|
||||
mTaskbarInfo = do_GetService(NS_TASKBAR_CONTRACTID);
|
||||
|
||||
if (!mTaskbarInfo) {
|
||||
MOZ_LOG(
|
||||
sTaskbarConcealerLog, LogLevel::Warning,
|
||||
("could not acquire IWinTaskbar (aWnd %p, aMark %s)", aWnd, sMark));
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
|
||||
("Calling PrepareFullScreen(%p, %s)", aWnd, sMark));
|
||||
|
||||
const nsresult hr = mTaskbarInfo->PrepareFullScreen(aWnd, aMark);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MOZ_LOG(sTaskbarConcealerLog, LogLevel::Error,
|
||||
("Call to PrepareFullScreen(%p, %s) failed with nsresult %x",
|
||||
aWnd, sMark, uint32_t(hr)));
|
||||
}
|
||||
}
|
||||
|
||||
MOZ_LOG(sTaskbarConcealerLog, LogLevel::Info,
|
||||
("Calling PrepareFullScreen(%p, %s)", aWnd, sMark));
|
||||
|
||||
const nsresult hr = mTaskbarInfo->PrepareFullScreen(aWnd, aMark);
|
||||
|
||||
if (FAILED(hr)) {
|
||||
MOZ_LOG(sTaskbarConcealerLog, LogLevel::Error,
|
||||
("Call to PrepareFullScreen(%p, %s) failed with nsresult %x", aWnd,
|
||||
sMark, uint32_t(hr)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**************************************************************
|
||||
*
|
||||
@@ -311,12 +403,26 @@ void nsWindow::TaskbarConcealer::OnWindowMaximized(nsWindow* aWin) {
|
||||
("==> OnWindowMaximized() for HWND %p on HMONITOR %p", aWin->mWnd,
|
||||
::MonitorFromWindow(aWin->mWnd, MONITOR_DEFAULTTONULL)));
|
||||
|
||||
// This is a workaround for a failure of `PrepareFullScreen`, and is only
|
||||
// useful when that's the only marking-mechanism in play.
|
||||
if (MOZ_LIKELY(TaskbarConcealerImpl::GetMarkingMethod() !=
|
||||
TaskbarConcealerImpl::MarkingMethod::PrepareFullScreen)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we're not using a custom nonclient area, then it's obvious to Windows
|
||||
// that we're not trying to be fullscreen, so the bug won't occur.
|
||||
if (!aWin->mCustomNonClient) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Mark this window, and only this window, as not-fullscreen. Everything else
|
||||
// can stay as it is. (This matches what UpdateAllState would do, if called.)
|
||||
//
|
||||
// Note: this is an unjustified hack. According to the documentation of
|
||||
// `ITaskbarList2::MarkFullscreenWindow()`, it should have no effect, but
|
||||
// testing confirms that it does. See bug 1949079.
|
||||
// testing confirms that it sometimes does. See bug 1949079.
|
||||
//
|
||||
(TaskbarConcealerImpl{}).MarkAsHidingTaskbar(aWin->mWnd, false);
|
||||
}
|
||||
|
||||
@@ -376,8 +482,7 @@ void nsWindow::TaskbarConcealer::OnAsyncStateUpdateRequest(HWND hwnd) {
|
||||
// shell to notify it to recalculate the current rudeness state of all
|
||||
// monitors.
|
||||
//
|
||||
// [0]
|
||||
// https://github.com/dechamps/RudeWindowFixer#a-race-condition-activating-a-minimized-window
|
||||
// [0] https://github.com/dechamps/RudeWindowFixer#a-race-condition-activating-a-minimized-window
|
||||
//
|
||||
static UINT const shellHookMsg = ::RegisterWindowMessageW(L"SHELLHOOK");
|
||||
if (shellHookMsg != 0) {
|
||||
|
||||
Reference in New Issue
Block a user