M135: [UIA] Implement IRawElementProviderAdviseEvents to reduce event overhead
This is a cherry-pick to M135. It will improve performance for the 1.34%
of users that currently use the native UiaProvider. We also needed to
include the one-line check to the DCHECK in CL:6349079 (in
base/win/scoped_safearray.h) to avoid test crashes.
This CL implements the IRawElementProviderAdviseEvents UIA interface to
limit event firing to only those actively listened to by UIA clients.
The interface is implemented on AXPlatformNodeWin, but per UIA
documentation, its methods are only invoked on the fragment root.
To achieve this, AXFragmentRootWin now maintains two unordered_maps --
one for EVENTIDs and another for PROPERTYIDs. Before firing an event, we
check if a UIA client has registered interest in it within the fragment
tree, reducing unnecessary event processing and improving performance.
While we manually tested that we're always firing the required events
when an assistive technology is listening in, we're adding a killswitch
to turn off the feature remotely in case unexpected problems are
discovered in prod.
(cherry picked from commit 4a358fb1c1
)
Bug: 40919326, 401016023, 402375302
Fixed: 403626090
Change-Id: I18554f50e59a1013452c691c3f310f2629835b59
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6337330
Reviewed-by: Greg Thompson <grt@chromium.org>
Commit-Queue: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Cr-Original-Commit-Position: refs/heads/main@{#1432231}
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6358320
Auto-Submit: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Commit-Queue: Greg Thompson <grt@chromium.org>
Cr-Commit-Position: refs/branch-heads/7049@{#841}
Cr-Branched-From: 2dab7846d0951a552bdc4f350dad497f986e6fed-refs/heads/main@{#1427262}
This commit is contained in:

committed by
Chromium LUCI CQ

parent
7608d012e7
commit
80e8fe8cc8
@ -218,7 +218,7 @@ class BASE_EXPORT ScopedSafearray {
|
||||
DCHECK(SUCCEEDED(hr));
|
||||
LONG count = upper - lower + 1;
|
||||
// SafeArrays may have negative lower bounds, so check for wraparound.
|
||||
DCHECK_GT(count, 0);
|
||||
DCHECK_GE(count, 0);
|
||||
return static_cast<size_t>(count);
|
||||
}
|
||||
|
||||
|
@ -165,6 +165,10 @@ bool IsSelectiveUIAEnablementEnabled() {
|
||||
}
|
||||
|
||||
BASE_FEATURE(kUiaProvider, "UiaProvider", base::FEATURE_DISABLED_BY_DEFAULT);
|
||||
|
||||
BASE_FEATURE(kUiaEventOptimization,
|
||||
"UiaEventOptimization",
|
||||
base::FEATURE_ENABLED_BY_DEFAULT);
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
|
@ -140,6 +140,12 @@ AX_BASE_EXPORT bool IsSelectiveUIAEnablementEnabled();
|
||||
// Use the browser's UIA provider when requested by
|
||||
// an accessibility client.
|
||||
AX_BASE_EXPORT BASE_DECLARE_FEATURE(kUiaProvider);
|
||||
|
||||
// Optimizes event firing by only emitting events when at least one listener is
|
||||
// subscribed. Killswitch to turn it off in case this work has negative
|
||||
// side-effects on assistive technologies.
|
||||
// TODO(https://crbug.com/402375302): Remove in M139.
|
||||
AX_BASE_EXPORT BASE_DECLARE_FEATURE(kUiaEventOptimization);
|
||||
#endif // BUILDFLAG(IS_WIN)
|
||||
|
||||
#if BUILDFLAG(IS_CHROMEOS)
|
||||
|
@ -4,11 +4,20 @@
|
||||
|
||||
#include "ui/accessibility/platform/ax_fragment_root_win.h"
|
||||
|
||||
#include <limits>
|
||||
#include <unordered_map>
|
||||
|
||||
#include "base/containers/contains.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "base/no_destructor.h"
|
||||
#include "base/strings/string_number_conversions.h"
|
||||
#include "base/trace_event/memory_allocator_dump.h"
|
||||
#include "base/trace_event/memory_dump_manager.h"
|
||||
#include "base/trace_event/memory_dump_provider.h"
|
||||
#include "base/trace_event/process_memory_dump.h"
|
||||
#include "base/trace_event/typed_macros.h"
|
||||
#include "base/win/scoped_safearray.h"
|
||||
#include "ui/accessibility/accessibility_features.h"
|
||||
#include "ui/accessibility/platform/ax_fragment_root_delegate_win.h"
|
||||
#include "ui/accessibility/platform/ax_platform_node_win.h"
|
||||
#include "ui/accessibility/platform/uia_registrar_win.h"
|
||||
@ -18,15 +27,17 @@ namespace ui {
|
||||
|
||||
class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
|
||||
public IItemContainerProvider,
|
||||
public IRawElementProviderFragmentRoot {
|
||||
public IRawElementProviderFragmentRoot,
|
||||
public IRawElementProviderAdviseEvents {
|
||||
public:
|
||||
BEGIN_COM_MAP(AXFragmentRootPlatformNodeWin)
|
||||
COM_INTERFACE_ENTRY(IItemContainerProvider)
|
||||
COM_INTERFACE_ENTRY(IRawElementProviderFragmentRoot)
|
||||
COM_INTERFACE_ENTRY(IRawElementProviderAdviseEvents)
|
||||
COM_INTERFACE_ENTRY_CHAIN(AXPlatformNodeWin)
|
||||
END_COM_MAP()
|
||||
|
||||
static Pointer Create(AXPlatformNodeDelegate* delegate) {
|
||||
static Pointer Create(AXFragmentRootWin* delegate) {
|
||||
// Make sure ATL is initialized in this module.
|
||||
win::CreateATLModuleIfNeeded();
|
||||
|
||||
@ -219,6 +230,54 @@ class AXFragmentRootPlatformNodeWin : public AXPlatformNodeWin,
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
//
|
||||
// IRawElementProviderAdviseEvents methods.
|
||||
//
|
||||
|
||||
IFACEMETHODIMP AdviseEventAdded(EVENTID event_id,
|
||||
SAFEARRAY* property_ids) override {
|
||||
WIN_ACCESSIBILITY_API_TRACE_EVENT("AdviseEventAdded");
|
||||
UIA_VALIDATE_CALL();
|
||||
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_ADDED);
|
||||
|
||||
AXFragmentRootWin* root = static_cast<AXFragmentRootWin*>(delegate_);
|
||||
if (!root) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
base::win::ScopedSafearray safe_array(property_ids);
|
||||
absl::Cleanup release_safe_array(
|
||||
[&safe_array]() { (void)safe_array.Release(); });
|
||||
|
||||
auto lock = safe_array.CreateLockScope<VT_I4>();
|
||||
root->OnEventListenerAdded(event_id,
|
||||
lock ? *lock : base::span<PROPERTYID>());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
IFACEMETHODIMP AdviseEventRemoved(EVENTID event_id,
|
||||
SAFEARRAY* property_ids) override {
|
||||
WIN_ACCESSIBILITY_API_TRACE_EVENT("AdviseEventRemoved");
|
||||
UIA_VALIDATE_CALL();
|
||||
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_ADVISE_EVENT_REMOVED);
|
||||
|
||||
AXFragmentRootWin* root = static_cast<AXFragmentRootWin*>(delegate_);
|
||||
if (!root) {
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
base::win::ScopedSafearray safe_array(property_ids);
|
||||
absl::Cleanup release_safe_array(
|
||||
[&safe_array]() { (void)safe_array.Release(); });
|
||||
|
||||
auto lock = safe_array.CreateLockScope<VT_I4>();
|
||||
root->OnEventListenerRemoved(event_id,
|
||||
lock ? *lock : base::span<PROPERTYID>());
|
||||
|
||||
return S_OK;
|
||||
}
|
||||
};
|
||||
|
||||
class AXFragmentRootMapWin {
|
||||
@ -381,6 +440,43 @@ AXPlatformNodeDelegate* AXFragmentRootWin::GetChildNodeDelegate() const {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void AXFragmentRootWin::OnEventListenerAdded(
|
||||
EVENTID event_id,
|
||||
base::span<const PROPERTYID> property_ids) {
|
||||
CHECK_LT(event_listener_count_[event_id], std::numeric_limits<int>::max());
|
||||
++event_listener_count_[event_id];
|
||||
|
||||
for (PROPERTYID property_id : property_ids) {
|
||||
CHECK_LT(property_listener_count_[property_id],
|
||||
std::numeric_limits<int>::max());
|
||||
++property_listener_count_[property_id];
|
||||
}
|
||||
}
|
||||
|
||||
void AXFragmentRootWin::OnEventListenerRemoved(
|
||||
EVENTID event_id,
|
||||
base::span<const PROPERTYID> property_ids) {
|
||||
auto event_it = event_listener_count_.find(event_id);
|
||||
if (event_it != event_listener_count_.end() && (--event_it->second) <= 0) {
|
||||
event_listener_count_.erase(event_it);
|
||||
}
|
||||
|
||||
for (PROPERTYID property_id : property_ids) {
|
||||
auto prop_it = property_listener_count_.find(property_id);
|
||||
if (prop_it != property_listener_count_.end() && (--prop_it->second) <= 0) {
|
||||
property_listener_count_.erase(prop_it);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool AXFragmentRootWin::HasEventListenerForEvent(EVENTID event_id) {
|
||||
return base::Contains(event_listener_count_, event_id);
|
||||
}
|
||||
|
||||
bool AXFragmentRootWin::HasEventListenerForProperty(PROPERTYID property_id) {
|
||||
return base::Contains(property_listener_count_, property_id);
|
||||
}
|
||||
|
||||
size_t AXFragmentRootWin::GetIndexInParentOfChild() const {
|
||||
AXPlatformNodeDelegate* parent = GetParentNodeDelegate();
|
||||
|
||||
|
@ -6,7 +6,9 @@
|
||||
#define UI_ACCESSIBILITY_PLATFORM_AX_FRAGMENT_ROOT_WIN_H_
|
||||
|
||||
#include "base/component_export.h"
|
||||
#include "base/containers/span.h"
|
||||
#include "base/memory/raw_ptr.h"
|
||||
#include "third_party/abseil-cpp/absl/container/flat_hash_map.h"
|
||||
#include "ui/accessibility/platform/ax_platform_node_delegate.h"
|
||||
#include "ui/accessibility/platform/ax_unique_id.h"
|
||||
|
||||
@ -56,6 +58,12 @@ class COMPONENT_EXPORT(AX_PLATFORM) AXFragmentRootWin
|
||||
// If a child node is available, return its delegate.
|
||||
AXPlatformNodeDelegate* GetChildNodeDelegate() const;
|
||||
|
||||
void OnEventListenerAdded(int event_id, base::span<const int> property_ids);
|
||||
void OnEventListenerRemoved(int event_id, base::span<const int> property_ids);
|
||||
|
||||
bool HasEventListenerForEvent(int event_id);
|
||||
bool HasEventListenerForProperty(int property_id);
|
||||
|
||||
private:
|
||||
// AXPlatformNodeDelegate overrides.
|
||||
gfx::NativeViewAccessible GetParent() const override;
|
||||
@ -83,6 +91,11 @@ class COMPONENT_EXPORT(AX_PLATFORM) AXFragmentRootWin
|
||||
const raw_ptr<AXFragmentRootDelegateWin> delegate_;
|
||||
const AXUniqueId unique_id_{AXUniqueId::Create()};
|
||||
AXPlatformNode::Pointer platform_node_;
|
||||
|
||||
// Track the listeners count for each event and property ID in maps, so that
|
||||
// we can retrieve it quickly to decide whether to raise a UIA event.
|
||||
absl::flat_hash_map<int, int> event_listener_count_;
|
||||
absl::flat_hash_map<int, int> property_listener_count_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
@ -7,9 +7,11 @@
|
||||
#include <wrl/client.h>
|
||||
|
||||
#include "base/auto_reset.h"
|
||||
#include "base/test/gmock_expected_support.h"
|
||||
#include "base/win/scoped_safearray.h"
|
||||
#include "base/win/scoped_variant.h"
|
||||
#include "testing/gtest/include/gtest/gtest.h"
|
||||
#include "ui/accessibility/accessibility_features.h"
|
||||
#include "ui/accessibility/platform/ax_platform_node_win.h"
|
||||
#include "ui/accessibility/platform/ax_platform_node_win_unittest.h"
|
||||
#include "ui/accessibility/platform/test_ax_node_wrapper.h"
|
||||
@ -709,4 +711,166 @@ TEST_F(AXFragmentRootTest, TestFragmentRootMap) {
|
||||
GetRootIAccessible().Get()));
|
||||
}
|
||||
|
||||
TEST_F(AXFragmentRootTest, DetectEventListenersForEvents) {
|
||||
AXNodeData root_data;
|
||||
root_data.id = 1;
|
||||
|
||||
Init(root_data);
|
||||
InitFragmentRoot();
|
||||
|
||||
ASSERT_NE(ax_fragment_root_, nullptr);
|
||||
|
||||
ComPtr<IRawElementProviderAdviseEvents> provider_advise_events;
|
||||
ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
|
||||
IID_PPV_ARGS(&provider_advise_events));
|
||||
|
||||
// We start with no event listener.
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_Invoke_InvokedEventId));
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_MenuOpenedEventId));
|
||||
|
||||
// Then we add one for the UIA_Invoke_InvokedEventId event.
|
||||
provider_advise_events->AdviseEventAdded(UIA_Invoke_InvokedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_Invoke_InvokedEventId));
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_MenuOpenedEventId));
|
||||
|
||||
// Then we add a second one for UIA_Invoke_InvokedEventId and add a first one
|
||||
// for UIA_MenuOpenedEventId.
|
||||
provider_advise_events->AdviseEventAdded(UIA_Invoke_InvokedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
provider_advise_events->AdviseEventAdded(UIA_MenuOpenedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_Invoke_InvokedEventId));
|
||||
EXPECT_TRUE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_MenuOpenedEventId));
|
||||
|
||||
// Then we remove one of each, leaving us with only one listener for
|
||||
// UIA_Invoke_InvokedEventId.
|
||||
provider_advise_events->AdviseEventRemoved(UIA_Invoke_InvokedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
provider_advise_events->AdviseEventRemoved(UIA_MenuOpenedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_Invoke_InvokedEventId));
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_MenuOpenedEventId));
|
||||
|
||||
// Finally, we remove the last listener.
|
||||
provider_advise_events->AdviseEventRemoved(UIA_Invoke_InvokedEventId,
|
||||
/*property_ids=*/nullptr);
|
||||
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_Invoke_InvokedEventId));
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForEvent(UIA_MenuOpenedEventId));
|
||||
}
|
||||
|
||||
TEST_F(AXFragmentRootTest, DetectEventListenersForProperties) {
|
||||
AXNodeData root_data;
|
||||
root_data.id = 1;
|
||||
|
||||
Init(root_data);
|
||||
InitFragmentRoot();
|
||||
|
||||
ASSERT_NE(ax_fragment_root_, nullptr);
|
||||
|
||||
ComPtr<IRawElementProviderAdviseEvents> provider_advise_events;
|
||||
ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
|
||||
IID_PPV_ARGS(&provider_advise_events));
|
||||
|
||||
// We start with no property listener.
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForProperty(UIA_NamePropertyId));
|
||||
EXPECT_FALSE(ax_fragment_root_->HasEventListenerForProperty(
|
||||
UIA_ControlTypePropertyId));
|
||||
|
||||
// Create a SAFEARRAY with property IDs of size 2.
|
||||
base::win::ScopedSafearray property_ids(
|
||||
::SafeArrayCreateVector(VT_I4, 0, /*cElements=*/2));
|
||||
|
||||
// Put first property.
|
||||
{
|
||||
ASSERT_OK_AND_ASSIGN(auto lock, property_ids.CreateLockScope<VT_I4>());
|
||||
lock[0] = UIA_NamePropertyId;
|
||||
}
|
||||
// Put second property.
|
||||
{
|
||||
ASSERT_OK_AND_ASSIGN(auto lock, property_ids.CreateLockScope<VT_I4>());
|
||||
lock[1] = UIA_ControlTypePropertyId;
|
||||
}
|
||||
|
||||
// Add a listener for the properties.
|
||||
provider_advise_events->AdviseEventAdded(UIA_AutomationPropertyChangedEventId,
|
||||
property_ids.Get());
|
||||
|
||||
EXPECT_TRUE(
|
||||
ax_fragment_root_->HasEventListenerForProperty(UIA_NamePropertyId));
|
||||
EXPECT_TRUE(ax_fragment_root_->HasEventListenerForProperty(
|
||||
UIA_ControlTypePropertyId));
|
||||
|
||||
// Remove the listener for one property by creating a SAFEARRAY with that
|
||||
// single property.
|
||||
base::win::ScopedSafearray single_property_ids(
|
||||
::SafeArrayCreateVector(VT_I4, 0, /*cElements=*/1));
|
||||
|
||||
{
|
||||
ASSERT_OK_AND_ASSIGN(auto lock,
|
||||
single_property_ids.CreateLockScope<VT_I4>());
|
||||
lock[0] = UIA_NamePropertyId;
|
||||
}
|
||||
|
||||
provider_advise_events->AdviseEventRemoved(
|
||||
UIA_AutomationPropertyChangedEventId, single_property_ids.Get());
|
||||
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForProperty(UIA_NamePropertyId));
|
||||
EXPECT_TRUE(ax_fragment_root_->HasEventListenerForProperty(
|
||||
UIA_ControlTypePropertyId));
|
||||
|
||||
// Remove the listener for the remaining property.
|
||||
provider_advise_events->AdviseEventRemoved(
|
||||
UIA_AutomationPropertyChangedEventId, property_ids.Get());
|
||||
|
||||
EXPECT_FALSE(
|
||||
ax_fragment_root_->HasEventListenerForProperty(UIA_NamePropertyId));
|
||||
EXPECT_FALSE(ax_fragment_root_->HasEventListenerForProperty(
|
||||
UIA_ControlTypePropertyId));
|
||||
}
|
||||
|
||||
TEST_F(AXFragmentRootTest, EventListenersCountDisabledWhenFlagIsOff) {
|
||||
base::test::ScopedFeatureList scoped_feature_list;
|
||||
scoped_feature_list.InitAndDisableFeature(::features::kUiaEventOptimization);
|
||||
|
||||
AXNodeData root_data;
|
||||
root_data.id = 1;
|
||||
|
||||
Init(root_data);
|
||||
InitFragmentRoot();
|
||||
|
||||
ASSERT_NE(ax_fragment_root_, nullptr);
|
||||
|
||||
ComPtr<IRawElementProviderAdviseEvents> provider_advise_events;
|
||||
EXPECT_EQ(ax_fragment_root_->GetNativeViewAccessible()->QueryInterface(
|
||||
IID_PPV_ARGS(&provider_advise_events)),
|
||||
E_NOINTERFACE);
|
||||
|
||||
AXPlatformNodeWin* platform_node =
|
||||
static_cast<AXPlatformNodeWin*>(AXPlatformNodeFromNode(GetRoot()));
|
||||
ASSERT_NE(platform_node, nullptr);
|
||||
|
||||
// Despite not having any event listener, we should return true when the flag
|
||||
// is off.
|
||||
EXPECT_TRUE(platform_node->HasEventListenerForProperty(UIA_NamePropertyId));
|
||||
EXPECT_TRUE(
|
||||
platform_node->HasEventListenerForProperty(UIA_ControlTypePropertyId));
|
||||
}
|
||||
|
||||
} // namespace ui
|
||||
|
@ -622,7 +622,8 @@ class COMPONENT_EXPORT(AX_PLATFORM) AXPlatformNodeDelegate {
|
||||
//
|
||||
|
||||
// Return the platform-native GUI object that should be used as a target
|
||||
// for accessibility events.
|
||||
// for accessibility events. This function is performance-critical and must
|
||||
// remain efficient.
|
||||
virtual gfx::AcceleratedWidget GetTargetForNativeAccessibilityEvent();
|
||||
|
||||
//
|
||||
|
@ -764,28 +764,29 @@ void AXPlatformNodeWin::NotifyAccessibilityEvent(ax::mojom::Event event_type) {
|
||||
|
||||
if (std::optional<DWORD> native_event = MojoEventToMSAAEvent(event_type)) {
|
||||
HWND hwnd = GetDelegate()->GetTargetForNativeAccessibilityEvent();
|
||||
if (!hwnd)
|
||||
return;
|
||||
|
||||
TRACE_EVENT("accessibility", "NotifyWinEvent", "native_event",
|
||||
base::StringPrintf("0x%04lX", native_event.value()));
|
||||
::NotifyWinEvent((*native_event), hwnd, OBJID_CLIENT, -GetUniqueId());
|
||||
if (hwnd) {
|
||||
TRACE_EVENT("accessibility", "NotifyWinEvent", "native_event",
|
||||
base::StringPrintf("0x%04lX", native_event.value()));
|
||||
::NotifyWinEvent(*native_event, hwnd, OBJID_CLIENT, -GetUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
if (std::optional<PROPERTYID> uia_property =
|
||||
MojoEventToUIAProperty(event_type)) {
|
||||
MojoEventToUIAProperty(event_type);
|
||||
uia_property.has_value() && HasEventListenerForProperty(*uia_property)) {
|
||||
// For this event, we're not concerned with the old value.
|
||||
base::win::ScopedVariant old_value;
|
||||
::VariantInit(old_value.Receive());
|
||||
base::win::ScopedVariant new_value;
|
||||
::VariantInit(new_value.Receive());
|
||||
GetPropertyValueImpl((*uia_property), new_value.Receive());
|
||||
::UiaRaiseAutomationPropertyChangedEvent(this, (*uia_property), old_value,
|
||||
GetPropertyValueImpl(*uia_property, new_value.Receive());
|
||||
::UiaRaiseAutomationPropertyChangedEvent(this, *uia_property, old_value,
|
||||
new_value);
|
||||
}
|
||||
|
||||
if (std::optional<EVENTID> uia_event = MojoEventToUIAEvent(event_type)) {
|
||||
::UiaRaiseAutomationEvent(this, (*uia_event));
|
||||
if (std::optional<EVENTID> uia_event = MojoEventToUIAEvent(event_type);
|
||||
uia_event.has_value() && HasEventListenerForEvent(*uia_event)) {
|
||||
::UiaRaiseAutomationEvent(this, *uia_event);
|
||||
}
|
||||
|
||||
// Keep track of objects that are a target of an alert event.
|
||||
@ -819,7 +820,8 @@ void AXPlatformNodeWin::FireUiaTextEditTextChangedEvent(
|
||||
const gfx::Range& range,
|
||||
const std::wstring& active_composition_text,
|
||||
bool is_composition_committed) {
|
||||
if (!AXPlatform::GetInstance().IsUiaProviderEnabled()) {
|
||||
if (!AXPlatform::GetInstance().IsUiaProviderEnabled() ||
|
||||
!HasEventListenerForEvent(UIA_Text_TextChangedEventId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -5271,17 +5273,12 @@ IFACEMETHODIMP AXPlatformNodeWin::get_FragmentRoot(
|
||||
WIN_ACCESSIBILITY_API_HISTOGRAM(UMA_API_GET_FRAGMENTROOT);
|
||||
UIA_VALIDATE_CALL_1_ARG(fragment_root);
|
||||
|
||||
gfx::AcceleratedWidget widget =
|
||||
delegate_->GetTargetForNativeAccessibilityEvent();
|
||||
if (widget) {
|
||||
AXFragmentRootWin* root =
|
||||
AXFragmentRootWin::GetForAcceleratedWidget(widget);
|
||||
if (root != nullptr) {
|
||||
root->GetNativeViewAccessible()->QueryInterface(
|
||||
IID_PPV_ARGS(fragment_root));
|
||||
DCHECK(*fragment_root);
|
||||
return S_OK;
|
||||
}
|
||||
AXFragmentRootWin* root = GetAXFragmentRootWin();
|
||||
if (root) {
|
||||
root->GetNativeViewAccessible()->QueryInterface(
|
||||
IID_PPV_ARGS(fragment_root));
|
||||
DCHECK(*fragment_root);
|
||||
return S_OK;
|
||||
}
|
||||
|
||||
*fragment_root = nullptr;
|
||||
@ -6002,6 +5999,10 @@ STDMETHODIMP AXPlatformNodeWin::InternalQueryInterface(
|
||||
if (!features::IsIChromeAccessibleEnabled()) {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
} else if (riid == IID_IRawElementProviderAdviseEvents) {
|
||||
if (!base::FeatureList::IsEnabled(features::kUiaEventOptimization)) {
|
||||
return E_NOINTERFACE;
|
||||
}
|
||||
}
|
||||
|
||||
return CComObjectRootBase::InternalQueryInterface(this_ptr, entries, riid,
|
||||
@ -6984,6 +6985,15 @@ int AXPlatformNodeWin::MSAARole() {
|
||||
}
|
||||
}
|
||||
|
||||
AXFragmentRootWin* AXPlatformNodeWin::GetAXFragmentRootWin() {
|
||||
gfx::AcceleratedWidget widget =
|
||||
delegate_->GetTargetForNativeAccessibilityEvent();
|
||||
if (widget) {
|
||||
return AXFragmentRootWin::GetForAcceleratedWidget(widget);
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
AXPlatformNodeWin* AXPlatformNodeWin::GetParentPlatformNodeWin() const {
|
||||
return static_cast<AXPlatformNodeWin*>(
|
||||
AXPlatformNode::FromNativeViewAccessible(GetParent()));
|
||||
@ -8312,6 +8322,32 @@ void AXPlatformNodeWin::ResetComputedHypertext() {
|
||||
hypertext_ = AXLegacyHypertext();
|
||||
}
|
||||
|
||||
bool AXPlatformNodeWin::HasEventListenerForEvent(EVENTID event_id) {
|
||||
if (!base::FeatureList::IsEnabled(features::kUiaEventOptimization)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AXFragmentRootWin* fragment_root = GetAXFragmentRootWin();
|
||||
if (!fragment_root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fragment_root->HasEventListenerForEvent(event_id);
|
||||
}
|
||||
|
||||
bool AXPlatformNodeWin::HasEventListenerForProperty(PROPERTYID property_id) {
|
||||
if (!base::FeatureList::IsEnabled(features::kUiaEventOptimization)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
AXFragmentRootWin* fragment_root = GetAXFragmentRootWin();
|
||||
if (!fragment_root) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return fragment_root->HasEventListenerForProperty(property_id);
|
||||
}
|
||||
|
||||
double AXPlatformNodeWin::GetHorizontalScrollPercent() {
|
||||
if (!IsHorizontallyScrollable())
|
||||
return UIA_ScrollPatternNoScroll;
|
||||
|
@ -360,6 +360,7 @@ class VariantVector;
|
||||
|
||||
namespace ui {
|
||||
|
||||
class AXFragmentRootWin;
|
||||
class AXPlatformNodeWin;
|
||||
|
||||
// A simple interface for a class that wants to be notified when Windows
|
||||
@ -396,8 +397,8 @@ class COMPONENT_EXPORT(AX_PLATFORM)
|
||||
~WinAccessibilityAPIUsageScopedUIAEventsNotifier();
|
||||
};
|
||||
|
||||
class COMPONENT_EXPORT(AX_PLATFORM) __declspec(
|
||||
uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) AXPlatformNodeWin
|
||||
class COMPONENT_EXPORT(AX_PLATFORM)
|
||||
__declspec(uuid("26f5641a-246d-457b-a96d-07f3fae6acf2")) AXPlatformNodeWin
|
||||
: public SequenceAffineComObjectRoot,
|
||||
public IDispatchImpl<IAccessible2_4,
|
||||
&IID_IAccessible2_4,
|
||||
@ -1203,6 +1204,9 @@ class COMPONENT_EXPORT(AX_PLATFORM) __declspec(
|
||||
// Clear the computed hypertext.
|
||||
void ResetComputedHypertext();
|
||||
|
||||
bool HasEventListenerForEvent(EVENTID event_id);
|
||||
bool HasEventListenerForProperty(PROPERTYID property_id);
|
||||
|
||||
// Convert a mojo event to an MSAA event. Exposed for testing.
|
||||
static std::optional<DWORD> MojoEventToMSAAEvent(ax::mojom::Event event);
|
||||
|
||||
@ -1318,6 +1322,8 @@ class COMPONENT_EXPORT(AX_PLATFORM) __declspec(
|
||||
const wchar_t* aria_role;
|
||||
};
|
||||
|
||||
AXFragmentRootWin* GetAXFragmentRootWin();
|
||||
|
||||
AXPlatformNodeWin* GetParentPlatformNodeWin() const;
|
||||
|
||||
int GetAnnotationTypeImpl() const;
|
||||
|
@ -139,6 +139,14 @@ void BrowserAccessibilityManagerWin::FireAriaNotificationEvent(
|
||||
ax::mojom::AriaNotificationInterrupt interrupt_property,
|
||||
ax::mojom::AriaNotificationPriority priority_property) {
|
||||
DCHECK(node);
|
||||
if (!AXPlatform::GetInstance().IsUiaProviderEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ToBrowserAccessibilityWin(node)->GetCOM()->HasEventListenerForEvent(
|
||||
UIA_NotificationEventId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// This API is only supported from Windows10 (version 1709) onwards.
|
||||
// Check if the function pointer is valid or not.
|
||||
@ -665,6 +673,11 @@ void BrowserAccessibilityManagerWin::FireUiaAccessibilityEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ToBrowserAccessibilityWin(node)->GetCOM()->HasEventListenerForEvent(
|
||||
uia_event)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WinAccessibilityAPIUsageScopedUIAEventsNotifier scoped_events_notifier;
|
||||
|
||||
::UiaRaiseAutomationEvent(ToBrowserAccessibilityWin(node)->GetCOM(),
|
||||
@ -691,14 +704,18 @@ void BrowserAccessibilityManagerWin::FireUiaPropertyChangedEvent(
|
||||
VARIANT old_value = {};
|
||||
old_value.vt = VT_EMPTY;
|
||||
|
||||
if (!ToBrowserAccessibilityWin(node)->GetCOM()->HasEventListenerForProperty(
|
||||
uia_property)) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
if (SUCCEEDED(ToBrowserAccessibilityWin(node)->GetCOM()->GetPropertyValueImpl(
|
||||
uia_property, new_value.Receive()))) {
|
||||
::UiaRaiseAutomationPropertyChangedEvent(
|
||||
ToBrowserAccessibilityWin(node)->GetCOM(), uia_property, old_value,
|
||||
new_value);
|
||||
}
|
||||
}
|
||||
|
||||
@ -723,10 +740,10 @@ void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent(
|
||||
return;
|
||||
}
|
||||
|
||||
auto* provider = ToBrowserAccessibilityWin(node);
|
||||
auto* provider_com = provider ? provider->GetCOM() : nullptr;
|
||||
if (!provider || !provider_com)
|
||||
if (!ToBrowserAccessibilityWin(node)->GetCOM()->HasEventListenerForEvent(
|
||||
UIA_StructureChangedEventId)) {
|
||||
return;
|
||||
}
|
||||
|
||||
WinAccessibilityAPIUsageScopedUIAEventsNotifier scoped_events_notifier;
|
||||
|
||||
@ -738,7 +755,8 @@ void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent(
|
||||
auto* parent_com = parent ? parent->GetCOM() : nullptr;
|
||||
if (parent && parent_com) {
|
||||
AXPlatformNodeWin::RuntimeIdArray runtime_id;
|
||||
provider_com->GetRuntimeIdArray(runtime_id);
|
||||
ToBrowserAccessibilityWin(node)->GetCOM()->GetRuntimeIdArray(
|
||||
runtime_id);
|
||||
UiaRaiseStructureChangedEvent(parent_com, change_type,
|
||||
runtime_id.data(), runtime_id.size());
|
||||
}
|
||||
@ -748,7 +766,8 @@ void BrowserAccessibilityManagerWin::FireUiaStructureChangedEvent(
|
||||
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);
|
||||
UiaRaiseStructureChangedEvent(ToBrowserAccessibilityWin(node)->GetCOM(),
|
||||
change_type, nullptr, 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -776,24 +795,30 @@ BrowserAccessibilityManagerWin::GetUiaActiveTextPositionChangedEventFunction() {
|
||||
|
||||
void BrowserAccessibilityManagerWin::FireUiaActiveTextPositionChangedEvent(
|
||||
BrowserAccessibility* node) {
|
||||
if (!ShouldFireEventForNode(node))
|
||||
if (!ShouldFireEventForNode(node)) {
|
||||
return;
|
||||
}
|
||||
|
||||
UiaRaiseActiveTextPositionChangedEventFunction
|
||||
active_text_position_changed_func =
|
||||
GetUiaActiveTextPositionChangedEventFunction();
|
||||
|
||||
if (!active_text_position_changed_func)
|
||||
if (!active_text_position_changed_func) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Create the text range contained by the target node.
|
||||
auto* target_node = ToBrowserAccessibilityWin(node)->GetCOM();
|
||||
if (!ToBrowserAccessibilityWin(node)->GetCOM()->HasEventListenerForEvent(
|
||||
UIA_ActiveTextPositionChangedEventId)) {
|
||||
return;
|
||||
}
|
||||
Microsoft::WRL::ComPtr<ITextRangeProvider> text_range;
|
||||
AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(target_node,
|
||||
&text_range);
|
||||
AXPlatformNodeTextProviderWin::CreateDegenerateRangeAtStart(
|
||||
ToBrowserAccessibilityWin(node)->GetCOM(), &text_range);
|
||||
|
||||
// Fire the UiaRaiseActiveTextPositionChangedEvent.
|
||||
active_text_position_changed_func(target_node, text_range.Get());
|
||||
active_text_position_changed_func(ToBrowserAccessibilityWin(node)->GetCOM(),
|
||||
text_range.Get());
|
||||
}
|
||||
|
||||
bool BrowserAccessibilityManagerWin::CanFireEvents() const {
|
||||
|
Reference in New Issue
Block a user