• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
Create(const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)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*
ToBrowserAccessibilityManagerWin()29 BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
30   return static_cast<BrowserAccessibilityManagerWin*>(this);
31 }
32 
BrowserAccessibilityManagerWin(LegacyRenderWidgetHostHWND * accessible_hwnd,IAccessible * parent_iaccessible,const ui::AXTreeUpdate & initial_tree,BrowserAccessibilityDelegate * delegate,BrowserAccessibilityFactory * factory)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 
~BrowserAccessibilityManagerWin()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
GetEmptyDocument()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 
SetAccessibleHWND(LegacyRenderWidgetHostHWND * accessible_hwnd)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 
MaybeCallNotifyWinEvent(DWORD event,LONG child_id)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 
OnNodeCreated(ui::AXNode * node)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 
OnNodeWillBeDeleted(ui::AXNode * node)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 
OnWindowFocused()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 
NotifyAccessibilityEvent(ui::AXEvent event_type,BrowserAccessibility * node)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 
OnRootChanged(ui::AXNode * new_root)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 
TrackScrollingObject(BrowserAccessibilityWin * node)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 
GetFromUniqueIdWin(LONG unique_id_win)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 
OnAccessibleHwndDeleted()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