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 <atlbase.h>
      8 #include <atlapp.h>
      9 #include <atlcom.h>
     10 #include <atlcrack.h>
     11 #include <oleacc.h>
     12 
     13 #include "base/command_line.h"
     14 #include "base/win/scoped_comptr.h"
     15 #include "base/win/windows_version.h"
     16 #include "content/browser/accessibility/browser_accessibility_state_impl.h"
     17 #include "content/browser/accessibility/browser_accessibility_win.h"
     18 #include "content/common/accessibility_messages.h"
     19 
     20 namespace content {
     21 
     22 // Some screen readers expect every tab / every unique web content container
     23 // to be in its own HWND, like it was before Aura, but with Aura there's just
     24 // one main HWND for a frame, or even for the whole desktop. So, we need a
     25 // fake HWND as the root of the accessibility tree for each tab.
     26 // We should get rid of this code when the latest two versions of all
     27 // supported screen readers no longer make this assumption.
     28 //
     29 // This class implements a child HWND with zero size, that delegates its
     30 // accessibility implementation to the root of the BrowserAccessibilityManager
     31 // tree. This HWND is hooked up as the parent of the root object in the
     32 // BrowserAccessibilityManager tree, so when any accessibility client
     33 // calls ::WindowFromAccessibleObject, they get this HWND instead of the
     34 // DesktopRootWindowHostWin.
     35 class AccessibleHWND
     36     : public ATL::CWindowImpl<AccessibleHWND,
     37                               ATL::CWindow,
     38                               ATL::CWinTraits<WS_CHILD> > {
     39  public:
     40   // Unfortunately, some screen readers look for this exact window class
     41   // to enable certain features. It'd be great to remove this.
     42   DECLARE_WND_CLASS_EX(L"Chrome_RenderWidgetHostHWND", CS_DBLCLKS, 0);
     43 
     44   BEGIN_MSG_MAP_EX(AccessibleHWND)
     45     MESSAGE_HANDLER_EX(WM_GETOBJECT, OnGetObject)
     46   END_MSG_MAP()
     47 
     48   AccessibleHWND(HWND parent, BrowserAccessibilityManagerWin* manager)
     49       : manager_(manager) {
     50     Create(parent);
     51     ShowWindow(true);
     52     MoveWindow(0, 0, 0, 0);
     53 
     54     HRESULT hr = ::CreateStdAccessibleObject(
     55         hwnd(), OBJID_WINDOW, IID_IAccessible,
     56         reinterpret_cast<void **>(window_accessible_.Receive()));
     57     DCHECK(SUCCEEDED(hr));
     58   }
     59 
     60   HWND hwnd() {
     61     DCHECK(::IsWindow(m_hWnd));
     62     return m_hWnd;
     63   }
     64 
     65   IAccessible* window_accessible() { return window_accessible_; }
     66 
     67   void OnManagerDeleted() {
     68     manager_ = NULL;
     69   }
     70 
     71  protected:
     72   virtual void OnFinalMessage(HWND hwnd) OVERRIDE {
     73     if (manager_)
     74       manager_->OnAccessibleHwndDeleted();
     75     delete this;
     76   }
     77 
     78  private:
     79   LRESULT OnGetObject(UINT message,
     80                       WPARAM w_param,
     81                       LPARAM l_param) {
     82     if (OBJID_CLIENT != l_param || !manager_)
     83       return static_cast<LRESULT>(0L);
     84 
     85     base::win::ScopedComPtr<IAccessible> root(
     86         manager_->GetRoot()->ToBrowserAccessibilityWin());
     87     return LresultFromObject(IID_IAccessible, w_param,
     88         static_cast<IAccessible*>(root.Detach()));
     89   }
     90 
     91   BrowserAccessibilityManagerWin* manager_;
     92   base::win::ScopedComPtr<IAccessible> window_accessible_;
     93 
     94   DISALLOW_COPY_AND_ASSIGN(AccessibleHWND);
     95 };
     96 
     97 
     98 // static
     99 BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
    100     const AccessibilityNodeData& src,
    101     BrowserAccessibilityDelegate* delegate,
    102     BrowserAccessibilityFactory* factory) {
    103   return new BrowserAccessibilityManagerWin(
    104       GetDesktopWindow(), NULL, src, delegate, factory);
    105 }
    106 
    107 BrowserAccessibilityManagerWin*
    108 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
    109   return static_cast<BrowserAccessibilityManagerWin*>(this);
    110 }
    111 
    112 BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
    113     HWND parent_hwnd,
    114     IAccessible* parent_iaccessible,
    115     const AccessibilityNodeData& src,
    116     BrowserAccessibilityDelegate* delegate,
    117     BrowserAccessibilityFactory* factory)
    118     : BrowserAccessibilityManager(src, delegate, factory),
    119       parent_hwnd_(parent_hwnd),
    120       parent_iaccessible_(parent_iaccessible),
    121       tracked_scroll_object_(NULL),
    122       is_chrome_frame_(
    123           CommandLine::ForCurrentProcess()->HasSwitch("chrome-frame")),
    124       accessible_hwnd_(NULL) {
    125 }
    126 
    127 BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
    128   if (tracked_scroll_object_) {
    129     tracked_scroll_object_->Release();
    130     tracked_scroll_object_ = NULL;
    131   }
    132   if (accessible_hwnd_)
    133     accessible_hwnd_->OnManagerDeleted();
    134 }
    135 
    136 // static
    137 AccessibilityNodeData BrowserAccessibilityManagerWin::GetEmptyDocument() {
    138   AccessibilityNodeData empty_document;
    139   empty_document.id = 0;
    140   empty_document.role = blink::WebAXRoleRootWebArea;
    141   empty_document.state =
    142       (1 << blink::WebAXStateEnabled) |
    143       (1 << blink::WebAXStateReadonly) |
    144       (1 << blink::WebAXStateBusy);
    145   return empty_document;
    146 }
    147 
    148 void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent(DWORD event,
    149                                                              LONG child_id) {
    150   // Don't fire events if this view isn't hooked up to its parent.
    151   if (!parent_iaccessible())
    152     return;
    153 
    154 #if defined(USE_AURA)
    155   // If this is an Aura build on Win 7 and complete accessibility is
    156   // enabled, create a fake child HWND to use as the root of the
    157   // accessibility tree. See comments above AccessibleHWND for details.
    158   if (BrowserAccessibilityStateImpl::GetInstance()->IsAccessibleBrowser() &&
    159       !is_chrome_frame_ &&
    160       !accessible_hwnd_) {
    161     accessible_hwnd_ = new AccessibleHWND(parent_hwnd_, this);
    162     parent_hwnd_ = accessible_hwnd_->hwnd();
    163     parent_iaccessible_ = accessible_hwnd_->window_accessible();
    164   }
    165 #endif
    166 
    167   ::NotifyWinEvent(event, parent_hwnd(), OBJID_CLIENT, child_id);
    168 }
    169 
    170 void BrowserAccessibilityManagerWin::AddNodeToMap(BrowserAccessibility* node) {
    171   BrowserAccessibilityManager::AddNodeToMap(node);
    172   LONG unique_id_win = node->ToBrowserAccessibilityWin()->unique_id_win();
    173   unique_id_to_renderer_id_map_[unique_id_win] = node->renderer_id();
    174 }
    175 
    176 void BrowserAccessibilityManagerWin::RemoveNode(BrowserAccessibility* node) {
    177   unique_id_to_renderer_id_map_.erase(
    178       node->ToBrowserAccessibilityWin()->unique_id_win());
    179   BrowserAccessibilityManager::RemoveNode(node);
    180   if (node == tracked_scroll_object_) {
    181     tracked_scroll_object_->Release();
    182     tracked_scroll_object_ = NULL;
    183   }
    184 }
    185 
    186 void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent(
    187     blink::WebAXEvent event_type,
    188     BrowserAccessibility* node) {
    189   if (node->role() == blink::WebAXRoleInlineTextBox)
    190     return;
    191 
    192   LONG event_id = EVENT_MIN;
    193   switch (event_type) {
    194     case blink::WebAXEventActiveDescendantChanged:
    195       event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED;
    196       break;
    197     case blink::WebAXEventAlert:
    198       event_id = EVENT_SYSTEM_ALERT;
    199       break;
    200     case blink::WebAXEventAriaAttributeChanged:
    201       event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
    202       break;
    203     case blink::WebAXEventAutocorrectionOccured:
    204       event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED;
    205       break;
    206     case blink::WebAXEventBlur:
    207       // Equivalent to focus on the root.
    208       event_id = EVENT_OBJECT_FOCUS;
    209       node = GetRoot();
    210       break;
    211     case blink::WebAXEventCheckedStateChanged:
    212       event_id = EVENT_OBJECT_STATECHANGE;
    213       break;
    214     case blink::WebAXEventChildrenChanged:
    215       event_id = EVENT_OBJECT_REORDER;
    216       break;
    217     case blink::WebAXEventFocus:
    218       event_id = EVENT_OBJECT_FOCUS;
    219       break;
    220     case blink::WebAXEventInvalidStatusChanged:
    221       event_id = EVENT_OBJECT_STATECHANGE;
    222       break;
    223     case blink::WebAXEventLiveRegionChanged:
    224       // TODO: try not firing a native notification at all, since
    225       // on Windows, each individual item in a live region that changes
    226       // already gets its own notification.
    227       event_id = EVENT_OBJECT_REORDER;
    228       break;
    229     case blink::WebAXEventLoadComplete:
    230       event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE;
    231       break;
    232     case blink::WebAXEventMenuListItemSelected:
    233       event_id = EVENT_OBJECT_FOCUS;
    234       break;
    235     case blink::WebAXEventMenuListValueChanged:
    236       event_id = EVENT_OBJECT_VALUECHANGE;
    237       break;
    238     case blink::WebAXEventHide:
    239       event_id = EVENT_OBJECT_HIDE;
    240       break;
    241     case blink::WebAXEventShow:
    242       event_id = EVENT_OBJECT_SHOW;
    243       break;
    244     case blink::WebAXEventScrolledToAnchor:
    245       event_id = EVENT_SYSTEM_SCROLLINGSTART;
    246       break;
    247     case blink::WebAXEventSelectedChildrenChanged:
    248       event_id = EVENT_OBJECT_SELECTIONWITHIN;
    249       break;
    250     case blink::WebAXEventSelectedTextChanged:
    251       event_id = IA2_EVENT_TEXT_CARET_MOVED;
    252       break;
    253     case blink::WebAXEventTextChanged:
    254       event_id = EVENT_OBJECT_NAMECHANGE;
    255       break;
    256     case blink::WebAXEventTextInserted:
    257       event_id = IA2_EVENT_TEXT_INSERTED;
    258       break;
    259     case blink::WebAXEventTextRemoved:
    260       event_id = IA2_EVENT_TEXT_REMOVED;
    261       break;
    262     case blink::WebAXEventValueChanged:
    263       event_id = EVENT_OBJECT_VALUECHANGE;
    264       break;
    265     default:
    266       // Not all WebKit accessibility events result in a Windows
    267       // accessibility notification.
    268       break;
    269   }
    270 
    271   if (event_id != EVENT_MIN) {
    272     // Pass the node's unique id in the |child_id| argument to NotifyWinEvent;
    273     // the AT client will then call get_accChild on the HWND's accessibility
    274     // object and pass it that same id, which we can use to retrieve the
    275     // IAccessible for this node.
    276     LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win();
    277     MaybeCallNotifyWinEvent(event_id, child_id);
    278   }
    279 
    280   // If this is a layout complete notification (sent when a container scrolls)
    281   // and there is a descendant tracked object, send a notification on it.
    282   // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed.
    283   if (event_type == blink::WebAXEventLayoutComplete &&
    284       tracked_scroll_object_ &&
    285       tracked_scroll_object_->IsDescendantOf(node)) {
    286     MaybeCallNotifyWinEvent(
    287         IA2_EVENT_VISIBLE_DATA_CHANGED,
    288         tracked_scroll_object_->ToBrowserAccessibilityWin()->unique_id_win());
    289     tracked_scroll_object_->Release();
    290     tracked_scroll_object_ = NULL;
    291   }
    292 }
    293 
    294 void BrowserAccessibilityManagerWin::TrackScrollingObject(
    295     BrowserAccessibilityWin* node) {
    296   if (tracked_scroll_object_)
    297     tracked_scroll_object_->Release();
    298   tracked_scroll_object_ = node;
    299   tracked_scroll_object_->AddRef();
    300 }
    301 
    302 BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin(
    303     LONG unique_id_win) {
    304   base::hash_map<LONG, int32>::iterator iter =
    305       unique_id_to_renderer_id_map_.find(unique_id_win);
    306   if (iter != unique_id_to_renderer_id_map_.end()) {
    307     BrowserAccessibility* result = GetFromRendererID(iter->second);
    308     if (result)
    309       return result->ToBrowserAccessibilityWin();
    310   }
    311   return NULL;
    312 }
    313 
    314 void BrowserAccessibilityManagerWin::OnAccessibleHwndDeleted() {
    315   // If the AccessibleHWND is deleted, |parent_hwnd_| and
    316   // |parent_iaccessible_| are no longer valid either, since they were
    317   // derived from AccessibleHWND. We don't have to restore them to
    318   // previous values, though, because this should only happen
    319   // during the destruct sequence for this window.
    320   accessible_hwnd_ = NULL;
    321   parent_hwnd_ = NULL;
    322   parent_iaccessible_ = NULL;
    323 }
    324 
    325 }  // namespace content
    326