Home | History | Annotate | Download | only in accessibility
      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 <string>
      6 
      7 #include "base/message_loop/message_loop.h"
      8 #include "base/strings/string_util.h"
      9 #include "base/strings/utf_string_conversions.h"
     10 #include "chrome/browser/accessibility/accessibility_extension_api.h"
     11 #include "chrome/browser/accessibility/accessibility_extension_api_constants.h"
     12 #include "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
     13 #include "chrome/test/base/testing_profile.h"
     14 #include "testing/gtest/include/gtest/gtest.h"
     15 #include "ui/accessibility/ax_enums.h"
     16 #include "ui/accessibility/ax_view_state.h"
     17 #include "ui/base/models/simple_menu_model.h"
     18 #include "ui/views/controls/button/label_button.h"
     19 #include "ui/views/controls/label.h"
     20 #include "ui/views/controls/menu/menu_item_view.h"
     21 #include "ui/views/controls/menu/menu_model_adapter.h"
     22 #include "ui/views/controls/menu/menu_runner.h"
     23 #include "ui/views/controls/menu/submenu_view.h"
     24 #include "ui/views/layout/grid_layout.h"
     25 #include "ui/views/test/test_views_delegate.h"
     26 #include "ui/views/widget/native_widget.h"
     27 #include "ui/views/widget/root_view.h"
     28 #include "ui/views/widget/widget.h"
     29 #include "ui/views/widget/widget_delegate.h"
     30 
     31 #if defined(OS_WIN)
     32 #include "ui/base/win/scoped_ole_initializer.h"
     33 #endif
     34 
     35 #if defined(USE_AURA)
     36 #include "ui/aura/test/aura_test_helper.h"
     37 #include "ui/aura/window_event_dispatcher.h"
     38 #include "ui/compositor/test/context_factories_for_test.h"
     39 #include "ui/wm/core/default_activation_client.h"
     40 #endif
     41 
     42 using base::ASCIIToUTF16;
     43 
     44 class AccessibilityViewsDelegate : public views::TestViewsDelegate {
     45  public:
     46   AccessibilityViewsDelegate() {}
     47   virtual ~AccessibilityViewsDelegate() {}
     48 
     49   // Overridden from views::TestViewsDelegate:
     50   virtual void NotifyAccessibilityEvent(
     51       views::View* view, ui::AXEvent event_type) OVERRIDE {
     52     AccessibilityEventRouterViews::GetInstance()->HandleAccessibilityEvent(
     53         view, event_type);
     54   }
     55 
     56  private:
     57   DISALLOW_COPY_AND_ASSIGN(AccessibilityViewsDelegate);
     58 };
     59 
     60 class AccessibilityWindowDelegate : public views::WidgetDelegate {
     61  public:
     62   explicit AccessibilityWindowDelegate(views::View* contents)
     63       : contents_(contents) { }
     64 
     65   // Overridden from views::WidgetDelegate:
     66   virtual void DeleteDelegate() OVERRIDE { delete this; }
     67   virtual views::View* GetContentsView() OVERRIDE { return contents_; }
     68   virtual const views::Widget* GetWidget() const OVERRIDE {
     69     return contents_->GetWidget();
     70   }
     71   virtual views::Widget* GetWidget() OVERRIDE { return contents_->GetWidget(); }
     72 
     73  private:
     74   views::View* contents_;
     75 
     76   DISALLOW_COPY_AND_ASSIGN(AccessibilityWindowDelegate);
     77 };
     78 
     79 class ViewWithNameAndRole : public views::View {
     80  public:
     81   explicit ViewWithNameAndRole(const base::string16& name,
     82                                ui::AXRole role)
     83       : name_(name),
     84         role_(role) {
     85   }
     86 
     87   virtual void GetAccessibleState(ui::AXViewState* state) OVERRIDE {
     88     views::View::GetAccessibleState(state);
     89     state->name = name_;
     90     state->role = role_;
     91   }
     92 
     93   void set_name(const base::string16& name) { name_ = name; }
     94 
     95  private:
     96   base::string16 name_;
     97   ui::AXRole role_;
     98   DISALLOW_COPY_AND_ASSIGN(ViewWithNameAndRole);
     99 };
    100 
    101 class AccessibilityEventRouterViewsTest
    102     : public testing::Test {
    103  public:
    104   AccessibilityEventRouterViewsTest() : control_event_count_(0) {
    105   }
    106 
    107   virtual void SetUp() {
    108 #if defined(OS_WIN)
    109     ole_initializer_.reset(new ui::ScopedOleInitializer());
    110 #endif
    111     views::ViewsDelegate::views_delegate = new AccessibilityViewsDelegate();
    112 #if defined(USE_AURA)
    113     // The ContextFactory must exist before any Compositors are created.
    114     bool enable_pixel_output = false;
    115     ui::ContextFactory* context_factory =
    116         ui::InitializeContextFactoryForTests(enable_pixel_output);
    117 
    118     aura_test_helper_.reset(new aura::test::AuraTestHelper(&message_loop_));
    119     aura_test_helper_->SetUp(context_factory);
    120     new wm::DefaultActivationClient(aura_test_helper_->root_window());
    121 #endif  // USE_AURA
    122     EnableAccessibilityAndListenToFocusNotifications();
    123   }
    124 
    125   virtual void TearDown() {
    126     ClearCallback();
    127 #if defined(USE_AURA)
    128     aura_test_helper_->TearDown();
    129     ui::TerminateContextFactoryForTests();
    130 #endif
    131     delete views::ViewsDelegate::views_delegate;
    132 
    133     // The Widget's FocusManager is deleted using DeleteSoon - this
    134     // forces it to be deleted now, so we don't have any memory leaks
    135     // when this method exits.
    136     base::MessageLoop::current()->RunUntilIdle();
    137 
    138 #if defined(OS_WIN)
    139     ole_initializer_.reset();
    140 #endif
    141   }
    142 
    143   views::Widget* CreateWindowWithContents(views::View* contents) {
    144     gfx::NativeWindow context = NULL;
    145 #if defined(USE_AURA)
    146     context = aura_test_helper_->root_window();
    147 #endif
    148     views::Widget* widget = views::Widget::CreateWindowWithContextAndBounds(
    149         new AccessibilityWindowDelegate(contents),
    150         context,
    151         gfx::Rect(0, 0, 500, 500));
    152 
    153     // Create a profile and associate it with this window.
    154     widget->SetNativeWindowProperty(Profile::kProfileKey, &profile_);
    155 
    156     return widget;
    157   }
    158 
    159   void EnableAccessibilityAndListenToFocusNotifications() {
    160     // Switch on accessibility event notifications.
    161     ExtensionAccessibilityEventRouter* accessibility_event_router =
    162         ExtensionAccessibilityEventRouter::GetInstance();
    163     accessibility_event_router->SetAccessibilityEnabled(true);
    164     accessibility_event_router->SetControlEventCallbackForTesting(base::Bind(
    165         &AccessibilityEventRouterViewsTest::OnControlEvent,
    166         base::Unretained(this)));
    167   }
    168 
    169   void ClearCallback() {
    170     ExtensionAccessibilityEventRouter* accessibility_event_router =
    171         ExtensionAccessibilityEventRouter::GetInstance();
    172     accessibility_event_router->ClearControlEventCallback();
    173   }
    174 
    175  protected:
    176   // Handle Focus event.
    177   virtual void OnControlEvent(ui::AXEvent event,
    178                             const AccessibilityControlInfo* info) {
    179     control_event_count_++;
    180     last_control_type_ = info->type();
    181     last_control_name_ = info->name();
    182     last_control_context_ = info->context();
    183   }
    184 
    185   base::MessageLoopForUI message_loop_;
    186   int control_event_count_;
    187   std::string last_control_type_;
    188   std::string last_control_name_;
    189   std::string last_control_context_;
    190   TestingProfile profile_;
    191 #if defined(OS_WIN)
    192   scoped_ptr<ui::ScopedOleInitializer> ole_initializer_;
    193 #endif
    194 #if defined(USE_AURA)
    195   scoped_ptr<aura::test::AuraTestHelper> aura_test_helper_;
    196 #endif
    197 };
    198 
    199 TEST_F(AccessibilityEventRouterViewsTest, TestFocusNotification) {
    200   const char kButton1ASCII[] = "Button1";
    201   const char kButton2ASCII[] = "Button2";
    202   const char kButton3ASCII[] = "Button3";
    203   const char kButton3NewASCII[] = "Button3New";
    204 
    205   // Create a contents view with 3 buttons.
    206   views::View* contents = new views::View();
    207   views::LabelButton* button1 = new views::LabelButton(
    208       NULL, ASCIIToUTF16(kButton1ASCII));
    209   button1->SetStyle(views::Button::STYLE_BUTTON);
    210   contents->AddChildView(button1);
    211   views::LabelButton* button2 = new views::LabelButton(
    212       NULL, ASCIIToUTF16(kButton2ASCII));
    213   button2->SetStyle(views::Button::STYLE_BUTTON);
    214   contents->AddChildView(button2);
    215   views::LabelButton* button3 = new views::LabelButton(
    216       NULL, ASCIIToUTF16(kButton3ASCII));
    217   button3->SetStyle(views::Button::STYLE_BUTTON);
    218   contents->AddChildView(button3);
    219 
    220   // Put the view in a window.
    221   views::Widget* window = CreateWindowWithContents(contents);
    222   window->Show();
    223 
    224   // Set focus to the first button initially and run message loop to execute
    225   // callback.
    226   button1->RequestFocus();
    227   base::MessageLoop::current()->RunUntilIdle();
    228 
    229   // Change the accessible name of button3.
    230   button3->SetAccessibleName(ASCIIToUTF16(kButton3NewASCII));
    231 
    232   // Advance focus to the next button and test that we got the
    233   // expected notification with the name of button 2.
    234   views::FocusManager* focus_manager = contents->GetWidget()->GetFocusManager();
    235   control_event_count_ = 0;
    236   focus_manager->AdvanceFocus(false);
    237   base::MessageLoop::current()->RunUntilIdle();
    238   EXPECT_EQ(1, control_event_count_);
    239   EXPECT_EQ(kButton2ASCII, last_control_name_);
    240 
    241   // Advance to button 3. Expect the new accessible name we assigned.
    242   focus_manager->AdvanceFocus(false);
    243   base::MessageLoop::current()->RunUntilIdle();
    244   EXPECT_EQ(2, control_event_count_);
    245   EXPECT_EQ(kButton3NewASCII, last_control_name_);
    246 
    247   // Advance to button 1 and check the notification.
    248   focus_manager->AdvanceFocus(false);
    249   base::MessageLoop::current()->RunUntilIdle();
    250   EXPECT_EQ(3, control_event_count_);
    251   EXPECT_EQ(kButton1ASCII, last_control_name_);
    252 
    253   window->CloseNow();
    254 }
    255 
    256 TEST_F(AccessibilityEventRouterViewsTest, TestToolbarContext) {
    257   const char kToolbarNameASCII[] = "MyToolbar";
    258   const char kButtonNameASCII[] = "MyButton";
    259 
    260   // Create a toolbar with a button.
    261   views::View* contents = new ViewWithNameAndRole(
    262       ASCIIToUTF16(kToolbarNameASCII),
    263       ui::AX_ROLE_TOOLBAR);
    264   views::LabelButton* button = new views::LabelButton(
    265       NULL, ASCIIToUTF16(kButtonNameASCII));
    266   button->SetStyle(views::Button::STYLE_BUTTON);
    267   contents->AddChildView(button);
    268 
    269   // Put the view in a window.
    270   views::Widget* window = CreateWindowWithContents(contents);
    271 
    272   // Set focus to the button.
    273   control_event_count_ = 0;
    274   button->RequestFocus();
    275 
    276   base::MessageLoop::current()->RunUntilIdle();
    277 
    278   // Test that we got the event with the expected name and context.
    279   EXPECT_EQ(1, control_event_count_);
    280   EXPECT_EQ(kButtonNameASCII, last_control_name_);
    281   EXPECT_EQ(kToolbarNameASCII, last_control_context_);
    282 
    283   window->CloseNow();
    284 }
    285 
    286 TEST_F(AccessibilityEventRouterViewsTest, TestAlertContext) {
    287   const char kAlertTextASCII[] = "MyAlertText";
    288   const char kButtonNameASCII[] = "MyButton";
    289 
    290   // Create an alert with static text and a button, similar to an infobar.
    291   views::View* contents = new ViewWithNameAndRole(
    292       base::string16(),
    293       ui::AX_ROLE_ALERT);
    294   views::Label* label = new views::Label(ASCIIToUTF16(kAlertTextASCII));
    295   contents->AddChildView(label);
    296   views::LabelButton* button = new views::LabelButton(
    297       NULL, ASCIIToUTF16(kButtonNameASCII));
    298   button->SetStyle(views::Button::STYLE_BUTTON);
    299   contents->AddChildView(button);
    300 
    301   // Put the view in a window.
    302   views::Widget* window = CreateWindowWithContents(contents);
    303 
    304   // Set focus to the button.
    305   control_event_count_ = 0;
    306   button->RequestFocus();
    307 
    308   base::MessageLoop::current()->RunUntilIdle();
    309 
    310   // Test that we got the event with the expected name and context.
    311   EXPECT_EQ(1, control_event_count_);
    312   EXPECT_EQ(kButtonNameASCII, last_control_name_);
    313   EXPECT_EQ(kAlertTextASCII, last_control_context_);
    314 
    315   window->CloseNow();
    316 }
    317 
    318 TEST_F(AccessibilityEventRouterViewsTest, StateChangeAfterNotification) {
    319   const char kContentsNameASCII[] = "Contents";
    320   const char kOldNameASCII[] = "OldName";
    321   const char kNewNameASCII[] = "NewName";
    322 
    323   // Create a toolbar with a button.
    324   views::View* contents = new ViewWithNameAndRole(
    325       ASCIIToUTF16(kContentsNameASCII),
    326       ui::AX_ROLE_CLIENT);
    327   ViewWithNameAndRole* child = new ViewWithNameAndRole(
    328       ASCIIToUTF16(kOldNameASCII),
    329       ui::AX_ROLE_BUTTON);
    330   child->SetFocusable(true);
    331   contents->AddChildView(child);
    332 
    333   // Put the view in a window.
    334   views::Widget* window = CreateWindowWithContents(contents);
    335 
    336   // Set focus to the child view.
    337   control_event_count_ = 0;
    338   child->RequestFocus();
    339 
    340   // Change the child's name after the focus notification.
    341   child->set_name(ASCIIToUTF16(kNewNameASCII));
    342 
    343   // We shouldn't get the notification right away.
    344   EXPECT_EQ(0, control_event_count_);
    345 
    346   // Process anything in the event loop. Now we should get the notification,
    347   // and it should give us the new control name, not the old one.
    348   base::MessageLoop::current()->RunUntilIdle();
    349   EXPECT_EQ(1, control_event_count_);
    350   EXPECT_EQ(kNewNameASCII, last_control_name_);
    351 
    352   window->CloseNow();
    353 }
    354 
    355 TEST_F(AccessibilityEventRouterViewsTest, NotificationOnDeletedObject) {
    356   const char kContentsNameASCII[] = "Contents";
    357   const char kNameASCII[] = "OldName";
    358 
    359   // Create a toolbar with a button.
    360   views::View* contents = new ViewWithNameAndRole(
    361       ASCIIToUTF16(kContentsNameASCII),
    362       ui::AX_ROLE_CLIENT);
    363   ViewWithNameAndRole* child = new ViewWithNameAndRole(
    364       ASCIIToUTF16(kNameASCII),
    365       ui::AX_ROLE_BUTTON);
    366   child->SetFocusable(true);
    367   contents->AddChildView(child);
    368 
    369   // Put the view in a window.
    370   views::Widget* window = CreateWindowWithContents(contents);
    371 
    372   // Set focus to the child view.
    373   control_event_count_ = 0;
    374   child->RequestFocus();
    375 
    376   // Delete the child!
    377   delete child;
    378 
    379   // We shouldn't get the notification right away.
    380   EXPECT_EQ(0, control_event_count_);
    381 
    382   // Process anything in the event loop. We shouldn't get a notification
    383   // because the view is no longer valid, and this shouldn't crash.
    384   base::MessageLoop::current()->RunUntilIdle();
    385   EXPECT_EQ(0, control_event_count_);
    386 
    387   window->CloseNow();
    388 }
    389 
    390 TEST_F(AccessibilityEventRouterViewsTest, AlertsFromWindowAndControl) {
    391   const char kButtonASCII[] = "Button";
    392   const char* kTypeAlert = extension_accessibility_api_constants::kTypeAlert;
    393   const char* kTypeWindow = extension_accessibility_api_constants::kTypeWindow;
    394 
    395   // Create a contents view with a button.
    396   views::View* contents = new views::View();
    397   views::LabelButton* button = new views::LabelButton(
    398       NULL, ASCIIToUTF16(kButtonASCII));
    399   button->SetStyle(views::Button::STYLE_BUTTON);
    400   contents->AddChildView(button);
    401 
    402   // Put the view in a window.
    403   views::Widget* window = CreateWindowWithContents(contents);
    404   window->Show();
    405 
    406   // Send an alert event from the button and let the event loop run.
    407   control_event_count_ = 0;
    408   button->NotifyAccessibilityEvent(ui::AX_EVENT_ALERT, true);
    409   base::MessageLoop::current()->RunUntilIdle();
    410 
    411   EXPECT_EQ(kTypeAlert, last_control_type_);
    412   EXPECT_EQ(1, control_event_count_);
    413   EXPECT_EQ(kButtonASCII, last_control_name_);
    414 
    415   // Send an alert event from the window and let the event loop run.
    416   control_event_count_ = 0;
    417   window->GetRootView()->NotifyAccessibilityEvent(
    418       ui::AX_EVENT_ALERT, true);
    419   base::MessageLoop::current()->RunUntilIdle();
    420 
    421   EXPECT_EQ(1, control_event_count_);
    422   EXPECT_EQ(kTypeWindow, last_control_type_);
    423 
    424   window->CloseNow();
    425 }
    426 
    427 TEST_F(AccessibilityEventRouterViewsTest, AccessibilityFocusableView) {
    428   // Create a view with a child view.
    429   views::View* parent = new views::View();
    430   views::View* child = new views::View();
    431   parent->AddChildView(child);
    432 
    433   // Put the view in a window.
    434   views::Widget* window = CreateWindowWithContents(parent);
    435 
    436   // Since the child view has no accessibility focusable ancestors, this
    437   // should still be the child view.
    438   views::View* accessible_view =
    439       AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child);
    440   EXPECT_EQ(accessible_view, child);
    441 
    442   // Now make the parent view accessibility focusable. Calling
    443   // FindFirstAccessibleAncestor() again on child should return the parent
    444   // view.
    445   parent->SetAccessibilityFocusable(true);
    446   accessible_view =
    447       AccessibilityEventRouterViews::FindFirstAccessibleAncestor(child);
    448   EXPECT_EQ(accessible_view, parent);
    449 
    450   window->CloseNow();
    451 }
    452 
    453 namespace {
    454 
    455 class SimpleMenuDelegate : public ui::SimpleMenuModel::Delegate {
    456  public:
    457   enum {
    458     IDC_MENU_ITEM_1,
    459     IDC_MENU_ITEM_2,
    460     IDC_MENU_INVISIBLE,
    461     IDC_MENU_ITEM_3,
    462   };
    463 
    464   SimpleMenuDelegate() {}
    465   virtual ~SimpleMenuDelegate() {}
    466 
    467   views::MenuItemView* BuildMenu() {
    468     menu_model_.reset(new ui::SimpleMenuModel(this));
    469     menu_model_->AddItem(IDC_MENU_ITEM_1, ASCIIToUTF16("Item 1"));
    470     menu_model_->AddItem(IDC_MENU_ITEM_2, ASCIIToUTF16("Item 2"));
    471     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    472     menu_model_->AddItem(IDC_MENU_INVISIBLE, ASCIIToUTF16("Invisible"));
    473     menu_model_->AddSeparator(ui::NORMAL_SEPARATOR);
    474     menu_model_->AddItem(IDC_MENU_ITEM_3, ASCIIToUTF16("Item 3"));
    475 
    476     menu_adapter_.reset(new views::MenuModelAdapter(menu_model_.get()));
    477     views::MenuItemView* menu_view = menu_adapter_->CreateMenu();
    478     menu_runner_.reset(new views::MenuRunner(menu_view, 0));
    479     return menu_view;
    480   }
    481 
    482   virtual bool IsCommandIdChecked(int command_id) const OVERRIDE {
    483     return false;
    484   }
    485 
    486   virtual bool IsCommandIdEnabled(int command_id) const OVERRIDE {
    487     return true;
    488   }
    489 
    490   virtual bool IsCommandIdVisible(int command_id) const OVERRIDE {
    491     return command_id != IDC_MENU_INVISIBLE;
    492   }
    493 
    494   virtual bool GetAcceleratorForCommandId(
    495       int command_id,
    496       ui::Accelerator* accelerator) OVERRIDE {
    497     return false;
    498   }
    499 
    500   virtual void ExecuteCommand(int command_id, int event_flags) OVERRIDE {
    501   }
    502 
    503  private:
    504   scoped_ptr<ui::SimpleMenuModel> menu_model_;
    505   scoped_ptr<views::MenuModelAdapter> menu_adapter_;
    506   scoped_ptr<views::MenuRunner> menu_runner_;
    507 
    508   DISALLOW_COPY_AND_ASSIGN(SimpleMenuDelegate);
    509 };
    510 
    511 }  // namespace
    512 
    513 TEST_F(AccessibilityEventRouterViewsTest, MenuIndexAndCountForInvisibleMenu) {
    514   SimpleMenuDelegate menu_delegate;
    515   views::MenuItemView* menu = menu_delegate.BuildMenu();
    516   views::View* menu_container = menu->CreateSubmenu();
    517 
    518   struct TestCase {
    519     int command_id;
    520     int expected_index;
    521     int expected_count;
    522   } kTestCases[] = {
    523     { SimpleMenuDelegate::IDC_MENU_ITEM_1, 0, 3 },
    524     { SimpleMenuDelegate::IDC_MENU_ITEM_2, 1, 3 },
    525     { SimpleMenuDelegate::IDC_MENU_INVISIBLE, 0, 3 },
    526     { SimpleMenuDelegate::IDC_MENU_ITEM_3, 2, 3 },
    527   };
    528 
    529   for (size_t i = 0; i < ARRAYSIZE_UNSAFE(kTestCases); ++i) {
    530     int index = 0;
    531     int count = 0;
    532 
    533     AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
    534         menu_container,
    535         menu->GetMenuItemByID(kTestCases[i].command_id),
    536         &index,
    537         &count);
    538     EXPECT_EQ(kTestCases[i].expected_index, index) << "Case " << i;
    539     EXPECT_EQ(kTestCases[i].expected_count, count) << "Case " << i;
    540   }
    541 }
    542