0

[AriaNotify] Scope v1 feature and gate v2 properties behind flag

This CL scopes the ARIA Notify API to accept only a string
`announcement` and a ax::mojom::AriaNotificationPriority `priority`
(values of which can be "high" or "normal", with "normal" being the
default value).

This API shape will serve as V1 of the feature, as agreed upon by the
ARIA working group: https://github.com/w3c/aria/issues/2426

Future versions of the API may include properties of `interrupt` and
`notification_id` (soon to be renamed `type`). More information about
these properties, and the feature in general, can be found in the
explainer:
https://github.com/MicrosoftEdge/MSEdgeExplainers/blob/main/Accessibility/AriaNotify/explainer.md

This CL gates the use of `interrupt` and `notification_id` behind the
"AriaNotifyV2" feature flag. As part of the process of serializing the
ARIA notification attributes, `notification_id` will be passed to the
AXNodeData as an empty string and `interrupt` will be passed as kNone
unless the AriaNotifyV2 flag is enabled.

We cannot even read the value of the `notification_id` or `interrupt`
properties that may be passed by the web author as a part of V1 -- if we
don't avoid reading the properties, then feature detection for those
properties will be enabled. See more:
https://github.com/w3c/aria/issues/2426#issuecomment-2634700360

This CL also adds new tests that use the AriaNotifyV2 feature flag.

AX-Relnotes: This feature is behind a feature flag so it should not
impact the experience of users who are not enabling the flag.

Bug: 326277796

