1 // Copyright 2013 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/widget/root_view.h"
6 
7 #include <memory>
8 #include <utility>
9 
10 #include "base/macros.h"
11 #include "base/memory/ptr_util.h"
12 #include "build/build_config.h"
13 #include "ui/accessibility/ax_enums.mojom.h"
14 #include "ui/accessibility/ax_node_data.h"
15 #include "ui/events/event_utils.h"
16 #include "ui/events/keycodes/dom/dom_code.h"
17 #include "ui/views/context_menu_controller.h"
18 #include "ui/views/test/test_views.h"
19 #include "ui/views/test/views_test_base.h"
20 #include "ui/views/view_targeter.h"
21 #include "ui/views/widget/widget_deletion_observer.h"
22 #include "ui/views/window/dialog_delegate.h"
23 
24 namespace views {
25 namespace test {
26 
27 using RootViewTest = ViewsTestBase;
28 
29 class DeleteOnKeyEventView : public View {
30  public:
DeleteOnKeyEventView(bool * set_on_key)31   explicit DeleteOnKeyEventView(bool* set_on_key) : set_on_key_(set_on_key) {}
32   ~DeleteOnKeyEventView() override = default;
33 
OnKeyPressed(const ui::KeyEvent & event)34   bool OnKeyPressed(const ui::KeyEvent& event) override {
35     *set_on_key_ = true;
36     delete this;
37     return true;
38   }
39 
40  private:
41   // Set to true in OnKeyPressed().
42   bool* set_on_key_;
43 
44   DISALLOW_COPY_AND_ASSIGN(DeleteOnKeyEventView);
45 };
46 
47 // Verifies deleting a View in OnKeyPressed() doesn't crash and that the
48 // target is marked as destroyed in the returned EventDispatchDetails.
TEST_F(RootViewTest,DeleteViewDuringKeyEventDispatch)49 TEST_F(RootViewTest, DeleteViewDuringKeyEventDispatch) {
50   Widget widget;
51   Widget::InitParams init_params =
52       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
53   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
54   widget.Init(std::move(init_params));
55   widget.Show();
56 
57   bool got_key_event = false;
58 
59   View* content = widget.SetContentsView(std::make_unique<View>());
60 
61   View* child = new DeleteOnKeyEventView(&got_key_event);
62   content->AddChildView(child);
63 
64   // Give focus to |child| so that it will be the target of the key event.
65   child->SetFocusBehavior(View::FocusBehavior::ALWAYS);
66   child->RequestFocus();
67 
68   internal::RootView* root_view =
69       static_cast<internal::RootView*>(widget.GetRootView());
70   ViewTargeter* view_targeter = new ViewTargeter(root_view);
71   root_view->SetEventTargeter(base::WrapUnique(view_targeter));
72 
73   ui::KeyEvent key_event(ui::ET_KEY_PRESSED, ui::VKEY_ESCAPE, ui::EF_NONE);
74   ui::EventDispatchDetails details = root_view->OnEventFromSource(&key_event);
75   EXPECT_TRUE(details.target_destroyed);
76   EXPECT_FALSE(details.dispatcher_destroyed);
77   EXPECT_TRUE(got_key_event);
78 }
79 
80 // Tracks whether a context menu is shown.
81 class TestContextMenuController : public ContextMenuController {
82  public:
83   TestContextMenuController() = default;
84   ~TestContextMenuController() override = default;
85 
show_context_menu_calls() const86   int show_context_menu_calls() const { return show_context_menu_calls_; }
menu_source_view() const87   View* menu_source_view() const { return menu_source_view_; }
menu_source_type() const88   ui::MenuSourceType menu_source_type() const { return menu_source_type_; }
89 
Reset()90   void Reset() {
91     show_context_menu_calls_ = 0;
92     menu_source_view_ = nullptr;
93     menu_source_type_ = ui::MENU_SOURCE_NONE;
94   }
95 
96   // ContextMenuController:
ShowContextMenuForViewImpl(View * source,const gfx::Point & point,ui::MenuSourceType source_type)97   void ShowContextMenuForViewImpl(View* source,
98                                   const gfx::Point& point,
99                                   ui::MenuSourceType source_type) override {
100     show_context_menu_calls_++;
101     menu_source_view_ = source;
102     menu_source_type_ = source_type;
103   }
104 
105  private:
106   int show_context_menu_calls_ = 0;
107   View* menu_source_view_ = nullptr;
108   ui::MenuSourceType menu_source_type_ = ui::MENU_SOURCE_NONE;
109 
110   DISALLOW_COPY_AND_ASSIGN(TestContextMenuController);
111 };
112 
113 // Tests that context menus are shown for certain key events (Shift+F10
114 // and VKEY_APPS) by the pre-target handler installed on RootView.
TEST_F(RootViewTest,ContextMenuFromKeyEvent)115 TEST_F(RootViewTest, ContextMenuFromKeyEvent) {
116   // This behavior is intentionally unsupported on macOS.
117 #if !defined(OS_APPLE)
118   Widget widget;
119   Widget::InitParams init_params =
120       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
121   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
122   widget.Init(std::move(init_params));
123   widget.Show();
124   internal::RootView* root_view =
125       static_cast<internal::RootView*>(widget.GetRootView());
126 
127   TestContextMenuController controller;
128   View* focused_view = widget.SetContentsView(std::make_unique<View>());
129   focused_view->set_context_menu_controller(&controller);
130   focused_view->SetFocusBehavior(View::FocusBehavior::ALWAYS);
131   focused_view->RequestFocus();
132 
133   // No context menu should be shown for a keypress of 'A'.
134   ui::KeyEvent nomenu_key_event('a', ui::VKEY_A, ui::DomCode::NONE,
135                                 ui::EF_NONE);
136   ui::EventDispatchDetails details =
137       root_view->OnEventFromSource(&nomenu_key_event);
138   EXPECT_FALSE(details.target_destroyed);
139   EXPECT_FALSE(details.dispatcher_destroyed);
140   EXPECT_EQ(0, controller.show_context_menu_calls());
141   EXPECT_EQ(nullptr, controller.menu_source_view());
142   EXPECT_EQ(ui::MENU_SOURCE_NONE, controller.menu_source_type());
143   controller.Reset();
144 
145   // A context menu should be shown for a keypress of Shift+F10.
146   ui::KeyEvent menu_key_event(ui::ET_KEY_PRESSED, ui::VKEY_F10,
147                               ui::EF_SHIFT_DOWN);
148   details = root_view->OnEventFromSource(&menu_key_event);
149   EXPECT_FALSE(details.target_destroyed);
150   EXPECT_FALSE(details.dispatcher_destroyed);
151   EXPECT_EQ(1, controller.show_context_menu_calls());
152   EXPECT_EQ(focused_view, controller.menu_source_view());
153   EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD, controller.menu_source_type());
154   controller.Reset();
155 
156   // A context menu should be shown for a keypress of VKEY_APPS.
157   ui::KeyEvent menu_key_event2(ui::ET_KEY_PRESSED, ui::VKEY_APPS, ui::EF_NONE);
158   details = root_view->OnEventFromSource(&menu_key_event2);
159   EXPECT_FALSE(details.target_destroyed);
160   EXPECT_FALSE(details.dispatcher_destroyed);
161   EXPECT_EQ(1, controller.show_context_menu_calls());
162   EXPECT_EQ(focused_view, controller.menu_source_view());
163   EXPECT_EQ(ui::MENU_SOURCE_KEYBOARD, controller.menu_source_type());
164   controller.Reset();
165 #endif
166 }
167 
168 // View which handles all gesture events.
169 class GestureHandlingView : public View {
170  public:
171   GestureHandlingView() = default;
172 
173   ~GestureHandlingView() override = default;
174 
OnGestureEvent(ui::GestureEvent * event)175   void OnGestureEvent(ui::GestureEvent* event) override { event->SetHandled(); }
176 
177  private:
178   DISALLOW_COPY_AND_ASSIGN(GestureHandlingView);
179 };
180 
181 // Tests that context menus are shown for long press by the post-target handler
182 // installed on the RootView only if the event is targetted at a view which can
183 // show a context menu.
TEST_F(RootViewTest,ContextMenuFromLongPress)184 TEST_F(RootViewTest, ContextMenuFromLongPress) {
185   Widget widget;
186   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
187   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
188   init_params.bounds = gfx::Rect(100, 100);
189   widget.Init(std::move(init_params));
190   internal::RootView* root_view =
191       static_cast<internal::RootView*>(widget.GetRootView());
192 
193   // Create a view capable of showing the context menu with two children one of
194   // which handles all gesture events (e.g. a button).
195   TestContextMenuController controller;
196   View* parent_view = widget.SetContentsView(std::make_unique<View>());
197   parent_view->set_context_menu_controller(&controller);
198 
199   View* gesture_handling_child_view = new GestureHandlingView;
200   gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
201   parent_view->AddChildView(gesture_handling_child_view);
202 
203   View* other_child_view = new View;
204   other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
205   parent_view->AddChildView(other_child_view);
206 
207   // |parent_view| should not show a context menu as a result of a long press on
208   // |gesture_handling_child_view|.
209   ui::GestureEvent long_press1(
210       5, 5, 0, base::TimeTicks(),
211       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
212   ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
213 
214   ui::GestureEvent end1(5, 5, 0, base::TimeTicks(),
215                         ui::GestureEventDetails(ui::ET_GESTURE_END));
216   details = root_view->OnEventFromSource(&end1);
217 
218   EXPECT_FALSE(details.target_destroyed);
219   EXPECT_FALSE(details.dispatcher_destroyed);
220   EXPECT_EQ(0, controller.show_context_menu_calls());
221   controller.Reset();
222 
223   // |parent_view| should show a context menu as a result of a long press on
224   // |other_child_view|.
225   ui::GestureEvent long_press2(
226       25, 5, 0, base::TimeTicks(),
227       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
228   details = root_view->OnEventFromSource(&long_press2);
229 
230   ui::GestureEvent end2(25, 5, 0, base::TimeTicks(),
231                         ui::GestureEventDetails(ui::ET_GESTURE_END));
232   details = root_view->OnEventFromSource(&end2);
233 
234   EXPECT_FALSE(details.target_destroyed);
235   EXPECT_FALSE(details.dispatcher_destroyed);
236   EXPECT_EQ(1, controller.show_context_menu_calls());
237   controller.Reset();
238 
239   // |parent_view| should show a context menu as a result of a long press on
240   // itself.
241   ui::GestureEvent long_press3(
242       50, 50, 0, base::TimeTicks(),
243       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
244   details = root_view->OnEventFromSource(&long_press3);
245 
246   ui::GestureEvent end3(25, 5, 0, base::TimeTicks(),
247                         ui::GestureEventDetails(ui::ET_GESTURE_END));
248   details = root_view->OnEventFromSource(&end3);
249 
250   EXPECT_FALSE(details.target_destroyed);
251   EXPECT_FALSE(details.dispatcher_destroyed);
252   EXPECT_EQ(1, controller.show_context_menu_calls());
253 }
254 
255 // Tests that context menus are not shown for disabled views on a long press.
TEST_F(RootViewTest,ContextMenuFromLongPressOnDisabledView)256 TEST_F(RootViewTest, ContextMenuFromLongPressOnDisabledView) {
257   Widget widget;
258   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
259   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
260   init_params.bounds = gfx::Rect(100, 100);
261   widget.Init(std::move(init_params));
262   internal::RootView* root_view =
263       static_cast<internal::RootView*>(widget.GetRootView());
264 
265   // Create a view capable of showing the context menu with two children one of
266   // which handles all gesture events (e.g. a button). Also mark this view
267   // as disabled.
268   TestContextMenuController controller;
269   View* parent_view = widget.SetContentsView(std::make_unique<View>());
270   parent_view->set_context_menu_controller(&controller);
271   parent_view->SetEnabled(false);
272 
273   View* gesture_handling_child_view = new GestureHandlingView;
274   gesture_handling_child_view->SetBoundsRect(gfx::Rect(10, 10));
275   parent_view->AddChildView(gesture_handling_child_view);
276 
277   View* other_child_view = new View;
278   other_child_view->SetBoundsRect(gfx::Rect(20, 0, 10, 10));
279   parent_view->AddChildView(other_child_view);
280 
281   // |parent_view| should not show a context menu as a result of a long press on
282   // |gesture_handling_child_view|.
283   ui::GestureEvent long_press1(
284       5, 5, 0, base::TimeTicks(),
285       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
286   ui::EventDispatchDetails details = root_view->OnEventFromSource(&long_press1);
287 
288   ui::GestureEvent end1(5, 5, 0, base::TimeTicks(),
289                         ui::GestureEventDetails(ui::ET_GESTURE_END));
290   details = root_view->OnEventFromSource(&end1);
291 
292   EXPECT_FALSE(details.target_destroyed);
293   EXPECT_FALSE(details.dispatcher_destroyed);
294   EXPECT_EQ(0, controller.show_context_menu_calls());
295   controller.Reset();
296 
297   // |parent_view| should not show a context menu as a result of a long press on
298   // |other_child_view|.
299   ui::GestureEvent long_press2(
300       25, 5, 0, base::TimeTicks(),
301       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
302   details = root_view->OnEventFromSource(&long_press2);
303 
304   ui::GestureEvent end2(25, 5, 0, base::TimeTicks(),
305                         ui::GestureEventDetails(ui::ET_GESTURE_END));
306   details = root_view->OnEventFromSource(&end2);
307 
308   EXPECT_FALSE(details.target_destroyed);
309   EXPECT_FALSE(details.dispatcher_destroyed);
310   EXPECT_EQ(0, controller.show_context_menu_calls());
311   controller.Reset();
312 
313   // |parent_view| should not show a context menu as a result of a long press on
314   // itself.
315   ui::GestureEvent long_press3(
316       50, 50, 0, base::TimeTicks(),
317       ui::GestureEventDetails(ui::ET_GESTURE_LONG_PRESS));
318   details = root_view->OnEventFromSource(&long_press3);
319 
320   ui::GestureEvent end3(25, 5, 0, base::TimeTicks(),
321                         ui::GestureEventDetails(ui::ET_GESTURE_END));
322   details = root_view->OnEventFromSource(&end3);
323 
324   EXPECT_FALSE(details.target_destroyed);
325   EXPECT_FALSE(details.dispatcher_destroyed);
326   EXPECT_EQ(0, controller.show_context_menu_calls());
327 }
328 
329 namespace {
330 
331 // View class which destroys itself when it gets an event of type
332 // |delete_event_type|.
333 class DeleteViewOnEvent : public View {
334  public:
DeleteViewOnEvent(ui::EventType delete_event_type,bool * was_destroyed)335   DeleteViewOnEvent(ui::EventType delete_event_type, bool* was_destroyed)
336       : delete_event_type_(delete_event_type), was_destroyed_(was_destroyed) {}
337 
~DeleteViewOnEvent()338   ~DeleteViewOnEvent() override { *was_destroyed_ = true; }
339 
OnEvent(ui::Event * event)340   void OnEvent(ui::Event* event) override {
341     if (event->type() == delete_event_type_)
342       delete this;
343   }
344 
345  private:
346   // The event type which causes the view to destroy itself.
347   ui::EventType delete_event_type_;
348 
349   // Tracks whether the view was destroyed.
350   bool* was_destroyed_;
351 
352   DISALLOW_COPY_AND_ASSIGN(DeleteViewOnEvent);
353 };
354 
355 // View class which remove itself when it gets an event of type
356 // |remove_event_type|.
357 class RemoveViewOnEvent : public View {
358  public:
RemoveViewOnEvent(ui::EventType remove_event_type)359   explicit RemoveViewOnEvent(ui::EventType remove_event_type)
360       : remove_event_type_(remove_event_type) {}
361 
OnEvent(ui::Event * event)362   void OnEvent(ui::Event* event) override {
363     if (event->type() == remove_event_type_)
364       parent()->RemoveChildView(this);
365   }
366 
367  private:
368   // The event type which causes the view to remove itself.
369   ui::EventType remove_event_type_;
370 
371   DISALLOW_COPY_AND_ASSIGN(RemoveViewOnEvent);
372 };
373 
374 // View class which generates a nested event the first time it gets an event of
375 // type |nested_event_type|. This is used to simulate nested event loops which
376 // can cause |RootView::mouse_event_handler_| to get reset.
377 class NestedEventOnEvent : public View {
378  public:
NestedEventOnEvent(ui::EventType nested_event_type,View * root_view)379   NestedEventOnEvent(ui::EventType nested_event_type, View* root_view)
380       : nested_event_type_(nested_event_type), root_view_(root_view) {}
381 
OnEvent(ui::Event * event)382   void OnEvent(ui::Event* event) override {
383     if (event->type() == nested_event_type_) {
384       ui::MouseEvent exit_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
385                                 ui::EventTimeForNow(), ui::EF_NONE,
386                                 ui::EF_NONE);
387       // Avoid infinite recursion if |nested_event_type_| == ET_MOUSE_EXITED.
388       nested_event_type_ = ui::ET_UNKNOWN;
389       root_view_->OnMouseExited(exit_event);
390     }
391   }
392 
393  private:
394   // The event type which causes the view to generate a nested event.
395   ui::EventType nested_event_type_;
396   // root view of this view; owned by widget.
397   View* root_view_;
398 
399   DISALLOW_COPY_AND_ASSIGN(NestedEventOnEvent);
400 };
401 
402 }  // namespace
403 
404 // Verifies deleting a View in OnMouseExited() doesn't crash.
TEST_F(RootViewTest,DeleteViewOnMouseExitDispatch)405 TEST_F(RootViewTest, DeleteViewOnMouseExitDispatch) {
406   Widget widget;
407   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
408   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
409   widget.Init(std::move(init_params));
410   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
411 
412   View* content = widget.SetContentsView(std::make_unique<View>());
413 
414   bool view_destroyed = false;
415   View* child = new DeleteViewOnEvent(ui::ET_MOUSE_EXITED, &view_destroyed);
416   content->AddChildView(child);
417   child->SetBounds(10, 10, 500, 500);
418 
419   internal::RootView* root_view =
420       static_cast<internal::RootView*>(widget.GetRootView());
421 
422   // Generate a mouse move event which ensures that |mouse_moved_handler_|
423   // is set in the RootView class.
424   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
425                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
426   root_view->OnMouseMoved(moved_event);
427   ASSERT_FALSE(view_destroyed);
428 
429   // Generate a mouse exit event which in turn will delete the child view which
430   // was the target of the mouse move event above. This should not crash when
431   // the mouse exit handler returns from the child.
432   ui::MouseEvent exit_event(ui::ET_MOUSE_EXITED, gfx::Point(), gfx::Point(),
433                             ui::EventTimeForNow(), 0, 0);
434   root_view->OnMouseExited(exit_event);
435 
436   EXPECT_TRUE(view_destroyed);
437   EXPECT_TRUE(content->children().empty());
438 }
439 
440 // Verifies deleting a View in OnMouseEntered() doesn't crash.
TEST_F(RootViewTest,DeleteViewOnMouseEnterDispatch)441 TEST_F(RootViewTest, DeleteViewOnMouseEnterDispatch) {
442   Widget widget;
443   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
444   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
445   widget.Init(std::move(init_params));
446   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
447 
448   View* content = widget.SetContentsView(std::make_unique<View>());
449 
450   bool view_destroyed = false;
451   View* child = new DeleteViewOnEvent(ui::ET_MOUSE_ENTERED, &view_destroyed);
452   content->AddChildView(child);
453 
454   // Make |child| smaller than the containing Widget and RootView.
455   child->SetBounds(100, 100, 100, 100);
456 
457   internal::RootView* root_view =
458       static_cast<internal::RootView*>(widget.GetRootView());
459 
460   // Move the mouse within |widget| but outside of |child|.
461   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
462                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
463   root_view->OnMouseMoved(moved_event);
464   ASSERT_FALSE(view_destroyed);
465 
466   // Move the mouse within |child|, which should dispatch a mouse enter event to
467   // |child| and destroy the view. This should not crash when the mouse enter
468   // handler returns from the child.
469   ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
470                               gfx::Point(115, 115), ui::EventTimeForNow(), 0,
471                               0);
472   root_view->OnMouseMoved(moved_event2);
473 
474   EXPECT_TRUE(view_destroyed);
475   EXPECT_TRUE(content->children().empty());
476 }
477 
478 // Verifies removing a View in OnMouseEntered() doesn't crash.
TEST_F(RootViewTest,RemoveViewOnMouseEnterDispatch)479 TEST_F(RootViewTest, RemoveViewOnMouseEnterDispatch) {
480   Widget widget;
481   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
482   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
483   widget.Init(std::move(init_params));
484   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
485 
486   View* content = widget.SetContentsView(std::make_unique<View>());
487 
488   // |child| gets removed without being deleted, so make it a local
489   // to prevent test memory leak.
490   RemoveViewOnEvent child(ui::ET_MOUSE_ENTERED);
491 
492   content->AddChildView(&child);
493 
494   // Make |child| smaller than the containing Widget and RootView.
495   child.SetBounds(100, 100, 100, 100);
496 
497   internal::RootView* root_view =
498       static_cast<internal::RootView*>(widget.GetRootView());
499 
500   // Move the mouse within |widget| but outside of |child|.
501   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
502                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
503   root_view->OnMouseMoved(moved_event);
504 
505   // Move the mouse within |child|, which should dispatch a mouse enter event to
506   // |child| and remove the view. This should not crash when the mouse enter
507   // handler returns.
508   ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
509                               gfx::Point(115, 115), ui::EventTimeForNow(), 0,
510                               0);
511   root_view->OnMouseMoved(moved_event2);
512 
513   EXPECT_TRUE(content->children().empty());
514 }
515 
516 // Verifies clearing the root view's |mouse_move_handler_| in OnMouseExited()
517 // doesn't crash.
TEST_F(RootViewTest,ClearMouseMoveHandlerOnMouseExitDispatch)518 TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseExitDispatch) {
519   Widget widget;
520   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
521   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
522   widget.Init(std::move(init_params));
523   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
524 
525   View* content = widget.SetContentsView(std::make_unique<View>());
526 
527   View* root_view = widget.GetRootView();
528 
529   View* child = new NestedEventOnEvent(ui::ET_MOUSE_EXITED, root_view);
530   content->AddChildView(child);
531   // Make |child| smaller than the containing Widget and RootView.
532   child->SetBounds(100, 100, 100, 100);
533 
534   // Generate a mouse move event which ensures that |mouse_moved_handler_|
535   // is set to the child view in the RootView class.
536   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(110, 110),
537                              gfx::Point(110, 110), ui::EventTimeForNow(), 0, 0);
538   root_view->OnMouseMoved(moved_event);
539 
540   // Move the mouse outside of |child| which causes a mouse exit event to be
541   // dispatched  to |child|, which will in turn generate a nested event that
542   // clears |mouse_move_handler_|. This should not crash
543   // RootView::OnMouseMoved.
544   ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
545                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
546   root_view->OnMouseMoved(move_event2);
547 }
548 
549 // Verifies clearing the root view's |mouse_move_handler_| in OnMouseExited()
550 // doesn't crash, in the case where the root view is targeted, because
551 // it's the first enabled view encountered walking up the target tree.
TEST_F(RootViewTest,ClearMouseMoveHandlerOnMouseExitDispatchWithContentViewDisabled)552 TEST_F(RootViewTest,
553        ClearMouseMoveHandlerOnMouseExitDispatchWithContentViewDisabled) {
554   Widget widget;
555   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
556   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
557   widget.Init(std::move(init_params));
558   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
559 
560   View* content = widget.SetContentsView(std::make_unique<View>());
561 
562   View* root_view = widget.GetRootView();
563 
564   View* child = new NestedEventOnEvent(ui::ET_MOUSE_EXITED, root_view);
565   content->AddChildView(child);
566 
567   // Make |child| smaller than the containing Widget and RootView.
568   child->SetBounds(100, 100, 100, 100);
569 
570   // Generate a mouse move event which ensures that the |mouse_moved_handler_|
571   // member is set to the child view in the RootView class.
572   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(110, 110),
573                              gfx::Point(110, 110), ui::EventTimeForNow(), 0, 0);
574   root_view->OnMouseMoved(moved_event);
575 
576   // This will make RootView::OnMouseMoved skip the content view when looking
577   // for a handler for the mouse event, and instead use the root view.
578   content->SetEnabled(false);
579   // Move the mouse outside of |child| which should dispatch a mouse exit event
580   // to |mouse_move_handler_| (currently |child|), which will in turn generate a
581   // nested event that clears |mouse_move_handler_|. This should not crash
582   // RootView::OnMouseMoved.
583   ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(200, 200),
584                              gfx::Point(200, 200), ui::EventTimeForNow(), 0, 0);
585   root_view->OnMouseMoved(move_event2);
586 }
587 
588 // Verifies clearing the root view's |mouse_move_handler_| in OnMouseEntered()
589 // doesn't crash.
TEST_F(RootViewTest,ClearMouseMoveHandlerOnMouseEnterDispatch)590 TEST_F(RootViewTest, ClearMouseMoveHandlerOnMouseEnterDispatch) {
591   Widget widget;
592   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
593   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
594   widget.Init(std::move(init_params));
595   widget.SetBounds(gfx::Rect(10, 10, 500, 500));
596 
597   View* content = widget.SetContentsView(std::make_unique<View>());
598 
599   View* root_view = widget.GetRootView();
600 
601   View* child = new NestedEventOnEvent(ui::ET_MOUSE_ENTERED, root_view);
602   content->AddChildView(child);
603 
604   // Make |child| smaller than the containing Widget and RootView.
605   child->SetBounds(100, 100, 100, 100);
606 
607   // Move the mouse within |widget| but outside of |child|.
608   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
609                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
610   root_view->OnMouseMoved(moved_event);
611 
612   // Move the mouse within |child|, which dispatches a mouse enter event to
613   // |child| and resets the root view's |mouse_move_handler_|. This should not
614   // crash when the mouse enter handler generates an ET_MOUSE_ENTERED event.
615   ui::MouseEvent moved_event2(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
616                               gfx::Point(115, 115), ui::EventTimeForNow(), 0,
617                               0);
618   root_view->OnMouseMoved(moved_event2);
619 }
620 
621 namespace {
622 
623 // View class which deletes its owning Widget when it gets a mouse exit event.
624 class DeleteWidgetOnMouseExit : public View {
625  public:
DeleteWidgetOnMouseExit(Widget * widget)626   explicit DeleteWidgetOnMouseExit(Widget* widget) : widget_(widget) {}
627 
628   ~DeleteWidgetOnMouseExit() override = default;
629 
OnMouseExited(const ui::MouseEvent & event)630   void OnMouseExited(const ui::MouseEvent& event) override { delete widget_; }
631 
632  private:
633   Widget* widget_;
634 
635   DISALLOW_COPY_AND_ASSIGN(DeleteWidgetOnMouseExit);
636 };
637 
638 }  // namespace
639 
640 // Test that there is no crash if a View deletes its parent Widget in
641 // View::OnMouseExited().
TEST_F(RootViewTest,DeleteWidgetOnMouseExitDispatch)642 TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatch) {
643   Widget* widget = new Widget;
644   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
645   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
646   widget->Init(std::move(init_params));
647   widget->SetBounds(gfx::Rect(10, 10, 500, 500));
648   WidgetDeletionObserver widget_deletion_observer(widget);
649 
650   auto content = std::make_unique<View>();
651   View* child = new DeleteWidgetOnMouseExit(widget);
652   content->AddChildView(child);
653   widget->SetContentsView(std::move(content));
654 
655   // Make |child| smaller than the containing Widget and RootView.
656   child->SetBounds(100, 100, 100, 100);
657 
658   internal::RootView* root_view =
659       static_cast<internal::RootView*>(widget->GetRootView());
660 
661   // Move the mouse within |child|.
662   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
663                              gfx::Point(115, 115), ui::EventTimeForNow(), 0, 0);
664   root_view->OnMouseMoved(moved_event);
665   ASSERT_TRUE(widget_deletion_observer.IsWidgetAlive());
666 
667   // Move the mouse outside of |child| which should dispatch a mouse exit event
668   // to |child| and destroy the widget. This should not crash when the mouse
669   // exit handler returns from the child.
670   ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
671                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
672   root_view->OnMouseMoved(move_event2);
673   EXPECT_FALSE(widget_deletion_observer.IsWidgetAlive());
674 }
675 
676 // Test that there is no crash if a View deletes its parent widget as a result
677 // of a mouse exited event which was propagated from one of its children.
TEST_F(RootViewTest,DeleteWidgetOnMouseExitDispatchFromChild)678 TEST_F(RootViewTest, DeleteWidgetOnMouseExitDispatchFromChild) {
679   Widget* widget = new Widget;
680   Widget::InitParams init_params = CreateParams(Widget::InitParams::TYPE_POPUP);
681   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
682   widget->Init(std::move(init_params));
683   widget->SetBounds(gfx::Rect(10, 10, 500, 500));
684   WidgetDeletionObserver widget_deletion_observer(widget);
685 
686   View* child = new DeleteWidgetOnMouseExit(widget);
687   View* subchild = new View();
688   View* content = widget->SetContentsView(std::make_unique<View>());
689   content->AddChildView(child);
690   child->AddChildView(subchild);
691 
692   // Make |child| and |subchild| smaller than the containing Widget and
693   // RootView.
694   child->SetBounds(100, 100, 100, 100);
695   subchild->SetBounds(0, 0, 100, 100);
696 
697   // Make mouse enter and exit events get propagated from |subchild| to |child|.
698   child->SetNotifyEnterExitOnChild(true);
699 
700   internal::RootView* root_view =
701       static_cast<internal::RootView*>(widget->GetRootView());
702 
703   // Move the mouse within |subchild| and |child|.
704   ui::MouseEvent moved_event(ui::ET_MOUSE_MOVED, gfx::Point(115, 115),
705                              gfx::Point(115, 115), ui::EventTimeForNow(), 0, 0);
706   root_view->OnMouseMoved(moved_event);
707   ASSERT_TRUE(widget_deletion_observer.IsWidgetAlive());
708 
709   // Move the mouse outside of |subchild| and |child| which should dispatch a
710   // mouse exit event to |subchild| and destroy the widget. This should not
711   // crash when the mouse exit handler returns from |subchild|.
712   ui::MouseEvent move_event2(ui::ET_MOUSE_MOVED, gfx::Point(15, 15),
713                              gfx::Point(15, 15), ui::EventTimeForNow(), 0, 0);
714   root_view->OnMouseMoved(move_event2);
715   EXPECT_FALSE(widget_deletion_observer.IsWidgetAlive());
716 }
717 
718 namespace {
719 class RootViewTestDialogDelegate : public DialogDelegateView {
720  public:
RootViewTestDialogDelegate()721   RootViewTestDialogDelegate() {
722     // Ensure that buttons don't influence the layout.
723     DialogDelegate::SetButtons(ui::DIALOG_BUTTON_NONE);
724   }
725   ~RootViewTestDialogDelegate() override = default;
726 
layout_count() const727   int layout_count() const { return layout_count_; }
728 
729   // DialogDelegateView:
CalculatePreferredSize() const730   gfx::Size CalculatePreferredSize() const override { return preferred_size_; }
Layout()731   void Layout() override {
732     EXPECT_EQ(size(), preferred_size_);
733     ++layout_count_;
734   }
735 
736  private:
737   const gfx::Size preferred_size_ = gfx::Size(111, 111);
738 
739   int layout_count_ = 0;
740 
741   DISALLOW_COPY_AND_ASSIGN(RootViewTestDialogDelegate);
742 };
743 }  // namespace
744 
745 // Ensure only one call to Layout() happens during Widget initialization, and
746 // ensure it happens at the ContentView's preferred size.
TEST_F(RootViewTest,SingleLayoutDuringInit)747 TEST_F(RootViewTest, SingleLayoutDuringInit) {
748   RootViewTestDialogDelegate* delegate = new RootViewTestDialogDelegate();
749   Widget* widget =
750       DialogDelegate::CreateDialogWidget(delegate, GetContext(), nullptr);
751   EXPECT_EQ(1, delegate->layout_count());
752   widget->CloseNow();
753 }
754 
755 using RootViewDesktopNativeWidgetTest = ViewsTestWithDesktopNativeWidget;
756 
757 // Also test Aura desktop Widget codepaths.
TEST_F(RootViewDesktopNativeWidgetTest,SingleLayoutDuringInit)758 TEST_F(RootViewDesktopNativeWidgetTest, SingleLayoutDuringInit) {
759   RootViewTestDialogDelegate* delegate = new RootViewTestDialogDelegate();
760   Widget* widget =
761       DialogDelegate::CreateDialogWidget(delegate, GetContext(), nullptr);
762   EXPECT_EQ(1, delegate->layout_count());
763   widget->CloseNow();
764 }
765 
766 #if !defined(OS_APPLE)
767 
768 // Tests that AnnounceText sets up the correct text value on the hidden view,
769 // and that the resulting hidden view actually stays hidden.
TEST_F(RootViewTest,AnnounceTextTest)770 TEST_F(RootViewTest, AnnounceTextTest) {
771   Widget widget;
772   Widget::InitParams init_params =
773       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
774   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
775   init_params.bounds = {100, 100, 100, 100};
776   widget.Init(std::move(init_params));
777   widget.Show();
778   internal::RootView* root_view =
779       static_cast<internal::RootView*>(widget.GetRootView());
780   root_view->SetContentsView(new View());
781 
782   EXPECT_EQ(1U, root_view->children().size());
783   const base::string16 kText = base::ASCIIToUTF16("Text");
784   root_view->AnnounceText(kText);
785   EXPECT_EQ(2U, root_view->children().size());
786   root_view->Layout();
787   EXPECT_FALSE(root_view->children()[0]->size().IsEmpty());
788   EXPECT_TRUE(root_view->children()[1]->size().IsEmpty());
789   View* const hidden_view = root_view->children()[1];
790   ui::AXNodeData node_data;
791   hidden_view->GetAccessibleNodeData(&node_data);
792   EXPECT_EQ(kText,
793             node_data.GetString16Attribute(ax::mojom::StringAttribute::kName));
794 }
795 
796 #endif  // !defined(OS_APPLE)
797 
TEST_F(RootViewTest,MouseEventDispatchedToClosestEnabledView)798 TEST_F(RootViewTest, MouseEventDispatchedToClosestEnabledView) {
799   Widget widget;
800   Widget::InitParams init_params =
801       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
802   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
803   init_params.bounds = {100, 100, 100, 100};
804   widget.Init(std::move(init_params));
805   widget.Show();
806   internal::RootView* root_view =
807       static_cast<internal::RootView*>(widget.GetRootView());
808   root_view->SetContentsView(new View());
809 
810   View* const contents_view = root_view->GetContentsView();
811   EventCountView* const v1 =
812       contents_view->AddChildView(std::make_unique<EventCountView>());
813   EventCountView* const v2 =
814       v1->AddChildView(std::make_unique<EventCountView>());
815   EventCountView* const v3 =
816       v2->AddChildView(std::make_unique<EventCountView>());
817 
818   contents_view->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
819   v1->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
820   v2->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
821   v3->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
822 
823   v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
824   v2->set_handle_mode(EventCountView::CONSUME_EVENTS);
825   v3->set_handle_mode(EventCountView::CONSUME_EVENTS);
826 
827   ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, gfx::Point(5, 5),
828                                gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
829   ui::MouseEvent released_event(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5),
830                                 gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
831   root_view->OnMousePressed(pressed_event);
832   root_view->OnMouseReleased(released_event);
833   EXPECT_EQ(0, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
834   EXPECT_EQ(0, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
835   EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
836 
837   v3->SetEnabled(false);
838   root_view->OnMousePressed(pressed_event);
839   root_view->OnMouseReleased(released_event);
840   EXPECT_EQ(0, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
841   EXPECT_EQ(1, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
842   EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
843 
844   v3->SetEnabled(true);
845   v2->SetEnabled(false);
846   root_view->OnMousePressed(pressed_event);
847   root_view->OnMouseReleased(released_event);
848   EXPECT_EQ(1, v1->GetEventCount(ui::ET_MOUSE_PRESSED));
849   EXPECT_EQ(1, v2->GetEventCount(ui::ET_MOUSE_PRESSED));
850   EXPECT_EQ(1, v3->GetEventCount(ui::ET_MOUSE_PRESSED));
851 }
852 
853 // If RootView::OnMousePressed() receives a double-click event that isn't
854 // handled by any views, it should still report it as handled if the first click
855 // was handled. However, it should *not* if the first click was unhandled.
856 // Regression test for https://crbug.com/1055674.
TEST_F(RootViewTest,DoubleClickHandledIffFirstClickHandled)857 TEST_F(RootViewTest, DoubleClickHandledIffFirstClickHandled) {
858   Widget widget;
859   Widget::InitParams init_params =
860       CreateParams(Widget::InitParams::TYPE_WINDOW_FRAMELESS);
861   init_params.ownership = Widget::InitParams::WIDGET_OWNS_NATIVE_WIDGET;
862   init_params.bounds = {100, 100, 100, 100};
863   widget.Init(std::move(init_params));
864   widget.Show();
865   internal::RootView* root_view =
866       static_cast<internal::RootView*>(widget.GetRootView());
867   root_view->SetContentsView(new View());
868 
869   View* const contents_view = root_view->GetContentsView();
870   EventCountView* const v1 =
871       contents_view->AddChildView(std::make_unique<EventCountView>());
872 
873   contents_view->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
874   v1->SetBoundsRect(gfx::Rect(0, 0, 10, 10));
875 
876   ui::MouseEvent pressed_event(ui::ET_MOUSE_PRESSED, gfx::Point(5, 5),
877                                gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
878   ui::MouseEvent released_event(ui::ET_MOUSE_RELEASED, gfx::Point(5, 5),
879                                 gfx::Point(5, 5), ui::EventTimeForNow(), 0, 0);
880 
881   // First click handled, second click unhandled.
882   v1->set_handle_mode(EventCountView::CONSUME_EVENTS);
883   pressed_event.SetClickCount(1);
884   released_event.SetClickCount(1);
885   EXPECT_TRUE(root_view->OnMousePressed(pressed_event));
886   root_view->OnMouseReleased(released_event);
887   v1->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
888   pressed_event.SetClickCount(2);
889   released_event.SetClickCount(2);
890   EXPECT_TRUE(root_view->OnMousePressed(pressed_event));
891   root_view->OnMouseReleased(released_event);
892 
893   // Both clicks unhandled.
894   v1->set_handle_mode(EventCountView::PROPAGATE_EVENTS);
895   pressed_event.SetClickCount(1);
896   released_event.SetClickCount(1);
897   EXPECT_FALSE(root_view->OnMousePressed(pressed_event));
898   root_view->OnMouseReleased(released_event);
899   pressed_event.SetClickCount(2);
900   released_event.SetClickCount(2);
901   EXPECT_FALSE(root_view->OnMousePressed(pressed_event));
902   root_view->OnMouseReleased(released_event);
903 }
904 
905 }  // namespace test
906 }  // namespace views
907