Windows Native Window Occlusion Detection

Background

Ui::aura has an API 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.

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 a set of event 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 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 , 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.