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