Change-Id: Id72dab40aea10daab3449bd32d2ab99a3746d76d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/6306426
Reviewed-by: Alison Maher <almaher@microsoft.com>
Commit-Queue: Evelynn Kaplan <evelynn.kaplan@microsoft.com>
Reviewed-by: Benjamin Beaudry <benjamin.beaudry@microsoft.com>
Cr-Commit-Position: refs/heads/main@{#1431762}
This commit is contained in:
Evelynn Kaplan
2025-03-12 14:38:46 -07:00
committed by Chromium LUCI CQ
parent eacdc6d0f1
commit 129bb5341c
12 changed files with 249 additions and 55 deletions

@ -419,9 +419,9 @@ void BrowserAccessibilityManagerAndroid::FireGeneratedEvent(
void BrowserAccessibilityManagerAndroid::FireAriaNotificationEvent(
ui::BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) {
ax::mojom::AriaNotificationInterrupt interrupt_property) {
DCHECK(node);
auto* wcax = GetWebContentsAXFromRootManager();

@ -115,9 +115,9 @@ class CONTENT_EXPORT BrowserAccessibilityManagerAndroid
void FireAriaNotificationEvent(
ui::BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) override;
ax::mojom::AriaNotificationInterrupt interrupt_property) override;
void FireLocationChanged(ui::BrowserAccessibility* node);

@ -3082,6 +3082,23 @@ IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
}
#endif
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
TestAccessibilityFocus) {
LoadInitialAccessibilityTreeFromHtml("<button>ok</button>");
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), "ok");
ui::BrowserAccessibility* button_node = FindNode("ok");
ASSERT_NE(button_node, nullptr);
ui::BrowserAccessibilityManager* manager = button_node->manager();
manager->ScrollToMakeVisible(*button_node, gfx::Rect());
EXPECT_EQ(manager->GetAccessibilityFocus(), button_node);
}
#endif // !BUILDFLAG(IS_ANDROID)
class AriaNotifyCrossPlatformAccessibilityBrowserTest
: public CrossPlatformAccessibilityBrowserTest {
public:
@ -3175,13 +3192,16 @@ IN_PROC_BROWSER_TEST_F(AriaNotifyCrossPlatformAccessibilityBrowserTest,
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationAnnouncements));
EXPECT_EQ(std::vector<std::string>{"test"},
// For v1 of the feature, notificationId should have a default value of
// empty string.
EXPECT_EQ(std::vector<std::string>{""},
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds));
// For v1 of the feature, interrupt should have a default value of none.
EXPECT_EQ(
std::vector<int32_t>{static_cast<int32_t>(
ax::mojom::AriaNotificationInterrupt::kPending)},
std::vector<int32_t>{
static_cast<int32_t>(ax::mojom::AriaNotificationInterrupt::kNone)},
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties));
@ -3248,6 +3268,8 @@ IN_PROC_BROWSER_TEST_F(AriaNotifyCrossPlatformAccessibilityBrowserTest,
}
}
// For v1 of the feature, notificationId should have a default value of empty
// string and interrupt should have the default value of none.
IN_PROC_BROWSER_TEST_F(AriaNotifyCrossPlatformAccessibilityBrowserTest,
TestConsecutiveAriaNotifications) {
const std::string url_str(R"HTML(
@ -3269,6 +3291,186 @@ IN_PROC_BROWSER_TEST_F(AriaNotifyCrossPlatformAccessibilityBrowserTest,
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Container");
{
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
ui::AXEventGenerator::Event::ARIA_NOTIFICATIONS_POSTED);
ExecuteScript("document.getElementById('a').click();");
ASSERT_TRUE(waiter.WaitForNotification());
const auto* button = FindNode("a");
ASSERT_NE(button, nullptr);
EXPECT_EQ(
std::vector<std::string>({"one", "two", "three"}),
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationAnnouncements));
EXPECT_EQ(
std::vector<int32_t>(
{static_cast<int32_t>(ax::mojom::AriaNotificationPriority::kNormal),
static_cast<int32_t>(ax::mojom::AriaNotificationPriority::kHigh),
static_cast<int32_t>(
ax::mojom::AriaNotificationPriority::kNormal)}),
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties));
EXPECT_EQ(std::vector<std::string>({"", "", ""}),
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds));
EXPECT_EQ(
std::vector<int32_t>(
{static_cast<int32_t>(ax::mojom::AriaNotificationInterrupt::kNone),
static_cast<int32_t>(ax::mojom::AriaNotificationInterrupt::kNone),
static_cast<int32_t>(
ax::mojom::AriaNotificationInterrupt::kNone)}),
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties));
}
}
class AriaNotifyV2CrossPlatformAccessibilityBrowserTest
: public AriaNotifyCrossPlatformAccessibilityBrowserTest {
public:
AriaNotifyV2CrossPlatformAccessibilityBrowserTest() = default;
AriaNotifyV2CrossPlatformAccessibilityBrowserTest(
const AriaNotifyV2CrossPlatformAccessibilityBrowserTest&) = delete;
AriaNotifyCrossPlatformAccessibilityBrowserTest& operator=(
const AriaNotifyV2CrossPlatformAccessibilityBrowserTest&) = delete;
~AriaNotifyV2CrossPlatformAccessibilityBrowserTest() override = default;
void ChooseFeatures(
std::vector<base::test::FeatureRef>* enabled_features,
std::vector<base::test::FeatureRef>* disabled_features) override {
CrossPlatformAccessibilityBrowserTest::ChooseFeatures(enabled_features,
disabled_features);
enabled_features->emplace_back(blink::features::kAriaNotify);
enabled_features->emplace_back(blink::features::kAriaNotifyV2);
}
};
IN_PROC_BROWSER_TEST_F(AriaNotifyV2CrossPlatformAccessibilityBrowserTest,
TestSingleAriaNotification) {
const std::string url_str(R"HTML(
<!DOCTYPE html>
<div aria-label="Container">
<button aria-label="a" id="a" onclick="notify(this)"></button>
<button aria-label="b" id="b" onclick="otherNotify(this)"></button>
</div>
<script>
function notify(clickedElement) {
clickedElement.ariaNotify("hello");
}
function otherNotify(clickedElement) {
clickedElement.ariaNotify("world", {"interrupt": "pending",
"notificationId": "test",
"priority": "high"});
}
</script>)HTML");
LoadInitialAccessibilityTreeFromHtml(url_str);
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Container");
{
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
ui::AXEventGenerator::Event::ARIA_NOTIFICATIONS_POSTED);
ExecuteScript("document.getElementById('a').click();");
ASSERT_TRUE(waiter.WaitForNotification());
const auto* button = FindNode("a");
ASSERT_NE(button, nullptr);
EXPECT_EQ(
std::vector<std::string>{"hello"},
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationAnnouncements));
EXPECT_EQ(std::vector<std::string>{""},
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds));
EXPECT_EQ(
std::vector<int32_t>{
static_cast<int32_t>(ax::mojom::AriaNotificationInterrupt::kNone)},
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties));
EXPECT_EQ(
std::vector<int32_t>{
static_cast<int32_t>(ax::mojom::AriaNotificationPriority::kNormal)},
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties));
}
{
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
ui::AXEventGenerator::Event::ARIA_NOTIFICATIONS_POSTED);
ExecuteScript("document.getElementById('b').click();");
ASSERT_TRUE(waiter.WaitForNotification());
const auto* button = FindNode("b");
ASSERT_NE(button, nullptr);
EXPECT_EQ(
std::vector<std::string>{"world"},
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationAnnouncements));
EXPECT_EQ(std::vector<std::string>{"test"},
button->GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds));
EXPECT_EQ(
std::vector<int32_t>{
static_cast<int32_t>(ax::mojom::AriaNotificationPriority::kHigh)},
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties));
EXPECT_EQ(
std::vector<int32_t>{static_cast<int32_t>(
ax::mojom::AriaNotificationInterrupt::kPending)},
button->GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties));
}
}
// For v2 of the feature, notificationId and interrupt should have their given
// values.
IN_PROC_BROWSER_TEST_F(AriaNotifyV2CrossPlatformAccessibilityBrowserTest,
TestConsecutiveAriaNotificationsV2) {
std::vector<base::test::FeatureRef> enabled_features;
std::vector<base::test::FeatureRef> disabled_features;
enabled_features.emplace_back(blink::features::kAriaNotifyV2);
ChooseFeatures(&enabled_features, &disabled_features);
const std::string url_str(R"HTML(
<!DOCTYPE html>
<div aria-label="Container">
<button aria-label="a" id="a" onclick="notify(this)"></button>
</div>
<script>
function notify(clickedElement) {
clickedElement.ariaNotify("one", {"notificationId": "kOne",
"interrupt": "all"});
clickedElement.ariaNotify("two", {"priority": "high"});
clickedElement.ariaNotify("three", {"notificationId": "kThree",
"interrupt": "pending"});
}
</script>)HTML");
LoadInitialAccessibilityTreeFromHtml(url_str);
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(),
"Container");
{
AccessibilityNotificationWaiter waiter(
shell()->web_contents(), ui::kAXModeComplete,
@ -3309,21 +3511,4 @@ IN_PROC_BROWSER_TEST_F(AriaNotifyCrossPlatformAccessibilityBrowserTest,
}
}
#if !BUILDFLAG(IS_ANDROID)
IN_PROC_BROWSER_TEST_F(CrossPlatformAccessibilityBrowserTest,
TestAccessibilityFocus) {
LoadInitialAccessibilityTreeFromHtml("<button>ok</button>");
WaitForAccessibilityTreeToContainNodeWithName(shell()->web_contents(), "ok");
ui::BrowserAccessibility* button_node = FindNode("ok");
ASSERT_NE(button_node, nullptr);
ui::BrowserAccessibilityManager* manager = button_node->manager();
manager->ScrollToMakeVisible(*button_node, gfx::Rect());
EXPECT_EQ(manager->GetAccessibilityFocus(), button_node);
}
#endif // !BUILDFLAG(IS_ANDROID)
} // namespace content

