1 // Copyright 2014 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/view_targeter.h"
7 #include "ui/events/event_targeter.h"
8 #include "ui/events/event_utils.h"
9 #include "ui/gfx/path.h"
10 #include "ui/views/masked_view_targeter.h"
11 #include "ui/views/test/views_test_base.h"
12 #include "ui/views/widget/root_view.h"
16 // A class used to define a triangular-shaped hit test mask on a View.
17 class TestMaskedViewTargeter
: public MaskedViewTargeter
{
19 explicit TestMaskedViewTargeter(View
* masked_view
)
20 : MaskedViewTargeter(masked_view
) {}
21 virtual ~TestMaskedViewTargeter() {}
24 virtual bool GetHitTestMask(const View
* view
,
25 gfx::Path
* mask
) const OVERRIDE
{
26 SkScalar w
= SkIntToScalar(view
->width());
27 SkScalar h
= SkIntToScalar(view
->height());
29 // Create a triangular mask within the bounds of |view|.
30 mask
->moveTo(w
/ 2, 0);
38 DISALLOW_COPY_AND_ASSIGN(TestMaskedViewTargeter
);
41 // A derived class of View used for testing purposes.
42 class TestingView
: public View
{
44 TestingView() : can_process_events_within_subtree_(true) {}
45 virtual ~TestingView() {}
47 // Reset all test state.
48 void Reset() { can_process_events_within_subtree_
= true; }
50 void set_can_process_events_within_subtree(bool can_process
) {
51 can_process_events_within_subtree_
= can_process
;
55 virtual bool CanProcessEventsWithinSubtree() const OVERRIDE
{
56 return can_process_events_within_subtree_
;
60 // Value to return from CanProcessEventsWithinSubtree().
61 bool can_process_events_within_subtree_
;
63 DISALLOW_COPY_AND_ASSIGN(TestingView
);
68 typedef ViewsTestBase ViewTargeterTest
;
70 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
71 // and ViewTargeter::FindNextBestTarget() are implemented correctly
73 TEST_F(ViewTargeterTest
, ViewTargeterForKeyEvents
) {
75 Widget::InitParams init_params
=
76 CreateParams(Widget::InitParams::TYPE_POPUP
);
77 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
78 widget
.Init(init_params
);
80 View
* content
= new View
;
81 View
* child
= new View
;
82 View
* grandchild
= new View
;
84 widget
.SetContentsView(content
);
85 content
->AddChildView(child
);
86 child
->AddChildView(grandchild
);
88 grandchild
->SetFocusable(true);
89 grandchild
->RequestFocus();
91 ui::EventTargeter
* targeter
= new ViewTargeter();
92 internal::RootView
* root_view
=
93 static_cast<internal::RootView
*>(widget
.GetRootView());
94 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
96 ui::KeyEvent
key_event(ui::ET_KEY_PRESSED
, ui::VKEY_A
, 0, true);
98 // The focused view should be the initial target of the event.
99 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
101 EXPECT_EQ(grandchild
, static_cast<View
*>(current_target
));
103 // Verify that FindNextBestTarget() will return the parent view of the
104 // argument (and NULL if the argument has no parent view).
105 current_target
= targeter
->FindNextBestTarget(grandchild
, &key_event
);
106 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
107 current_target
= targeter
->FindNextBestTarget(child
, &key_event
);
108 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
109 current_target
= targeter
->FindNextBestTarget(content
, &key_event
);
110 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
111 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
113 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
116 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
117 // and ViewTargeter::FindNextBestTarget() are implemented correctly
118 // for scroll events.
119 TEST_F(ViewTargeterTest
, ViewTargeterForScrollEvents
) {
121 Widget::InitParams init_params
=
122 CreateParams(Widget::InitParams::TYPE_POPUP
);
123 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
124 init_params
.bounds
= gfx::Rect(0, 0, 200, 200);
125 widget
.Init(init_params
);
127 // The coordinates used for SetBounds() are in the parent coordinate space.
128 View
* content
= new View
;
129 content
->SetBounds(0, 0, 100, 100);
130 View
* child
= new View
;
131 child
->SetBounds(50, 50, 20, 20);
132 View
* grandchild
= new View
;
133 grandchild
->SetBounds(0, 0, 5, 5);
135 widget
.SetContentsView(content
);
136 content
->AddChildView(child
);
137 child
->AddChildView(grandchild
);
139 ui::EventTargeter
* targeter
= new ViewTargeter();
140 internal::RootView
* root_view
=
141 static_cast<internal::RootView
*>(widget
.GetRootView());
142 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
144 // The event falls within the bounds of |child| and |content| but not
145 // |grandchild|, so |child| should be the initial target for the event.
146 ui::ScrollEvent
scroll(ui::ET_SCROLL
,
148 ui::EventTimeForNow(),
153 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
155 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
157 // Verify that FindNextBestTarget() will return the parent view of the
158 // argument (and NULL if the argument has no parent view).
159 current_target
= targeter
->FindNextBestTarget(child
, &scroll
);
160 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
161 current_target
= targeter
->FindNextBestTarget(content
, &scroll
);
162 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
163 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
165 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
167 // The event falls outside of the original specified bounds of |content|,
168 // |child|, and |grandchild|. But since |content| is the contents view,
169 // and contents views are resized to fill the entire area of the root
170 // view, the event's initial target should still be |content|.
171 scroll
= ui::ScrollEvent(ui::ET_SCROLL
,
172 gfx::Point(150, 150),
173 ui::EventTimeForNow(),
178 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
179 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
182 // Tests the basic functionality of the method
183 // ViewTargeter::SubtreeShouldBeExploredForEvent().
184 TEST_F(ViewTargeterTest
, SubtreeShouldBeExploredForEvent
) {
186 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
187 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
188 params
.bounds
= gfx::Rect(0, 0, 650, 650);
191 ui::EventTargeter
* targeter
= new ViewTargeter();
192 internal::RootView
* root_view
=
193 static_cast<internal::RootView
*>(widget
.GetRootView());
194 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
196 // The coordinates used for SetBounds() are in the parent coordinate space.
198 v1
.SetBounds(0, 0, 300, 300);
199 v2
.SetBounds(100, 100, 100, 100);
200 v3
.SetBounds(0, 0, 10, 10);
201 v3
.SetVisible(false);
202 root_view
->AddChildView(&v1
);
203 v1
.AddChildView(&v2
);
204 v2
.AddChildView(&v3
);
206 // Note that the coordinates used below are in |v1|'s coordinate space,
207 // and that SubtreeShouldBeExploredForEvent() expects the event location
208 // to be in the coordinate space of the target's parent. |v1| and
209 // its parent share a common coordinate space.
211 // Event located within |v1| only.
212 gfx::Point
point(10, 10);
213 ui::MouseEvent
event(ui::ET_MOUSE_PRESSED
, point
, point
,
214 ui::EF_LEFT_MOUSE_BUTTON
, ui::EF_LEFT_MOUSE_BUTTON
);
215 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
216 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
217 v1
.ConvertEventToTarget(&v2
, &event
);
218 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
220 // Event located within |v1| and |v2| only.
221 event
.set_location(gfx::Point(150, 150));
222 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
223 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
224 v1
.ConvertEventToTarget(&v2
, &event
);
225 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
227 // Event located within |v1|, |v2|, and |v3|. Note that |v3| is not
228 // visible, so it cannot handle the event.
229 event
.set_location(gfx::Point(105, 105));
230 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
231 EXPECT_TRUE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
232 v1
.ConvertEventToTarget(&v2
, &event
);
233 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
235 // Event located outside the bounds of all views.
236 event
.set_location(gfx::Point(400, 400));
237 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v1
, event
));
238 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v2
, event
));
239 v1
.ConvertEventToTarget(&v2
, &event
);
240 EXPECT_FALSE(targeter
->SubtreeShouldBeExploredForEvent(&v3
, event
));
242 // TODO(tdanderson): Move the hit-testing unit tests out of view_unittest
243 // and into here. See crbug.com/355425.
246 // Tests that FindTargetForEvent() returns the correct target when some
247 // views in the view tree return false when CanProcessEventsWithinSubtree()
248 // is called on them.
249 TEST_F(ViewTargeterTest
, CanProcessEventsWithinSubtree
) {
251 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
252 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
253 params
.bounds
= gfx::Rect(0, 0, 650, 650);
256 ui::EventTargeter
* targeter
= new ViewTargeter();
257 internal::RootView
* root_view
=
258 static_cast<internal::RootView
*>(widget
.GetRootView());
259 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
261 // The coordinates used for SetBounds() are in the parent coordinate space.
262 TestingView v1
, v2
, v3
;
263 v1
.SetBounds(0, 0, 300, 300);
264 v2
.SetBounds(100, 100, 100, 100);
265 v3
.SetBounds(0, 0, 10, 10);
266 root_view
->AddChildView(&v1
);
267 v1
.AddChildView(&v2
);
268 v2
.AddChildView(&v3
);
270 // Note that the coordinates used below are in the coordinate space of
273 // Define |scroll| to be (105, 105) (in the coordinate space of the root
274 // view). This is located within all of |v1|, |v2|, and |v3|.
275 gfx::Point
scroll_point(105, 105);
276 ui::ScrollEvent
scroll(
277 ui::ET_SCROLL
, scroll_point
, ui::EventTimeForNow(), 0, 0, 3, 0, 3, 2);
279 // If CanProcessEventsWithinSubtree() returns true for each view,
280 // |scroll| should be targeted at the deepest view in the hierarchy,
282 ui::EventTarget
* current_target
=
283 targeter
->FindTargetForEvent(root_view
, &scroll
);
284 EXPECT_EQ(&v3
, current_target
);
286 // If CanProcessEventsWithinSubtree() returns |false| when called
287 // on |v3|, then |v3| cannot be the target of |scroll| (this should
288 // instead be |v2|). Note we need to reset the location of |scroll|
289 // because it may have been mutated by the previous call to
290 // FindTargetForEvent().
291 scroll
.set_location(scroll_point
);
292 v3
.set_can_process_events_within_subtree(false);
293 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
294 EXPECT_EQ(&v2
, current_target
);
296 // If CanProcessEventsWithinSubtree() returns |false| when called
297 // on |v2|, then neither |v2| nor |v3| can be the target of |scroll|
298 // (this should instead be |v1|).
299 scroll
.set_location(scroll_point
);
301 v2
.set_can_process_events_within_subtree(false);
302 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
303 EXPECT_EQ(&v1
, current_target
);
305 // If CanProcessEventsWithinSubtree() returns |false| when called
306 // on |v1|, then none of |v1|, |v2| or |v3| can be the target of |scroll|
307 // (this should instead be the root view itself).
308 scroll
.set_location(scroll_point
);
310 v1
.set_can_process_events_within_subtree(false);
311 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
312 EXPECT_EQ(root_view
, current_target
);
314 // TODO(tdanderson): We should also test that targeting works correctly
315 // with gestures. See crbug.com/375822.
318 // Tests that FindTargetForEvent() returns the correct target when some
319 // views in the view tree have a MaskedViewTargeter installed, i.e.,
320 // they have a custom-shaped hit test mask.
321 TEST_F(ViewTargeterTest
, MaskedViewTargeter
) {
323 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
324 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
325 params
.bounds
= gfx::Rect(0, 0, 650, 650);
328 ui::EventTargeter
* targeter
= new ViewTargeter();
329 internal::RootView
* root_view
=
330 static_cast<internal::RootView
*>(widget
.GetRootView());
331 root_view
->SetEventTargeter(make_scoped_ptr(targeter
));
333 // The coordinates used for SetBounds() are in the parent coordinate space.
334 View masked_view
, unmasked_view
, masked_child
;
335 masked_view
.SetBounds(0, 0, 200, 200);
336 unmasked_view
.SetBounds(300, 0, 300, 300);
337 masked_child
.SetBounds(0, 0, 100, 100);
338 root_view
->AddChildView(&masked_view
);
339 root_view
->AddChildView(&unmasked_view
);
340 unmasked_view
.AddChildView(&masked_child
);
342 // Install event targeters of type TestMaskedViewTargeter on the two masked
343 // views to define their hit test masks.
344 ui::EventTargeter
* masked_targeter
= new TestMaskedViewTargeter(&masked_view
);
345 masked_view
.SetEventTargeter(make_scoped_ptr(masked_targeter
));
346 masked_targeter
= new TestMaskedViewTargeter(&masked_child
);
347 masked_child
.SetEventTargeter(make_scoped_ptr(masked_targeter
));
349 // Note that the coordinates used below are in the coordinate space of
352 // Event located within the hit test mask of |masked_view|.
353 ui::ScrollEvent
scroll(ui::ET_SCROLL
,
354 gfx::Point(100, 190),
355 ui::EventTimeForNow(),
362 ui::EventTarget
* current_target
=
363 targeter
->FindTargetForEvent(root_view
, &scroll
);
364 EXPECT_EQ(&masked_view
, static_cast<View
*>(current_target
));
366 // Event located outside the hit test mask of |masked_view|.
367 scroll
.set_location(gfx::Point(10, 10));
368 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
369 EXPECT_EQ(root_view
, static_cast<View
*>(current_target
));
371 // Event located within the hit test mask of |masked_child|.
372 scroll
.set_location(gfx::Point(350, 3));
373 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
374 EXPECT_EQ(&masked_child
, static_cast<View
*>(current_target
));
376 // Event located within the hit test mask of |masked_child|.
377 scroll
.set_location(gfx::Point(300, 12));
378 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
379 EXPECT_EQ(&unmasked_view
, static_cast<View
*>(current_target
));
381 // TODO(tdanderson): We should also test that targeting of masked views
382 // works correctly with gestures. See crbug.com/375822.