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