@ -2,7 +2,7 @@ enum AriaNotifyInterrupt { "none", "all", "pending" };
enum AriaNotifyPriority { "normal", "high" };
dictionary AriaNotificationOptions {
AriaNotifyInterrupt interrupt = "none";
AriaNotifyPriority priority = "normal";
DOMString notificationId = "";
[RuntimeEnabled=AriaNotifyV2] AriaNotifyInterrupt interrupt = "none";
[RuntimeEnabled=AriaNotifyV2] DOMString notificationId = "";
};

@ -1300,37 +1300,43 @@ void SerializeAriaNotificationAttributes(const AriaNotifications& notifications,
}
std::vector<std::string> announcements;
std::vector<int32_t> priority_properties;
std::vector<std::string> notification_ids;
std::vector<int32_t> interrupt_properties;
std::vector<int32_t> priority_properties;
announcements.reserve(size);
priority_properties.reserve(size);
notification_ids.reserve(size);
interrupt_properties.reserve(size);
priority_properties.reserve(size);
for (const auto& notification : notifications) {
announcements.emplace_back(TruncateString(notification.Announcement()));
notification_ids.emplace_back(
TruncateString(notification.NotificationId()));
interrupt_properties.emplace_back(
static_cast<int32_t>(notification.Interrupt()));
priority_properties.emplace_back(
static_cast<int32_t>(notification.Priority()));
if (RuntimeEnabledFeatures::AriaNotifyV2Enabled()) {
notification_ids.emplace_back(
TruncateString(notification.NotificationId()));
interrupt_properties.emplace_back(
static_cast<int32_t>(notification.Interrupt()));
} else {
notification_ids.emplace_back();
interrupt_properties.emplace_back(static_cast<int32_t>(
ax::mojom::blink::AriaNotificationInterrupt::kNone));
}
}
node_data->AddStringListAttribute(
ax::mojom::blink::StringListAttribute::kAriaNotificationAnnouncements,
announcements);
node_data->AddIntListAttribute(
ax::mojom::blink::IntListAttribute::kAriaNotificationPriorityProperties,
priority_properties);
node_data->AddStringListAttribute(
ax::mojom::blink::StringListAttribute::kAriaNotificationIds,
notification_ids);
node_data->AddIntListAttribute(
ax::mojom::blink::IntListAttribute::kAriaNotificationInterruptProperties,
interrupt_properties);
node_data->AddIntListAttribute(
ax::mojom::blink::IntListAttribute::kAriaNotificationPriorityProperties,
priority_properties);
}
} // namespace

