// Copyright (c) 2012 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.h" #include #include #include #include #include #include "base/compiler_specific.h" #include "base/macros.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/strings/stringprintf.h" #include "base/test/bind.h" #include "build/build_config.h" #include "cc/trees/layer_tree_frame_sink.h" #include "testing/gtest/include/gtest/gtest.h" #include "ui/aura/client/capture_client.h" #include "ui/aura/client/focus_change_observer.h" #include "ui/aura/client/visibility_client.h" #include "ui/aura/client/window_parenting_client.h" #include "ui/aura/layout_manager.h" #include "ui/aura/scoped_window_capture_request.h" #include "ui/aura/scoped_window_event_targeting_blocker.h" #include "ui/aura/test/aura_test_base.h" #include "ui/aura/test/aura_test_utils.h" #include "ui/aura/test/test_window_delegate.h" #include "ui/aura/test/test_windows.h" #include "ui/aura/test/window_test_api.h" #include "ui/aura/window_delegate.h" #include "ui/aura/window_event_dispatcher.h" #include "ui/aura/window_observer.h" #include "ui/aura/window_tracker.h" #include "ui/aura/window_tree_host.h" #include "ui/base/class_property.h" #include "ui/base/hit_test.h" #include "ui/compositor/compositor_observer.h" #include "ui/compositor/layer.h" #include "ui/compositor/layer_animation_observer.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/compositor/test/test_layers.h" #include "ui/display/screen.h" #include "ui/events/event.h" #include "ui/events/event_utils.h" #include "ui/events/gesture_detection/gesture_configuration.h" #include "ui/events/keycodes/keyboard_codes.h" #include "ui/events/test/event_generator.h" #include "ui/gfx/canvas.h" #include "ui/gfx/geometry/vector2d.h" #include "ui/gfx/overlay_transform_utils.h" #include "ui/gfx/skia_util.h" DEFINE_UI_CLASS_PROPERTY_TYPE(const char*) namespace { enum class DeletionOrder { LAYOUT_MANAGER_FIRST, PROPERTY_FIRST, UNKNOWN, }; class DeletionTracker { public: DeletionTracker() {} ~DeletionTracker() {} DeletionOrder order() const { return order_; } bool property_deleted() const { return property_deleted_; } bool layout_manager_deleted() const { return layout_manager_deleted_; } void PropertyDeleted() { property_deleted_ = true; if (order_ == DeletionOrder::UNKNOWN) order_ = DeletionOrder::PROPERTY_FIRST; } void LayoutManagerDeleted() { layout_manager_deleted_ = true; if (order_ == DeletionOrder::UNKNOWN) order_ = DeletionOrder::LAYOUT_MANAGER_FIRST; } private: bool property_deleted_ = false; bool layout_manager_deleted_ = false; DeletionOrder order_ = DeletionOrder::UNKNOWN; DISALLOW_COPY_AND_ASSIGN(DeletionTracker); }; // The helper class to wait for the animation completion and run callbacks. class LayerTranslationAnimationNotifier : public ui::CompositorObserver { public: using AnimationCallback = base::RepeatingCallback; LayerTranslationAnimationNotifier( ui::Layer* animation_layer, const gfx::Transform& initial_transform_, const gfx::Transform& target_transform, AnimationCallback animation_start_callback, AnimationCallback animation_end_callback, AnimationCallback animation_progress_callback) : animation_layer_(animation_layer), initial_transform_(initial_transform_), target_transform_(target_transform), animation_start_callback_(animation_start_callback), animation_end_callback_(animation_end_callback), animation_progress_callback_(animation_progress_callback) { animation_layer_->GetCompositor()->AddObserver(this); } LayerTranslationAnimationNotifier(const LayerTranslationAnimationNotifier&) = delete; LayerTranslationAnimationNotifier& operator=( const LayerTranslationAnimationNotifier&) = delete; ~LayerTranslationAnimationNotifier() override { animation_layer_->GetCompositor()->RemoveObserver(this); } void WaitForAnimationCompletion() { run_loop_.Run(); } // ui::CompositorObserver: void OnCompositingDidCommit(ui::Compositor* compositor) override { const gfx::Transform current_transform = animation_layer_->transform(); if (current_transform == initial_transform_) { animation_start_callback_.Run(current_transform); } else if (current_transform == target_transform_) { animation_end_callback_.Run(current_transform); run_loop_.Quit(); } else { animation_progress_callback_.Run(current_transform); } } private: // The layer to be animated. ui::Layer* const animation_layer_; // The initial transform. gfx::Transform initial_transform_; // The target transform. gfx::Transform target_transform_; // The callback to run at the start of the animation. AnimationCallback animation_start_callback_; // The callback to run at the end of the animation. AnimationCallback animation_end_callback_; // The callback to run during the progress of the animation. AnimationCallback animation_progress_callback_; base::RunLoop run_loop_; }; class DeletionTestProperty { public: explicit DeletionTestProperty(DeletionTracker* tracker) : tracker_(tracker) {} ~DeletionTestProperty() { tracker_->PropertyDeleted(); } private: DeletionTracker* tracker_; DISALLOW_COPY_AND_ASSIGN(DeletionTestProperty); }; DEFINE_OWNED_UI_CLASS_PROPERTY_KEY(DeletionTestProperty, kDeletionTestPropertyKey, nullptr) } // namespace DEFINE_UI_CLASS_PROPERTY_TYPE(DeletionTestProperty*) namespace aura { namespace test { namespace { class WindowTest : public AuraTestBase { public: WindowTest() : max_separation_(0) { } void SetUp() override { AuraTestBase::SetUp(); // TODO: there needs to be an easier way to do this. max_separation_ = ui::GestureConfiguration::GetInstance() ->max_separation_for_gesture_touches_in_pixels(); ui::GestureConfiguration::GetInstance() ->set_max_separation_for_gesture_touches_in_pixels(0); } void TearDown() override { AuraTestBase::TearDown(); ui::GestureConfiguration::GetInstance() ->set_max_separation_for_gesture_touches_in_pixels(max_separation_); } private: float max_separation_; DISALLOW_COPY_AND_ASSIGN(WindowTest); }; // Used for verifying destruction methods are invoked. class DestroyTrackingDelegateImpl : public TestWindowDelegate { public: DestroyTrackingDelegateImpl() : destroying_count_(0), destroyed_count_(0), in_destroying_(false) {} void clear_destroying_count() { destroying_count_ = 0; } int destroying_count() const { return destroying_count_; } void clear_destroyed_count() { destroyed_count_ = 0; } int destroyed_count() const { return destroyed_count_; } bool in_destroying() const { return in_destroying_; } void OnWindowDestroying(Window* window) override { EXPECT_FALSE(in_destroying_); in_destroying_ = true; destroying_count_++; } void OnWindowDestroyed(Window* window) override { EXPECT_TRUE(in_destroying_); in_destroying_ = false; destroyed_count_++; } private: int destroying_count_; int destroyed_count_; bool in_destroying_; DISALLOW_COPY_AND_ASSIGN(DestroyTrackingDelegateImpl); }; // Used to verify that when OnWindowDestroying is invoked the parent is also // is in the process of being destroyed. class ChildWindowDelegateImpl : public DestroyTrackingDelegateImpl { public: explicit ChildWindowDelegateImpl( DestroyTrackingDelegateImpl* parent_delegate) : parent_delegate_(parent_delegate) { } void OnWindowDestroying(Window* window) override { EXPECT_TRUE(parent_delegate_->in_destroying()); DestroyTrackingDelegateImpl::OnWindowDestroying(window); } private: DestroyTrackingDelegateImpl* parent_delegate_; DISALLOW_COPY_AND_ASSIGN(ChildWindowDelegateImpl); }; // Used to verify that a Window is removed from its parent when // OnWindowDestroyed is called. class DestroyOrphanDelegate : public TestWindowDelegate { public: DestroyOrphanDelegate() : window_(nullptr) {} void set_window(Window* window) { window_ = window; } void OnWindowDestroyed(Window* window) override { EXPECT_FALSE(window_->parent()); } private: Window* window_; DISALLOW_COPY_AND_ASSIGN(DestroyOrphanDelegate); }; // Used in verifying mouse capture. class CaptureWindowDelegateImpl : public TestWindowDelegate { public: CaptureWindowDelegateImpl() { ResetCounts(); } void ResetCounts() { capture_changed_event_count_ = 0; capture_lost_count_ = 0; mouse_event_count_ = 0; touch_event_count_ = 0; gesture_event_count_ = 0; } int capture_changed_event_count() const { return capture_changed_event_count_; } int capture_lost_count() const { return capture_lost_count_; } int mouse_event_count() const { return mouse_event_count_; } int touch_event_count() const { return touch_event_count_; } int gesture_event_count() const { return gesture_event_count_; } void OnMouseEvent(ui::MouseEvent* event) override { if (event->type() == ui::ET_MOUSE_CAPTURE_CHANGED) capture_changed_event_count_++; mouse_event_count_++; } void OnTouchEvent(ui::TouchEvent* event) override { touch_event_count_++; } void OnGestureEvent(ui::GestureEvent* event) override { gesture_event_count_++; } void OnCaptureLost() override { capture_lost_count_++; } private: int capture_changed_event_count_; int capture_lost_count_; int mouse_event_count_; int touch_event_count_; int gesture_event_count_; DISALLOW_COPY_AND_ASSIGN(CaptureWindowDelegateImpl); }; // Keeps track of the location of the gesture. class GestureTrackPositionDelegate : public TestWindowDelegate { public: GestureTrackPositionDelegate() {} void OnGestureEvent(ui::GestureEvent* event) override { position_ = event->location(); event->StopPropagation(); } const gfx::Point& position() const { return position_; } private: gfx::Point position_; DISALLOW_COPY_AND_ASSIGN(GestureTrackPositionDelegate); }; base::TimeTicks getTime() { return ui::EventTimeForNow(); } class SelfEventHandlingWindowDelegate : public TestWindowDelegate { public: SelfEventHandlingWindowDelegate() {} bool ShouldDescendIntoChildForEventHandling( Window* child, const gfx::Point& location) override { return false; } private: DISALLOW_COPY_AND_ASSIGN(SelfEventHandlingWindowDelegate); }; // The delegate deletes itself when the window is being destroyed. class DestroyWindowDelegate : public TestWindowDelegate { public: DestroyWindowDelegate() {} private: ~DestroyWindowDelegate() override {} // Overridden from WindowDelegate. void OnWindowDestroyed(Window* window) override { delete this; } DISALLOW_COPY_AND_ASSIGN(DestroyWindowDelegate); }; void OffsetBounds(Window* window, int horizontal, int vertical) { gfx::Rect bounds = window->bounds(); bounds.Offset(horizontal, vertical); window->SetBounds(bounds); } TEST_F(WindowTest, GetChildById) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); std::unique_ptr w11(CreateTestWindowWithId(11, w1.get())); std::unique_ptr w111(CreateTestWindowWithId(111, w11.get())); std::unique_ptr w12(CreateTestWindowWithId(12, w1.get())); EXPECT_FALSE(w1->GetChildById(57)); EXPECT_EQ(w12.get(), w1->GetChildById(12)); EXPECT_EQ(w111.get(), w1->GetChildById(111)); } // Make sure that Window::Contains correctly handles children, grandchildren, // and not containing NULL or parents. TEST_F(WindowTest, Contains) { Window parent(nullptr); parent.Init(ui::LAYER_NOT_DRAWN); Window child1(nullptr); child1.Init(ui::LAYER_NOT_DRAWN); Window child2(nullptr); child2.Init(ui::LAYER_NOT_DRAWN); parent.AddChild(&child1); child1.AddChild(&child2); EXPECT_TRUE(parent.Contains(&parent)); EXPECT_TRUE(parent.Contains(&child1)); EXPECT_TRUE(parent.Contains(&child2)); EXPECT_FALSE(parent.Contains(nullptr)); EXPECT_FALSE(child1.Contains(&parent)); EXPECT_FALSE(child2.Contains(&child1)); } TEST_F(WindowTest, ContainsPointInRoot) { std::unique_ptr w(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 5, 5), root_window())); EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(9, 9))); EXPECT_TRUE(w->ContainsPointInRoot(gfx::Point(10, 10))); EXPECT_TRUE(w->ContainsPointInRoot(gfx::Point(14, 14))); EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(15, 15))); EXPECT_FALSE(w->ContainsPointInRoot(gfx::Point(20, 20))); } TEST_F(WindowTest, ContainsPoint) { std::unique_ptr w(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 5, 5), root_window())); EXPECT_TRUE(w->ContainsPoint(gfx::Point(0, 0))); EXPECT_TRUE(w->ContainsPoint(gfx::Point(4, 4))); EXPECT_FALSE(w->ContainsPoint(gfx::Point(5, 5))); EXPECT_FALSE(w->ContainsPoint(gfx::Point(10, 10))); } TEST_F(WindowTest, MakeWindowCapturable) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); // Initially the window is not capturable. EXPECT_FALSE(w1->subtree_capture_id().is_valid()); // Creating requests makes the window capturable as long as those requests // remain alive. ScopedWindowCaptureRequest request1 = w1->MakeWindowCapturable(); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); EXPECT_EQ(request1.GetCaptureId(), w1->subtree_capture_id()); EXPECT_EQ(request1.GetCaptureId(), w1->layer()->GetSubtreeCaptureId()); // A new request does not affect the subtree capture ID. const viz::SubtreeCaptureId current_id = w1->subtree_capture_id(); ScopedWindowCaptureRequest request2 = w1->MakeWindowCapturable(); EXPECT_EQ(current_id, w1->subtree_capture_id()); EXPECT_EQ(request1.GetCaptureId(), request2.GetCaptureId()); // Create a new request, then move an existing request into it. This should // invalidate the moved-from request. ScopedWindowCaptureRequest request3 = w1->MakeWindowCapturable(); EXPECT_EQ(current_id, request3.GetCaptureId()); request3 = std::move(request2); EXPECT_FALSE(request2.window()); EXPECT_FALSE(request2.GetCaptureId().is_valid()); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); // Destroying |request2| does nothing. auto consume_request = [](ScopedWindowCaptureRequest request) {}; consume_request(std::move(request2)); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); EXPECT_EQ(current_id, w1->subtree_capture_id()); // Destroying |request1| won't affect the window, it will remain capturable, // since |request3| is still alive. consume_request(std::move(request1)); EXPECT_FALSE(request1.window()); EXPECT_FALSE(request1.GetCaptureId().is_valid()); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); EXPECT_EQ(current_id, w1->subtree_capture_id()); // Once all requests are destroyed, the window no longer has a valid capture // ID. consume_request(std::move(request3)); EXPECT_FALSE(w1->subtree_capture_id().is_valid()); EXPECT_FALSE(w1->layer()->GetSubtreeCaptureId().is_valid()); } TEST_F(WindowTest, DeletingCapturableWindows) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); // Initially the window is not capturable. EXPECT_FALSE(w1->subtree_capture_id().is_valid()); // Deleting a window with capture requests on them will not result in a use- // after-free crash. ScopedWindowCaptureRequest request1 = w1->MakeWindowCapturable(); ScopedWindowCaptureRequest request2 = w1->MakeWindowCapturable(); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); w1.reset(); EXPECT_FALSE(request1.GetCaptureId().is_valid()); EXPECT_FALSE(request2.GetCaptureId().is_valid()); } TEST_F(WindowTest, LayerReleasingAndSettingOfCapturableWindow) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); EXPECT_FALSE(w1->subtree_capture_id().is_valid()); ScopedWindowCaptureRequest request = w1->MakeWindowCapturable(); EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid()); // Releasing the capturable window's layer (i.e. it's no longer associated // with the window) will clear its capture ID. However, the window remains // marked as capturable with a valid SubtreeCaptureId even though it has no // layer. std::unique_ptr taken_layer = w1->ReleaseLayer(); EXPECT_FALSE(w1->layer()); EXPECT_FALSE(taken_layer->GetSubtreeCaptureId().is_valid()); EXPECT_TRUE(w1->subtree_capture_id().is_valid()); // Setting a new layer on the window will set the layer's capture ID. auto new_layer = std::make_unique(); taken_layer->parent()->Add(new_layer.get()); w1->Reset(std::move(new_layer)); EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid()); EXPECT_EQ(request.GetCaptureId(), w1->layer()->GetSubtreeCaptureId()); } TEST_F(WindowTest, RecreateLayerOfCapturableWindow) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); EXPECT_FALSE(w1->subtree_capture_id().is_valid()); ScopedWindowCaptureRequest request = w1->MakeWindowCapturable(); EXPECT_TRUE(w1->layer()->GetSubtreeCaptureId().is_valid()); // Recreating the layer of a capturable window will preserve the capture ID // on the newly recreated window, and clears it from the old layer. const viz::SubtreeCaptureId current_id = w1->subtree_capture_id(); std::unique_ptr old_layer = w1->RecreateLayer(); EXPECT_FALSE(old_layer->GetSubtreeCaptureId().is_valid()); EXPECT_EQ(current_id, w1->subtree_capture_id()); EXPECT_EQ(current_id, w1->layer()->GetSubtreeCaptureId()); } TEST_F(WindowTest, ConvertPointToWindow) { // Window::ConvertPointToWindow is mostly identical to // Layer::ConvertPointToLayer, except NULL values for |source| are permitted, // in which case the function just returns. std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); gfx::Point reference_point(100, 100); gfx::Point test_point = reference_point; Window::ConvertPointToTarget(nullptr, w1.get(), &test_point); EXPECT_EQ(reference_point, test_point); } TEST_F(WindowTest, MoveCursorTo) { std::unique_ptr w1(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), root_window())); std::unique_ptr w11( CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); std::unique_ptr w111( CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); std::unique_ptr w1111( CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); Window* root = root_window(); root->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("10,10", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); w1->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("20,20", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); w11->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("25,25", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); w111->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("30,30", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); w1111->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("35,35", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); } TEST_F(WindowTest, ContainsMouse) { std::unique_ptr w(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), root_window())); w->Show(); WindowTestApi w_test_api(w.get()); Window* root = root_window(); root->MoveCursorTo(gfx::Point(10, 10)); EXPECT_TRUE(w_test_api.ContainsMouse()); root->MoveCursorTo(gfx::Point(9, 10)); EXPECT_FALSE(w_test_api.ContainsMouse()); } // Tests that the root window gets a valid LocalSurfaceId. TEST_F(WindowTest, RootWindowHasValidLocalSurfaceId) { EXPECT_TRUE(root_window()->GetLocalSurfaceId().is_valid()); } TEST_F(WindowTest, WindowEmbeddingClientHasValidLocalSurfaceId) { std::unique_ptr window(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 300, 200), root_window())); test::WindowTestApi(window.get()).DisableFrameSinkRegistration(); window->SetEmbedFrameSinkId(viz::FrameSinkId(0, 1)); EXPECT_TRUE(window->GetLocalSurfaceId().is_valid()); } // Test Window::ConvertPointToWindow() with transform to root_window. TEST_F(WindowTest, MoveCursorToWithTransformRootWindow) { gfx::Transform transform; transform.Translate(100.0, 100.0); transform = transform * OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_90, gfx::SizeF()); transform.Scale(2.0, 5.0); host()->SetRootTransform(transform); host()->MoveCursorToLocationInDIP(gfx::Point(10, 10)); #if !defined(OS_WIN) // TODO(yoshiki): fix this to build on Windows. See crbug.com/133413.OD EXPECT_EQ("50,120", QueryLatestMousePositionRequestInHost(host()).ToString()); #endif EXPECT_EQ("10,10", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); } // Tests Window::ConvertPointToWindow() with transform to non-root windows. TEST_F(WindowTest, MoveCursorToWithTransformWindow) { std::unique_ptr w1(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), root_window())); gfx::Transform transform1; transform1.Scale(2, 2); w1->SetTransform(transform1); w1->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("30,30", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); gfx::Transform transform2; transform2.Translate(-10, 20); w1->SetTransform(transform2); w1->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("10,40", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); gfx::Transform transform3; transform3 = transform3 * OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_90, gfx::SizeF()); w1->SetTransform(transform3); w1->MoveCursorTo(gfx::Point(5, 5)); EXPECT_EQ("5,15", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); gfx::Transform transform4; transform4.Translate(100.0, 100.0); transform4 = transform4 * OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_90, gfx::SizeF()); transform4.Scale(2.0, 5.0); w1->SetTransform(transform4); w1->MoveCursorTo(gfx::Point(10, 10)); EXPECT_EQ("60,130", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); } // Test Window::ConvertPointToWindow() with complex transforms to both root and // non-root windows. // Test Window::ConvertPointToWindow() with transform to root_window. TEST_F(WindowTest, MoveCursorToWithComplexTransform) { std::unique_ptr w1(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), root_window())); std::unique_ptr w11( CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); std::unique_ptr w111( CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); std::unique_ptr w1111( CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); // The root window expects transforms that produce integer rects. gfx::Transform root_transform; root_transform.Translate(60.0, 70.0); root_transform = root_transform * OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_270, gfx::SizeF()); root_transform.Translate(-50.0, -50.0); root_transform.Scale(2.0, 3.0); gfx::Transform transform; transform.Translate(10.0, 20.0); transform.Rotate(10.0); transform.Scale(0.3f, 0.5f); host()->SetRootTransform(root_transform); w1->SetTransform(transform); w11->SetTransform(transform); w111->SetTransform(transform); w1111->SetTransform(transform); w1111->MoveCursorTo(gfx::Point(10, 10)); #if !defined(OS_WIN) // TODO(yoshiki): fix this to build on Windows. See crbug.com/133413. EXPECT_EQ("169,80", QueryLatestMousePositionRequestInHost(host()).ToString()); #endif EXPECT_EQ("20,53", display::Screen::GetScreen()->GetCursorScreenPoint().ToString()); } // Tests that we do not crash when a Window is destroyed by going out of // scope (as opposed to being explicitly deleted by its WindowDelegate). TEST_F(WindowTest, NoCrashOnWindowDelete) { CaptureWindowDelegateImpl delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); } TEST_F(WindowTest, GetEventHandlerForPoint) { std::unique_ptr w1(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 10, 500, 500), root_window())); std::unique_ptr w11( CreateTestWindow(SK_ColorGREEN, 11, gfx::Rect(5, 5, 100, 100), w1.get())); std::unique_ptr w111( CreateTestWindow(SK_ColorCYAN, 111, gfx::Rect(5, 5, 75, 75), w11.get())); std::unique_ptr w1111( CreateTestWindow(SK_ColorRED, 1111, gfx::Rect(5, 5, 50, 50), w111.get())); std::unique_ptr w12(CreateTestWindow( SK_ColorMAGENTA, 12, gfx::Rect(10, 420, 25, 25), w1.get())); std::unique_ptr w121( CreateTestWindow(SK_ColorYELLOW, 121, gfx::Rect(5, 5, 5, 5), w12.get())); std::unique_ptr w13( CreateTestWindow(SK_ColorGRAY, 13, gfx::Rect(5, 470, 50, 50), w1.get())); Window* root = root_window(); w1->parent()->SetBounds(gfx::Rect(500, 500)); EXPECT_EQ(nullptr, root->GetEventHandlerForPoint(gfx::Point(5, 5))); EXPECT_EQ(w1.get(), root->GetEventHandlerForPoint(gfx::Point(11, 11))); EXPECT_EQ(w11.get(), root->GetEventHandlerForPoint(gfx::Point(16, 16))); EXPECT_EQ(w111.get(), root->GetEventHandlerForPoint(gfx::Point(21, 21))); EXPECT_EQ(w1111.get(), root->GetEventHandlerForPoint(gfx::Point(26, 26))); EXPECT_EQ(w12.get(), root->GetEventHandlerForPoint(gfx::Point(21, 431))); EXPECT_EQ(w121.get(), root->GetEventHandlerForPoint(gfx::Point(26, 436))); EXPECT_EQ(w13.get(), root->GetEventHandlerForPoint(gfx::Point(26, 481))); } TEST_F(WindowTest, GetEventHandlerForPointInCornerOfChildBounds) { // If our child is flush to our top-left corner it gets events just inside the // window edges. std::unique_ptr parent(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(10, 20, 400, 500), root_window())); std::unique_ptr child( CreateTestWindow(SK_ColorRED, 2, gfx::Rect(0, 0, 60, 70), parent.get())); EXPECT_EQ(child.get(), parent->GetEventHandlerForPoint(gfx::Point(0, 0))); EXPECT_EQ(child.get(), parent->GetEventHandlerForPoint(gfx::Point(1, 1))); } TEST_F(WindowTest, GetEventHandlerForPointWithOverrideDescendingOrder) { std::unique_ptr parent_delegate( new SelfEventHandlingWindowDelegate); std::unique_ptr parent(CreateTestWindowWithDelegate( parent_delegate.get(), 1, gfx::Rect(10, 20, 400, 500), root_window())); std::unique_ptr child(CreateTestWindow( SK_ColorRED, 2, gfx::Rect(0, 0, 390, 480), parent.get())); // We can override ShouldDescendIntoChildForEventHandling to make the parent // grab all events. EXPECT_EQ(parent.get(), parent->GetEventHandlerForPoint(gfx::Point(0, 0))); EXPECT_EQ(parent.get(), parent->GetEventHandlerForPoint(gfx::Point(50, 50))); } TEST_F(WindowTest, GetToplevelWindow) { const gfx::Rect kBounds(0, 0, 10, 10); TestWindowDelegate delegate; std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); std::unique_ptr w11( CreateTestWindowWithDelegate(&delegate, 11, kBounds, w1.get())); std::unique_ptr w111(CreateTestWindowWithId(111, w11.get())); std::unique_ptr w1111( CreateTestWindowWithDelegate(&delegate, 1111, kBounds, w111.get())); EXPECT_TRUE(root_window()->GetToplevelWindow() == nullptr); EXPECT_TRUE(w1->GetToplevelWindow() == nullptr); EXPECT_EQ(w11.get(), w11->GetToplevelWindow()); EXPECT_EQ(w11.get(), w111->GetToplevelWindow()); EXPECT_EQ(w11.get(), w1111->GetToplevelWindow()); } class AddedToRootWindowObserver : public WindowObserver { public: AddedToRootWindowObserver() : called_(false) {} void OnWindowAddedToRootWindow(Window* window) override { called_ = true; } bool called() const { return called_; } private: bool called_; DISALLOW_COPY_AND_ASSIGN(AddedToRootWindowObserver); }; TEST_F(WindowTest, WindowAddedToRootWindowShouldNotifyChildAndNotParent) { AddedToRootWindowObserver parent_observer; AddedToRootWindowObserver child_observer; std::unique_ptr parent_window( CreateTestWindowWithId(1, root_window())); std::unique_ptr child_window(new Window(nullptr)); child_window->Init(ui::LAYER_TEXTURED); child_window->Show(); parent_window->AddObserver(&parent_observer); child_window->AddObserver(&child_observer); parent_window->AddChild(child_window.get()); EXPECT_FALSE(parent_observer.called()); EXPECT_TRUE(child_observer.called()); parent_window->RemoveObserver(&parent_observer); child_window->RemoveObserver(&child_observer); } // Various destruction assertions. TEST_F(WindowTest, DestroyTest) { DestroyTrackingDelegateImpl parent_delegate; ChildWindowDelegateImpl child_delegate(&parent_delegate); { std::unique_ptr parent(CreateTestWindowWithDelegate( &parent_delegate, 0, gfx::Rect(), root_window())); CreateTestWindowWithDelegate(&child_delegate, 0, gfx::Rect(), parent.get()); } // Both the parent and child should have been destroyed. EXPECT_EQ(1, parent_delegate.destroying_count()); EXPECT_EQ(1, parent_delegate.destroyed_count()); EXPECT_EQ(1, child_delegate.destroying_count()); EXPECT_EQ(1, child_delegate.destroyed_count()); } // Tests that a window is orphaned before OnWindowDestroyed is called. TEST_F(WindowTest, OrphanedBeforeOnDestroyed) { TestWindowDelegate parent_delegate; DestroyOrphanDelegate child_delegate; { std::unique_ptr parent(CreateTestWindowWithDelegate( &parent_delegate, 0, gfx::Rect(), root_window())); std::unique_ptr child(CreateTestWindowWithDelegate( &child_delegate, 0, gfx::Rect(), parent.get())); child_delegate.set_window(child.get()); } } // Make sure StackChildAtTop moves both the window and layer to the front. TEST_F(WindowTest, StackChildAtTop) { Window parent(nullptr); parent.Init(ui::LAYER_NOT_DRAWN); Window child1(nullptr); child1.Init(ui::LAYER_NOT_DRAWN); Window child2(nullptr); child2.Init(ui::LAYER_NOT_DRAWN); parent.AddChild(&child1); parent.AddChild(&child2); ASSERT_EQ(2u, parent.children().size()); EXPECT_EQ(&child1, parent.children()[0]); EXPECT_EQ(&child2, parent.children()[1]); ASSERT_EQ(2u, parent.layer()->children().size()); EXPECT_EQ(child1.layer(), parent.layer()->children()[0]); EXPECT_EQ(child2.layer(), parent.layer()->children()[1]); parent.StackChildAtTop(&child1); ASSERT_EQ(2u, parent.children().size()); EXPECT_EQ(&child1, parent.children()[1]); EXPECT_EQ(&child2, parent.children()[0]); ASSERT_EQ(2u, parent.layer()->children().size()); EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); } // Make sure StackChildBelow works. TEST_F(WindowTest, StackChildBelow) { Window parent(nullptr); parent.Init(ui::LAYER_NOT_DRAWN); Window child1(nullptr); child1.Init(ui::LAYER_NOT_DRAWN); child1.SetId(1); Window child2(nullptr); child2.Init(ui::LAYER_NOT_DRAWN); child2.SetId(2); Window child3(nullptr); child3.Init(ui::LAYER_NOT_DRAWN); child3.SetId(3); parent.AddChild(&child1); parent.AddChild(&child2); parent.AddChild(&child3); EXPECT_EQ("1 2 3", ChildWindowIDsAsString(&parent)); parent.StackChildBelow(&child1, &child2); EXPECT_EQ("1 2 3", ChildWindowIDsAsString(&parent)); parent.StackChildBelow(&child2, &child1); EXPECT_EQ("2 1 3", ChildWindowIDsAsString(&parent)); parent.StackChildBelow(&child3, &child2); EXPECT_EQ("3 2 1", ChildWindowIDsAsString(&parent)); parent.StackChildBelow(&child3, &child1); EXPECT_EQ("2 3 1", ChildWindowIDsAsString(&parent)); } // Various assertions for StackChildAbove. TEST_F(WindowTest, StackChildAbove) { Window parent(nullptr); parent.Init(ui::LAYER_NOT_DRAWN); Window child1(nullptr); child1.Init(ui::LAYER_NOT_DRAWN); Window child2(nullptr); child2.Init(ui::LAYER_NOT_DRAWN); Window child3(nullptr); child3.Init(ui::LAYER_NOT_DRAWN); parent.AddChild(&child1); parent.AddChild(&child2); // Move 1 in front of 2. parent.StackChildAbove(&child1, &child2); ASSERT_EQ(2u, parent.children().size()); EXPECT_EQ(&child2, parent.children()[0]); EXPECT_EQ(&child1, parent.children()[1]); ASSERT_EQ(2u, parent.layer()->children().size()); EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); // Add 3, resulting in order [2, 1, 3], then move 2 in front of 1, resulting // in [1, 2, 3]. parent.AddChild(&child3); parent.StackChildAbove(&child2, &child1); ASSERT_EQ(3u, parent.children().size()); EXPECT_EQ(&child1, parent.children()[0]); EXPECT_EQ(&child2, parent.children()[1]); EXPECT_EQ(&child3, parent.children()[2]); ASSERT_EQ(3u, parent.layer()->children().size()); EXPECT_EQ(child1.layer(), parent.layer()->children()[0]); EXPECT_EQ(child2.layer(), parent.layer()->children()[1]); EXPECT_EQ(child3.layer(), parent.layer()->children()[2]); // Move 1 in front of 3, resulting in [2, 3, 1]. parent.StackChildAbove(&child1, &child3); ASSERT_EQ(3u, parent.children().size()); EXPECT_EQ(&child2, parent.children()[0]); EXPECT_EQ(&child3, parent.children()[1]); EXPECT_EQ(&child1, parent.children()[2]); ASSERT_EQ(3u, parent.layer()->children().size()); EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); EXPECT_EQ(child3.layer(), parent.layer()->children()[1]); EXPECT_EQ(child1.layer(), parent.layer()->children()[2]); // Moving 1 in front of 2 should lower it, resulting in [2, 1, 3]. parent.StackChildAbove(&child1, &child2); ASSERT_EQ(3u, parent.children().size()); EXPECT_EQ(&child2, parent.children()[0]); EXPECT_EQ(&child1, parent.children()[1]); EXPECT_EQ(&child3, parent.children()[2]); ASSERT_EQ(3u, parent.layer()->children().size()); EXPECT_EQ(child2.layer(), parent.layer()->children()[0]); EXPECT_EQ(child1.layer(), parent.layer()->children()[1]); EXPECT_EQ(child3.layer(), parent.layer()->children()[2]); } // Various capture assertions. TEST_F(WindowTest, CaptureTests) { CaptureWindowDelegateImpl delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); EXPECT_FALSE(window->HasCapture()); delegate.ResetCounts(); // Do a capture. window->SetCapture(); EXPECT_TRUE(window->HasCapture()); EXPECT_EQ(0, delegate.capture_lost_count()); EXPECT_EQ(0, delegate.capture_changed_event_count()); ui::test::EventGenerator generator(root_window(), gfx::Point(50, 50)); generator.PressLeftButton(); EXPECT_EQ(1, delegate.mouse_event_count()); generator.ReleaseLeftButton(); EXPECT_EQ(2, delegate.mouse_event_count()); delegate.ResetCounts(); ui::TouchEvent touchev(ui::ET_TOUCH_PRESSED, gfx::Point(50, 50), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&touchev); EXPECT_EQ(1, delegate.touch_event_count()); delegate.ResetCounts(); window->ReleaseCapture(); EXPECT_FALSE(window->HasCapture()); EXPECT_EQ(1, delegate.capture_lost_count()); EXPECT_EQ(1, delegate.capture_changed_event_count()); EXPECT_EQ(1, delegate.mouse_event_count()); EXPECT_EQ(0, delegate.touch_event_count()); generator.PressLeftButton(); EXPECT_EQ(1, delegate.mouse_event_count()); ui::TouchEvent touchev2(ui::ET_TOUCH_PRESSED, gfx::Point(250, 250), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 1)); DispatchEventUsingWindowDispatcher(&touchev2); EXPECT_EQ(0, delegate.touch_event_count()); // Removing the capture window from parent should reset the capture window // in the root window. window->SetCapture(); EXPECT_EQ(window.get(), aura::client::GetCaptureWindow(root_window())); window->parent()->RemoveChild(window.get()); EXPECT_FALSE(window->HasCapture()); EXPECT_EQ(nullptr, aura::client::GetCaptureWindow(root_window())); } TEST_F(WindowTest, TouchCaptureCancelsOtherTouches) { CaptureWindowDelegateImpl delegate1; std::unique_ptr w1(CreateTestWindowWithDelegate( &delegate1, 0, gfx::Rect(0, 0, 50, 50), root_window())); CaptureWindowDelegateImpl delegate2; std::unique_ptr w2(CreateTestWindowWithDelegate( &delegate2, 0, gfx::Rect(50, 50, 50, 50), root_window())); // Press on w1. ui::TouchEvent press1(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&press1); // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. EXPECT_EQ(2, delegate1.gesture_event_count()); delegate1.ResetCounts(); // Capturing to w2 should cause the touch to be canceled. w2->SetCapture(); EXPECT_EQ(1, delegate1.touch_event_count()); EXPECT_EQ(0, delegate2.touch_event_count()); delegate1.ResetCounts(); delegate2.ResetCounts(); // Events are now untargeted. ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(10, 20), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&move); EXPECT_EQ(0, delegate1.gesture_event_count()); EXPECT_EQ(0, delegate1.touch_event_count()); EXPECT_EQ(0, delegate2.gesture_event_count()); EXPECT_EQ(0, delegate2.touch_event_count()); ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(10, 20), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&release); EXPECT_EQ(0, delegate1.gesture_event_count()); EXPECT_EQ(0, delegate2.gesture_event_count()); // A new press is captured by w2. ui::TouchEvent press2(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&press2); EXPECT_EQ(0, delegate1.gesture_event_count()); // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. EXPECT_EQ(2, delegate2.gesture_event_count()); delegate1.ResetCounts(); delegate2.ResetCounts(); // And releasing capture changes nothing. w2->ReleaseCapture(); EXPECT_EQ(0, delegate1.gesture_event_count()); EXPECT_EQ(0, delegate1.touch_event_count()); EXPECT_EQ(0, delegate2.gesture_event_count()); EXPECT_EQ(0, delegate2.touch_event_count()); } TEST_F(WindowTest, TouchCaptureDoesntCancelCapturedTouches) { CaptureWindowDelegateImpl delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(0, 0, 50, 50), root_window())); base::TimeTicks time = getTime(); const int kTimeDelta = 100; ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), time, ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&press); // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. EXPECT_EQ(2, delegate.gesture_event_count()); EXPECT_EQ(1, delegate.touch_event_count()); delegate.ResetCounts(); window->SetCapture(); EXPECT_EQ(0, delegate.gesture_event_count()); EXPECT_EQ(0, delegate.touch_event_count()); delegate.ResetCounts(); // On move We will get TOUCH_MOVED, GESTURE_TAP_CANCEL, // GESTURE_SCROLL_START and GESTURE_SCROLL_UPDATE. time += base::TimeDelta::FromMilliseconds(kTimeDelta); ui::TouchEvent move(ui::ET_TOUCH_MOVED, gfx::Point(10, 20), time, ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&move); EXPECT_EQ(1, delegate.touch_event_count()); EXPECT_EQ(3, delegate.gesture_event_count()); delegate.ResetCounts(); // Release capture shouldn't change anything. window->ReleaseCapture(); EXPECT_EQ(0, delegate.touch_event_count()); EXPECT_EQ(0, delegate.gesture_event_count()); delegate.ResetCounts(); // On move we still get TOUCH_MOVED and GESTURE_SCROLL_UPDATE. time += base::TimeDelta::FromMilliseconds(kTimeDelta); ui::TouchEvent move2(ui::ET_TOUCH_MOVED, gfx::Point(10, 30), time, ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&move2); EXPECT_EQ(1, delegate.touch_event_count()); EXPECT_EQ(1, delegate.gesture_event_count()); delegate.ResetCounts(); // And on release we get TOUCH_RELEASED, GESTURE_SCROLL_END, GESTURE_END time += base::TimeDelta::FromMilliseconds(kTimeDelta); ui::TouchEvent release(ui::ET_TOUCH_RELEASED, gfx::Point(10, 20), time, ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&release); EXPECT_EQ(1, delegate.touch_event_count()); EXPECT_EQ(2, delegate.gesture_event_count()); } // Assertions around SetCapture() and touch/gestures. TEST_F(WindowTest, TransferCaptureTouchEvents) { // Touch on |w1|. CaptureWindowDelegateImpl d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 0, gfx::Rect(0, 0, 20, 20), root_window())); ui::TouchEvent p1(ui::ET_TOUCH_PRESSED, gfx::Point(10, 10), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&p1); // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN. EXPECT_EQ(1, d1.touch_event_count()); EXPECT_EQ(2, d1.gesture_event_count()); d1.ResetCounts(); // Touch on |w2| with a different id. CaptureWindowDelegateImpl d2; std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 0, gfx::Rect(40, 0, 40, 20), root_window())); ui::TouchEvent p2(ui::ET_TOUCH_PRESSED, gfx::Point(41, 10), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 1)); DispatchEventUsingWindowDispatcher(&p2); EXPECT_EQ(0, d1.touch_event_count()); EXPECT_EQ(0, d1.gesture_event_count()); // We will get both GESTURE_BEGIN and GESTURE_TAP_DOWN for new target window. EXPECT_EQ(1, d2.touch_event_count()); EXPECT_EQ(2, d2.gesture_event_count()); d1.ResetCounts(); d2.ResetCounts(); // Set capture on |w2|, this should send a cancel (TAP_CANCEL, END) to |w1| // but not |w2|. w2->SetCapture(); EXPECT_EQ(1, d1.touch_event_count()); EXPECT_EQ(2, d1.gesture_event_count()); EXPECT_EQ(0, d2.touch_event_count()); EXPECT_EQ(0, d2.gesture_event_count()); d1.ResetCounts(); d2.ResetCounts(); CaptureWindowDelegateImpl d3; std::unique_ptr w3(CreateTestWindowWithDelegate( &d3, 0, gfx::Rect(0, 0, 100, 101), root_window())); // Set capture on |w3|. All touches have already been cancelled. w3->SetCapture(); EXPECT_EQ(0, d1.touch_event_count()); EXPECT_EQ(0, d1.gesture_event_count()); EXPECT_EQ(1, d2.touch_event_count()); EXPECT_EQ(2, d2.gesture_event_count()); EXPECT_EQ(0, d3.touch_event_count()); EXPECT_EQ(0, d3.gesture_event_count()); d2.ResetCounts(); // Move touch id originally associated with |w2|. The touch has been // cancelled, so no events should be dispatched. ui::TouchEvent m3(ui::ET_TOUCH_MOVED, gfx::Point(110, 105), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 1)); DispatchEventUsingWindowDispatcher(&m3); EXPECT_EQ(0, d1.touch_event_count()); EXPECT_EQ(0, d1.gesture_event_count()); EXPECT_EQ(0, d2.touch_event_count()); EXPECT_EQ(0, d2.gesture_event_count()); EXPECT_EQ(0, d3.touch_event_count()); EXPECT_EQ(0, d3.gesture_event_count()); // When we release capture, no touches are canceled. w3->ReleaseCapture(); EXPECT_EQ(0, d1.touch_event_count()); EXPECT_EQ(0, d1.gesture_event_count()); EXPECT_EQ(0, d2.touch_event_count()); EXPECT_EQ(0, d2.gesture_event_count()); EXPECT_EQ(0, d3.touch_event_count()); EXPECT_EQ(0, d3.gesture_event_count()); // The touch has been cancelled, so no events are dispatched. ui::TouchEvent m4(ui::ET_TOUCH_MOVED, gfx::Point(120, 105), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 1)); DispatchEventUsingWindowDispatcher(&m4); EXPECT_EQ(0, d1.touch_event_count()); EXPECT_EQ(0, d1.gesture_event_count()); EXPECT_EQ(0, d2.touch_event_count()); EXPECT_EQ(0, d2.gesture_event_count()); EXPECT_EQ(0, d3.touch_event_count()); EXPECT_EQ(0, d3.gesture_event_count()); } // Changes capture while capture is already ongoing. TEST_F(WindowTest, ChangeCaptureWhileMouseDown) { CaptureWindowDelegateImpl delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); CaptureWindowDelegateImpl delegate2; std::unique_ptr w2(CreateTestWindowWithDelegate( &delegate2, 0, gfx::Rect(20, 20, 20, 20), root_window())); // Execute the scheduled draws so that mouse events are not // aggregated. RunAllPendingInMessageLoop(); EXPECT_FALSE(window->HasCapture()); // Do a capture. delegate.ResetCounts(); window->SetCapture(); EXPECT_TRUE(window->HasCapture()); EXPECT_EQ(0, delegate.capture_lost_count()); EXPECT_EQ(0, delegate.capture_changed_event_count()); ui::test::EventGenerator generator(root_window(), gfx::Point(50, 50)); generator.PressLeftButton(); EXPECT_EQ(0, delegate.capture_lost_count()); EXPECT_EQ(0, delegate.capture_changed_event_count()); EXPECT_EQ(1, delegate.mouse_event_count()); // Set capture to |w2|, should implicitly unset capture for |window|. delegate.ResetCounts(); delegate2.ResetCounts(); w2->SetCapture(); generator.MoveMouseTo(gfx::Point(40, 40), 2); EXPECT_EQ(1, delegate.capture_lost_count()); EXPECT_EQ(1, delegate.capture_changed_event_count()); EXPECT_EQ(1, delegate.mouse_event_count()); EXPECT_EQ(2, delegate2.mouse_event_count()); } // Verifies capture is reset when a window is destroyed. TEST_F(WindowTest, ReleaseCaptureOnDestroy) { CaptureWindowDelegateImpl delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 0, gfx::Rect(0, 0, 20, 20), root_window())); EXPECT_FALSE(window->HasCapture()); // Do a capture. window->SetCapture(); EXPECT_TRUE(window->HasCapture()); // Destroy the window. window.reset(); // Make sure the root window doesn't reference the window anymore. EXPECT_EQ(nullptr, host()->dispatcher()->mouse_pressed_handler()); EXPECT_EQ(nullptr, aura::client::GetCaptureWindow(root_window())); } TEST_F(WindowTest, GetBoundsInRootWindow) { std::unique_ptr viewport( CreateTestWindowWithBounds(gfx::Rect(0, 0, 300, 300), root_window())); std::unique_ptr child( CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), viewport.get())); // Sanity check. EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window's screen bounds should move along with the |viewport|. viewport->SetBounds(gfx::Rect(-100, -100, 300, 300)); EXPECT_EQ("-100,-100 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window is moved to the 0,0 in screen coordinates. // |GetBoundsInRootWindow()| should return 0,0. child->SetBounds(gfx::Rect(100, 100, 100, 100)); EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); } TEST_F(WindowTest, GetBoundsInRootWindowWithLayers) { std::unique_ptr viewport( CreateTestWindowWithBounds(gfx::Rect(0, 0, 300, 300), root_window())); std::unique_ptr widget( CreateTestWindowWithBounds(gfx::Rect(0, 0, 200, 200), viewport.get())); std::unique_ptr child( CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), widget.get())); // Sanity check. EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window's screen bounds should move along with the |viewport|. OffsetBounds(viewport.get(), -100, -100); EXPECT_EQ("-100,-100 100x100", child->GetBoundsInRootWindow().ToString()); OffsetBounds(widget.get(), 50, 50); EXPECT_EQ("-50,-50 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window is moved to the 0,0 in screen coordinates. // |GetBoundsInRootWindow()| should return 0,0. OffsetBounds(child.get(), 50, 50); EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); } TEST_F(WindowTest, GetBoundsInRootWindowWithLayersAndTranslations) { std::unique_ptr viewport( CreateTestWindowWithBounds(gfx::Rect(0, 0, 300, 300), root_window())); std::unique_ptr widget( CreateTestWindowWithBounds(gfx::Rect(0, 0, 200, 200), viewport.get())); std::unique_ptr child( CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), widget.get())); // Sanity check. EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window's screen bounds should move along with the |viewport|. viewport->SetBounds(gfx::Rect(-100, -100, 300, 300)); EXPECT_EQ("-100,-100 100x100", child->GetBoundsInRootWindow().ToString()); widget->SetBounds(gfx::Rect(50, 50, 200, 200)); EXPECT_EQ("-50,-50 100x100", child->GetBoundsInRootWindow().ToString()); // The |child| window is moved to the 0,0 in screen coordinates. // |GetBoundsInRootWindow()| should return 0,0. child->SetBounds(gfx::Rect(50, 50, 100, 100)); EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); gfx::Transform transform1; transform1.Translate(-10, 20); viewport->SetTransform(transform1); EXPECT_EQ("-10,20 100x100", child->GetBoundsInRootWindow().ToString()); gfx::Transform transform2; transform2.Translate(40, 100); widget->SetTransform(transform2); EXPECT_EQ("30,120 100x100", child->GetBoundsInRootWindow().ToString()); // Testing potentially buggy place gfx::Transform transform3; transform3.Translate(-30, -120); child->SetTransform(transform3); EXPECT_EQ("0,0 100x100", child->GetBoundsInRootWindow().ToString()); } // TODO(tdanderson): Remove this class and use // test::EventCountDelegate in its place. class MouseEnterExitWindowDelegate : public TestWindowDelegate { public: MouseEnterExitWindowDelegate() : entered_(false), exited_(false) {} void OnMouseEvent(ui::MouseEvent* event) override { switch (event->type()) { case ui::ET_MOUSE_ENTERED: EXPECT_TRUE(event->flags() & ui::EF_IS_SYNTHESIZED); entered_ = true; break; case ui::ET_MOUSE_EXITED: EXPECT_TRUE(event->flags() & ui::EF_IS_SYNTHESIZED); exited_ = true; break; default: break; } } bool entered() const { return entered_; } bool exited() const { return exited_; } // Clear the entered / exited states. void ResetExpectations() { entered_ = false; exited_ = false; } private: bool entered_; bool exited_; DISALLOW_COPY_AND_ASSIGN(MouseEnterExitWindowDelegate); }; // Verifies that the WindowDelegate receives MouseExit and MouseEnter events for // mouse transitions from window to window. TEST_F(WindowTest, MouseEnterExit) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); MouseEnterExitWindowDelegate d2; std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 2, gfx::Rect(70, 70, 50, 50), root_window())); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w1.get()); EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); EXPECT_FALSE(d2.entered()); EXPECT_FALSE(d2.exited()); generator.MoveMouseToCenterOf(w2.get()); EXPECT_TRUE(d1.entered()); EXPECT_TRUE(d1.exited()); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); } // Verifies that the WindowDelegate receives MouseExit from ET_MOUSE_EXITED. TEST_F(WindowTest, WindowTreeHostExit) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w1.get()); EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); d1.ResetExpectations(); ui::MouseEvent exit_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(), ui::EventTimeForNow(), 0, 0); DispatchEventUsingWindowDispatcher(&exit_event); EXPECT_FALSE(d1.entered()); EXPECT_TRUE(d1.exited()); } // Verifies that the WindowDelegate receives MouseExit and MouseEnter events for // mouse transitions from window to window, even if the entered window sets // and releases capture. TEST_F(WindowTest, MouseEnterExitWithClick) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); MouseEnterExitWindowDelegate d2; std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 2, gfx::Rect(70, 70, 50, 50), root_window())); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w1.get()); EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); EXPECT_FALSE(d2.entered()); EXPECT_FALSE(d2.exited()); // Emmulate what Views does on a click by grabbing and releasing capture. generator.PressLeftButton(); w1->SetCapture(); w1->ReleaseCapture(); generator.ReleaseLeftButton(); generator.MoveMouseToCenterOf(w2.get()); EXPECT_TRUE(d1.entered()); EXPECT_TRUE(d1.exited()); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); } TEST_F(WindowTest, MouseEnterExitWhenDeleteWithCapture) { MouseEnterExitWindowDelegate delegate; std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 1, gfx::Rect(10, 10, 50, 50), root_window())); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(window.get()); EXPECT_TRUE(delegate.entered()); EXPECT_FALSE(delegate.exited()); // Emmulate what Views does on a click by grabbing and releasing capture. generator.PressLeftButton(); window->SetCapture(); delegate.ResetExpectations(); generator.MoveMouseTo(0, 0); EXPECT_FALSE(delegate.entered()); EXPECT_FALSE(delegate.exited()); delegate.ResetExpectations(); window.reset(); EXPECT_FALSE(delegate.entered()); EXPECT_FALSE(delegate.exited()); } // Verifies that the correct enter / exits are sent if windows appear and are // deleted under the current mouse position. TEST_F(WindowTest, MouseEnterExitWithWindowAppearAndDelete) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); // The cursor is moved into the bounds of |w1|. We expect the delegate // of |w1| to see an ET_MOUSE_ENTERED event. ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w1.get()); EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); d1.ResetExpectations(); MouseEnterExitWindowDelegate d2; { std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 2, gfx::Rect(10, 10, 50, 50), root_window())); // Enters / exits can be sent asynchronously. RunAllPendingInMessageLoop(); // |w2| appears over top of |w1|. We expect the delegate of |w1| to see // an ET_MOUSE_EXITED and the delegate of |w2| to see an ET_MOUSE_ENTERED. EXPECT_FALSE(d1.entered()); EXPECT_TRUE(d1.exited()); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); d1.ResetExpectations(); d2.ResetExpectations(); } // Enters / exits can be sent asynchronously. RunAllPendingInMessageLoop(); // |w2| has been destroyed, so its delegate should see no further events. // The delegate of |w1| should see an ET_MOUSE_ENTERED event. EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); EXPECT_FALSE(d2.entered()); EXPECT_FALSE(d2.exited()); } // Verifies that enter / exits are sent if windows appear and are hidden // under the current mouse position.. TEST_F(WindowTest, MouseEnterExitWithHide) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w1.get()); EXPECT_TRUE(d1.entered()); EXPECT_FALSE(d1.exited()); MouseEnterExitWindowDelegate d2; std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 2, gfx::Rect(10, 10, 50, 50), root_window())); // Enters / exits can be send asynchronously. RunAllPendingInMessageLoop(); EXPECT_TRUE(d1.entered()); EXPECT_TRUE(d1.exited()); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); d1.ResetExpectations(); w2->Hide(); // Enters / exits can be send asynchronously. RunAllPendingInMessageLoop(); EXPECT_TRUE(d2.exited()); EXPECT_TRUE(d1.entered()); } TEST_F(WindowTest, MouseEnterExitWithParentHide) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); MouseEnterExitWindowDelegate d2; Window* w2 = CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), w1.get()); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w2); // Enters / exits can be send asynchronously. RunAllPendingInMessageLoop(); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); d2.ResetExpectations(); w1->Hide(); RunAllPendingInMessageLoop(); EXPECT_FALSE(d2.entered()); EXPECT_TRUE(d2.exited()); w1.reset(); } TEST_F(WindowTest, MouseEnterExitWithParentDelete) { MouseEnterExitWindowDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(10, 10, 50, 50), root_window())); MouseEnterExitWindowDelegate d2; Window* w2 = CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(10, 10, 50, 50), w1.get()); ui::test::EventGenerator generator(root_window()); generator.MoveMouseToCenterOf(w2); // Enters / exits can be send asynchronously. RunAllPendingInMessageLoop(); EXPECT_TRUE(d2.entered()); EXPECT_FALSE(d2.exited()); d2.ResetExpectations(); w1.reset(); RunAllPendingInMessageLoop(); // Both windows are in the process of destroying, so their delegates should // not see any mouse events. EXPECT_FALSE(d1.entered()); EXPECT_FALSE(d1.exited()); EXPECT_FALSE(d2.entered()); EXPECT_FALSE(d2.exited()); } // Creates a window with a delegate (w111) that can handle events at a lower // z-index than a window without a delegate (w12). w12 is sized to fill the // entire bounds of the container. This test verifies that // GetEventHandlerForPoint() skips w12 even though its bounds contain the event, // because it has no children that can handle the event and it has no delegate // allowing it to handle the event itself. TEST_F(WindowTest, GetEventHandlerForPoint_NoDelegate) { TestWindowDelegate d111; std::unique_ptr w1(CreateTestWindowWithDelegate( nullptr, 1, gfx::Rect(0, 0, 500, 500), root_window())); std::unique_ptr w11(CreateTestWindowWithDelegate( nullptr, 11, gfx::Rect(0, 0, 500, 500), w1.get())); std::unique_ptr w111(CreateTestWindowWithDelegate( &d111, 111, gfx::Rect(50, 50, 450, 450), w11.get())); std::unique_ptr w12(CreateTestWindowWithDelegate( nullptr, 12, gfx::Rect(0, 0, 500, 500), w1.get())); gfx::Point target_point = w111->bounds().CenterPoint(); EXPECT_EQ(w111.get(), w1->GetEventHandlerForPoint(target_point)); } class VisibilityWindowDelegate : public TestWindowDelegate { public: VisibilityWindowDelegate() : shown_(0), hidden_(0) { } int shown() const { return shown_; } int hidden() const { return hidden_; } void Clear() { shown_ = 0; hidden_ = 0; } void OnWindowTargetVisibilityChanged(bool visible) override { if (visible) shown_++; else hidden_++; } private: int shown_; int hidden_; DISALLOW_COPY_AND_ASSIGN(VisibilityWindowDelegate); }; // Verifies show/hide propagate correctly to children and the layer. TEST_F(WindowTest, Visibility) { VisibilityWindowDelegate d; VisibilityWindowDelegate d2; std::unique_ptr w1( CreateTestWindowWithDelegate(&d, 1, gfx::Rect(), root_window())); std::unique_ptr w2( CreateTestWindowWithDelegate(&d2, 2, gfx::Rect(), w1.get())); std::unique_ptr w3(CreateTestWindowWithId(3, w2.get())); // Create shows all the windows. EXPECT_TRUE(w1->IsVisible()); EXPECT_TRUE(w2->IsVisible()); EXPECT_TRUE(w3->IsVisible()); EXPECT_EQ(1, d.shown()); d.Clear(); w1->Hide(); EXPECT_FALSE(w1->IsVisible()); EXPECT_FALSE(w2->IsVisible()); EXPECT_FALSE(w3->IsVisible()); EXPECT_EQ(1, d.hidden()); EXPECT_EQ(0, d.shown()); w2->Show(); EXPECT_FALSE(w1->IsVisible()); EXPECT_FALSE(w2->IsVisible()); EXPECT_FALSE(w3->IsVisible()); w3->Hide(); EXPECT_FALSE(w1->IsVisible()); EXPECT_FALSE(w2->IsVisible()); EXPECT_FALSE(w3->IsVisible()); d.Clear(); w1->Show(); EXPECT_TRUE(w1->IsVisible()); EXPECT_TRUE(w2->IsVisible()); EXPECT_FALSE(w3->IsVisible()); EXPECT_EQ(0, d.hidden()); EXPECT_EQ(1, d.shown()); w3->Show(); EXPECT_TRUE(w1->IsVisible()); EXPECT_TRUE(w2->IsVisible()); EXPECT_TRUE(w3->IsVisible()); // Verify that if an ancestor isn't visible and we change the visibility of a // child window that OnChildWindowVisibilityChanged() is still invoked. w1->Hide(); d2.Clear(); w2->Hide(); EXPECT_EQ(1, d2.hidden()); EXPECT_EQ(0, d2.shown()); d2.Clear(); w2->Show(); EXPECT_EQ(0, d2.hidden()); EXPECT_EQ(1, d2.shown()); } TEST_F(WindowTest, EventTargetingPolicy) { TestWindowDelegate d11; TestWindowDelegate d12; TestWindowDelegate d111; TestWindowDelegate d121; std::unique_ptr w1(CreateTestWindowWithDelegate( nullptr, 1, gfx::Rect(0, 0, 500, 500), root_window())); std::unique_ptr w11(CreateTestWindowWithDelegate( &d11, 11, gfx::Rect(0, 0, 500, 500), w1.get())); std::unique_ptr w111(CreateTestWindowWithDelegate( &d111, 111, gfx::Rect(50, 50, 450, 450), w11.get())); std::unique_ptr w12(CreateTestWindowWithDelegate( &d12, 12, gfx::Rect(0, 0, 500, 500), w1.get())); std::unique_ptr w121(CreateTestWindowWithDelegate( &d121, 121, gfx::Rect(150, 150, 50, 50), w12.get())); EXPECT_EQ(w121.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); w12->SetEventTargetingPolicy(EventTargetingPolicy::kTargetOnly); EXPECT_EQ(w12.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_TRUE(w12.get()->layer()->accept_events()); w12->SetEventTargetingPolicy(EventTargetingPolicy::kTargetAndDescendants); EXPECT_EQ(w12.get(), w1->GetEventHandlerForPoint(gfx::Point(10, 10))); EXPECT_TRUE(w12.get()->layer()->accept_events()); w12->SetEventTargetingPolicy(EventTargetingPolicy::kNone); EXPECT_EQ(w11.get(), w1->GetEventHandlerForPoint(gfx::Point(10, 10))); EXPECT_FALSE(w12.get()->layer()->accept_events()); w12->SetEventTargetingPolicy(EventTargetingPolicy::kTargetAndDescendants); EXPECT_EQ(w121.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); w121->SetEventTargetingPolicy(EventTargetingPolicy::kNone); EXPECT_EQ(w12.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_FALSE(w121.get()->layer()->accept_events()); w12->SetEventTargetingPolicy(EventTargetingPolicy::kNone); EXPECT_EQ(w111.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_FALSE(w12.get()->layer()->accept_events()); w111->SetEventTargetingPolicy(EventTargetingPolicy::kNone); EXPECT_EQ(w11.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_FALSE(w111.get()->layer()->accept_events()); w11->SetEventTargetingPolicy(EventTargetingPolicy::kDescendantsOnly); EXPECT_EQ(nullptr, w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_TRUE(w11.get()->layer()->accept_events()); w111->SetEventTargetingPolicy(EventTargetingPolicy::kTargetAndDescendants); EXPECT_EQ(w111.get(), w1->GetEventHandlerForPoint(gfx::Point(160, 160))); EXPECT_TRUE(w111.get()->layer()->accept_events()); } TEST_F(WindowTest, ScopedEventTargetingBlockerTest) { // Test only when all event targeting blockers are removed from the window, // its event targeting policy will restore back to its original value. std::unique_ptr window(CreateTestWindowWithDelegate( nullptr, 1, gfx::Rect(0, 0, 500, 500), root_window())); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kTargetAndDescendants); auto event_targeting_blocker1 = std::make_unique(window.get()); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kNone); auto event_targeting_blocker2 = std::make_unique(window.get()); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kNone); event_targeting_blocker2.reset(); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kNone); event_targeting_blocker1.reset(); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kTargetAndDescendants); // It's possible that the event target policy changes when there is an event // targeting blocker in place. In this case when the event targeting blocker // is removed from the window, the window should restore to the changed event // targeting policy. auto event_targeting_blocker3 = std::make_unique(window.get()); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kNone); window->SetEventTargetingPolicy(EventTargetingPolicy::kTargetOnly); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kNone); event_targeting_blocker3.reset(); EXPECT_EQ(window->event_targeting_policy(), EventTargetingPolicy::kTargetOnly); } // Tests transformation on the root window. TEST_F(WindowTest, Transform) { gfx::Size size = host()->GetBoundsInPixels().size(); EXPECT_EQ(gfx::Rect(size), display::Screen::GetScreen() ->GetDisplayNearestPoint(gfx::Point()) .bounds()); // Rotate it clock-wise 90 degrees. host()->SetRootTransform(OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_90, gfx::SizeF(size))); // The size should be the transformed size. gfx::Size transformed_size(size.height(), size.width()); EXPECT_EQ(transformed_size.ToString(), root_window()->bounds().size().ToString()); EXPECT_EQ(gfx::Rect(transformed_size).ToString(), display::Screen::GetScreen() ->GetDisplayNearestPoint(gfx::Point()) .bounds() .ToString()); // Host size shouldn't change. EXPECT_EQ(size.ToString(), host()->GetBoundsInPixels().size().ToString()); } TEST_F(WindowTest, TransformGesture) { gfx::Size size = host()->GetBoundsInPixels().size(); std::unique_ptr delegate( new GestureTrackPositionDelegate); std::unique_ptr window(CreateTestWindowWithDelegate( delegate.get(), -1234, gfx::Rect(0, 0, 20, 20), root_window())); // Rotate the root-window clock-wise 90 degrees. host()->SetRootTransform(OverlayTransformToTransform( gfx::OVERLAY_TRANSFORM_ROTATE_90, gfx::SizeF(size))); ui::TouchEvent press(ui::ET_TOUCH_PRESSED, gfx::Point(size.height() - 10, 10), getTime(), ui::PointerDetails(ui::EventPointerType::kTouch, 0)); DispatchEventUsingWindowDispatcher(&press); EXPECT_EQ(gfx::Point(10, 10).ToString(), delegate->position().ToString()); } TEST_F(WindowTest, Property) { std::unique_ptr w(CreateTestWindowWithId(0, root_window())); static const char native_prop_key[] = "fnord"; // Non-existent properties should return the default values. EXPECT_EQ(nullptr, w->GetNativeWindowProperty(native_prop_key)); w->SetNativeWindowProperty(native_prop_key, &*w); EXPECT_EQ(&*w, w->GetNativeWindowProperty(native_prop_key)); w->SetNativeWindowProperty(native_prop_key, nullptr); EXPECT_EQ(nullptr, w->GetNativeWindowProperty(native_prop_key)); } class DeletionTestLayoutManager : public LayoutManager { public: explicit DeletionTestLayoutManager(DeletionTracker* tracker) : tracker_(tracker) {} ~DeletionTestLayoutManager() override { tracker_->LayoutManagerDeleted(); } private: // LayoutManager: void OnWindowResized() override {} void OnWindowAddedToLayout(Window* child) override {} void OnWillRemoveWindowFromLayout(Window* child) override {} void OnWindowRemovedFromLayout(Window* child) override {} void OnChildWindowVisibilityChanged(Window* child, bool visible) override {} void SetChildBounds(Window* child, const gfx::Rect& requested_bounds) override {} DeletionTracker* tracker_; DISALLOW_COPY_AND_ASSIGN(DeletionTestLayoutManager); }; TEST_F(WindowTest, DeleteLayoutManagerBeforeOwnedProps) { DeletionTracker tracker; { Window w(nullptr); w.Init(ui::LAYER_NOT_DRAWN); w.SetLayoutManager(new DeletionTestLayoutManager(&tracker)); w.SetProperty(kDeletionTestPropertyKey, std::make_unique(&tracker)); } EXPECT_TRUE(tracker.property_deleted()); EXPECT_TRUE(tracker.layout_manager_deleted()); EXPECT_EQ(DeletionOrder::LAYOUT_MANAGER_FIRST, tracker.order()); } TEST_F(WindowTest, SetBoundsInternalShouldCheckTargetBounds) { // We cannot short-circuit animations in this test. ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); std::unique_ptr w1( CreateTestWindowWithBounds(gfx::Rect(0, 0, 100, 100), root_window())); EXPECT_TRUE(w1->layer()); w1->layer()->GetAnimator()->set_disable_timer_for_test(true); ui::LayerAnimator* animator = w1->layer()->GetAnimator(); EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); EXPECT_EQ("0,0 100x100", w1->layer()->GetTargetBounds().ToString()); // Animate to a different position. { ui::ScopedLayerAnimationSettings settings(w1->layer()->GetAnimator()); w1->SetBounds(gfx::Rect(100, 100, 100, 100)); } EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); EXPECT_EQ("100,100 100x100", w1->layer()->GetTargetBounds().ToString()); // Animate back to the first position. The animation hasn't started yet, so // the current bounds are still (0, 0, 100, 100), but the target bounds are // (100, 100, 100, 100). If we step the animator ahead, we should find that // we're at (0, 0, 100, 100). That is, the second animation should be applied. { ui::ScopedLayerAnimationSettings settings(w1->layer()->GetAnimator()); w1->SetBounds(gfx::Rect(0, 0, 100, 100)); } EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); EXPECT_EQ("0,0 100x100", w1->layer()->GetTargetBounds().ToString()); // Confirm that the target bounds are reached. base::TimeTicks start_time = w1->layer()->GetAnimator()->last_step_time(); animator->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); EXPECT_EQ("0,0 100x100", w1->bounds().ToString()); } typedef std::pair PropertyChangeInfo; class WindowObserverTest : public WindowTest, public WindowObserver { public: struct VisibilityInfo { bool window_visible; bool visible_param; int changed_count; }; struct WindowBoundsInfo { int changed_count = 0; Window* window = nullptr; gfx::Rect old_bounds; gfx::Rect new_bounds; ui::PropertyChangeReason reason = ui::PropertyChangeReason::NOT_FROM_ANIMATION; }; struct WindowOpacityInfo { int changed_count = 0; Window* window = nullptr; ui::PropertyChangeReason reason = ui::PropertyChangeReason::NOT_FROM_ANIMATION; }; struct WindowTargetTransformChangingInfo { int changed_count = 0; Window* window = nullptr; gfx::Transform new_transform; }; struct WindowTransformedInfo { int changed_count = 0; Window* window = nullptr; ui::PropertyChangeReason reason = ui::PropertyChangeReason::NOT_FROM_ANIMATION; }; struct CountAndWindow { int count = 0; Window* window = nullptr; }; WindowObserverTest() = default; ~WindowObserverTest() override = default; const VisibilityInfo* GetVisibilityInfo() const { return visibility_info_.get(); } const WindowBoundsInfo& window_bounds_info() const { return window_bounds_info_; } const WindowOpacityInfo& window_opacity_info() const { return window_opacity_info_; } const CountAndWindow& alpha_shape_info() const { return alpha_shape_info_; } const WindowTargetTransformChangingInfo& window_target_transform_changing_info() const { return window_target_transform_changing_info_; } const WindowTransformedInfo& window_transformed_info() const { return window_transformed_info_; } const CountAndWindow& layer_recreated_info() const { return layer_recreated_info_; } void ResetVisibilityInfo() { visibility_info_.reset(); } // Returns a description of the WindowObserver methods that have been invoked. std::string WindowObserverCountStateAndClear() { std::string result(base::StringPrintf("added=%d removing=%d removed=%d", added_count_, removing_count_, removed_count_)); added_count_ = removing_count_ = removed_count_ = 0; return result; } int DestroyedCountAndClear() { int result = destroyed_count_; destroyed_count_ = 0; return result; } // Return a tuple of the arguments passed in OnPropertyChanged callback. PropertyChangeInfo PropertyChangeInfoAndClear() { PropertyChangeInfo result(property_key_, old_property_value_); property_key_ = nullptr; old_property_value_ = -3; return result; } private: void OnWindowAdded(Window* new_window) override { added_count_++; } void OnWillRemoveWindow(Window* window) override { removing_count_++; } void OnWindowRemoved(Window* removed_window) override { removed_count_++; } void OnWindowVisibilityChanged(Window* window, bool visible) override { if (!visibility_info_) { visibility_info_ = std::make_unique(); visibility_info_->changed_count = 0; } visibility_info_->window_visible = window->IsVisible(); visibility_info_->visible_param = visible; visibility_info_->changed_count++; } void OnWindowDestroyed(Window* window) override { EXPECT_FALSE(window->parent()); destroyed_count_++; } void OnWindowPropertyChanged(Window* window, const void* key, intptr_t old) override { property_key_ = key; old_property_value_ = old; } void OnWindowBoundsChanged(Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds, ui::PropertyChangeReason reason) override { ++window_bounds_info_.changed_count; window_bounds_info_.window = window; window_bounds_info_.old_bounds = old_bounds; window_bounds_info_.new_bounds = new_bounds; window_bounds_info_.reason = reason; } void OnWindowOpacitySet(Window* window, ui::PropertyChangeReason reason) override { ++window_opacity_info_.changed_count; window_opacity_info_.window = window; window_opacity_info_.reason = reason; } void OnWindowAlphaShapeSet(Window* window) override { ++alpha_shape_info_.count; alpha_shape_info_.window = window; } void OnWindowTargetTransformChanging( Window* window, const gfx::Transform& new_transform) override { ++window_target_transform_changing_info_.changed_count; window_target_transform_changing_info_.window = window; window_target_transform_changing_info_.new_transform = new_transform; } void OnWindowTransformed(Window* window, ui::PropertyChangeReason reason) override { ++window_transformed_info_.changed_count; window_transformed_info_.window = window; window_transformed_info_.reason = reason; } void OnWindowLayerRecreated(Window* window) override { ++layer_recreated_info_.count; layer_recreated_info_.window = window; } int added_count_ = 0; int removing_count_ = 0; int removed_count_ = 0; int destroyed_count_ = 0; std::unique_ptr visibility_info_; const void* property_key_ = nullptr; intptr_t old_property_value_ = -3; std::vector > transform_notifications_; WindowBoundsInfo window_bounds_info_; WindowOpacityInfo window_opacity_info_; WindowTargetTransformChangingInfo window_target_transform_changing_info_; WindowTransformedInfo window_transformed_info_; CountAndWindow alpha_shape_info_; CountAndWindow layer_recreated_info_; DISALLOW_COPY_AND_ASSIGN(WindowObserverTest); }; // Various assertions for WindowObserver. TEST_F(WindowObserverTest, WindowObserver) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); w1->AddObserver(this); // Create a new window as a child of w1, our observer should be notified. std::unique_ptr w2(CreateTestWindowWithId(2, w1.get())); EXPECT_EQ("added=1 removing=0 removed=0", WindowObserverCountStateAndClear()); // Delete w2, which should result in the remove notifications. w2.reset(); EXPECT_EQ("added=0 removing=1 removed=1", WindowObserverCountStateAndClear()); // Create a window that isn't parented to w1, we shouldn't get any // notification. std::unique_ptr w3(CreateTestWindowWithId(3, root_window())); EXPECT_EQ("added=0 removing=0 removed=0", WindowObserverCountStateAndClear()); // Similarly destroying w3 shouldn't notify us either. w3.reset(); EXPECT_EQ("added=0 removing=0 removed=0", WindowObserverCountStateAndClear()); w1->RemoveObserver(this); } // Test if OnWindowVisibilityChanged is invoked with expected // parameters. TEST_F(WindowObserverTest, WindowVisibility) { std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); std::unique_ptr w2(CreateTestWindowWithId(1, w1.get())); w2->AddObserver(this); // Hide should make the window invisible and the passed visible // parameter is false. w2->Hide(); EXPECT_TRUE(GetVisibilityInfo()); EXPECT_TRUE(GetVisibilityInfo()); if (!GetVisibilityInfo()) return; EXPECT_FALSE(GetVisibilityInfo()->window_visible); EXPECT_FALSE(GetVisibilityInfo()->visible_param); EXPECT_EQ(1, GetVisibilityInfo()->changed_count); // If parent isn't visible, showing window won't make the window visible, but // passed visible value must be true. w1->Hide(); ResetVisibilityInfo(); EXPECT_TRUE(!GetVisibilityInfo()); w2->Show(); EXPECT_TRUE(GetVisibilityInfo()); if (!GetVisibilityInfo()) return; EXPECT_FALSE(GetVisibilityInfo()->window_visible); EXPECT_TRUE(GetVisibilityInfo()->visible_param); EXPECT_EQ(1, GetVisibilityInfo()->changed_count); // If parent is visible, showing window will make the window // visible and the passed visible value is true. w1->Show(); w2->Hide(); ResetVisibilityInfo(); w2->Show(); EXPECT_TRUE(GetVisibilityInfo()); if (!GetVisibilityInfo()) return; EXPECT_TRUE(GetVisibilityInfo()->window_visible); EXPECT_TRUE(GetVisibilityInfo()->visible_param); EXPECT_EQ(1, GetVisibilityInfo()->changed_count); // Verify that the OnWindowVisibilityChanged only once // per visibility change. w2->Hide(); EXPECT_EQ(2, GetVisibilityInfo()->changed_count); w2->Hide(); EXPECT_EQ(2, GetVisibilityInfo()->changed_count); } // Test if OnWindowDestroyed is invoked as expected. TEST_F(WindowObserverTest, WindowDestroyed) { // Delete a window should fire a destroyed notification. std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); w1->AddObserver(this); w1.reset(); EXPECT_EQ(1, DestroyedCountAndClear()); // Observe on child and delete parent window should fire a notification. std::unique_ptr parent(CreateTestWindowWithId(1, root_window())); Window* child = CreateTestWindowWithId(1, parent.get()); // owned by parent child->AddObserver(this); parent.reset(); EXPECT_EQ(1, DestroyedCountAndClear()); } TEST_F(WindowObserverTest, PropertyChanged) { // Setting property should fire a property change notification. std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); w1->AddObserver(this); static const WindowProperty prop = {-2}; static const char native_prop_key[] = "fnord"; w1->SetProperty(&prop, 1); EXPECT_EQ(PropertyChangeInfo(&prop, -2), PropertyChangeInfoAndClear()); w1->SetProperty(&prop, -2); EXPECT_EQ(PropertyChangeInfo(&prop, 1), PropertyChangeInfoAndClear()); w1->SetProperty(&prop, 3); EXPECT_EQ(PropertyChangeInfo(&prop, -2), PropertyChangeInfoAndClear()); w1->ClearProperty(&prop); EXPECT_EQ(PropertyChangeInfo(&prop, 3), PropertyChangeInfoAndClear()); w1->SetNativeWindowProperty(native_prop_key, &*w1); EXPECT_EQ(PropertyChangeInfo(native_prop_key, 0), PropertyChangeInfoAndClear()); w1->SetNativeWindowProperty(native_prop_key, nullptr); EXPECT_EQ(PropertyChangeInfo(native_prop_key, reinterpret_cast(&*w1)), PropertyChangeInfoAndClear()); // Sanity check to see if |PropertyChangeInfoAndClear| really clears. EXPECT_EQ(PropertyChangeInfo(nullptr, -3), PropertyChangeInfoAndClear()); } // Verify that WindowObserver::OnWindowBoundsChanged() is notified when the // bounds of a Window's Layer change without an animation. TEST_F(WindowObserverTest, WindowBoundsChanged) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); const gfx::Rect initial_bounds = window->bounds(); constexpr gfx::Rect kTargetBounds(10, 20, 30, 40); window->layer()->SetBounds(kTargetBounds); ASSERT_EQ(1, window_bounds_info().changed_count); EXPECT_EQ(window.get(), window_bounds_info().window); EXPECT_EQ(initial_bounds, window_bounds_info().old_bounds); EXPECT_EQ(kTargetBounds, window_bounds_info().new_bounds); EXPECT_EQ(ui::PropertyChangeReason::NOT_FROM_ANIMATION, window_bounds_info().reason); } // Verify that WindowObserver::OnWindowBoundsChanged() is notified at every step // of a bounds animation. TEST_F(WindowObserverTest, WindowBoundsChangedAnimation) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); const gfx::Rect initial_bounds = window->bounds(); constexpr gfx::Rect kTargetBounds(10, 20, 30, 40); const gfx::Rect step_bounds = gfx::Tween::RectValueBetween(0.5, initial_bounds, kTargetBounds); ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); window->layer()->SetBounds(kTargetBounds); ASSERT_EQ(0, window_opacity_info().changed_count); window->layer()->GetAnimator()->Step( window->layer()->GetAnimator()->last_step_time() + settings.GetTransitionDuration() / 2); ASSERT_EQ(1, window_bounds_info().changed_count); EXPECT_EQ(window.get(), window_bounds_info().window); EXPECT_EQ(initial_bounds, window_bounds_info().old_bounds); EXPECT_EQ(step_bounds, window_bounds_info().new_bounds); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_bounds_info().reason); window->layer()->GetAnimator()->StopAnimatingProperty( ui::LayerAnimationElement::BOUNDS); ASSERT_EQ(2, window_bounds_info().changed_count); EXPECT_EQ(window.get(), window_bounds_info().window); EXPECT_EQ(step_bounds, window_bounds_info().old_bounds); EXPECT_EQ(kTargetBounds, window_bounds_info().new_bounds); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_bounds_info().reason); } // Verify that WindowObserver::OnWindowOpacitySet() is notified when the // opacity of a Window's Layer changes without an animation. TEST_F(WindowObserverTest, WindowOpacityChanged) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); window->layer()->SetOpacity(0.5f); ASSERT_EQ(1, window_opacity_info().changed_count); EXPECT_EQ(window.get(), window_opacity_info().window); EXPECT_EQ(ui::PropertyChangeReason::NOT_FROM_ANIMATION, window_opacity_info().reason); } // Verify that WindowObserver::OnWindowOpacitySet() is notified at the // beginning and at the end of a threaded opacity animation. TEST_F(WindowObserverTest, WindowOpacityChangedAnimation) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); window->layer()->SetOpacity(0.5f); ASSERT_EQ(1, window_opacity_info().changed_count); EXPECT_EQ(window.get(), window_opacity_info().window); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_opacity_info().reason); window->layer()->GetAnimator()->StopAnimatingProperty( ui::LayerAnimationElement::OPACITY); ASSERT_EQ(2, window_opacity_info().changed_count); EXPECT_EQ(window.get(), window_opacity_info().window); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_opacity_info().reason); } // Verify that WindowObserver::OnWindowAlphaShapeSet() is notified when an alpha // shape is set for a window. TEST_F(WindowObserverTest, WindowAlphaShapeChanged) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); auto shape = std::make_unique(); shape->emplace_back(0, 0, 10, 20); EXPECT_EQ(0, alpha_shape_info().count); EXPECT_EQ(nullptr, alpha_shape_info().window); window->layer()->SetAlphaShape(std::move(shape)); EXPECT_EQ(1, alpha_shape_info().count); EXPECT_EQ(window.get(), alpha_shape_info().window); } // Verify that WindowObserver::OnWindow(TargetTransformChanging|Transformed)() // are notified when SetTransform() is called and there is no animation. TEST_F(WindowObserverTest, SetTransform) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); gfx::Transform target_transform; target_transform.Skew(10.0, 5.0); window->SetTransform(target_transform); ASSERT_EQ(1, window_target_transform_changing_info().changed_count); EXPECT_EQ(window.get(), window_target_transform_changing_info().window); EXPECT_EQ(target_transform, window_target_transform_changing_info().new_transform); ASSERT_EQ(1, window_transformed_info().changed_count); EXPECT_EQ(window.get(), window_transformed_info().window); EXPECT_EQ(ui::PropertyChangeReason::NOT_FROM_ANIMATION, window_transformed_info().reason); } // Verify that WindowObserver::OnWindowTransformed)() is notified at the // beginning and at the end of a threaded transform animation. Verify that // WindowObserver::OnWindowTargetTransformChanging() is notified when the // threaded animation is started by SetTransform(). TEST_F(WindowObserverTest, SetTransformAnimation) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); gfx::Transform target_transform; target_transform.Skew(10.0, 5.0); window->SetTransform(target_transform); ASSERT_EQ(1, window_target_transform_changing_info().changed_count); EXPECT_EQ(window.get(), window_target_transform_changing_info().window); EXPECT_EQ(target_transform, window_target_transform_changing_info().new_transform); ASSERT_EQ(0, window_transformed_info().changed_count); window->layer()->GetAnimator()->StopAnimatingProperty( ui::LayerAnimationElement::TRANSFORM); EXPECT_EQ(1, window_target_transform_changing_info().changed_count); ASSERT_EQ(1, window_transformed_info().changed_count); EXPECT_EQ(window.get(), window_transformed_info().window); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_transformed_info().reason); } TEST_F(WindowObserverTest, OnWindowLayerRecreated) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->AddObserver(this); EXPECT_EQ(0, layer_recreated_info().count); std::unique_ptr old_layer = window->RecreateLayer(); EXPECT_EQ(1, layer_recreated_info().count); EXPECT_EQ(window.get(), layer_recreated_info().window); } TEST_F(WindowObserverTest, OnWindowLayerRecreatedWithOpacityAnimation) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); window->layer()->SetOpacity(0.5); EXPECT_TRUE(window->layer()->GetAnimator()->IsAnimatingProperty( ui::LayerAnimationElement::OPACITY)); window->AddObserver(this); EXPECT_EQ(0, layer_recreated_info().count); EXPECT_EQ(0, window_opacity_info().changed_count); std::unique_ptr old_layer = window->RecreateLayer(); EXPECT_EQ(1, layer_recreated_info().count); EXPECT_EQ(window.get(), layer_recreated_info().window); EXPECT_EQ(1, window_opacity_info().changed_count); EXPECT_EQ(window.get(), window_opacity_info().window); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_opacity_info().reason); } TEST_F(WindowObserverTest, OnWindowLayerRecreatedWithTransformAnimation) { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); ui::ScopedAnimationDurationScaleMode scoped_animation_duration_scale_mode( ui::ScopedAnimationDurationScaleMode::NORMAL_DURATION); ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); gfx::Transform target_transform; target_transform.Skew(10.0, 5.0); window->SetTransform(target_transform); EXPECT_TRUE(window->layer()->GetAnimator()->IsAnimatingProperty( ui::LayerAnimationElement::TRANSFORM)); window->AddObserver(this); EXPECT_EQ(0, layer_recreated_info().count); EXPECT_EQ(0, window_transformed_info().changed_count); std::unique_ptr old_layer = window->RecreateLayer(); EXPECT_EQ(1, layer_recreated_info().count); EXPECT_EQ(window.get(), layer_recreated_info().window); EXPECT_EQ(1, window_transformed_info().changed_count); EXPECT_EQ(window.get(), window_transformed_info().window); EXPECT_EQ(ui::PropertyChangeReason::FROM_ANIMATION, window_transformed_info().reason); } TEST_F(WindowTest, AcquireLayer) { std::unique_ptr window1(CreateTestWindowWithId(1, root_window())); std::unique_ptr window2(CreateTestWindowWithId(2, root_window())); ui::Layer* parent = window1->parent()->layer(); EXPECT_EQ(2U, parent->children().size()); WindowTestApi window1_test_api(window1.get()); WindowTestApi window2_test_api(window2.get()); EXPECT_TRUE(window1_test_api.OwnsLayer()); EXPECT_TRUE(window2_test_api.OwnsLayer()); // After acquisition, window1 should not own its layer, but it should still // be available to the window. std::unique_ptr window1_layer(window1->AcquireLayer()); EXPECT_FALSE(window1_test_api.OwnsLayer()); EXPECT_TRUE(window1_layer.get() == window1->layer()); // The acquired layer's owner should be set NULL and re-acquring // should return NULL. EXPECT_FALSE(window1_layer->owner()); std::unique_ptr window1_layer_reacquired(window1->AcquireLayer()); EXPECT_FALSE(window1_layer_reacquired.get()); // Upon destruction, window1's layer should still be valid, and in the layer // hierarchy, but window2's should be gone, and no longer in the hierarchy. window1.reset(); window2.reset(); // This should be set by the window's destructor. EXPECT_FALSE(window1_layer->delegate()); EXPECT_EQ(1U, parent->children().size()); } // Make sure that properties which should persist from the old layer to the new // layer actually do. TEST_F(WindowTest, RecreateLayer) { // Set properties to non default values. gfx::Rect window_bounds(100, 100); Window w(new ColorTestWindowDelegate(SK_ColorWHITE)); w.SetId(1); w.Init(ui::LAYER_SOLID_COLOR); w.SetBounds(window_bounds); ui::Layer* layer = w.layer(); layer->SetVisible(false); layer->SetMasksToBounds(true); ui::Layer child_layer; layer->Add(&child_layer); std::unique_ptr old_layer(w.RecreateLayer()); layer = w.layer(); EXPECT_EQ(ui::LAYER_SOLID_COLOR, layer->type()); EXPECT_FALSE(layer->visible()); EXPECT_EQ(1u, layer->children().size()); EXPECT_TRUE(layer->GetMasksToBounds()); EXPECT_EQ("0,0 100x100", w.bounds().ToString()); EXPECT_EQ("0,0 100x100", layer->bounds().ToString()); } // Verify that RecreateLayer() stacks the old layer above the newly creatd // layer. TEST_F(WindowTest, RecreateLayerZOrder) { std::unique_ptr w(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(0, 0, 100, 100), root_window())); std::unique_ptr old_layer(w->RecreateLayer()); const std::vector& child_layers = root_window()->layer()->children(); ASSERT_EQ(2u, child_layers.size()); EXPECT_EQ(w->layer(), child_layers[0]); EXPECT_EQ(old_layer.get(), child_layers[1]); } // Ensure that acquiring a layer then recreating a layer does not crash // and that RecreateLayer returns null. TEST_F(WindowTest, AcquireThenRecreateLayer) { std::unique_ptr w(CreateTestWindow( SK_ColorWHITE, 1, gfx::Rect(0, 0, 100, 100), root_window())); std::unique_ptr acquired_layer(w->AcquireLayer()); std::unique_ptr doubly_acquired_layer(w->RecreateLayer()); EXPECT_FALSE(doubly_acquired_layer); // Destroy window before layer gets destroyed. w.reset(); } class TestVisibilityClient : public client::VisibilityClient { public: explicit TestVisibilityClient(Window* root_window) : ignore_visibility_changes_(false) { client::SetVisibilityClient(root_window, this); } ~TestVisibilityClient() override {} void set_ignore_visibility_changes(bool ignore_visibility_changes) { ignore_visibility_changes_ = ignore_visibility_changes; } // Overridden from client::VisibilityClient: void UpdateLayerVisibility(aura::Window* window, bool visible) override { if (!ignore_visibility_changes_) window->layer()->SetVisible(visible); } private: bool ignore_visibility_changes_; DISALLOW_COPY_AND_ASSIGN(TestVisibilityClient); }; TEST_F(WindowTest, VisibilityClientIsVisible) { TestVisibilityClient client(root_window()); std::unique_ptr window(CreateTestWindowWithId(1, root_window())); EXPECT_TRUE(window->IsVisible()); EXPECT_TRUE(window->layer()->visible()); window->Hide(); EXPECT_FALSE(window->IsVisible()); EXPECT_FALSE(window->layer()->visible()); window->Show(); client.set_ignore_visibility_changes(true); window->Hide(); EXPECT_FALSE(window->IsVisible()); EXPECT_TRUE(window->layer()->visible()); } // Tests the mouse events seen by WindowDelegates in a Window hierarchy when // changing the properties of a leaf Window. TEST_F(WindowTest, MouseEventsOnLeafWindowChange) { ui::test::EventGenerator generator(root_window()); generator.MoveMouseTo(50, 50); EventCountDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(0, 0, 100, 100), root_window())); RunAllPendingInMessageLoop(); // The format of result is "Enter/Move/Leave". EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); // Add new window |w11| on top of |w1| which contains the cursor. EventCountDelegate d11; std::unique_ptr w11(CreateTestWindowWithDelegate( &d11, 1, gfx::Rect(0, 0, 100, 100), w1.get())); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); // Resize |w11| so that it does not contain the cursor. w11->SetBounds(gfx::Rect(0, 0, 10, 10)); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); // Resize |w11| so that it does contain the cursor. w11->SetBounds(gfx::Rect(0, 0, 60, 60)); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); // Detach |w11| from |w1|. w1->RemoveChild(w11.get()); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); // Re-attach |w11| to |w1|. w1->AddChild(w11.get()); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); // Hide |w11|. w11->Hide(); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); // Show |w11|. w11->Show(); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); // Translate |w11| so that it does not contain the mouse cursor. gfx::Transform transform; transform.Translate(100, 100); w11->SetTransform(transform); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 1", d11.GetMouseMotionCountsAndReset()); // Clear the transform on |w11| so that it does contain the cursor. w11->SetTransform(gfx::Transform()); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d11.GetMouseMotionCountsAndReset()); // Close |w11|. Note that since |w11| is being destroyed, its delegate should // not see any further events. w11.reset(); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d11.GetMouseMotionCountsAndReset()); // Move the mouse outside the bounds of the root window. Since the mouse // cursor is no longer within their bounds, the delegates of the child // windows should not see any mouse events. generator.MoveMouseTo(-10, -10); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d11.GetMouseMotionCountsAndReset()); // Add |w11|. w11.reset(CreateTestWindowWithDelegate( &d11, 1, gfx::Rect(0, 0, 100, 100), w1.get())); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d11.GetMouseMotionCountsAndReset()); // Close |w11|. w11.reset(); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d11.GetMouseMotionCountsAndReset()); } // Tests the mouse events seen by WindowDelegates in a Window hierarchy when // deleting a non-leaf Window. TEST_F(WindowTest, MouseEventsOnNonLeafWindowDelete) { ui::test::EventGenerator generator(root_window()); generator.MoveMouseTo(50, 50); EventCountDelegate d1; std::unique_ptr w1(CreateTestWindowWithDelegate( &d1, 1, gfx::Rect(0, 0, 100, 100), root_window())); RunAllPendingInMessageLoop(); // The format of result is "Enter/Move/Leave". EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); // Add new window |w2| on top of |w1| which contains the cursor. EventCountDelegate d2; std::unique_ptr w2(CreateTestWindowWithDelegate( &d2, 1, gfx::Rect(0, 0, 100, 100), w1.get())); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 1", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d2.GetMouseMotionCountsAndReset()); // Add new window on top of |w2| which contains the cursor. EventCountDelegate d3; CreateTestWindowWithDelegate( &d3, 1, gfx::Rect(0, 0, 100, 100), w2.get()); RunAllPendingInMessageLoop(); EXPECT_EQ("0 0 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 1", d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("1 1 0", d3.GetMouseMotionCountsAndReset()); // Delete |w2|, which will also delete its owned child window. Since |w2| and // its child are in the process of being destroyed, their delegates should // not see any further events. w2.reset(); RunAllPendingInMessageLoop(); EXPECT_EQ("1 1 0", d1.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d2.GetMouseMotionCountsAndReset()); EXPECT_EQ("0 0 0", d3.GetMouseMotionCountsAndReset()); } class RootWindowAttachmentObserver : public WindowObserver { public: RootWindowAttachmentObserver() : added_count_(0), removed_count_(0) {} ~RootWindowAttachmentObserver() override {} int added_count() const { return added_count_; } int removed_count() const { return removed_count_; } void Clear() { added_count_ = 0; removed_count_ = 0; } // Overridden from WindowObserver: void OnWindowAddedToRootWindow(Window* window) override { ++added_count_; } void OnWindowRemovingFromRootWindow(Window* window, Window* new_root) override { ++removed_count_; } private: int added_count_; int removed_count_; DISALLOW_COPY_AND_ASSIGN(RootWindowAttachmentObserver); }; TEST_F(WindowTest, RootWindowAttachment) { RootWindowAttachmentObserver observer; // Test a direct add/remove from the RootWindow. std::unique_ptr w1(new Window(nullptr)); w1->Init(ui::LAYER_NOT_DRAWN); w1->AddObserver(&observer); ParentWindow(w1.get()); EXPECT_EQ(1, observer.added_count()); EXPECT_EQ(0, observer.removed_count()); w1.reset(); EXPECT_EQ(1, observer.added_count()); EXPECT_EQ(1, observer.removed_count()); observer.Clear(); // Test an indirect add/remove from the RootWindow. w1 = std::make_unique(nullptr); w1->Init(ui::LAYER_NOT_DRAWN); Window* w11 = new Window(nullptr); w11->Init(ui::LAYER_NOT_DRAWN); w11->AddObserver(&observer); w1->AddChild(w11); EXPECT_EQ(0, observer.added_count()); EXPECT_EQ(0, observer.removed_count()); ParentWindow(w1.get()); EXPECT_EQ(1, observer.added_count()); EXPECT_EQ(0, observer.removed_count()); w1.reset(); // Deletes w11. w11 = nullptr; EXPECT_EQ(1, observer.added_count()); EXPECT_EQ(1, observer.removed_count()); observer.Clear(); // Test an indirect add/remove with nested observers. w1 = std::make_unique(nullptr); w1->Init(ui::LAYER_NOT_DRAWN); w11 = new Window(nullptr); w11->Init(ui::LAYER_NOT_DRAWN); w11->AddObserver(&observer); w1->AddChild(w11); Window* w111 = new Window(nullptr); w111->Init(ui::LAYER_NOT_DRAWN); w111->AddObserver(&observer); w11->AddChild(w111); EXPECT_EQ(0, observer.added_count()); EXPECT_EQ(0, observer.removed_count()); ParentWindow(w1.get()); EXPECT_EQ(2, observer.added_count()); EXPECT_EQ(0, observer.removed_count()); w1.reset(); // Deletes w11 and w111. w11 = nullptr; w111 = nullptr; EXPECT_EQ(2, observer.added_count()); EXPECT_EQ(2, observer.removed_count()); } class BoundsChangedWindowObserver : public WindowObserver { public: BoundsChangedWindowObserver() : root_set_(false) {} void OnWindowBoundsChanged(Window* window, const gfx::Rect& old_bounds, const gfx::Rect& new_bounds, ui::PropertyChangeReason reason) override { root_set_ = !!window->GetRootWindow(); } bool root_set() const { return root_set_; } private: bool root_set_; DISALLOW_COPY_AND_ASSIGN(BoundsChangedWindowObserver); }; TEST_F(WindowTest, RootWindowSetWhenReparenting) { Window parent1(nullptr); parent1.Init(ui::LAYER_NOT_DRAWN); Window parent2(nullptr); parent2.Init(ui::LAYER_NOT_DRAWN); ParentWindow(&parent1); ParentWindow(&parent2); parent1.SetBounds(gfx::Rect(10, 10, 300, 300)); parent2.SetBounds(gfx::Rect(20, 20, 300, 300)); BoundsChangedWindowObserver observer; Window child(nullptr); child.Init(ui::LAYER_NOT_DRAWN); child.SetBounds(gfx::Rect(5, 5, 100, 100)); parent1.AddChild(&child); // We need animations to start in order to observe the bounds changes. ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); ui::ScopedLayerAnimationSettings settings1(child.layer()->GetAnimator()); settings1.SetTransitionDuration(base::TimeDelta::FromMilliseconds(100)); gfx::Rect new_bounds(gfx::Rect(35, 35, 50, 50)); child.SetBounds(new_bounds); child.AddObserver(&observer); // Reparenting the |child| will cause it to get moved. During this move // the window should still have root window set. parent2.AddChild(&child); EXPECT_TRUE(observer.root_set()); // Animations should stop and the bounds should be as set before the |child| // got reparented. EXPECT_EQ(new_bounds.ToString(), child.GetTargetBounds().ToString()); EXPECT_EQ(new_bounds.ToString(), child.bounds().ToString()); EXPECT_EQ("55,55 50x50", child.GetBoundsInRootWindow().ToString()); } TEST_F(WindowTest, OwnedByParentFalse) { // By default, a window is owned by its parent. If this is set to false, the // window will not be destroyed when its parent is. std::unique_ptr w1(new Window(nullptr)); w1->Init(ui::LAYER_NOT_DRAWN); std::unique_ptr w2(new Window(nullptr)); w2->set_owned_by_parent(false); w2->Init(ui::LAYER_NOT_DRAWN); w1->AddChild(w2.get()); w1.reset(); // We should be able to deref w2 still, but its parent should now be NULL. EXPECT_FALSE(w2->parent()); } // Used By DeleteWindowFromOnWindowDestroyed. Destroys a Window from // OnWindowDestroyed(). class OwningWindowDelegate : public TestWindowDelegate { public: OwningWindowDelegate() {} void SetOwnedWindow(Window* window) { owned_window_.reset(window); } void OnWindowDestroyed(Window* window) override { owned_window_.reset(); } private: std::unique_ptr owned_window_; DISALLOW_COPY_AND_ASSIGN(OwningWindowDelegate); }; // Creates a window with two child windows. When the first child window is // destroyed (WindowDelegate::OnWindowDestroyed) it deletes the second child. // This synthesizes BrowserView and the status bubble. Both are children of the // same parent and destroying BrowserView triggers it destroying the status // bubble. TEST_F(WindowTest, DeleteWindowFromOnWindowDestroyed) { std::unique_ptr parent(new Window(nullptr)); parent->Init(ui::LAYER_NOT_DRAWN); OwningWindowDelegate delegate; Window* c1 = new Window(&delegate); c1->Init(ui::LAYER_NOT_DRAWN); parent->AddChild(c1); Window* c2 = new Window(nullptr); c2->Init(ui::LAYER_NOT_DRAWN); parent->AddChild(c2); delegate.SetOwnedWindow(c2); parent.reset(); } // WindowObserver implementation that deletes a window in // OnWindowVisibilityChanged(). class DeleteOnVisibilityChangedObserver : public WindowObserver { public: // |to_observe| is the Window this is added as an observer to. When // OnWindowVisibilityChanged() is called |to_delete| is deleted. explicit DeleteOnVisibilityChangedObserver(Window* to_observe, Window* to_delete) : to_observe_(to_observe), to_delete_(to_delete) { to_observe_->AddObserver(this); } ~DeleteOnVisibilityChangedObserver() override { // OnWindowVisibilityChanged() should have been called. DCHECK(!to_delete_); } // WindowObserver: void OnWindowVisibilityChanged(Window* window, bool visible) override { to_observe_->RemoveObserver(this); delete to_delete_; to_delete_ = nullptr; } private: Window* to_observe_; Window* to_delete_; DISALLOW_COPY_AND_ASSIGN(DeleteOnVisibilityChangedObserver); }; TEST_F(WindowTest, DeleteParentWindowFromOnWindowVisibiltyChanged) { WindowTracker tracker; Window* root = CreateTestWindowWithId(0, nullptr); tracker.Add(root); Window* child1 = CreateTestWindowWithId(0, root); tracker.Add(child1); tracker.Add(CreateTestWindowWithId(0, root)); // This deletes |root| (the parent) when OnWindowVisibilityChanged() is // received by |child1|. DeleteOnVisibilityChangedObserver deletion_observer(child1, root); // The Hide() calls trigger deleting |root|, which should delete the whole // tree. root->Hide(); EXPECT_TRUE(tracker.windows().empty()); } // Used by DelegateNotifiedAsBoundsChange to verify OnBoundsChanged() is // invoked. class BoundsChangeDelegate : public TestWindowDelegate { public: BoundsChangeDelegate() : bounds_changed_(false) {} void clear_bounds_changed() { bounds_changed_ = false; } bool bounds_changed() const { return bounds_changed_; } // Window void OnBoundsChanged(const gfx::Rect& old_bounds, const gfx::Rect& new_bounds) override { bounds_changed_ = true; } private: // Was OnBoundsChanged() invoked? bool bounds_changed_; DISALLOW_COPY_AND_ASSIGN(BoundsChangeDelegate); }; // Verifies the delegate is notified when the actual bounds of the layer // change. TEST_F(WindowTest, DelegateNotifiedAsBoundsChange) { BoundsChangeDelegate delegate; // We cannot short-circuit animations in this test. ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 1, gfx::Rect(0, 0, 100, 100), root_window())); window->layer()->GetAnimator()->set_disable_timer_for_test(true); delegate.clear_bounds_changed(); // Animate to a different position. { ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); window->SetBounds(gfx::Rect(100, 100, 100, 100)); } // Bounds shouldn't immediately have changed. EXPECT_EQ("0,0 100x100", window->bounds().ToString()); EXPECT_FALSE(delegate.bounds_changed()); // Animate to the end, which should notify of the change. base::TimeTicks start_time = window->layer()->GetAnimator()->last_step_time(); ui::LayerAnimator* animator = window->layer()->GetAnimator(); animator->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); EXPECT_TRUE(delegate.bounds_changed()); EXPECT_NE("0,0 100x100", window->bounds().ToString()); } // Verifies the delegate is notified when the actual bounds of the layer // change even when the window is not the layer's delegate TEST_F(WindowTest, DelegateNotifiedAsBoundsChangeInHiddenLayer) { BoundsChangeDelegate delegate; // We cannot short-circuit animations in this test. ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); std::unique_ptr window(CreateTestWindowWithDelegate( &delegate, 1, gfx::Rect(0, 0, 100, 100), root_window())); window->layer()->GetAnimator()->set_disable_timer_for_test(true); delegate.clear_bounds_changed(); // Suppress paint on the layer since it is hidden (should reset the layer's // delegate to NULL) window->layer()->SuppressPaint(); EXPECT_FALSE(window->layer()->delegate()); // Animate to a different position. { ui::ScopedLayerAnimationSettings settings(window->layer()->GetAnimator()); window->SetBounds(gfx::Rect(100, 100, 110, 100)); } // Layer delegate is NULL but we should still get bounds changed notification. EXPECT_EQ("100,100 110x100", window->GetTargetBounds().ToString()); EXPECT_TRUE(delegate.bounds_changed()); delegate.clear_bounds_changed(); // Animate to the end: will *not* notify of the change since we are hidden. base::TimeTicks start_time = window->layer()->GetAnimator()->last_step_time(); ui::LayerAnimator* animator = window->layer()->GetAnimator(); animator->Step(start_time + base::TimeDelta::FromMilliseconds(1000)); // No bounds changed notification at the end of animation since layer // delegate is NULL. EXPECT_FALSE(delegate.bounds_changed()); EXPECT_NE("0,0 100x100", window->layer()->bounds().ToString()); } // Used by AddChildNotifications to track notification counts. class AddChildNotificationsObserver : public WindowObserver { public: AddChildNotificationsObserver() : added_count_(0), removed_count_(0) {} std::string CountStringAndReset() { std::string result = base::NumberToString(added_count_) + " " + base::NumberToString(removed_count_); added_count_ = removed_count_ = 0; return result; } // WindowObserver overrides: void OnWindowAddedToRootWindow(Window* window) override { added_count_++; } void OnWindowRemovingFromRootWindow(Window* window, Window* new_root) override { removed_count_++; } private: int added_count_; int removed_count_; DISALLOW_COPY_AND_ASSIGN(AddChildNotificationsObserver); }; // Assertions around when root window notifications are sent. TEST_F(WindowTest, AddChildNotifications) { AddChildNotificationsObserver observer; std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); std::unique_ptr w2(CreateTestWindowWithId(1, root_window())); w2->AddObserver(&observer); w2->Focus(); EXPECT_TRUE(w2->HasFocus()); // Move |w2| to be a child of |w1|. w1->AddChild(w2.get()); // Sine we moved in the same root, observer shouldn't be notified. EXPECT_EQ("0 0", observer.CountStringAndReset()); // |w2| should still have focus after moving. EXPECT_TRUE(w2->HasFocus()); } // Tests that a delegate that destroys itself when the window is destroyed does // not break. TEST_F(WindowTest, DelegateDestroysSelfOnWindowDestroy) { std::unique_ptr w1( CreateTestWindowWithDelegate(new DestroyWindowDelegate(), 0, gfx::Rect(10, 20, 30, 40), root_window())); } class HierarchyObserver : public WindowObserver { public: explicit HierarchyObserver(Window* target) : target_(target) { target_->AddObserver(this); } ~HierarchyObserver() override { target_->RemoveObserver(this); } void ValidateState( int index, const WindowObserver::HierarchyChangeParams& params) const { ParamsMatch(params_[index], params); } void Reset() { params_.clear(); } private: // Overridden from WindowObserver: void OnWindowHierarchyChanging(const HierarchyChangeParams& params) override { params_.push_back(params); } void OnWindowHierarchyChanged(const HierarchyChangeParams& params) override { params_.push_back(params); } void ParamsMatch(const WindowObserver::HierarchyChangeParams& p1, const WindowObserver::HierarchyChangeParams& p2) const { EXPECT_EQ(p1.phase, p2.phase); EXPECT_EQ(p1.target, p2.target); EXPECT_EQ(p1.new_parent, p2.new_parent); EXPECT_EQ(p1.old_parent, p2.old_parent); EXPECT_EQ(p1.receiver, p2.receiver); } Window* target_; std::vector params_; DISALLOW_COPY_AND_ASSIGN(HierarchyObserver); }; // Tests hierarchy change notifications. TEST_F(WindowTest, OnWindowHierarchyChange) { { // Simple add & remove. HierarchyObserver oroot(root_window()); std::unique_ptr w1(CreateTestWindowWithId(1, nullptr)); HierarchyObserver o1(w1.get()); // Add. root_window()->AddChild(w1.get()); WindowObserver::HierarchyChangeParams params; params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; params.target = w1.get(); params.old_parent = nullptr; params.new_parent = root_window(); params.receiver = w1.get(); o1.ValidateState(0, params); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; params.receiver = w1.get(); o1.ValidateState(1, params); params.receiver = root_window(); oroot.ValidateState(0, params); // Remove. o1.Reset(); oroot.Reset(); root_window()->RemoveChild(w1.get()); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; params.old_parent = root_window(); params.new_parent = nullptr; params.receiver = w1.get(); o1.ValidateState(0, params); params.receiver = root_window(); oroot.ValidateState(0, params); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; params.receiver = w1.get(); o1.ValidateState(1, params); } { // Add & remove of hierarchy. Tests notification order per documentation in // WindowObserver. HierarchyObserver o(root_window()); std::unique_ptr w1(CreateTestWindowWithId(1, nullptr)); Window* w11 = CreateTestWindowWithId(11, w1.get()); w1->AddObserver(&o); w11->AddObserver(&o); // Add. root_window()->AddChild(w1.get()); // Dispatched to target first. int index = 0; WindowObserver::HierarchyChangeParams params; params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; params.target = w1.get(); params.old_parent = nullptr; params.new_parent = root_window(); params.receiver = w1.get(); o.ValidateState(index++, params); // Dispatched to target's children. params.receiver = w11; o.ValidateState(index++, params); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; // Now process the "changed" phase. params.receiver = w1.get(); o.ValidateState(index++, params); params.receiver = w11; o.ValidateState(index++, params); params.receiver = root_window(); o.ValidateState(index++, params); // Remove. root_window()->RemoveChild(w1.get()); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; params.old_parent = root_window(); params.new_parent = nullptr; params.receiver = w1.get(); o.ValidateState(index++, params); params.receiver = w11; o.ValidateState(index++, params); params.receiver = root_window(); o.ValidateState(index++, params); params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; params.receiver = w1.get(); o.ValidateState(index++, params); params.receiver = w11; o.ValidateState(index++, params); w1.reset(); } { // Reparent. Tests notification order per documentation in WindowObserver. std::unique_ptr w1(CreateTestWindowWithId(1, root_window())); Window* w11 = CreateTestWindowWithId(11, w1.get()); Window* w111 = CreateTestWindowWithId(111, w11); std::unique_ptr w2(CreateTestWindowWithId(2, root_window())); HierarchyObserver o(root_window()); w1->AddObserver(&o); w11->AddObserver(&o); w111->AddObserver(&o); w2->AddObserver(&o); w2->AddChild(w11); // Dispatched to target first. int index = 0; WindowObserver::HierarchyChangeParams params; params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGING; params.target = w11; params.old_parent = w1.get(); params.new_parent = w2.get(); params.receiver = w11; o.ValidateState(index++, params); // Then to target's children. params.receiver = w111; o.ValidateState(index++, params); // Then to target's old parent chain. params.receiver = w1.get(); o.ValidateState(index++, params); params.receiver = root_window(); o.ValidateState(index++, params); // "Changed" phase. params.phase = WindowObserver::HierarchyChangeParams::HIERARCHY_CHANGED; params.receiver = w11; o.ValidateState(index++, params); params.receiver = w111; o.ValidateState(index++, params); params.receiver = w2.get(); o.ValidateState(index++, params); params.receiver = root_window(); o.ValidateState(index++, params); w1.reset(); w2.reset(); } } class TestLayerAnimationObserver : public ui::LayerAnimationObserver { public: TestLayerAnimationObserver() : animation_completed_(false), animation_aborted_(false) {} ~TestLayerAnimationObserver() override {} bool animation_completed() const { return animation_completed_; } bool animation_aborted() const { return animation_aborted_; } void Reset() { animation_completed_ = false; animation_aborted_ = false; } private: // ui::LayerAnimationObserver: void OnLayerAnimationEnded(ui::LayerAnimationSequence* sequence) override { animation_completed_ = true; } void OnLayerAnimationAborted(ui::LayerAnimationSequence* sequence) override { animation_aborted_ = true; } void OnLayerAnimationScheduled( ui::LayerAnimationSequence* sequence) override {} bool animation_completed_; bool animation_aborted_; DISALLOW_COPY_AND_ASSIGN(TestLayerAnimationObserver); }; TEST_F(WindowTest, WindowDestroyCompletesAnimations) { ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); scoped_refptr animator = ui::LayerAnimator::CreateImplicitAnimator(); TestLayerAnimationObserver observer; animator->AddObserver(&observer); // Make sure destroying a Window completes the animation. { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->layer()->SetAnimator(animator.get()); gfx::Transform transform; transform.Scale(0.5f, 0.5f); window->SetTransform(transform); EXPECT_TRUE(animator->is_animating()); EXPECT_FALSE(observer.animation_completed()); } EXPECT_TRUE(animator.get()); EXPECT_FALSE(animator->is_animating()); EXPECT_TRUE(observer.animation_completed()); EXPECT_FALSE(observer.animation_aborted()); animator->RemoveObserver(&observer); observer.Reset(); animator = ui::LayerAnimator::CreateImplicitAnimator(); animator->AddObserver(&observer); ui::Layer layer; layer.SetAnimator(animator.get()); { std::unique_ptr window(CreateTestWindowWithId(1, root_window())); window->layer()->Add(&layer); gfx::Transform transform; transform.Scale(0.5f, 0.5f); layer.SetTransform(transform); EXPECT_TRUE(animator->is_animating()); EXPECT_FALSE(observer.animation_completed()); } EXPECT_TRUE(animator.get()); EXPECT_FALSE(animator->is_animating()); EXPECT_TRUE(observer.animation_completed()); EXPECT_FALSE(observer.animation_aborted()); animator->RemoveObserver(&observer); } TEST_F(WindowTest, RootWindowUsesCompositorFrameSinkId) { EXPECT_EQ(host()->compositor()->frame_sink_id(), root_window()->GetFrameSinkId()); EXPECT_TRUE(root_window()->GetFrameSinkId().is_valid()); } TEST_F(WindowTest, LocalSurfaceIdChanges) { Window window(nullptr); window.Init(ui::LAYER_NOT_DRAWN); window.SetBounds(gfx::Rect(300, 300)); root_window()->AddChild(&window); std::unique_ptr frame_sink( window.CreateLayerTreeFrameSink()); viz::LocalSurfaceId local_surface_id1 = window.GetLocalSurfaceId(); EXPECT_NE(nullptr, frame_sink.get()); EXPECT_TRUE(local_surface_id1.is_valid()); // Resize to 0x0 to make sure the correct window size is stored before // creating the frame sink. window.SetBounds(gfx::Rect(0, 0)); viz::LocalSurfaceId local_surface_id2 = window.GetLocalSurfaceId(); EXPECT_TRUE(local_surface_id2.is_valid()); EXPECT_NE(local_surface_id1, local_surface_id2); window.SetBounds(gfx::Rect(300, 300)); viz::LocalSurfaceId local_surface_id3 = window.GetLocalSurfaceId(); EXPECT_TRUE(local_surface_id3.is_valid()); EXPECT_NE(local_surface_id1, local_surface_id3); EXPECT_NE(local_surface_id2, local_surface_id3); window.OnDeviceScaleFactorChanged(1.0f, 3.0f); viz::LocalSurfaceId local_surface_id4 = window.GetLocalSurfaceId(); EXPECT_TRUE(local_surface_id4.is_valid()); EXPECT_NE(local_surface_id1, local_surface_id4); EXPECT_NE(local_surface_id2, local_surface_id4); EXPECT_NE(local_surface_id3, local_surface_id4); window.RecreateLayer(); viz::LocalSurfaceId local_surface_id5 = window.GetLocalSurfaceId(); EXPECT_TRUE(local_surface_id5.is_valid()); EXPECT_NE(local_surface_id1, local_surface_id5); EXPECT_NE(local_surface_id2, local_surface_id5); EXPECT_NE(local_surface_id3, local_surface_id5); EXPECT_NE(local_surface_id4, local_surface_id5); window.AllocateLocalSurfaceId(); viz::LocalSurfaceId local_surface_id6 = window.GetLocalSurfaceId(); EXPECT_TRUE(local_surface_id6.is_valid()); EXPECT_NE(local_surface_id1, local_surface_id6); EXPECT_NE(local_surface_id2, local_surface_id6); EXPECT_NE(local_surface_id3, local_surface_id6); EXPECT_NE(local_surface_id4, local_surface_id6); EXPECT_NE(local_surface_id5, local_surface_id6); } TEST_F(WindowTest, CreateLayerTreeFrameSink) { Window window(nullptr); window.Init(ui::LAYER_NOT_DRAWN); window.SetBounds(gfx::Rect(300, 300)); root_window()->AddChild(&window); // The window shouldn't have FrameSinkId before CreateLayerTreeFrameSink() is // called. EXPECT_FALSE(window.GetFrameSinkId().is_valid()); std::unique_ptr layer_tree_frame_sink = window.CreateLayerTreeFrameSink(); // Calling CreateLayerTreeFrameSink() should return a LayerTreeFrameSink and // the window should now have a FrameSinkId. EXPECT_NE(layer_tree_frame_sink.get(), nullptr); EXPECT_TRUE(window.GetFrameSinkId().is_valid()); viz::FrameSinkId frame_sink_id = window.GetFrameSinkId(); // Reset and recreate the LayerTreeFrameSink. This would typically happen // after a GPU crash. layer_tree_frame_sink.reset(); layer_tree_frame_sink = window.CreateLayerTreeFrameSink(); // A new LayerTreeFrameSink should be created for the same FrameSinkId. EXPECT_NE(layer_tree_frame_sink.get(), nullptr); EXPECT_EQ(frame_sink_id, window.GetFrameSinkId()); } // This delegate moves its parent window to the specified one when the gesture // ends. class HandleGestureEndDelegate : public TestWindowDelegate { public: explicit HandleGestureEndDelegate( base::OnceCallback on_gesture_end) : on_gesture_end_(std::move(on_gesture_end)) {} ~HandleGestureEndDelegate() override = default; private: // WindowDelegate: void OnGestureEvent(ui::GestureEvent* event) override { switch (event->type()) { case ui::ET_GESTURE_SCROLL_END: case ui::ET_GESTURE_END: case ui::ET_GESTURE_PINCH_END: { if (on_gesture_end_) std::move(on_gesture_end_).Run(static_cast(event->target())); break; } default: break; } } base::OnceCallback on_gesture_end_; DISALLOW_COPY_AND_ASSIGN(HandleGestureEndDelegate); }; TEST_F(WindowTest, CleanupGestureStateChangesWindowHierarchy) { Window window(nullptr); window.Init(ui::LAYER_NOT_DRAWN); root_window()->AddChild(&window); window.SetBounds(gfx::Rect(0, 0, 100, 100)); window.Show(); Window window2(nullptr); window2.Init(ui::LAYER_NOT_DRAWN); root_window()->AddChild(&window2); root_window()->StackChildAtBottom(&window2); window2.Show(); HandleGestureEndDelegate delegate(base::BindLambdaForTesting( [&](Window* target_window) { window2.AddChild(target_window); })); std::unique_ptr child = std::make_unique(&delegate); child->Init(ui::LAYER_NOT_DRAWN); window.AddChild(child.get()); child->SetBounds(gfx::Rect(0, 0, 100, 100)); child->Show(); EXPECT_EQ(1u, window.children().size()); EXPECT_EQ(0u, window2.children().size()); ui::test::EventGenerator event_generator(root_window(), child.get()); event_generator.PressTouch(); window.CleanupGestureState(); EXPECT_EQ(0u, window.children().size()); EXPECT_EQ(1u, window2.children().size()); child.reset(); } TEST_F(WindowTest, CleanupGestureStateDeleteOtherWindows) { Window window(nullptr); window.Init(ui::LAYER_NOT_DRAWN); root_window()->AddChild(&window); window.SetBounds(gfx::Rect(0, 0, 200, 200)); window.Show(); std::unique_ptr child1 = std::make_unique(nullptr); child1->Init(ui::LAYER_NOT_DRAWN); child1->SetBounds(gfx::Rect(100, 100, 100, 100)); window.AddChild(child1.get()); child1->Show(); HandleGestureEndDelegate delegate(base::BindLambdaForTesting( [&](Window* target_window) { child1.reset(); })); std::unique_ptr child2 = std::make_unique(&delegate); child2->Init(ui::LAYER_NOT_DRAWN); window.AddChild(child2.get()); child2->SetBounds(gfx::Rect(0, 0, 100, 100)); child2->Show(); window.StackChildAtBottom(child2.get()); EXPECT_EQ(2u, window.children().size()); EXPECT_EQ(child2.get(), window.children().front()); ui::test::EventGenerator event_generator(root_window(), child2.get()); event_generator.PressTouch(); window.CleanupGestureState(); EXPECT_EQ(1u, window.children().size()); EXPECT_FALSE(child1); child2.reset(); } class WindowActualScreenBoundsTest : public WindowTest, public testing::WithParamInterface< /*is_target_transform_identical*/ bool> { public: WindowActualScreenBoundsTest() = default; WindowActualScreenBoundsTest(const WindowActualScreenBoundsTest&) = delete; WindowActualScreenBoundsTest& operator=(const WindowActualScreenBoundsTest&) = delete; ~WindowActualScreenBoundsTest() override = default; // WindowTest: void SetUp() override { WindowTest::SetUp(); viewport_ = std::unique_ptr(CreateTestWindowWithBounds( gfx::Rect(/*x=*/100, /*y=*/50, /*width=*/200, /*height=*/200), root_window())); child_ = std::unique_ptr(CreateTestWindowWithBounds( gfx::Rect(/*x=*/0, /*y=*/0, /*width=*/100, /*height*/ 100), viewport_.get())); } void TearDown() override { child_.reset(); viewport_.reset(); WindowTest::TearDown(); } void OnTranslationAnimationStarted(const gfx::Transform& transform) const { EXPECT_EQ("100,50 100x100", child_->GetActualBoundsInScreen().ToString()); EXPECT_EQ("50,0 100x100", child_->GetBoundsInScreen().ToString()); } void OnTranslationAnimationEnded(const gfx::Transform& transform) const { EXPECT_EQ("50,0 100x100", child_->GetActualBoundsInScreen().ToString()); EXPECT_EQ("50,0 100x100", child_->GetBoundsInScreen().ToString()); } void OnTranslationAnimationProgressed(const gfx::Rect& child_initial_bounds, const gfx::Transform& transform) const { gfx::RectF current_screen_bounds(child_initial_bounds); transform.TransformRect(¤t_screen_bounds); EXPECT_EQ(gfx::ToEnclosedRect(current_screen_bounds), child_->GetActualBoundsInScreen()); } std::unique_ptr viewport_; std::unique_ptr child_; }; INSTANTIATE_TEST_SUITE_P(All, WindowActualScreenBoundsTest, testing::Bool()); // Verifies that the function to get the window's screen bounds works as // expected during layer animation. TEST_P(WindowActualScreenBoundsTest, VerifyWindowActualBoundsDuringAnimation) { ui::ScopedAnimationDurationScaleMode test_duration_mode( ui::ScopedAnimationDurationScaleMode::NON_ZERO_DURATION); auto* viewport_layer = viewport_->layer(); gfx::Transform initial_transform; gfx::Transform target_transform; gfx::Rect child_initial_bounds; if (GetParam()) { // Test in the scenario where the target transform is identical. // Set the target bounds. viewport_->SetBounds( gfx::Rect(/*x=*/50, /*y=*/0, /*width=*/200, /*height=*/200)); // Set the transform to reverse the effect brought by bounds setting. initial_transform.Translate(50, 50); viewport_->layer()->SetTransform(initial_transform); // Trigger the translation animation. ui::ScopedLayerAnimationSettings settings(viewport_layer->GetAnimator()); viewport_layer->SetTransform(target_transform); // Calculate the child's initial screen bounds manually. child_initial_bounds = gfx::Rect(/*x=*/50, /*y=*/0, /*width=*/100, /*height=*/100); } else { // Test in the scenario where the target transform is non-identical. // Trigger the translation animation. ui::ScopedLayerAnimationSettings settings(viewport_layer->GetAnimator()); target_transform.Translate(-50, -50); viewport_layer->SetTransform(target_transform); // Calculate the child's initial screen bounds manually. child_initial_bounds = gfx::Rect(/*x=*/100, /*y=*/50, /*width=*/100, /*height=*/100); } LayerTranslationAnimationNotifier bounds_checker( viewport_layer, initial_transform, target_transform, base::BindRepeating( &WindowActualScreenBoundsTest::OnTranslationAnimationStarted, base::Unretained(this)), base::BindRepeating( &WindowActualScreenBoundsTest::OnTranslationAnimationEnded, base::Unretained(this)), base::BindRepeating( &WindowActualScreenBoundsTest::OnTranslationAnimationProgressed, base::Unretained(this), child_initial_bounds)); bounds_checker.WaitForAnimationCompletion(); } } // namespace } // namespace test } // namespace aura