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 "chrome/browser/ui/views/accessibility/accessibility_event_router_views.h"
      6 
      7 #include "base/basictypes.h"
      8 #include "base/callback.h"
      9 #include "base/memory/singleton.h"
     10 #include "base/message_loop/message_loop.h"
     11 #include "base/strings/utf_string_conversions.h"
     12 #include "chrome/browser/accessibility/accessibility_extension_api.h"
     13 #include "chrome/browser/browser_process.h"
     14 #include "chrome/browser/chrome_notification_types.h"
     15 #include "chrome/browser/profiles/profile.h"
     16 #include "chrome/browser/profiles/profile_manager.h"
     17 #include "content/public/browser/notification_service.h"
     18 #include "ui/accessibility/ax_view_state.h"
     19 #include "ui/views/controls/menu/menu_item_view.h"
     20 #include "ui/views/controls/menu/submenu_view.h"
     21 #include "ui/views/controls/tree/tree_view.h"
     22 #include "ui/views/focus/view_storage.h"
     23 #include "ui/views/view.h"
     24 #include "ui/views/widget/widget.h"
     25 
     26 using views::FocusManager;
     27 using views::ViewStorage;
     28 
     29 AccessibilityEventRouterViews::AccessibilityEventRouterViews()
     30     : most_recent_profile_(NULL),
     31       most_recent_view_id_(
     32           ViewStorage::GetInstance()->CreateStorageID()) {
     33   // Register for notification when profile is destroyed to ensure that all
     34   // observers are detatched at that time.
     35   registrar_.Add(this, chrome::NOTIFICATION_PROFILE_DESTROYED,
     36                  content::NotificationService::AllSources());
     37 }
     38 
     39 AccessibilityEventRouterViews::~AccessibilityEventRouterViews() {
     40 }
     41 
     42 // static
     43 AccessibilityEventRouterViews* AccessibilityEventRouterViews::GetInstance() {
     44   return Singleton<AccessibilityEventRouterViews>::get();
     45 }
     46 
     47 void AccessibilityEventRouterViews::HandleAccessibilityEvent(
     48     views::View* view, ui::AXEvent event_type) {
     49   if (!ExtensionAccessibilityEventRouter::GetInstance()->
     50       IsAccessibilityEnabled()) {
     51     return;
     52   }
     53 
     54   if (event_type == ui::AX_EVENT_TEXT_CHANGED ||
     55       event_type == ui::AX_EVENT_TEXT_SELECTION_CHANGED) {
     56     // These two events should only be sent for views that have focus. This
     57     // enforces the invariant that we fire events triggered by user action and
     58     // not by programmatic logic. For example, the location bar can be updated
     59     // by javascript while the user focus is within some other part of the
     60     // user interface. In contrast, the other supported events here do not
     61     // depend on focus. For example, a menu within a menubar can open or close
     62     // while focus is within the location bar or anywhere else as a result of
     63     // user action. Note that the below logic can at some point be removed if
     64     // we pass more information along to the listener such as focused state.
     65     if (!view->GetFocusManager() ||
     66         view->GetFocusManager()->GetFocusedView() != view)
     67       return;
     68   }
     69 
     70   // Don't dispatch the accessibility event until the next time through the
     71   // event loop, to handle cases where the view's state changes after
     72   // the call to post the event. It's safe to use base::Unretained(this)
     73   // because AccessibilityEventRouterViews is a singleton.
     74   ViewStorage* view_storage = ViewStorage::GetInstance();
     75   int view_storage_id = view_storage->CreateStorageID();
     76   view_storage->StoreView(view_storage_id, view);
     77   base::MessageLoop::current()->PostTask(
     78       FROM_HERE,
     79       base::Bind(
     80           &AccessibilityEventRouterViews::DispatchEventOnViewStorageId,
     81           view_storage_id,
     82           event_type));
     83 }
     84 
     85 void AccessibilityEventRouterViews::HandleMenuItemFocused(
     86     const base::string16& menu_name,
     87     const base::string16& menu_item_name,
     88     int item_index,
     89     int item_count,
     90     bool has_submenu) {
     91   if (!ExtensionAccessibilityEventRouter::GetInstance()->
     92       IsAccessibilityEnabled()) {
     93     return;
     94   }
     95 
     96   if (!most_recent_profile_)
     97     return;
     98 
     99   AccessibilityMenuItemInfo info(most_recent_profile_,
    100                                  base::UTF16ToUTF8(menu_item_name),
    101                                  base::UTF16ToUTF8(menu_name),
    102                                  has_submenu,
    103                                  item_index,
    104                                  item_count);
    105   SendControlAccessibilityNotification(
    106       ui::AX_EVENT_FOCUS, &info);
    107 }
    108 
    109 void AccessibilityEventRouterViews::Observe(
    110     int type,
    111     const content::NotificationSource& source,
    112     const content::NotificationDetails& details) {
    113   DCHECK_EQ(type, chrome::NOTIFICATION_PROFILE_DESTROYED);
    114   Profile* profile = content::Source<Profile>(source).ptr();
    115   if (profile == most_recent_profile_)
    116     most_recent_profile_ = NULL;
    117 }
    118 
    119 //
    120 // Private methods
    121 //
    122 
    123 void AccessibilityEventRouterViews::DispatchEventOnViewStorageId(
    124     int view_storage_id,
    125     ui::AXEvent type) {
    126   ViewStorage* view_storage = ViewStorage::GetInstance();
    127   views::View* view = view_storage->RetrieveView(view_storage_id);
    128   view_storage->RemoveView(view_storage_id);
    129   if (!view)
    130     return;
    131 
    132   AccessibilityEventRouterViews* instance =
    133       AccessibilityEventRouterViews::GetInstance();
    134   instance->DispatchAccessibilityEvent(view, type);
    135 }
    136 
    137 void AccessibilityEventRouterViews::DispatchAccessibilityEvent(
    138     views::View* view, ui::AXEvent type) {
    139   // Get the profile associated with this view. If it's not found, use
    140   // the most recent profile where accessibility events were sent, or
    141   // the default profile.
    142   Profile* profile = NULL;
    143   views::Widget* widget = view->GetWidget();
    144   if (widget) {
    145     profile = reinterpret_cast<Profile*>(
    146         widget->GetNativeWindowProperty(Profile::kProfileKey));
    147   }
    148   if (!profile)
    149     profile = most_recent_profile_;
    150   if (!profile) {
    151     if (g_browser_process->profile_manager())
    152       profile = g_browser_process->profile_manager()->GetLastUsedProfile();
    153   }
    154   if (!profile) {
    155     LOG(WARNING) << "Accessibility notification but no profile";
    156     return;
    157   }
    158 
    159   most_recent_profile_ = profile;
    160 
    161   if (type == ui::AX_EVENT_MENU_START ||
    162       type == ui::AX_EVENT_MENU_POPUP_START ||
    163       type == ui::AX_EVENT_MENU_END ||
    164       type == ui::AX_EVENT_MENU_POPUP_END) {
    165     SendMenuNotification(view, type, profile);
    166     return;
    167   }
    168 
    169   view = FindFirstAccessibleAncestor(view);
    170 
    171   // Since multiple items could share a highest focusable view, these items
    172   // could all dispatch the same accessibility hover events, which isn't
    173   // necessary.
    174   if (type == ui::AX_EVENT_HOVER &&
    175       ViewStorage::GetInstance()->RetrieveView(most_recent_view_id_) == view) {
    176     return;
    177   }
    178   // If there was already a view stored here from before, it must be removed
    179   // before storing a new view.
    180   ViewStorage::GetInstance()->RemoveView(most_recent_view_id_);
    181   ViewStorage::GetInstance()->StoreView(most_recent_view_id_, view);
    182 
    183   ui::AXViewState state;
    184   view->GetAccessibleState(&state);
    185 
    186   if (type == ui::AX_EVENT_ALERT &&
    187       !(state.role == ui::AX_ROLE_ALERT ||
    188         state.role == ui::AX_ROLE_WINDOW)) {
    189     SendAlertControlNotification(view, type, profile);
    190     return;
    191   }
    192 
    193   switch (state.role) {
    194   case ui::AX_ROLE_ALERT:
    195   case ui::AX_ROLE_DIALOG:
    196   case ui::AX_ROLE_WINDOW:
    197     SendWindowNotification(view, type, profile);
    198     break;
    199   case ui::AX_ROLE_POP_UP_BUTTON:
    200   case ui::AX_ROLE_MENU_BAR:
    201   case ui::AX_ROLE_MENU_LIST_POPUP:
    202     SendMenuNotification(view, type, profile);
    203     break;
    204   case ui::AX_ROLE_BUTTON_DROP_DOWN:
    205   case ui::AX_ROLE_BUTTON:
    206     SendButtonNotification(view, type, profile);
    207     break;
    208   case ui::AX_ROLE_CHECK_BOX:
    209     SendCheckboxNotification(view, type, profile);
    210     break;
    211   case ui::AX_ROLE_COMBO_BOX:
    212     SendComboboxNotification(view, type, profile);
    213     break;
    214   case ui::AX_ROLE_LINK:
    215     SendLinkNotification(view, type, profile);
    216     break;
    217   case ui::AX_ROLE_LOCATION_BAR:
    218   case ui::AX_ROLE_TEXT_FIELD:
    219     SendTextfieldNotification(view, type, profile);
    220     break;
    221   case ui::AX_ROLE_MENU_ITEM:
    222     SendMenuItemNotification(view, type, profile);
    223     break;
    224   case ui::AX_ROLE_RADIO_BUTTON:
    225     // Not used anymore?
    226   case ui::AX_ROLE_SLIDER:
    227     SendSliderNotification(view, type, profile);
    228     break;
    229   case ui::AX_ROLE_STATIC_TEXT:
    230     SendStaticTextNotification(view, type, profile);
    231     break;
    232   case ui::AX_ROLE_TREE:
    233     SendTreeNotification(view, type, profile);
    234     break;
    235   case ui::AX_ROLE_TAB:
    236     SendTabNotification(view, type, profile);
    237     break;
    238   case ui::AX_ROLE_TREE_ITEM:
    239     SendTreeItemNotification(view, type, profile);
    240     break;
    241   default:
    242     // Hover events can fire on literally any view, so it's safe to
    243     // ignore ones we don't care about.
    244     if (type == ui::AX_EVENT_HOVER)
    245       break;
    246 
    247     // If this is encountered, please file a bug with the role that wasn't
    248     // caught so we can add accessibility extension API support.
    249     NOTREACHED();
    250   }
    251 }
    252 
    253 // static
    254 void AccessibilityEventRouterViews::SendTabNotification(
    255     views::View* view,
    256     ui::AXEvent event,
    257     Profile* profile) {
    258   ui::AXViewState state;
    259   view->GetAccessibleState(&state);
    260   if (state.index == -1)
    261     return;
    262   std::string name = base::UTF16ToUTF8(state.name);
    263   std::string context = GetViewContext(view);
    264   AccessibilityTabInfo info(profile, name, context, state.index, state.count);
    265   info.set_bounds(view->GetBoundsInScreen());
    266   SendControlAccessibilityNotification(event, &info);
    267 }
    268 
    269 // static
    270 void AccessibilityEventRouterViews::SendButtonNotification(
    271     views::View* view,
    272     ui::AXEvent event,
    273     Profile* profile) {
    274   AccessibilityButtonInfo info(
    275       profile, GetViewName(view), GetViewContext(view));
    276   info.set_bounds(view->GetBoundsInScreen());
    277   SendControlAccessibilityNotification(event, &info);
    278 }
    279 
    280 // static
    281 void AccessibilityEventRouterViews::SendStaticTextNotification(
    282     views::View* view,
    283     ui::AXEvent event,
    284     Profile* profile) {
    285   AccessibilityStaticTextInfo info(
    286       profile, GetViewName(view), GetViewContext(view));
    287   info.set_bounds(view->GetBoundsInScreen());
    288   SendControlAccessibilityNotification(event, &info);
    289 }
    290 
    291 // static
    292 void AccessibilityEventRouterViews::SendLinkNotification(
    293     views::View* view,
    294     ui::AXEvent event,
    295     Profile* profile) {
    296   AccessibilityLinkInfo info(profile, GetViewName(view), GetViewContext(view));
    297   info.set_bounds(view->GetBoundsInScreen());
    298   SendControlAccessibilityNotification(event, &info);
    299 }
    300 
    301 // static
    302 void AccessibilityEventRouterViews::SendMenuNotification(
    303     views::View* view,
    304     ui::AXEvent event,
    305     Profile* profile) {
    306   AccessibilityMenuInfo info(profile, GetViewName(view));
    307   info.set_bounds(view->GetBoundsInScreen());
    308   SendMenuAccessibilityNotification(event, &info);
    309 }
    310 
    311 // static
    312 void AccessibilityEventRouterViews::SendMenuItemNotification(
    313     views::View* view,
    314     ui::AXEvent event,
    315     Profile* profile) {
    316   std::string name = GetViewName(view);
    317   std::string context = GetViewContext(view);
    318 
    319   bool has_submenu = false;
    320   int index = -1;
    321   int count = -1;
    322 
    323   if (!strcmp(view->GetClassName(), views::MenuItemView::kViewClassName))
    324     has_submenu = static_cast<views::MenuItemView*>(view)->HasSubmenu();
    325 
    326   views::View* parent_menu = view->parent();
    327   while (parent_menu != NULL && strcmp(parent_menu->GetClassName(),
    328                                        views::SubmenuView::kViewClassName)) {
    329     parent_menu = parent_menu->parent();
    330   }
    331   if (parent_menu) {
    332     count = 0;
    333     RecursiveGetMenuItemIndexAndCount(parent_menu, view, &index, &count);
    334   }
    335 
    336   AccessibilityMenuItemInfo info(
    337       profile, name, context, has_submenu, index, count);
    338   info.set_bounds(view->GetBoundsInScreen());
    339   SendControlAccessibilityNotification(event, &info);
    340 }
    341 
    342 // static
    343 void AccessibilityEventRouterViews::SendTreeNotification(
    344     views::View* view,
    345     ui::AXEvent event,
    346     Profile* profile) {
    347   AccessibilityTreeInfo info(profile, GetViewName(view));
    348   info.set_bounds(view->GetBoundsInScreen());
    349   SendControlAccessibilityNotification(event, &info);
    350 }
    351 
    352 // static
    353 void AccessibilityEventRouterViews::SendTreeItemNotification(
    354     views::View* view,
    355     ui::AXEvent event,
    356     Profile* profile) {
    357   std::string name = GetViewName(view);
    358   std::string context = GetViewContext(view);
    359 
    360   if (strcmp(view->GetClassName(), views::TreeView::kViewClassName) != 0) {
    361     NOTREACHED();
    362     return;
    363   }
    364 
    365   views::TreeView* tree = static_cast<views::TreeView*>(view);
    366   ui::TreeModelNode* selected_node = tree->GetSelectedNode();
    367   ui::TreeModel* model = tree->model();
    368 
    369   int siblings_count = model->GetChildCount(model->GetRoot());
    370   int children_count = -1;
    371   int index = -1;
    372   int depth = -1;
    373   bool is_expanded = false;
    374 
    375   if (selected_node) {
    376     children_count = model->GetChildCount(selected_node);
    377     is_expanded = tree->IsExpanded(selected_node);
    378     ui::TreeModelNode* parent_node = model->GetParent(selected_node);
    379     if (parent_node) {
    380       index = model->GetIndexOf(parent_node, selected_node);
    381       siblings_count = model->GetChildCount(parent_node);
    382     }
    383     // Get node depth.
    384     depth = 0;
    385     while (parent_node) {
    386       depth++;
    387       parent_node = model->GetParent(parent_node);
    388     }
    389   }
    390 
    391   AccessibilityTreeItemInfo info(
    392       profile, name, context, depth, index, siblings_count, children_count,
    393       is_expanded);
    394   info.set_bounds(view->GetBoundsInScreen());
    395   SendControlAccessibilityNotification(event, &info);
    396 }
    397 
    398 // static
    399 void AccessibilityEventRouterViews::SendTextfieldNotification(
    400     views::View* view,
    401     ui::AXEvent event,
    402     Profile* profile) {
    403   ui::AXViewState state;
    404   view->GetAccessibleState(&state);
    405   std::string name = base::UTF16ToUTF8(state.name);
    406   std::string context = GetViewContext(view);
    407   bool password = state.HasStateFlag(ui::AX_STATE_PROTECTED);
    408   AccessibilityTextBoxInfo info(profile, name, context, password);
    409   std::string value = base::UTF16ToUTF8(state.value);
    410   info.SetValue(value, state.selection_start, state.selection_end);
    411   info.set_bounds(view->GetBoundsInScreen());
    412   SendControlAccessibilityNotification(event, &info);
    413 }
    414 
    415 // static
    416 void AccessibilityEventRouterViews::SendComboboxNotification(
    417     views::View* view,
    418     ui::AXEvent event,
    419     Profile* profile) {
    420   ui::AXViewState state;
    421   view->GetAccessibleState(&state);
    422   std::string name = base::UTF16ToUTF8(state.name);
    423   std::string value = base::UTF16ToUTF8(state.value);
    424   std::string context = GetViewContext(view);
    425   AccessibilityComboBoxInfo info(
    426       profile, name, context, value, state.index, state.count);
    427   info.set_bounds(view->GetBoundsInScreen());
    428   SendControlAccessibilityNotification(event, &info);
    429 }
    430 
    431 // static
    432 void AccessibilityEventRouterViews::SendCheckboxNotification(
    433     views::View* view,
    434     ui::AXEvent event,
    435     Profile* profile) {
    436   ui::AXViewState state;
    437   view->GetAccessibleState(&state);
    438   std::string name = base::UTF16ToUTF8(state.name);
    439   std::string context = GetViewContext(view);
    440   AccessibilityCheckboxInfo info(
    441       profile,
    442       name,
    443       context,
    444       state.HasStateFlag(ui::AX_STATE_CHECKED));
    445   info.set_bounds(view->GetBoundsInScreen());
    446   SendControlAccessibilityNotification(event, &info);
    447 }
    448 
    449 // static
    450 void AccessibilityEventRouterViews::SendWindowNotification(
    451     views::View* view,
    452     ui::AXEvent event,
    453     Profile* profile) {
    454   ui::AXViewState state;
    455   view->GetAccessibleState(&state);
    456   std::string window_text;
    457 
    458   // If it's an alert, try to get the text from the contents of the
    459   // static text, not the window title.
    460   if (state.role == ui::AX_ROLE_ALERT)
    461     window_text = RecursiveGetStaticText(view);
    462 
    463   // Otherwise get it from the window's accessible name.
    464   if (window_text.empty())
    465     window_text = base::UTF16ToUTF8(state.name);
    466 
    467   AccessibilityWindowInfo info(profile, window_text);
    468   info.set_bounds(view->GetBoundsInScreen());
    469   SendWindowAccessibilityNotification(event, &info);
    470 }
    471 
    472 // static
    473 void AccessibilityEventRouterViews::SendSliderNotification(
    474     views::View* view,
    475     ui::AXEvent event,
    476     Profile* profile) {
    477   ui::AXViewState state;
    478   view->GetAccessibleState(&state);
    479 
    480   std::string name = base::UTF16ToUTF8(state.name);
    481   std::string value = base::UTF16ToUTF8(state.value);
    482   std::string context = GetViewContext(view);
    483   AccessibilitySliderInfo info(
    484       profile,
    485       name,
    486       context,
    487       value);
    488   info.set_bounds(view->GetBoundsInScreen());
    489   SendControlAccessibilityNotification(event, &info);
    490 }
    491 
    492 // static
    493 void AccessibilityEventRouterViews::SendAlertControlNotification(
    494     views::View* view,
    495     ui::AXEvent event,
    496     Profile* profile) {
    497   ui::AXViewState state;
    498   view->GetAccessibleState(&state);
    499 
    500   std::string name = base::UTF16ToUTF8(state.name);
    501   AccessibilityAlertInfo info(
    502       profile,
    503       name);
    504   info.set_bounds(view->GetBoundsInScreen());
    505   SendControlAccessibilityNotification(event, &info);
    506 }
    507 
    508 // static
    509 std::string AccessibilityEventRouterViews::GetViewName(views::View* view) {
    510   ui::AXViewState state;
    511   view->GetAccessibleState(&state);
    512   return base::UTF16ToUTF8(state.name);
    513 }
    514 
    515 // static
    516 std::string AccessibilityEventRouterViews::GetViewContext(views::View* view) {
    517   for (views::View* parent = view->parent();
    518        parent;
    519        parent = parent->parent()) {
    520     ui::AXViewState state;
    521     parent->GetAccessibleState(&state);
    522 
    523     // Two cases are handled right now. More could be added in the future
    524     // depending on how the UI evolves.
    525 
    526     // A control inside of alert, toolbar or dialog should use that container's
    527     // accessible name.
    528     if ((state.role == ui::AX_ROLE_ALERT ||
    529          state.role == ui::AX_ROLE_DIALOG ||
    530          state.role == ui::AX_ROLE_TOOLBAR) &&
    531         !state.name.empty()) {
    532       return base::UTF16ToUTF8(state.name);
    533     }
    534 
    535     // A control inside of an alert or dialog (including an infobar)
    536     // should grab the first static text descendant as the context;
    537     // that's the prompt.
    538     if (state.role == ui::AX_ROLE_ALERT ||
    539         state.role == ui::AX_ROLE_DIALOG) {
    540       views::View* static_text_child = FindDescendantWithAccessibleRole(
    541           parent, ui::AX_ROLE_STATIC_TEXT);
    542       if (static_text_child) {
    543         ui::AXViewState state;
    544         static_text_child->GetAccessibleState(&state);
    545         if (!state.name.empty())
    546           return base::UTF16ToUTF8(state.name);
    547       }
    548       return std::string();
    549     }
    550   }
    551 
    552   return std::string();
    553 }
    554 
    555 // static
    556 views::View* AccessibilityEventRouterViews::FindDescendantWithAccessibleRole(
    557     views::View* view, ui::AXRole role) {
    558   ui::AXViewState state;
    559   view->GetAccessibleState(&state);
    560   if (state.role == role)
    561     return view;
    562 
    563   for (int i = 0; i < view->child_count(); i++) {
    564     views::View* child = view->child_at(i);
    565     views::View* result = FindDescendantWithAccessibleRole(child, role);
    566     if (result)
    567       return result;
    568   }
    569 
    570   return NULL;
    571 }
    572 
    573 // static
    574 void AccessibilityEventRouterViews::RecursiveGetMenuItemIndexAndCount(
    575     views::View* menu,
    576     views::View* item,
    577     int* index,
    578     int* count) {
    579   for (int i = 0; i < menu->child_count(); ++i) {
    580     views::View* child = menu->child_at(i);
    581     if (!child->visible())
    582       continue;
    583 
    584     int previous_count = *count;
    585     RecursiveGetMenuItemIndexAndCount(child, item, index, count);
    586     ui::AXViewState state;
    587     child->GetAccessibleState(&state);
    588     if (state.role == ui::AX_ROLE_MENU_ITEM &&
    589         *count == previous_count) {
    590       if (item == child)
    591         *index = *count;
    592       (*count)++;
    593     } else if (state.role == ui::AX_ROLE_BUTTON) {
    594       if (item == child)
    595         *index = *count;
    596       (*count)++;
    597     }
    598   }
    599 }
    600 
    601 // static
    602 std::string AccessibilityEventRouterViews::RecursiveGetStaticText(
    603     views::View* view) {
    604   ui::AXViewState state;
    605   view->GetAccessibleState(&state);
    606   if (state.role == ui::AX_ROLE_STATIC_TEXT)
    607     return base::UTF16ToUTF8(state.name);
    608 
    609   for (int i = 0; i < view->child_count(); ++i) {
    610     views::View* child = view->child_at(i);
    611     std::string result = RecursiveGetStaticText(child);
    612     if (!result.empty())
    613       return result;
    614   }
    615   return std::string();
    616 }
    617 
    618 // static
    619 views::View* AccessibilityEventRouterViews::FindFirstAccessibleAncestor(
    620     views::View* view) {
    621   views::View* temp_view = view;
    622   while (temp_view->parent() && !temp_view->IsAccessibilityFocusable())
    623     temp_view = temp_view->parent();
    624   if (temp_view->IsAccessibilityFocusable())
    625     return temp_view;
    626   return view;
    627 }
    628