@ -434,6 +434,7 @@
{
name: "AriaNotify",
status: "test",
implied_by: ["AriaNotifyV2"],
},
{
name: "AriaNotifyV2",

@ -239,24 +239,26 @@ void BrowserAccessibilityManager::FireGeneratedEvent(
const auto& announcements = node_data.GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationAnnouncements);
const auto& notification_ids = node_data.GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds);
const auto& interrupt_properties = node_data.GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties);
const auto& priority_properties = node_data.GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationPriorityProperties);
const std::vector<std::string> notification_ids =
node_data.GetStringListAttribute(
ax::mojom::StringListAttribute::kAriaNotificationIds);
const std::vector<std::int32_t> interrupt_properties =
node_data.GetIntListAttribute(
ax::mojom::IntListAttribute::kAriaNotificationInterruptProperties);
DCHECK_EQ(announcements.size(), priority_properties.size());
DCHECK_EQ(announcements.size(), notification_ids.size());
DCHECK_EQ(announcements.size(), interrupt_properties.size());
DCHECK_EQ(announcements.size(), priority_properties.size());
for (std::size_t i = 0; i < announcements.size(); ++i) {
FireAriaNotificationEvent(wrapper, announcements[i], notification_ids[i],
static_cast<ax::mojom::AriaNotificationInterrupt>(
interrupt_properties[i]),
FireAriaNotificationEvent(wrapper, announcements[i],
static_cast<ax::mojom::AriaNotificationPriority>(
priority_properties[i]));
priority_properties[i]),
notification_ids[i],
static_cast<ax::mojom::AriaNotificationInterrupt>(
interrupt_properties[i]));
}
}

@ -140,9 +140,9 @@ class COMPONENT_EXPORT(AX_PLATFORM) BrowserAccessibilityManager
virtual void FireAriaNotificationEvent(
BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) {}
ax::mojom::AriaNotificationInterrupt interrupt_property) {}
virtual void FireBlinkEvent(ax::mojom::Event event_type,
BrowserAccessibility* node,

@ -56,9 +56,9 @@ class COMPONENT_EXPORT(AX_PLATFORM) BrowserAccessibilityManagerMac
void FireAriaNotificationEvent(
BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) override;
ax::mojom::AriaNotificationInterrupt interrupt_property) override;
bool OnAccessibilityEvents(const AXUpdatesAndEvents& details) override;

@ -439,9 +439,9 @@ void BrowserAccessibilityManagerMac::FireSentinelEventForTesting() {
void BrowserAccessibilityManagerMac::FireAriaNotificationEvent(
BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) {
ax::mojom::AriaNotificationInterrupt interrupt_property) {
DCHECK(node);
auto* root_manager = GetManagerForRootFrame();

@ -135,9 +135,9 @@ void BrowserAccessibilityManagerWin::UserIsReloading() {
void BrowserAccessibilityManagerWin::FireAriaNotificationEvent(
BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) {
ax::mojom::AriaNotificationInterrupt interrupt_property) {
DCHECK(node);
// This API is only supported from Windows10 (version 1709) onwards.

@ -58,9 +58,9 @@ class COMPONENT_EXPORT(AX_PLATFORM) BrowserAccessibilityManagerWin
void FireAriaNotificationEvent(
BrowserAccessibility* node,
const std::string& announcement,
ax::mojom::AriaNotificationPriority priority_property,
const std::string& notification_id,
ax::mojom::AriaNotificationInterrupt interrupt_property,
ax::mojom::AriaNotificationPriority priority_property) override;
ax::mojom::AriaNotificationInterrupt interrupt_property) override;
void FireFocusEvent(AXNode* node) override;
void FireBlinkEvent(ax::mojom::Event event_type,