• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.
4 
5 #include "ui/views/view_targeter.h"
6 
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"
13 
14 namespace views {
15 
16 // A class used to define a triangular-shaped hit test mask on a View.
17 class TestMaskedViewTargeter : public MaskedViewTargeter {
18  public:
TestMaskedViewTargeter(View * masked_view)19   explicit TestMaskedViewTargeter(View* masked_view)
20       : MaskedViewTargeter(masked_view) {}
~TestMaskedViewTargeter()21   virtual ~TestMaskedViewTargeter() {}
22 
23  private:
GetHitTestMask(const View * view,gfx::Path * mask) const24   virtual bool GetHitTestMask(const View* view,
25                               gfx::Path* mask) const OVERRIDE {
26     SkScalar w = SkIntToScalar(view->width());
27     SkScalar h = SkIntToScalar(view->height());
28 
29     // Create a triangular mask within the bounds of |view|.
30     mask->moveTo(w / 2, 0);
31     mask->lineTo(w, h);
32     mask->lineTo(0, h);
33     mask->close();
34 
35     return true;
36   }
37 
38   DISALLOW_COPY_AND_ASSIGN(TestMaskedViewTargeter);
39 };
40 
41 // A derived class of View used for testing purposes.
42 class TestingView : public View {
43  public:
TestingView()44   TestingView() : can_process_events_within_subtree_(true) {}
~TestingView()45   virtual ~TestingView() {}
46 
47   // Reset all test state.
Reset()48   void Reset() { can_process_events_within_subtree_ = true; }
49 
set_can_process_events_within_subtree(bool can_process)50   void set_can_process_events_within_subtree(bool can_process) {
51     can_process_events_within_subtree_ = can_process;
52   }
53 
54   // View:
CanProcessEventsWithinSubtree() const55   virtual bool CanProcessEventsWithinSubtree() const OVERRIDE {
56     return can_process_events_within_subtree_;
57   }
58 
59  private:
60   // Value to return from CanProcessEventsWithinSubtree().
61   bool can_process_events_within_subtree_;
62 
63   DISALLOW_COPY_AND_ASSIGN(TestingView);
64 };
65 
66 namespace test {
67 
68 typedef ViewsTestBase ViewTargeterTest;
69 
70 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
71 // and ViewTargeter::FindNextBestTarget() are implemented correctly
72 // for key events.
TEST_F(ViewTargeterTest,ViewTargeterForKeyEvents)73 TEST_F(ViewTargeterTest, ViewTargeterForKeyEvents) {
74   Widget widget;
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);
79 
80   View* content = new View;
81   View* child = new View;
82   View* grandchild = new View;
83 
84   widget.SetContentsView(content);
85   content->AddChildView(child);
86   child->AddChildView(grandchild);
87 
88   grandchild->SetFocusable(true);
89   grandchild->RequestFocus();
90 
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));
95 
96   ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_A, 0, true);
97 
98   // The focused view should be the initial target of the event.
99   ui::EventTarget* current_target = targeter->FindTargetForEvent(root_view,
100                                                                  &key_event);
101   EXPECT_EQ(grandchild, static_cast<View*>(current_target));
102 
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(),
112                                                 &key_event);
113   EXPECT_EQ(NULL, static_cast<View*>(current_target));
114 }
115 
116 // Verifies that the the functions ViewTargeter::FindTargetForEvent()
117 // and ViewTargeter::FindNextBestTarget() are implemented correctly
118 // for scroll events.
TEST_F(ViewTargeterTest,ViewTargeterForScrollEvents)119 TEST_F(ViewTargeterTest, ViewTargeterForScrollEvents) {
120   Widget widget;
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);
126 
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);
134 
135   widget.SetContentsView(content);
136   content->AddChildView(child);
137   child->AddChildView(grandchild);
138 
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));
143 
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,
147                          gfx::Point(60, 60),
148                          ui::EventTimeForNow(),
149                          0,
150                          0, 3,
151                          0, 3,
152                          2);
153   ui::EventTarget* current_target = targeter->FindTargetForEvent(root_view,
154                                                                  &scroll);
155   EXPECT_EQ(child, static_cast<View*>(current_target));
156 
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(),
164                                                 &scroll);
165   EXPECT_EQ(NULL, static_cast<View*>(current_target));
166 
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(),
174                            0,
175                            0, 3,
176                            0, 3,
177                            2);
178   current_target = targeter->FindTargetForEvent(root_view, &scroll);
179   EXPECT_EQ(content, static_cast<View*>(current_target));
180 }
181 
182 // Tests the basic functionality of the method
183 // ViewTargeter::SubtreeShouldBeExploredForEvent().
TEST_F(ViewTargeterTest,SubtreeShouldBeExploredForEvent)184 TEST_F(ViewTargeterTest, SubtreeShouldBeExploredForEvent) {
185   Widget widget;
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);
189   widget.Init(params);
190 
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));
195 
196   // The coordinates used for SetBounds() are in the parent coordinate space.
197   View v1, v2, v3;
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);
205 
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.
210 
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));
219 
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));
226 
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));
234 
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));
241 
242   // TODO(tdanderson): Move the hit-testing unit tests out of view_unittest
243   // and into here. See crbug.com/355425.
244 }
245 
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.
TEST_F(ViewTargeterTest,CanProcessEventsWithinSubtree)249 TEST_F(ViewTargeterTest, CanProcessEventsWithinSubtree) {
250   Widget widget;
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);
254   widget.Init(params);
255 
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));
260 
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);
269 
270   // Note that the coordinates used below are in the coordinate space of
271   // the root view.
272 
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);
278 
279   // If CanProcessEventsWithinSubtree() returns true for each view,
280   // |scroll| should be targeted at the deepest view in the hierarchy,
281   // which is |v3|.
282   ui::EventTarget* current_target =
283       targeter->FindTargetForEvent(root_view, &scroll);
284   EXPECT_EQ(&v3, current_target);
285 
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);
295 
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);
300   v3.Reset();
301   v2.set_can_process_events_within_subtree(false);
302   current_target = targeter->FindTargetForEvent(root_view, &scroll);
303   EXPECT_EQ(&v1, current_target);
304 
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);
309   v2.Reset();
310   v1.set_can_process_events_within_subtree(false);
311   current_target = targeter->FindTargetForEvent(root_view, &scroll);
312   EXPECT_EQ(root_view, current_target);
313 
314   // TODO(tdanderson): We should also test that targeting works correctly
315   //                   with gestures. See crbug.com/375822.
316 }
317 
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.
TEST_F(ViewTargeterTest,MaskedViewTargeter)321 TEST_F(ViewTargeterTest, MaskedViewTargeter) {
322   Widget widget;
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);
326   widget.Init(params);
327 
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));
332 
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);
341 
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));
348 
349   // Note that the coordinates used below are in the coordinate space of
350   // the root view.
351 
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(),
356                          0,
357                          0,
358                          3,
359                          0,
360                          3,
361                          2);
362   ui::EventTarget* current_target =
363       targeter->FindTargetForEvent(root_view, &scroll);
364   EXPECT_EQ(&masked_view, static_cast<View*>(current_target));
365 
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));
370 
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));
375 
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));
380 
381   // TODO(tdanderson): We should also test that targeting of masked views
382   //                   works correctly with gestures. See crbug.com/375822.
383 }
384 
385 }  // namespace test
386 }  // namespace views
387