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/focus/accelerator_handler.h"
24 #include "ui/views/focus/focus_manager_test.h"
25 #include "ui/views/widget/root_view.h"
26 #include "ui/views/widget/widget.h"
28 using base::ASCIIToUTF16;
36 const int kTopCheckBoxID = count++; // 1
37 const int kLeftContainerID = count++;
38 const int kAppleLabelID = count++;
39 const int kAppleTextfieldID = count++;
40 const int kOrangeLabelID = count++; // 5
41 const int kOrangeTextfieldID = count++;
42 const int kBananaLabelID = count++;
43 const int kBananaTextfieldID = count++;
44 const int kKiwiLabelID = count++;
45 const int kKiwiTextfieldID = count++; // 10
46 const int kFruitButtonID = count++;
47 const int kFruitCheckBoxID = count++;
48 const int kComboboxID = count++;
50 const int kRightContainerID = count++;
51 const int kAsparagusButtonID = count++; // 15
52 const int kBroccoliButtonID = count++;
53 const int kCauliflowerButtonID = count++;
55 const int kInnerContainerID = count++;
56 const int kScrollViewID = count++;
57 const int kRosettaLinkID = count++; // 20
58 const int kStupeurEtTremblementLinkID = count++;
59 const int kDinerGameLinkID = count++;
60 const int kRidiculeLinkID = count++;
61 const int kClosetLinkID = count++;
62 const int kVisitingLinkID = count++; // 25
63 const int kAmelieLinkID = count++;
64 const int kJoyeuxNoelLinkID = count++;
65 const int kCampingLinkID = count++;
66 const int kBriceDeNiceLinkID = count++;
67 const int kTaxiLinkID = count++; // 30
68 const int kAsterixLinkID = count++;
70 const int kOKButtonID = count++;
71 const int kCancelButtonID = count++;
72 const int kHelpButtonID = count++;
74 const int kStyleContainerID = count++; // 35
75 const int kBoldCheckBoxID = count++;
76 const int kItalicCheckBoxID = count++;
77 const int kUnderlinedCheckBoxID = count++;
78 const int kStyleHelpLinkID = count++;
79 const int kStyleTextEditID = count++; // 40
81 const int kSearchContainerID = count++;
82 const int kSearchTextfieldID = count++;
83 const int kSearchButtonID = count++;
84 const int kHelpLinkID = count++;
86 const int kThumbnailContainerID = count++; // 45
87 const int kThumbnailStarID = count++;
88 const int kThumbnailSuperStarID = count++;
90 class DummyComboboxModel : public ui::ComboboxModel {
92 // Overridden from ui::ComboboxModel:
93 virtual int GetItemCount() const OVERRIDE { return 10; }
94 virtual base::string16 GetItemAt(int index) OVERRIDE {
95 return ASCIIToUTF16("Item ") + base::IntToString16(index);
99 // A View that can act as a pane.
100 class PaneView : public View, public FocusTraversable {
102 PaneView() : focus_search_(NULL) {}
104 // If this method is called, this view will use GetPaneFocusTraversable to
105 // have this provided FocusSearch used instead of the default one, allowing
106 // you to trap focus within the pane.
107 void EnablePaneFocus(FocusSearch* focus_search) {
108 focus_search_ = focus_search;
111 // Overridden from View:
112 virtual FocusTraversable* GetPaneFocusTraversable() OVERRIDE {
119 // Overridden from FocusTraversable:
120 virtual views::FocusSearch* GetFocusSearch() OVERRIDE {
121 return focus_search_;
123 virtual FocusTraversable* GetFocusTraversableParent() OVERRIDE {
126 virtual View* GetFocusTraversableParentView() OVERRIDE {
131 FocusSearch* focus_search_;
134 // BorderView is a view containing a native window with its own view hierarchy.
135 // It is interesting to test focus traversal from a view hierarchy to an inner
137 class BorderView : public NativeViewHost {
139 explicit BorderView(View* child) : child_(child), widget_(NULL) {
144 virtual ~BorderView() {}
146 virtual internal::RootView* GetContentsRootView() {
147 return static_cast<internal::RootView*>(widget_->GetRootView());
150 virtual FocusTraversable* GetFocusTraversable() OVERRIDE {
151 return static_cast<internal::RootView*>(widget_->GetRootView());
154 virtual void ViewHierarchyChanged(
155 const ViewHierarchyChangedDetails& details) OVERRIDE {
156 NativeViewHost::ViewHierarchyChanged(details);
158 if (details.child == this && details.is_add) {
160 widget_ = new Widget;
161 Widget::InitParams params(Widget::InitParams::TYPE_CONTROL);
162 #if defined(OS_WIN) || defined(USE_AURA)
163 params.parent = details.parent->GetWidget()->GetNativeView();
167 widget_->Init(params);
168 widget_->SetFocusTraversableParentView(this);
169 widget_->SetContentsView(child_);
172 // We have been added to a view hierarchy, attach the native view.
173 Attach(widget_->GetNativeView());
174 // Also update the FocusTraversable parent so the focus traversal works.
175 static_cast<internal::RootView*>(widget_->GetRootView())->
176 SetFocusTraversableParent(GetWidget()->GetFocusTraversable());
184 DISALLOW_COPY_AND_ASSIGN(BorderView);
189 class FocusTraversalTest : public FocusManagerTest {
191 virtual ~FocusTraversalTest();
193 virtual void InitContentView() OVERRIDE;
196 FocusTraversalTest();
198 View* FindViewByID(int id) {
199 View* view = GetContentsView()->GetViewByID(id);
203 view = style_tab_->GetSelectedTab()->GetViewByID(id);
206 view = search_border_view_->GetContentsRootView()->GetViewByID(id);
213 TabbedPane* style_tab_;
214 BorderView* search_border_view_;
215 DummyComboboxModel combobox_model_;
216 PaneView* left_container_;
217 PaneView* right_container_;
219 DISALLOW_COPY_AND_ASSIGN(FocusTraversalTest);
222 FocusTraversalTest::FocusTraversalTest()
224 search_border_view_(NULL) {
227 FocusTraversalTest::~FocusTraversalTest() {
230 void FocusTraversalTest::InitContentView() {
231 // Create a complicated view hierarchy with lots of control types for
232 // use by all of the focus traversal tests.
234 // Class name, ID, and asterisk next to focusable views:
237 // Checkbox * kTopCheckBoxID
238 // PaneView kLeftContainerID
239 // Label kAppleLabelID
240 // Textfield * kAppleTextfieldID
241 // Label kOrangeLabelID
242 // Textfield * kOrangeTextfieldID
243 // Label kBananaLabelID
244 // Textfield * kBananaTextfieldID
245 // Label kKiwiLabelID
246 // Textfield * kKiwiTextfieldID
247 // NativeButton * kFruitButtonID
248 // Checkbox * kFruitCheckBoxID
249 // Combobox * kComboboxID
250 // PaneView kRightContainerID
251 // RadioButton * kAsparagusButtonID
252 // RadioButton * kBroccoliButtonID
253 // RadioButton * kCauliflowerButtonID
254 // View kInnerContainerID
255 // ScrollView kScrollViewID
257 // Link * kRosettaLinkID
258 // Link * kStupeurEtTremblementLinkID
259 // Link * kDinerGameLinkID
260 // Link * kRidiculeLinkID
261 // Link * kClosetLinkID
262 // Link * kVisitingLinkID
263 // Link * kAmelieLinkID
264 // Link * kJoyeuxNoelLinkID
265 // Link * kCampingLinkID
266 // Link * kBriceDeNiceLinkID
267 // Link * kTaxiLinkID
268 // Link * kAsterixLinkID
269 // NativeButton * kOKButtonID
270 // NativeButton * kCancelButtonID
271 // NativeButton * kHelpButtonID
272 // TabbedPane * kStyleContainerID
274 // Checkbox * kBoldCheckBoxID
275 // Checkbox * kItalicCheckBoxID
276 // Checkbox * kUnderlinedCheckBoxID
277 // Link * kStyleHelpLinkID
278 // Textfield * kStyleTextEditID
280 // BorderView kSearchContainerID
282 // Textfield * kSearchTextfieldID
283 // NativeButton * kSearchButtonID
284 // Link * kHelpLinkID
285 // View * kThumbnailContainerID
286 // NativeButton * kThumbnailStarID
287 // NativeButton * kThumbnailSuperStarID
289 GetContentsView()->set_background(
290 Background::CreateSolidBackground(SK_ColorWHITE));
292 Checkbox* cb = new Checkbox(ASCIIToUTF16("This is a checkbox"));
293 GetContentsView()->AddChildView(cb);
294 // In this fast paced world, who really has time for non hard-coded layout?
295 cb->SetBounds(10, 10, 200, 20);
296 cb->set_id(kTopCheckBoxID);
298 left_container_ = new PaneView();
299 left_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
300 left_container_->set_background(
301 Background::CreateSolidBackground(240, 240, 240));
302 left_container_->set_id(kLeftContainerID);
303 GetContentsView()->AddChildView(left_container_);
304 left_container_->SetBounds(10, 35, 250, 200);
307 int label_width = 50;
308 int label_height = 15;
309 int text_field_width = 150;
311 int gap_between_labels = 10;
313 Label* label = new Label(ASCIIToUTF16("Apple:"));
314 label->set_id(kAppleLabelID);
315 left_container_->AddChildView(label);
316 label->SetBounds(label_x, y, label_width, label_height);
318 Textfield* text_field = new Textfield();
319 text_field->set_id(kAppleTextfieldID);
320 left_container_->AddChildView(text_field);
321 text_field->SetBounds(label_x + label_width + 5, y,
322 text_field_width, label_height);
324 y += label_height + gap_between_labels;
326 label = new Label(ASCIIToUTF16("Orange:"));
327 label->set_id(kOrangeLabelID);
328 left_container_->AddChildView(label);
329 label->SetBounds(label_x, y, label_width, label_height);
331 text_field = new Textfield();
332 text_field->set_id(kOrangeTextfieldID);
333 left_container_->AddChildView(text_field);
334 text_field->SetBounds(label_x + label_width + 5, y,
335 text_field_width, label_height);
337 y += label_height + gap_between_labels;
339 label = new Label(ASCIIToUTF16("Banana:"));
340 label->set_id(kBananaLabelID);
341 left_container_->AddChildView(label);
342 label->SetBounds(label_x, y, label_width, label_height);
344 text_field = new Textfield();
345 text_field->set_id(kBananaTextfieldID);
346 left_container_->AddChildView(text_field);
347 text_field->SetBounds(label_x + label_width + 5, y,
348 text_field_width, label_height);
350 y += label_height + gap_between_labels;
352 label = new Label(ASCIIToUTF16("Kiwi:"));
353 label->set_id(kKiwiLabelID);
354 left_container_->AddChildView(label);
355 label->SetBounds(label_x, y, label_width, label_height);
357 text_field = new Textfield();
358 text_field->set_id(kKiwiTextfieldID);
359 left_container_->AddChildView(text_field);
360 text_field->SetBounds(label_x + label_width + 5, y,
361 text_field_width, label_height);
363 y += label_height + gap_between_labels;
365 LabelButton* button = new LabelButton(NULL, ASCIIToUTF16("Click me"));
366 button->SetStyle(Button::STYLE_BUTTON);
367 button->SetBounds(label_x, y + 10, 80, 30);
368 button->set_id(kFruitButtonID);
369 left_container_->AddChildView(button);
372 cb = new Checkbox(ASCIIToUTF16("This is another check box"));
373 cb->SetBounds(label_x + label_width + 5, y, 180, 20);
374 cb->set_id(kFruitCheckBoxID);
375 left_container_->AddChildView(cb);
378 Combobox* combobox = new Combobox(&combobox_model_);
379 combobox->SetBounds(label_x + label_width + 5, y, 150, 30);
380 combobox->set_id(kComboboxID);
381 left_container_->AddChildView(combobox);
383 right_container_ = new PaneView();
384 right_container_->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
385 right_container_->set_background(
386 Background::CreateSolidBackground(240, 240, 240));
387 right_container_->set_id(kRightContainerID);
388 GetContentsView()->AddChildView(right_container_);
389 right_container_->SetBounds(270, 35, 300, 200);
392 int radio_button_height = 18;
393 int gap_between_radio_buttons = 10;
394 RadioButton* radio_button = new RadioButton(ASCIIToUTF16("Asparagus"), 1);
395 radio_button->set_id(kAsparagusButtonID);
396 right_container_->AddChildView(radio_button);
397 radio_button->SetBounds(5, y, 70, radio_button_height);
398 radio_button->SetGroup(1);
399 y += radio_button_height + gap_between_radio_buttons;
400 radio_button = new RadioButton(ASCIIToUTF16("Broccoli"), 1);
401 radio_button->set_id(kBroccoliButtonID);
402 right_container_->AddChildView(radio_button);
403 radio_button->SetBounds(5, y, 70, radio_button_height);
404 radio_button->SetGroup(1);
405 RadioButton* radio_button_to_check = radio_button;
406 y += radio_button_height + gap_between_radio_buttons;
407 radio_button = new RadioButton(ASCIIToUTF16("Cauliflower"), 1);
408 radio_button->set_id(kCauliflowerButtonID);
409 right_container_->AddChildView(radio_button);
410 radio_button->SetBounds(5, y, 70, radio_button_height);
411 radio_button->SetGroup(1);
412 y += radio_button_height + gap_between_radio_buttons;
414 View* inner_container = new View();
415 inner_container->SetBorder(Border::CreateSolidBorder(1, SK_ColorBLACK));
416 inner_container->set_background(
417 Background::CreateSolidBackground(230, 230, 230));
418 inner_container->set_id(kInnerContainerID);
419 right_container_->AddChildView(inner_container);
420 inner_container->SetBounds(100, 10, 150, 180);
422 ScrollView* scroll_view = new ScrollView();
423 scroll_view->set_id(kScrollViewID);
424 inner_container->AddChildView(scroll_view);
425 scroll_view->SetBounds(1, 1, 148, 178);
427 View* scroll_content = new View();
428 scroll_content->SetBounds(0, 0, 200, 200);
429 scroll_content->set_background(
430 Background::CreateSolidBackground(200, 200, 200));
431 scroll_view->SetContents(scroll_content);
433 static const char* const kTitles[] = {
434 "Rosetta", "Stupeur et tremblement", "The diner game",
435 "Ridicule", "Le placard", "Les Visiteurs", "Amelie",
436 "Joyeux Noel", "Camping", "Brice de Nice",
440 static const int kIDs[] = {
441 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
442 kRidiculeLinkID, kClosetLinkID, kVisitingLinkID, kAmelieLinkID,
443 kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
444 kTaxiLinkID, kAsterixLinkID
447 DCHECK(arraysize(kTitles) == arraysize(kIDs));
450 for (size_t i = 0; i < arraysize(kTitles); ++i) {
451 Link* link = new Link(ASCIIToUTF16(kTitles[i]));
452 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
453 link->set_id(kIDs[i]);
454 scroll_content->AddChildView(link);
455 link->SetBounds(5, y, 300, 15);
461 button = new LabelButton(NULL, ASCIIToUTF16("OK"));
462 button->SetStyle(Button::STYLE_BUTTON);
463 button->set_id(kOKButtonID);
464 button->SetIsDefault(true);
466 GetContentsView()->AddChildView(button);
467 button->SetBounds(150, y, width, 30);
469 button = new LabelButton(NULL, ASCIIToUTF16("Cancel"));
470 button->SetStyle(Button::STYLE_BUTTON);
471 button->set_id(kCancelButtonID);
472 GetContentsView()->AddChildView(button);
473 button->SetBounds(220, y, width, 30);
475 button = new LabelButton(NULL, ASCIIToUTF16("Help"));
476 button->SetStyle(Button::STYLE_BUTTON);
477 button->set_id(kHelpButtonID);
478 GetContentsView()->AddChildView(button);
479 button->SetBounds(290, y, width, 30);
483 View* contents = NULL;
486 // Left bottom box with style checkboxes.
487 contents = new View();
488 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
489 cb = new Checkbox(ASCIIToUTF16("Bold"));
490 contents->AddChildView(cb);
491 cb->SetBounds(10, 10, 50, 20);
492 cb->set_id(kBoldCheckBoxID);
494 cb = new Checkbox(ASCIIToUTF16("Italic"));
495 contents->AddChildView(cb);
496 cb->SetBounds(70, 10, 50, 20);
497 cb->set_id(kItalicCheckBoxID);
499 cb = new Checkbox(ASCIIToUTF16("Underlined"));
500 contents->AddChildView(cb);
501 cb->SetBounds(130, 10, 70, 20);
502 cb->set_id(kUnderlinedCheckBoxID);
504 link = new Link(ASCIIToUTF16("Help"));
505 contents->AddChildView(link);
506 link->SetBounds(10, 35, 70, 10);
507 link->set_id(kStyleHelpLinkID);
509 text_field = new Textfield();
510 contents->AddChildView(text_field);
511 text_field->SetBounds(10, 50, 100, 20);
512 text_field->set_id(kStyleTextEditID);
514 style_tab_ = new TabbedPane();
515 style_tab_->set_id(kStyleContainerID);
516 GetContentsView()->AddChildView(style_tab_);
517 style_tab_->SetBounds(10, y, 210, 100);
518 style_tab_->AddTab(ASCIIToUTF16("Style"), contents);
519 style_tab_->AddTab(ASCIIToUTF16("Other"), new View());
521 // Right bottom box with search.
522 contents = new View();
523 contents->set_background(Background::CreateSolidBackground(SK_ColorWHITE));
524 text_field = new Textfield();
525 contents->AddChildView(text_field);
526 text_field->SetBounds(10, 10, 100, 20);
527 text_field->set_id(kSearchTextfieldID);
529 button = new LabelButton(NULL, ASCIIToUTF16("Search"));
530 button->SetStyle(Button::STYLE_BUTTON);
531 contents->AddChildView(button);
532 button->SetBounds(112, 5, 60, 30);
533 button->set_id(kSearchButtonID);
535 link = new Link(ASCIIToUTF16("Help"));
536 link->SetHorizontalAlignment(gfx::ALIGN_LEFT);
537 link->set_id(kHelpLinkID);
538 contents->AddChildView(link);
539 link->SetBounds(175, 10, 30, 20);
541 search_border_view_ = new BorderView(contents);
542 search_border_view_->set_id(kSearchContainerID);
544 GetContentsView()->AddChildView(search_border_view_);
545 search_border_view_->SetBounds(300, y, 240, 50);
549 contents = new View();
550 contents->SetFocusable(true);
551 contents->set_background(Background::CreateSolidBackground(SK_ColorBLUE));
552 contents->set_id(kThumbnailContainerID);
553 button = new LabelButton(NULL, ASCIIToUTF16("Star"));
554 button->SetStyle(Button::STYLE_BUTTON);
555 contents->AddChildView(button);
556 button->SetBounds(5, 5, 50, 30);
557 button->set_id(kThumbnailStarID);
558 button = new LabelButton(NULL, ASCIIToUTF16("SuperStar"));
559 button->SetStyle(Button::STYLE_BUTTON);
560 contents->AddChildView(button);
561 button->SetBounds(60, 5, 100, 30);
562 button->set_id(kThumbnailSuperStarID);
564 GetContentsView()->AddChildView(contents);
565 contents->SetBounds(250, y, 200, 50);
566 // We can only call RadioButton::SetChecked() on the radio-button is part of
567 // the view hierarchy.
568 radio_button_to_check->SetChecked(true);
571 TEST_F(FocusTraversalTest, NormalTraversal) {
572 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
573 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
574 kFruitButtonID, kFruitCheckBoxID, kComboboxID, kBroccoliButtonID,
575 kRosettaLinkID, kStupeurEtTremblementLinkID,
576 kDinerGameLinkID, kRidiculeLinkID, kClosetLinkID, kVisitingLinkID,
577 kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID, kBriceDeNiceLinkID,
578 kTaxiLinkID, kAsterixLinkID, kOKButtonID, kCancelButtonID, kHelpButtonID,
579 kStyleContainerID, kBoldCheckBoxID, kItalicCheckBoxID,
580 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
581 kSearchTextfieldID, kSearchButtonID, kHelpLinkID,
582 kThumbnailContainerID, kThumbnailStarID, kThumbnailSuperStarID };
584 // Uncomment the following line to manually test the UI of this test.
585 // base::RunLoop(new AcceleratorHandler()).Run();
587 // Let's traverse the whole focus hierarchy (several times, to make sure it
589 GetFocusManager()->ClearFocus();
590 for (int i = 0; i < 3; ++i) {
591 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
592 GetFocusManager()->AdvanceFocus(false);
593 View* focused_view = GetFocusManager()->GetFocusedView();
594 EXPECT_TRUE(focused_view != NULL);
596 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
600 // Let's traverse in reverse order.
601 GetFocusManager()->ClearFocus();
602 for (int i = 0; i < 3; ++i) {
603 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
604 GetFocusManager()->AdvanceFocus(true);
605 View* focused_view = GetFocusManager()->GetFocusedView();
606 EXPECT_TRUE(focused_view != NULL);
608 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
613 TEST_F(FocusTraversalTest, TraversalWithNonEnabledViews) {
614 const int kDisabledIDs[] = {
615 kBananaTextfieldID, kFruitCheckBoxID, kComboboxID, kAsparagusButtonID,
616 kCauliflowerButtonID, kClosetLinkID, kVisitingLinkID, kBriceDeNiceLinkID,
617 kTaxiLinkID, kAsterixLinkID, kHelpButtonID, kBoldCheckBoxID,
618 kSearchTextfieldID, kHelpLinkID };
620 const int kTraversalIDs[] = { kTopCheckBoxID, kAppleTextfieldID,
621 kOrangeTextfieldID, kKiwiTextfieldID, kFruitButtonID, kBroccoliButtonID,
622 kRosettaLinkID, kStupeurEtTremblementLinkID, kDinerGameLinkID,
623 kRidiculeLinkID, kAmelieLinkID, kJoyeuxNoelLinkID, kCampingLinkID,
624 kOKButtonID, kCancelButtonID, kStyleContainerID, kItalicCheckBoxID,
625 kUnderlinedCheckBoxID, kStyleHelpLinkID, kStyleTextEditID,
626 kSearchButtonID, kThumbnailContainerID, kThumbnailStarID,
627 kThumbnailSuperStarID };
629 // Let's disable some views.
630 for (size_t i = 0; i < arraysize(kDisabledIDs); i++) {
631 View* v = FindViewByID(kDisabledIDs[i]);
632 ASSERT_TRUE(v != NULL);
633 v->SetEnabled(false);
636 // Uncomment the following line to manually test the UI of this test.
637 // base::RunLoop(new AcceleratorHandler()).Run();
640 // Let's do one traversal (several times, to make sure it loops ok).
641 GetFocusManager()->ClearFocus();
642 for (int i = 0; i < 3; ++i) {
643 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
644 GetFocusManager()->AdvanceFocus(false);
645 focused_view = GetFocusManager()->GetFocusedView();
646 EXPECT_TRUE(focused_view != NULL);
648 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
652 // Same thing in reverse.
653 GetFocusManager()->ClearFocus();
654 for (int i = 0; i < 3; ++i) {
655 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
656 GetFocusManager()->AdvanceFocus(true);
657 focused_view = GetFocusManager()->GetFocusedView();
658 EXPECT_TRUE(focused_view != NULL);
660 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
665 TEST_F(FocusTraversalTest, TraversalWithInvisibleViews) {
666 const int kInvisibleIDs[] = { kTopCheckBoxID, kOKButtonID,
667 kThumbnailContainerID };
669 const int kTraversalIDs[] = { kAppleTextfieldID, kOrangeTextfieldID,
670 kBananaTextfieldID, kKiwiTextfieldID, kFruitButtonID, kFruitCheckBoxID,
671 kComboboxID, kBroccoliButtonID, kRosettaLinkID,
672 kStupeurEtTremblementLinkID, kDinerGameLinkID, kRidiculeLinkID,
673 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
674 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID,
675 kCancelButtonID, kHelpButtonID, kStyleContainerID, kBoldCheckBoxID,
676 kItalicCheckBoxID, kUnderlinedCheckBoxID, kStyleHelpLinkID,
677 kStyleTextEditID, kSearchTextfieldID, kSearchButtonID, kHelpLinkID };
680 // Let's make some views invisible.
681 for (size_t i = 0; i < arraysize(kInvisibleIDs); i++) {
682 View* v = FindViewByID(kInvisibleIDs[i]);
683 ASSERT_TRUE(v != NULL);
684 v->SetVisible(false);
687 // Uncomment the following line to manually test the UI of this test.
688 // base::RunLoop(new AcceleratorHandler()).Run();
691 // Let's do one traversal (several times, to make sure it loops ok).
692 GetFocusManager()->ClearFocus();
693 for (int i = 0; i < 3; ++i) {
694 for (size_t j = 0; j < arraysize(kTraversalIDs); j++) {
695 GetFocusManager()->AdvanceFocus(false);
696 focused_view = GetFocusManager()->GetFocusedView();
697 EXPECT_TRUE(focused_view != NULL);
699 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
703 // Same thing in reverse.
704 GetFocusManager()->ClearFocus();
705 for (int i = 0; i < 3; ++i) {
706 for (int j = arraysize(kTraversalIDs) - 1; j >= 0; --j) {
707 GetFocusManager()->AdvanceFocus(true);
708 focused_view = GetFocusManager()->GetFocusedView();
709 EXPECT_TRUE(focused_view != NULL);
711 EXPECT_EQ(kTraversalIDs[j], focused_view->id());
716 TEST_F(FocusTraversalTest, PaneTraversal) {
717 // Tests trapping the traversal within a pane - useful for full
718 // keyboard accessibility for toolbars.
720 // First test the left container.
721 const int kLeftTraversalIDs[] = {
723 kOrangeTextfieldID, kBananaTextfieldID, kKiwiTextfieldID,
724 kFruitButtonID, kFruitCheckBoxID, kComboboxID };
726 FocusSearch focus_search_left(left_container_, true, false);
727 left_container_->EnablePaneFocus(&focus_search_left);
728 FindViewByID(kComboboxID)->RequestFocus();
730 // Traverse the focus hierarchy within the pane several times.
731 for (int i = 0; i < 3; ++i) {
732 for (size_t j = 0; j < arraysize(kLeftTraversalIDs); j++) {
733 GetFocusManager()->AdvanceFocus(false);
734 View* focused_view = GetFocusManager()->GetFocusedView();
735 EXPECT_TRUE(focused_view != NULL);
737 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
741 // Traverse in reverse order.
742 FindViewByID(kAppleTextfieldID)->RequestFocus();
743 for (int i = 0; i < 3; ++i) {
744 for (int j = arraysize(kLeftTraversalIDs) - 1; j >= 0; --j) {
745 GetFocusManager()->AdvanceFocus(true);
746 View* focused_view = GetFocusManager()->GetFocusedView();
747 EXPECT_TRUE(focused_view != NULL);
749 EXPECT_EQ(kLeftTraversalIDs[j], focused_view->id());
753 // Now test the right container, but this time with accessibility mode.
754 // Make some links not focusable, but mark one of them as
755 // "accessibility focusable", so it should show up in the traversal.
756 const int kRightTraversalIDs[] = {
757 kBroccoliButtonID, kDinerGameLinkID, kRidiculeLinkID,
758 kClosetLinkID, kVisitingLinkID, kAmelieLinkID, kJoyeuxNoelLinkID,
759 kCampingLinkID, kBriceDeNiceLinkID, kTaxiLinkID, kAsterixLinkID };
761 FocusSearch focus_search_right(right_container_, true, true);
762 right_container_->EnablePaneFocus(&focus_search_right);
763 FindViewByID(kRosettaLinkID)->SetFocusable(false);
764 FindViewByID(kStupeurEtTremblementLinkID)->SetFocusable(false);
765 FindViewByID(kDinerGameLinkID)->SetAccessibilityFocusable(true);
766 FindViewByID(kDinerGameLinkID)->SetFocusable(false);
767 FindViewByID(kAsterixLinkID)->RequestFocus();
769 // Traverse the focus hierarchy within the pane several times.
770 for (int i = 0; i < 3; ++i) {
771 for (size_t j = 0; j < arraysize(kRightTraversalIDs); j++) {
772 GetFocusManager()->AdvanceFocus(false);
773 View* focused_view = GetFocusManager()->GetFocusedView();
774 EXPECT_TRUE(focused_view != NULL);
776 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());
780 // Traverse in reverse order.
781 FindViewByID(kBroccoliButtonID)->RequestFocus();
782 for (int i = 0; i < 3; ++i) {
783 for (int j = arraysize(kRightTraversalIDs) - 1; j >= 0; --j) {
784 GetFocusManager()->AdvanceFocus(true);
785 View* focused_view = GetFocusManager()->GetFocusedView();
786 EXPECT_TRUE(focused_view != NULL);
788 EXPECT_EQ(kRightTraversalIDs[j], focused_view->id());