| // Copyright 2012 The Chromium Authors |
| // 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 <set> |
| #include <vector> |
| |
| #include "base/command_line.h" |
| #include "base/containers/contains.h" |
| #include "base/strings/stringprintf.h" |
| #include "base/trace_event/typed_macros.h" |
| #include "base/win/scoped_bstr.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/public/common/content_switches.h" |
| #include "ui/accessibility/ax_role_properties.h" |
| #include "ui/accessibility/platform/ax_fragment_root_win.h" |
| #include "ui/accessibility/platform/ax_platform.h" |
| #include "ui/accessibility/platform/ax_platform_node_delegate_utils_win.h" |
| #include "ui/accessibility/platform/ax_platform_node_textprovider_win.h" |
| #include "ui/accessibility/platform/ax_platform_tree_manager_delegate.h" |
| #include "ui/accessibility/platform/uia_registrar_win.h" |
| #include "ui/base/win/atl_module.h" |
| |
| namespace content { |
| |
| namespace { |
| |
| #if DCHECK_IS_ON() |
| #define DCHECK_IN_ON_ACCESSIBILITY_EVENTS() \ |
| DCHECK(in_on_accessibility_events_) \ |
| << "This method should only be called during OnAccessibilityEvents " \ |
| "because memoized information is cleared afterwards in " \ |
| "FinalizeAccessibilityEvents" |
| #else |
| #define DCHECK_IN_ON_ACCESSIBILITY_EVENTS() |
| #endif // DCHECK_IS_ON() |
| |
| BrowserAccessibility* GetUiaTextPatternProvider(BrowserAccessibility& node) { |
| for (BrowserAccessibility* current_node = &node; current_node; |
| current_node = current_node->PlatformGetParent()) { |
| if (ToBrowserAccessibilityWin(current_node) |
| ->GetCOM() |
| ->IsPatternProviderSupported(UIA_TextPatternId)) { |
| return current_node; |
| } |
| } |
| |
| return nullptr; |
| } |
| |
| } // namespace |
| |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::Create( |
| const ui::AXTreeUpdate& initial_tree, |
| ui::AXPlatformTreeManagerDelegate* delegate) { |
| return new BrowserAccessibilityManagerWin(initial_tree, delegate); |
| } |
| |
| // static |
| BrowserAccessibilityManager* BrowserAccessibilityManager::Create( |
| ui::AXPlatformTreeManagerDelegate* delegate) { |
| return new BrowserAccessibilityManagerWin( |
| BrowserAccessibilityManagerWin::GetEmptyDocument(), delegate); |
| } |
| |
| BrowserAccessibilityManagerWin* |
| BrowserAccessibilityManager::ToBrowserAccessibilityManagerWin() { |
| return static_cast<BrowserAccessibilityManagerWin*>(this); |
| } |
| |
| BrowserAccessibilityManagerWin::BrowserAccessibilityManagerWin( |
| const ui::AXTreeUpdate& initial_tree, |
| ui::AXPlatformTreeManagerDelegate* delegate) |
| : BrowserAccessibilityManager(delegate) { |
| ui::win::CreateATLModuleIfNeeded(); |
| Initialize(initial_tree); |
| } |
| |
| BrowserAccessibilityManagerWin::~BrowserAccessibilityManagerWin() { |
| // In some cases, an iframe's HWND is destroyed before the hypertext |
| // on the parent child tree owner is destroyed. In this case, we reset |
| // the parent's hypertext to avoid API calls involving stale hypertext. |
| BrowserAccessibility* parent = |
| GetParentNodeFromParentTreeAsBrowserAccessibility(); |
| if (!parent) { |
| return; |
| } |
| |
| ui::AXPlatformNode* parent_ax_platform_node = parent->GetAXPlatformNode(); |
| if (!parent_ax_platform_node) { |
| return; |
| } |
| |
| static_cast<ui::AXPlatformNodeWin*>(parent_ax_platform_node) |
| ->ResetComputedHypertext(); |
| } |
| |
| // static |
| ui::AXTreeUpdate BrowserAccessibilityManagerWin::GetEmptyDocument() { |
| ui::AXNodeData empty_document; |
| empty_document.id = ui::kInitialEmptyDocumentRootNodeID; |
| 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() const { |
| ui::AXPlatformTreeManagerDelegate* delegate = GetDelegateFromRootManager(); |
| if (!delegate) |
| return NULL; |
| return delegate->AccessibilityGetAcceleratedWidget(); |
| } |
| |
| void BrowserAccessibilityManagerWin::UserIsReloading() { |
| if (GetBrowserAccessibilityRoot()) |
| FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_RELOAD, |
| GetBrowserAccessibilityRoot()); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireAriaNotificationEvent( |
| BrowserAccessibility* node, |
| const std::string& announcement, |
| const std::string& notification_id, |
| ax::mojom::AriaNotificationInterrupt interrupt_property, |
| ax::mojom::AriaNotificationPriority priority_property) { |
| DCHECK(node); |
| |
| // This API is only supported from Windows10 (version 1709) onwards. |
| // Check if the function pointer is valid or not. |
| using UiaRaiseNotificationEventFunction = |
| HRESULT(WINAPI*)(IRawElementProviderSimple*, NotificationKind, |
| NotificationProcessing, BSTR, BSTR); |
| static const UiaRaiseNotificationEventFunction |
| uia_raise_notification_event_func = |
| reinterpret_cast<UiaRaiseNotificationEventFunction>( |
| ::GetProcAddress(GetModuleHandle(L"uiautomationcore.dll"), |
| "UiaRaiseNotificationEvent")); |
| if (!uia_raise_notification_event_func) { |
| return; |
| } |
| |
| auto MapPropertiesToUiaNotificationProcessing = |
| [&]() -> NotificationProcessing { |
| switch (interrupt_property) { |
| case ax::mojom::AriaNotificationInterrupt::kNone: |
| switch (priority_property) { |
| case ax::mojom::AriaNotificationPriority::kNone: |
| return NotificationProcessing_All; |
| case ax::mojom::AriaNotificationPriority::kImportant: |
| return NotificationProcessing_ImportantAll; |
| } |
| case ax::mojom::AriaNotificationInterrupt::kAll: |
| switch (priority_property) { |
| case ax::mojom::AriaNotificationPriority::kNone: |
| return NotificationProcessing_MostRecent; |
| case ax::mojom::AriaNotificationPriority::kImportant: |
| return NotificationProcessing_ImportantMostRecent; |
| } |
| case ax::mojom::AriaNotificationInterrupt::kPending: |
| switch (priority_property) { |
| case ax::mojom::AriaNotificationPriority::kNone: |
| return NotificationProcessing_CurrentThenMostRecent; |
| case ax::mojom::AriaNotificationPriority::kImportant: |
| // This is resolved the same as `AriaNotificationInterrupt::kAll`, |
| // but UIA doesn't have a specific enum value for these options yet. |
| return NotificationProcessing_ImportantMostRecent; |
| } |
| } |
| NOTREACHED_NORETURN(); |
| }; |
| |
| const base::win::ScopedBstr announcement_bstr(base::UTF8ToWide(announcement)); |
| const base::win::ScopedBstr notification_id_bstr( |
| base::UTF8ToWide(notification_id)); |
| |
| uia_raise_notification_event_func(ToBrowserAccessibilityWin(node)->GetCOM(), |
| NotificationKind_ActionCompleted, |
| MapPropertiesToUiaNotificationProcessing(), |
| announcement_bstr.Get(), |
| notification_id_bstr.Get()); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireFocusEvent(ui::AXNode* node) { |
| ui::AXTreeManager::FireFocusEvent(node); |
| DCHECK(node); |
| BrowserAccessibility* wrapper = GetFromAXNode(node); |
| FireWinAccessibilityEvent(EVENT_OBJECT_FOCUS, wrapper); |
| FireUiaAccessibilityEvent(UIA_AutomationFocusChangedEventId, wrapper); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireBlinkEvent(ax::mojom::Event event_type, |
| BrowserAccessibility* node, |
| int action_request_id) { |
| DCHECK(CanFireEvents()); |
| |
| BrowserAccessibilityManager::FireBlinkEvent(event_type, node, |
| action_request_id); |
| |
| switch (event_type) { |
| case ax::mojom::Event::kClicked: |
| if (node->GetData().IsInvocable()) |
| FireUiaAccessibilityEvent(UIA_Invoke_InvokedEventId, node); |
| break; |
| case ax::mojom::Event::kEndOfTest: |
| // Event tests use kEndOfTest as a sentinel to mark the end of the test. |
| FireUiaAccessibilityEvent( |
| ui::UiaRegistrarWin::GetInstance().GetTestCompleteEventId(), node); |
| break; |
| case ax::mojom::Event::kLoadComplete: |
| FireWinAccessibilityEvent(IA2_EVENT_DOCUMENT_LOAD_COMPLETE, node); |
| FireUiaAccessibilityEvent(UIA_AsyncContentLoadedEventId, 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); |
| FireUiaActiveTextPositionChangedEvent(node); |
| break; |
| } |
| case ax::mojom::Event::kTextChanged: |
| // TODO(crbug.com/40672441) Remove when Views are exposed in the AXTree |
| // which will fire generated text-changed events. |
| if (!node->IsWebContent()) |
| EnqueueTextChangedEvent(*node); |
| break; |
| default: |
| break; |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::FireGeneratedEvent( |
| ui::AXEventGenerator::Event event_type, |
| const ui::AXNode* node) { |
| BrowserAccessibilityManager::FireGeneratedEvent(event_type, node); |
| BrowserAccessibility* wrapper = GetFromAXNode(node); |
| DCHECK(wrapper); |
| switch (event_type) { |
| case ui::AXEventGenerator::Event::ACCESS_KEY_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_AccessKeyPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ACTIVE_DESCENDANT_CHANGED: |
| FireWinAccessibilityEvent(IA2_EVENT_ACTIVE_DESCENDANT_CHANGED, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ALERT: |
| FireWinAccessibilityEvent(EVENT_SYSTEM_ALERT, wrapper); |
| // Generated 'ALERT' events come from role=alert nodes in the tree. |
| // These should just be treated as normal live region changed events, |
| // since we don't want web pages to be performing system-wide alerts. |
| FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ATOMIC_CHANGED: |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::BUSY_CHANGED: |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::CHECKED_STATE_CHANGED: |
| // https://www.w3.org/TR/core-aam-1.1/#mapping_state-property_table |
| // SelectionItem.IsSelected is set according to the True or False value of |
| // aria-checked for 'radio' and 'menuitemradio' roles. |
| if (ui::IsRadio(wrapper->GetRole())) { |
| HandleSelectedStateChanged(uia_selection_events_, wrapper, |
| IsUIANodeSelected(wrapper)); |
| } |
| FireUiaPropertyChangedEvent(UIA_ToggleToggleStatePropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::CHILDREN_CHANGED: { |
| // If this node is ignored, fire the event on the platform parent since |
| // ignored nodes cannot raise events. |
| BrowserAccessibility* target_node = |
| wrapper->IsIgnored() ? wrapper->PlatformGetParent() : wrapper; |
| if (target_node) { |
| FireWinAccessibilityEvent(EVENT_OBJECT_REORDER, target_node); |
| FireUiaStructureChangedEvent(StructureChangeType_ChildrenReordered, |
| target_node); |
| } |
| break; |
| } |
| case ui::AXEventGenerator::Event::COLLAPSED: |
| case ui::AXEventGenerator::Event::EXPANDED: |
| FireUiaPropertyChangedEvent( |
| UIA_ExpandCollapseExpandCollapseStatePropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::CONTROLS_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_ControllerForPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::DESCRIBED_BY_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_DescribedByPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::DESCRIPTION_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_FullDescriptionPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::DOCUMENT_SELECTION_CHANGED: { |
| // Fire the event on the object where the focus of the selection is. This |
| // is because the focus is the only endpoint that can move, and because |
| // the caret (if present) is at the focus. Since this is a focus-related |
| // event (and focused nodes are never ignored), we can query the focused |
| // node directly from the AXTree, and avoid calling GetUnignoredSelection. |
| ui::AXNodeID focus_id = ax_tree()->data().sel_focus_object_id; |
| BrowserAccessibility* focus_object = GetFromID(focus_id); |
| if (focus_object) { |
| EnqueueSelectionChangedEvent(*focus_object); |
| if (BrowserAccessibility* text_field = |
| focus_object->PlatformGetTextFieldAncestor()) { |
| EnqueueSelectionChangedEvent(*text_field); |
| |
| // Atomic text fields (including input and textarea elements) have |
| // descendant objects that are part of their internal implementation |
| // in Blink, which are not exposed to platform APIs in the |
| // accessibility tree. Firing an event on such descendants will not |
| // reach the assistive software. |
| if (text_field->IsAtomicTextField()) { |
| FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, text_field); |
| } else { |
| FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object); |
| } |
| } else { |
| // Fire the event on the root object, which in the absence of a text |
| // field ancestor is the closest UIA text provider (other than the |
| // focused object) in which the selection has changed. |
| DCHECK(ui::IsPlatformDocument(wrapper->GetRole())); |
| EnqueueSelectionChangedEvent(*wrapper); |
| |
| // "IA2_EVENT_TEXT_CARET_MOVED" should only be fired when a visible |
| // caret or a selection is present. In the case of a text field above, |
| // this is implicitly true. |
| if (wrapper->HasVisibleCaretOrSelection()) |
| FireWinAccessibilityEvent(IA2_EVENT_TEXT_CARET_MOVED, focus_object); |
| } |
| } |
| break; |
| } |
| case ui::AXEventGenerator::Event::EDITABLE_TEXT_CHANGED: |
| EnqueueTextChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ENABLED_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_IsEnabledPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::FLOW_FROM_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_FlowsFromPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::FLOW_TO_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_FlowsToPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::HASPOPUP_CHANGED: |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::HIERARCHICAL_LEVEL_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_LevelPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::IGNORED_CHANGED: |
| if (wrapper->IsIgnored()) { |
| FireWinAccessibilityEvent(EVENT_OBJECT_HIDE, wrapper); |
| FireUiaStructureChangedEvent(StructureChangeType_ChildRemoved, wrapper); |
| } |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::IMAGE_ANNOTATION_CHANGED: |
| FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::INVALID_STATUS_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_IsDataValidForFormPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ARIA_CURRENT_CHANGED: |
| // TODO(accessibility) No UIA mapping yet exists for aria-current. |
| // Request a mapping from API owners and implement. |
| FireWinAccessibilityEvent(IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::KEY_SHORTCUTS_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_AcceleratorKeyPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::LABELED_BY_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_LabeledByPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::LANGUAGE_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_CulturePropertyId, wrapper); |
| 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, wrapper); |
| FireUiaAccessibilityEvent(UIA_LiveRegionChangedEventId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::LIVE_RELEVANT_CHANGED: |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::LIVE_STATUS_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_LiveSettingPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::LAYOUT_INVALIDATED: |
| FireUiaAccessibilityEvent(UIA_LayoutInvalidatedEventId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::MENU_POPUP_END: |
| FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPEND, wrapper); |
| FireUiaAccessibilityEvent(UIA_MenuClosedEventId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::MENU_POPUP_START: |
| FireWinAccessibilityEvent(EVENT_SYSTEM_MENUPOPUPSTART, wrapper); |
| FireUiaAccessibilityEvent(UIA_MenuOpenedEventId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::MULTILINE_STATE_CHANGED: |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::MULTISELECTABLE_STATE_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_SelectionCanSelectMultiplePropertyId, |
| wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::NAME_CHANGED: |
| if (wrapper->IsText()) { |
| EnqueueTextChangedEvent(*wrapper); |
| } else { |
| FireUiaPropertyChangedEvent(UIA_NamePropertyId, wrapper); |
| } |
| // Only fire name changes when the name comes from an attribute, otherwise |
| // name changes are redundant with text removed/inserted events. |
| if (wrapper->GetNameFrom() != ax::mojom::NameFrom::kContents) |
| FireWinAccessibilityEvent(EVENT_OBJECT_NAMECHANGE, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::OBJECT_ATTRIBUTE_CHANGED: |
| FireWinAccessibilityEvent(IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, wrapper); |
| // TODO(crbug.com/40707706): Fire UIA event. |
| break; |
| case ui::AXEventGenerator::Event::PLACEHOLDER_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_HelpTextPropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::POSITION_IN_SET_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_PositionInSetPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::RANGE_VALUE_CHANGED: |
| DCHECK(wrapper->GetData().IsRangeValueSupported()); |
| FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, wrapper); |
| FireUiaPropertyChangedEvent(UIA_RangeValueValuePropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::RANGE_VALUE_MAX_CHANGED: |
| DCHECK(wrapper->GetData().IsRangeValueSupported()); |
| FireUiaPropertyChangedEvent(UIA_RangeValueMaximumPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::RANGE_VALUE_MIN_CHANGED: |
| DCHECK(wrapper->GetData().IsRangeValueSupported()); |
| FireUiaPropertyChangedEvent(UIA_RangeValueMinimumPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::RANGE_VALUE_STEP_CHANGED: |
| DCHECK(wrapper->GetData().IsRangeValueSupported()); |
| FireUiaPropertyChangedEvent(UIA_RangeValueSmallChangePropertyId, wrapper); |
| FireUiaPropertyChangedEvent(UIA_RangeValueLargeChangePropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::READONLY_CHANGED: |
| if (wrapper->GetData().IsRangeValueSupported()) |
| FireUiaPropertyChangedEvent(UIA_RangeValueIsReadOnlyPropertyId, |
| wrapper); |
| else if (ui::IsValuePatternSupported(wrapper)) |
| FireUiaPropertyChangedEvent(UIA_ValueIsReadOnlyPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::REQUIRED_STATE_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_IsRequiredForFormPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::ROLE_CHANGED: |
| FireWinAccessibilityEvent(IA2_EVENT_ROLE_CHANGED, wrapper); |
| FireUiaPropertyChangedEvent(UIA_AriaRolePropertyId, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SCROLL_HORIZONTAL_POSITION_CHANGED: |
| FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, wrapper); |
| FireUiaPropertyChangedEvent(UIA_ScrollHorizontalScrollPercentPropertyId, |
| wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SCROLL_VERTICAL_POSITION_CHANGED: |
| FireWinAccessibilityEvent(EVENT_SYSTEM_SCROLLINGEND, wrapper); |
| FireUiaPropertyChangedEvent(UIA_ScrollVerticalScrollPercentPropertyId, |
| wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SELECTED_CHANGED: |
| HandleSelectedStateChanged(ia2_selection_events_, wrapper, |
| IsIA2NodeSelected(wrapper)); |
| HandleSelectedStateChanged(uia_selection_events_, wrapper, |
| IsUIANodeSelected(wrapper)); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SELECTED_CHILDREN_CHANGED: |
| FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONWITHIN, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SELECTED_VALUE_CHANGED: |
| DCHECK(ui::IsSelectElement(wrapper->GetRole())); |
| FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, wrapper); |
| FireUiaPropertyChangedEvent(UIA_ValueValuePropertyId, wrapper); |
| // By changing the value of a combo box, the document's text contents will |
| // also have changed. |
| EnqueueTextChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SET_SIZE_CHANGED: |
| FireUiaPropertyChangedEvent(UIA_SizeOfSetPropertyId, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SORT_CHANGED: |
| FireWinAccessibilityEvent(IA2_EVENT_OBJECT_ATTRIBUTE_CHANGED, wrapper); |
| HandleAriaPropertiesChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::SUBTREE_CREATED: |
| FireWinAccessibilityEvent(EVENT_OBJECT_SHOW, wrapper); |
| FireUiaStructureChangedEvent(StructureChangeType_ChildAdded, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::TEXT_ATTRIBUTE_CHANGED: |
| FireWinAccessibilityEvent(IA2_EVENT_TEXT_ATTRIBUTE_CHANGED, wrapper); |
| break; |
| case ui::AXEventGenerator::Event::VALUE_IN_TEXT_FIELD_CHANGED: |
| DCHECK(wrapper->IsTextField()); |
| FireWinAccessibilityEvent(EVENT_OBJECT_VALUECHANGE, wrapper); |
| FireUiaPropertyChangedEvent(UIA_ValueValuePropertyId, wrapper); |
| EnqueueTextChangedEvent(*wrapper); |
| break; |
| case ui::AXEventGenerator::Event::WIN_IACCESSIBLE_STATE_CHANGED: |
| FireWinAccessibilityEvent(EVENT_OBJECT_STATECHANGE, wrapper); |
| break; |
| |
| // Currently unused events on this platform. |
| case ui::AXEventGenerator::Event::NONE: |
| case ui::AXEventGenerator::Event::ARIA_NOTIFICATIONS_POSTED: |
| case ui::AXEventGenerator::Event::ATK_TEXT_OBJECT_ATTRIBUTE_CHANGED: |
| case ui::AXEventGenerator::Event::AUTO_COMPLETE_CHANGED: |
| case ui::AXEventGenerator::Event::AUTOFILL_AVAILABILITY_CHANGED: |
| case ui::AXEventGenerator::Event::CARET_BOUNDS_CHANGED: |
| case ui::AXEventGenerator::Event::CHECKED_STATE_DESCRIPTION_CHANGED: |
| case ui::AXEventGenerator::Event::DETAILS_CHANGED: |
| case ui::AXEventGenerator::Event::DOCUMENT_TITLE_CHANGED: |
| case ui::AXEventGenerator::Event::FOCUS_CHANGED: |
| case ui::AXEventGenerator::Event::LIVE_REGION_CREATED: |
| case ui::AXEventGenerator::Event::LIVE_REGION_NODE_CHANGED: |
| case ui::AXEventGenerator::Event::MENU_ITEM_SELECTED: |
| case ui::AXEventGenerator::Event::ORIENTATION_CHANGED: |
| case ui::AXEventGenerator::Event::PARENT_CHANGED: |
| case ui::AXEventGenerator::Event::PORTAL_ACTIVATED: |
| case ui::AXEventGenerator::Event::RELATED_NODE_CHANGED: |
| case ui::AXEventGenerator::Event::ROW_COUNT_CHANGED: |
| case ui::AXEventGenerator::Event::STATE_CHANGED: |
| case ui::AXEventGenerator::Event::TEXT_SELECTION_CHANGED: |
| break; |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::FireWinAccessibilityEvent( |
| LONG win_event_type, |
| BrowserAccessibility* node) { |
| TRACE_EVENT("accessibility", "FireWinAccessibilityEvent"); |
| if (!ShouldFireEventForNode(node)) |
| return; |
| // Suppress events when |IGNORED_CHANGED| except for related SHOW / HIDE. |
| // Also include MENUPOPUPSTART / MENUPOPUPEND since a change in the ignored |
| // state may show / hide a popup by exposing it to the tree or not. |
| // Also include focus events since a node may become visible at the same time |
| // it receives focus It's never good to suppress a po |
| if (IsIgnoredChangedNode(node)) { |
| switch (win_event_type) { |
| case EVENT_OBJECT_HIDE: |
| case EVENT_OBJECT_SHOW: |
| case EVENT_OBJECT_FOCUS: |
| case EVENT_SYSTEM_MENUPOPUPEND: |
| case EVENT_SYSTEM_MENUPOPUPSTART: |
| break; |
| default: |
| return; |
| } |
| } else if (node->IsIgnored()) { |
| 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. |
| auto* const com = ToBrowserAccessibilityWin(node)->GetCOM(); |
| TRACE_EVENT("accessibility", "NotifyWinEvent", |
| perfetto::Flow::FromPointer(com), "win_event_type", |
| base::StringPrintf("0x%04lX", win_event_type)); |
| ::NotifyWinEvent(win_event_type, hwnd, OBJID_CLIENT, -(com->GetUniqueId())); |
| } |
| |
| bool BrowserAccessibilityManagerWin::IsIgnoredChangedNode( |
| const BrowserAccessibility* node) const { |
| return base::Contains(ignored_changed_nodes_, |
| const_cast<BrowserAccessibility*>(node)); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireUiaAccessibilityEvent( |
| LONG uia_event, |
| BrowserAccessibility* node) { |
| if (!::ui::AXPlatform::GetInstance().IsUiaProviderEnabled()) { |
| return; |
| } |
| if (!ShouldFireEventForNode(node)) |
| return; |
| |
| // Suppress most events when the node just became ignored/unignored. |
| if (IsIgnoredChangedNode(node)) { |
| switch (uia_event) { |
| case UIA_LiveRegionChangedEventId: |
| // Don't suppress live region changed events on nodes that just became |
| // unignored, but suppress them on nodes that just became ignored. This |
| // ensures that ATs can announce LiveRegionChanged events on nodes that |
| // just appeared in the tree and not announce the ones that just got |
| // removed. |
| if (node->IsIgnored()) |
| return; |
| break; |
| case UIA_MenuClosedEventId: |
| case UIA_MenuOpenedEventId: |
| // Don't suppress MenuClosed/MenuOpened events since a change in the |
| // ignored state may hide/show a popup by exposing it to the tree or |
| // not. |
| break; |
| default: |
| return; |
| } |
| } else if (node->IsIgnored()) { |
| return; |
| } |
| |
| ui::WinAccessibilityAPIUsageScopedUIAEventsNotifier scoped_events_notifier; |
| |
| ::UiaRaiseAutomationEvent(ToBrowserAccessibilityWin(node)->GetCOM(), |
| uia_event); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireUiaPropertyChangedEvent( |
| LONG uia_property, |
| BrowserAccessibility* node) { |
| if (!::ui::AXPlatform::GetInstance().IsUiaProviderEnabled()) { |
| return; |
| } |
| if (!ShouldFireEventForNode(node)) |
| return; |
| // Suppress events when |IGNORED_CHANGED| with the exception for firing |
| // UIA_AriaPropertiesPropertyId-hidden event on non-text node marked as |
| // ignored. |
| if (node->IsIgnored() || IsIgnoredChangedNode(node)) { |
| if (uia_property != UIA_AriaPropertiesPropertyId || node->IsText()) |
| return; |
| } |
| |
| // The old value is not used by the system |
| VARIANT old_value = {}; |
| old_value.vt = VT_EMPTY; |
| |
| ui::WinAccessibilityAPIUsageScopedUIAEventsNotifier scoped_events_notifier; |
| |
| auto* provider = ToBrowserAccessibilityWin(node)->GetCOM(); |
| base::win::ScopedVariant new_value; |
| if (SUCCEEDED( |
| provider->GetPropertyValueImpl(uia_property, new_value.Receive()))) { |
| ::UiaRaiseAutomationPropertyChangedEvent(provider, uia_property, old_value, |
| new_value); |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent( |
| StructureChangeType change_type, |
| BrowserAccessibility* node) { |
| if (!::ui::AXPlatform::GetInstance().IsUiaProviderEnabled()) { |
| return; |
| } |
| if (!ShouldFireEventForNode(node)) |
| return; |
| // Suppress events when |IGNORED_CHANGED| except for related structure changes |
| if (IsIgnoredChangedNode(node)) { |
| switch (change_type) { |
| case StructureChangeType_ChildRemoved: |
| case StructureChangeType_ChildAdded: |
| break; |
| default: |
| return; |
| } |
| } else if (node->IsIgnored()) { |
| return; |
| } |
| |
| auto* provider = ToBrowserAccessibilityWin(node); |
| auto* provider_com = provider ? provider->GetCOM() : nullptr; |
| if (!provider || !provider_com) |
| return; |
| |
| ui::WinAccessibilityAPIUsageScopedUIAEventsNotifier scoped_events_notifier; |
| |
| 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); |
| } |
| } |
| } |
| |
| // static |
| bool BrowserAccessibilityManagerWin:: |
| IsUiaActiveTextPositionChangedEventSupported() { |
| return GetUiaActiveTextPositionChangedEventFunction(); |
| } |
| |
| // static |
| UiaRaiseActiveTextPositionChangedEventFunction |
| BrowserAccessibilityManagerWin::GetUiaActiveTextPositionChangedEventFunction() { |
| // MSDN |
| // (https://learn.microsoft.com/en-us/windows/win32/api/uiautomationcoreapi/nf-uiautomationcoreapi-uiaraiseactivetextpositionchangedevent) |
| // claims this API is fully supported from Win8.1 ownwards, but older |
| // versions of this dll on Win10 (e.g., Windows-10-15063 aka. version 1703) |
| // don't seem to have the API, which makes chrome.dll fail to load, if |
| // ::UiaRaiseActiveTextPositionChangedEvent is called directly. On these older |
| // versions of Win10, we will return nullptr. |
| return reinterpret_cast<UiaRaiseActiveTextPositionChangedEventFunction>( |
| ::GetProcAddress(::GetModuleHandle(L"uiautomationcore.dll"), |
| "UiaRaiseActiveTextPositionChangedEvent")); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireUiaActiveTextPositionChangedEvent( |
| BrowserAccessibility* node) { |
| if (!ShouldFireEventForNode(node)) |
| return; |
| |
| UiaRaiseActiveTextPositionChangedEventFunction |
| active_text_position_changed_func = |
| GetUiaActiveTextPositionChangedEventFunction(); |
| |
| if (!active_text_position_changed_func) |
| return; |
| |
| // Create the text range contained by the target node. |
| auto* target_node = ToBrowserAccessibilityWin(node)->GetCOM(); |
| Microsoft::WRL::ComPtr<ITextRangeProvider> text_range; |
| ui::AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(target_node, |
| &text_range); |
| |
| // Fire the UiaRaiseActiveTextPositionChangedEvent. |
| active_text_position_changed_func(target_node, text_range.Get()); |
| } |
| |
| bool BrowserAccessibilityManagerWin::CanFireEvents() const { |
| return BrowserAccessibilityManager::CanFireEvents() && |
| GetDelegateFromRootManager() && |
| GetDelegateFromRootManager()->AccessibilityGetAcceleratedWidget(); |
| } |
| |
| void BrowserAccessibilityManagerWin::OnSubtreeWillBeDeleted(ui::AXTree* tree, |
| ui::AXNode* node) { |
| BrowserAccessibility* obj = GetFromAXNode(node); |
| DCHECK(obj); |
| if (obj) { |
| FireWinAccessibilityEvent(EVENT_OBJECT_HIDE, obj); |
| FireUiaStructureChangedEvent(StructureChangeType_ChildRemoved, obj); |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::OnAtomicUpdateFinished( |
| ui::AXTree* tree, |
| bool root_changed, |
| const std::vector<ui::AXTreeObserver::Change>& changes) { |
| BrowserAccessibilityManager::OnAtomicUpdateFinished(tree, root_changed, |
| changes); |
| |
| // 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::set<ui::AXPlatformNode*> objs_to_update; |
| CollectChangedNodesAndParentsForAtomicUpdate(tree, changes, &objs_to_update); |
| |
| // The first step moves win_attributes_ to old_win_attributes_ and then |
| // recomputes all of win_attributes_ other than IAccessibleText. |
| for (auto* node : objs_to_update) { |
| static_cast<BrowserAccessibilityComWin*>(node) |
| ->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* node : objs_to_update) { |
| static_cast<BrowserAccessibilityComWin*>(node) |
| ->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* node : objs_to_update) { |
| static_cast<BrowserAccessibilityComWin*>(node)->UpdateStep3FireEvents(); |
| } |
| } |
| |
| // static |
| bool BrowserAccessibilityManagerWin::IsIA2NodeSelected( |
| BrowserAccessibility* node) { |
| return node->IsIA2NodeSelected(); |
| } |
| |
| // static |
| bool BrowserAccessibilityManagerWin::IsUIANodeSelected( |
| BrowserAccessibility* node) { |
| return node->IsUIANodeSelected(); |
| } |
| |
| void BrowserAccessibilityManagerWin::FireIA2SelectionEvents( |
| BrowserAccessibility* container, |
| BrowserAccessibility* only_selected_child, |
| const SelectionEvents& changes) { |
| if (only_selected_child) { |
| // Fire 'ElementSelected' on the only selected child. |
| FireWinAccessibilityEvent(EVENT_OBJECT_SELECTION, only_selected_child); |
| } else { |
| const bool container_is_multiselectable = |
| container && container->HasState(ax::mojom::State::kMultiselectable); |
| for (content::BrowserAccessibility* item : changes.added) { |
| if (container_is_multiselectable) |
| FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONADD, item); |
| else |
| FireWinAccessibilityEvent(EVENT_OBJECT_SELECTION, item); |
| } |
| for (content::BrowserAccessibility* item : changes.removed) { |
| FireWinAccessibilityEvent(EVENT_OBJECT_SELECTIONREMOVE, item); |
| } |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::FireUIASelectionEvents( |
| BrowserAccessibility* container, |
| BrowserAccessibility* only_selected_child, |
| const SelectionEvents& changes) { |
| if (only_selected_child) { |
| // Fire 'ElementSelected' on the only selected child. |
| FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId, |
| only_selected_child); |
| FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId, |
| only_selected_child); |
| for (content::BrowserAccessibility* item : changes.removed) { |
| FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId, item); |
| } |
| } 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) { |
| DCHECK_NE(container, nullptr); |
| FireUiaAccessibilityEvent(UIA_Selection_InvalidatedEventId, container); |
| } else { |
| const bool container_is_multiselectable = |
| container && container->HasState(ax::mojom::State::kMultiselectable); |
| for (content::BrowserAccessibility* item : changes.added) { |
| if (container_is_multiselectable) { |
| FireUiaAccessibilityEvent( |
| UIA_SelectionItem_ElementAddedToSelectionEventId, item); |
| } else { |
| FireUiaAccessibilityEvent(UIA_SelectionItem_ElementSelectedEventId, |
| item); |
| } |
| FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId, |
| item); |
| } |
| for (content::BrowserAccessibility* item : changes.removed) { |
| FireUiaAccessibilityEvent( |
| UIA_SelectionItem_ElementRemovedFromSelectionEventId, item); |
| FireUiaPropertyChangedEvent(UIA_SelectionItemIsSelectedPropertyId, |
| item); |
| } |
| } |
| } |
| } |
| |
| // static |
| void BrowserAccessibilityManagerWin::HandleSelectedStateChanged( |
| SelectionEventsMap& selection_events_map, |
| BrowserAccessibility* node, |
| bool is_selected) { |
| // If |node| belongs to a selection container, then map the events with the |
| // selection container as the key because |FinalizeSelectionEvents| needs to |
| // determine whether or not there is only one element selected in order to |
| // optimize what platform events are sent. |
| BrowserAccessibility* key = node; |
| if (auto* selection_container = node->PlatformGetSelectionContainer()) |
| key = selection_container; |
| |
| if (is_selected) |
| selection_events_map[key].added.push_back(node); |
| else |
| selection_events_map[key].removed.push_back(node); |
| } |
| |
| // static |
| void BrowserAccessibilityManagerWin::FinalizeSelectionEvents( |
| SelectionEventsMap& selection_events_map, |
| IsSelectedPredicate is_selected_predicate, |
| FirePlatformSelectionEventsCallback fire_platform_events_callback) { |
| for (auto&& selected : selection_events_map) { |
| BrowserAccessibility* key_node = selected.first; |
| SelectionEvents& changes = selected.second; |
| |
| // Determine if |node| is a selection container with one selected child in |
| // order to optimize what platform events are sent. |
| BrowserAccessibility* container = nullptr; |
| BrowserAccessibility* only_selected_child = nullptr; |
| if (ui::IsContainerWithSelectableChildren(key_node->GetRole())) { |
| container = key_node; |
| for (auto it = container->InternalChildrenBegin(); |
| it != container->InternalChildrenEnd(); ++it) { |
| auto* child = it.get(); |
| if (is_selected_predicate.Run(child)) { |
| if (!only_selected_child) { |
| only_selected_child = child; |
| continue; |
| } |
| |
| only_selected_child = nullptr; |
| break; |
| } |
| } |
| } |
| |
| fire_platform_events_callback.Run(container, only_selected_child, changes); |
| } |
| |
| selection_events_map.clear(); |
| } |
| |
| void BrowserAccessibilityManagerWin::HandleAriaPropertiesChangedEvent( |
| BrowserAccessibility& node) { |
| DCHECK_IN_ON_ACCESSIBILITY_EVENTS(); |
| aria_properties_events_.insert(&node); |
| } |
| |
| void BrowserAccessibilityManagerWin::EnqueueTextChangedEvent( |
| BrowserAccessibility& node) { |
| DCHECK_IN_ON_ACCESSIBILITY_EVENTS(); |
| if (BrowserAccessibility* text_provider = GetUiaTextPatternProvider(node)) |
| text_changed_nodes_.insert(text_provider); |
| } |
| |
| void BrowserAccessibilityManagerWin::EnqueueSelectionChangedEvent( |
| BrowserAccessibility& node) { |
| DCHECK_IN_ON_ACCESSIBILITY_EVENTS(); |
| selection_changed_nodes_.insert(&node); |
| } |
| |
| gfx::Rect BrowserAccessibilityManagerWin::GetViewBoundsInScreenCoordinates() |
| const { |
| ui::AXPlatformTreeManagerDelegate* delegate = GetDelegateFromRootManager(); |
| if (!delegate) { |
| return gfx::Rect(); |
| } |
| |
| gfx::Rect bounds = delegate->AccessibilityGetViewBounds(); |
| |
| // On Windows, we cannot directly multiply the bounds in screen DIPs by the |
| // display's scale factor to get screen physical coordinates like we can on |
| // other platforms. We need to go through the ScreenWin::DIPToScreenRect |
| // helper function to perform the right set of offset transformations needed. |
| // |
| // This is because Chromium transforms the screen physical coordinates it |
| // receives from Windows into an internal representation of screen physical |
| // coordinates adjusted for multiple displays of different resolutions. |
| return display::win::ScreenWin::DIPToScreenRect(GetParentHWND(), bounds); |
| } |
| |
| void BrowserAccessibilityManagerWin::BeforeAccessibilityEvents() { |
| BrowserAccessibilityManager::BeforeAccessibilityEvents(); |
| |
| DCHECK(aria_properties_events_.empty()); |
| DCHECK(text_changed_nodes_.empty()); |
| DCHECK(selection_changed_nodes_.empty()); |
| DCHECK(ignored_changed_nodes_.empty()); |
| |
| for (const auto& targeted_event : event_generator()) { |
| if (targeted_event.event_params->event == |
| ui::AXEventGenerator::Event::IGNORED_CHANGED) { |
| BrowserAccessibility* event_target = GetFromID(targeted_event.node_id); |
| if (!event_target) |
| continue; |
| |
| const auto insert_pair = ignored_changed_nodes_.insert(event_target); |
| |
| // Expect that |IGNORED_CHANGED| only fires once for a given |
| // node in a given event frame. |
| DCHECK(insert_pair.second); |
| } |
| } |
| } |
| |
| void BrowserAccessibilityManagerWin::FinalizeAccessibilityEvents() { |
| BrowserAccessibilityManager::FinalizeAccessibilityEvents(); |
| |
| // Finalize aria properties events. |
| for (BrowserAccessibility* event_node : aria_properties_events_) |
| FireUiaPropertyChangedEvent(UIA_AriaPropertiesPropertyId, event_node); |
| aria_properties_events_.clear(); |
| |
| // Finalize selection changed events. |
| for (BrowserAccessibility* event_node : selection_changed_nodes_) { |
| DCHECK(event_node); |
| if (ToBrowserAccessibilityWin(event_node) |
| ->GetCOM() |
| ->IsPatternProviderSupported(UIA_TextPatternId)) { |
| FireUiaAccessibilityEvent(UIA_Text_TextSelectionChangedEventId, |
| event_node); |
| } |
| } |
| selection_changed_nodes_.clear(); |
| |
| // Finalize text changed events. |
| for (BrowserAccessibility* event_node : text_changed_nodes_) |
| FireUiaAccessibilityEvent(UIA_Text_TextChangedEventId, event_node); |
| text_changed_nodes_.clear(); |
| |
| // Finalize selection item events. |
| FinalizeSelectionEvents( |
| ia2_selection_events_, base::BindRepeating(&IsIA2NodeSelected), |
| base::BindRepeating( |
| &BrowserAccessibilityManagerWin::FireIA2SelectionEvents, |
| base::Unretained(this))); |
| FinalizeSelectionEvents( |
| uia_selection_events_, base::BindRepeating(&IsUIANodeSelected), |
| base::BindRepeating( |
| &BrowserAccessibilityManagerWin::FireUIASelectionEvents, |
| base::Unretained(this))); |
| |
| ignored_changed_nodes_.clear(); |
| } |
| |
| BrowserAccessibilityManagerWin::SelectionEvents::SelectionEvents() = default; |
| BrowserAccessibilityManagerWin::SelectionEvents::~SelectionEvents() = default; |
| |
| } // namespace content |