Skip to content

AX: Override aria-hidden on a modal when the page would otherwise be completely empty due to inertness#64337

Merged
webkit-commit-queue merged 1 commit into
WebKit:mainfrom
twilco:eng/AX-Override-aria-hidden-on-a-modal-when-the-page-would-otherwise-be-completely-empty-due-to-inertness
May 6, 2026
Merged

AX: Override aria-hidden on a modal when the page would otherwise be completely empty due to inertness#64337
webkit-commit-queue merged 1 commit into
WebKit:mainfrom
twilco:eng/AX-Override-aria-hidden-on-a-modal-when-the-page-would-otherwise-be-completely-empty-due-to-inertness

Conversation

@twilco
Copy link
Copy Markdown
Contributor

@twilco twilco commented May 6, 2026

4170dc9

AX: Override aria-hidden on a modal when the page would otherwise be completely empty due to inertness
https://bugs.webkit.org/show_bug.cgi?id=314165
rdar://176326010

Reviewed by Dominic Mazzoni.

On a very popular webpage, the web developer added an aria-modal to the
page, and used JavaScript to make all other content outside the modal
inert. This is reasonable, but unfortunately they forget to remove
aria-hidden="true" from the modal's container element, meaning the page
remains empty forever.

To fix this, we start detecting this scenario during modalNode() queries. When
the page has zero unignored content objects and a modal element exists
that is blocked by an ancestor aria-hidden, we override aria-hidden on
that ancestor using the existing shouldIgnoreARIAHidden mechanism.

To make the "is the page empty" check O(1), we maintain an unignored
content object counter that is incremented/decremented during
ignored-state transitions in isIgnoredWithoutCache and decremented in
the AccessibilityObject destructor. Structural roles (web areas, mock
objects, scroll bars, etc.) are excluded from the count since they
don't represent meaningful content, and the lifetime model for mock
objects is broken (we leak all of them), so including them in the count
would make it difficult to maintain.

The override check is armed lazily: only when aria-hidden is set to
true, when an element becomes inert/invisible, or when modal elements
are found. There is no cost on pages that don't hit this pattern.

* LayoutTests/accessibility/aria-hidden-modal-override-when-page-empty-expected.txt: Added.
* LayoutTests/accessibility/aria-hidden-modal-override-when-page-empty.html: Added.
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::findModalNodes):
(WebCore::AXObjectCache::modalNode):
(WebCore::AXObjectCache::onInertOrVisibilityChange):
(WebCore::AXObjectCache::handleAttributeChange):
* Source/WebCore/accessibility/AXObjectCache.h:
* Source/WebCore/accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::~AccessibilityObject):
(WebCore::AccessibilityObject::isIgnoredWithoutCache const):

Canonical link: https://commits.webkit.org/312744@main

b4467d4

Misc iOS, visionOS, tvOS & watchOS macOS Linux Windows Apple Internal
✅ 🧪 style ✅ 🛠 ios ✅ 🛠 mac ✅ 🛠 wpe ✅ 🛠 win ⏳ 🛠 ios-apple
✅ 🧪 bindings ✅ 🛠 ios-sim ✅ 🛠 mac-AS-debug ✅ 🧪 wpe-wk2 ✅ 🧪 win-tests ⏳ 🛠 mac-apple
✅ 🧪 webkitperl ✅ 🧪 ios-wk2 ❌ 🧪 api-mac ✅ 🧪 api-wpe ⏳ 🛠 vision-apple
✅ 🧪 ios-wk2-wpt ✅ 🧪 api-mac-debug ✅ 🛠 gtk3-libwebrtc
✅ 🧪 api-ios ✅ 🧪 mac-wk1 ✅ 🛠 gtk
✅ 🛠 ios-safer-cpp ✅ 🧪 mac-wk2 ✅ 🧪 gtk-wk2
✅ 🛠 vision ✅ 🧪 mac-AS-debug-wk2 ✅ 🧪 api-gtk
✅ 🛠 🧪 merge ✅ 🛠 vision-sim ✅ 🧪 mac-wk2-stress ✅ 🛠 playstation
✅ 🧪 vision-wk2 ✅ 🧪 mac-intel-wk2
✅ 🛠 tv ✅ 🛠 mac-safer-cpp
✅ 🛠 tv-sim
✅ 🛠 watch
✅ 🛠 watch-sim

