Commit b8bda029 authored by Mitsuru Oshima's avatar Mitsuru Oshima Committed by Commit Bot

Exclude dragged window in tablet overview mode from occlusion tracking.

* Add ScopdExclude class.
 The window specified will be ignored in occlusion tracking process.
* Exclude the dragging window in overview mode.

Bug: 902074
Test: covered by unit tests.
  Manaully tested on slate and make sure that dragging window
  in tablet overview mode doesn't not trigger frame evictor.

Change-Id: I4312f2dbc1928ac7cbbd4eac562547e7009fdbc0
Reviewed-on: https://chromium-review.googlesource.com/c/1318360Reviewed-by: default avatarFrançois Doray <fdoray@chromium.org>
Reviewed-by: default avatarScott Violet <sky@chromium.org>
Commit-Queue: Mitsuru Oshima <oshima@chromium.org>
Cr-Commit-Position: refs/heads/master@{#608157}
parent 40939452
......@@ -79,6 +79,11 @@ TabletModeWindowDragDelegate::~TabletModeWindowDragDelegate() {
void TabletModeWindowDragDelegate::StartWindowDrag(
aura::Window* dragged_window,
const gfx::Point& location_in_screen) {
// TODO(oshima): Consider doing the same for normal window dragging as well
// crbug.com/904631.
DCHECK(!occlusion_excluder_);
occlusion_excluder_.emplace(dragged_window);
dragged_window_ = dragged_window;
initial_location_in_screen_ = location_in_screen;
......@@ -199,6 +204,7 @@ void TabletModeWindowDragDelegate::EndWindowDrag(
// For child class to do its special handling if any.
EndedWindowDrag(location_in_screen);
occlusion_excluder_.reset();
dragged_window_ = nullptr;
did_move_ = false;
}
......
......@@ -11,6 +11,8 @@
#include "ash/wm/splitview/split_view_controller.h"
#include "ash/wm/wm_toplevel_window_event_handler.h"
#include "base/memory/weak_ptr.h"
#include "base/optional.h"
#include "ui/aura/window_occlusion_tracker.h"
#include "ui/wm/core/shadow_types.h"
namespace ash {
......@@ -141,6 +143,9 @@ class TabletModeWindowDragDelegate {
// the window is moved, it will stay as 'moved'.
bool did_move_ = false;
base::Optional<aura::WindowOcclusionTracker::ScopedExclude>
occlusion_excluder_;
base::WeakPtrFactory<TabletModeWindowDragDelegate> weak_ptr_factory_;
private:
......
......@@ -96,6 +96,30 @@ WindowOcclusionTracker::ScopedPause::~ScopedPause() {
env_->UnpauseWindowOcclusionTracking();
}
WindowOcclusionTracker::ScopedExclude::ScopedExclude(Window* window)
: window_(window) {
window->AddObserver(this);
window->env()->GetWindowOcclusionTracker()->Exclude(window_);
}
WindowOcclusionTracker::ScopedExclude::~ScopedExclude() {
Shutdown();
}
void WindowOcclusionTracker::ScopedExclude::OnWindowDestroying(
aura::Window* window) {
DCHECK_EQ(window_, window);
Shutdown();
}
void WindowOcclusionTracker::ScopedExclude::Shutdown() {
if (window_) {
window_->RemoveObserver(this);
window_->env()->GetWindowOcclusionTracker()->Unexclude(window_);
window_ = nullptr;
}
}
void WindowOcclusionTracker::Track(Window* window) {
DCHECK(window);
DCHECK(window != window->GetRootWindow());
......@@ -214,7 +238,7 @@ bool WindowOcclusionTracker::RecomputeOcclusionImpl(
return false;
}
if (WindowIsAnimated(window)) {
if (WindowIsAnimated(window) || WindowIsExcluded(window)) {
SetWindowAndDescendantsAreOccluded(window, false);
return true;
}
......@@ -282,9 +306,7 @@ void WindowOcclusionTracker::CleanupAnimatedWindows() {
if (animator->IsAnimatingOnePropertyOf(kSkipWindowWhenPropertiesAnimated))
return false;
animator->RemoveObserver(this);
auto root_window_state_it = root_windows_.find(window->GetRootWindow());
if (root_window_state_it != root_windows_.end())
MarkRootWindowAsDirty(&root_window_state_it->second);
MarkRootWindowAsDirty(window->GetRootWindow());
return true;
});
}
......@@ -345,6 +367,10 @@ bool WindowOcclusionTracker::WindowIsAnimated(Window* window) const {
return base::ContainsKey(animated_windows_, window);
}
bool WindowOcclusionTracker::WindowIsExcluded(Window* window) const {
return base::ContainsKey(excluded_windows_, window);
}
template <typename Predicate>
void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
Window* window,
......@@ -366,12 +392,12 @@ void WindowOcclusionTracker::MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(
if (root_window_state_it->second.dirty)
return;
if (predicate()) {
MarkRootWindowAsDirty(&root_window_state_it->second);
MarkRootWindowStateAsDirty(&root_window_state_it->second);
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::MarkRootWindowAsDirty(
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
......@@ -382,6 +408,14 @@ void WindowOcclusionTracker::MarkRootWindowAsDirty(
root_window_state->dirty = true;
}
bool WindowOcclusionTracker::MarkRootWindowAsDirty(aura::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();
......@@ -430,13 +464,15 @@ bool WindowOcclusionTracker::WindowOpacityChangeMayAffectOcclusionStates(
// 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).
return window->IsVisible() && !WindowOrParentIsAnimated(window);
// windows aren't considered in occlusion computations), unless it is
// excluded.
return window->IsVisible() && !WindowOrParentIsAnimated(window) &&
!WindowIsExcluded(window);
}
bool WindowOcclusionTracker::WindowMoveMayAffectOcclusionStates(
Window* window) const {
return !WindowOrParentIsAnimated(window) &&
return !WindowOrParentIsAnimated(window) && !WindowIsExcluded(window) &&
(WindowOrDescendantIsOpaque(window) ||
WindowOrDescendantIsTrackedAndVisible(window));
}
......@@ -446,7 +482,7 @@ void WindowOcclusionTracker::TrackedWindowAddedToRoot(Window* window) {
DCHECK(root_window);
RootWindowState& root_window_state = root_windows_[root_window];
++root_window_state.num_tracked_windows;
MarkRootWindowAsDirty(&root_window_state);
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.
......@@ -510,6 +546,27 @@ void WindowOcclusionTracker::Unpause() {
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 (window->IsVisible()) {
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::Unexclude(Window* window) {
DCHECK(WindowIsExcluded(window));
excluded_windows_.erase(window);
if (window->IsVisible()) {
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::OnLayerAnimationEnded(
ui::LayerAnimationSequence* sequence) {
CleanupAnimatedWindows();
......@@ -651,11 +708,8 @@ void WindowOcclusionTracker::OnWindowLayerRecreated(Window* window) {
return;
animator->RemoveObserver(this);
auto root_window_state_it = root_windows_.find(window->GetRootWindow());
if (root_window_state_it != root_windows_.end()) {
MarkRootWindowAsDirty(&root_window_state_it->second);
if (MarkRootWindowAsDirty(window->GetRootWindow()))
MaybeComputeOcclusion();
}
}
void WindowOcclusionTracker::OnOcclusionStateChanged(
......
......@@ -65,6 +65,31 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
DISALLOW_COPY_AND_ASSIGN(ScopedPause);
};
// Exclude the window from occlusion tracking so that a window behind the
// given window is still considered visible. The excluded window itself and
// its descendant windows, if tracked, are considered visible. This is useful
// for a window being dragged or resized to avoid unnecessary occlusion state
// change triggered by these operation, because the window bounds are
// temporary until it is finished.
// Note that this is intended to be used by window manager and not by mus
// client process.
class AURA_EXPORT ScopedExclude : public aura::WindowObserver {
public:
explicit ScopedExclude(Window* window);
~ScopedExclude() override;
Window* window() { return window_; }
private:
// aura::WindowObserver:
void OnWindowDestroying(aura::Window* window) override;
void Shutdown();
Window* window_;
DISALLOW_COPY_AND_ASSIGN(ScopedExclude);
};
// Start tracking the occlusion state of |window|.
void Track(Window* window);
......@@ -154,6 +179,9 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
// Returns true if |window| is in |animated_windows_|.
bool WindowIsAnimated(Window* window) const;
// Returns true if |window| is in |excluded_windows_|.
bool WindowIsExcluded(Window* window) const;
// If the root of |window| is not dirty and |predicate| is true, marks the
// root of |window| as dirty. Then, calls MaybeComputeOcclusion().
// |predicate| is not evaluated if the root of |window| is already dirty when
......@@ -162,8 +190,12 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
void MarkRootWindowAsDirtyAndMaybeComputeOcclusionIf(Window* window,
Predicate predicate);
// Marks |root_window| as dirty.
void MarkRootWindowAsDirty(RootWindowState* root_window_state);
// Marks |root_window_state| as dirty.
void MarkRootWindowStateAsDirty(RootWindowState* root_window_state);
// Marks |root_window| as dirty. Returns false if none of the descendent
// windows in |root_window| are tracked.
bool MarkRootWindowAsDirty(aura::Window* root_window);
// Returns true if |window| or one of its parents is in |animated_windows_|.
bool WindowOrParentIsAnimated(Window* window) const;
......@@ -206,6 +238,11 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
void Pause();
void Unpause();
// Exclucde/Unexclude a window from occlusion tracking. See comment on
// ScopedExclude.
void Exclude(Window* window);
void Unexclude(Window* window);
// ui::LayerAnimationObserver:
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override;
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override;
......@@ -247,6 +284,10 @@ class AURA_EXPORT WindowOcclusionTracker : public ui::LayerAnimationObserver,
// aborted.
base::flat_set<Window*> animated_windows_;
// Windows that are excluded from occlustion tracking. See comment on
// ScopedExclude.
base::flat_set<Window*> excluded_windows_;
// Root Windows of Windows in |tracked_windows_|.
base::flat_map<Window*, RootWindowState> root_windows_;
......
......@@ -37,6 +37,8 @@ class MockWindowDelegate : public test::ColorTestWindowDelegate {
void set_window(Window* window) { window_ = window; }
void SetName(const std::string& name) { window_->SetName(name); }
void set_expectation(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) {
expected_occlusion_state_ = occlusion_state;
......@@ -49,6 +51,7 @@ class MockWindowDelegate : public test::ColorTestWindowDelegate {
void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
SCOPED_TRACE(window_->GetName());
ASSERT_TRUE(window_);
EXPECT_NE(occlusion_state, Window::OcclusionState::UNKNOWN);
EXPECT_EQ(occlusion_state, expected_occlusion_state_);
......@@ -2017,4 +2020,135 @@ TEST_F(WindowOcclusionTrackerTest, WindowCanBeOccludedByMultipleWindows) {
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that the excluded window is indeed ignored by occlusion tracking.
TEST_F(WindowOcclusionTrackerTest, ExcludeWindow) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->SetName("WindowA");
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 100, 100), nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
MockWindowDelegate* delegate_bb = new MockWindowDelegate();
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_bb, gfx::Rect(0, 0, 10, 10), window_b);
EXPECT_FALSE(delegate_bb->is_expecting_call());
delegate_bb->SetName("WindowBB");
delegate_bb->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_c = CreateUntrackedWindow(gfx::Rect(0, 0, 100, 100), nullptr);
EXPECT_FALSE(delegate_bb->is_expecting_call());
{
// |window_b| is excluded, so its child's occlusion state becomes VISIBlE.
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
EXPECT_TRUE(delegate_bb->is_expecting_call());
WindowOcclusionTracker::ScopedExclude scoped(window_b);
EXPECT_FALSE(delegate_bb->is_expecting_call());
// Moving |window_c| out from |window_a| will make |window_a| visible
// because |window_b| is ignored.
SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
SkRegion window_bb_occlusion;
window_bb_occlusion.op(SkIRect::MakeXYWH(100, 100, 100, 100),
SkRegion::kUnion_Op);
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
window_bb_occlusion);
window_c->SetBounds(gfx::Rect(100, 100, 100, 100));
// Un-excluding wil make |window_bb| OCCLUDED.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
window_bb_occlusion);
}
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_bb->is_expecting_call());
{
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
EXPECT_TRUE(delegate_bb->is_expecting_call());
WindowOcclusionTracker::ScopedExclude scoped(window_b);
EXPECT_FALSE(delegate_bb->is_expecting_call());
EXPECT_FALSE(delegate_a->is_expecting_call());
// Moving |window_b| will not affect the occlusion status.
window_b->SetBounds(gfx::Rect(5, 5, 100, 100));
// Un-excluding will update the occlustion status.
// A's occlustion status includes all windows above a.
window_a_occlusion.setEmpty();
window_a_occlusion.op(SkIRect::MakeXYWH(5, 5, 100, 100),
SkRegion::kUnion_Op);
window_a_occlusion.op(SkIRect::MakeXYWH(100, 100, 100, 100),
SkRegion::kUnion_Op);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
SkRegion window_bb_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE,
window_bb_occlusion);
}
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_bb->is_expecting_call());
{
delegate_bb->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
EXPECT_TRUE(delegate_bb->is_expecting_call());
WindowOcclusionTracker::ScopedExclude scoped(window_b);
EXPECT_FALSE(delegate_bb->is_expecting_call());
EXPECT_FALSE(delegate_a->is_expecting_call());
// Deleting the excluded window will un-exclude itself and recomputes the
// occlustion state, but should not affect the state on existing windows
// because it's already excluded.
delete window_b;
EXPECT_FALSE(scoped.window());
}
MockWindowDelegate* delegate_d = new MockWindowDelegate();
delegate_d->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
auto* window_d = CreateTrackedWindow(delegate_d, gfx::Rect(0, 0, 10, 10));
window_d->SetName("WindowD");
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_d->is_expecting_call());
{
// Make sure excluding the tracked window also works.
SkRegion window_a_occlusion(SkIRect::MakeXYWH(100, 100, 100, 100));
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
WindowOcclusionTracker::ScopedExclude scoped(window_d);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Changing opacity/bounds shouldn't change the occlusion state.
window_d->layer()->SetOpacity(0.5f);
window_d->SetBounds(gfx::Rect(0, 0, 20, 20));
// A is now visible even if |window_d| is un-excluded becaues
// window_d is not fully opaque.
}
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_d->layer()->SetOpacity(1.f);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
} // 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