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