blob: fe175880c8f53d2140407bd3aa50cec798ab4ce8 [file] [log] [blame]
// Copyright 2017 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/window_occlusion_tracker.h"
#include "base/auto_reset.h"
#include "base/containers/adapters.h"
#include "base/metrics/histogram_macros.h"
#include "base/stl_util.h"
#include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/env.h"
#include "ui/aura/window_occlusion_change_builder.h"
#include "ui/aura/window_tree_host.h"
#include "ui/gfx/geometry/rect_conversions.h"
#include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/skia_util.h"
#include "ui/gfx/transform.h"
namespace aura {
namespace {
// When one of these properties is animated, a window is considered non-occluded
// and cannot occlude other windows.
constexpr ui::LayerAnimationElement::AnimatableProperties
kSkipWindowWhenPropertiesAnimated =
ui::LayerAnimationElement::TRANSFORM |
ui::LayerAnimationElement::BOUNDS | ui::LayerAnimationElement::OPACITY;
// When an animation ends for one of these properties, occlusion states might
// be affected. The end of an animation for a property in
// |kSkipWindowWhenPropertiesAnimated| might affect occlusion states because
// a window suddenly stops being excluded from occlusion computations. The
// end of a visibility animation might affect occlusion states because a
// window is suddenly considered drawn/not drawn.
constexpr ui::LayerAnimationElement::AnimatableProperties
kOcclusionCanChangeWhenPropertyAnimationEnds =
kSkipWindowWhenPropertiesAnimated |
ui::LayerAnimationElement::VISIBILITY;
// Maximum number of times that MaybeComputeOcclusion() should have to recompute
// occlusion states before they become stable.
//
// TODO(fdoray): This can be changed to 2 once showing/hiding a WebContents
// doesn't cause a call to Show()/Hide() on the Window of a
// RenderWidgetHostViewAura. https://crbug.com/827268
constexpr int kMaxRecomputeOcclusion = 3;
bool WindowOrParentHasShape(Window* window) {
if (window->layer()->alpha_shape())
return true;
if (window->parent())
return WindowOrParentHasShape(window->parent());
return false;
}
bool WindowHasOpaqueRegionsForOcclusion(Window* window) {
return !window->opaque_regions_for_occlusion().empty();
}
// Returns the transform of |window| relative to its root.
// |parent_transform_relative_to_root| is the transform of |window->parent()|
// relative to its root.
gfx::Transform GetWindowTransformRelativeToRoot(
Window* window,
const gfx::Transform& parent_transform_relative_to_root,
bool use_target_values) {
if (window->IsRootWindow())
return gfx::Transform();
// Compute the transform relative to root by concatenating the transform
// of this layer and the transform of the parent relative to root.
// If |use_target_values| is true, use the target bounds and transform instead
// of the true values.
gfx::Transform translation;
gfx::Transform transform_relative_to_root;
if (use_target_values) {
translation.Translate(
static_cast<float>(window->layer()->GetTargetBounds().x()),
static_cast<float>(window->layer()->GetTargetBounds().y()));
transform_relative_to_root = window->layer()->GetTargetTransform();
} else {
translation.Translate(static_cast<float>(window->layer()->bounds().x()),
static_cast<float>(window->layer()->bounds().y()));
transform_relative_to_root = window->layer()->transform();
}
transform_relative_to_root.ConcatTransform(translation);
transform_relative_to_root.ConcatTransform(parent_transform_relative_to_root);
return transform_relative_to_root;
}
SkIRect ComputeClippedAndTransformedBounds(
const gfx::Rect& bounds,
const gfx::Transform& transform_relative_to_root,
const SkIRect* clipped_bounds) {
DCHECK(transform_relative_to_root.Preserves2dAxisAlignment());
gfx::RectF transformed_bounds(bounds);
transform_relative_to_root.TransformRect(&transformed_bounds);
SkIRect skirect_bounds =
gfx::RectToSkIRect(gfx::ToEnclosedRect(transformed_bounds));
// If necessary, clip the bounds.
if (clipped_bounds && !skirect_bounds.intersect(*clipped_bounds))
return SkIRect::MakeEmpty();
return skirect_bounds;
}
// Returns the bounds of |window| relative to its |root|.
// |transform_relative_to_root| is the transform of |window| relative to its
// root. If |clipped_bounds| is not null, the returned bounds are clipped by it.
SkIRect GetWindowBoundsInRootWindow(
Window* window,
const gfx::Transform& transform_relative_to_root,
const SkIRect* clipped_bounds,
bool use_target_values) {
// Compute the unclipped bounds of |window|.
const gfx::Rect src_bounds =
use_target_values ? window->layer()->GetTargetBounds() : window->bounds();
return ComputeClippedAndTransformedBounds(
gfx::Rect(src_bounds.size()), transform_relative_to_root, clipped_bounds);
}
// Returns the bounds that |window| should contribute to be used for occluding
// other windows. This is different to the bounds of the window if |window|
// has opaque regions for occlusion set. We need to use different sets of bounds
// for computing the occlusion of a window itself versus what it should
// contribute to occluding other windows because a translucent region should
// not be considered to occlude other windows, but must be covered by something
// opaque for it itself to be occluded.
SkIRect GetOpaqueBoundsInRootWindow(
Window* window,
const gfx::Transform& transform_relative_to_root,
const SkIRect* clipped_bounds) {
DCHECK(WindowHasOpaqueRegionsForOcclusion(window));
// TODO: Currently, we only support one Rect in the opaque region.
DCHECK_EQ(1u, window->opaque_regions_for_occlusion().size());
// Don't let clients mark regions outside their window bounds as opaque.
gfx::Rect opaque_region = window->opaque_regions_for_occlusion()[0];
opaque_region.Intersect(window->bounds());
return ComputeClippedAndTransformedBounds(
opaque_region, transform_relative_to_root, clipped_bounds);
}
float GetLayerCombinedTargetOpacity(const ui::Layer* layer) {
float opacity = layer->GetTargetOpacity();
const ui::Layer* current = layer->parent();
while (current) {
opacity *= current->GetTargetOpacity();
current = current->parent();
}
return opacity;
}
} // namespace
WindowOcclusionTracker::ScopedPause::ScopedPause() {
Env::GetInstance()->PauseWindowOcclusionTracking();
}
WindowOcclusionTracker::ScopedPause::~ScopedPause() {
Env::GetInstance()->UnpauseWindowOcclusionTracking();
}
WindowOcclusionTracker::ScopedExclude::ScopedExclude(Window* window)
: window_(window) {
window->AddObserver(this);
Env::GetInstance()->GetWindowOcclusionTracker()->Exclude(window_);
}
WindowOcclusionTracker::ScopedExclude::~ScopedExclude() {
Shutdown();
}
void WindowOcclusionTracker::ScopedExclude::OnWindowDestroying(Window* window) {
DCHECK_EQ(window_, window);
Shutdown();
}
void WindowOcclusionTracker::ScopedExclude::Shutdown() {
if (window_) {
window_->RemoveObserver(this);
Env::GetInstance()->GetWindowOcclusionTracker()->Unexclude(window_);
window_ = nullptr;
}
}
WindowOcclusionTracker::ScopedForceVisible::ScopedForceVisible(Window* window)
: window_(window) {
window_->AddObserver(this);
Env::GetInstance()->GetWindowOcclusionTracker()->ForceWindowVisible(window_);
}
WindowOcclusionTracker::ScopedForceVisible::~ScopedForceVisible() {
Shutdown();
}
void WindowOcclusionTracker::ScopedForceVisible::OnWindowDestroying(
Window* window) {
DCHECK_EQ(window_, window);
Shutdown();
}
void WindowOcclusionTracker::ScopedForceVisible::Shutdown() {
if (window_) {
window_->RemoveObserver(this);
Env::GetInstance()->GetWindowOcclusionTracker()->RemoveForceWindowVisible(
window_);
window_ = nullptr;
}
}
void WindowOcclusionTracker::Track(Window* window) {
DCHECK(window);
DCHECK(window != window->GetRootWindow());
auto insert_result = tracked_windows_.insert({window, {}});
if (!insert_result.second)
return;
if (!window_observer_.IsObserving(window))
window_observer_.Add(window);
if (window->GetRootWindow())
TrackedWindowAddedToRoot(window);
}
WindowOcclusionTracker::OcclusionData
WindowOcclusionTracker::ComputeTargetOcclusionForWindow(Window* window) {
// Compute the occlusion with target state, just for this window.
// This doesn't update the occlusion states of any window, so we should only
// require one pass.
auto tracked_window_iter = tracked_windows_.find(window);
DCHECK(tracked_window_iter != tracked_windows_.end());
base::AutoReset<OcclusionData> auto_reset_occlusion_data(
&tracked_window_iter->second, OcclusionData());
DCHECK(!target_occlusion_window_);
base::AutoReset<Window*> auto_reset_target_occlusion_window(
&target_occlusion_window_, window);
Window* root_window = window->GetRootWindow();
SkRegion occluded_region;
RecomputeOcclusionImpl(root_window, gfx::Transform(), nullptr,
&occluded_region);
return tracked_window_iter->second;
}
WindowOcclusionTracker::WindowOcclusionTracker() = default;
WindowOcclusionTracker::~WindowOcclusionTracker() = default;
bool WindowOcclusionTracker::OcclusionStatesMatch(
const base::flat_map<Window*, OcclusionData>& tracked_windows) {
for (const auto& tracked_window : tracked_windows) {
if (tracked_window.second.occlusion_state !=
tracked_window.first->occlusion_state())
return false;
}
return true;
}
void WindowOcclusionTracker::MaybeComputeOcclusion() {
if (num_pause_occlusion_tracking_ ||
num_times_occlusion_recomputed_in_current_step_ != 0) {
return;
}
base::AutoReset<int> auto_reset(
&num_times_occlusion_recomputed_in_current_step_, 0);
// Recompute occlusion states until either:
// - They are stable, i.e. calling Window::SetOcclusionInfo() on all tracked
// windows does not provoke changes that could affect occlusion.
// - Occlusion states have been recomputed
// |kMaxComputeOcclusionIterationsBeforeStable|
// times.
// If occlusion states have been recomputed
// |kMaxComputeOcclusionIterationsBeforeStable| times and are still not
// stable, iterate one last time to set the occlusion state of all tracked
// windows based on IsVisible().
while (num_times_occlusion_recomputed_in_current_step_ <=
kMaxRecomputeOcclusion) {
const bool exceeded_max_num_times_occlusion_recomputed =
num_times_occlusion_recomputed_in_current_step_ ==
kMaxRecomputeOcclusion;
bool found_dirty_root = false;
// Compute occlusion states and store them in |tracked_windows_|. Do not
// call Window::SetOcclusionInfo() in this phase to prevent changes to the
// window tree while it is being traversed.
for (auto& root_window_pair : root_windows_) {
if (root_window_pair.second.dirty) {
found_dirty_root = true;
root_window_pair.second.dirty = false;
if (!exceeded_max_num_times_occlusion_recomputed) {
Window* root_window = root_window_pair.first;
if (root_window_pair.second.occlusion_state ==
Window::OcclusionState::OCCLUDED) {
SetWindowAndDescendantsAreOccluded(
root_window, /* is_occluded */ true, root_window->IsVisible());
} else {
SkRegion occluded_region;
RecomputeOcclusionImpl(root_window, gfx::Transform(), nullptr,
&occluded_region);
}
}
}
}
if (!found_dirty_root)
break;
++num_times_occlusion_recomputed_;
++num_times_occlusion_recomputed_in_current_step_;
std::unique_ptr<WindowOcclusionChangeBuilder> change_builder =
occlusion_change_builder_factory_
? occlusion_change_builder_factory_.Run()
: WindowOcclusionChangeBuilder::Create();
for (auto& it : tracked_windows_) {
Window* window = it.first;
if (it.second.occlusion_state == Window::OcclusionState::UNKNOWN)
continue;
// Fallback to VISIBLE/HIDDEN if the maximum number of times that
// occlusion can be recomputed was exceeded.
if (exceeded_max_num_times_occlusion_recomputed) {
if (WindowIsVisible(window))
it.second.occlusion_state = Window::OcclusionState::VISIBLE;
else
it.second.occlusion_state = Window::OcclusionState::HIDDEN;
it.second.occluded_region = SkRegion();
}
change_builder->Add(window, it.second.occlusion_state,
it.second.occluded_region);
}
}
// Sanity check: Occlusion states in |tracked_windows_| should match those
// returned by Window::occlusion_state().
DCHECK(OcclusionStatesMatch(tracked_windows_));
}
bool WindowOcclusionTracker::RecomputeOcclusionImpl(
Window* window,
const gfx::Transform& parent_transform_relative_to_root,
const SkIRect* clipped_bounds,
SkRegion* occluded_region) {
DCHECK(window);
const bool force_visible = WindowIsForcedVisible(window);
// This does not use Window::IsVisible() as that returns the wrong thing for
// any ancestors that are forced visible.
const bool is_visible =
force_visible ||
(ShouldUseTargetValues() ? window->layer()->GetTargetVisibility()
: window->layer()->visible());
if (!is_visible) {
SetWindowAndDescendantsAreOccluded(window, /* is_occluded */ true,
/* is_parent_visible */ true);
return false;
}
if (WindowIsAnimated(window) || WindowIsExcluded(window)) {
SetWindowAndDescendantsAreOccluded(window, /* is_occluded */ false,
/* is_parent_visible */ true);
return true;
}
// Compute window bounds.
const gfx::Transform transform_relative_to_root =
GetWindowTransformRelativeToRoot(
window, parent_transform_relative_to_root, ShouldUseTargetValues());
if (!transform_relative_to_root.Preserves2dAxisAlignment()) {
// For simplicity, windows that are not axis-aligned are considered
// unoccluded and do not occlude other windows.
SetWindowAndDescendantsAreOccluded(window, /* is_occluded */ false,
/* is_parent_visible */ true);
return true;
}
const SkIRect window_bounds = GetWindowBoundsInRootWindow(
window, transform_relative_to_root,
force_visible ? nullptr : clipped_bounds, ShouldUseTargetValues());
// Compute children occlusion states.
const SkIRect* clipped_bounds_for_children =
window->layer()->GetMasksToBounds() ? &window_bounds : clipped_bounds;
bool has_visible_child = false;
SkRegion occluded_region_before_traversing_children = *occluded_region;
// Windows that are forced visible are always considered visible, so have no
// clip.
SkRegion region_for_forced_visible_windows;
SkRegion* occluded_region_for_children =
force_visible ? &region_for_forced_visible_windows : occluded_region;
for (auto* child : base::Reversed(window->children())) {
has_visible_child |= RecomputeOcclusionImpl(
child, transform_relative_to_root, clipped_bounds_for_children,
occluded_region_for_children);
}
// Window is fully occluded.
if (!force_visible && occluded_region->contains(window_bounds) &&
!has_visible_child) {
SetOccluded(window, /* is_occluded */ true, /* is_parent_visible */ true,
SkRegion());
return false;
}
// Window is partially occluded or unoccluded. Windows that are forced visible
// are considered completely visible (so they get an empty SkRegion()).
SetOccluded(
window, false, /* is_parent_visible */ true,
force_visible ? SkRegion() : occluded_region_before_traversing_children);
if (!force_visible && VisibleWindowCanOccludeOtherWindows(window)) {
const SkIRect occlusion_bounds =
WindowHasOpaqueRegionsForOcclusion(window)
? GetOpaqueBoundsInRootWindow(window, transform_relative_to_root,
clipped_bounds)
: window_bounds;
occluded_region->op(occlusion_bounds, SkRegion::kUnion_Op);
}
return true;
}
bool WindowOcclusionTracker::VisibleWindowCanOccludeOtherWindows(
Window* window) const {
DCHECK(window->layer());
const float combined_opacity =
ShouldUseTargetValues() ? GetLayerCombinedTargetOpacity(window->layer())
: window->layer()->GetCombinedOpacity();
return (!window->transparent() && WindowHasContent(window) &&
combined_opacity == 1.0f &&
// For simplicity, a shaped window is not considered opaque.
!WindowOrParentHasShape(window)) ||
WindowHasOpaqueRegionsForOcclusion(window);
}
bool WindowOcclusionTracker::WindowHasContent(Window* window) const {
if (window->layer()->type() != ui::LAYER_NOT_DRAWN)
return true;
if (window_has_content_callback_)
return window_has_content_callback_.Run(window);
return false;
}
void WindowOcclusionTracker::CleanupAnimatedWindows() {
base::EraseIf(animated_windows_, [=](Window* window) {
ui::LayerAnimator* const animator = window->layer()->GetAnimator();
if (animator->IsAnimatingOnePropertyOf(
kOcclusionCanChangeWhenPropertyAnimationEnds))
return false;
animator->RemoveObserver(this);
MarkRootWindowAsDirty(window->GetRootWindow());
return true;
});
}
bool WindowOcclusionTracker::MaybeObserveAnimatedWindow(Window* window) {
// MaybeObserveAnimatedWindow() is called when OnWindowBoundsChanged(),
// OnWindowTransformed() or OnWindowOpacitySet() is called with
// ui::PropertyChangeReason::FROM_ANIMATION. Despite that, if the animation is
// ending, the IsAnimatingOnePropertyOf() call below may return false. It is
// important not to register an observer in that case because it would never
// be notified.
ui::LayerAnimator* const animator = window->layer()->GetAnimator();
if (animator->IsAnimatingOnePropertyOf(
kOcclusionCanChangeWhenPropertyAnimationEnds)) {
const auto insert_result = animated_windows_.insert(window);
if (insert_result.second) {
animator->AddObserver(this);
return true;
}
}
return false;
}
void WindowOcclusionTracker::SetWindowAndDescendantsAreOccluded(
Window* window,
bool is_occluded,
bool is_parent_visible) {
const bool force_visible = WindowIsForcedVisible(window);
const bool is_visible =
force_visible || (is_parent_visible && window->layer()->visible());
is_occluded = is_occluded && !force_visible;
SetOccluded(window, is_occluded, is_visible, SkRegion());
for (Window* child_window : window->children())
SetWindowAndDescendantsAreOccluded(child_window, is_occluded, is_visible);
}
void WindowOcclusionTracker::SetOccluded(Window* window,
bool is_occluded,
bool is_parent_visible,
const SkRegion& occluded_region) {
// Don't modify occlusion state if we're just computing occlusion for one
// window.
if (target_occlusion_window_ != nullptr && target_occlusion_window_ != window)
return;
auto tracked_window = tracked_windows_.find(window);
if (tracked_window == tracked_windows_.end())
return;
// Set the occluded region of the window.
tracked_window->second.occluded_region = occluded_region;
const bool is_visible = WindowIsForcedVisible(window) ||
(is_parent_visible && window->layer()->visible());
if (!is_visible)
tracked_window->second.occlusion_state = Window::OcclusionState::HIDDEN;
else if (is_occluded)
tracked_window->second.occlusion_state = Window::OcclusionState::OCCLUDED;
else
tracked_window->second.occlusion_state = Window::OcclusionState::VISIBLE;
DCHECK(tracked_window->second.occlusion_state ==
Window::OcclusionState::VISIBLE ||
tracked_window->second.occluded_region.isEmpty());
}
bool WindowOcclusionTracker::WindowIsTracked(Window* window) const {
return base::Contains(tracked_windows_, window);
}
bool WindowOcclusionTracker::WindowIsAnimated(Window* window) const {
return !ShouldUseTargetValues() &&
base::Contains(animated_windows_, window) &&
window->layer()->GetAnimator()->IsAnimatingOnePropertyOf(
kSkipWindowWhenPropertiesAnimated);
}
bool WindowOcclusionTracker::WindowIsExcluded(Window* window) const {
return base::Contains(excluded_windows_, window);
}
bool WindowOcclusionTracker::WindowIsVisible(Window* window) const {
if (forced_visible_count_map_.empty())
return window->IsVisible();
Window* w = window;
Window* last = w;
while (w) {
if (!WindowIsForcedVisible(window)) {
if (ShouldUseTargetValues() && !w->layer()->GetTargetVisibility())
return false;
if (!ShouldUseTargetValues() && !w->layer()->visible())
return false;
}
last = w;
w = w->parent();
}
return last->IsRootWindow();
}
bool WindowOcclusionTracker::WindowIsForcedVisible(Window* window) const {
return forced_visible_count_map_.count(window) > 0;
}
template <typename Predicate>
void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
Window* window,
Predicate predicate) {
Window* root_window = window->GetRootWindow();
if (!root_window)
return;
auto root_window_state_it = root_windows_.find(root_window);
// This may be called if a WindowObserver or a LayoutManager changes |window|
// after Window::AddChild() has added it to a new root but before
// OnWindowAddedToRootWindow() is called on |this|. In that case, do nothing
// here and rely on OnWindowAddedToRootWindow() to mark the new root as dirty.
if (root_window_state_it == root_windows_.end()) {
DCHECK(WindowIsTracked(window));
return;
}
if (root_window_state_it->second.dirty)
return;
if (predicate()) {
MarkRootWindowStateAsDirty(&root_window_state_it->second);
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::MarkRootWindowStateAsDirty(
RootWindowState* root_window_state) {
// If a root window is marked as dirty and occlusion states have already been
// recomputed |kMaxRecomputeOcclusion| times, it means that they are not
// stabilizing.
DCHECK_LT(num_times_occlusion_recomputed_in_current_step_,
kMaxRecomputeOcclusion);
root_window_state->dirty = true;
}
bool WindowOcclusionTracker::MarkRootWindowAsDirty(Window* root_window) {
auto root_window_state_it = root_windows_.find(root_window);
if (root_window_state_it == root_windows_.end())
return false;
MarkRootWindowStateAsDirty(&root_window_state_it->second);
return true;
}
bool WindowOcclusionTracker::WindowOrParentIsAnimated(Window* window) const {
while (window && !WindowIsAnimated(window))
window = window->parent();
return window != nullptr;
}
bool WindowOcclusionTracker::WindowOrDescendantIsTrackedAndVisible(
Window* window) const {
if (!WindowIsVisible(window))
return false;
if (WindowIsTracked(window))
return true;
for (Window* child_window : window->children()) {
if (WindowOrDescendantIsTrackedAndVisible(child_window))
return true;
}
return false;
}
bool WindowOcclusionTracker::WindowOrDescendantCanOccludeOtherWindows(
Window* window,
bool assume_parent_opaque,
bool assume_window_opaque) const {
const bool parent_window_is_opaque =
assume_parent_opaque || !window->parent() ||
window->parent()->layer()->GetCombinedOpacity() == 1.0f;
const bool window_is_opaque =
parent_window_is_opaque &&
(assume_window_opaque || window->layer()->opacity() == 1.0f);
if (!WindowIsVisible(window) || !window->layer() || !window_is_opaque ||
WindowIsAnimated(window)) {
return false;
}
if ((!window->transparent() && WindowHasContent(window)) ||
WindowHasOpaqueRegionsForOcclusion(window)) {
return true;
}
for (Window* child_window : window->children()) {
if (WindowOrDescendantCanOccludeOtherWindows(child_window, true))
return true;
}
return false;
}
bool WindowOcclusionTracker::WindowOpacityChangeMayAffectOcclusionStates(
Window* window) const {
// Changing the opacity of a window has no effect on the occlusion state of
// the window or its children. It can however affect the occlusion state of
// other windows in the tree if it is visible and not animated (animated
// windows aren't considered in occlusion computations), unless it is
// excluded.
return WindowIsVisible(window) && !WindowOrParentIsAnimated(window) &&
!WindowIsExcluded(window);
}
bool WindowOcclusionTracker::WindowMoveMayAffectOcclusionStates(
Window* window) const {
return !WindowOrParentIsAnimated(window) && !WindowIsExcluded(window) &&
(WindowOrDescendantCanOccludeOtherWindows(window) ||
WindowOrDescendantIsTrackedAndVisible(window));
}
void WindowOcclusionTracker::TrackedWindowAddedToRoot(Window* window) {
Window* const root_window = window->GetRootWindow();
DCHECK(root_window);
RootWindowState& root_window_state = root_windows_[root_window];
++root_window_state.num_tracked_windows;
MarkRootWindowStateAsDirty(&root_window_state);
// It's only useful to track the host if |window| is the first tracked window
// under |root_window|. All windows under the same root have the same host.
if (root_window_state.num_tracked_windows == 1) {
AddObserverToWindowAndDescendants(root_window);
auto* host = root_window->GetHost();
if (host) {
host->AddObserver(this);
host->EnableNativeWindowOcclusionTracking();
}
}
MaybeComputeOcclusion();
}
void WindowOcclusionTracker::TrackedWindowRemovedFromRoot(Window* window) {
Window* const root_window = window->GetRootWindow();
DCHECK(root_window);
auto root_window_state_it = root_windows_.find(root_window);
DCHECK(root_window_state_it != root_windows_.end());
--root_window_state_it->second.num_tracked_windows;
if (root_window_state_it->second.num_tracked_windows == 0) {
RemoveObserverFromWindowAndDescendants(root_window);
root_windows_.erase(root_window_state_it);
root_window->GetHost()->RemoveObserver(this);
root_window->GetHost()->DisableNativeWindowOcclusionTracking();
}
}
void WindowOcclusionTracker::RemoveObserverFromWindowAndDescendants(
Window* window) {
if (WindowIsTracked(window)) {
DCHECK(window_observer_.IsObserving(window));
} else {
if (window_observer_.IsObserving(window))
window_observer_.Remove(window);
window->layer()->GetAnimator()->RemoveObserver(this);
animated_windows_.erase(window);
}
for (Window* child_window : window->children())
RemoveObserverFromWindowAndDescendants(child_window);
}
void WindowOcclusionTracker::AddObserverToWindowAndDescendants(Window* window) {
if (WindowIsTracked(window)) {
DCHECK(window_observer_.IsObserving(window));
} else {
DCHECK(!window_observer_.IsObserving(window));
window_observer_.Add(window);
}
for (Window* child_window : window->children())
AddObserverToWindowAndDescendants(child_window);
}
void WindowOcclusionTracker::Pause() {
++num_pause_occlusion_tracking_;
}
void WindowOcclusionTracker::Unpause() {
--num_pause_occlusion_tracking_;
DCHECK_GE(num_pause_occlusion_tracking_, 0);
MaybeComputeOcclusion();
}
void WindowOcclusionTracker::Exclude(Window* window) {
// If threre is a valid use case to exclude the same window twice
// (e.g. independent clients may try to exclude the same window),
// introduce the count.
DCHECK(!WindowIsExcluded(window));
excluded_windows_.insert(window);
if (WindowIsVisible(window)) {
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::Unexclude(Window* window) {
DCHECK(WindowIsExcluded(window));
excluded_windows_.erase(window);
if (WindowIsVisible(window)) {
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::ForceWindowVisible(Window* window) {
if (forced_visible_count_map_[window]++ == 0) {
Window* root_window = window->GetRootWindow();
if (root_window && MarkRootWindowAsDirty(root_window))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::RemoveForceWindowVisible(Window* window) {
auto iter = forced_visible_count_map_.find(window);
DCHECK(iter != forced_visible_count_map_.end());
if (--iter->second == 0u) {
forced_visible_count_map_.erase(iter);
Window* root_window = window->GetRootWindow();
if (root_window && MarkRootWindowAsDirty(root_window))
MaybeComputeOcclusion();
}
}
bool WindowOcclusionTracker::ShouldUseTargetValues() const {
return target_occlusion_window_;
}
void WindowOcclusionTracker::OnLayerAnimationEnded(
ui::LayerAnimationSequence* sequence) {
CleanupAnimatedWindows();
MaybeComputeOcclusion();
}
void WindowOcclusionTracker::OnLayerAnimationAborted(
ui::LayerAnimationSequence* sequence) {
CleanupAnimatedWindows();
MaybeComputeOcclusion();
}
void WindowOcclusionTracker::OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) {}
void WindowOcclusionTracker::OnWindowHierarchyChanged(
const HierarchyChangeParams& params) {
Window* const window = params.target;
Window* const root_window = window->GetRootWindow();
if (root_window && base::Contains(root_windows_, root_window) &&
!window_observer_.IsObserving(window)) {
AddObserverToWindowAndDescendants(window);
}
}
void WindowOcclusionTracker::OnWindowAdded(Window* window) {
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
window, [=]() { return WindowMoveMayAffectOcclusionStates(window); });
}
void WindowOcclusionTracker::OnWillRemoveWindow(Window* window) {
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return !WindowOrParentIsAnimated(window) &&
WindowOrDescendantCanOccludeOtherWindows(window);
});
}
void WindowOcclusionTracker::OnWindowVisibilityChanged(Window* window,
bool visible) {
MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
// A child isn't visible when its parent isn't IsVisible(). Therefore, there
// is no need to compute occlusion when Show() or Hide() is called on a
// window with a hidden parent.
return (!window->parent() || WindowIsVisible(window->parent())) &&
!WindowOrParentIsAnimated(window);
});
}
void WindowOcclusionTracker::OnWindowBoundsChanged(
Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) {
// Call MaybeObserveAnimatedWindow() outside the lambda so that the window can
// be marked as animated even when its root is dirty.
const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started || WindowMoveMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnWindowOpacitySet(
Window* window,
ui::PropertyChangeReason reason) {
// Call MaybeObserveAnimatedWindow() outside the lambda so that the window can
// be marked as animated even when its root is dirty.
const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started ||
WindowOpacityChangeMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnWindowAlphaShapeSet(Window* window) {
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return WindowOpacityChangeMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnWindowTransparentChanged(Window* window) {
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return WindowOpacityChangeMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnWindowTransformed(
Window* window,
ui::PropertyChangeReason reason) {
// Call MaybeObserveAnimatedWindow() outside the lambda so that the window can
// be marked as animated even when its root is dirty.
const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started || WindowMoveMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnWindowStackingChanged(Window* window) {
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
window, [=]() { return WindowMoveMayAffectOcclusionStates(window); });
}
void WindowOcclusionTracker::OnWindowDestroyed(Window* window) {
DCHECK(!window->GetRootWindow() || (window == window->GetRootWindow()));
tracked_windows_.erase(window);
window_observer_.Remove(window);
// Animations should be completed or aborted before a window is destroyed.
DCHECK(!window->layer()->GetAnimator()->IsAnimatingOnePropertyOf(
kOcclusionCanChangeWhenPropertyAnimationEnds));
// |window| must be removed from |animated_windows_| to prevent an invalid
// access in CleanupAnimatedWindows() if |window| is being destroyed from a
// LayerAnimationObserver after an animation has ended but before |this| has
// been notified.
animated_windows_.erase(window);
}
void WindowOcclusionTracker::OnWindowAddedToRootWindow(Window* window) {
DCHECK(window->GetRootWindow());
if (WindowIsTracked(window))
TrackedWindowAddedToRoot(window);
}
void WindowOcclusionTracker::OnWindowRemovingFromRootWindow(Window* window,
Window* new_root) {
DCHECK(window->GetRootWindow());
if (WindowIsTracked(window))
TrackedWindowRemovedFromRoot(window);
RemoveObserverFromWindowAndDescendants(window);
}
void WindowOcclusionTracker::OnWindowLayerRecreated(Window* window) {
ui::LayerAnimator* animator = window->layer()->GetAnimator();
// Recreating the layer may have stopped animations.
if (animator->IsAnimatingOnePropertyOf(
kOcclusionCanChangeWhenPropertyAnimationEnds))
return;
size_t num_removed = animated_windows_.erase(window);
if (num_removed == 0)
return;
animator->RemoveObserver(this);
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
void WindowOcclusionTracker::OnWindowOpaqueRegionsForOcclusionChanged(
Window* window) {
// If the opaque regions for occlusion change, the occlusion state may be
// affected if the effective opacity of the window changes (e.g. clearing the
// regions for occlusion), or if their bounds change.
MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return WindowOpacityChangeMayAffectOcclusionStates(window) ||
WindowMoveMayAffectOcclusionStates(window);
});
}
void WindowOcclusionTracker::OnOcclusionStateChanged(
WindowTreeHost* host,
Window::OcclusionState new_state) {
UMA_HISTOGRAM_ENUMERATION("WindowOcclusionChanged", new_state);
Window* root_window = host->window();
auto root_window_state_it = root_windows_.find(root_window);
if (root_window_state_it != root_windows_.end())
root_window_state_it->second.occlusion_state = new_state;
MarkRootWindowAsDirty(root_window);
MaybeComputeOcclusion();
}
} // namespace aura