// 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/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. // TODO(crbug.com/1057024): Mark a window VISIBLE when COLOR animation starts. constexpr ui::LayerAnimationElement::AnimatableProperties kSkipWindowWhenPropertiesAnimated = ui::LayerAnimationElement::TRANSFORM | ui::LayerAnimationElement::BOUNDS | ui::LayerAnimationElement::OPACITY | ui::LayerAnimationElement::COLOR; // 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(window->layer()->GetTargetBounds().x()), static_cast(window->layer()->GetTargetBounds().y())); transform_relative_to_root = window->layer()->GetTargetTransform(); } else { translation.Translate(static_cast(window->layer()->bounds().x()), static_cast(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. // Note: opaque_regions_for_occlusion() are relative to the window, i.e. the // top-left corner of the window is considered to be the point (0, 0). gfx::Rect opaque_region = window->opaque_regions_for_occlusion()[0]; opaque_region.Intersect(gfx::Rect(window->bounds().size())); 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 auto_reset_occlusion_data( &tracked_window_iter->second, OcclusionData()); DCHECK(!target_occlusion_window_); base::AutoReset 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& 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 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 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; } // TODO: While considering that a window whose color is animated doesn't // occlude other windows helps reduce the number of times that occlusion is // recomputed, it isn't necessary to consider that the window whose color is // animated itself is non-occluded. 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 ? ®ion_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()); float combined_opacity = ShouldUseTargetValues() ? GetLayerCombinedTargetOpacity(window->layer()) : window->layer()->GetCombinedOpacity(); // Just check the alpha on this layer as an alpha on parent solid color layers // will not affect children's opacity. if (window->layer()->type() == ui::LAYER_SOLID_COLOR) { auto color = ShouldUseTargetValues() ? window->layer()->GetTargetColor() : window->layer()->background_color(); combined_opacity *= SkColorGetA(color) / 255.f; } 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 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); native_window_occlusion_tracker_.EnableNativeWindowOcclusionTracking( host); } } 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); native_window_occlusion_tracker_.DisableNativeWindowOcclusionTracking( root_window->GetHost()); } } 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, 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::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