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_targeter_delegate.h"
11 #include "ui/views/test/views_test_base.h"
12 #include "ui/views/view_targeter.h"
13 #include "ui/views/view_targeter_delegate.h"
14 #include "ui/views/widget/root_view.h"
18 // A derived class of View used for testing purposes.
19 class TestingView
: public View
, public ViewTargeterDelegate
{
21 TestingView() : can_process_events_within_subtree_(true) {}
22 virtual ~TestingView() {}
24 // Reset all test state.
25 void Reset() { can_process_events_within_subtree_
= true; }
27 void set_can_process_events_within_subtree(bool can_process
) {
28 can_process_events_within_subtree_
= can_process
;
31 // A call-through function to ViewTargeterDelegate::DoesIntersectRect().
32 bool TestDoesIntersectRect(const View
* target
, const gfx::Rect
& rect
) const {
33 return DoesIntersectRect(target
, rect
);
37 virtual bool CanProcessEventsWithinSubtree() const OVERRIDE
{
38 return can_process_events_within_subtree_
;
42 // Value to return from CanProcessEventsWithinSubtree().
43 bool can_process_events_within_subtree_
;
45 DISALLOW_COPY_AND_ASSIGN(TestingView
);
48 // A derived class of View having a triangular-shaped hit test mask.
49 class TestMaskedView
: public View
, public MaskedTargeterDelegate
{
52 virtual ~TestMaskedView() {}
54 // A call-through function to MaskedTargeterDelegate::DoesIntersectRect().
55 bool TestDoesIntersectRect(const View
* target
, const gfx::Rect
& rect
) const {
56 return DoesIntersectRect(target
, rect
);
60 // MaskedTargeterDelegate:
61 virtual bool GetHitTestMask(gfx::Path
* mask
) const OVERRIDE
{
63 SkScalar w
= SkIntToScalar(width());
64 SkScalar h
= SkIntToScalar(height());
66 // Create a triangular mask within the bounds of this View.
67 mask
->moveTo(w
/ 2, 0);
74 DISALLOW_COPY_AND_ASSIGN(TestMaskedView
);
79 // TODO(tdanderson): Clean up this test suite by moving common code/state into
80 // ViewTargeterTest and overriding SetUp(), TearDown(), etc.
81 // See crbug.com/355680.
82 class ViewTargeterTest
: public ViewsTestBase
{
85 virtual ~ViewTargeterTest() {}
87 void SetGestureHandler(internal::RootView
* root_view
, View
* handler
) {
88 root_view
->gesture_handler_
= handler
;
91 void SetAllowGestureEventRetargeting(internal::RootView
* root_view
,
93 root_view
->allow_gesture_event_retargeting_
= allow
;
97 DISALLOW_COPY_AND_ASSIGN(ViewTargeterTest
);
102 gfx::Point
ConvertPointToView(View
* view
, const gfx::Point
& p
) {
104 View::ConvertPointToTarget(view
->GetWidget()->GetRootView(), view
, &tmp
);
108 gfx::Rect
ConvertRectToView(View
* view
, const gfx::Rect
& r
) {
110 tmp
.set_origin(ConvertPointToView(view
, r
.origin()));
116 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
117 // and ViewTargeter::FindNextBestTarget() are implemented correctly
119 TEST_F(ViewTargeterTest
, ViewTargeterForKeyEvents
) {
121 Widget::InitParams init_params
=
122 CreateParams(Widget::InitParams::TYPE_POPUP
);
123 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
124 widget
.Init(init_params
);
126 View
* content
= new View
;
127 View
* child
= new View
;
128 View
* grandchild
= new View
;
130 widget
.SetContentsView(content
);
131 content
->AddChildView(child
);
132 child
->AddChildView(grandchild
);
134 grandchild
->SetFocusable(true);
135 grandchild
->RequestFocus();
137 internal::RootView
* root_view
=
138 static_cast<internal::RootView
*>(widget
.GetRootView());
139 ui::EventTargeter
* targeter
= root_view
->targeter();
141 ui::KeyEvent
key_event('a', ui::VKEY_A
, ui::EF_NONE
);
143 // The focused view should be the initial target of the event.
144 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
146 EXPECT_EQ(grandchild
, static_cast<View
*>(current_target
));
148 // Verify that FindNextBestTarget() will return the parent view of the
149 // argument (and NULL if the argument has no parent view).
150 current_target
= targeter
->FindNextBestTarget(grandchild
, &key_event
);
151 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
152 current_target
= targeter
->FindNextBestTarget(child
, &key_event
);
153 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
154 current_target
= targeter
->FindNextBestTarget(content
, &key_event
);
155 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
156 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
158 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
161 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
162 // and ViewTargeter::FindNextBestTarget() are implemented correctly
163 // for scroll events.
164 TEST_F(ViewTargeterTest
, ViewTargeterForScrollEvents
) {
166 Widget::InitParams init_params
=
167 CreateParams(Widget::InitParams::TYPE_POPUP
);
168 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
169 init_params
.bounds
= gfx::Rect(0, 0, 200, 200);
170 widget
.Init(init_params
);
172 // The coordinates used for SetBounds() are in the parent coordinate space.
173 View
* content
= new View
;
174 content
->SetBounds(0, 0, 100, 100);
175 View
* child
= new View
;
176 child
->SetBounds(50, 50, 20, 20);
177 View
* grandchild
= new View
;
178 grandchild
->SetBounds(0, 0, 5, 5);
180 widget
.SetContentsView(content
);
181 content
->AddChildView(child
);
182 child
->AddChildView(grandchild
);
184 internal::RootView
* root_view
=
185 static_cast<internal::RootView
*>(widget
.GetRootView());
186 ui::EventTargeter
* targeter
= root_view
->targeter();
188 // The event falls within the bounds of |child| and |content| but not
189 // |grandchild|, so |child| should be the initial target for the event.
190 ui::ScrollEvent
scroll(ui::ET_SCROLL
,
192 ui::EventTimeForNow(),
197 ui::EventTarget
* current_target
= targeter
->FindTargetForEvent(root_view
,
199 EXPECT_EQ(child
, static_cast<View
*>(current_target
));
201 // Verify that FindNextBestTarget() will return the parent view of the
202 // argument (and NULL if the argument has no parent view).
203 current_target
= targeter
->FindNextBestTarget(child
, &scroll
);
204 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
205 current_target
= targeter
->FindNextBestTarget(content
, &scroll
);
206 EXPECT_EQ(widget
.GetRootView(), static_cast<View
*>(current_target
));
207 current_target
= targeter
->FindNextBestTarget(widget
.GetRootView(),
209 EXPECT_EQ(NULL
, static_cast<View
*>(current_target
));
211 // The event falls outside of the original specified bounds of |content|,
212 // |child|, and |grandchild|. But since |content| is the contents view,
213 // and contents views are resized to fill the entire area of the root
214 // view, the event's initial target should still be |content|.
215 scroll
= ui::ScrollEvent(ui::ET_SCROLL
,
216 gfx::Point(150, 150),
217 ui::EventTimeForNow(),
222 current_target
= targeter
->FindTargetForEvent(root_view
, &scroll
);
223 EXPECT_EQ(content
, static_cast<View
*>(current_target
));
226 // Convenience to make constructing a GestureEvent simpler.
227 class GestureEventForTest
: public ui::GestureEvent
{
229 GestureEventForTest(ui::EventType type
, int x
, int y
)
234 ui::GestureEventDetails(type
, 0.0f
, 0.0f
)) {}
236 GestureEventForTest(ui::GestureEventDetails details
, int x
, int y
)
237 : GestureEvent(x
, y
, 0, base::TimeDelta(), details
) {}
240 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
241 // and ViewTargeter::FindNextBestTarget() are implemented correctly
242 // for gesture events.
243 TEST_F(ViewTargeterTest
, ViewTargeterForGestureEvents
) {
245 Widget::InitParams init_params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
246 init_params
.ownership
= Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
247 init_params
.bounds
= gfx::Rect(0, 0, 200, 200);
248 widget
.Init(init_params
);
250 // The coordinates used for SetBounds() are in the parent coordinate space.
251 View
* content
= new View
;
252 content
->SetBounds(0, 0, 100, 100);
253 View
* child
= new View
;
254 child
->SetBounds(50, 50, 20, 20);
255 View
* grandchild
= new View
;
256 grandchild
->SetBounds(0, 0, 5, 5);
258 widget
.SetContentsView(content
);
259 content
->AddChildView(child
);
260 child
->AddChildView(grandchild
);
262 internal::RootView
* root_view
=
263 static_cast<internal::RootView
*>(widget
.GetRootView());
264 ui::EventTargeter
* targeter
= root_view
->targeter();
266 // Define a GESTURE_TAP and a GESTURE_SCROLL_BEGIN.
267 gfx::Rect
bounding_box(gfx::Point(46, 46), gfx::Size(8, 8));
268 gfx::Point
center_point(bounding_box
.CenterPoint());
269 ui::GestureEventDetails
details(ui::ET_GESTURE_TAP
, 0.0f
, 0.0f
);
270 details
.set_bounding_box(bounding_box
);
271 GestureEventForTest
tap(details
, center_point
.x(), center_point
.y());
272 details
= ui::GestureEventDetails(ui::ET_GESTURE_SCROLL_BEGIN
, 0.0f
, 0.0f
);
273 details
.set_bounding_box(bounding_box
);
274 GestureEventForTest
scroll_begin(details
, center_point
.x(), center_point
.y());
276 // Assume that the view currently handling gestures has been set as
277 // |grandchild| by a previous gesture event. Thus subsequent gesture events
278 // should be initially targeted to |grandchild|, and re-targeting should
279 // be prohibited for all gesture event types except for GESTURE_SCROLL_BEGIN
280 // (which should be re-targeted to the parent of |grandchild|).
281 SetAllowGestureEventRetargeting(root_view
, false);
282 SetGestureHandler(root_view
, grandchild
);
283 EXPECT_EQ(grandchild
, targeter
->FindTargetForEvent(root_view
, &tap
));
284 EXPECT_EQ(NULL
, targeter
->FindNextBestTarget(grandchild
, &tap
));
285 EXPECT_EQ(grandchild
, targeter
->FindTargetForEvent(root_view
, &scroll_begin
));
286 EXPECT_EQ(child
, targeter
->FindNextBestTarget(grandchild
, &scroll_begin
));
288 // Assume that the view currently handling gestures is still set as
289 // |grandchild|, but this was not done by a previous gesture. Thus we are
290 // in the process of finding the View to which subsequent gestures will be
291 // dispatched, so all gesture events should be re-targeted up the ancestor
293 SetAllowGestureEventRetargeting(root_view
, true);
294 EXPECT_EQ(child
, targeter
->FindNextBestTarget(grandchild
, &tap
));
295 EXPECT_EQ(child
, targeter
->FindNextBestTarget(grandchild
, &scroll_begin
));
297 // Assume that the default gesture handler was set by the previous gesture,
298 // but that this handler is currently NULL. No gesture events should be
299 // re-targeted in this case (regardless of the view that is passed in to
300 // FindNextBestTarget() as the previous target).
301 SetGestureHandler(root_view
, NULL
);
302 SetAllowGestureEventRetargeting(root_view
, false);
303 EXPECT_EQ(NULL
, targeter
->FindNextBestTarget(child
, &tap
));
304 EXPECT_EQ(NULL
, targeter
->FindNextBestTarget(NULL
, &tap
));
305 EXPECT_EQ(NULL
, targeter
->FindNextBestTarget(content
, &scroll_begin
));
307 // If no default gesture handler is currently set, targeting should be
308 // performed using the location of the gesture event.
309 SetAllowGestureEventRetargeting(root_view
, true);
310 EXPECT_EQ(grandchild
, targeter
->FindTargetForEvent(root_view
, &tap
));
311 EXPECT_EQ(grandchild
, targeter
->FindTargetForEvent(root_view
, &scroll_begin
));
314 // Tests that the functions ViewTargeterDelegate::DoesIntersectRect()
315 // and MaskedTargeterDelegate::DoesIntersectRect() work as intended when
316 // called on views which are derived from ViewTargeterDelegate.
317 // Also verifies that ViewTargeterDelegate::DoesIntersectRect() can
318 // be called from the ViewTargeter installed on RootView.
319 TEST_F(ViewTargeterTest
, DoesIntersectRect
) {
321 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
322 params
.ownership
= views::Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET
;
323 params
.bounds
= gfx::Rect(0, 0, 650, 650);
326 internal::RootView
* root_view
=
327 static_cast<internal::RootView
*>(widget
.GetRootView());
328 ViewTargeter
* view_targeter
= root_view
->targeter();
330 // The coordinates used for SetBounds() are in the parent coordinate space.
332 TestMaskedView v1
, v3
;
333 v1
.SetBounds(0, 0, 200, 200);
334 v2
.SetBounds(300, 0, 300, 300);
335 v3
.SetBounds(0, 0, 100, 100);
336 root_view
->AddChildView(&v1
);
337 root_view
->AddChildView(&v2
);
338 v2
.AddChildView(&v3
);
340 // The coordinates used below are in the local coordinate space of the
341 // view that is passed in as an argument.
343 // Hit tests against |v1|, which has a hit test mask.
344 EXPECT_TRUE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(0, 0, 200, 200)));
345 EXPECT_TRUE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(-10, -10, 110, 12)));
346 EXPECT_TRUE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(112, 142, 1, 1)));
347 EXPECT_FALSE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(0, 0, 20, 20)));
348 EXPECT_FALSE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(-10, -10, 90, 12)));
349 EXPECT_FALSE(v1
.TestDoesIntersectRect(&v1
, gfx::Rect(150, 49, 1, 1)));
351 // Hit tests against |v2|, which does not have a hit test mask.
352 EXPECT_TRUE(v2
.TestDoesIntersectRect(&v2
, gfx::Rect(0, 0, 200, 200)));
353 EXPECT_TRUE(v2
.TestDoesIntersectRect(&v2
, gfx::Rect(-10, 250, 60, 60)));
354 EXPECT_TRUE(v2
.TestDoesIntersectRect(&v2
, gfx::Rect(250, 250, 1, 1)));
355 EXPECT_FALSE(v2
.TestDoesIntersectRect(&v2
, gfx::Rect(-10, 250, 7, 7)));
356 EXPECT_FALSE(v2
.TestDoesIntersectRect(&v2
, gfx::Rect(-1, -1, 1, 1)));
358 // Hit tests against |v3|, which has a hit test mask and is a child of |v2|.
359 EXPECT_TRUE(v3
.TestDoesIntersectRect(&v3
, gfx::Rect(0, 0, 50, 50)));
360 EXPECT_TRUE(v3
.TestDoesIntersectRect(&v3
, gfx::Rect(90, 90, 1, 1)));
361 EXPECT_FALSE(v3
.TestDoesIntersectRect(&v3
, gfx::Rect(10, 125, 50, 50)));
362 EXPECT_FALSE(v3
.TestDoesIntersectRect(&v3
, gfx::Rect(110, 110, 1, 1)));
364 // Verify that hit-testing is performed correctly when using the
365 // call-through function ViewTargeter::DoesIntersectRect().
366 EXPECT_TRUE(view_targeter
->DoesIntersectRect(root_view
,
367 gfx::Rect(0, 0, 50, 50)));
368 EXPECT_FALSE(view_targeter
->DoesIntersectRect(root_view
,
369 gfx::Rect(-20, -20, 10, 10)));
372 // Tests that calls made directly on the hit-testing methods in View
373 // (HitTestPoint(), HitTestRect(), etc.) return the correct values.
374 TEST_F(ViewTargeterTest
, HitTestCallsOnView
) {
375 // The coordinates in this test are in the coordinate space of the root view.
376 Widget
* widget
= new Widget
;
377 Widget::InitParams params
= CreateParams(Widget::InitParams::TYPE_POPUP
);
378 widget
->Init(params
);
379 View
* root_view
= widget
->GetRootView();
380 root_view
->SetBoundsRect(gfx::Rect(0, 0, 500, 500));
382 // |v1| has no hit test mask. No ViewTargeter is installed on |v1|, which
383 // means that View::HitTestRect() will call into the targeter installed on
384 // the root view instead when we hit test against |v1|.
385 gfx::Rect v1_bounds
= gfx::Rect(0, 0, 100, 100);
386 TestingView
* v1
= new TestingView();
387 v1
->SetBoundsRect(v1_bounds
);
388 root_view
->AddChildView(v1
);
390 // |v2| has a triangular hit test mask. Install a ViewTargeter on |v2| which
391 // will be called into by View::HitTestRect().
392 gfx::Rect v2_bounds
= gfx::Rect(105, 0, 100, 100);
393 TestMaskedView
* v2
= new TestMaskedView();
394 v2
->SetBoundsRect(v2_bounds
);
395 root_view
->AddChildView(v2
);
396 ViewTargeter
* view_targeter
= new ViewTargeter(v2
);
397 v2
->SetEventTargeter(make_scoped_ptr(view_targeter
));
399 gfx::Point v1_centerpoint
= v1_bounds
.CenterPoint();
400 gfx::Point v2_centerpoint
= v2_bounds
.CenterPoint();
401 gfx::Point v1_origin
= v1_bounds
.origin();
402 gfx::Point v2_origin
= v2_bounds
.origin();
403 gfx::Rect
r1(10, 10, 110, 15);
404 gfx::Rect
r2(106, 1, 98, 98);
405 gfx::Rect
r3(0, 0, 300, 300);
406 gfx::Rect
r4(115, 342, 200, 10);
408 // Test calls into View::HitTestPoint().
409 EXPECT_TRUE(v1
->HitTestPoint(ConvertPointToView(v1
, v1_centerpoint
)));
410 EXPECT_TRUE(v2
->HitTestPoint(ConvertPointToView(v2
, v2_centerpoint
)));
412 EXPECT_TRUE(v1
->HitTestPoint(ConvertPointToView(v1
, v1_origin
)));
413 EXPECT_FALSE(v2
->HitTestPoint(ConvertPointToView(v2
, v2_origin
)));
415 // Test calls into View::HitTestRect().
416 EXPECT_TRUE(v1
->HitTestRect(ConvertRectToView(v1
, r1
)));
417 EXPECT_FALSE(v2
->HitTestRect(ConvertRectToView(v2
, r1
)));
419 EXPECT_FALSE(v1
->HitTestRect(ConvertRectToView(v1
, r2
)));
420 EXPECT_TRUE(v2
->HitTestRect(ConvertRectToView(v2
, r2
)));
422 EXPECT_TRUE(v1
->HitTestRect(ConvertRectToView(v1
, r3
)));
423 EXPECT_TRUE(v2
->HitTestRect(ConvertRectToView(v2
, r3
)));
425 EXPECT_FALSE(v1
->HitTestRect(ConvertRectToView(v1
, r4
)));
426 EXPECT_FALSE(v2
->HitTestRect(ConvertRectToView(v2
, r4
)));
428 // Test calls into View::GetEventHandlerForPoint().
429 EXPECT_EQ(v1
, root_view
->GetEventHandlerForPoint(v1_centerpoint
));
430 EXPECT_EQ(v2
, root_view
->GetEventHandlerForPoint(v2_centerpoint
));
432 EXPECT_EQ(v1
, root_view
->GetEventHandlerForPoint(v1_origin
));
433 EXPECT_EQ(root_view
, root_view
->GetEventHandlerForPoint(v2_origin
));
435 // Test calls into View::GetTooltipHandlerForPoint().
436 EXPECT_EQ(v1
, root_view
->GetTooltipHandlerForPoint(v1_centerpoint
));
437 EXPECT_EQ(v2
, root_view
->GetTooltipHandlerForPoint(v2_centerpoint
));
439 EXPECT_EQ(v1
, root_view
->GetTooltipHandlerForPoint(v1_origin
));
440 EXPECT_EQ(root_view
, root_view
->GetTooltipHandlerForPoint(v2_origin
));
442 EXPECT_FALSE(v1
->GetTooltipHandlerForPoint(v2_origin
));