diff --git a/modules/libpref/init/StaticPrefList.yaml b/modules/libpref/init/StaticPrefList.yaml index 5ad1784074e9..a34cccaf3cf5 100644 --- a/modules/libpref/init/StaticPrefList.yaml +++ b/modules/libpref/init/StaticPrefList.yaml @@ -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). # diff --git a/widget/windows/nsWindowTaskbarConcealer.cpp b/widget/windows/nsWindowTaskbarConcealer.cpp index becff78d4c10..ff4d23eb70cc 100644 --- a/widget/windows/nsWindowTaskbarConcealer.cpp +++ b/widget/windows/nsWindowTaskbarConcealer.cpp @@ -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 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(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) {