blob: 9aaec7112bbdb5ed7a7188647a77d471cdb7592d [file] [log] [blame]
// 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 <stddef.h>
#include <stdint.h>
#include <vector>
#include "base/command_line.h"
#include "base/win/scoped_variant.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 "content/public/common/content_switches.h"
#include "ui/accessibility/accessibility_switches.h"
#include "ui/accessibility/ax_role_properties.h"
#include "ui/accessibility/platform/ax_fragment_root_win.h"
#include "ui/accessibility/platform/ax_platform_node_delegate_utils_win.h"
#include "ui/base/win/atl_module.h"
namespace content {
// static
BrowserAccessibilityManager* BrowserAccessibilityManager::Create(
const ui::AXTreeUpdate& initial_tree,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory) {
return new BrowserAccessibilityManagerWin(initial_tree, delegate, factory);
}
BrowserAccessibilityManagerWin*
BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() {
return static_cast<BrowserAccessibilityManagerWin*>(this);
}
BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin(
const ui::AXTreeUpdate& initial_tree,
BrowserAccessibilityDelegate* delegate,
BrowserAccessibilityFactory* factory)
: BrowserAccessibilityManager(delegate, factory),
load_complete_pending_(false) {
ui::win::CreateATLModuleIfNeeded();
Initialize(initial_tree);
}
BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() {
// Destroy the tree in the subclass, rather than in the inherited
// destructor, otherwise our overrides of functions like
// OnNodeWillBeDeleted won't be called.
tree_.reset(NULL);
}
// static
ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() {
ui::AXNodeData empty_document;
empty_document.id = 0;
empty_document.role = ax::mojom::Role::kRootWebArea;
empty_document.AddBoolAttribute(ax::mojom::BoolAttribute::kBusy, true);
ui::AXTreeUpdate update;
update.root_id = empty_document.id;
update.nodes.push_back(empty_document);
return update;
}
HWND BrowserAccessibilityManagerWin::GetParentHWND() {
BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
if (!delegate)
return NULL;
return delegate->AccessibilityGetAcceleratedWidget();
}
void BrowserAccessibilityManagerWin::OnSubtreeWillBeDeleted(ui::AXTree* tree,
ui::AXNode* node) {
BrowserAccessibilityManager::OnSubtreeWillBeDeleted(tree, node);
BrowserAccessibility* obj = GetFromAXNode(node);
FireWinAccessibilityEvent(EVENT_OBJECT_HIDE, obj);
FireUiaStructureChangedEvent(StructureChangeType_ChildRemoved, obj);
if (obj && obj->GetRole() == ax::mojom::Role::kMenu) {
FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPEND, obj);
FireUiaAccessibilityEvent(UIA_MenuClosedEventId, obj);
}
}
void BrowserAccessibilityManagerWin::UserIsReloading() {
if (GetRoot())
FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_RELOAD, GetRoot());
}
BrowserAccessibility* BrowserAccessibilityManagerWin::GetFocus() const {
BrowserAccessibility* focus = BrowserAccessibilityManager::GetFocus();
return GetActiveDescendant(focus);
}
void BrowserAccessibilityManagerWin::FireFocusEvent(
BrowserAccessibility* node) {
BrowserAccessibilityManager::FireFocusEvent(node);
DCHECK(node);
FireWinAccessibilityEvent(EVENT_OBJECT_FOCUS, node);
FireUiaAccessibilityEvent(UIA_AutomationFocusChangedEventId, node);
}
void BrowserAccessibilityManagerWin::FireBlinkEvent(
ax::mojom::Event event_type,
BrowserAccessibility* node) {
BrowserAccessibilityManager::FireBlinkEvent(event_type, node);
switch (event_type) {
case ax::mojom::Event::kClicked:
if (ui::IsInvokable(node->GetData()))
FireUiaAccessibilityEvent(UIA_Invoke_InvokedEventId, node);
break;
case ax::mojom::Event::kEndOfTest: {
if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
// Event tests use kEndOfTest as a sentinel to mark the end of the test.
Microsoft::WRL::ComPtr<IUIAutomationRegistrar> registrar;
CoCreateInstance(CLSID_CUIAutomationRegistrar, NULL,
CLSCTX_INPROC_SERVER, IID_IUIAutomationRegistrar,
&registrar);
CHECK(registrar.Get());
UIAutomationEventInfo custom_event = {kUiaTestCompleteSentinelGuid,
kUiaTestCompleteSentinel};
EVENTID custom_event_id = 0;
CHECK(SUCCEEDED(
registrar->RegisterEvent(&custom_event, &custom_event_id)));
FireUiaAccessibilityEvent(custom_event_id, node);
}
break;
}
case ax::mojom::Event::kLocationChanged:
FireWinAccessibilityEvent(IA2_EVENT_VISIBLE_DATA_CHANGED, node);
break;
case ax::mojom::Event::kScrolledToAnchor:
FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGSTART, node);
break;
case ax::mojom::Event::kTextChanged:
FireUiaTextContainerEvent(UIA_Text_TextChangedEventId, node);
break;
case ax::mojom::Event::kTextSelectionChanged:
FireUiaTextContainerEvent(UIA_Text_TextSelectionChangedEventId, node);
break;
default:
break;
}
}
void BrowserAccessibilityManagerWin::FireGeneratedEvent(
ui::AXEventGenerator::Event event_type,
BrowserAccessibility* node) {
BrowserAccessibilityManager::FireGeneratedEvent(event_type, node);
bool can_fire_events = CanFireEvents();
if (event_type == ui::AXEventGenerator::Event::LOAD_COMPLETE &&
can_fire_events)
load_complete_pending_ = false;
if (load_complete_pending_ && can_fire_events && GetRoot()) {
load_complete_pending_ = false;
FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, GetRoot());
}
if (!can_fire_events && !load_complete_pending_ &&
event_type == ui::AXEventGenerator::Event::LOAD_COMPLETE && GetRoot() &&
!GetRoot()->IsOffscreen() && GetRoot()->PlatformChildCount() > 0) {
load_complete_pending_ = true;
}
switch (event_type) {
case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED:
FireUiaPropertyChangedEvent(UIA_AccessKeyPropertyId, node);
break;
case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED:
FireWinAccessibilityEvent(IA2_EVENT_ACTIVE_DESCENDANT_CHANGED, node);
break;
case ui::AXEventGenerator::Event::ALERT:
FireWinAccessibilityEvent(EVENT_SYSTEM_ALERT, node);
FireUiaAccessibilityEvent(UIA_SystemAlertEventId, node);
break;
case ui::AXEventGenerator::Event::ATOMIC_CHANGED:
case ui::AXEventGenerator::Event::BUSY_CHANGED:
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED:
FireUiaPropertyChangedEvent(UIA_ToggleToggleStatePropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::CHILDREN_CHANGED:
FireWinAccessibilityEvent(EVENT_OBJECT_REORDER, node);
FireUiaStructureChangedEvent(StructureChangeType_ChildrenReordered, node);
break;
case ui::AXEventGenerator::Event::CLASS_NAME_CHANGED:
FireUiaPropertyChangedEvent(UIA_ClassNamePropertyId, node);
break;
case ui::AXEventGenerator::Event::COLLAPSED:
case ui::AXEventGenerator::Event::EXPANDED:
FireUiaPropertyChangedEvent(
UIA_ExpandCollapseExpandCollapseStatePropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::CONTROLS_CHANGED:
FireUiaPropertyChangedEvent(UIA_ControllerForPropertyId, node);
break;
case ui::AXEventGenerator::Event::DESCRIBED_BY_CHANGED:
FireUiaPropertyChangedEvent(UIA_DescribedByPropertyId, node);
break;
case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED:
FireUiaPropertyChangedEvent(UIA_FullDescriptionPropertyId, node);
break;
case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED: {
// Fire the event on the object where the focus of the selection is.
int32_t focus_id = GetTreeData().sel_focus_object_id;
BrowserAccessibility* focus_object = GetFromID(focus_id);
if (focus_object && focus_object->HasVisibleCaretOrSelection())
FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object);
break;
}
// aria-dropeffect is deprecated in WAI-ARIA 1.1.
case ui::AXEventGenerator::Event::DROPEFFECT_CHANGED:
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::ENABLED_CHANGED:
FireUiaPropertyChangedEvent(UIA_IsEnabledPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::FLOW_FROM_CHANGED:
FireUiaPropertyChangedEvent(UIA_FlowsFromPropertyId, node);
break;
case ui::AXEventGenerator::Event::FLOW_TO_CHANGED:
FireUiaPropertyChangedEvent(UIA_FlowsToPropertyId, node);
break;
// aria-grabbed is deprecated in WAI-ARIA 1.1.
case ui::AXEventGenerator::Event::GRABBED_CHANGED:
case ui::AXEventGenerator::Event::HASPOPUP_CHANGED:
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED:
FireUiaPropertyChangedEvent(UIA_LevelPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED:
FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, node);
break;
case ui::AXEventGenerator::Event::INVALID_STATUS_CHANGED:
FireUiaPropertyChangedEvent(UIA_IsDataValidForFormPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED:
FireUiaPropertyChangedEvent(UIA_AcceleratorKeyPropertyId, node);
break;
case ui::AXEventGenerator::Event::LABELED_BY_CHANGED:
FireUiaPropertyChangedEvent(UIA_LabeledByPropertyId, node);
break;
case ui::AXEventGenerator::Event::LANGUAGE_CHANGED:
FireUiaPropertyChangedEvent(UIA_CulturePropertyId, node);
break;
case ui::AXEventGenerator::Event::LIVE_REGION_CREATED:
FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, node);
break;
case ui::AXEventGenerator::Event::LIVE_REGION_CHANGED:
// This event is redundant with the IA2_EVENT_TEXT_INSERTED events;
// however, JAWS 2018 and earlier do not process the text inserted
// events when "virtual cursor mode" is turned off (Insert+Z).
// Fortunately, firing the redudant event does not cause duplicate
// verbalizations in either screen reader.
// Future versions of JAWS may process the text inserted event when
// in focus mode, and so at some point the live region
// changed events may truly become redundant with the text inserted
// events. Note: Firefox does not fire this event, but JAWS processes
// Firefox live region events differently (utilizes MSAA's
// EVENT_OBJECT_SHOW).
FireWinAccessibilityEvent(EVENT_OBJECT_LIVEREGIONCHANGED, node);
FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, node);
break;
case ui::AXEventGenerator::Event::LIVE_STATUS_CHANGED:
FireUiaPropertyChangedEvent(UIA_LiveSettingPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::LOAD_COMPLETE:
FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, node);
break;
case ui::AXEventGenerator::Event::LAYOUT_INVALIDATED:
FireUiaAccessibilityEvent(UIA_LayoutInvalidatedEventId, node);
break;
case ui::AXEventGenerator::Event::LIVE_RELEVANT_CHANGED:
case ui::AXEventGenerator::Event::MULTILINE_STATE_CHANGED:
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED:
FireUiaPropertyChangedEvent(UIA_SelectionCanSelectMultiplePropertyId,
node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::NAME_CHANGED:
FireUiaPropertyChangedEvent(UIA_NamePropertyId, node);
break;
case ui::AXEventGenerator::Event::PLACEHOLDER_CHANGED:
FireUiaPropertyChangedEvent(UIA_HelpTextPropertyId, node);
break;
case ui::AXEventGenerator::Event::POSITION_IN_SET_CHANGED:
FireUiaPropertyChangedEvent(UIA_PositionInSetPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::READONLY_CHANGED:
if (ui::IsRangeValueSupported(node->GetData()))
FireUiaPropertyChangedEvent(UIA_RangeValueIsReadOnlyPropertyId, node);
else if (ui::IsValuePatternSupported(node))
FireUiaPropertyChangedEvent(UIA_ValueIsReadOnlyPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED:
FireUiaPropertyChangedEvent(UIA_IsRequiredForFormPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::ROLE_CHANGED:
FireUiaPropertyChangedEvent(UIA_AriaRolePropertyId, node);
break;
case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED:
FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, node);
FireUiaPropertyChangedEvent(UIA_ScrollHorizontalScrollPercentPropertyId,
node);
break;
case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED:
FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, node);
FireUiaPropertyChangedEvent(UIA_ScrollVerticalScrollPercentPropertyId,
node);
break;
case ui::AXEventGenerator::Event::SELECTED_CHANGED:
HandleSelectedStateChanged(node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED:
FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONWITHIN, node);
break;
case ui::AXEventGenerator::Event::SET_SIZE_CHANGED:
FireUiaPropertyChangedEvent(UIA_SizeOfSetPropertyId, node);
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::SORT_CHANGED:
aria_properties_events_.insert(node);
break;
case ui::AXEventGenerator::Event::SUBTREE_CREATED:
FireWinAccessibilityEvent(EVENT_OBJECT_SHOW, node);
FireUiaStructureChangedEvent(StructureChangeType_ChildAdded, node);
if (node->GetRole() == ax::mojom::Role::kMenu) {
FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPSTART, node);
FireUiaAccessibilityEvent(UIA_MenuOpenedEventId, node);
}
break;
case ui::AXEventGenerator::Event::VALUE_CHANGED:
FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, node);
if (ui::IsRangeValueSupported(node->GetData())) {
FireUiaPropertyChangedEvent(UIA_RangeValueValuePropertyId, node);
aria_properties_events_.insert(node);
} else if (ui::IsValuePatternSupported(node)) {
FireUiaPropertyChangedEvent(UIA_ValueValuePropertyId, node);
}
break;
case ui::AXEventGenerator::Event::VALUE_MAX_CHANGED:
if (IsRangeValueSupported(node->GetData())) {
FireUiaPropertyChangedEvent(UIA_RangeValueMaximumPropertyId, node);
aria_properties_events_.insert(node);
}
break;
case ui::AXEventGenerator::Event::VALUE_MIN_CHANGED:
if (IsRangeValueSupported(node->GetData())) {
FireUiaPropertyChangedEvent(UIA_RangeValueMinimumPropertyId, node);
aria_properties_events_.insert(node);
}
break;
case ui::AXEventGenerator::Event::VALUE_STEP_CHANGED:
if (IsRangeValueSupported(node->GetData())) {
FireUiaPropertyChangedEvent(UIA_RangeValueSmallChangePropertyId, node);
FireUiaPropertyChangedEvent(UIA_RangeValueLargeChangePropertyId, node);
}
break;
case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED:
case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED:
case ui::AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED:
case ui::AXEventGenerator::Event::LOAD_START:
case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED:
case ui::AXEventGenerator::Event::OTHER_ATTRIBUTE_CHANGED:
case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED:
case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED:
case ui::AXEventGenerator::Event::STATE_CHANGED:
// There are some notifications that aren't meaningful on Windows.
// It's okay to skip them.
break;
}
}
void BrowserAccessibilityManagerWin::FireWinAccessibilityEvent(
LONG win_event_type,
BrowserAccessibility* node) {
if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
return;
if (!ShouldFireEventForNode(node))
return;
HWND hwnd = GetParentHWND();
if (!hwnd)
return;
// Pass the negation of this 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.
LONG child_id = -(ToBrowserAccessibilityWin(node)->GetCOM()->GetUniqueId());
::NotifyWinEvent(win_event_type, hwnd, OBJID_CLIENT, child_id);
}
void BrowserAccessibilityManagerWin::FireUiaAccessibilityEvent(
LONG uia_event,
BrowserAccessibility* node) {
if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
return;
if (!ShouldFireEventForNode(node))
return;
::UiaRaiseAutomationEvent(ToBrowserAccessibilityWin(node)->GetCOM(),
uia_event);
}
void BrowserAccessibilityManagerWin::FireUiaPropertyChangedEvent(
LONG uia_property,
BrowserAccessibility* node) {
if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
return;
if (!ShouldFireEventForNode(node))
return;
// The old value is not used by the system
VARIANT old_value = {};
old_value.vt = VT_EMPTY;
auto* provider = ToBrowserAccessibilityWin(node)->GetCOM();
base::win::ScopedVariant new_value;
if (SUCCEEDED(
provider->GetPropertyValue(uia_property, new_value.Receive()))) {
::UiaRaiseAutomationPropertyChangedEvent(provider, uia_property, old_value,
new_value);
}
}
void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent(
StructureChangeType change_type,
BrowserAccessibility* node) {
if (!::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
return;
if (!ShouldFireEventForNode(node))
return;
auto* provider = ToBrowserAccessibilityWin(node);
auto* provider_com = provider ? provider->GetCOM() : nullptr;
if (!provider || !provider_com)
return;
switch (change_type) {
case StructureChangeType_ChildRemoved: {
// 'ChildRemoved' fires on the parent and provides the runtime ID of
// the removed child (which was passed as |node|).
auto* parent = ToBrowserAccessibilityWin(node->PlatformGetParent());
auto* parent_com = parent ? parent->GetCOM() : nullptr;
if (parent && parent_com) {
ui::AXPlatformNodeWin::RuntimeIdArray runtime_id;
provider_com->GetRuntimeIdArray(runtime_id);
UiaRaiseStructureChangedEvent(parent_com, change_type,
runtime_id.data(), runtime_id.size());
}
break;
}
default: {
// All other types are fired on |node|. For 'ChildAdded' |node| is the
// child that was added; for other types, it's the parent container.
UiaRaiseStructureChangedEvent(provider_com, change_type, nullptr, 0);
}
}
}
void BrowserAccessibilityManagerWin::FireUiaTextContainerEvent(
LONG uia_event,
BrowserAccessibility* node) {
// If the node supports text pattern, fire the event from itself, otherwise,
// fire the event from the closest ancestor that supports text pattern.
while (node) {
if (ToBrowserAccessibilityWin(node)->GetCOM()->IsPatternProviderSupported(
UIA_TextPatternId)) {
FireUiaAccessibilityEvent(uia_event, node);
return;
}
node = node->PlatformGetParent();
}
}
bool BrowserAccessibilityManagerWin::CanFireEvents() const {
if (!BrowserAccessibilityManager::CanFireEvents())
return false;
BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
if (!root_delegate)
return false;
HWND hwnd = root_delegate->AccessibilityGetAcceleratedWidget();
return hwnd != nullptr;
}
gfx::Rect BrowserAccessibilityManagerWin::GetViewBounds() {
// We have to take the device scale factor into account on Windows.
BrowserAccessibilityDelegate* delegate = GetDelegateFromRootManager();
if (delegate) {
gfx::Rect bounds = delegate->AccessibilityGetViewBounds();
if (device_scale_factor() > 0.0 && device_scale_factor() != 1.0)
bounds = ScaleToEnclosingRect(bounds, device_scale_factor());
return bounds;
}
return gfx::Rect();
}
void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished(
ui::AXTree* tree,
bool root_changed,
const std::vector<ui::AXTreeObserver::Change>& changes) {
BrowserAccessibilityManager::OnAtomicUpdateFinished(tree, root_changed,
changes);
if (root_changed && IsRootTree() &&
switches::IsExperimentalAccessibilityPlatformUIAEnabled()) {
// If a fragment root has been created, inform it that the content root has
// changed.
BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
if (root_delegate) {
ui::AXFragmentRootWin* fragment_root =
ui::AXFragmentRootWin::GetForAcceleratedWidget(
root_delegate->AccessibilityGetAcceleratedWidget());
if (fragment_root) {
fragment_root->SetChild(ToBrowserAccessibilityWin(GetRoot())->GetCOM());
}
}
}
// 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 nodes that need to be updated are all of the nodes that were changed,
// plus some parents.
std::map<BrowserAccessibilityComWin*, bool /* is_subtree_created */>
objs_to_update;
for (const auto& change : changes) {
const ui::AXNode* changed_node = change.node;
DCHECK(changed_node);
bool is_subtree_created = change.type == AXTreeObserver::SUBTREE_CREATED;
BrowserAccessibility* obj = GetFromAXNode(changed_node);
if (obj && obj->IsNative()) {
objs_to_update[ToBrowserAccessibilityWin(obj)->GetCOM()] =
is_subtree_created;
}
// When a node is a text node or line break, update its parent, because
// its text is part of its hypertext.
const ui::AXNode* parent = changed_node->parent();
if (!parent)
continue;
if (ui::IsTextOrLineBreak(changed_node->data().role)) {
BrowserAccessibility* parent_obj = GetFromAXNode(parent);
if (parent_obj && parent_obj->IsNative()) {
BrowserAccessibilityComWin* parent_com_obj =
ToBrowserAccessibilityWin(parent_obj)->GetCOM();
if (objs_to_update.find(parent_com_obj) == objs_to_update.end())
objs_to_update[parent_com_obj] = false;
}
}
// When a node is editable, update the editable root too.
if (!changed_node->data().HasState(ax::mojom::State::kEditable))
continue;
const ui::AXNode* editable_root = changed_node;
while (editable_root->parent() && editable_root->parent()->data().HasState(
ax::mojom::State::kEditable)) {
editable_root = editable_root->parent();
}
BrowserAccessibility* editable_root_obj = GetFromAXNode(editable_root);
if (editable_root_obj && editable_root_obj->IsNative()) {
BrowserAccessibilityComWin* editable_root_com_obj =
ToBrowserAccessibilityWin(editable_root_obj)->GetCOM();
if (objs_to_update.find(editable_root_com_obj) == objs_to_update.end())
objs_to_update[editable_root_com_obj] = false;
}
}
// The first step moves win_attributes_ to old_win_attributes_ and then
// recomputes all of win_attributes_ other than IAccessibleText.
for (auto& key_value : objs_to_update)
key_value.first->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 (auto& key_value : objs_to_update)
key_value.first->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 (auto& key_value : objs_to_update) {
BrowserAccessibilityComWin* obj = key_value.first;
bool is_subtree_created = key_value.second;
obj->UpdateStep3FireEvents(is_subtree_created);
}
}
bool BrowserAccessibilityManagerWin::ShouldFireEventForNode(
BrowserAccessibility* node) const {
if (!node || !node->CanFireEvents())
return false;
// If the root delegate isn't the main-frame, this may be a new frame that
// hasn't yet been swapped in or added to the frame tree. Suppress firing
// events until then.
BrowserAccessibilityDelegate* root_delegate = GetDelegateFromRootManager();
if (!root_delegate)
return false;
if (!root_delegate->AccessibilityIsMainFrame())
return false;
// 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 false;
// Inline text boxes are an internal implementation detail, we don't
// expose them to Windows.
if (node->GetRole() == ax::mojom::Role::kInlineTextBox)
return false;
return true;
}
void BrowserAccessibilityManagerWin::HandleSelectedStateChanged(
BrowserAccessibility* node) {
const bool is_selected =
node->GetBoolAttribute(ax::mojom::BoolAttribute::kSelected);
bool multiselect = false;
auto* selection_container = node->PlatformGetSelectionContainer();
if (selection_container &&
selection_container->HasState(ax::mojom::State::kMultiselectable))
multiselect = true;
if (multiselect) {
if (is_selected) {
FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONADD, node);
if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
selection_events_[selection_container].added.push_back(node);
} else {
FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONREMOVE, node);
if (::switches::IsExperimentalAccessibilityPlatformUIAEnabled())
selection_events_[selection_container].removed.push_back(node);
}
} else if (is_selected) {
FireWinAccessibilityEvent(EVENT_OBJECT_SELECTION, node);
FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId, node);
}
}
void BrowserAccessibilityManagerWin::FinalizeAccessibilityEvents() {
BrowserAccessibilityManager::FinalizeAccessibilityEvents();
for (auto&& event_node : aria_properties_events_) {
FireUiaPropertyChangedEvent(UIA_AriaPropertiesPropertyId, event_node);
}
aria_properties_events_.clear();
for (auto&& selected : selection_events_) {
auto* container = selected.first;
auto&& changes = selected.second;
// Count the number of selected items
size_t selected_count = 0;
BrowserAccessibility* first_selected_child = nullptr;
for (size_t i = 0; i < container->InternalChildCount(); ++i) {
auto* child = container->InternalGetChild(i);
if (child->GetBoolAttribute(ax::mojom::BoolAttribute::kSelected)) {
if (!first_selected_child)
first_selected_child = child;
selected_count++;
}
}
if (selected_count == 1) {
// Fire 'ElementSelected' on the only selected child
FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId,
first_selected_child);
} else {
// Per UIA documentation, beyond the "invalidate limit" we're supposed to
// fire a 'SelectionInvalidated' event. The exact value isn't specified,
// but System.Windows.Automation.Provider uses a value of 20.
static const size_t kInvalidateLimit = 20;
if ((changes.added.size() + changes.removed.size()) > kInvalidateLimit) {
FireUiaAccessibilityEvent(UIA_Selection_InvalidatedEventId, container);
} else {
for (auto* item : changes.added) {
FireUiaAccessibilityEvent(
UIA_SelectionItem_ElementAddedToSelectionEventId, item);
}
for (auto* item : changes.removed) {
FireUiaAccessibilityEvent(
UIA_SelectionItem_ElementRemovedFromSelectionEventId, item);
}
}
}
}
selection_events_.clear();
}
BrowserAccessibilityManagerWin::SelectionEvents::SelectionEvents() = default;
BrowserAccessibilityManagerWin::SelectionEvents::~SelectionEvents() = default;
} // namespace content