/* -*- 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/. */
// Can be called on Main thread
LazyLogModule gWinOcclusionTrackerLog("WinOcclusionTracker"); #define LOG(type, ...) MOZ_LOG(gWinOcclusionTrackerLog, type, (__VA_ARGS__))
// Can be called on OcclusionCalculator thread
LazyLogModule gWinOcclusionCalculatorLog("WinOcclusionCalculator"); #define CALC_LOG(type, ...) \
MOZ_LOG(gWinOcclusionCalculatorLog, type, (__VA_ARGS__))
// ~16 ms = time between frames when frame rate is 60 FPS. constint kOcclusionUpdateRunnableDelayMs = 16;
class OcclusionUpdateRunnable : public CancelableRunnable { public: explicit OcclusionUpdateRunnable(
WinWindowOcclusionTracker::WindowOcclusionCalculator*
aOcclusionCalculator)
: CancelableRunnable("OcclusionUpdateRunnable"),
mOcclusionCalculator(aOcclusionCalculator) {
mTimeStamp = TimeStamp::Now();
}
// Used to serialize tasks related to mRootWindowHwndsOcclusionState. class SerializedTaskDispatcher {
NS_INLINE_DECL_THREADSAFE_REFCOUNTING(SerializedTaskDispatcher)
// Get front task
{ auto data = mData.Lock(); if (data->mDestroyed) { return;
}
MOZ_RELEASE_ASSERT(data->mCurrentRunnable);
MOZ_RELEASE_ASSERT(!data->mTasks.empty());
// Get next task
{ auto data = mData.Lock(); if (data->mDestroyed) { return;
}
frontTask = nullptr;
data->mTasks.pop(); // Check if next task could be handled on current thread if (!data->mTasks.empty() &&
data->mTasks.front().second == mCurrentEventTarget) {
frontTask = data->mTasks.front().first;
}
}
}
MOZ_ASSERT(!frontTask);
// Post tasks to different thread if pending tasks exist.
{ auto data = mData.Lock();
data->mCurrentRunnable = nullptr;
mCurrentEventTarget = nullptr;
if (data->mDestroyed || data->mTasks.empty()) { return;
}
if (sTracker) { // Try to reuse the thread, which involves stopping and restarting it.
sTracker->mThread->Stop(); if (sTracker->mThread->StartWithOptions(options)) { // Success!
sTracker->mHasAttemptedShutdown = false; return;
} // Restart failed, so null out our sTracker and try again with a new // thread. This will cause the old singleton instance to be deallocated, // which will destroy its mThread as well.
sTracker = nullptr;
}
// Our thread could hang while we're waiting for it to stop. // Since we're shutting down, that's not a critical problem. // We set a reasonable amount of time to wait for shutdown, // and if it succeeds within that time, we correctly stop // our thread by nulling out the refptr, which will cause it // to be deallocated and join the thread. If it times out, // we do nothing, which means that the thread will not be // joined and sTracker memory will leak.
CVStatus status;
{ // It's important to hold the lock before posting the // runnable. This ensures that the runnable can't begin // until we've started our Wait, which prevents us from // Waiting on a monitor that has already been notified.
MonitorAutoLock lock(sTracker->mMonitor);
// Monitor uses SleepConditionVariableSRW, which can have // spurious wakeups which are reported as timeouts, so we // check timestamps to ensure that we've waited as long we // intended to. If we wake early, we don't bother calculating // a precise amount for the next wait; we just wait the same // amount of time. This means timeout might happen after as // much as 2x the TIMEOUT time.
TimeStamp timeStart = TimeStamp::NowLoRes(); do {
status = sTracker->mMonitor.Wait(TIMEOUT);
} while ((status == CVStatus::Timeout) &&
((TimeStamp::NowLoRes() - timeStart) < TIMEOUT));
}
// static bool WinWindowOcclusionTracker::IsWindowVisibleAndFullyOpaque(
HWND aHwnd, LayoutDeviceIntRect* aWindowRect) { // Filter out windows that are not "visible", IsWindowVisible(). if (!::IsWindow(aHwnd) || !::IsWindowVisible(aHwnd)) { returnfalse;
}
// Filter out minimized windows. if (::IsIconic(aHwnd)) { returnfalse;
}
LONG exStyles = ::GetWindowLong(aHwnd, GWL_EXSTYLE); // Filter out "transparent" windows, windows where the mouse clicks fall // through them. if (exStyles & WS_EX_TRANSPARENT) { returnfalse;
}
// Filter out "tool windows", which are floating windows that do not appear on // the taskbar or ALT-TAB. Floating windows can have larger window rectangles // than what is visible to the user, so by filtering them out we will avoid // incorrectly marking native windows as occluded. We do not filter out the // Windows Taskbar. if (exStyles & WS_EX_TOOLWINDOW) {
nsAutoString className; if (WinUtils::GetClassName(aHwnd, className)) { if (!className.Equals(L"Shell_TrayWnd")) { returnfalse;
}
}
}
// Filter out layered windows that are not opaque or that set a transparency // colorkey. if (exStyles & WS_EX_LAYERED) {
BYTE alpha;
DWORD flags;
// GetLayeredWindowAttributes only works if the application has // previously called SetLayeredWindowAttributes on the window. // The function will fail if the layered window was setup with // UpdateLayeredWindow. Treat this failure as the window being transparent. // See Remarks section of // https://docs.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getlayeredwindowattributes if (!::GetLayeredWindowAttributes(aHwnd, nullptr, &alpha, &flags)) { returnfalse;
}
if (flags & LWA_ALPHA && alpha < 255) { returnfalse;
} if (flags & LWA_COLORKEY) { returnfalse;
}
}
// Filter out windows that do not have a simple rectangular region.
HRGN region = ::CreateRectRgn(0, 0, 0, 0); int result = GetWindowRgn(aHwnd, region);
::DeleteObject(region); if (result == COMPLEXREGION) { returnfalse;
}
// Windows 10 has cloaked windows, windows with WS_VISIBLE attribute but // not displayed. explorer.exe, in particular has one that's the // size of the desktop. It's usually behind Chrome windows in the z-order, // but using a remote desktop can move it up in the z-order. So, ignore them.
DWORD reason; if (SUCCEEDED(::DwmGetWindowAttribute(aHwnd, DWMWA_CLOAKED, &reason, sizeof(reason))) &&
reason != 0) { returnfalse;
}
RECT winRect; // Filter out windows that take up zero area. The call to GetWindowRect is one // of the most expensive parts of this function, so it is last. if (!::GetWindowRect(aHwnd, &winRect)) { returnfalse;
} if (::IsRectEmpty(&winRect)) { returnfalse;
}
// Ignore popup windows since they're transient unless it is the Windows // Taskbar // XXX Chrome Widget popup handling is removed for now. if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
nsAutoString className; if (WinUtils::GetClassName(aHwnd, className)) { if (!className.Equals(L"Shell_TrayWnd")) { returnfalse;
}
}
}
WINDOWPLACEMENT windowPlacement = {0};
windowPlacement.length = sizeof(WINDOWPLACEMENT);
::GetWindowPlacement(aHwnd, &windowPlacement); if (windowPlacement.showCmd == SW_MAXIMIZE) { // If the window is maximized the window border extends beyond the visible // region of the screen. Adjust the maximized window rect to fit the // screen dimensions to ensure that fullscreen windows, which do not extend // beyond the screen boundaries since they typically have no borders, will // occlude maximized windows underneath them.
HMONITOR hmon = ::MonitorFromWindow(aHwnd, MONITOR_DEFAULTTONEAREST); if (hmon) {
MONITORINFO mi;
mi.cbSize = sizeof(mi); if (GetMonitorInfo(hmon, &mi)) {
LayoutDeviceIntRect workArea(mi.rcWork.left, mi.rcWork.top,
mi.rcWork.right - mi.rcWork.left,
mi.rcWork.bottom - mi.rcWork.top); // Adjust aWindowRect to fit to monitor.
aWindowRect->width = std::min(workArea.width, aWindowRect->width); if (aWindowRect->x < workArea.x) {
aWindowRect->x = workArea.x;
} else {
aWindowRect->x = std::min(workArea.x + workArea.width,
aWindowRect->x + aWindowRect->width) -
aWindowRect->width;
}
aWindowRect->height = std::min(workArea.height, aWindowRect->height); if (aWindowRect->y < workArea.y) {
aWindowRect->y = workArea.y;
} else {
aWindowRect->y = std::min(workArea.y + workArea.height,
aWindowRect->y + aWindowRect->height) -
aWindowRect->height;
}
}
}
}
mNumVisibleRootWindows = 0; for (auto& [hwnd, state] : *aMap) { auto it = mHwndRootWindowMap.find(hwnd); // The window was destroyed while processing occlusion. if (it == mHwndRootWindowMap.end()) { continue;
} auto occlState = state;
// If the screen is locked or off, ignore occlusion state results and // mark the window as occluded. if (mScreenLocked || !mDisplayOn) {
occlState = OcclusionState::OCCLUDED;
} elseif (aShowAllWindows) {
occlState = OcclusionState::VISIBLE;
}
nsCOMPtr<nsIWidget> widget = do_QueryReferent(it->second); if (!widget) { continue;
} auto* baseWidget = static_cast<nsBaseWidget*>(widget.get());
baseWidget->NotifyOcclusionState(occlState); if (baseWidget->SizeMode() != nsSizeMode_Minimized) {
mNumVisibleRootWindows++;
}
}
}
if (aStatusCode == WTS_SESSION_UNLOCK) {
LOG(LogLevel::Info, "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_UNLOCK");
// UNLOCK will cause a foreground window change, which will // trigger an occlusion calculation on its own.
mScreenLocked = false;
} elseif (aStatusCode == WTS_SESSION_LOCK) {
LOG(LogLevel::Info, "WinWindowOcclusionTracker::OnSessionChange() WTS_SESSION_LOCK");
mDisplayOn = aDisplayOn; if (aDisplayOn) { // Notify the window occlusion calculator of the display turning on // which will schedule an occlusion calculation. This must be run // on the WindowOcclusionCalculator thread.
RefPtr<Runnable> runnable =
WrapRunnable(RefPtr<WindowOcclusionCalculator>(
WindowOcclusionCalculator::GetInstance()),
&WindowOcclusionCalculator::HandleVisibilityChanged, /* aVisible */ true);
mSerializedTaskDispatcher->PostTaskToCalculator(runnable.forget());
} else {
MarkNonIconicWindowsOccluded();
}
}
// Set all visible root windows as occluded. If not visible, // set them as hidden. for (auto& [hwnd, weak] : mHwndRootWindowMap) {
nsCOMPtr<nsIWidget> widget = do_QueryReferent(weak); if (!widget) { continue;
} auto* baseWidget = static_cast<nsBaseWidget*>(widget.get()); auto state = (baseWidget->SizeMode() == nsSizeMode_Minimized)
? OcclusionState::HIDDEN
: OcclusionState::OCCLUDED;
baseWidget->NotifyOcclusionState(state);
}
}
if (mGlobalEventHooks.empty()) {
RegisterEventHooks();
}
// Schedule an occlusion calculation so that the newly tracked window does // not have a stale occlusion status.
ScheduleOcclusionCalculationIfNeeded();
}
// May have gone from having no visible windows to having one, in // which case we need to register event hooks, and make sure that an // occlusion calculation is scheduled. if (aVisible) {
MaybeRegisterEventHooks();
ScheduleOcclusionCalculationIfNeeded();
}
}
if (mOcclusionUpdateRunnable) {
mOcclusionUpdateRunnable = nullptr;
}
if (mRootWindowHwndsOcclusionState.empty()) { return;
}
// Set up initial conditions for occlusion calculation. bool shouldUnregisterEventHooks = true;
// Compute the LayoutDeviceIntRegion for the screen. int screenLeft = ::GetSystemMetrics(SM_XVIRTUALSCREEN); int screenTop = ::GetSystemMetrics(SM_YVIRTUALSCREEN); int screenWidth = ::GetSystemMetrics(SM_CXVIRTUALSCREEN); int screenHeight = ::GetSystemMetrics(SM_CYVIRTUALSCREEN);
LayoutDeviceIntRegion screenRegion =
LayoutDeviceIntRect(screenLeft, screenTop, screenWidth, screenHeight);
mNumRootWindowsWithUnknownOcclusionState = 0;
for (auto& [hwnd, state] : mRootWindowHwndsOcclusionState) { // IsIconic() checks for a minimized window. Immediately set the state of // minimized windows to HIDDEN. if (::IsIconic(hwnd)) {
state = OcclusionState::HIDDEN;
} elseif (IsWindowOnCurrentVirtualDesktop(hwnd) == Some(false)) { // If window is not on the current virtual desktop, immediately // set the state of the window to OCCLUDED.
state = OcclusionState::OCCLUDED; // Don't unregister event hooks when not on current desktop. There's no // notification when that changes, so we can't reregister event hooks.
shouldUnregisterEventHooks = false;
} else {
state = OcclusionState::UNKNOWN;
shouldUnregisterEventHooks = false;
mNumRootWindowsWithUnknownOcclusionState++;
}
}
// Unregister event hooks if all native windows are minimized. if (shouldUnregisterEventHooks) {
UnregisterEventHooks();
} else {
std::unordered_set<DWORD> currentPidsWithVisibleWindows;
mUnoccludedDesktopRegion = screenRegion; // Calculate unoccluded region if there is a non-minimized native window. // Also compute |current_pids_with_visible_windows| as we enumerate // the windows.
EnumWindows(&ComputeNativeWindowOcclusionStatusCallback, reinterpret_cast<LPARAM>(¤tPidsWithVisibleWindows)); // Check if mPidsForLocationChangeHook has any pids of processes // currently without visible windows. If so, unhook the win event, // remove the pid from mPidsForLocationChangeHook and remove // the corresponding event hook from mProcessEventHooks.
std::unordered_set<DWORD> pidsToRemove; for (auto locChangePid : mPidsForLocationChangeHook) { if (currentPidsWithVisibleWindows.find(locChangePid) ==
currentPidsWithVisibleWindows.end()) { // Remove the event hook from our map, and unregister the event hook. // It's possible the eventhook will no longer be valid, but if we don't // unregister the event hook, a process that toggles between having // visible windows and not having visible windows could cause duplicate // event hooks to get registered for the process.
UnhookWinEvent(mProcessEventHooks[locChangePid]);
mProcessEventHooks.erase(locChangePid);
pidsToRemove.insert(locChangePid);
}
} if (!pidsToRemove.empty()) { // XXX simplify for (auto it = mPidsForLocationChangeHook.begin();
it != mPidsForLocationChangeHook.end();) { if (pidsToRemove.find(*it) != pidsToRemove.end()) {
it = mPidsForLocationChangeHook.erase(it);
} else {
++it;
}
}
}
}
// Detects objects getting shown and hidden. Used to know when the task bar // and alt tab are showing preview windows so we can unocclude windows.
RegisterGlobalEventHook(EVENT_OBJECT_SHOW, EVENT_OBJECT_HIDE);
// Detects object state changes, e.g., enable/disable state, native window // maximize and native window restore events.
RegisterGlobalEventHook(EVENT_OBJECT_STATECHANGE, EVENT_OBJECT_STATECHANGE);
// Cloaking and uncloaking of windows should trigger an occlusion calculation. // In particular, switching virtual desktops seems to generate these events.
RegisterGlobalEventHook(EVENT_OBJECT_CLOAKED, EVENT_OBJECT_UNCLOAKED);
// Determine which subset of processes to set EVENT_OBJECT_LOCATIONCHANGE on // because otherwise event throughput is very high, as it generates events // for location changes of all objects, including the mouse moving on top of a // window.
UpdateVisibleWindowProcessIds(); for (DWORD pid : mPidsForLocationChangeHook) {
RegisterEventHookForProcess(pid);
}
}
for (constauto eventHook : mGlobalEventHooks) {
::UnhookWinEvent(eventHook);
}
mGlobalEventHooks.clear();
for (constauto& [pid, eventHook] : mProcessEventHooks) {
::UnhookWinEvent(eventHook);
}
mProcessEventHooks.clear();
mPidsForLocationChangeHook.clear();
}
bool WinWindowOcclusionTracker::WindowOcclusionCalculator::
ProcessComputeNativeWindowOcclusionStatusCallback(
HWND aHwnd, std::unordered_set<DWORD>* aCurrentPidsWithVisibleWindows) {
LayoutDeviceIntRegion currUnoccludedDestkop = mUnoccludedDesktopRegion;
LayoutDeviceIntRect windowRect; bool windowIsOccluding =
WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(aHwnd, &windowRect); if (windowIsOccluding) { // Hook this window's process with EVENT_OBJECT_LOCATION_CHANGE, if we are // not already doing so.
DWORD pid;
::GetWindowThreadProcessId(aHwnd, &pid);
aCurrentPidsWithVisibleWindows->insert(pid); auto it = mProcessEventHooks.find(pid); if (it == mProcessEventHooks.end()) {
RegisterEventHookForProcess(pid);
}
// If no more root windows to consider, return true so we can continue // looking for windows we haven't hooked. if (mNumRootWindowsWithUnknownOcclusionState == 0) { returntrue;
}
mUnoccludedDesktopRegion.SubOut(windowRect);
} elseif (mNumRootWindowsWithUnknownOcclusionState == 0) { // This window can't occlude other windows, but we've determined the // occlusion state of all root windows, so we can return. returntrue;
}
// Ignore moving windows when deciding if windows under it are occluded. if (aHwnd == mMovingWindow) { returntrue;
}
// Check if |hwnd| is a root window; if so, we're done figuring out // if it's occluded because we've seen all the windows "over" it. auto it = mRootWindowHwndsOcclusionState.find(aHwnd); if (it == mRootWindowHwndsOcclusionState.end() ||
it->second != OcclusionState::UNKNOWN) { returntrue;
}
// On Win7, default theme makes root windows have complex regions by // default. But we can still check if their bounding rect is occluded. if (!windowIsOccluding) {
RECT rect; if (::GetWindowRect(aHwnd, &rect) != 0) {
LayoutDeviceIntRect windowRect(
rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
currUnoccludedDestkop.SubOut(windowRect);
}
}
void WinWindowOcclusionTracker::WindowOcclusionCalculator::
ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
HWND aHwnd, LONG aIdObject, LONG aIdChild) {
MOZ_ASSERT(IsInWinWindowOcclusionThread());
// No need to calculate occlusion if a zero HWND generated the event. This // happens if there is no window associated with the event, e.g., mouse move // events. if (!aHwnd) { return;
}
// We only care about events for window objects. In particular, we don't care // about OBJID_CARET, which is spammy. if (aIdObject != OBJID_WINDOW) { return;
}
// We generally ignore events for popup windows, except for when the taskbar // is hidden or Windows Taskbar, in which case we recalculate occlusion. // XXX Chrome Widget popup handling is removed for now. bool calculateOcclusion = true; if (::GetWindowLong(aHwnd, GWL_STYLE) & WS_POPUP) {
nsAutoString className; if (WinUtils::GetClassName(aHwnd, className)) {
calculateOcclusion = className.Equals(L"Shell_TrayWnd");
}
}
// Detect if either the alt tab view or the task list thumbnail is being // shown. If so, mark all non-hidden windows as occluded, and remember that // we're in the showing_thumbnails state. This lasts until we get told that // either the alt tab view or task list thumbnail are hidden. if (aEvent == EVENT_OBJECT_SHOW) { // Avoid getting the aHwnd's class name, and recomputing occlusion, if not // needed. if (mShowingThumbnails) { return;
}
nsAutoString className; if (WinUtils::GetClassName(aHwnd, className)) { constauto name = NS_ConvertUTF16toUTF8(className);
CALC_LOG(LogLevel::Debug, "ProcessEventHookCallback() EVENT_OBJECT_SHOW %s", name.get());
std::unordered_map<HWND, OcclusionState>* map =
&mRootWindowHwndsOcclusionState; bool showAllWindows = mShowingThumbnails;
RefPtr<Runnable> runnable = NS_NewRunnableFunction( "CallUpdateOcclusionState", [map, showAllWindows]() {
WinWindowOcclusionTracker::CallUpdateOcclusionState(
map, showAllWindows);
});
mSerializedTaskDispatcher->PostTaskToMain(runnable.forget());
}
} return;
} elseif (aEvent == EVENT_OBJECT_HIDE) { // Avoid getting the aHwnd's class name, and recomputing occlusion, if not // needed. if (!mShowingThumbnails) { return;
}
nsAutoString className;
WinUtils::GetClassName(aHwnd, className); constauto name = NS_ConvertUTF16toUTF8(className);
CALC_LOG(LogLevel::Debug, "ProcessEventHookCallback() EVENT_OBJECT_HIDE %s",
name.get()); if (name.Equals("MultitaskingViewFrame") ||
name.Equals("TaskListThumbnailWnd")) {
CALC_LOG(LogLevel::Info, "ProcessEventHookCallback() mShowingThumbnails = false");
mShowingThumbnails = false; // Let occlusion calculation fix occlusion state, even though hwnd might // be a popup window.
calculateOcclusion = true;
} else { return;
}
} // Don't continually calculate occlusion while a window is moving (unless it's // a root window), but instead once at the beginning and once at the end. // Remember the window being moved so if it's a root window, we can ignore // it when deciding if windows under it are occluded. elseif (aEvent == EVENT_SYSTEM_MOVESIZESTART) {
mMovingWindow = aHwnd;
} elseif (aEvent == EVENT_SYSTEM_MOVESIZEEND) {
mMovingWindow = 0;
} elseif (mMovingWindow != 0) { if (aEvent == EVENT_OBJECT_LOCATIONCHANGE ||
aEvent == EVENT_OBJECT_STATECHANGE) { // Ignore move events if it's not a root window that's being moved. If it // is a root window, we want to calculate occlusion to support tab // dragging to windows that were occluded when the drag was started but // are no longer occluded. if (mRootWindowHwndsOcclusionState.find(aHwnd) ==
mRootWindowHwndsOcclusionState.end()) { return;
}
} else { // If we get an event that isn't a location/state change, then we probably // missed the movesizeend notification, or got events out of order. In // that case, we want to go back to normal occlusion calculation.
mMovingWindow = 0;
}
}
BOOL onCurrentDesktop;
HRESULT hr = mVirtualDesktopManager->IsWindowOnCurrentVirtualDesktop(
aHwnd, &onCurrentDesktop); if (FAILED(hr)) { // In this case, we do not know the window is in which virtual desktop. return Nothing();
}
if (onCurrentDesktop) { return Some(true);
}
GUID workspaceGuid;
hr = mVirtualDesktopManager->GetWindowDesktopId(aHwnd, &workspaceGuid); if (FAILED(hr)) { // In this case, we do not know the window is in which virtual desktop. return Nothing();
}
// IsWindowOnCurrentVirtualDesktop() is flaky for newly opened windows, // which causes test flakiness. Occasionally, it incorrectly says a window // is not on the current virtual desktop when it is. In this situation, // it also returns GUID_NULL for the desktop id. if (workspaceGuid == GUID_NULL) { // In this case, we do not know if the window is in which virtual desktop. // But we hanle it as on current virtual desktop. // It does not cause a problem to window occlusion. // Since if window is not on current virtual desktop, window size becomes // (0, 0, 0, 0). It makes window occlusion handling explicit. It is // necessary for gtest. return Some(true);
}
return Some(false);
}
#undef LOG #undef CALC_LOG
} // namespace mozilla::widget
¤ Dauer der Verarbeitung: 0.52 Sekunden
(vorverarbeitet)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.