@twilco twilco self-assigned this May 6, 2026
@twilco twilco added the Accessibility For bugs related to accessibility. label May 6, 2026
Copy link
Copy Markdown
Contributor

@minorninth minorninth left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions for readability and maintainability, but no blockers - the fix looks good

Comment thread Source/WebCore/accessibility/AXObjectCache.cpp
Comment thread Source/WebCore/accessibility/AXObjectCache.cpp
Comment thread Source/WebCore/accessibility/AXObjectCache.cpp
Comment thread Source/WebCore/accessibility/AXObjectCache.h
@twilco twilco added the merge-queue Applied to send a pull request to merge-queue label May 6, 2026
…completely empty due to inertness

https://bugs.webkit.org/show_bug.cgi?id=314165
rdar://176326010

Reviewed by Dominic Mazzoni.

On a very popular webpage, the web developer added an aria-modal to the
page, and used JavaScript to make all other content outside the modal
inert. This is reasonable, but unfortunately they forget to remove
aria-hidden="true" from the modal's container element, meaning the page
remains empty forever.

To fix this, we start detecting this scenario during modalNode() queries. When
the page has zero unignored content objects and a modal element exists
that is blocked by an ancestor aria-hidden, we override aria-hidden on
that ancestor using the existing shouldIgnoreARIAHidden mechanism.

To make the "is the page empty" check O(1), we maintain an unignored
content object counter that is incremented/decremented during
ignored-state transitions in isIgnoredWithoutCache and decremented in
the AccessibilityObject destructor. Structural roles (web areas, mock
objects, scroll bars, etc.) are excluded from the count since they
don't represent meaningful content, and the lifetime model for mock
objects is broken (we leak all of them), so including them in the count
would make it difficult to maintain.

The override check is armed lazily: only when aria-hidden is set to
true, when an element becomes inert/invisible, or when modal elements
are found. There is no cost on pages that don't hit this pattern.

* LayoutTests/accessibility/aria-hidden-modal-override-when-page-empty-expected.txt: Added.
* LayoutTests/accessibility/aria-hidden-modal-override-when-page-empty.html: Added.
* Source/WebCore/accessibility/AXObjectCache.cpp:
(WebCore::AXObjectCache::findModalNodes):
(WebCore::AXObjectCache::modalNode):
(WebCore::AXObjectCache::onInertOrVisibilityChange):
(WebCore::AXObjectCache::handleAttributeChange):
* Source/WebCore/accessibility/AXObjectCache.h:
* Source/WebCore/accessibility/AccessibilityObject.cpp:
(WebCore::AccessibilityObject::~AccessibilityObject):
(WebCore::AccessibilityObject::isIgnoredWithoutCache const):

Canonical link: https://commits.webkit.org/312744@main
@webkit-commit-queue webkit-commit-queue force-pushed the eng/AX-Override-aria-hidden-on-a-modal-when-the-page-would-otherwise-be-completely-empty-due-to-inertness branch from b4467d4 to 4170dc9 Compare May 6, 2026 22:55
@webkit-commit-queue
Copy link
Copy Markdown
Collaborator

Committed 312744@main (4170dc9): https://commits.webkit.org/312744@main

Reviewed commits have been landed. Closing PR #64337 and removing active labels.

@webkit-commit-queue webkit-commit-queue merged commit 4170dc9 into WebKit:main May 6, 2026
@webkit-commit-queue webkit-commit-queue removed the merge-queue Applied to send a pull request to merge-queue label May 6, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Accessibility For bugs related to accessibility.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants