Pin Chrome's shortcut to the Win10 Start menu on install and OS upgrade.
[chromium-blink-merge.git] / ui / views / focus / focus_traversal_unittest.cc
blob79e22efcb248309a2c9eafa1844d17b520365551
1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
5 #include "ui/views/focus/focus_manager.h"
7 #include "base/run_loop.h"
8 #include "base/strings/string_number_conversions.h"
9 #include "base/strings/utf_string_conversions.h"
10 #include "ui/base/models/combobox_model.h"
11 #include "ui/views/background.h"
12 #include "ui/views/border.h"
13 #include "ui/views/controls/button/checkbox.h"
14 #include "ui/views/controls/button/label_button.h"
15 #include "ui/views/controls/button/radio_button.h"
16 #include "ui/views/controls/combobox/combobox.h"
17 #include "ui/views/controls/label.h"
18 #include "ui/views/controls/link.h"
19 #include "ui/views/controls/native/native_view_host.h"
20 #include "ui/views/controls/scroll_view.h"
21 #include "ui/views/controls/tabbed_pane/tabbed_pane.h"
22 #include "ui/views/controls/textfield/textfield.h"
23 #include "ui/views/test/focus_manager_test.h"
24 #include "ui/views/widget/root_view.h"
25 #include "ui/views/widget/widget.h"
27 using base::ASCIIToUTF16;
29 namespace views {
31 namespace {
33 int count = 1;
35 const int kTopCheckBoxID = count++; // 1
36 const int kLeftContainerID = count++;
37 const int kAppleLabelID = count++;
38 const int kAppleTextfieldID = count++;
39 const int kOrangeLabelID = count++; // 5
40 const int kOrangeTextfieldID = count++;
41 const int kBananaLabelID = count++;
42 const int kBananaTextfieldID = count++;
43 const int kKiwiLabelID = count++;
44 const int kKiwiTextfieldID = count++; // 10
45 const int kFruitButtonID = count++;
46 const int kFruitCheckBoxID = count++;
47 const int kComboboxID = count++;
49 const int kRightContainerID = count++;
50 const int kAsparagusButtonID = count++; // 15
51 const int kBroccoliButtonID = count++;
52 const int kCauliflowerButtonID = count++;
54 const int kInnerContainerID = count++;
55 const int kScrollViewID = count++;
56 const int kRosettaLinkID = count++; // 20
57 const int kStupeurEtTremblementLinkID = count++;
58 const int kDinerGameLinkID = count++;
59 const int kRidiculeLinkID = count++;
60 const int kClosetLinkID = count++;
61 const int kVisitingLinkID = count++; // 25
62 const int kAmelieLinkID = count++;
63 const int kJoyeuxNoelLinkID = count++;
64 const int kCampingLinkID = count++;
65 const int kBriceDeNiceLinkID = count++;
66 const int kTaxiLinkID = count++; // 30
67 const int kAsterixLinkID = count++;
69 const int kOKButtonID = count++;
70 const int kCancelButtonID = count++;
71 const int kHelpButtonID = count++;
73 const int kStyleContainerID = count++; // 35
74 const int kBoldCheckBoxID = count++;
75 const int kItalicCheckBoxID = count++;
76 const int kUnderlinedCheckBoxID = count++;
77 const int kStyleHelpLinkID = count++;
78 const int kStyleTextEditID = count++; // 40
80 const int kSearchContainerID = count++;
81 const int kSearchTextfieldID = count++;
82 const int kSearchButtonID = count++;
83 const int kHelpLinkID = count++;
85 const int kThumbnailContainerID = count++; // 45
86 const int kThumbnailStarID = count++;
87 const int kThumbnailSuperStarID = count++;
89 class DummyComboboxModel : public ui::ComboboxModel {
90 public:
91 // Overridden from ui::ComboboxModel:
92 int GetItemCount() const override { return 10; }
93 base::string16 GetItemAt(int index) override {
94 return ASCIIToUTF16("Item ") + base::IntToString16(index);
98 // A View that can act as a pane.
99 class PaneView : public View, public FocusTraversable {
100 public:
101 PaneView() : focus_search_(NULL) {}
103 // If this method is called, this view will use GetPaneFocusTraversable to
104 // have this provided FocusSearch used instead of the default one, allowing
105 // you to trap focus within the pane.
106 void EnablePaneFocus(FocusSearch* focus_search) {
107 focus_search_ = focus_search;
110 // Overridden from View:
111 FocusTraversable* GetPaneFocusTraversable() override {
112 if (focus_search_)
113 return this;
114 else
115 return NULL;
118 // Overridden from FocusTraversable:
119 views::FocusSearch* GetFocusSearch() override { return focus_search_; }
120 FocusTraversable* GetFocusTraversableParent() override { return NULL; }
121 View* GetFocusTraversableParentView() override { return NULL; }
123 private:
124 FocusSearch* focus_search_;
127 // BorderView is a view containing a native window with its own view hierarchy.
128 // It is interesting to test focus traversal from a view hierarchy to an inner
129 // view hierarchy.
130 class BorderView : public NativeViewHost {
131 public:
132 explicit BorderView(View* child) : child_(child), widget_(NULL) {
133 DCHECK(child);
134 SetFocusable(false);
137 ~BorderView() override {}
139 virtual internal::RootView* GetContentsRootView() {
140 return static_cast<internal::RootView*>(widget_->GetRootView());
143 FocusTraversable* GetFocusTraversable() override {
144 return static_cast<internal::RootView*>(widget_->GetRootView());
147 void ViewHierarchyChanged(
148 const ViewHierarchyChangedDetails& details) override {
149 NativeViewHost::ViewHierarchyChanged(details);
151 if (details.child == this && details.is_add) {
152 if (!widget_) {
153 widget_ = new Widget;
154 Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
155 params.parent = details.parent->GetWidget()->GetNativeView();
156 widget_->Init(params);
157 widget_->SetFocusTraversableParentView(this);
158 widget_->SetContentsView(child_);
161 // We have been added to a view hierarchy, attach the native view.
162 Attach(widget_->GetNativeView());
163 // Also update the FocusTraversable parent so the focus traversal works.
164 static_cast<internal::RootView*>(widget_->GetRootView())->
165 SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
169 private:
170 View* child_;
171 Widget* widget_;
173 DISALLOW_COPY_AND_ASSIGN(BorderView);
176 } // namespace
178 class FocusTraversalTest : public FocusManagerTest {
179 public:
180 ~FocusTraversalTest() override;
182 void InitContentView() override;
184 protected:
185 FocusTraversalTest();
187 View* FindViewByID(int id) {
188 View* view = GetContentsView()->GetViewByID(id);
189 if (view)
190 return view;
191 if (style_tab_)
192 view = style_tab_->GetSelectedTab()->GetViewByID(id);
193 if (view)
194 return view;
195 view = search_border_view_->GetContentsRootView()->GetViewByID(id);
196 if (view)
197 return view;
198 return NULL;
201 protected:
202 TabbedPane* style_tab_;
203 BorderView* search_border_view_;
204 DummyComboboxModel combobox_model_;
205 PaneView* left_container_;
206 PaneView* right_container_;
208 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
211 FocusTraversalTest::FocusTraversalTest()
212 : style_tab_(NULL),
213 search_border_view_(NULL) {
216 FocusTraversalTest::~FocusTraversalTest() {
219 void FocusTraversalTest::InitContentView() {
220 // Create a complicated view hierarchy with lots of control types for
221 // use by all of the focus traversal tests.
223 // Class name, ID, and asterisk next to focusable views:
225 // View
226 // Checkbox * kTopCheckBoxID
227 // PaneView kLeftContainerID
228 // Label kAppleLabelID
229 // Textfield * kAppleTextfieldID
230 // Label kOrangeLabelID
231 // Textfield * kOrangeTextfieldID
232 // Label kBananaLabelID
233 // Textfield * kBananaTextfieldID
234 // Label kKiwiLabelID
235 // Textfield * kKiwiTextfieldID
236 // NativeButton * kFruitButtonID
237 // Checkbox * kFruitCheckBoxID
238 // Combobox * kComboboxID
239 // PaneView kRightContainerID
240 // RadioButton * kAsparagusButtonID
241 // RadioButton * kBroccoliButtonID
242 // RadioButton * kCauliflowerButtonID
243 // View kInnerContainerID
244 // ScrollView kScrollViewID
245 // View
246 // Link * kRosettaLinkID
247 // Link * kStupeurEtTremblementLinkID
248 // Link * kDinerGameLinkID
249 // Link * kRidiculeLinkID
250 // Link * kClosetLinkID
251 // Link * kVisitingLinkID
252 // Link * kAmelieLinkID
253 // Link * kJoyeuxNoelLinkID
254 // Link * kCampingLinkID
255 // Link * kBriceDeNiceLinkID
256 // Link * kTaxiLinkID
257 // Link * kAsterixLinkID
258 // NativeButton * kOKButtonID
259 // NativeButton * kCancelButtonID
260 // NativeButton * kHelpButtonID
261 // TabbedPane * kStyleContainerID
262 // TabStrip
263 // Tab ("Style")
264 // Tab ("Other")
265 // View
266 // View
267 // Checkbox * kBoldCheckBoxID
268 // Checkbox * kItalicCheckBoxID
269 // Checkbox * kUnderlinedCheckBoxID
270 // Link * kStyleHelpLinkID
271 // Textfield * kStyleTextEditID
272 // View
273 // BorderView kSearchContainerID
274 // View
275 // Textfield * kSearchTextfieldID
276 // NativeButton * kSearchButtonID
277 // Link * kHelpLinkID
278 // View * kThumbnailContainerID
279 // NativeButton * kThumbnailStarID
280 // NativeButton * kThumbnailSuperStarID
282 GetContentsView()->set_background(
283 Background::CreateSolidBackground(SK_ColorWHITE));
285 Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
286 GetContentsView()->AddChildView(cb);
287 // In this fast paced world, who really has time for non hard-coded layout?
288 cb->SetBounds(10, 10, 200, 20);
289 cb->set_id(kTopCheckBoxID);
291 left_container_ = new PaneView();
292 left_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
293 left_container_->set_background(
294 Background::CreateSolidBackground(240, 240, 240));
295 left_container_->set_id(kLeftContainerID);
296 GetContentsView()->AddChildView(left_container_);
297 left_container_->SetBounds(10, 35, 250, 200);
299 int label_x = 5;
300 int label_width = 50;
301 int label_height = 15;
302 int text_field_width = 150;
303 int y = 10;
304 int gap_between_labels = 10;
306 Label* label = new Label(ASCIIToUTF16("Apple:"));
307 label->set_id(kAppleLabelID);
308 left_container_->AddChildView(label);
309 label->SetBounds(label_x, y, label_width, label_height);
311 Textfield* text_field = new Textfield();
312 text_field->set_id(kAppleTextfieldID);
313 left_container_->AddChildView(text_field);
314 text_field->SetBounds(label_x + label_width + 5, y,
315 text_field_width, label_height);
317 y += label_height + gap_between_labels;
319 label = new Label(ASCIIToUTF16("Orange:"));
320 label->set_id(kOrangeLabelID);
321 left_container_->AddChildView(label);
322 label->SetBounds(label_x, y, label_width, label_height);
324 text_field = new Textfield();
325 text_field->set_id(kOrangeTextfieldID);
326 left_container_->AddChildView(text_field);
327 text_field->SetBounds(label_x + label_width + 5, y,
328 text_field_width, label_height);
330 y += label_height + gap_between_labels;
332 label = new Label(ASCIIToUTF16("Banana:"));
333 label->set_id(kBananaLabelID);
334 left_container_->AddChildView(label);
335 label->SetBounds(label_x, y, label_width, label_height);
337 text_field = new Textfield();
338 text_field->set_id(kBananaTextfieldID);
339 left_container_->AddChildView(text_field);
340 text_field->SetBounds(label_x + label_width + 5, y,
341 text_field_width, label_height);
343 y += label_height + gap_between_labels;
345 label = new Label(ASCIIToUTF16("Kiwi:"));
346 label->set_id(kKiwiLabelID);
347 left_container_->AddChildView(label);
348 label->SetBounds(label_x, y, label_width, label_height);
350 text_field = new Textfield();
351 text_field->set_id(kKiwiTextfieldID);
352 left_container_->AddChildView(text_field);
353 text_field->SetBounds(label_x + label_width + 5, y,
354 text_field_width, label_height);
356 y += label_height + gap_between_labels;
358 LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me"));
359 button->SetStyle(Button::STYLE_BUTTON);
360 button->SetBounds(label_x, y + 10, 80, 30);
361 button->set_id(kFruitButtonID);
362 left_container_->AddChildView(button);
363 y += 40;
365 cb = new Checkbox(ASCIIToUTF16("This is another check box"));
366 cb->SetBounds(label_x + label_width + 5, y, 180, 20);
367 cb->set_id(kFruitCheckBoxID);
368 left_container_->AddChildView(cb);
369 y += 20;
371 Combobox* combobox = new Combobox(&combobox_model_);
372 combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
373 combobox->set_id(kComboboxID);
374 left_container_->AddChildView(combobox);
376 right_container_ = new PaneView();
377 right_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
378 right_container_->set_background(
379 Background::CreateSolidBackground(240, 240, 240));
380 right_container_->set_id(kRightContainerID);
381 GetContentsView()->AddChildView(right_container_);
382 right_container_->SetBounds(270, 35, 300, 200);
384 y = 10;
385 int radio_button_height = 18;
386 int gap_between_radio_buttons = 10;
387 RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
388 radio_button->set_id(kAsparagusButtonID);
389 right_container_->AddChildView(radio_button);
390 radio_button->SetBounds(5, y, 70, radio_button_height);
391 radio_button->SetGroup(1);
392 y += radio_button_height + gap_between_radio_buttons;
393 radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
394 radio_button->set_id(kBroccoliButtonID);
395 right_container_->AddChildView(radio_button);
396 radio_button->SetBounds(5, y, 70, radio_button_height);
397 radio_button->SetGroup(1);
398 RadioButton* radio_button_to_check = radio_button;
399 y += radio_button_height + gap_between_radio_buttons;
400 radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
401 radio_button->set_id(kCauliflowerButtonID);
402 right_container_->AddChildView(radio_button);
403 radio_button->SetBounds(5, y, 70, radio_button_height);
404 radio_button->SetGroup(1);
405 y += radio_button_height + gap_between_radio_buttons;
407 View* inner_container = new View();
408 inner_container->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
409 inner_container->set_background(
410 Background::CreateSolidBackground(230, 230, 230));
411 inner_container->set_id(kInnerContainerID);
412 right_container_->AddChildView(inner_container);
413 inner_container->SetBounds(100, 10, 150, 180);
415 ScrollView* scroll_view = new ScrollView();
416 scroll_view->set_id(kScrollViewID);
417 inner_container->AddChildView(scroll_view);
418 scroll_view->SetBounds(1, 1, 148, 178);
420 View* scroll_content = new View();
421 scroll_content->SetBounds(0, 0, 200, 200);
422 scroll_content->set_background(
423 Background::CreateSolidBackground(200, 200, 200));
424 scroll_view->SetContents(scroll_content);
426 static const char* const kTitles[] = {
427 "Rosetta", "Stupeur et tremblement", "The diner game",
428 "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
429 "Joyeux Noel", "Camping", "Brice de Nice",
430 "Taxi", "Asterix"
433 static const int kIDs[] = {
434 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
435 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
436 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
437 kTaxiLinkID, kAsterixLinkID
440 DCHECK(arraysize(kTitles) == arraysize(kIDs));
442 y = 5;
443 for (size_t i = 0; i < arraysize(kTitles); ++i) {
444 Link* link = new Link(ASCIIToUTF16(kTitles[i]));
445 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
446 link->set_id(kIDs[i]);
447 scroll_content->AddChildView(link);
448 link->SetBounds(5, y, 300, 15);
449 y += 15;
452 y = 250;
453 int width = 60;
454 button = new LabelButton(NULL, ASCIIToUTF16("OK"));
455 button->SetStyle(Button::STYLE_BUTTON);
456 button->set_id(kOKButtonID);
457 button->SetIsDefault(true);
459 GetContentsView()->AddChildView(button);
460 button->SetBounds(150, y, width, 30);
462 button = new LabelButton(NULL, ASCIIToUTF16("Cancel"));
463 button->SetStyle(Button::STYLE_BUTTON);
464 button->set_id(kCancelButtonID);
465 GetContentsView()->AddChildView(button);
466 button->SetBounds(220, y, width, 30);
468 button = new LabelButton(NULL, ASCIIToUTF16("Help"));
469 button->SetStyle(Button::STYLE_BUTTON);
470 button->set_id(kHelpButtonID);
471 GetContentsView()->AddChildView(button);
472 button->SetBounds(290, y, width, 30);
474 y += 40;
476 View* contents = NULL;
477 Link* link = NULL;
479 // Left bottom box with style checkboxes.
480 contents = new View();
481 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
482 cb = new Checkbox(ASCIIToUTF16("Bold"));
483 contents->AddChildView(cb);
484 cb->SetBounds(10, 10, 50, 20);
485 cb->set_id(kBoldCheckBoxID);
487 cb = new Checkbox(ASCIIToUTF16("Italic"));
488 contents->AddChildView(cb);
489 cb->SetBounds(70, 10, 50, 20);
490 cb->set_id(kItalicCheckBoxID);
492 cb = new Checkbox(ASCIIToUTF16("Underlined"));
493 contents->AddChildView(cb);
494 cb->SetBounds(130, 10, 70, 20);
495 cb->set_id(kUnderlinedCheckBoxID);
497 link = new Link(ASCIIToUTF16("Help"));
498 contents->AddChildView(link);
499 link->SetBounds(10, 35, 70, 10);
500 link->set_id(kStyleHelpLinkID);
502 text_field = new Textfield();
503 contents->AddChildView(text_field);
504 text_field->SetBounds(10, 50, 100, 20);
505 text_field->set_id(kStyleTextEditID);
507 style_tab_ = new TabbedPane();
508 style_tab_->set_id(kStyleContainerID);
509 GetContentsView()->AddChildView(style_tab_);
510 style_tab_->SetBounds(10, y, 210, 100);
511 style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
512 style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
514 // Right bottom box with search.
515 contents = new View();
516 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
517 text_field = new Textfield();
518 contents->AddChildView(text_field);
519 text_field->SetBounds(10, 10, 100, 20);
520 text_field->set_id(kSearchTextfieldID);
522 button = new LabelButton(NULL, ASCIIToUTF16("Search"));
523 button->SetStyle(Button::STYLE_BUTTON);
524 contents->AddChildView(button);
525 button->SetBounds(112, 5, 60, 30);
526 button->set_id(kSearchButtonID);
528 link = new Link(ASCIIToUTF16("Help"));
529 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
530 link->set_id(kHelpLinkID);
531 contents->AddChildView(link);
532 link->SetBounds(175, 10, 30, 20);
534 search_border_view_ = new BorderView(contents);
535 search_border_view_->set_id(kSearchContainerID);
537 GetContentsView()->AddChildView(search_border_view_);
538 search_border_view_->SetBounds(300, y, 240, 50);
540 y += 60;
542 contents = new View();
543 contents->SetFocusable(true);
544 contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
545 contents->set_id(kThumbnailContainerID);
546 button = new LabelButton(NULL, ASCIIToUTF16("Star"));
547 button->SetStyle(Button::STYLE_BUTTON);
548 contents->AddChildView(button);
549 button->SetBounds(5, 5, 50, 30);
550 button->set_id(kThumbnailStarID);
551 button = new LabelButton(NULL, ASCIIToUTF16("SuperStar"));
552 button->SetStyle(Button::STYLE_BUTTON);
553 contents->AddChildView(button);
554 button->SetBounds(60, 5, 100, 30);
555 button->set_id(kThumbnailSuperStarID);
557 GetContentsView()->AddChildView(contents);
558 contents->SetBounds(250, y, 200, 50);
559 // We can only call RadioButton::SetChecked() on the radio-button is part of
560 // the view hierarchy.
561 radio_button_to_check->SetChecked(true);
564 TEST_F(FocusTraversalTest, NormalTraversal) {
565 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
566 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
567 kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
568 kRosettaLinkID, kStupeurEtTremblementLinkID,
569 kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
570 kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
571 kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
572 kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
573 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
574 kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
575 kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
577 // Let's traverse the whole focus hierarchy (several times, to make sure it
578 // loops OK).
579 GetFocusManager()->ClearFocus();
580 for (int i = 0; i < 3; ++i) {
581 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
582 GetFocusManager()->AdvanceFocus(false);
583 View* focused_view = GetFocusManager()->GetFocusedView();
584 EXPECT_TRUE(focused_view != NULL);
585 if (focused_view)
586 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
590 // Let's traverse in reverse order.
591 GetFocusManager()->ClearFocus();
592 for (int i = 0; i < 3; ++i) {
593 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
594 GetFocusManager()->AdvanceFocus(true);
595 View* focused_view = GetFocusManager()->GetFocusedView();
596 EXPECT_TRUE(focused_view != NULL);
597 if (focused_view)
598 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
603 TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) {
604 const int kDisabledIDs[] = {
605 kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID,
606 kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
607 kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID,
608 kSearchTextfieldID, kHelpLinkID };
610 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
611 kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID,
612 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
613 kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
614 kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID,
615 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
616 kSearchButtonID, kThumbnailContainerID, kThumbnailStarID,
617 kThumbnailSuperStarID };
619 // Let's disable some views.
620 for (size_t i = 0; i < arraysize(kDisabledIDs); i++) {
621 View* v = FindViewByID(kDisabledIDs[i]);
622 ASSERT_TRUE(v != NULL);
623 v->SetEnabled(false);
626 View* focused_view;
627 // Let's do one traversal (several times, to make sure it loops ok).
628 GetFocusManager()->ClearFocus();
629 for (int i = 0; i < 3; ++i) {
630 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
631 GetFocusManager()->AdvanceFocus(false);
632 focused_view = GetFocusManager()->GetFocusedView();
633 EXPECT_TRUE(focused_view != NULL);
634 if (focused_view)
635 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
639 // Same thing in reverse.
640 GetFocusManager()->ClearFocus();
641 for (int i = 0; i < 3; ++i) {
642 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
643 GetFocusManager()->AdvanceFocus(true);
644 focused_view = GetFocusManager()->GetFocusedView();
645 EXPECT_TRUE(focused_view != NULL);
646 if (focused_view)
647 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
652 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
653 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
654 kThumbnailContainerID };
656 const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
657 kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
658 kComboboxID, kBroccoliButtonID, kRosettaLinkID,
659 kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
660 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
661 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
662 kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
663 kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
664 kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
667 // Let's make some views invisible.
668 for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
669 View* v = FindViewByID(kInvisibleIDs[i]);
670 ASSERT_TRUE(v != NULL);
671 v->SetVisible(false);
674 View* focused_view;
675 // Let's do one traversal (several times, to make sure it loops ok).
676 GetFocusManager()->ClearFocus();
677 for (int i = 0; i < 3; ++i) {
678 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
679 GetFocusManager()->AdvanceFocus(false);
680 focused_view = GetFocusManager()->GetFocusedView();
681 EXPECT_TRUE(focused_view != NULL);
682 if (focused_view)
683 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
687 // Same thing in reverse.
688 GetFocusManager()->ClearFocus();
689 for (int i = 0; i < 3; ++i) {
690 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
691 GetFocusManager()->AdvanceFocus(true);
692 focused_view = GetFocusManager()->GetFocusedView();
693 EXPECT_TRUE(focused_view != NULL);
694 if (focused_view)
695 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
700 TEST_F(FocusTraversalTest, PaneTraversal) {
701 // Tests trapping the traversal within a pane - useful for full
702 // keyboard accessibility for toolbars.
704 // First test the left container.
705 const int kLeftTraversalIDs[] = {
706 kAppleTextfieldID,
707 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
708 kFruitButtonID, kFruitCheckBoxID, kComboboxID };
710 FocusSearch focus_search_left(left_container_, true, false);
711 left_container_->EnablePaneFocus(&focus_search_left);
712 FindViewByID(kComboboxID)->RequestFocus();
714 // Traverse the focus hierarchy within the pane several times.
715 for (int i = 0; i < 3; ++i) {
716 for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
717 GetFocusManager()->AdvanceFocus(false);
718 View* focused_view = GetFocusManager()->GetFocusedView();
719 EXPECT_TRUE(focused_view != NULL);
720 if (focused_view)
721 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
725 // Traverse in reverse order.
726 FindViewByID(kAppleTextfieldID)->RequestFocus();
727 for (int i = 0; i < 3; ++i) {
728 for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
729 GetFocusManager()->AdvanceFocus(true);
730 View* focused_view = GetFocusManager()->GetFocusedView();
731 EXPECT_TRUE(focused_view != NULL);
732 if (focused_view)
733 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
737 // Now test the right container, but this time with accessibility mode.
738 // Make some links not focusable, but mark one of them as
739 // "accessibility focusable", so it should show up in the traversal.
740 const int kRightTraversalIDs[] = {
741 kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
742 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
743 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
745 FocusSearch focus_search_right(right_container_, true, true);
746 right_container_->EnablePaneFocus(&focus_search_right);
747 FindViewByID(kRosettaLinkID)->SetFocusable(false);
748 FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false);
749 FindViewByID(kDinerGameLinkID)->SetAccessibilityFocusable(true);
750 FindViewByID(kDinerGameLinkID)->SetFocusable(false);
751 FindViewByID(kAsterixLinkID)->RequestFocus();
753 // Traverse the focus hierarchy within the pane several times.
754 for (int i = 0; i < 3; ++i) {
755 for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
756 GetFocusManager()->AdvanceFocus(false);
757 View* focused_view = GetFocusManager()->GetFocusedView();
758 EXPECT_TRUE(focused_view != NULL);
759 if (focused_view)
760 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
764 // Traverse in reverse order.
765 FindViewByID(kBroccoliButtonID)->RequestFocus();
766 for (int i = 0; i < 3; ++i) {
767 for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
768 GetFocusManager()->AdvanceFocus(true);
769 View* focused_view = GetFocusManager()->GetFocusedView();
770 EXPECT_TRUE(focused_view != NULL);
771 if (focused_view)
772 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
777 class FocusTraversalNonFocusableTest : public FocusManagerTest {
778 public:
779 ~FocusTraversalNonFocusableTest() override {}
781 void InitContentView() override;
783 protected:
784 FocusTraversalNonFocusableTest() {}
786 private:
787 DISALLOW_COPY_AND_ASSIGN(FocusTraversalNonFocusableTest);
790 void FocusTraversalNonFocusableTest::InitContentView() {
791 // Create a complex nested view hierarchy with no focusable views. This is a
792 // regression test for http://crbug.com/453699. There was previously a bug
793 // where advancing focus backwards through this tree resulted in an
794 // exponential number of nodes being searched. (Each time it traverses one of
795 // the x1-x3-x2 triangles, it will traverse the left sibling of x1, (x+1)0,
796 // twice, which means it will visit O(2^n) nodes.)
798 // | 0 |
799 // | / \ |
800 // | / \ |
801 // | 10 1 |
802 // | / \ / \ |
803 // | / \ / \ |
804 // | 20 11 2 3 |
805 // | / \ / \ |
806 // | / \ / \ |
807 // | ... 21 12 13 |
808 // | / \ |
809 // | / \ |
810 // | 22 23 |
812 View* v = GetContentsView();
813 // Create 30 groups of 4 nodes. |v| is the top of each group.
814 for (int i = 0; i < 300; i += 10) {
815 // |v|'s left child is the top of the next group. If |v| is 20, this is 30.
816 View* v10 = new View;
817 v10->set_id(i + 10);
818 v->AddChildView(v10);
820 // |v|'s right child. If |v| is 20, this is 21.
821 View* v1 = new View;
822 v1->set_id(i + 1);
823 v->AddChildView(v1);
825 // |v|'s right child has two children. If |v| is 20, these are 22 and 23.
826 View* v2 = new View;
827 v2->set_id(i + 2);
828 View* v3 = new View;
829 v3->set_id(i + 3);
830 v1->AddChildView(v2);
831 v1->AddChildView(v3);
833 v = v10;
837 // See explanation in InitContentView.
838 // NOTE: The failure mode of this test (if http://crbug.com/453699 were to
839 // regress) is a timeout, due to exponential run time.
840 TEST_F(FocusTraversalNonFocusableTest, PathologicalSiblingTraversal) {
841 // Advance forwards from the root node.
842 GetFocusManager()->ClearFocus();
843 GetFocusManager()->AdvanceFocus(false);
844 EXPECT_FALSE(GetFocusManager()->GetFocusedView());
846 // Advance backwards from the root node.
847 GetFocusManager()->ClearFocus();
848 GetFocusManager()->AdvanceFocus(true);
849 EXPECT_FALSE(GetFocusManager()->GetFocusedView());
852 } // namespace views