1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4  * License, v. 2.0. If a copy of the MPL was not distributed with this
5  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
6 
7 #ifndef widget_windows_WinWindowOcclusionTracker_h
8 #define widget_windows_WinWindowOcclusionTracker_h
9 
10 #include <string>
11 #include <unordered_map>
12 #include <unordered_set>
13 #include <vector>
14 
15 #include "nsIWeakReferenceUtils.h"
16 #include "mozilla/ThreadSafeWeakPtr.h"
17 #include "mozilla/widget/WindowOcclusionState.h"
18 #include "mozilla/widget/WinEventObserver.h"
19 
20 class nsBaseWidget;
21 struct IVirtualDesktopManager;
22 class WinWindowOcclusionTrackerTest;
23 class WinWindowOcclusionTrackerInteractiveTest;
24 
25 namespace base {
26 class Thread;
27 }  // namespace base
28 
29 namespace mozilla {
30 
31 namespace layers {
32 class SynchronousTask;
33 }
34 
35 namespace widget {
36 
37 class OcclusionUpdateRunnable;
38 class SerializedTaskDispatcher;
39 class UpdateOcclusionStateRunnable;
40 
41 // This class handles window occlusion tracking by using HWND.
42 // Implementation is borrowed from chromium's NativeWindowOcclusionTrackerWin.
43 class WinWindowOcclusionTracker final : public DisplayStatusListener,
44                                         public SessionChangeListener {
45  public:
46   NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WinWindowOcclusionTracker)
47 
48   /// Can only be called from the main thread.
49   static WinWindowOcclusionTracker* Get();
50 
51   /// Can only be called from the main thread.
52   static void Ensure();
53 
54   /// Can only be called from the main thread.
55   static void ShutDown();
56 
57   /// Can be called from any thread.
58   static MessageLoop* OcclusionCalculatorLoop();
59 
60   /// Can be called from any thread.
61   static bool IsInWinWindowOcclusionThread();
62 
63   /// Can only be called from the main thread.
64   void EnsureDisplayStatusObserver();
65 
66   /// Can only be called from the main thread.
67   void EnsureSessionChangeObserver();
68 
69   // Enables notifying to widget via NotifyOcclusionState() when the occlusion
70   // state has been computed.
71   void Enable(nsBaseWidget* aWindow, HWND aHwnd);
72 
73   // Disables notifying to widget via NotifyOcclusionState() when the occlusion
74   // state has been computed.
75   void Disable(nsBaseWidget* aWindow, HWND aHwnd);
76 
77   // Called when widget's visibility is changed
78   void OnWindowVisibilityChanged(nsBaseWidget* aWindow, bool aVisible);
79 
GetSerializedTaskDispatcher()80   SerializedTaskDispatcher* GetSerializedTaskDispatcher() {
81     return mSerializedTaskDispatcher;
82   }
83 
84   void TriggerCalculation();
85 
86   void DumpOccludingWindows(HWND aHWnd);
87 
88  private:
89   friend class ::WinWindowOcclusionTrackerTest;
90   friend class ::WinWindowOcclusionTrackerInteractiveTest;
91 
92   explicit WinWindowOcclusionTracker(base::Thread* aThread);
93   virtual ~WinWindowOcclusionTracker();
94 
95   // This class computes the occlusion state of the tracked windows.
96   // It runs on a separate thread, and notifies the main thread of
97   // the occlusion state of the tracked windows.
98   class WindowOcclusionCalculator {
99     NS_INLINE_DECL_THREADSAFE_REFCOUNTING(WindowOcclusionCalculator)
100    public:
101     // Creates WindowOcclusionCalculator instance.
102     static void CreateInstance();
103 
104     // Clear WindowOcclusionCalculator instance.
105     static void ClearInstance();
106 
107     // Returns existing WindowOcclusionCalculator instance.
GetInstance()108     static WindowOcclusionCalculator* GetInstance() { return sCalculator; }
109 
110     void Initialize();
111     void Shutdown(layers::SynchronousTask* aTask);
112 
113     void EnableOcclusionTrackingForWindow(HWND hwnd);
114     void DisableOcclusionTrackingForWindow(HWND hwnd);
115 
116     // If a window becomes visible, makes sure event hooks are registered.
117     void HandleVisibilityChanged(bool aVisible);
118 
119     void HandleTriggerCalculation();
120 
121    private:
122     WindowOcclusionCalculator();
123     ~WindowOcclusionCalculator();
124 
125     // Registers event hooks, if not registered.
126     void MaybeRegisterEventHooks();
127 
128     // This is the callback registered to get notified of various Windows
129     // events, like window moving/resizing.
130     static void CALLBACK EventHookCallback(HWINEVENTHOOK aWinEventHook,
131                                            DWORD aEvent, HWND aHwnd,
132                                            LONG aIdObject, LONG aIdChild,
133                                            DWORD aEventThread,
134                                            DWORD aMsEventTime);
135 
136     // EnumWindows callback used to iterate over all hwnds to determine
137     // occlusion status of all tracked root windows.  Also builds up
138     // |current_pids_with_visible_windows_| and registers event hooks for newly
139     // discovered processes with visible hwnds.
140     static BOOL CALLBACK
141     ComputeNativeWindowOcclusionStatusCallback(HWND hwnd, LPARAM lParam);
142 
143     // EnumWindows callback used to update the list of process ids with
144     // visible hwnds, |pids_for_location_change_hook_|.
145     static BOOL CALLBACK UpdateVisibleWindowProcessIdsCallback(HWND aHwnd,
146                                                                LPARAM aLParam);
147 
148     // Determines which processes owning visible application windows to set the
149     // EVENT_OBJECT_LOCATIONCHANGE event hook for and stores the pids in
150     // |pids_for_location_change_hook_|.
151     void UpdateVisibleWindowProcessIds();
152 
153     // Computes the native window occlusion status for all tracked root gecko
154     // windows in |root_window_hwnds_occlusion_state_| and notifies them if
155     // their occlusion status has changed.
156     void ComputeNativeWindowOcclusionStatus();
157 
158     // Schedules an occlusion calculation , if one isn't already scheduled.
159     void ScheduleOcclusionCalculationIfNeeded();
160 
161     // Registers a global event hook (not per process) for the events in the
162     // range from |event_min| to |event_max|, inclusive.
163     void RegisterGlobalEventHook(DWORD aEventMin, DWORD aEventMax);
164 
165     // Registers the EVENT_OBJECT_LOCATIONCHANGE event hook for the process with
166     // passed id. The process has one or more visible, opaque windows.
167     void RegisterEventHookForProcess(DWORD aPid);
168 
169     // Registers/Unregisters the event hooks necessary for occlusion tracking
170     // via calls to RegisterEventHook. These event hooks are disabled when all
171     // tracked windows are minimized.
172     void RegisterEventHooks();
173     void UnregisterEventHooks();
174 
175     // EnumWindows callback for occlusion calculation. Returns true to
176     // continue enumeration, false otherwise. Currently, always returns
177     // true because this function also updates currentPidsWithVisibleWindows,
178     // and needs to see all HWNDs.
179     bool ProcessComputeNativeWindowOcclusionStatusCallback(
180         HWND aHwnd, std::unordered_set<DWORD>* aCurrentPidsWithVisibleWindows);
181 
182     // Processes events sent to OcclusionEventHookCallback.
183     // It generally triggers scheduling of the occlusion calculation, but
184     // ignores certain events in order to not calculate occlusion more than
185     // necessary.
186     void ProcessEventHookCallback(HWINEVENTHOOK aWinEventHook, DWORD aEvent,
187                                   HWND aHwnd, LONG aIdObject, LONG aIdChild);
188 
189     // EnumWindows callback for determining which processes to set the
190     // EVENT_OBJECT_LOCATIONCHANGE event hook for. We set that event hook for
191     // processes hosting fully visible, opaque windows.
192     void ProcessUpdateVisibleWindowProcessIdsCallback(HWND aHwnd);
193 
194     // Returns true if the window is visible, fully opaque, and on the current
195     // virtual desktop, false otherwise.
196     bool WindowCanOccludeOtherWindowsOnCurrentVirtualDesktop(
197         HWND aHwnd, LayoutDeviceIntRect* aWindowRect);
198 
199     // Returns true if aHwnd is definitely on the current virtual desktop,
200     // false if it's definitely not on the current virtual desktop, and Nothing
201     // if we we can't tell for sure.
202     Maybe<bool> IsWindowOnCurrentVirtualDesktop(HWND aHwnd);
203 
204     static StaticRefPtr<WindowOcclusionCalculator> sCalculator;
205 
206     // Map of root app window hwnds and their occlusion state. This contains
207     // both visible and hidden windows.
208     // It is accessed from WinWindowOcclusionTracker::UpdateOcclusionState()
209     // without using mutex. The access is safe by using
210     // SerializedTaskDispatcher.
211     std::unordered_map<HWND, OcclusionState> mRootWindowHwndsOcclusionState;
212 
213     // Values returned by SetWinEventHook are stored so that hooks can be
214     // unregistered when necessary.
215     std::vector<HWINEVENTHOOK> mGlobalEventHooks;
216 
217     // Map from process id to EVENT_OBJECT_LOCATIONCHANGE event hook.
218     std::unordered_map<DWORD, HWINEVENTHOOK> mProcessEventHooks;
219 
220     // Pids of processes for which the EVENT_OBJECT_LOCATIONCHANGE event hook is
221     // set.
222     std::unordered_set<DWORD> mPidsForLocationChangeHook;
223 
224     // Used as a timer to delay occlusion update.
225     RefPtr<CancelableRunnable> mOcclusionUpdateRunnable;
226 
227     // Used to determine if a window is occluded. As we iterate through the
228     // hwnds in z-order, we subtract each opaque window's rect from
229     // mUnoccludedDesktopRegion. When we get to a root window, we subtract
230     // it from mUnoccludedDesktopRegion, and if mUnoccludedDesktopRegion
231     // doesn't change, the root window was already occluded.
232     LayoutDeviceIntRegion mUnoccludedDesktopRegion;
233 
234     // Keeps track of how many root windows we need to compute the occlusion
235     // state of in a call to ComputeNativeWindowOcclusionStatus. Once we've
236     // determined the state of all root windows, we can stop subtracting
237     // windows from mUnoccludedDesktopRegion;.
238     int mNumRootWindowsWithUnknownOcclusionState;
239 
240     // This is true if the task bar thumbnails or the alt tab thumbnails are
241     // showing.
242     bool mShowingThumbnails = false;
243 
244     // Used to keep track of the window that's currently moving. That window
245     // is ignored for calculation occlusion so that tab dragging won't
246     // ignore windows occluded by the dragged window.
247     HWND mMovingWindow = 0;
248 
249     // Only used on Win10+.
250     RefPtr<IVirtualDesktopManager> mVirtualDesktopManager;
251 
252     // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
253     RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
254 
255     friend class OcclusionUpdateRunnable;
256   };
257 
258   static BOOL CALLBACK DumpOccludingWindowsCallback(HWND aHWnd, LPARAM aLParam);
259 
260   // Returns true if we are interested in |hwnd| for purposes of occlusion
261   // calculation. We are interested in |hwnd| if it is a window that is
262   // visible, opaque, bounded, and not a popup or floating window. If we are
263   // interested in |hwnd|, stores the window rectangle in |window_rect|.
264   static bool IsWindowVisibleAndFullyOpaque(HWND aHwnd,
265                                             LayoutDeviceIntRect* aWindowRect);
266 
267   void Destroy();
268 
269   static void CallUpdateOcclusionState(
270       std::unordered_map<HWND, OcclusionState>* aMap, bool aShowAllWindows);
271 
272   // Updates root windows occclusion state. If aShowAllWindows is true,
273   // all non-hidden windows will be marked visible.  This is used to force
274   // rendering of thumbnails.
275   void UpdateOcclusionState(std::unordered_map<HWND, OcclusionState>* aMap,
276                             bool aShowAllWindows);
277 
278   // This is called with session changed notifications. If the screen is locked
279   // by the current session, it marks app windows as occluded.
280   void OnSessionChange(WPARAM aStatusCode,
281                        Maybe<bool> aIsCurrentSession) override;
282 
283   // This is called when the display is put to sleep. If the display is sleeping
284   // it marks app windows as occluded.
285   void OnDisplayStateChanged(bool aDisplayOn) override;
286 
287   // Marks all root windows as either occluded, or if hwnd IsIconic, hidden.
288   void MarkNonIconicWindowsOccluded();
289 
290   static StaticRefPtr<WinWindowOcclusionTracker> sTracker;
291 
292   // "WinWindowOcclusionCalc" thread.
293   base::Thread* const mThread;
294 
295   // Map of HWND to widget. Maintained on main thread, and used to send
296   // occlusion state notifications to Windows from
297   // mRootWindowHwndsOcclusionState.
298   std::unordered_map<HWND, nsWeakPtr> mHwndRootWindowMap;
299 
300   // This is set by UpdateOcclusionState(). It is currently only used by tests.
301   int mNumVisibleRootWindows = 0;
302 
303   // If the screen is locked, windows are considered occluded.
304   bool mScreenLocked = false;
305 
306   // If the display is off, windows are considered occluded.
307   bool mDisplayOn = true;
308 
309   RefPtr<DisplayStatusObserver> mDisplayStatusObserver;
310 
311   RefPtr<SessionChangeObserver> mSessionChangeObserver;
312 
313   // Used to serialize tasks related to mRootWindowHwndsOcclusionState.
314   RefPtr<SerializedTaskDispatcher> mSerializedTaskDispatcher;
315 
316   friend class OcclusionUpdateRunnable;
317   friend class UpdateOcclusionStateRunnable;
318 };
319 
320 }  // namespace widget
321 }  // namespace mozilla
322 
323 #endif  // widget_windows_WinWindowOcclusionTracker_h
324