Commit a2f523dc authored by Francois Doray's avatar Francois Doray Committed by Commit Bot

Support changing the window tree from aura::WindowDelegate::OnWindowOcclusionChanged.

Some code in Chrome needs to change the window tree in response
to a change of visibility for a WebContents (e.g. close zoom
bubble, create a new RenderWidgetHostViewAura). Because of that,
we need to allow changes to the window tree from
aura::WindowDelegate::OnWindowOcclusionChanged().

With this CL, aura::Window::SetOcclusionState() is not called
immediately when the occlusion of a tracked window is computed.
Instead, we compute occlusion for all tracked windows. Then,
we call aura::Window::SetOcclusionState() on all tracked
windows. Having separate phases ensures that the window tree is
not modified while occlusion is being computed. If a call to
aura::Window::SetOcclusionState() in the 2nd phase changes the
window tree in a way that could affect occlusion, we go back
to phase 1. There is a hard cap of 2 iterations, after which we
crash.

Bug: 668690, 813076
Change-Id: I61e5cdbb605343a5b587aeb96effcbd8dc07de33
Reviewed-on: https://chromium-review.googlesource.com/861953
Commit-Queue: François Doray <fdoray@chromium.org>
Reviewed-by: default avatarSadrul Chowdhury <sadrul@chromium.org>
Cr-Commit-Position: refs/heads/master@{#537310}
parent 250fb9b3
...@@ -4,11 +4,13 @@ ...@@ -4,11 +4,13 @@
#include "ui/aura/window_occlusion_tracker.h" #include "ui/aura/window_occlusion_tracker.h"
#include "base/auto_reset.h"
#include "base/containers/adapters.h" #include "base/containers/adapters.h"
#include "base/debug/dump_without_crashing.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "third_party/skia/include/core/SkRect.h" #include "third_party/skia/include/core/SkRect.h"
#include "third_party/skia/include/core/SkRegion.h" #include "third_party/skia/include/core/SkRegion.h"
#include "ui/aura/window.h" #include "ui/aura/window_tracker.h"
#include "ui/gfx/geometry/safe_integer_conversions.h" #include "ui/gfx/geometry/safe_integer_conversions.h"
#include "ui/gfx/transform.h" #include "ui/gfx/transform.h"
...@@ -23,6 +25,10 @@ constexpr ui::LayerAnimationElement::AnimatableProperties ...@@ -23,6 +25,10 @@ constexpr ui::LayerAnimationElement::AnimatableProperties
ui::LayerAnimationElement::TRANSFORM | ui::LayerAnimationElement::TRANSFORM |
ui::LayerAnimationElement::BOUNDS | ui::LayerAnimationElement::OPACITY; ui::LayerAnimationElement::BOUNDS | ui::LayerAnimationElement::OPACITY;
// Maximum number of times that MaybeComputeOcclusion() should have to recompute
// occlusion states before they become stable.
constexpr int kMaxRecomputeOcclusion = 2;
WindowOcclusionTracker* g_tracker = nullptr; WindowOcclusionTracker* g_tracker = nullptr;
int g_num_pause_occlusion_tracking = 0; int g_num_pause_occlusion_tracking = 0;
...@@ -80,6 +86,17 @@ SkIRect GetWindowBoundsInRootWindow( ...@@ -80,6 +86,17 @@ SkIRect GetWindowBoundsInRootWindow(
return skirect_bounds; return skirect_bounds;
} }
// Returns true iff the occlusion states in |tracked_windows| match those
// returned by Window::occlusion_state().
bool OcclusionStatesMatch(
const base::flat_map<Window*, Window::OcclusionState>& tracked_windows) {
for (const auto& tracked_window : tracked_windows) {
if (tracked_window.second != tracked_window.first->occlusion_state())
return false;
}
return true;
}
} // namespace } // namespace
WindowOcclusionTracker::ScopedPauseOcclusionTracking:: WindowOcclusionTracker::ScopedPauseOcclusionTracking::
...@@ -92,7 +109,7 @@ WindowOcclusionTracker::ScopedPauseOcclusionTracking:: ...@@ -92,7 +109,7 @@ WindowOcclusionTracker::ScopedPauseOcclusionTracking::
--g_num_pause_occlusion_tracking; --g_num_pause_occlusion_tracking;
DCHECK_GE(g_num_pause_occlusion_tracking, 0); DCHECK_GE(g_num_pause_occlusion_tracking, 0);
if (g_tracker) if (g_tracker)
g_tracker->MaybeRecomputeOcclusion(); g_tracker->MaybeComputeOcclusion();
} }
void WindowOcclusionTracker::Track(Window* window) { void WindowOcclusionTracker::Track(Window* window) {
...@@ -102,7 +119,8 @@ void WindowOcclusionTracker::Track(Window* window) { ...@@ -102,7 +119,8 @@ void WindowOcclusionTracker::Track(Window* window) {
if (!g_tracker) if (!g_tracker)
g_tracker = new WindowOcclusionTracker(); g_tracker = new WindowOcclusionTracker();
auto insert_result = g_tracker->tracked_windows_.insert(window); auto insert_result = g_tracker->tracked_windows_.insert(
{window, Window::OcclusionState::UNKNOWN});
DCHECK(insert_result.second); DCHECK(insert_result.second);
if (!window->HasObserver(g_tracker)) if (!window->HasObserver(g_tracker))
window->AddObserver(g_tracker); window->AddObserver(g_tracker);
...@@ -114,22 +132,71 @@ WindowOcclusionTracker::WindowOcclusionTracker() = default; ...@@ -114,22 +132,71 @@ WindowOcclusionTracker::WindowOcclusionTracker() = default;
WindowOcclusionTracker::~WindowOcclusionTracker() = default; WindowOcclusionTracker::~WindowOcclusionTracker() = default;
void WindowOcclusionTracker::MaybeRecomputeOcclusion() { void WindowOcclusionTracker::MaybeComputeOcclusion() {
if (g_num_pause_occlusion_tracking) if (g_num_pause_occlusion_tracking || num_times_occlusion_recomputed_ != 0)
return; return;
for (auto& root_window_pair : root_windows_) {
RootWindowState& root_window_state = root_window_pair.second; base::AutoReset<int> auto_reset(&num_times_occlusion_recomputed_, 0);
if (root_window_state.dirty == true) {
ScopedPauseOcclusionTracking scoped_pause_occlusion_tracking; // Recompute occlusion states until either:
root_window_state.dirty = false; // - They are stable, i.e. calling Window::SetOcclusionState() on all tracked
SkRegion occluded_region; // windows does not provoke changes that could affect occlusion.
RecomputeOcclusionImpl(root_window_pair.first, gfx::Transform(), nullptr, // - Occlusion states have been recomputed
&occluded_region); // |kMaxComputeOcclusionIterationsBeforeStable|
// WindowDelegate::OnWindowOcclusionChanged() impls must not change any // times.
// Window. // If occlusion states have been recomputed
DCHECK(!root_window_state.dirty); // |kMaxComputeOcclusionIterationsBeforeStable| times and are still not
// stable, iterate one last time to set the occlusion state of all tracked
// windows to NOT_OCCLUDED.
while (num_times_occlusion_recomputed_ <= kMaxRecomputeOcclusion) {
const bool exceeded_max_num_times_occlusion_recomputed =
num_times_occlusion_recomputed_ == kMaxRecomputeOcclusion;
bool found_dirty_root = false;
// Compute occlusion states and store them in |tracked_windows_|. Do not
// call Window::SetOcclusionState() 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) {
SkRegion occluded_region;
RecomputeOcclusionImpl(root_window_pair.first, gfx::Transform(),
nullptr, &occluded_region);
}
}
}
++num_times_occlusion_recomputed_;
if (!found_dirty_root)
break;
// Call Window::SetOcclusionState() on tracked windows. A WindowDelegate may
// change the window tree in response to this.
WindowTracker tracked_windows_list;
for (const auto& tracked_window : tracked_windows_)
tracked_windows_list.Add(tracked_window.first);
while (!tracked_windows_list.windows().empty()) {
Window* window = tracked_windows_list.Pop();
auto it = tracked_windows_.find(window);
if (it != tracked_windows_.end() &&
it->second != Window::OcclusionState::UNKNOWN) {
// Fallback to NOT_OCCLUDED for all IsVisible() windows if the maximum
// number of times that occlusion can be recomputed was exceeded.
if (exceeded_max_num_times_occlusion_recomputed && window->IsVisible())
it->second = Window::OcclusionState::NOT_OCCLUDED;
window->SetOccluded(it->second == Window::OcclusionState::OCCLUDED);
}
} }
} }
// Sanity check: Occlusion states in |tracked_windows_| should match those
// returned by Window::occlusion_state().
DCHECK(OcclusionStatesMatch(tracked_windows_));
} }
bool WindowOcclusionTracker::RecomputeOcclusionImpl( bool WindowOcclusionTracker::RecomputeOcclusionImpl(
...@@ -192,7 +259,7 @@ void WindowOcclusionTracker::CleanupAnimatedWindows() { ...@@ -192,7 +259,7 @@ void WindowOcclusionTracker::CleanupAnimatedWindows() {
animator->RemoveObserver(this); animator->RemoveObserver(this);
auto root_window_state_it = root_windows_.find(window->GetRootWindow()); auto root_window_state_it = root_windows_.find(window->GetRootWindow());
if (root_window_state_it != root_windows_.end()) if (root_window_state_it != root_windows_.end())
root_window_state_it->second.dirty = true; MarkRootWindowAsDirty(&root_window_state_it->second);
return true; return true;
}); });
} }
...@@ -224,8 +291,11 @@ void WindowOcclusionTracker::SetWindowAndDescendantsAreOccluded( ...@@ -224,8 +291,11 @@ void WindowOcclusionTracker::SetWindowAndDescendantsAreOccluded(
} }
void WindowOcclusionTracker::SetOccluded(Window* window, bool occluded) { void WindowOcclusionTracker::SetOccluded(Window* window, bool occluded) {
if (WindowIsTracked(window)) auto tracked_window = tracked_windows_.find(window);
window->SetOccluded(occluded); if (tracked_window != tracked_windows_.end()) {
tracked_window->second = occluded ? Window::OcclusionState::OCCLUDED
: Window::OcclusionState::NOT_OCCLUDED;
}
} }
bool WindowOcclusionTracker::WindowIsTracked(Window* window) const { bool WindowOcclusionTracker::WindowIsTracked(Window* window) const {
...@@ -237,7 +307,7 @@ bool WindowOcclusionTracker::WindowIsAnimated(Window* window) const { ...@@ -237,7 +307,7 @@ bool WindowOcclusionTracker::WindowIsAnimated(Window* window) const {
} }
template <typename Predicate> template <typename Predicate>
void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf( void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
Window* window, Window* window,
Predicate predicate) { Predicate predicate) {
Window* root_window = window->GetRootWindow(); Window* root_window = window->GetRootWindow();
...@@ -257,11 +327,28 @@ void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf( ...@@ -257,11 +327,28 @@ void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(
if (root_window_state_it->second.dirty) if (root_window_state_it->second.dirty)
return; return;
if (predicate()) { if (predicate()) {
root_window_state_it->second.dirty = true; MarkRootWindowAsDirty(&root_window_state_it->second);
MaybeRecomputeOcclusion(); MaybeComputeOcclusion();
} }
} }
void WindowOcclusionTracker::MarkRootWindowAsDirty(
RootWindowState* root_window_state) {
root_window_state->dirty = true;
// Generate a crash report when a root window is marked as dirty and occlusion
// states have been recomputed |kMaxRecomputeOcclusion| times, because it
// indicates that they are not stabilizing. Don't report it when
// |num_times_occlusion_recomputed_| is greater than |kMaxRecomputeOcclusion|
// to avoid generating multiple reports from the same client.
//
// TODO(fdoray): Remove this once we are confident that occlusion states are
// stable after |kMaxRecomputeOcclusion| iterations in production.
// https://crbug.com/813076
if (num_times_occlusion_recomputed_ == kMaxRecomputeOcclusion)
base::debug::DumpWithoutCrashing();
}
bool WindowOcclusionTracker::WindowOrParentIsAnimated(Window* window) const { bool WindowOcclusionTracker::WindowOrParentIsAnimated(Window* window) const {
while (window && !WindowIsAnimated(window)) while (window && !WindowIsAnimated(window))
window = window->parent(); window = window->parent();
...@@ -319,8 +406,8 @@ void WindowOcclusionTracker::TrackedWindowAddedToRoot(Window* window) { ...@@ -319,8 +406,8 @@ void WindowOcclusionTracker::TrackedWindowAddedToRoot(Window* window) {
++root_window_state.num_tracked_windows; ++root_window_state.num_tracked_windows;
if (root_window_state.num_tracked_windows == 1) if (root_window_state.num_tracked_windows == 1)
AddObserverToWindowAndDescendants(root_window); AddObserverToWindowAndDescendants(root_window);
root_window_state.dirty = true; MarkRootWindowAsDirty(&root_window_state);
MaybeRecomputeOcclusion(); MaybeComputeOcclusion();
} }
void WindowOcclusionTracker::TrackedWindowRemovedFromRoot(Window* window) { void WindowOcclusionTracker::TrackedWindowRemovedFromRoot(Window* window) {
...@@ -360,13 +447,13 @@ void WindowOcclusionTracker::AddObserverToWindowAndDescendants(Window* window) { ...@@ -360,13 +447,13 @@ void WindowOcclusionTracker::AddObserverToWindowAndDescendants(Window* window) {
void WindowOcclusionTracker::OnLayerAnimationEnded( void WindowOcclusionTracker::OnLayerAnimationEnded(
ui::LayerAnimationSequence* sequence) { ui::LayerAnimationSequence* sequence) {
CleanupAnimatedWindows(); CleanupAnimatedWindows();
MaybeRecomputeOcclusion(); MaybeComputeOcclusion();
} }
void WindowOcclusionTracker::OnLayerAnimationAborted( void WindowOcclusionTracker::OnLayerAnimationAborted(
ui::LayerAnimationSequence* sequence) { ui::LayerAnimationSequence* sequence) {
CleanupAnimatedWindows(); CleanupAnimatedWindows();
MaybeRecomputeOcclusion(); MaybeComputeOcclusion();
} }
void WindowOcclusionTracker::OnLayerAnimationScheduled( void WindowOcclusionTracker::OnLayerAnimationScheduled(
...@@ -383,12 +470,12 @@ void WindowOcclusionTracker::OnWindowHierarchyChanged( ...@@ -383,12 +470,12 @@ void WindowOcclusionTracker::OnWindowHierarchyChanged(
} }
void WindowOcclusionTracker::OnWindowAdded(Window* window) { void WindowOcclusionTracker::OnWindowAdded(Window* window) {
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf( MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
window, [=]() { return WindowMoveMayAffectOcclusionStates(window); }); window, [=]() { return WindowMoveMayAffectOcclusionStates(window); });
} }
void WindowOcclusionTracker::OnWillRemoveWindow(Window* window) { void WindowOcclusionTracker::OnWillRemoveWindow(Window* window) {
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(window, [=]() { MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return !WindowOrParentIsAnimated(window) && return !WindowOrParentIsAnimated(window) &&
WindowOrDescendantIsOpaque(window); WindowOrDescendantIsOpaque(window);
}); });
...@@ -396,7 +483,7 @@ void WindowOcclusionTracker::OnWillRemoveWindow(Window* window) { ...@@ -396,7 +483,7 @@ void WindowOcclusionTracker::OnWillRemoveWindow(Window* window) {
void WindowOcclusionTracker::OnWindowVisibilityChanged(Window* window, void WindowOcclusionTracker::OnWindowVisibilityChanged(Window* window,
bool visible) { bool visible) {
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf( MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
window, [=]() { return !WindowOrParentIsAnimated(window); }); window, [=]() { return !WindowOrParentIsAnimated(window); });
} }
...@@ -410,7 +497,7 @@ void WindowOcclusionTracker::OnWindowBoundsChanged( ...@@ -410,7 +497,7 @@ void WindowOcclusionTracker::OnWindowBoundsChanged(
const bool animation_started = const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) && (reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window); MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(window, [=]() { MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started || WindowMoveMayAffectOcclusionStates(window); return animation_started || WindowMoveMayAffectOcclusionStates(window);
}); });
} }
...@@ -423,7 +510,7 @@ void WindowOcclusionTracker::OnWindowOpacitySet( ...@@ -423,7 +510,7 @@ void WindowOcclusionTracker::OnWindowOpacitySet(
const bool animation_started = const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) && (reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window); MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(window, [=]() { MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started || !WindowOrParentIsAnimated(window); return animation_started || !WindowOrParentIsAnimated(window);
}); });
} }
...@@ -436,13 +523,13 @@ void WindowOcclusionTracker::OnWindowTransformed( ...@@ -436,13 +523,13 @@ void WindowOcclusionTracker::OnWindowTransformed(
const bool animation_started = const bool animation_started =
(reason == ui::PropertyChangeReason::FROM_ANIMATION) && (reason == ui::PropertyChangeReason::FROM_ANIMATION) &&
MaybeObserveAnimatedWindow(window); MaybeObserveAnimatedWindow(window);
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(window, [=]() { MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(window, [=]() {
return animation_started || WindowMoveMayAffectOcclusionStates(window); return animation_started || WindowMoveMayAffectOcclusionStates(window);
}); });
} }
void WindowOcclusionTracker::OnWindowStackingChanged(Window* window) { void WindowOcclusionTracker::OnWindowStackingChanged(Window* window) {
MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf( MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
window, [=]() { return WindowMoveMayAffectOcclusionStates(window); }); window, [=]() { return WindowMoveMayAffectOcclusionStates(window); });
} }
...@@ -487,8 +574,8 @@ void WindowOcclusionTracker::OnWindowLayerRecreated(Window* window) { ...@@ -487,8 +574,8 @@ void WindowOcclusionTracker::OnWindowLayerRecreated(Window* window) {
animator->RemoveObserver(this); animator->RemoveObserver(this);
auto root_window_state_it = root_windows_.find(window->GetRootWindow()); auto root_window_state_it = root_windows_.find(window->GetRootWindow());
if (root_window_state_it != root_windows_.end()) { if (root_window_state_it != root_windows_.end()) {
root_window_state_it->second.dirty = true; MarkRootWindowAsDirty(&root_window_state_it->second);
MaybeRecomputeOcclusion(); MaybeComputeOcclusion();
} }
} }
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
#include "base/containers/flat_set.h" #include "base/containers/flat_set.h"
#include "base/macros.h" #include "base/macros.h"
#include "ui/aura/aura_export.h" #include "ui/aura/aura_export.h"
#include "ui/aura/window.h"
#include "ui/aura/window_observer.h" #include "ui/aura/window_observer.h"
#include "ui/compositor/layer_animation_observer.h" #include "ui/compositor/layer_animation_observer.h"
...@@ -21,8 +22,6 @@ class Transform; ...@@ -21,8 +22,6 @@ class Transform;
namespace aura { namespace aura {
class Window;
// Notifies tracked Windows when their occlusion state change. // Notifies tracked Windows when their occlusion state change.
// //
// To start tracking the occlusion state of a Window, call // To start tracking the occlusion state of a Window, call
...@@ -55,13 +54,22 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver, ...@@ -55,13 +54,22 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
static void Track(Window* window); static void Track(Window* window);
private: private:
struct RootWindowState {
// Number of Windows whose occlusion state is tracked under this root
// Window.
int num_tracked_windows = 0;
// Whether the occlusion state of tracked Windows under this root is stale.
bool dirty = false;
};
WindowOcclusionTracker(); WindowOcclusionTracker();
~WindowOcclusionTracker() override; ~WindowOcclusionTracker() override;
// Recomputes the occlusion state of tracked windows under roots marked as // Recomputes the occlusion state of tracked windows under roots marked as
// dirty in |root_windows_| if there are no active // dirty in |root_windows_| if there are no active
// ScopedPauseOcclusionTracking instance. // ScopedPauseOcclusionTracking instance.
void MaybeRecomputeOcclusion(); void MaybeComputeOcclusion();
// Recomputes the occlusion state of |window| and its descendants. // Recomputes the occlusion state of |window| and its descendants.
// |parent_transform_relative_to_root| is the transform of |window->parent()| // |parent_transform_relative_to_root| is the transform of |window->parent()|
...@@ -87,8 +95,8 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver, ...@@ -87,8 +95,8 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
// are in |tracked_windows_|. // are in |tracked_windows_|.
void SetWindowAndDescendantsAreOccluded(Window* window, bool is_occluded); void SetWindowAndDescendantsAreOccluded(Window* window, bool is_occluded);
// Calls SetOccluded() on |window| with |occluded| as argument if |window| is // Updates the occlusion state of |window| in |tracked_windows_|. No-op if
// in |tracked_windows_|. // |window| is not in |tracked_windows_|.
void SetOccluded(Window* window, bool occluded); void SetOccluded(Window* window, bool occluded);
// Returns true if |window| is in |tracked_windows_|. // Returns true if |window| is in |tracked_windows_|.
...@@ -98,12 +106,15 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver, ...@@ -98,12 +106,15 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
bool WindowIsAnimated(Window* window) const; bool WindowIsAnimated(Window* window) const;
// If the root of |window| is not dirty and |predicate| is true, marks the // If the root of |window| is not dirty and |predicate| is true, marks the
// root of |window| as dirty. Then, calls MaybeRecomputeOcclusion(). // root of |window| as dirty. Then, calls MaybeComputeOcclusion().
// |predicate| is not evaluated if the root of |window| is already dirty when // |predicate| is not evaluated if the root of |window| is already dirty when
// this is called. // this is called.
template <typename Predicate> template <typename Predicate>
void MarkRootWindowAsDirtyAndMaybeRecomputeOcclusionIf(Window* window, void MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(Window* window,
Predicate predicate); Predicate predicate);
// Marks |root_window| as dirty.
void MarkRootWindowAsDirty(RootWindowState* root_window_state);
// Returns true if |window| or one of its parents is in |animated_windows_|. // Returns true if |window| or one of its parents is in |animated_windows_|.
bool WindowOrParentIsAnimated(Window* window) const; bool WindowOrParentIsAnimated(Window* window) const;
...@@ -163,17 +174,8 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver, ...@@ -163,17 +174,8 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
Window* new_root) override; Window* new_root) override;
void OnWindowLayerRecreated(Window* window) override; void OnWindowLayerRecreated(Window* window) override;
struct RootWindowState {
// Number of Windows whose occlusion state is tracked under this root
// Window.
int num_tracked_windows = 0;
// Whether the occlusion state of tracked Windows under this root is stale.
bool dirty = false;
};
// Windows whose occlusion state is tracked. // Windows whose occlusion state is tracked.
base::flat_set<Window*> tracked_windows_; base::flat_map<Window*, Window::OcclusionState> tracked_windows_;
// Windows whose bounds or transform are animated. // Windows whose bounds or transform are animated.
// //
...@@ -187,6 +189,10 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver, ...@@ -187,6 +189,10 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
// Root Windows of Windows in |tracked_windows_|. // Root Windows of Windows in |tracked_windows_|.
base::flat_map<Window*, RootWindowState> root_windows_; base::flat_map<Window*, RootWindowState> root_windows_;
// Number of times that the current call to MaybeComputeOcclusion() has
// recomputed occlusion states. Always 0 when not in MaybeComputeOcclusion().
int num_times_occlusion_recomputed_ = 0;
DISALLOW_COPY_AND_ASSIGN(WindowOcclusionTracker); DISALLOW_COPY_AND_ASSIGN(WindowOcclusionTracker);
}; };
......
...@@ -1321,4 +1321,191 @@ TEST_F(WindowOcclusionTrackerTest, ...@@ -1321,4 +1321,191 @@ TEST_F(WindowOcclusionTrackerTest,
window->layer()->GetAnimator()->StopAnimating(); window->layer()->GetAnimator()->StopAnimating();
} }
namespace {
class WindowDelegateHidingWindowIfOccluded : public MockWindowDelegate {
public:
WindowDelegateHidingWindowIfOccluded(Window* other_window,
MockWindowDelegate* other_delegate)
: other_window_(other_window), other_delegate_(other_delegate) {}
// MockWindowDelegate:
void OnWindowOcclusionChanged(bool is_occluded) override {
MockWindowDelegate::OnWindowOcclusionChanged(is_occluded);
if (is_occluded) {
other_window_->Hide();
other_delegate_->set_expectation(
WindowOcclusionChangedExpectation::OCCLUDED);
}
}
private:
Window* other_window_;
MockWindowDelegate* other_delegate_;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateHidingWindowIfOccluded);
};
} // namespace
// Verify that a window delegate can change the visibility of another window
// when it is notified that its occlusion changed.
TEST_F(WindowOcclusionTrackerTest, HideFromOnWindowOcclusionChanged) {
// Create a tracked window. Expect it to be visible.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create a tracked window. Expect it to be visible.
MockWindowDelegate* delegate_b =
new WindowDelegateHidingWindowIfOccluded(window_a, delegate_a);
delegate_b->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(5, 5, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
// Hide the tracked window. It should be able to hide |window_a|.
delegate_b->set_expectation(WindowOcclusionChangedExpectation::OCCLUDED);
window_b->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(window_a->IsVisible());
EXPECT_FALSE(window_b->IsVisible());
}
namespace {
class WindowDelegateDeletingWindow : public MockWindowDelegate {
public:
WindowDelegateDeletingWindow() = default;
void set_other_window(Window* other_window) { other_window_ = other_window; }
// MockWindowDelegate:
void OnWindowOcclusionChanged(bool is_occluded) override {
MockWindowDelegate::OnWindowOcclusionChanged(is_occluded);
if (is_occluded) {
delete other_window_;
other_window_ = nullptr;
}
}
private:
Window* other_window_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateDeletingWindow);
};
} // namespace
// Verify that a window can delete a window that is on top of it when it is
// notified that its occlusion changed (a crash would occur if
// WindowOcclusionTracker accessed that window after it was deleted).
TEST_F(WindowOcclusionTrackerTest, DeleteFromOnWindowOcclusionChanged) {
// Create a tracked window. Expect it to be visible.
WindowDelegateDeletingWindow* delegate_a = new WindowDelegateDeletingWindow();
delegate_a->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create a tracked window. Expect it to be visible.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create a tracked window. Expect it to be visible.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(20, 0, 10, 10));
EXPECT_FALSE(delegate_c->is_expecting_call());
// |window_c| will be deleted when |window_a| is occluded.
delegate_a->set_other_window(window_c);
// Move |window_b| on top of |window_a|.
delegate_a->set_expectation(WindowOcclusionChangedExpectation::OCCLUDED);
window_b->SetBounds(window_a->bounds());
EXPECT_FALSE(delegate_a->is_expecting_call());
}
namespace {
class WindowDelegateChangingWindowVisibility : public MockWindowDelegate {
public:
WindowDelegateChangingWindowVisibility() = default;
void set_window_to_update(Window* window) { window_to_update_ = window; }
// MockWindowDelegate:
void OnWindowOcclusionChanged(bool is_occluded) override {
MockWindowDelegate::OnWindowOcclusionChanged(is_occluded);
if (!window_to_update_)
return;
if (window_to_update_->IsVisible()) {
window_to_update_->Hide();
} else {
window_to_update_->Show();
EXPECT_FALSE(did_set_expectation_from_occlusion_changed_);
set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
did_set_expectation_from_occlusion_changed_ = true;
}
}
private:
Window* window_to_update_ = nullptr;
bool did_set_expectation_from_occlusion_changed_ = false;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateChangingWindowVisibility);
};
} // namespace
// Verify that if a window changes its visibility every time it is notified that
// its occlusion state changed, the occlusion state of all IsVisible() windows
// is set to NOT_OCCLUDED and no infinite loop is entered.
TEST_F(WindowOcclusionTrackerTest, OcclusionStatesDontBecomeStable) {
// Create 2 superposed tracked windows.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(WindowOcclusionChangedExpectation::OCCLUDED);
delegate_b->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create a hidden tracked window.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate_c->is_expecting_call());
delegate_c->set_expectation(WindowOcclusionChangedExpectation::OCCLUDED);
window_c->Hide();
EXPECT_FALSE(delegate_c->is_expecting_call());
// Create a tracked window. Expect it to be non-occluded.
auto* delegate_d = new WindowDelegateChangingWindowVisibility();
delegate_d->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
Window* window_d = CreateTrackedWindow(delegate_d, gfx::Rect(20, 0, 10, 10));
EXPECT_FALSE(delegate_d->is_expecting_call());
// Store a pointer to |window_d| in |delegate_d|. This will cause a call to
// Show()/Hide() every time |delegate_d| is notified of an occlusion change.
delegate_d->set_window_to_update(window_d);
// Hide |window_d|. This will cause occlusion to be recomputed multiple times.
// Once the maximum number of times that occlusion can be recomputed is
// reached, the occlusion state of all IsVisible() windows should be set to
// NOT_OCCLUDED.
delegate_a->set_expectation(WindowOcclusionChangedExpectation::NOT_OCCLUDED);
delegate_d->set_expectation(WindowOcclusionChangedExpectation::OCCLUDED);
window_d->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_d->is_expecting_call());
}
} // namespace aura } // namespace aura
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment