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