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