David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 1 | # Windows Native Window Occlusion Detection |
| 2 | |
| 3 | ## Background |
| 4 | |
| 5 | Ui::aura has an |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 6 | [API](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/window_occlusion_tracker.h) |
David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 7 | to track which aura windows are occluded, i.e., covered by |
| 8 | one or more other windows. If a window is occluded, Chromium treats foreground |
| 9 | tabs as if they were background tabs; rendering stops, and js is throttled. On |
| 10 | ChromeOS, since all windows are aura windows, this is sufficient to determine |
| 11 | if a Chromium window is covered by other windows. On Windows, we need to |
| 12 | consider native app windows when determining if a Chromium window is occluded. |
| 13 | This is implemented in |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 14 | [native_window_occlusion_tracker_win.cc](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc). |
David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 15 | |
| 16 | ## Implementation |
| 17 | When the core WindowOcclusionTracker decides to track a WindowTreeHost, it |
| 18 | calls EnableNativeWindowOcclusionTracking. On non-Windows platforms, this |
| 19 | does nothing. On Windows, it calls ::Enable on the singleton |
| 20 | NativeWindowOcclusionTrackerWin object, creating it first, if it hasn't already |
| 21 | been created. |
| 22 | |
| 23 | When NativeWindowOcclusionTrackerWin starts tracking a WindowTreeHost, it adds |
| 24 | the HWND of the host's root window to a map of Windows HWNDs it is tracking, |
| 25 | and its corresponding aura Window. It also starts observing the window to know |
| 26 | when its visibility changes, or it is destroyed. |
| 27 | |
| 28 | The main work of occlusion calculation is done by a helper class, |
| 29 | WindowOcclusionCalculator, which runs on a separate COM task runner, in order |
| 30 | to not block the UI thread. If the WindowOcclusionCalculator is tracking any |
| 31 | windows, it |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 32 | [registers](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc?q=WindowOcclusionCalculator::RegisterEventHooks) |
David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 33 | a set of |
| 34 | [event](https://docs.microsoft.com/en-us/windows/win32/winauto/event-constants) |
| 35 | hooks with Windows, in order to know when |
| 36 | the occlusion state might need to be recalculated. These events include window |
| 37 | move/resize, minimize/restore, foreground window changing, etc. Most of these |
| 38 | are global event hooks, so that we get notified of events for all Windows |
| 39 | windows. For windows that could possibly |
| 40 | occlude Chromium windows, (i.e., fully visible windows on the current virtual |
| 41 | desktop), we register for EVENT_OBJECT_LOCATIONCHANGE events for the window's |
| 42 | process. pids_for_location_change_hook_ keeps track of which pids are hooked, |
| 43 | and is |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 44 | [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) |
David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 45 | if the process no longer has any windows open. |
| 46 | |
| 47 | When the event handler gets notified of an event, it usually kicks off new |
| 48 | occlusion calculation, which runs after a 16ms timer. It doesn't do a new |
| 49 | occlusion calculation if the timer is currently running. 16ms corresponds to the |
| 50 | interval between frames when displaying 60 frames per second(FPS). There's |
| 51 | no point in doing occlusion calculations more frequently than frames are |
| 52 | displayed. If the user is in the middle of moving a window around, occlusion |
| 53 | isn't calculated until the window stops moving, because moving a window is |
| 54 | essentially modal, and there's no point in recalculating occlusion over and |
| 55 | over again for each incremental move event. |
| 56 | |
| 57 | To calculate occlusion, we first mark minimized Chromium windows as hidden, and |
| 58 | Chromium windows on a different virtual desktop as occluded. We compute the |
| 59 | SKRegion for the virtual screen, which takes multiple monitor configurations |
| 60 | into account, and set the initial unoccluded_desktop_region_ to the screen |
| 61 | region. Then, we enumerate all the HWNDs, in z-order (topmost window first). |
| 62 | For each occluding window (visible, not transparent, etc), we save the current |
| 63 | unoccluded_desktop_region_, and subtract the window's window_rect from the |
| 64 | unoccluded_desktop_region_ . If the hwnd is not a root Chromium window, we |
| 65 | continue to the next hwnd. If it is a root Chromium window, then we have seen |
| 66 | all the windows above it, and know whether it is occluded or not. We determine |
| 67 | this by checking if subtracting its window_rect from the |
| 68 | unoccluded_desktop_region_ actually changed the unoccluded_desktop_region_. If |
| 69 | not, that means previous windows occluded the current window's window_rect, and |
| 70 | it is occluded, otherwise, not. |
| 71 | Once the occlusion state of all root Chromium windows has been determined, the |
| 72 | WindowOcclusionTracker posts a task to the ui thread to run a callback on the |
| 73 | NativeWindowOcclusionTrackerWin object. That callback is |
John Palmer | 046f987 | 2021-05-24 01:24:56 | [diff] [blame] | 74 | [NativeWindowOcclusionTrackerWin::UpdateOcclusionState](https://source.chromium.org/chromium/chromium/src/+/main:ui/aura/native_window_occlusion_tracker_win.cc;l=226?q=NativeWindowOcclusionTrackerWin::UpdateOcclusionState) |
David Bienvenu | bded26a | 2020-09-21 17:17:57 | [diff] [blame] | 75 | , and is passed |
| 76 | root_window_hwnds_occlusion_state_, which is a map between root window HWNDs |
| 77 | and their calculated occlusion state. |
| 78 | NativeWindowOcclusionTrackerWin::UpdateOcclusionState iterates over those HWNDs, |
| 79 | finds the corresponding root window, and calls SetNativeWindowOcclusionState on |
| 80 | its WindowTreeHost, with the corresponding HWND's occlusion state from the map. |
| 81 | If the screen is locked, however, it sets the occlusion state to OCCLUDED. |
| 82 | |
| 83 | ## Miscellaneous |
| 84 | |
| 85 | * If a window is falsely determined to be occluded, the content area will be |
| 86 | white. |
| 87 | * When the screen is locked, all Chromium windows are considered occluded. |
| 88 | * Windows on other virtual desktops are considered occluded. |
| 89 | * Transparent windows, cloaked windows, floating windows, non-rectangular |
| 90 | windows, etc, are not considered occluding. |