blob: 2ab48cb7585b79ed1a6c7c0a3aa5b9ae176fd588 [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/bind_helpers.h"
#include "base/macros.h"
#include "base/run_loop.h"
#include "base/test/bind_test_util.h"
#include "base/test/gtest_util.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/aura/env.h"
#include "ui/aura/test/aura_test_base.h"
#include "ui/aura/test/test_window_delegate.h"
#include "ui/aura/test/test_windows.h"
#include "ui/aura/test/window_occlusion_tracker_test_api.h"
#include "ui/aura/window_observer.h"
#include "ui/compositor/layer_animation_observer.h"
#include "ui/compositor/layer_animation_sequence.h"
#include "ui/compositor/layer_animator.h"
#include "ui/compositor/scoped_animation_duration_scale_mode.h"
#include "ui/compositor/scoped_layer_animation_settings.h"
#include "ui/compositor/test/layer_animator_test_controller.h"
#include "ui/gfx/interpolated_transform.h"
namespace aura {
namespace {
constexpr base::TimeDelta kTransitionDuration = base::TimeDelta::FromSeconds(3);
class MockWindowDelegate : public test::ColorTestWindowDelegate {
public:
MockWindowDelegate() : test::ColorTestWindowDelegate(SK_ColorWHITE) {}
~MockWindowDelegate() override { EXPECT_FALSE(is_expecting_call()); }
void set_window(Window* window) { window_ = window; }
void SetName(const std::string& name) { window_->SetName(name); }
void set_expectation(Window::OcclusionState occlusion_state,
SkRegion occluded_region = SkRegion()) {
expected_occlusion_state_ = occlusion_state;
expected_occluded_region_ = occluded_region;
}
bool is_expecting_call() const {
return expected_occlusion_state_ != Window::OcclusionState::UNKNOWN;
}
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_);
EXPECT_EQ(occluded_region, expected_occluded_region_);
expected_occlusion_state_ = Window::OcclusionState::UNKNOWN;
expected_occluded_region_ = SkRegion();
}
private:
Window::OcclusionState expected_occlusion_state_ =
Window::OcclusionState::UNKNOWN;
SkRegion expected_occluded_region_ = SkRegion();
Window* window_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(MockWindowDelegate);
};
class WindowOcclusionTrackerTest : public test::AuraTestBase {
public:
WindowOcclusionTrackerTest() = default;
Window* CreateTrackedWindow(MockWindowDelegate* delegate,
const gfx::Rect& bounds,
Window* parent = nullptr,
bool transparent = false) {
Window* window = new Window(delegate);
delegate->set_window(window);
window->SetType(client::WINDOW_TYPE_NORMAL);
window->Init(ui::LAYER_TEXTURED);
window->SetTransparent(transparent);
window->SetBounds(bounds);
window->Show();
parent = parent ? parent : root_window();
parent->AddChild(window);
window->TrackOcclusionState();
return window;
}
Window* CreateUntrackedWindow(const gfx::Rect& bounds,
Window* parent = nullptr) {
Window* window = test::CreateTestWindow(SK_ColorWHITE, 1, bounds,
parent ? parent : root_window());
return window;
}
WindowOcclusionTracker& GetOcclusionTracker() {
return *root_window()->env()->GetWindowOcclusionTracker();
}
private:
DISALLOW_COPY_AND_ASSIGN(WindowOcclusionTrackerTest);
};
SkRegion SkRegionFromSkIRects(std::initializer_list<SkIRect> rects) {
SkRegion r;
r.setRects(rects.begin(), rects.size());
return r;
}
} // namespace
// Verify that non-overlapping windows have a VISIBLE occlusion state.
// _____ _____
// | | | |
// |____| |____|
TEST_F(WindowOcclusionTrackerTest, NonOverlappingWindows) {
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(15, 0, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(15, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that partially overlapping windows have a VISIBLE occlusion state.
// ______
// |__| |
// |_____|
TEST_F(WindowOcclusionTrackerTest, PartiallyOverlappingWindow) {
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeWH(5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that a window whose bounds are covered by a hidden window is not
// occluded. Also, verify that calling Show() on the hidden window causes
// occlusion states to be recomputed.
// __.... ... = hidden window
// |__| .
// .......
TEST_F(WindowOcclusionTrackerTest, HiddenWindowCoversWindow) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b. Expect it to be non-occluded and expect window a to be
// occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 15, 15));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Hide window b. Expect window a to be non-occluded and window b to be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_b->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Show window b. Expect window a to be occluded and window b to be non-
// occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->Show();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that a window whose bounds are covered by a semi-transparent window is
// not occluded. Also, verify that that when the opacity of a window changes,
// occlusion states are updated.
// __.... ... = semi-transparent window
// |__| .
// .......
TEST_F(WindowOcclusionTrackerTest, SemiTransparentWindowCoversWindow) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b. Expect it to be non-occluded and expect window a to be
// occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 15, 15));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Change the opacity of window b to 0.5f. Expect both windows to be non-
// occluded.
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetOpacity(0.5f);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Change the opacity of window b back to 1.0f. Expect window a to be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->layer()->SetOpacity(1.0f);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Same as previous test, but the occlusion state of the semi-transparent is not
// tracked.
TEST_F(WindowOcclusionTrackerTest, SemiTransparentUntrackedWindowCoversWindow) {
// Create window a. Expect it to be non-occluded.
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());
// Create untracked window b. Expect window a to be occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 15, 15));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Change the opacity of window b to 0.5f. Expect both windows to be non-
// occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetOpacity(0.5f);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Change the opacity of window b back to 1.0f. Expect window a to be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->layer()->SetOpacity(1.0f);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that one window whose bounds are covered by a set of two opaque
// windows is occluded.
// ______
// | | | <-- these two windows cover another window
// |__|__|
TEST_F(WindowOcclusionTrackerTest, TwoWindowsOccludeOneWindow) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b with bounds that partially cover window a. Expect both
// windows to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeWH(5, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 5, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c with bounds that cover the portion of window a that isn't
// already covered by window b. Expect window a to be occluded and window a/b
// to be non-occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(5, 0, 5, 10)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(5, 0, 5, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that a window and its child that are covered by a sibling are
// occluded.
TEST_F(WindowOcclusionTrackerTest, SiblingOccludesWindowAndChild) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b, with bounds that occlude half of its parent window a.
// Expect it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 20), window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c, with bounds that occlude window a and window b. Expect it
// to be non-occluded, and window a and b to be occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that a window with one half covered by a child and the other half
// covered by a sibling is non-occluded.
TEST_F(WindowOcclusionTrackerTest, ChildAndSiblingOccludeOneWindow) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b, with bounds that occlude half of its parent window a.
// Expect it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 20), window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c, with bounds that occlude the other half of window a.
// Expect it to be non-occluded and expect window a to remain non-occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 20)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 20)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(10, 0, 10, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that a window covered by 2 non-occluded children is non-occluded.
TEST_F(WindowOcclusionTrackerTest, ChildrenOccludeOneWindow) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b, with bounds that cover half of its parent window a. Expect
// it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 20), window_a);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window c, with bounds that cover the other half of its parent window
// a. Expect it to be non-occluded. Expect window a to remain non-occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 20)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(10, 0, 10, 20), window_a);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that when the bounds of a child window covers the bounds of a parent
// window but is itself visible, the parent window is visible.
TEST_F(WindowOcclusionTrackerTest, ChildDoesNotOccludeParent) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b with window a as parent. The bounds of window b fully cover
// window a. Expect both windows to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b =
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10), window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c whose bounds don't overlap existing windows.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(15, 0, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(15, 0, 10, 10)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(15, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Change the parent of window b from window a to window c. Expect all windows
// to remain non-occluded.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_c->AddChild(window_b);
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that when the stacking order of windows change, occlusion states are
// updated.
TEST_F(WindowOcclusionTrackerTest, StackingChanged) {
// Create three windows that have the same bounds. Expect window on top of the
// stack to be non-occluded and other windows to be occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = 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(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Move window a on top of the stack. Expect it to be non-occluded and expect
// window c to be occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
root_window()->StackChildAtTop(window_a);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that when the stacking order of two transparent window changes, the
// occlusion states of their children is updated. The goal of this test is to
// ensure that the fact that the windows whose stacking order change are
// transparent doesn't prevent occlusion states from being recomputed.
TEST_F(WindowOcclusionTrackerTest, TransparentParentStackingChanged) {
// Create window a which is transparent. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10),
root_window(), true);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which has the same bounds as its parent window a. Expect it
// to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10), window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c which is transparent and has the same bounds as window a
// and window b. Expect it to be non-occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 10, 10),
root_window(), true);
EXPECT_FALSE(delegate_c->is_expecting_call());
// Create window d which has the same bounds as its parent window c. Expect
// window d to be non-occluded and window a and b to be occluded.
MockWindowDelegate* delegate_d = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_d->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_d, gfx::Rect(0, 0, 10, 10), window_c);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_d->is_expecting_call());
// Move window a on top of the stack. Expect window a and b to be non-occluded
// and window c and d to be occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_d->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
root_window()->StackChildAtTop(window_a);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
EXPECT_FALSE(delegate_d->is_expecting_call());
}
// Verify that when StackChildAtTop() is called on a window whose occlusion
// state is not tracked, the occlusion state of tracked siblings is updated.
TEST_F(WindowOcclusionTrackerTest, UntrackedWindowStackingChanged) {
Window* window_a = CreateUntrackedWindow(gfx::Rect(0, 0, 5, 5));
// Create window b. Expect it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_b->is_expecting_call());
// Stack window a on top of window b. Expect window b to be occluded.
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
root_window()->StackChildAtTop(window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that occlusion states are updated when the bounds of a window change.
TEST_F(WindowOcclusionTrackerTest, BoundsChanged) {
// Create two non-overlapping windows. Expect them to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = 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(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Move window b on top of window a. Expect window a to be occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->SetBounds(window_a->bounds());
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that when the bounds of a window are animated, occlusion states are
// updated at the beginning and at the end of the animation, but not during the
// animation. At the beginning of the animation, the window animated window
// should be considered non-occluded and should not occlude other windows. The
// animated window starts occluded.
TEST_F(WindowOcclusionTrackerTest, OccludedWindowBoundsAnimated) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create 3 windows. Window a is unoccluded. Window c occludes window b.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = 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(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->layer()->SetAnimator(test_controller.animator());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Start animating the bounds of window b so that it moves on top of window a.
// Window b should be non-occluded when the animation starts.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->SetBounds(window_a->bounds());
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Window a should remain non-occluded during the animation.
test_controller.Step(kTransitionDuration / 3);
// Window b should occlude window a at the end of the animation.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
// Window b should have window c in its potential occlusion region.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 10, 10)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->layer()->SetAnimator(nullptr);
}
// Same as the previous test, but the animated window starts non-occluded.
TEST_F(WindowOcclusionTrackerTest, NonOccludedWindowBoundsAnimated) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create 3 windows. Window a is unoccluded. Window c occludes window b.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = 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(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
window_c->layer()->SetAnimator(test_controller.animator());
// Start animating the bounds of window c so that it moves on top of window a.
// Window b should be non-occluded when the animation starts.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_c->SetBounds(window_a->bounds());
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Window a should remain non-occluded during the animation.
test_controller.Step(kTransitionDuration / 3);
// Window c should occlude window a at the end of the animation.
// Window b should have a potentially occluded region including window c.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 0, 10, 10)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_c->layer()->SetAnimator(nullptr);
}
// Verify that occlusion states are updated when the bounds of a transparent
// window with opaque children change.
TEST_F(WindowOcclusionTrackerTest, TransparentParentBoundsChanged) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which doesn't overlap window a and is transparent. Expect
// it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10),
root_window(), true);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c which has window b as parent and doesn't occlude any
// window.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 5, 5), window_b);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Move window b so that window c occludes window a. Expect window a to be
// occluded and other windows to be non-occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->SetBounds(gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that occlusion states are updated when the bounds of a window whose
// occlusion state is not tracked change.
TEST_F(WindowOcclusionTrackerTest, UntrackedWindowBoundsChanged) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b. It should not occlude window a.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Move window b on top of window a. Expect window a to be occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->SetBounds(window_a->bounds());
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that occlusion states are updated when the transform of a window
// changes.
TEST_F(WindowOcclusionTrackerTest, TransformChanged) {
// Create two non-overlapping windows. Expect them to be non-occluded.
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Scale and translate window b so that it covers window a. Expect window a to
// be occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
gfx::Transform transform;
transform.Translate(0.0f, -10.0f);
transform.Scale(2.0f, 2.0f);
window_b->SetTransform(transform);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that when the transform of a window is animated, occlusion states are
// updated at the beginning and at the end of the animation, but not during the
// animation. At the beginning of the animation, the window animated window
// should be considered non-occluded and should not occlude other windows. The
// animated window starts occluded.
TEST_F(WindowOcclusionTrackerTest, OccludedWindowTransformAnimated) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create 3 windows. Window a is unoccluded. Window c occludes window b.
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->layer()->SetAnimator(test_controller.animator());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Start animating the transform of window b so that it moves on top of window
// a. Window b should be non-occluded when the animation starts.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
auto transform = std::make_unique<ui::InterpolatedScale>(
gfx::Point3F(1, 1, 1), gfx::Point3F(2.0f, 2.0f, 1));
transform->SetChild(std::make_unique<ui::InterpolatedTranslation>(
gfx::PointF(), gfx::PointF(-0.0f, -10.0f)));
test_controller.animator()->StartAnimation(new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateInterpolatedTransformElement(
std::move(transform), kTransitionDuration)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Window a should remain non-occluded during the animation.
test_controller.Step(kTransitionDuration / 3);
// Window b should occlude window a at the end of the animation.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
// Window b should see window c as part of the potential occlusion region.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->layer()->SetAnimator(nullptr);
}
// Same as the previous test, but the animated window starts non-occluded.
TEST_F(WindowOcclusionTrackerTest, NonOccludedWindowTransformAnimated) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create 3 windows. Window a is unoccluded. Window c occludes window b.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 20, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 20, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(0, 20, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
window_c->layer()->SetAnimator(test_controller.animator());
// Start animating the bounds of window c so that it moves on top of window a.
// Window b should be non-occluded when the animation starts.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
auto transform = std::make_unique<ui::InterpolatedScale>(
gfx::Point3F(1, 1, 1), gfx::Point3F(2.0f, 2.0f, 1));
transform->SetChild(std::make_unique<ui::InterpolatedTranslation>(
gfx::PointF(), gfx::PointF(-0.0f, -20.0f)));
test_controller.animator()->StartAnimation(new ui::LayerAnimationSequence(
ui::LayerAnimationElement::CreateInterpolatedTransformElement(
std::move(transform), kTransitionDuration)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Window a should remain non-occluded during the animation.
test_controller.Step(kTransitionDuration / 3);
// Window c should occlude window a at the end of the animation.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
// Window b should now see window c in the potential occlusion region.
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeWH(20, 20)));
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_c->layer()->SetAnimator(nullptr);
}
// Verify that occlusion states are updated when the transform of a transparent
// window with opaque children change.
TEST_F(WindowOcclusionTrackerTest, TransparentParentTransformChanged) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b which doesn't overlap window a and is transparent. Expect
// it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10),
root_window(), true);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c which has window b as parent and doesn't occlude any
// window.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 5, 5), window_b);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Scale and translate window b so that window c occludes window a. Expect
// window a to be occluded and other windows to be non-occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
gfx::Transform transform;
transform.Translate(0.0f, -10.0f);
transform.Scale(2.0f, 2.0f);
window_b->SetTransform(transform);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that occlusion states are updated when the transform of a window whose
// occlusion state is not tracked changes.
TEST_F(WindowOcclusionTrackerTest, UntrackedWindowTransformChanged) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b. It should not occlude window a.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Scale and translate window b so that it occludes window a. Expect window a
// to be occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
gfx::Transform transform;
transform.Translate(0.0f, -10.0f);
transform.Scale(2.0f, 2.0f);
window_b->SetTransform(transform);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that deleting an untracked window which covers a tracked window causes
// the tracked window to be non-occluded.
TEST_F(WindowOcclusionTrackerTest, DeleteUntrackedWindow) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which occludes window a.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Delete window b. Expect a to be non-occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delete window_b;
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that removing an untracked window which covers a tracked window causes
// the tracked window to be non-occluded.
TEST_F(WindowOcclusionTrackerTest, RemoveUntrackedWindow) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which occludes window a.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Delete window b. Expect a to be non-occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
root_window()->RemoveChild(window_b);
EXPECT_FALSE(delegate_a->is_expecting_call());
delete window_b;
}
// Verify that occlusion tracking with customized WindowHasContent callback.
TEST_F(WindowOcclusionTrackerTest, CustomizedWindowHasContent) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b with layer type LAYER_NOT_DRAWN. Occlusion state of a is
// not changed.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
Window* window_b = new Window(delegate_b);
delegate_b->set_window(window_b);
window_b->Init(ui::LAYER_NOT_DRAWN);
window_b->SetBounds(gfx::Rect(0, 0, 10, 10));
root_window()->AddChild(window_b);
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_b->TrackOcclusionState();
EXPECT_FALSE(delegate_b->is_expecting_call());
// Use customized WindowHasContent callback to mark b as opaque.
window_b->env()->GetWindowOcclusionTracker()->set_window_has_content_callback(
base::BindLambdaForTesting([window_b](const Window* window) -> bool {
return window == window_b;
}));
// Show window b to trigger a occlusion compute and window a is occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->Show();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->env()->GetWindowOcclusionTracker()->set_window_has_content_callback(
base::NullCallback());
}
// Verify that when a tracked window is removed and re-added to a root,
// occlusion states are still tracked.
TEST_F(WindowOcclusionTrackerTest, RemoveAndAddTrackedToRoot) {
Window* window_a = CreateUntrackedWindow(gfx::Rect(0, 0, 1, 1));
CreateUntrackedWindow(gfx::Rect(0, 0, 10, 10), window_a);
// Create window b. Expect it to be non-occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_c->is_expecting_call());
// Remove window c from its root.
root_window()->RemoveChild(window_c);
// Add window c back under its root.
root_window()->AddChild(window_c);
// Create untracked window d which covers window a. Expect window a to be
// occluded.
delegate_c->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_d = CreateUntrackedWindow(gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_c->is_expecting_call());
// Move window d so that it doesn't cover window c.
delegate_c->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
window_d->SetBounds(gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_c->is_expecting_call());
// Stack window a on top of window c. Expect window c to be non-occluded. This
// won't work if WindowOcclusionTracked didn't register as an observer of
// window a when window c was made a child of root_window().
delegate_c->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
root_window()->StackChildAtTop(window_a);
EXPECT_FALSE(delegate_c->is_expecting_call());
}
namespace {
class ResizeWindowObserver : public WindowObserver {
public:
ResizeWindowObserver(Window* window_to_resize)
: window_to_resize_(window_to_resize) {}
void OnWindowBoundsChanged(Window* window,
const gfx::Rect& old_bounds,
const gfx::Rect& new_bounds,
ui::PropertyChangeReason reason) override {
window_to_resize_->SetBounds(old_bounds);
}
private:
Window* const window_to_resize_;
DISALLOW_COPY_AND_ASSIGN(ResizeWindowObserver);
};
} // namespace
// Verify that when the bounds of a child window are updated in response to the
// bounds of a parent window being updated, occlusion states are updated once.
TEST_F(WindowOcclusionTrackerTest, ResizeChildFromObserver) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b. Expect it to be non-occluded and to occlude window a.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create window c, which is a child of window b. Expect it to be non-
// occluded.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c =
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 5, 5), window_b);
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create an observer that will resize window c when window b is resized.
ResizeWindowObserver resize_window_observer(window_c);
window_b->AddObserver(&resize_window_observer);
// Resize window b. Expect window c to be resized so that window a stays
// occluded. Window a should not temporarily be non-occluded.
window_b->SetBounds(gfx::Rect(0, 0, 5, 5));
window_b->RemoveObserver(&resize_window_observer);
}
// Verify that the bounds of windows are changed multiple times within the scope
// of a ScopedPause, occlusion states are updated once at the end of the scope.
TEST_F(WindowOcclusionTrackerTest, ScopedPause) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which doesn't overlap window a. Expect it to be non
// occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Change bounds multiple times. At the end of the scope, expect window a to
// be occluded.
{
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking(
root_window()->env());
window_b->SetBounds(window_a->bounds());
window_a->SetBounds(gfx::Rect(0, 10, 5, 5));
window_b->SetBounds(window_a->bounds());
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
}
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Same as the previous test, but with nested ScopedPause.
TEST_F(WindowOcclusionTrackerTest, NestedScopedPause) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b which doesn't overlap window a. Expect it to be non-
// occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Change bounds multiple times. At the end of the scope, expect window a to
// be occluded.
{
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking_a(
root_window()->env());
{
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking_b(
root_window()->env());
window_b->SetBounds(window_a->bounds());
}
{
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking_c(
root_window()->env());
window_a->SetBounds(gfx::Rect(0, 10, 5, 5));
}
{
WindowOcclusionTracker::ScopedPause pause_occlusion_tracking_d(
root_window()->env());
window_b->SetBounds(window_a->bounds());
}
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
}
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that bounds are computed correctly when a hierarchy of windows have
// transforms.
TEST_F(WindowOcclusionTrackerTest, HierarchyOfTransforms) {
gfx::Transform scale_2x_transform;
scale_2x_transform.Scale(2.0f, 2.0f);
// Effective bounds: x = 2, y = 2, height = 10, width = 10
Window* window_a = CreateUntrackedWindow(gfx::Rect(2, 2, 5, 5));
window_a->SetTransform(scale_2x_transform);
// Effective bounds: x = 4, y = 4, height = 4, width = 4
Window* window_b = CreateUntrackedWindow(gfx::Rect(1, 1, 2, 2), window_a);
// Effective bounds: x = 34, y = 36, height = 8, width = 10
CreateUntrackedWindow(gfx::Rect(15, 16, 4, 5), window_b);
MockWindowDelegate* delegate_d = new MockWindowDelegate();
delegate_d->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_d = CreateTrackedWindow(delegate_d, gfx::Rect(34, 36, 8, 10));
EXPECT_FALSE(delegate_d->is_expecting_call());
delegate_d->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
root_window()->StackChildAtBottom(window_d);
EXPECT_FALSE(delegate_d->is_expecting_call());
SkRegion occluded_area = SkRegionFromSkIRects(
{SkIRect::MakeXYWH(2, 2, 10, 10), SkIRect::MakeXYWH(4, 4, 4, 4),
SkIRect::MakeXYWH(34, 36, 8, 10)});
delegate_d->set_expectation(Window::OcclusionState::VISIBLE, occluded_area);
window_d->SetBounds(gfx::Rect(35, 36, 8, 10));
EXPECT_FALSE(delegate_d->is_expecting_call());
// |window_d| should remain non-occluded with the following bounds changes.
window_d->SetBounds(gfx::Rect(33, 36, 8, 10));
window_d->SetBounds(gfx::Rect(34, 37, 8, 10));
window_d->SetBounds(gfx::Rect(34, 35, 8, 10));
}
// Verify that clipping is taken into account when computing occlusion.
TEST_F(WindowOcclusionTrackerTest, Clipping) {
// Create window a. Expect it to be non-occluded.
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());
// Create window b. Expect it to be non-occluded.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeWH(5, 5)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
window_b->layer()->SetMasksToBounds(true);
// Create window c which has window b as parent. Don't expect it to occlude
// window a since its bounds are clipped by window b.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 10, 10), window_b);
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that the DCHECK(!WindowIsAnimated(window)) in
// WindowOcclusionTracker::OnWindowDestroyed() doesn't fire if a window is
// destroyed with an incomplete animation (~Window should complete the animation
// and the window should be removed from |animated_windows_| before
// OnWindowDestroyed() is called).
TEST_F(WindowOcclusionTrackerTest, DestroyWindowWithPendingAnimation) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
MockWindowDelegate* delegate = new MockWindowDelegate();
delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window = CreateTrackedWindow(delegate, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate->is_expecting_call());
window->layer()->SetAnimator(test_controller.animator());
// Start animating the bounds of window.
window->SetBounds(gfx::Rect(10, 10, 5, 5));
test_controller.Step(kTransitionDuration / 3);
EXPECT_TRUE(test_controller.animator()->IsAnimatingProperty(
ui::LayerAnimationElement::BOUNDS));
// Destroy the window. Expect no DCHECK failure.
delete window;
}
// Verify that an animated window stops being considered as animated when its
// layer is recreated.
TEST_F(WindowOcclusionTrackerTest, RecreateLayerOfAnimatedWindow) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create 2 windows. Window b occludes window a.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(2, 2, 1, 1));
EXPECT_FALSE(delegate_a->is_expecting_call());
window_a->layer()->SetAnimator(test_controller.animator());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Start animating the bounds of window a. Window a should be non-occluded
// when the animation starts.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_a->SetBounds(gfx::Rect(6, 6, 1, 1));
test_controller.Step(kTransitionDuration / 2);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Recreate the layer of window b. Expect this to behave the same as if the
// animation was abandoned. Occlusion region should be half way between the
// animation bounds.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
std::unique_ptr<ui::Layer> old_layer = window_a->RecreateLayer();
EXPECT_FALSE(delegate_a->is_expecting_call());
window_a->layer()->SetAnimator(nullptr);
}
namespace {
class ObserverChangingWindowBounds : public WindowObserver {
public:
ObserverChangingWindowBounds() = default;
// WindowObserver:
void OnWindowParentChanged(Window* window, Window* parent) override {
window->SetBounds(gfx::Rect(1, 2, 3, 4));
}
private:
DISALLOW_COPY_AND_ASSIGN(ObserverChangingWindowBounds);
};
} // namespace
// Verify that no crash occurs if a tracked window is modified by an observer
// after it has been added to a new root but before WindowOcclusionTracker has
// been notified.
TEST_F(WindowOcclusionTrackerTest, ChangeTrackedWindowBeforeObserveAddToRoot) {
// Create a window. Expect it to be non-occluded.
MockWindowDelegate* delegate = new MockWindowDelegate();
delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window = CreateTrackedWindow(delegate, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate->is_expecting_call());
// Remove the window from its root.
root_window()->RemoveChild(window);
// Add an observer that changes the bounds of |window| when it gets a new
// parent.
ObserverChangingWindowBounds observer;
window->AddObserver(&observer);
// Re-add the window to its root. Expect no crash when |observer| changes the
// bounds.
root_window()->AddChild(window);
window->RemoveObserver(&observer);
}
namespace {
class ObserverDestroyingWindowOnAnimationEnded
: public ui::LayerAnimationObserver {
public:
ObserverDestroyingWindowOnAnimationEnded(Window* window) : window_(window) {}
~ObserverDestroyingWindowOnAnimationEnded() override {
EXPECT_FALSE(window_);
}
void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override {
EXPECT_TRUE(window_);
delete window_;
window_ = nullptr;
}
void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override {}
void OnLayerAnimationScheduled(
ui::LayerAnimationSequence* sequence) override {}
private:
Window* window_;
DISALLOW_COPY_AND_ASSIGN(ObserverDestroyingWindowOnAnimationEnded);
};
} // namespace
// Verify that no crash occurs if a LayerAnimationObserver destroys a tracked
// window before WindowOcclusionTracker is notified that the animation ended.
TEST_F(WindowOcclusionTrackerTest,
DestroyTrackedWindowFromLayerAnimationObserver) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create a window. Expect it to be non-occluded.
MockWindowDelegate* delegate = new MockWindowDelegate();
delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window = CreateTrackedWindow(delegate, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate->is_expecting_call());
window->layer()->SetAnimator(test_controller.animator());
// Add a LayerAnimationObserver that destroys the window when an animation
// ends.
ObserverDestroyingWindowOnAnimationEnded observer(window);
window->layer()->GetAnimator()->AddObserver(&observer);
// Start animating the opacity of the window.
window->layer()->SetOpacity(0.5f);
// Complete the animation. Expect no crash.
window->layer()->GetAnimator()->StopAnimating();
}
// Verify that no crash occurs if an animation completes on a non-tracked
// window's layer after the window has been removed from a root with a tracked
// window and deleted.
TEST_F(WindowOcclusionTrackerTest,
DeleteNonTrackedAnimatedWindowRemovedFromTrackedRoot) {
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
// Create a tracked window. Expect it to be non-occluded.
MockWindowDelegate* delegate = new MockWindowDelegate();
delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate->is_expecting_call());
// Create a non-tracked window and add an observer that deletes it when its
// stops being animated.
delegate->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 10)));
Window* window = CreateUntrackedWindow(gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate->is_expecting_call());
window->layer()->SetAnimator(test_controller.animator());
ObserverDestroyingWindowOnAnimationEnded observer(window);
window->layer()->GetAnimator()->AddObserver(&observer);
// Animate the window. WindowOcclusionTracker should add itself as an observer
// of its LayerAnimator (after |observer|). Upon beginning animation, the
// window should no longer affect the occluded region.
delegate->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window->layer()->SetOpacity(0.5f);
EXPECT_FALSE(delegate->is_expecting_call());
// Remove the non-tracked window from its root. WindowOcclusionTracker should
// remove the window from its list of animated windows and stop observing it
// and its LayerAnimator.
root_window()->RemoveChild(window);
// Complete animations on the window. |observer| will delete the window when
// it is notified that animations are complete. Expect that
// WindowOcclusionTracker will not try to access |window| after that (if it
// does, the test will crash).
window->layer()->GetAnimator()->StopAnimating();
}
namespace {
class WindowDelegateHidingWindowIfOccluded : public MockWindowDelegate {
public:
WindowDelegateHidingWindowIfOccluded(Window* other_window)
: other_window_(other_window) {}
// MockWindowDelegate:
void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (occlusion_state == Window::OcclusionState::HIDDEN) {
other_window_->Hide();
}
}
private:
Window* other_window_;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateHidingWindowIfOccluded);
};
class WindowDelegateWithQueuedExpectation : public MockWindowDelegate {
public:
WindowDelegateWithQueuedExpectation() = default;
void set_queued_expectation(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) {
queued_expected_occlusion_state_ = occlusion_state;
queued_expected_occluded_region_ = occluded_region;
}
// MockWindowDelegate:
void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (queued_expected_occlusion_state_ != Window::OcclusionState::UNKNOWN) {
set_expectation(queued_expected_occlusion_state_,
queued_expected_occluded_region_);
queued_expected_occlusion_state_ = Window::OcclusionState::UNKNOWN;
queued_expected_occluded_region_ = SkRegion();
}
}
private:
Window::OcclusionState queued_expected_occlusion_state_ =
Window::OcclusionState::UNKNOWN;
SkRegion queued_expected_occluded_region_ = SkRegion();
DISALLOW_COPY_AND_ASSIGN(WindowDelegateWithQueuedExpectation);
};
} // 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.
WindowDelegateWithQueuedExpectation* delegate_a =
new WindowDelegateWithQueuedExpectation();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
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->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Hide the tracked window. It should be able to hide |window_a|. Before
// |window_a| is hidden, it will notice that the occlusion region has changed
// now that |window_b| is hidden. Then, it will be hidden by |window_b|.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_a->set_queued_expectation(Window::OcclusionState::HIDDEN,
SkRegion());
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
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(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (occlusion_state == Window::OcclusionState::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(Window::OcclusionState::VISIBLE, SkRegion());
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_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Create a tracked window. Expect it to be visible.
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(
Window::OcclusionState::VISIBLE,
SkRegionFromSkIRects({SkIRect::MakeXYWH(10, 0, 10, 10),
SkIRect::MakeXYWH(20, 0, 10, 10)}));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(20, 0, 10, 10)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(20, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
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(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->SetBounds(window_a->bounds());
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->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(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (!window_to_update_)
return;
++num_occlusion_change_;
if (window_to_update_->IsVisible()) {
window_to_update_->Hide();
if (num_occlusion_change_ <= 3)
set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
} else {
window_to_update_->Show();
set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
}
}
private:
Window* window_to_update_ = nullptr;
int num_occlusion_change_ = 0;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateChangingWindowVisibility);
};
} // namespace
// Verify that if a window changes its visibility every time it is notified that
// its occlusion state changed, a DCHECK occurs.
TEST_F(WindowOcclusionTrackerTest, OcclusionStatesDontBecomeStable) {
test::WindowOcclusionTrackerTestApi test_api(
root_window()->env()->GetWindowOcclusionTracker());
// Create 2 superposed tracked windows.
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
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_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 0, 10, 10)));
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_c = CreateTrackedWindow(delegate_c, gfx::Rect(10, 0, 10, 10));
EXPECT_FALSE(delegate_c->is_expecting_call());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
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_b->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(20, 0, 10, 10)));
delegate_d->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_d = CreateTrackedWindow(delegate_d, gfx::Rect(20, 0, 10, 10));
EXPECT_FALSE(delegate_b->is_expecting_call());
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
// VISIBLE.
EXPECT_DCHECK_DEATH({
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(20, 0, 10, 10)));
delegate_d->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_d->Hide();
});
}
// Verify that the occlusion states are correctly updated when a branch of the
// tree is hidden.
TEST_F(WindowOcclusionTrackerTest, HideTreeBranch) {
// Create a branch of 3 tracked windows. Expect them to be visible.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b =
CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10), window_a);
EXPECT_FALSE(delegate_b->is_expecting_call());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 20, 10, 10), window_b);
EXPECT_FALSE(delegate_c->is_expecting_call());
// Hide |window_b| (and hence |window_c|). Expect |window_b| and |window_c| to
// be hidden.
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_b->Hide();
EXPECT_FALSE(delegate_b->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
}
// Verify that a window covered by a shaped window isn't considered occluded.
TEST_F(WindowOcclusionTrackerTest, WindowWithAlphaShape) {
// Create 2 superposed tracked windows.
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());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Set a shape for the top window. The window underneath should no longer be
// occluded.
auto shape = std::make_unique<ui::Layer::ShapeRects>();
shape->emplace_back(0, 0, 5, 5);
// Shaped windows are not considered opaque, so the occluded region is empty.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetAlphaShape(std::move(shape));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Clear the shape for the top window. The window underneath should be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->layer()->SetAlphaShape(nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that a window covered by a window whose parent has an alpha shape
// isn't considered occluded.
TEST_F(WindowOcclusionTrackerTest, WindowWithParentAlphaShape) {
// Create a child and parent that cover another window.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeWH(10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
MockWindowDelegate* delegate_c = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
delegate_c->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_c, gfx::Rect(0, 0, 20, 20), window_b);
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_c->is_expecting_call());
// Set a shape for |window_b|. |window_a| and |window_b| should no longer be
// occluded.
auto shape = std::make_unique<ui::Layer::ShapeRects>();
shape->emplace_back(0, 0, 5, 5);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetAlphaShape(std::move(shape));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Clear the shape for |window_b|. |window_a| and |window_b| should be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
window_b->layer()->SetAlphaShape(nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
namespace {
class WindowDelegateHidingWindow : public MockWindowDelegate {
public:
WindowDelegateHidingWindow() = default;
void set_window_to_update(Window* window) { window_to_update_ = window; }
// MockWindowDelegate:
void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (!window_to_update_)
return;
window_to_update_->Hide();
}
private:
Window* window_to_update_ = nullptr;
DISALLOW_COPY_AND_ASSIGN(WindowDelegateHidingWindow);
};
class WindowDelegateAddingAndHidingChild : public MockWindowDelegate {
public:
explicit WindowDelegateAddingAndHidingChild(WindowOcclusionTrackerTest* test)
: test_(test) {}
void set_queued_expectation(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) {
queued_expected_occlusion_state_ = occlusion_state;
queued_expected_occluded_region_ = occluded_region;
}
void set_window_to_update(Window* window) { window_to_update_ = window; }
// MockWindowDelegate:
void OnWindowOcclusionChanged(Window::OcclusionState occlusion_state,
const SkRegion& occluded_region) override {
MockWindowDelegate::OnWindowOcclusionChanged(occlusion_state,
occluded_region);
if (queued_expected_occlusion_state_ != Window::OcclusionState::UNKNOWN) {
set_expectation(queued_expected_occlusion_state_,
queued_expected_occluded_region_);
queued_expected_occlusion_state_ = Window::OcclusionState::UNKNOWN;
queued_expected_occluded_region_ = SkRegion();
}
if (!window_to_update_)
return;
// Create a child window and hide it. Since this code runs when occlusion
// has already been recomputed twice, if one of the operations below causes
// occlusion to be recomputed, the test will fail with a DCHECK.
Window* window =
test_->CreateUntrackedWindow(gfx::Rect(0, 0, 5, 5), window_to_update_);
window->Hide();
}
private:
WindowOcclusionTrackerTest* test_;
Window* window_to_update_ = nullptr;
Window::OcclusionState queued_expected_occlusion_state_ =
Window::OcclusionState::UNKNOWN;
SkRegion queued_expected_occluded_region_ = SkRegion();
DISALLOW_COPY_AND_ASSIGN(WindowDelegateAddingAndHidingChild);
};
} // namespace
// Verify that hiding a window that has a hidden parent doesn't cause occlusion
// to be recomputed.
TEST_F(WindowOcclusionTrackerTest,
HideWindowWithHiddenParentOnOcclusionChange) {
test::WindowOcclusionTrackerTestApi test_api(
root_window()->env()->GetWindowOcclusionTracker());
auto* delegate_a = new WindowDelegateAddingAndHidingChild(this);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
auto* delegate_b = new WindowDelegateHidingWindow();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(0, 10, 10, 10)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(0, 10, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// When |window_b| is hidden, it will hide |window_a|. |window_a| will in turn
// add a child to itself and hide it.
delegate_a->set_window_to_update(window_a);
delegate_b->set_window_to_update(window_a);
// Initially A is marked as visible with no potential occlusion.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_a->set_queued_expectation(Window::OcclusionState::HIDDEN,
SkRegion());
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
// Hiding a child to |window_a| and hiding it shouldn't cause occlusion to be
// recomputed too many times (i.e. the call below shouldn't DCHECK).
window_b->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that hiding a window changes the occlusion region to show that the
// window is fully occluded.
TEST_F(WindowOcclusionTrackerTest,
HideWindowChangesOcclusionRegionToBeFullyOccluded) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_a->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Test partial occlusion, test partial occlusion changing hidden, alpha shape
// occlusion from multiple windows
// Verify that a window can occlude another one partially.
TEST_F(WindowOcclusionTrackerTest, WindowOccludesWindowPartially) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b, occluding window a partially.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(6, 7, 8, 9)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(6, 7, 8, 9));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Hiding window b should stop occluding window a partially.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
delegate_b->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window_b->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
}
// Verify that windows with alpha shape do not affect occlusion regions.
TEST_F(WindowOcclusionTrackerTest,
WindowWithAlphaShapeDoesNotPartiallyOccludeOtherWindows) {
// Create window a. Expect it to be non-occluded.
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 20, 20));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Create window b, occluding window a partially.
MockWindowDelegate* delegate_b = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(6, 7, 8, 9)));
delegate_b->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_b = CreateTrackedWindow(delegate_b, gfx::Rect(6, 7, 8, 9));
EXPECT_FALSE(delegate_a->is_expecting_call());
EXPECT_FALSE(delegate_b->is_expecting_call());
// Set a shape for window b. The window underneath should no longer be
// partially occluded.
auto shape = std::make_unique<ui::Layer::ShapeRects>();
shape->emplace_back(0, 0, 5, 5);
// Shaped windows are not considered opaque, so the occluded region is empty.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetAlphaShape(std::move(shape));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Clear the shape for the top window. The window underneath should be
// occluded.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(6, 7, 8, 9)));
window_b->layer()->SetAlphaShape(nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
// Verify that a window can be occluded by multiple other windows.
TEST_F(WindowOcclusionTrackerTest, WindowCanBeOccludedByMultipleWindows) {
// Create window a. Expect it to be non-occluded.
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());
SkRegion window_a_occlusion = SkRegion(SkIRect::MakeXYWH(9, 9, 5, 5));
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
CreateUntrackedWindow(gfx::Rect(9, 9, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
window_a_occlusion.op(SkIRect::MakeXYWH(-4, -4, 5, 5),
SkRegion::Op::kUnion_Op);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
CreateUntrackedWindow(gfx::Rect(-4, -4, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
window_a_occlusion.op(SkIRect::MakeXYWH(9, -4, 5, 5),
SkRegion::Op::kUnion_Op);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
CreateUntrackedWindow(gfx::Rect(9, -4, 5, 5));
EXPECT_FALSE(delegate_a->is_expecting_call());
window_a_occlusion.op(SkIRect::MakeXYWH(5, 5, 2, 3), SkRegion::Op::kUnion_Op);
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
window_a_occlusion);
CreateUntrackedWindow(gfx::Rect(5, 5, 2, 3));
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());
}
// Test that calling OnOcclusionStateChanged on a root window causes children
// of the root window to have their delegate notified that it is occluded or
// visible, depending on whether the root window is occluded or not.
TEST_F(WindowOcclusionTrackerTest, NativeWindowOcclusion) {
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->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
// Make the host call OnOcclusionStateChanged on the root window.
host()->SetNativeWindowOcclusionState(Window::OcclusionState::OCCLUDED);
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
host()->SetNativeWindowOcclusionState(Window::OcclusionState::VISIBLE);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
TEST_F(WindowOcclusionTrackerTest, ScopedForceVisible) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
window->Hide();
EXPECT_FALSE(delegate_a->is_expecting_call());
// Using ScopedForceVisible when the window is hidden should force it visible.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
{
WindowOcclusionTracker::ScopedForceVisible force_visible(window);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Destroying the ScopedForceVisible should return the window to hidden.
delegate_a->set_expectation(Window::OcclusionState::HIDDEN, SkRegion());
}
EXPECT_FALSE(delegate_a->is_expecting_call());
}
TEST_F(WindowOcclusionTrackerTest, ScopedForceVisibleSiblingsIgnored) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
CreateUntrackedWindow(gfx::Rect(0, 0, 100, 100), nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Using ScopedForceVisible when the window is occluded should force it
// visible.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
{
WindowOcclusionTracker::ScopedForceVisible force_visible(window);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Destroying the ScopedForceVisible should return the window to hidden.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
}
EXPECT_FALSE(delegate_a->is_expecting_call());
}
TEST_F(WindowOcclusionTrackerTest, ScopedForceVisibleWithOccludedSibling) {
// Creates three windows, a parent with two children. Both children have
// the same bounds.
std::unique_ptr<WindowOcclusionTracker::ScopedPause>
pause_occlusion_tracking =
std::make_unique<WindowOcclusionTracker::ScopedPause>(
root_window()->env());
MockWindowDelegate* parent_delegate = new MockWindowDelegate();
Window* parent_window =
CreateTrackedWindow(parent_delegate, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(parent_delegate->is_expecting_call());
MockWindowDelegate* occluded_child_delegate = new MockWindowDelegate();
CreateTrackedWindow(occluded_child_delegate, gfx::Rect(0, 0, 10, 10),
parent_window);
EXPECT_FALSE(occluded_child_delegate->is_expecting_call());
MockWindowDelegate* visible_child_delegate = new MockWindowDelegate();
CreateTrackedWindow(visible_child_delegate, gfx::Rect(0, 0, 10, 10),
parent_window);
EXPECT_FALSE(visible_child_delegate->is_expecting_call());
// Initial state after creation.
parent_delegate->set_expectation(Window::OcclusionState::VISIBLE);
occluded_child_delegate->set_expectation(Window::OcclusionState::OCCLUDED);
visible_child_delegate->set_expectation(Window::OcclusionState::VISIBLE);
pause_occlusion_tracking.reset();
EXPECT_FALSE(parent_delegate->is_expecting_call());
EXPECT_FALSE(occluded_child_delegate->is_expecting_call());
EXPECT_FALSE(visible_child_delegate->is_expecting_call());
// Hiding the parent should result in all windows being hidden.
parent_delegate->set_expectation(Window::OcclusionState::HIDDEN);
occluded_child_delegate->set_expectation(Window::OcclusionState::HIDDEN);
visible_child_delegate->set_expectation(Window::OcclusionState::HIDDEN);
parent_window->Hide();
EXPECT_FALSE(parent_delegate->is_expecting_call());
EXPECT_FALSE(occluded_child_delegate->is_expecting_call());
EXPECT_FALSE(visible_child_delegate->is_expecting_call());
// Creating a ScopedForceVisible for the parent should return to the initial
// state.
parent_delegate->set_expectation(Window::OcclusionState::VISIBLE);
occluded_child_delegate->set_expectation(Window::OcclusionState::OCCLUDED);
visible_child_delegate->set_expectation(Window::OcclusionState::VISIBLE);
WindowOcclusionTracker::ScopedForceVisible force_visible(parent_window);
EXPECT_FALSE(parent_delegate->is_expecting_call());
EXPECT_FALSE(occluded_child_delegate->is_expecting_call());
EXPECT_FALSE(visible_child_delegate->is_expecting_call());
// Do another show, so that once the |force_visible| is destroyed the
// assertions in MockWindowDelegate aren't tripped.
parent_window->Show();
}
TEST_F(WindowOcclusionTrackerTest, ComputeTargetOcclusionForWindow) {
auto* window_a = CreateUntrackedWindow(gfx::Rect(0, 0, 10, 10));
CreateUntrackedWindow(gfx::Rect(9, 9, 5, 5));
CreateUntrackedWindow(gfx::Rect(-4, -4, 5, 5));
CreateUntrackedWindow(gfx::Rect(9, -4, 5, 5));
CreateUntrackedWindow(gfx::Rect(5, 5, 2, 3));
SkRegion window_a_occlusion = SkRegionFromSkIRects(
{SkIRect::MakeXYWH(9, 9, 5, 5), SkIRect::MakeXYWH(-4, -4, 5, 5),
SkIRect::MakeXYWH(9, -4, 5, 5), SkIRect::MakeXYWH(5, 5, 2, 3)});
auto& occlusion_tracker = GetOcclusionTracker();
window_a->TrackOcclusionState();
auto occlusion_data =
occlusion_tracker.ComputeTargetOcclusionForWindow(window_a);
EXPECT_EQ(Window::OcclusionState::VISIBLE, occlusion_data.occlusion_state);
EXPECT_EQ(window_a_occlusion, occlusion_data.occluded_region);
}
TEST_F(WindowOcclusionTrackerTest,
ComputeTargetOcclusionForWindowUsesTargetBounds) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::VISIBLE,
SkRegion(SkIRect::MakeXYWH(10, 10, 10, 10)));
Window* window_b = CreateUntrackedWindow(gfx::Rect(10, 10, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
// Should be visible for target occlusion.
auto& occlusion_tracker = GetOcclusionTracker();
auto occlusion_data =
occlusion_tracker.ComputeTargetOcclusionForWindow(window_a);
EXPECT_EQ(Window::OcclusionState::VISIBLE, occlusion_data.occlusion_state);
EXPECT_EQ(SkRegion(SkIRect::MakeXYWH(10, 10, 10, 10)),
occlusion_data.occluded_region);
// Start animating |window_b| to fully occlude |window_a|.
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::CreateImplicitAnimator());
ui::ScopedLayerAnimationSettings layer_animation_settings(
test_controller.animator());
layer_animation_settings.SetTransitionDuration(kTransitionDuration);
window_b->layer()->SetAnimator(test_controller.animator());
window_b->SetBounds(gfx::Rect(0, 0, 10, 10));
// Animated windows are ignored by the occlusion tracker.
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
test_controller.Step(kTransitionDuration / 3);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Target occlusion should include them, however:
occlusion_data = occlusion_tracker.ComputeTargetOcclusionForWindow(window_a);
EXPECT_EQ(Window::OcclusionState::OCCLUDED, occlusion_data.occlusion_state);
EXPECT_EQ(SkRegion(), occlusion_data.occluded_region);
// Don't expect the target occlusion to be affected by progress through
// the animation.
test_controller.Step(kTransitionDuration / 3);
occlusion_data = occlusion_tracker.ComputeTargetOcclusionForWindow(window_a);
EXPECT_EQ(Window::OcclusionState::OCCLUDED, occlusion_data.occlusion_state);
EXPECT_EQ(SkRegion(), occlusion_data.occluded_region);
// Finish the animation, and expect the occlusion state to update.
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
test_controller.Step(kTransitionDuration / 3);
window_b->layer()->SetAnimator(nullptr);
EXPECT_FALSE(delegate_a->is_expecting_call());
}
TEST_F(WindowOcclusionTrackerTest,
ComputeTargetOcclusionForWindowUsesTargetOpacity) {
MockWindowDelegate* delegate_a = new MockWindowDelegate();
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
Window* window_a = CreateTrackedWindow(delegate_a, gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::OCCLUDED, SkRegion());
Window* window_b = CreateUntrackedWindow(gfx::Rect(0, 0, 10, 10));
EXPECT_FALSE(delegate_a->is_expecting_call());
delegate_a->set_expectation(Window::OcclusionState::VISIBLE, SkRegion());
window_b->layer()->SetOpacity(0.0f);
EXPECT_FALSE(delegate_a->is_expecting_call());
// Should be visible for target occlusion.
auto& occlusion_tracker = GetOcclusionTracker();
auto occlusion_data =
occlusion_tracker.ComputeTargetOcclusionForWindow(window_a);
EXPECT_EQ(Window::OcclusionState::VISIBLE, occlusion_data.occlusion_state);
EXPECT_EQ(SkRegion(), occlusion_data.occluded_region);
// Start animating |window_b| to fully occlude |window_a|.
ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode(
ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION);
ui::LayerAnimatorTestController test_controller(
ui::LayerAnimator::Cr