| // Copyright 2018 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/aura_extra/window_occlusion_impl_win.h" |
| |
| #include "base/metrics/histogram_macros.h" |
| #include "base/win/scoped_gdi_object.h" |
| #include "ui/aura/window_tree_host.h" |
| #include "ui/gfx/geometry/rect.h" |
| |
| namespace aura_extra { |
| |
| namespace { |
| |
| // Determines the occlusion status of each aura::Window in |windows_of_interest| |
| // passed to the constructor. Evaluates a window by subtracting its bounds from |
| // every window beneath it in the z-order. |
| class WindowEvaluatorImpl : public WindowEvaluator { |
| public: |
| // |windows_of_interest| are the aura::WindowTreeHost's whose occlusion |
| // status are being calculated. |bounds_delegate| is used to obtain the bounds |
| // in pixels of each root aura::Window in |windows_of_interest|. |
| WindowEvaluatorImpl( |
| const std::vector<aura::WindowTreeHost*>& windows_of_interest, |
| std::unique_ptr<WindowBoundsDelegate> bounds_delegate); |
| ~WindowEvaluatorImpl(); |
| |
| // WindowEvaluator. |
| bool EvaluateWindow(bool is_relevant, |
| const gfx::Rect& window_rect_in_pixels, |
| HWND hwnd) override; |
| |
| // Returns whether there was at least one visible root aura::Window passed to |
| // ComputeNativeWindowOcclusionStatus(). |
| bool HasAtLeastOneVisibleWindow() const { |
| return !unoccluded_regions_.empty(); |
| } |
| |
| // Called once the occlusion computation is done. Returns |occlusion_states_| |
| base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState> |
| TakeResult(); |
| |
| private: |
| using WindowRegionPair = std::pair<aura::WindowTreeHost*, SkRegion>; |
| |
| // Stores intermediate values for the unoccluded regions of an aura::Window in |
| // pixels. Once an aura::Window::OcclusionState is determined for a root |
| // aura::Window, that aura::Window is removed from |unoccluded_regions_| and |
| // added to |occlusion_states_| with the computed |
| // aura::Window::OcclusionState. |
| std::vector<WindowRegionPair> unoccluded_regions_; |
| |
| // Stores the final aura::Window::OcclusionState for each root |
| // aura::WindowTreeHost that is passed to |
| // ComputeNativeWindowOcclusionStatus(). |
| base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState> |
| occlusion_states_; |
| |
| DISALLOW_COPY_AND_ASSIGN(WindowEvaluatorImpl); |
| }; |
| |
| WindowEvaluatorImpl::WindowEvaluatorImpl( |
| const std::vector<aura::WindowTreeHost*>& windows_of_interest, |
| std::unique_ptr<WindowBoundsDelegate> bounds_delegate) { |
| for (aura::WindowTreeHost* window : windows_of_interest) { |
| // If the window isn't visible at this time, it is |
| // aura::Window::OcclusionState::HIDDEN. |
| if (!window->window()->IsVisible() || |
| IsIconic(window->GetAcceleratedWidget())) { |
| occlusion_states_[window] = aura::Window::OcclusionState::HIDDEN; |
| continue; |
| } |
| |
| gfx::Rect window_rect_in_pixels = |
| bounds_delegate->GetBoundsInPixels(window); |
| |
| SkRegion window_region(SkIRect::MakeXYWH( |
| window_rect_in_pixels.x(), window_rect_in_pixels.y(), |
| window_rect_in_pixels.width(), window_rect_in_pixels.height())); |
| |
| unoccluded_regions_.emplace_back(window, window_region); |
| } |
| } |
| |
| WindowEvaluatorImpl::~WindowEvaluatorImpl() = default; |
| |
| bool WindowEvaluatorImpl::EvaluateWindow(bool is_relevant, |
| const gfx::Rect& window_rect_in_pixels, |
| HWND hwnd) { |
| // Loop through |unoccluded_regions_| and determine how |hwnd| affects each |
| // root window with respect to occlusion. |
| for (WindowRegionPair& root_window_pair : unoccluded_regions_) { |
| HWND window_hwnd = root_window_pair.first->GetAcceleratedWidget(); |
| |
| // The EnumWindows callbacks have reached this window in the Z-order |
| // (EnumWindows goes from front to back). This window must be visible |
| // because we did not discover that it was completely occluded earlier. |
| if (hwnd == window_hwnd) { |
| occlusion_states_[root_window_pair.first] = |
| aura::Window::OcclusionState::VISIBLE; |
| // Set the unoccluded region for this window to empty as a signal that the |
| // occlusion computation is complete. |
| root_window_pair.second.setEmpty(); |
| continue; |
| } |
| |
| // |hwnd| is not taken into account for occlusion computations, move on. |
| if (!is_relevant) |
| continue; |
| |
| // Current occlusion state for this window cannot be determined yet. The |
| // EnumWindows callbacks are currently above this window in the Z-order. |
| // Subtract the other windows bounding rectangle from this window's |
| // unoccluded region if the two regions intersect. |
| SkRegion window_region(SkIRect::MakeXYWH( |
| window_rect_in_pixels.x(), window_rect_in_pixels.y(), |
| window_rect_in_pixels.width(), window_rect_in_pixels.height())); |
| |
| if (root_window_pair.second.intersects(window_region)) { |
| root_window_pair.second.op(window_region, SkRegion::kDifference_Op); |
| |
| if (root_window_pair.second.isEmpty()) { |
| occlusion_states_[root_window_pair.first] = |
| aura::Window::OcclusionState::OCCLUDED; |
| } |
| } |
| } |
| |
| // Occlusion computation is done for windows with an empty region in |
| // |unoccluded_regions_|. If the window is visible, the region is set to empty |
| // explicitly. If it is occluded, the region is implicitly empty. |
| base::EraseIf(unoccluded_regions_, [](const WindowRegionPair& element) { |
| return element.second.isEmpty(); |
| }); |
| |
| // If |unoccluded_regions_| is empty, the occlusion calculation is complete. |
| // So, we return false to signal to EnumWindows to stop enumerating. |
| // Otherwise, we want EnumWindows to continue, and return true. |
| return !unoccluded_regions_.empty(); |
| } |
| |
| base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState> |
| WindowEvaluatorImpl::TakeResult() { |
| return std::move(occlusion_states_); |
| } |
| |
| } // namespace |
| |
| WindowsDesktopWindowIterator::WindowsDesktopWindowIterator() = default; |
| |
| void WindowsDesktopWindowIterator::Iterate(WindowEvaluator* evaluator) { |
| evaluator_ = evaluator; |
| EnumWindows(&EnumWindowsOcclusionCallback, reinterpret_cast<LPARAM>(this)); |
| } |
| |
| BOOL WindowsDesktopWindowIterator::RunEvaluator(HWND hwnd) { |
| gfx::Rect window_rect; |
| bool is_relevant = IsWindowVisibleAndFullyOpaque(hwnd, &window_rect); |
| return evaluator_->EvaluateWindow(is_relevant, window_rect, hwnd); |
| } |
| |
| // static |
| BOOL CALLBACK |
| WindowsDesktopWindowIterator::EnumWindowsOcclusionCallback(HWND hwnd, |
| LPARAM lParam) { |
| WindowsDesktopWindowIterator* iterator = |
| reinterpret_cast<WindowsDesktopWindowIterator*>(lParam); |
| return iterator->RunEvaluator(hwnd); |
| } |
| |
| base::flat_map<aura::WindowTreeHost*, aura::Window::OcclusionState> |
| ComputeNativeWindowOcclusionStatusImpl( |
| const std::vector<aura::WindowTreeHost*>& windows, |
| std::unique_ptr<NativeWindowIterator> iterator, |
| std::unique_ptr<WindowBoundsDelegate> bounds_delegate) { |
| base::TimeTicks calculation_start_time = base::TimeTicks::Now(); |
| |
| WindowEvaluatorImpl window_evaluator(windows, std::move(bounds_delegate)); |
| |
| // Only compute occlusion if there was at least one window that is visible. |
| if (window_evaluator.HasAtLeastOneVisibleWindow()) |
| iterator->Iterate(&window_evaluator); |
| |
| UMA_HISTOGRAM_CUSTOM_MICROSECONDS_TIMES( |
| "Windows.ComputeNativeWindowOcclusionTime", |
| base::TimeTicks::Now() - calculation_start_time, |
| base::TimeDelta::FromMicroseconds(1), base::TimeDelta::FromSeconds(10), |
| 50); |
| |
| return window_evaluator.TakeResult(); |
| } |
| |
| bool IsWindowVisibleAndFullyOpaque(HWND hwnd, gfx::Rect* rect_in_pixels) { |
| // Filter out invalid hwnds. |
| if (!IsWindow(hwnd)) |
| return false; |
| |
| // Filter out windows that are not “visible”. |
| if (!IsWindowVisible(hwnd)) |
| return false; |
| |
| // Filter out minimized windows. |
| if (IsIconic(hwnd)) |
| return false; |
| |
| LONG ex_styles = GetWindowLong(hwnd, GWL_EXSTYLE); |
| |
| // Filter out “transparent” windows, windows where the mouse clicks fall |
| // through them. |
| if (ex_styles & WS_EX_TRANSPARENT) |
| return false; |
| |
| // Filter out “tool windows”, which are floating windows that do not appear on |
| // the taskbar or ALT-TAB. Floating windows can have larger window rectangles |
| // than what is visible to the user, so by filtering them out we will avoid |
| // incorrectly marking native windows as occluded. |
| if (ex_styles & WS_EX_TOOLWINDOW) |
| return false; |
| |
| // Filter out layered windows. |
| if (ex_styles & WS_EX_LAYERED) |
| return false; |
| |
| // Filter out windows that do not have a simple rectangular region. |
| base::win::ScopedRegion region(CreateRectRgn(0, 0, 0, 0)); |
| if (GetWindowRgn(hwnd, region.get()) == COMPLEXREGION) |
| return false; |
| |
| RECT window_rect; |
| // Filter out windows that take up zero area. The call to GetWindowRect is one |
| // of the most expensive parts of this function, so it is last. |
| if (!GetWindowRect(hwnd, &window_rect)) |
| return false; |
| if (IsRectEmpty(&window_rect)) |
| return false; |
| |
| rect_in_pixels->SetByBounds(window_rect.left, window_rect.top, |
| window_rect.right, window_rect.bottom); |
| return true; |
| } |
| |
| } // namespace aura_extra |