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 "content/browser/accessibility/browser_accessibility_manager_win.h"
      6 
      7 #include "base/command_line.h"
      8 #include "base/win/scoped_comptr.h"
      9 #include "base/win/windows_version.h"
     10 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
     11 #include "content/browser/accessibility/browser_accessibility_win.h"
     12 #include "content/browser/renderer_host/legacy_render_widget_host_win.h"
     13 #include "content/common/accessibility_messages.h"
     14 #include "ui/base/win/atl_module.h"
     15 
     16 namespace content {
     17 
     18 // static
     19 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
     20     const ui::AXTreeUpdate& initial_tree,
     21     BrowserAccessibilityDelegate* delegate,
     22     BrowserAccessibilityFactory* factory) {
     23   return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory);
     24 }
     25 
     26 BrowserAccessibilityManagerWin*
     27 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
     28   return static_cast<BrowserAccessibilityManagerWin*>(this);
     29 }
     30 
     31 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
     32     const ui::AXTreeUpdate& initial_tree,
     33     BrowserAccessibilityDelegate* delegate,
     34     BrowserAccessibilityFactory* factory)
     35     : BrowserAccessibilityManager(initial_tree, delegate, factory),
     36       tracked_scroll_object_(NULL),
     37       focus_event_on_root_needed_(false) {
     38   ui::win::CreateATLModuleIfNeeded();
     39 }
     40 
     41 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
     42   if (tracked_scroll_object_) {
     43     tracked_scroll_object_->Release();
     44     tracked_scroll_object_ = NULL;
     45   }
     46 }
     47 
     48 // static
     49 ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
     50   ui::AXNodeData empty_document;
     51   empty_document.id = 0;
     52   empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA;
     53   empty_document.state =
     54       (1 << ui::AX_STATE_ENABLED) |
     55       (1 << ui::AX_STATE_READ_ONLY) |
     56       (1 << ui::AX_STATE_BUSY);
     57 
     58   ui::AXTreeUpdate update;
     59   update.nodes.push_back(empty_document);
     60   return update;
     61 }
     62 
     63 HWND BrowserAccessibilityManagerWin::GetParentHWND() {
     64   if (!delegate_)
     65     return NULL;
     66   return delegate_->AccessibilityGetAcceleratedWidget();
     67 }
     68 
     69 IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() {
     70   if (!delegate_)
     71     return NULL;
     72   return delegate_->AccessibilityGetNativeViewAccessible();
     73 }
     74 
     75 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
     76                                                              LONG child_id) {
     77   if (!delegate_)
     78     return;
     79 
     80   HWND hwnd = delegate_->AccessibilityGetAcceleratedWidget();
     81   if (!hwnd)
     82     return;
     83 
     84   ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id);
     85 }
     86 
     87 
     88 void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXNode* node) {
     89   BrowserAccessibilityManager::OnNodeCreated(node);
     90   BrowserAccessibility* obj = GetFromAXNode(node);
     91   LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win();
     92   unique_id_to_ax_id_map_[unique_id_win] = obj->GetId();
     93 }
     94 
     95 void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXNode* node) {
     96   BrowserAccessibilityManager::OnNodeWillBeDeleted(node);
     97   BrowserAccessibility* obj = GetFromAXNode(node);
     98   if (!obj)
     99     return;
    100   unique_id_to_ax_id_map_.erase(
    101       obj->ToBrowserAccessibilityWin()->unique_id_win());
    102   if (obj == tracked_scroll_object_) {
    103     tracked_scroll_object_->Release();
    104     tracked_scroll_object_ = NULL;
    105   }
    106 }
    107 
    108 void BrowserAccessibilityManagerWin::OnWindowFocused() {
    109   // This is called either when this web frame gets focused, or when
    110   // the root of the accessibility tree changes. In both cases, we need
    111   // to fire a focus event on the root and then on the focused element
    112   // within the page, if different.
    113 
    114   // Set this flag so that we'll keep trying to fire these focus events
    115   // if they're not successful this time.
    116   focus_event_on_root_needed_ = true;
    117 
    118   if (!delegate_ || !delegate_->AccessibilityViewHasFocus())
    119     return;
    120 
    121   // Try to fire a focus event on the root first and then the focused node.
    122   // This will clear focus_event_on_root_needed_ if successful.
    123   if (focus_ != tree_->GetRoot())
    124     NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot());
    125   BrowserAccessibilityManager::OnWindowFocused();
    126 }
    127 
    128 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
    129     ui::AXEvent event_type,
    130     BrowserAccessibility* node) {
    131   if (!delegate_ || !delegate_->AccessibilityGetAcceleratedWidget())
    132     return;
    133 
    134   // Inline text boxes are an internal implementation detail, we don't
    135   // expose them to Windows.
    136   if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX)
    137     return;
    138 
    139   // Don't fire focus, blur, or load complete notifications if the
    140   // window isn't focused, because that can confuse screen readers into
    141   // entering their "browse" mode.
    142   if ((event_type == ui::AX_EVENT_FOCUS ||
    143        event_type == ui::AX_EVENT_BLUR ||
    144        event_type == ui::AX_EVENT_LOAD_COMPLETE) &&
    145       (!delegate_ || !delegate_->AccessibilityViewHasFocus())) {
    146     return;
    147   }
    148 
    149   // NVDA gets confused if we focus the main document element when it hasn't
    150   // finished loading and it has no children at all, so suppress that event.
    151   if (event_type == ui::AX_EVENT_FOCUS &&
    152       node == GetRoot() &&
    153       node->PlatformChildCount() == 0 &&
    154       !node->HasState(ui::AX_STATE_BUSY) &&
    155       !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) {
    156     return;
    157   }
    158 
    159   // If a focus event is needed on the root, fire that first before
    160   // this event.
    161   if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot())
    162     focus_event_on_root_needed_ = false;
    163   else if (focus_event_on_root_needed_)
    164     OnWindowFocused();
    165 
    166   LONG event_id = EVENT_MIN;
    167   switch (event_type) {
    168     case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED:
    169       event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
    170       break;
    171     case ui::AX_EVENT_ALERT:
    172       event_id = EVENT_SYSTEM_ALERT;
    173       break;
    174     case ui::AX_EVENT_ARIA_ATTRIBUTE_CHANGED:
    175       event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
    176       break;
    177     case ui::AX_EVENT_AUTOCORRECTION_OCCURED:
    178       event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
    179       break;
    180     case ui::AX_EVENT_BLUR:
    181       // Equivalent to focus on the root.
    182       event_id = EVENT_OBJECT_FOCUS;
    183       node = GetRoot();
    184       break;
    185     case ui::AX_EVENT_CHECKED_STATE_CHANGED:
    186       event_id = EVENT_OBJECT_STATECHANGE;
    187       break;
    188     case ui::AX_EVENT_CHILDREN_CHANGED:
    189       event_id = EVENT_OBJECT_REORDER;
    190       break;
    191     case ui::AX_EVENT_FOCUS:
    192       event_id = EVENT_OBJECT_FOCUS;
    193       break;
    194     case ui::AX_EVENT_INVALID_STATUS_CHANGED:
    195       event_id = EVENT_OBJECT_STATECHANGE;
    196       break;
    197     case ui::AX_EVENT_LIVE_REGION_CHANGED:
    198       if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY))
    199         return;
    200       event_id = EVENT_OBJECT_LIVEREGIONCHANGED;
    201       break;
    202     case ui::AX_EVENT_LOAD_COMPLETE:
    203       event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
    204       break;
    205     case ui::AX_EVENT_MENU_LIST_ITEM_SELECTED:
    206       event_id = EVENT_OBJECT_FOCUS;
    207       break;
    208     case ui::AX_EVENT_MENU_LIST_VALUE_CHANGED:
    209       event_id = EVENT_OBJECT_VALUECHANGE;
    210       break;
    211     case ui::AX_EVENT_HIDE:
    212       event_id = EVENT_OBJECT_HIDE;
    213       break;
    214     case ui::AX_EVENT_SHOW:
    215       event_id = EVENT_OBJECT_SHOW;
    216       break;
    217     case ui::AX_EVENT_SCROLL_POSITION_CHANGED:
    218       event_id = EVENT_SYSTEM_SCROLLINGEND;
    219       break;
    220     case ui::AX_EVENT_SCROLLED_TO_ANCHOR:
    221       event_id = EVENT_SYSTEM_SCROLLINGSTART;
    222       break;
    223     case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED:
    224       event_id = EVENT_OBJECT_SELECTIONWITHIN;
    225       break;
    226     case ui::AX_EVENT_TEXT_CHANGED:
    227       event_id = EVENT_OBJECT_NAMECHANGE;
    228       break;
    229     case ui::AX_EVENT_TEXT_SELECTION_CHANGED:
    230       event_id = IA2_EVENT_TEXT_CARET_MOVED;
    231       break;
    232     case ui::AX_EVENT_VALUE_CHANGED:
    233       event_id = EVENT_OBJECT_VALUECHANGE;
    234       break;
    235     default:
    236       // Not all WebKit accessibility events result in a Windows
    237       // accessibility notification.
    238       break;
    239   }
    240 
    241   if (event_id != EVENT_MIN) {
    242     // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
    243     // the AT client will then call get_accChild on the HWND's accessibility
    244     // object and pass it that same id, which we can use to retrieve the
    245     // IAccessible for this node.
    246     LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
    247     MaybeCallNotifyWinEvent(event_id, child_id);
    248   }
    249 
    250   // If this is a layout complete notification (sent when a container scrolls)
    251   // and there is a descendant tracked object, send a notification on it.
    252   // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
    253   if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE &&
    254       tracked_scroll_object_ &&
    255       tracked_scroll_object_->IsDescendantOf(node)) {
    256     MaybeCallNotifyWinEvent(
    257         IA2_EVENT_VISIBLE_DATA_CHANGED,
    258         tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
    259     tracked_scroll_object_->Release();
    260     tracked_scroll_object_ = NULL;
    261   }
    262 }
    263 
    264 void BrowserAccessibilityManagerWin::OnRootChanged(ui::AXNode* new_root) {
    265   // In order to make screen readers aware of the new accessibility root,
    266   // we need to fire a focus event on it.
    267   OnWindowFocused();
    268 }
    269 
    270 void BrowserAccessibilityManagerWin::TrackScrollingObject(
    271     BrowserAccessibilityWin* node) {
    272   if (tracked_scroll_object_)
    273     tracked_scroll_object_->Release();
    274   tracked_scroll_object_ = node;
    275   tracked_scroll_object_->AddRef();
    276 }
    277 
    278 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
    279     LONG unique_id_win) {
    280   base::hash_map<LONG, int32>::iterator iter =
    281       unique_id_to_ax_id_map_.find(unique_id_win);
    282   if (iter != unique_id_to_ax_id_map_.end()) {
    283     BrowserAccessibility* result = GetFromID(iter->second);
    284     if (result)
    285       return result->ToBrowserAccessibilityWin();
    286   }
    287   return NULL;
    288 }
    289 
    290 }  // namespace content
    291