| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "content/browser/accessibility/browser_accessibility_manager_win.h" |
| |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/win/scoped_comptr.h" |
| #include "base/win/windows_version.h" |
| #include "content/browser/accessibility/browser_accessibility_state_impl.h" |
| #include "content/browser/accessibility/browser_accessibility_win.h" |
| #include "content/browser/renderer_host/legacy_render_widget_host_win.h" |
| #include "content/common/accessibility_messages.h" |
| #include "ui/base/win/atl_module.h" |
| |
| namespace content { |
| |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::Create( |
| const SimpleAXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) { |
| return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory); |
| } |
| |
| BrowserAccessibilityManagerWin* |
| BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { |
| return static_cast<BrowserAccessibilityManagerWin*>(this); |
| } |
| |
| BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( |
| const SimpleAXTreeUpdate& initial_tree, |
| BrowserAccessibilityDelegate* delegate, |
| BrowserAccessibilityFactory* factory) |
| : BrowserAccessibilityManager(delegate, factory), |
| tracked_scroll_object_(NULL), |
| focus_event_on_root_needed_(false), |
| inside_on_window_focused_(false) { |
| ui::win::CreateATLModuleIfNeeded(); |
| Initialize(initial_tree); |
| } |
| |
| BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { |
| if (tracked_scroll_object_) { |
| tracked_scroll_object_->Release(); |
| tracked_scroll_object_ = NULL; |
| } |
| } |
| |
| // static |
| SimpleAXTreeUpdate |
| BrowserAccessibilityManagerWin::GetEmptyDocument() { |
| ui::AXNodeData empty_document; |
| empty_document.id = 0; |
| empty_document.role = ui::AX_ROLE_ROOT_WEB_AREA; |
| empty_document.state = |
| (1 << ui::AX_STATE_ENABLED) | |
| (1 << ui::AX_STATE_READ_ONLY) | |
| (1 << ui::AX_STATE_BUSY); |
| |
| SimpleAXTreeUpdate update; |
| update.nodes.push_back(empty_document); |
| return update; |
| } |
| |
| HWND BrowserAccessibilityManagerWin::GetParentHWND() { |
| if (!delegate_) |
| return NULL; |
| return delegate_->AccessibilityGetAcceleratedWidget(); |
| } |
| |
| IAccessible* BrowserAccessibilityManagerWin::GetParentIAccessible() { |
| if (!delegate_) |
| return NULL; |
| return delegate_->AccessibilityGetNativeViewAccessible(); |
| } |
| |
| void BrowserAccessibilityManagerWin::MaybeCallNotifyWinEvent( |
| DWORD event, BrowserAccessibility* node) { |
| BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager(); |
| if (!delegate) { |
| // This line and other LOG(WARNING) lines are temporary, to debug |
| // flaky failures in DumpAccessibilityEvent* tests. |
| // http://crbug.com/440579 |
| LOG(WARNING) << "Not firing AX event because of no delegate"; |
| return; |
| } |
| |
| if (!node->IsNative()) |
| return; |
| |
| HWND hwnd = delegate->AccessibilityGetAcceleratedWidget(); |
| if (!hwnd) { |
| LOG(WARNING) << "Not firing AX event because of no hwnd"; |
| return; |
| } |
| |
| // Inline text boxes are an internal implementation detail, we don't |
| // expose them to Windows. |
| if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX) |
| return; |
| |
| // It doesn't make sense to fire a REORDER event on a leaf node; that |
| // happens when the node has internal children line inline text boxes. |
| if (event == EVENT_OBJECT_REORDER && node->PlatformIsLeaf()) |
| return; |
| |
| // Don't fire focus, or load complete notifications if the |
| // window isn't focused, because that can confuse screen readers into |
| // entering their "browse" mode. |
| if ((event == EVENT_OBJECT_FOCUS || |
| event == IA2_EVENT_DOCUMENT_LOAD_COMPLETE) && |
| (!delegate_->AccessibilityViewHasFocus())) { |
| return; |
| } |
| |
| // NVDA gets confused if we focus the main document element when it hasn't |
| // finished loading and it has no children at all, so suppress that event. |
| if (event == EVENT_OBJECT_FOCUS && |
| node == GetRoot() && |
| node->PlatformChildCount() == 0 && |
| !node->HasState(ui::AX_STATE_BUSY) && |
| !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) { |
| return; |
| } |
| |
| // If a focus event is needed on the root, fire that first before |
| // this event. |
| if (event == EVENT_OBJECT_FOCUS && node == GetRoot()) |
| focus_event_on_root_needed_ = false; |
| else if (focus_event_on_root_needed_) |
| OnWindowFocused(); |
| |
| LONG child_id = node->ToBrowserAccessibilityWin()->unique_id_win(); |
| ::NotifyWinEvent(event, hwnd, OBJID_CLIENT, child_id); |
| } |
| |
| void BrowserAccessibilityManagerWin::OnWindowFocused() { |
| // Make sure we don't call this recursively. |
| if (inside_on_window_focused_) |
| return; |
| inside_on_window_focused_ = true; |
| |
| // This is called either when this web frame gets focused, or when |
| // the root of the accessibility tree changes. In both cases, we need |
| // to fire a focus event on the root and then on the focused element |
| // within the page, if different. |
| |
| // Set this flag so that we'll keep trying to fire these focus events |
| // if they're not successful this time. |
| focus_event_on_root_needed_ = true; |
| |
| if (!delegate_ || !delegate_->AccessibilityViewHasFocus()) { |
| inside_on_window_focused_ = false; |
| return; |
| } |
| |
| // Try to fire a focus event on the root first and then the focused node. |
| // This will clear focus_event_on_root_needed_ if successful. |
| if (focus_ != tree_->root() && GetRoot()) |
| NotifyAccessibilityEvent(ui::AX_EVENT_FOCUS, GetRoot()); |
| BrowserAccessibilityManager::OnWindowFocused(); |
| inside_on_window_focused_ = false; |
| } |
| |
| void BrowserAccessibilityManagerWin::UserIsReloading() { |
| if (GetRoot()) |
| MaybeCallNotifyWinEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot()); |
| } |
| |
| void BrowserAccessibilityManagerWin::NotifyAccessibilityEvent( |
| ui::AXEvent event_type, |
| BrowserAccessibility* node) { |
| BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager(); |
| if (!root_delegate || !root_delegate->AccessibilityGetAcceleratedWidget()) { |
| LOG(WARNING) << "Not firing AX event because of no root_delegate or hwnd"; |
| return; |
| } |
| |
| // Don't fire events when this document might be stale as the user has |
| // started navigating to a new document. |
| if (user_is_navigating_away_) |
| return; |
| |
| // Inline text boxes are an internal implementation detail, we don't |
| // expose them to Windows. |
| if (node->GetRole() == ui::AX_ROLE_INLINE_TEXT_BOX) |
| return; |
| |
| // Don't fire focus, blur, or load complete notifications if the |
| // window isn't focused, because that can confuse screen readers into |
| // entering their "browse" mode. |
| if ((event_type == ui::AX_EVENT_FOCUS || |
| event_type == ui::AX_EVENT_BLUR || |
| event_type == ui::AX_EVENT_LOAD_COMPLETE) && |
| !root_delegate->AccessibilityViewHasFocus()) { |
| return; |
| } |
| |
| // NVDA gets confused if we focus the main document element when it hasn't |
| // finished loading and it has no children at all, so suppress that event. |
| if (event_type == ui::AX_EVENT_FOCUS && |
| node == GetRoot() && |
| node->PlatformChildCount() == 0 && |
| !node->HasState(ui::AX_STATE_BUSY) && |
| !node->GetBoolAttribute(ui::AX_ATTR_DOC_LOADED)) { |
| return; |
| } |
| |
| // If a focus event is needed on the root, fire that first before |
| // this event. |
| if (event_type == ui::AX_EVENT_FOCUS && node == GetRoot()) |
| focus_event_on_root_needed_ = false; |
| else if (focus_event_on_root_needed_) |
| OnWindowFocused(); |
| |
| LONG event_id = EVENT_MIN; |
| switch (event_type) { |
| case ui::AX_EVENT_ACTIVEDESCENDANTCHANGED: |
| event_id = IA2_EVENT_ACTIVE_DESCENDANT_CHANGED; |
| break; |
| case ui::AX_EVENT_ALERT: |
| event_id = EVENT_SYSTEM_ALERT; |
| break; |
| case ui::AX_EVENT_AUTOCORRECTION_OCCURED: |
| event_id = IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED; |
| break; |
| case ui::AX_EVENT_BLUR: |
| // Equivalent to focus on the root. |
| event_id = EVENT_OBJECT_FOCUS; |
| node = GetRoot(); |
| break; |
| case ui::AX_EVENT_CHILDREN_CHANGED: |
| event_id = EVENT_OBJECT_REORDER; |
| break; |
| case ui::AX_EVENT_FOCUS: |
| event_id = EVENT_OBJECT_FOCUS; |
| break; |
| case ui::AX_EVENT_LIVE_REGION_CHANGED: |
| if (node->GetBoolAttribute(ui::AX_ATTR_CONTAINER_LIVE_BUSY)) |
| return; |
| event_id = EVENT_OBJECT_LIVEREGIONCHANGED; |
| break; |
| case ui::AX_EVENT_LOAD_COMPLETE: |
| event_id = IA2_EVENT_DOCUMENT_LOAD_COMPLETE; |
| break; |
| case ui::AX_EVENT_SCROLL_POSITION_CHANGED: |
| event_id = EVENT_SYSTEM_SCROLLINGEND; |
| break; |
| case ui::AX_EVENT_SCROLLED_TO_ANCHOR: |
| event_id = EVENT_SYSTEM_SCROLLINGSTART; |
| break; |
| case ui::AX_EVENT_SELECTED_CHILDREN_CHANGED: |
| event_id = EVENT_OBJECT_SELECTIONWITHIN; |
| break; |
| case ui::AX_EVENT_TEXT_SELECTION_CHANGED: |
| event_id = IA2_EVENT_TEXT_CARET_MOVED; |
| break; |
| default: |
| // Not all WebKit accessibility events result in a Windows |
| // accessibility notification. |
| break; |
| } |
| |
| if (!node) |
| return; |
| |
| if (event_id != EVENT_MIN) { |
| // Pass the node's unique id in the |child_id| argument to NotifyWinEvent; |
| // the AT client will then call get_accChild on the HWND's accessibility |
| // object and pass it that same id, which we can use to retrieve the |
| // IAccessible for this node. |
| MaybeCallNotifyWinEvent(event_id, node); |
| } |
| |
| // If this is a layout complete notification (sent when a container scrolls) |
| // and there is a descendant tracked object, send a notification on it. |
| // TODO(dmazzoni): remove once http://crbug.com/113483 is fixed. |
| if (event_type == ui::AX_EVENT_LAYOUT_COMPLETE && |
| tracked_scroll_object_ && |
| tracked_scroll_object_->IsDescendantOf(node)) { |
| MaybeCallNotifyWinEvent( |
| IA2_EVENT_VISIBLE_DATA_CHANGED, tracked_scroll_object_); |
| tracked_scroll_object_->Release(); |
| tracked_scroll_object_ = NULL; |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::OnNodeCreated(ui::AXTree* tree, |
| ui::AXNode* node) { |
| BrowserAccessibilityManager::OnNodeCreated(tree, node); |
| BrowserAccessibility* obj = GetFromAXNode(node); |
| if (!obj) |
| return; |
| if (!obj->IsNative()) |
| return; |
| LONG unique_id_win = obj->ToBrowserAccessibilityWin()->unique_id_win(); |
| unique_id_to_ax_id_map_[unique_id_win] = obj->GetId(); |
| unique_id_to_ax_tree_id_map_[unique_id_win] = ax_tree_id_; |
| } |
| |
| void BrowserAccessibilityManagerWin::OnNodeWillBeDeleted(ui::AXTree* tree, |
| ui::AXNode* node) { |
| BrowserAccessibilityManager::OnNodeWillBeDeleted(tree, node); |
| BrowserAccessibility* obj = GetFromAXNode(node); |
| if (!obj) |
| return; |
| if (!obj->IsNative()) |
| return; |
| unique_id_to_ax_id_map_.erase( |
| obj->ToBrowserAccessibilityWin()->unique_id_win()); |
| unique_id_to_ax_tree_id_map_.erase( |
| obj->ToBrowserAccessibilityWin()->unique_id_win()); |
| if (obj == tracked_scroll_object_) { |
| tracked_scroll_object_->Release(); |
| tracked_scroll_object_ = NULL; |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished( |
| ui::AXTree* tree, |
| bool root_changed, |
| const std::vector<ui::AXTreeDelegate::Change>& changes) { |
| BrowserAccessibilityManager::OnAtomicUpdateFinished( |
| tree, root_changed, changes); |
| |
| if (root_changed) { |
| // In order to make screen readers aware of the new accessibility root, |
| // we need to fire a focus event on it. |
| OnWindowFocused(); |
| } |
| |
| // Do a sequence of Windows-specific updates on each node. Each one is |
| // done in a single pass that must complete before the next step starts. |
| // The first step moves win_attributes_ to old_win_attributes_ and then |
| // recomputes all of win_attributes_ other than IAccessibleText. |
| for (size_t i = 0; i < changes.size(); ++i) { |
| BrowserAccessibility* obj = GetFromAXNode(changes[i].node); |
| if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) |
| obj->ToBrowserAccessibilityWin()->UpdateStep1ComputeWinAttributes(); |
| } |
| |
| // The next step updates the hypertext of each node, which is a |
| // concatenation of all of its child text nodes, so it can't run until |
| // the text of all of the nodes was computed in the previous step. |
| for (size_t i = 0; i < changes.size(); ++i) { |
| BrowserAccessibility* obj = GetFromAXNode(changes[i].node); |
| if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) |
| obj->ToBrowserAccessibilityWin()->UpdateStep2ComputeHypertext(); |
| } |
| |
| // The third step fires events on nodes based on what's changed - like |
| // if the name, value, or description changed, or if the hypertext had |
| // text inserted or removed. It's able to figure out exactly what changed |
| // because we still have old_win_attributes_ populated. |
| // This step has to run after the previous two steps complete because the |
| // client may walk the tree when it receives any of these events. |
| // At the end, it deletes old_win_attributes_ since they're not needed |
| // anymore. |
| for (size_t i = 0; i < changes.size(); ++i) { |
| BrowserAccessibility* obj = GetFromAXNode(changes[i].node); |
| if (obj && obj->IsNative() && !obj->PlatformIsChildOfLeaf()) { |
| obj->ToBrowserAccessibilityWin()->UpdateStep3FireEvents( |
| changes[i].type == AXTreeDelegate::SUBTREE_CREATED); |
| } |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::TrackScrollingObject( |
| BrowserAccessibilityWin* node) { |
| if (tracked_scroll_object_) |
| tracked_scroll_object_->Release(); |
| tracked_scroll_object_ = node; |
| tracked_scroll_object_->AddRef(); |
| } |
| |
| BrowserAccessibilityWin* BrowserAccessibilityManagerWin::GetFromUniqueIdWin( |
| LONG unique_id_win) { |
| auto tree_iter = unique_id_to_ax_tree_id_map_.find(unique_id_win); |
| if (tree_iter == unique_id_to_ax_tree_id_map_.end()) |
| return nullptr; |
| |
| int tree_id = tree_iter->second; |
| if (tree_id != ax_tree_id_) { |
| BrowserAccessibilityManagerWin* manager = |
| BrowserAccessibilityManager::FromID(tree_id) |
| ->ToBrowserAccessibilityManagerWin(); |
| if (!manager) |
| return nullptr; |
| if (manager != this) |
| return manager->GetFromUniqueIdWin(unique_id_win); |
| return nullptr; |
| } |
| |
| auto iter = unique_id_to_ax_id_map_.find(unique_id_win); |
| if (iter == unique_id_to_ax_id_map_.end()) |
| return nullptr; |
| |
| BrowserAccessibility* result = GetFromID(iter->second); |
| if (result && result->IsNative()) |
| return result->ToBrowserAccessibilityWin(); |
| |
| return nullptr; |
| } |
| |
| } // namespace content |