window_occlusion_impl_win.cc 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246
  1. // Copyright 2018 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "ui/aura_extra/window_occlusion_impl_win.h"
  5. #include "base/containers/cxx20_erase.h"
  6. #include "base/win/scoped_gdi_object.h"
  7. #include "ui/aura/window_tree_host.h"
  8. #include "ui/gfx/geometry/rect.h"
  9. namespace aura_extra {
  10. namespace {
  11. // Determines the occlusion status of each aura::Window in |windows_of_interest|
  12. // passed to the constructor. Evaluates a window by subtracting its bounds from
  13. // every window beneath it in the z-order.
  14. class WindowEvaluatorImpl : public WindowEvaluator {
  15. public:
  16. // |windows_of_interest| are the aura::WindowTreeHost's whose occlusion
  17. // status are being calculated. |bounds_delegate| is used to obtain the bounds
  18. // in pixels of each root aura::Window in |windows_of_interest|.
  19. WindowEvaluatorImpl(
  20. const std::vector<aura::WindowTreeHost*>& windows_of_interest,
  21. std::unique_ptr<WindowBoundsDelegate> bounds_delegate);
  22. WindowEvaluatorImpl(const WindowEvaluatorImpl&) = delete;
  23. WindowEvaluatorImpl& operator=(const WindowEvaluatorImpl&) = delete;
  24. ~WindowEvaluatorImpl();
  25. // WindowEvaluator.
  26. bool EvaluateWindow(bool is_relevant,
  27. const gfx::Rect& window_rect_in_pixels,
  28. HWND hwnd) override;
  29. // Returns whether there was at least one visible root aura::Window passed to
  30. // ComputeNativeWindowOcclusionStatus().
  31. bool HasAtLeastOneVisibleWindow() const {
  32. return !unoccluded_regions_.empty();
  33. }
  34. // Called once the occlusion computation is done. Returns |occlusion_states_|
  35. base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
  36. TakeResult();
  37. private:
  38. using WindowRegionPair = std::pair<aura::WindowTreeHost*, SkRegion>;
  39. // Stores intermediate values for the unoccluded regions of an aura::Window in
  40. // pixels. Once an aura::Window::OcclusionState is determined for a root
  41. // aura::Window, that aura::Window is removed from |unoccluded_regions_| and
  42. // added to |occlusion_states_| with the computed
  43. // aura::Window::OcclusionState.
  44. std::vector<WindowRegionPair> unoccluded_regions_;
  45. // Stores the final aura::Window::OcclusionState for each root
  46. // aura::WindowTreeHost that is passed to
  47. // ComputeNativeWindowOcclusionStatus().
  48. base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
  49. occlusion_states_;
  50. };
  51. WindowEvaluatorImpl::WindowEvaluatorImpl(
  52. const std::vector<aura::WindowTreeHost*>& windows_of_interest,
  53. std::unique_ptr<WindowBoundsDelegate> bounds_delegate) {
  54. for (aura::WindowTreeHost* window : windows_of_interest) {
  55. // If the window isn't visible at this time, it is
  56. // aura::Window::OcclusionState::HIDDEN.
  57. if (!window->window()->IsVisible() ||
  58. IsIconic(window->GetAcceleratedWidget())) {
  59. occlusion_states_[window] = aura::Window::OcclusionState::HIDDEN;
  60. continue;
  61. }
  62. gfx::Rect window_rect_in_pixels =
  63. bounds_delegate->GetBoundsInPixels(window);
  64. SkRegion window_region(SkIRect::MakeXYWH(
  65. window_rect_in_pixels.x(), window_rect_in_pixels.y(),
  66. window_rect_in_pixels.width(), window_rect_in_pixels.height()));
  67. unoccluded_regions_.emplace_back(window, window_region);
  68. }
  69. }
  70. WindowEvaluatorImpl::~WindowEvaluatorImpl() = default;
  71. bool WindowEvaluatorImpl::EvaluateWindow(bool is_relevant,
  72. const gfx::Rect& window_rect_in_pixels,
  73. HWND hwnd) {
  74. // Loop through |unoccluded_regions_| and determine how |hwnd| affects each
  75. // root window with respect to occlusion.
  76. for (WindowRegionPair& root_window_pair : unoccluded_regions_) {
  77. HWND window_hwnd = root_window_pair.first->GetAcceleratedWidget();
  78. // The EnumWindows callbacks have reached this window in the Z-order
  79. // (EnumWindows goes from front to back). This window must be visible
  80. // because we did not discover that it was completely occluded earlier.
  81. if (hwnd == window_hwnd) {
  82. occlusion_states_[root_window_pair.first] =
  83. aura::Window::OcclusionState::VISIBLE;
  84. // Set the unoccluded region for this window to empty as a signal that the
  85. // occlusion computation is complete.
  86. root_window_pair.second.setEmpty();
  87. continue;
  88. }
  89. // |hwnd| is not taken into account for occlusion computations, move on.
  90. if (!is_relevant)
  91. continue;
  92. // Current occlusion state for this window cannot be determined yet. The
  93. // EnumWindows callbacks are currently above this window in the Z-order.
  94. // Subtract the other windows bounding rectangle from this window's
  95. // unoccluded region if the two regions intersect.
  96. SkRegion window_region(SkIRect::MakeXYWH(
  97. window_rect_in_pixels.x(), window_rect_in_pixels.y(),
  98. window_rect_in_pixels.width(), window_rect_in_pixels.height()));
  99. if (root_window_pair.second.intersects(window_region)) {
  100. root_window_pair.second.op(window_region, SkRegion::kDifference_Op);
  101. if (root_window_pair.second.isEmpty()) {
  102. occlusion_states_[root_window_pair.first] =
  103. aura::Window::OcclusionState::OCCLUDED;
  104. }
  105. }
  106. }
  107. // Occlusion computation is done for windows with an empty region in
  108. // |unoccluded_regions_|. If the window is visible, the region is set to empty
  109. // explicitly. If it is occluded, the region is implicitly empty.
  110. base::EraseIf(unoccluded_regions_, [](const WindowRegionPair& element) {
  111. return element.second.isEmpty();
  112. });
  113. // If |unoccluded_regions_| is empty, the occlusion calculation is complete.
  114. // So, we return false to signal to EnumWindows to stop enumerating.
  115. // Otherwise, we want EnumWindows to continue, and return true.
  116. return !unoccluded_regions_.empty();
  117. }
  118. base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
  119. WindowEvaluatorImpl::TakeResult() {
  120. return std::move(occlusion_states_);
  121. }
  122. } // namespace
  123. WindowsDesktopWindowIterator::WindowsDesktopWindowIterator() = default;
  124. void WindowsDesktopWindowIterator::Iterate(WindowEvaluator* evaluator) {
  125. evaluator_ = evaluator;
  126. EnumWindows(&EnumWindowsOcclusionCallback, reinterpret_cast<LPARAM>(this));
  127. }
  128. BOOL WindowsDesktopWindowIterator::RunEvaluator(HWND hwnd) {
  129. gfx::Rect window_rect;
  130. bool is_relevant = IsWindowVisibleAndFullyOpaque(hwnd, &window_rect);
  131. return evaluator_->EvaluateWindow(is_relevant, window_rect, hwnd);
  132. }
  133. // static
  134. BOOL CALLBACK
  135. WindowsDesktopWindowIterator::EnumWindowsOcclusionCallback(HWND hwnd,
  136. LPARAM lParam) {
  137. WindowsDesktopWindowIterator* iterator =
  138. reinterpret_cast<WindowsDesktopWindowIterator*>(lParam);
  139. return iterator->RunEvaluator(hwnd);
  140. }
  141. base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState>
  142. ComputeNativeWindowOcclusionStatusImpl(
  143. const std::vector<aura::WindowTreeHost*>& windows,
  144. std::unique_ptr<NativeWindowIterator> iterator,
  145. std::unique_ptr<WindowBoundsDelegate> bounds_delegate) {
  146. // Time to execute this method, according to 28 days of Stable data
  147. // ending on June 15, 2020:
  148. //
  149. // 50th percentile: 156 us
  150. // 75th percentile: 273 us
  151. // 95th percentile: 647 us
  152. // 99th percentile: 1939 us
  153. // 99.5th percentile: 5592 us
  154. WindowEvaluatorImpl window_evaluator(windows, std::move(bounds_delegate));
  155. // Only compute occlusion if there was at least one window that is visible.
  156. if (window_evaluator.HasAtLeastOneVisibleWindow())
  157. iterator->Iterate(&window_evaluator);
  158. return window_evaluator.TakeResult();
  159. }
  160. bool IsWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* rect_in_pixels) {
  161. // Filter out invalid hwnds.
  162. if (!IsWindow(hwnd))
  163. return false;
  164. // Filter out windows that are not “visible”.
  165. if (!IsWindowVisible(hwnd))
  166. return false;
  167. // Filter out minimized windows.
  168. if (IsIconic(hwnd))
  169. return false;
  170. LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE);
  171. // Filter out “transparent” windows, windows where the mouse clicks fall
  172. // through them.
  173. if (ex_styles & WS_EX_TRANSPARENT)
  174. return false;
  175. // Filter out “tool windows”, which are floating windows that do not appear on
  176. // the taskbar or ALT-TAB. Floating windows can have larger window rectangles
  177. // than what is visible to the user, so by filtering them out we will avoid
  178. // incorrectly marking native windows as occluded.
  179. if (ex_styles & WS_EX_TOOLWINDOW)
  180. return false;
  181. // Filter out layered windows.
  182. if (ex_styles & WS_EX_LAYERED)
  183. return false;
  184. // Filter out windows that do not have a simple rectangular region.
  185. base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0));
  186. if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION)
  187. return false;
  188. RECT window_rect;
  189. // Filter out windows that take up zero area. The call to GetWindowRect is one
  190. // of the most expensive parts of this function, so it is last.
  191. if (!GetWindowRect(hwnd, &window_rect))
  192. return false;
  193. if (IsRectEmpty(&window_rect))
  194. return false;
  195. rect_in_pixels->SetByBounds(window_rect.left, window_rect.top,
  196. window_rect.right, window_rect.bottom);
  197. return true;
  198. }
  199. } // namespace aura_extra