| // Copyright 2017 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "ui/android/view_android.h" |
| |
| #include <utility> |
| |
| #include "base/android/jni_android.h" |
| #include "base/functional/callback.h" |
| #include "base/test/bind.h" |
| #include "base/test/gtest_util.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "ui/android/event_forwarder.h" |
| #include "ui/android/test_view_android_delegate.h" |
| #include "ui/android/ui_android_features.h" |
| #include "ui/android/view_android.h" |
| #include "ui/android/view_android_observer.h" |
| #include "ui/android/window_android.h" |
| #include "ui/color/android/color_provider_bridge.h" |
| #include "ui/color/color_provider_key.h" |
| #include "ui/events/android/event_handler_android.h" |
| #include "ui/events/android/motion_event_android_factory.h" |
| #include "ui/events/android/motion_event_android_java.h" |
| #include "ui/events/motionevent_jni_headers/MotionEvent_jni.h" |
| #include "ui/events/test/scoped_event_test_tick_clock.h" |
| |
| namespace ui { |
| |
| using base::android::JavaRef; |
| |
| class TestViewAndroid : public ViewAndroid { |
| public: |
| TestViewAndroid(ViewAndroid::LayoutType layout_type) |
| : ViewAndroid(layout_type) {} |
| |
| float GetDipScale() override { return 1.f; } |
| }; |
| |
| class TestEventHandler : public EventHandlerAndroid { |
| public: |
| TestEventHandler() {} |
| |
| bool OnTouchEvent(const MotionEventAndroid& event) override { |
| touch_called_ = true; |
| return handle_event_; |
| } |
| void OnSizeChanged() override { onsize_called_ = true; } |
| |
| void SetHandleEvent(bool handle_event) { handle_event_ = handle_event; } |
| bool TouchEventHandled() { return touch_called_ && handle_event_; } |
| bool TouchEventCalled() { return touch_called_; } |
| bool OnSizeCalled() { return onsize_called_; } |
| void Reset() { |
| touch_called_ = false; |
| onsize_called_ = false; |
| } |
| |
| private: |
| bool handle_event_{true}; // Marks as event was consumed. True by default. |
| bool touch_called_{false}; |
| bool onsize_called_{false}; |
| }; |
| |
| class ViewAndroidBoundsTest : public testing::Test { |
| public: |
| ViewAndroidBoundsTest() |
| : root_(ViewAndroid::LayoutType::kMatchParent), |
| view1_(ViewAndroid::LayoutType::kNormal), |
| view2_(ViewAndroid::LayoutType::kNormal), |
| view3_(ViewAndroid::LayoutType::kNormal), |
| viewm_(ViewAndroid::LayoutType::kMatchParent) { |
| root_.GetEventForwarder(); |
| view1_.set_event_handler(&handler1_); |
| view2_.set_event_handler(&handler2_); |
| view3_.set_event_handler(&handler3_); |
| viewm_.set_event_handler(&handlerm_); |
| } |
| |
| void Reset() { |
| handler1_.Reset(); |
| handler2_.Reset(); |
| handler3_.Reset(); |
| handlerm_.Reset(); |
| test_clock_.SetNowTicks(base::TimeTicks()); |
| } |
| |
| void GenerateTouchEventAt(float x, float y) { |
| ui::MotionEventAndroid::Pointer pointer0(0, x, y, 0, 0, 0, 0, 0, 0); |
| |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> obj = |
| JNI_MotionEvent::Java_MotionEvent_obtain( |
| env, /*downTime=*/0, /*eventTime=*/0, /*action=*/0, /*x=*/0, |
| /*y=*/0, /*metaState=*/0); |
| |
| auto event = ui::MotionEventAndroidFactory::CreateFromJava( |
| env, obj, |
| /*pix_to_dip=*/1.f, |
| /*ticks_x=*/0, |
| /*ticks_y=*/0, |
| /*tick_multiplier=*/0, |
| /*oldest_event_time=*/base::TimeTicks(), |
| /*android_action=*/0, |
| /*pointer_count=*/1, |
| /*history_size=*/0, |
| /*action_index=*/0, |
| /*android_action_button=*/0, |
| /*android_gesture_classification=*/0, |
| /*android_button_state=*/0, |
| /*raw_offset_x_pixels=*/0, |
| /*raw_offset_y_pixels=*/0, |
| /*for_touch_handle=*/false, &pointer0, |
| /*pointer1=*/nullptr); |
| root_.OnTouchEvent(*event); |
| } |
| |
| void ExpectHit(const TestEventHandler& hitHandler) { |
| TestEventHandler* handlers[4] = {&handler1_, &handler2_, &handler3_, |
| &handlerm_}; |
| for (auto* handler : handlers) { |
| if (&hitHandler == handler) |
| EXPECT_TRUE(handler->TouchEventHandled()); |
| else |
| EXPECT_FALSE(handler->TouchEventHandled()); |
| } |
| Reset(); |
| } |
| |
| void AddThreeStackedChildren() { |
| view1_.SetLayoutForTesting(50, 50, 400, 600); |
| view2_.SetLayoutForTesting(50, 50, 400, 600); |
| view3_.SetLayoutForTesting(50, 50, 400, 600); |
| root_.AddChild(&view1_); |
| root_.AddChild(&view2_); |
| root_.AddChild(&view3_); |
| } |
| |
| void RemoveAllChildren(ViewAndroid* view) { |
| view->RemoveAllChildren(/*attached_to_window=*/false); |
| } |
| |
| // Teardown path that notifies each child, and so can re-enter. |
| void RemoveAllChildrenAttached(ViewAndroid* view) { |
| view->RemoveAllChildren(/*attached_to_window=*/true); |
| } |
| |
| TestViewAndroid root_; |
| TestViewAndroid view1_; |
| TestViewAndroid view2_; |
| TestViewAndroid view3_; |
| TestViewAndroid viewm_; // match-parent view |
| TestEventHandler handler1_; |
| TestEventHandler handler2_; |
| TestEventHandler handler3_; |
| TestEventHandler handlerm_; |
| ui::test::ScopedEventTestTickClock test_clock_; |
| }; |
| |
| TEST_F(ViewAndroidBoundsTest, MatchesViewInFront) { |
| view1_.SetLayoutForTesting(50, 50, 400, 600); |
| view2_.SetLayoutForTesting(50, 50, 400, 600); |
| root_.AddChild(&view2_); |
| root_.AddChild(&view1_); |
| |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler1_); |
| |
| // View 2 moves up to the top, and events should hit it from now. |
| root_.MoveToFront(&view2_); |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler2_); |
| |
| // View 2 moves back to the bottom, and events should hit View 1 again. |
| root_.MoveToBack(&view2_); |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler1_); |
| } |
| |
| TEST_F(ViewAndroidBoundsTest, MatchesViewArea) { |
| view1_.SetLayoutForTesting(50, 50, 200, 200); |
| view2_.SetLayoutForTesting(20, 20, 400, 600); |
| |
| root_.AddChild(&view2_); |
| root_.AddChild(&view1_); |
| |
| // Falls within |view1_|'s bounds |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler1_); |
| |
| // Falls within |view2_|'s bounds |
| GenerateTouchEventAt(300.f, 400.f); |
| ExpectHit(handler2_); |
| } |
| |
| TEST_F(ViewAndroidBoundsTest, MatchesViewAfterMove) { |
| view1_.SetLayoutForTesting(50, 50, 200, 200); |
| view2_.SetLayoutForTesting(20, 20, 400, 600); |
| root_.AddChild(&view2_); |
| root_.AddChild(&view1_); |
| |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler1_); |
| |
| view1_.SetLayoutForTesting(150, 150, 200, 200); |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler2_); |
| } |
| |
| TEST_F(ViewAndroidBoundsTest, MatchesViewSizeOfkMatchParent) { |
| view1_.SetLayoutForTesting(20, 20, 400, 600); |
| view2_.SetLayoutForTesting(50, 50, 200, 200); |
| |
| root_.AddChild(&view1_); |
| root_.AddChild(&view2_); |
| view1_.AddChild(&viewm_); |
| |
| GenerateTouchEventAt(100.f, 100.f); |
| ExpectHit(handler2_); |
| |
| GenerateTouchEventAt(300.f, 400.f); |
| ExpectHit(handler1_); |
| |
| handler1_.SetHandleEvent(false); |
| GenerateTouchEventAt(300.f, 400.f); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handlerm_); |
| } |
| |
| TEST_F(ViewAndroidBoundsTest, MatchesViewsWithOffset) { |
| view1_.SetLayoutForTesting(10, 20, 150, 100); |
| view2_.SetLayoutForTesting(20, 30, 40, 30); |
| view3_.SetLayoutForTesting(70, 30, 40, 30); |
| |
| root_.AddChild(&view1_); |
| view1_.AddChild(&view2_); |
| view1_.AddChild(&view3_); |
| |
| GenerateTouchEventAt(70, 30); |
| ExpectHit(handler1_); |
| |
| handler1_.SetHandleEvent(false); |
| GenerateTouchEventAt(40, 60); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handler2_); |
| |
| GenerateTouchEventAt(100, 70); |
| EXPECT_TRUE(handler1_.TouchEventCalled()); |
| ExpectHit(handler3_); |
| } |
| |
| TEST_F(ViewAndroidBoundsTest, OnSizeChanged) { |
| root_.AddChild(&view1_); |
| view1_.AddChild(&viewm_); |
| view1_.AddChild(&view3_); |
| |
| // Size event propagates to non-match-parent children only. |
| view1_.OnSizeChanged(100, 100); |
| EXPECT_TRUE(handler1_.OnSizeCalled()); |
| EXPECT_TRUE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.OnSizeCalled()); |
| |
| Reset(); |
| |
| // Match-parent view should not receive size events in the first place. |
| EXPECT_DCHECK_DEATH(viewm_.OnSizeChanged(100, 200)); |
| EXPECT_FALSE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.OnSizeCalled()); |
| |
| viewm_.RemoveFromParent(); |
| viewm_.OnSizeChangedInternal(gfx::Size(0, 0)); // Reset the size. |
| |
| Reset(); |
| |
| view1_.OnSizeChanged(100, 100); |
| |
| // Size event is generated for a newly added, match-parent child view. |
| EXPECT_FALSE(handlerm_.OnSizeCalled()); |
| view1_.AddChild(&viewm_); |
| EXPECT_TRUE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.OnSizeCalled()); |
| |
| viewm_.RemoveFromParent(); |
| |
| Reset(); |
| |
| view1_.OnSizeChanged(100, 100); |
| |
| // Size event won't propagate if the children already have the same size. |
| view1_.AddChild(&viewm_); |
| EXPECT_FALSE(handlerm_.OnSizeCalled()); |
| EXPECT_FALSE(handler3_.OnSizeCalled()); |
| } |
| |
| // Event handler that runs an arbitrary callback during dispatch, for reentrancy |
| // tests. |
| class ReentrantActionHandler : public EventHandlerAndroid { |
| public: |
| ReentrantActionHandler() = default; |
| explicit ReentrantActionHandler(base::RepeatingClosure action) |
| : action_(std::move(action)) {} |
| |
| bool OnTouchEvent(const MotionEventAndroid& event) override { |
| ++count_; |
| if (action_) { |
| action_.Run(); |
| } |
| return false; |
| } |
| |
| void OnPhysicalBackingSizeChanged( |
| std::optional<base::TimeDelta> deadline_override) override { |
| ++count_; |
| if (action_) { |
| action_.Run(); |
| } |
| } |
| |
| void set_action(base::RepeatingClosure action) { |
| action_ = std::move(action); |
| } |
| int count() const { return count_; } |
| |
| private: |
| base::RepeatingClosure action_; |
| int count_ = 0; |
| }; |
| |
| // Exercises StepIteratorsOver across both iteration directions: |
| // - Reverse (HitTest): current element erases its next sibling, and the head |
| // element erases itself. |
| // - Forward (OnPhysicalBackingSizeChanged): current element erases its next |
| // sibling (cursor sits on it and must be bumped), and the tail erases itself. |
| TEST_F(ViewAndroidBoundsTest, ChildRemovalReentrancy) { |
| AddThreeStackedChildren(); |
| |
| // Reverse pass: view3_ erases view2_ (next); view1_ erases itself (head). |
| ReentrantActionHandler h3( |
| base::BindLambdaForTesting([&]() { view2_.RemoveFromParent(); })); |
| ReentrantActionHandler h2; |
| ReentrantActionHandler h1( |
| base::BindLambdaForTesting([&]() { view1_.RemoveFromParent(); })); |
| view3_.set_event_handler(&h3); |
| view2_.set_event_handler(&h2); |
| view1_.set_event_handler(&h1); |
| |
| GenerateTouchEventAt(100.f, 100.f); |
| EXPECT_EQ(1, h3.count()); |
| EXPECT_EQ(0, h2.count()); |
| EXPECT_EQ(1, h1.count()); |
| |
| // Reset tree to [view1_, view2_, view3_] for the forward pass: view1_ erases |
| // view2_ (next); view3_ erases itself (tail). |
| RemoveAllChildren(&root_); |
| root_.AddChild(&view1_); |
| root_.AddChild(&view2_); |
| root_.AddChild(&view3_); |
| h1.set_action( |
| base::BindLambdaForTesting([&]() { view2_.RemoveFromParent(); })); |
| h3.set_action( |
| base::BindLambdaForTesting([&]() { view3_.RemoveFromParent(); })); |
| |
| root_.OnPhysicalBackingSizeChanged(gfx::Size(100, 100), std::nullopt); |
| EXPECT_EQ(2, h1.count()); |
| EXPECT_EQ(0, h2.count()); |
| EXPECT_EQ(2, h3.count()); |
| } |
| |
| // Reordering mid-traversal via splice: StepIteratorsOver keeps the cursor from |
| // following the moved node. Promoting a view moves it into already-visited |
| // territory (skipped); demoting moves it ahead of the cursor (still visited). |
| TEST_F(ViewAndroidBoundsTest, HitTestReorderReentrancy) { |
| AddThreeStackedChildren(); |
| handler1_.SetHandleEvent(false); |
| handler2_.SetHandleEvent(false); |
| |
| ReentrantActionHandler h3(base::BindLambdaForTesting([&]() { |
| root_.MoveToFront(&view1_); // Moved behind reverse cursor -> skipped. |
| root_.MoveToBack(&view2_); // Moved ahead of reverse cursor -> visited. |
| })); |
| view3_.set_event_handler(&h3); |
| |
| GenerateTouchEventAt(100.f, 100.f); |
| |
| EXPECT_EQ(1, h3.count()); |
| EXPECT_TRUE(handler2_.TouchEventCalled()); |
| EXPECT_FALSE(handler1_.TouchEventCalled()); |
| } |
| |
| // RemoveAllChildren() clears `parent_` on each child it detaches. If the child |
| // reparents itself from its OnDetachedFromWindow() notification, that clear |
| // must not run afterwards and strand the child with a null parent. |
| TEST_F(ViewAndroidBoundsTest, RemoveAllChildrenKeepsReparentedChild) { |
| class ReparentingObserver : public ViewAndroidObserver { |
| public: |
| ReparentingObserver(ViewAndroid* child, ViewAndroid* new_parent) |
| : child_(child), new_parent_(new_parent) {} |
| |
| void OnDetachedFromWindow() override { |
| if (reparented_) { |
| return; |
| } |
| reparented_ = true; |
| new_parent_->AddChild(child_); |
| } |
| |
| private: |
| raw_ptr<ViewAndroid> child_; |
| raw_ptr<ViewAndroid> new_parent_; |
| bool reparented_ = false; |
| }; |
| |
| TestViewAndroid new_parent(ViewAndroid::LayoutType::kNormal); |
| root_.AddChild(&view1_); |
| root_.AddChild(&view2_); |
| |
| ReparentingObserver observer(&view2_, &new_parent); |
| view2_.AddObserver(&observer); |
| |
| RemoveAllChildrenAttached(&root_); |
| |
| EXPECT_EQ(&new_parent, view2_.parent()); |
| EXPECT_EQ(1u, new_parent.GetChildrenCountForTesting()); |
| EXPECT_EQ(0u, root_.GetChildrenCountForTesting()); |
| |
| view2_.RemoveObserver(&observer); |
| } |
| |
| // Helper observer that invokes a callback on OnDetachedFromWindow(). |
| class ReentrantDetachObserver : public ViewAndroidObserver { |
| public: |
| explicit ReentrantDetachObserver(base::OnceClosure action) |
| : action_(std::move(action)) {} |
| |
| void OnDetachedFromWindow() override { |
| if (action_) { |
| std::move(action_).Run(); |
| } |
| } |
| |
| private: |
| base::OnceClosure action_; |
| }; |
| |
| // Verifies RemoveAllChildren() handles a child unlinking itself during |
| // OnDetachedFromWindow(). |
| TEST_F(ViewAndroidBoundsTest, RemoveAllChildrenSurvivesChildUnlink) { |
| ReentrantDetachObserver observer( |
| base::BindLambdaForTesting([&]() { view1_.RemoveFromParent(); })); |
| view1_.AddObserver(&observer); |
| |
| bool view2_detached = false; |
| ReentrantDetachObserver view2_observer( |
| base::BindLambdaForTesting([&]() { view2_detached = true; })); |
| view2_.AddObserver(&view2_observer); |
| |
| root_.AddChild(&view1_); |
| root_.AddChild(&view2_); |
| |
| RemoveAllChildrenAttached(&root_); |
| |
| EXPECT_TRUE(view2_detached); |
| EXPECT_EQ(0u, root_.GetChildrenCountForTesting()); |
| EXPECT_EQ(nullptr, view1_.parent()); |
| EXPECT_EQ(nullptr, view2_.parent()); |
| |
| view1_.RemoveObserver(&observer); |
| view2_.RemoveObserver(&view2_observer); |
| } |
| |
| // Verifies RemoveAllChildren() handles a child being destroyed during |
| // OnDetachedFromWindow(). |
| TEST_F(ViewAndroidBoundsTest, RemoveAllChildrenSurvivesChildDestruction) { |
| auto child_to_destroy = |
| std::make_unique<TestViewAndroid>(ViewAndroid::LayoutType::kNormal); |
| ReentrantDetachObserver destroy_observer( |
| base::BindLambdaForTesting([&]() { child_to_destroy.reset(); })); |
| |
| bool view2_detached = false; |
| ReentrantDetachObserver view2_observer( |
| base::BindLambdaForTesting([&]() { view2_detached = true; })); |
| view2_.AddObserver(&view2_observer); |
| |
| // Detaching view1_ triggers destruction of `child_to_destroy`. |
| child_to_destroy->AddChild(&view1_); |
| view1_.AddObserver(&destroy_observer); |
| root_.AddChild(child_to_destroy.get()); |
| root_.AddChild(&view2_); |
| |
| RemoveAllChildrenAttached(&root_); |
| |
| EXPECT_FALSE(child_to_destroy); |
| EXPECT_TRUE(view2_detached); |
| EXPECT_EQ(0u, root_.GetChildrenCountForTesting()); |
| EXPECT_EQ(nullptr, view1_.parent()); |
| EXPECT_EQ(nullptr, view2_.parent()); |
| |
| view1_.RemoveObserver(&destroy_observer); |
| view2_.RemoveObserver(&view2_observer); |
| } |
| |
| TEST(ViewAndroidTest, ChecksMultipleEventForwarders) { |
| ViewAndroid parent; |
| ViewAndroid child; |
| parent.GetEventForwarder(); |
| child.GetEventForwarder(); |
| EXPECT_DCHECK_DEATH(parent.AddChild(&child)); |
| |
| ViewAndroid parent2; |
| ViewAndroid child2; |
| parent2.GetEventForwarder(); |
| parent2.AddChild(&child2); |
| EXPECT_DCHECK_DEATH(child2.GetEventForwarder()); |
| |
| ViewAndroid window; |
| ViewAndroid wcv1, wcv2; |
| ViewAndroid rwhv1a, rwhv1b, rwhv2; |
| wcv1.GetEventForwarder(); |
| wcv2.GetEventForwarder(); |
| |
| window.AddChild(&wcv1); |
| wcv1.AddChild(&rwhv1a); |
| wcv1.AddChild(&rwhv1b); |
| |
| wcv2.AddChild(&rwhv2); |
| |
| // window should be able to add wcv2 since there's only one event forwarder |
| // in the path window - wcv2* - rwvh2 |
| window.AddChild(&wcv2); |
| |
| // Additional event forwarder will cause failure. |
| EXPECT_DCHECK_DEATH(rwhv2.GetEventForwarder()); |
| } |
| |
| class Observer : public ViewAndroidObserver { |
| public: |
| Observer() = default; |
| |
| void OnAttachedToWindow() override { attached_ = true; } |
| |
| void OnDetachedFromWindow() override { attached_ = false; } |
| |
| void OnViewAndroidDestroyed() override { destroyed_ = true; } |
| |
| void OnDelegateSet() override { delegate_set_ = true; } |
| |
| bool attached_ = false; |
| bool destroyed_ = false; |
| bool delegate_set_ = false; |
| }; |
| |
| TEST(ViewAndroidTest, Observer) { |
| std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window = |
| ui::WindowAndroid::CreateForTesting(); |
| |
| Observer top_observer; |
| Observer bottom_observer; |
| Observer top_observer2; |
| |
| { |
| ViewAndroid top; |
| ViewAndroid bottom; |
| |
| top.AddObserver(&top_observer); |
| bottom.AddObserver(&bottom_observer); |
| |
| top.AddChild(&bottom); |
| |
| EXPECT_FALSE(top_observer.attached_); |
| EXPECT_FALSE(bottom_observer.attached_); |
| |
| // Views in a tree all get notified of 'attached' event. |
| window->get()->AddChild(&top); |
| EXPECT_TRUE(top_observer.attached_); |
| EXPECT_TRUE(bottom_observer.attached_); |
| |
| // Observer, upon addition, does not get notified of the current |
| // attached state. |
| top.AddObserver(&top_observer2); |
| EXPECT_FALSE(top_observer2.attached_); |
| |
| bottom.RemoveFromParent(); |
| EXPECT_FALSE(bottom_observer.attached_); |
| top.RemoveFromParent(); |
| EXPECT_FALSE(top_observer.attached_); |
| |
| window->get()->AddChild(&top); |
| EXPECT_TRUE(top_observer.attached_); |
| |
| // View, upon addition to a tree in the attached state, should be notified. |
| top.AddChild(&bottom); |
| EXPECT_TRUE(bottom_observer.attached_); |
| |
| // Observer is notified about the new delegate. |
| TestViewAndroidDelegate view_android_delegate; |
| EXPECT_FALSE(top_observer.delegate_set_); |
| view_android_delegate.SetupTestDelegate(&top); |
| EXPECT_TRUE(top_observer.delegate_set_); |
| |
| // Views in a tree all get notified of 'detached' event. |
| top.RemoveFromParent(); |
| EXPECT_FALSE(top_observer.attached_); |
| EXPECT_FALSE(bottom_observer.attached_); |
| |
| // Remove the second top observer to test the destruction notification. |
| top.RemoveObserver(&top_observer2); |
| } |
| |
| EXPECT_TRUE(top_observer.destroyed_); |
| EXPECT_FALSE(top_observer2.destroyed_); |
| EXPECT_TRUE(bottom_observer.destroyed_); |
| } |
| |
| TEST(ViewAndroidTest, WindowAndroidDestructionDetachesAllViewAndroid) { |
| std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window = |
| ui::WindowAndroid::CreateForTesting(); |
| ViewAndroid top; |
| ViewAndroid bottom; |
| |
| Observer top_observer; |
| Observer bottom_observer; |
| |
| top.AddObserver(&top_observer); |
| bottom.AddObserver(&bottom_observer); |
| |
| window->get()->AddChild(&top); |
| top.AddChild(&bottom); |
| |
| EXPECT_TRUE(top_observer.attached_); |
| EXPECT_TRUE(bottom_observer.attached_); |
| |
| window.reset(); |
| |
| EXPECT_FALSE(top_observer.attached_); |
| EXPECT_FALSE(bottom_observer.attached_); |
| |
| top.RemoveObserver(&top_observer); |
| bottom.RemoveObserver(&bottom_observer); |
| } |
| |
| TEST(ViewAndroidTest, WindowAndroidColorProviderSource) { |
| std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window = |
| ui::WindowAndroid::CreateForTesting(); |
| ui::WindowAndroid* window_android = window->get(); |
| |
| // Test that color provider key holds the context from WindowAndroid. |
| ui::ColorProviderKey key = window_android->GetColorProviderKey(); |
| EXPECT_NE(key.context_hash, 0); |
| |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| base::android::ScopedJavaLocalRef<jobject> context = key.context.get(env); |
| EXPECT_FALSE(context.is_null()); |
| } |
| |
| TEST(ViewAndroidTest, |
| WindowAndroidColorProviderKeyIsContextAgnosticWithoutBridge) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(kAvoidPerContextColorProviders); |
| |
| // No ColorProviderBridge is registered in unit tests, so the key should not |
| // be scoped to the Context. |
| ASSERT_FALSE(ColorProviderBridge::HasInstance()); |
| |
| std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window = |
| ui::WindowAndroid::CreateForTesting(); |
| ui::ColorProviderKey key = window->get()->GetColorProviderKey(); |
| |
| EXPECT_EQ(key.context_hash, 0); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| EXPECT_TRUE(key.context.get(env).is_null()); |
| } |
| |
| TEST(ViewAndroidTest, WindowAndroidColorProviderKeyKeepsContextWithBridge) { |
| base::test::ScopedFeatureList feature_list; |
| feature_list.InitAndEnableFeature(kAvoidPerContextColorProviders); |
| |
| std::unique_ptr<ui::WindowAndroid::ScopedWindowAndroidForTesting> window = |
| ui::WindowAndroid::CreateForTesting(); |
| ui::ColorProviderKey key = window->get()->GetColorProviderKeyForTesting( |
| /*has_color_provider_bridge=*/true); |
| |
| EXPECT_NE(key.context_hash, 0); |
| JNIEnv* env = base::android::AttachCurrentThread(); |
| EXPECT_FALSE(key.context.get(env).is_null()); |
| } |
| |
| } // namespace ui |