| # Windows Native Window Occlusion Detection |
| |
| ## Background |
| |
| Ui::aura has an |
| [API](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_occlusion_tracker.h) |
| to track which aura windows are occluded, i.e., covered by |
| one or more other windows. If a window is occluded, Chromium treats foreground |
| tabs as if they were background tabs; rendering stops, and js is throttled. On |
| ChromeOS, since all windows are aura windows, this is sufficient to determine |
| if a Chromium window is covered by other windows. On Windows, we need to |
| consider native app windows when determining if a Chromium window is occluded. |
| This is implemented in |
| [native_window_occlusion_tracker_win.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc). |
| |
| ## Implementation |
| When the core WindowOcclusionTracker decides to track a WindowTreeHost, it |
| calls EnableNativeWindowOcclusionTracking. On non-Windows platforms, this |
| does nothing. On Windows, it calls ::Enable on the singleton |
| NativeWindowOcclusionTrackerWin object, creating it first, if it hasn't already |
| been created. |
| |
| When NativeWindowOcclusionTrackerWin starts tracking a WindowTreeHost, it adds |
| the HWND of the host's root window to a map of Windows HWNDs it is tracking, |
| and its corresponding aura Window. It also starts observing the window to know |
| when its visibility changes, or it is destroyed. |
| |
| The main work of occlusion calculation is done by a helper class, |
| WindowOcclusionCalculator, which runs on a separate COM task runner, in order |
| to not block the UI thread. If the WindowOcclusionCalculator is tracking any |
| windows, it |
| [registers](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc?q=WindowOcclusionCalculator::RegisterEventHooks) |
| a set of |
| [event](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) |
| hooks with Windows, in order to know when |
| the occlusion state might need to be recalculated. These events include window |
| move/resize, minimize/restore, foreground window changing, etc. Most of these |
| are global event hooks, so that we get notified of events for all Windows |
| windows. For windows that could possibly |
| occlude Chromium windows, (i.e., fully visible windows on the current virtual |
| desktop), we register for EVENT_OBJECT_LOCATIONCHANGE events for the window's |
| process. pids_for_location_change_hook_ keeps track of which pids are hooked, |
| and is |
| [used to remove the hook](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc;drc=eeee643ae963e1d78c7457184f8af93f48bba9d3;l=443) |
| if the process no longer has any windows open. |
| |
| When the event handler gets notified of an event, it usually kicks off new |
| occlusion calculation, which runs after a 16ms timer. It doesn't do a new |
| occlusion calculation if the timer is currently running. 16ms corresponds to the |
| interval between frames when displaying 60 frames per second(FPS). There's |
| no point in doing occlusion calculations more frequently than frames are |
| displayed. If the user is in the middle of moving a window around, occlusion |
| isn't calculated until the window stops moving, because moving a window is |
| essentially modal, and there's no point in recalculating occlusion over and |
| over again for each incremental move event. |
| |
| To calculate occlusion, we first mark minimized Chromium windows as hidden, and |
| Chromium windows on a different virtual desktop as occluded. We compute the |
| SKRegion for the virtual screen, which takes multiple monitor configurations |
| into account, and set the initial unoccluded_desktop_region_ to the screen |
| region. Then, we enumerate all the HWNDs, in z-order (topmost window first). |
| For each occluding window (visible, not transparent, etc), we save the current |
| unoccluded_desktop_region_, and subtract the window's window_rect from the |
| unoccluded_desktop_region_ . If the hwnd is not a root Chromium window, we |
| continue to the next hwnd. If it is a root Chromium window, then we have seen |
| all the windows above it, and know whether it is occluded or not. We determine |
| this by checking if subtracting its window_rect from the |
| unoccluded_desktop_region_ actually changed the unoccluded_desktop_region_. If |
| not, that means previous windows occluded the current window's window_rect, and |
| it is occluded, otherwise, not. |
| Once the occlusion state of all root Chromium windows has been determined, the |
| WindowOcclusionTracker posts a task to the ui thread to run a callback on the |
| NativeWindowOcclusionTrackerWin object. That callback is |
| [NativeWindowOcclusionTrackerWin::UpdateOcclusionState](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc;l=226?q=NativeWindowOcclusionTrackerWin::UpdateOcclusionState) |
| , and is passed |
| root_window_hwnds_occlusion_state_, which is a map between root window HWNDs |
| and their calculated occlusion state. |
| NativeWindowOcclusionTrackerWin::UpdateOcclusionState iterates over those HWNDs, |
| finds the corresponding root window, and calls SetNativeWindowOcclusionState on |
| its WindowTreeHost, with the corresponding HWND's occlusion state from the map. |
| If the screen is locked, however, it sets the occlusion state to OCCLUDED. |
| |
| ## Miscellaneous |
| |
| * If a window is falsely determined to be occluded, the content area will be |
| white. |
| * When the screen is locked, all Chromium windows are considered occluded. |
| * Windows on other virtual desktops are considered occluded. |
| * Transparent windows, cloaked windows, floating windows, non-rectangular |
| windows, etc, are not considered occluding. |