Skip to content

Clarify relationship between aria-hidden and aria-owns #1839

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 21 commits into
base: main
Choose a base branch
from

Conversation

adampage
Copy link
Member

@adampage adampage commented Oct 21, 2022

Closes #1818.

The definition of Owning Element is already inclusive of both DOM ancestry and ownership via aria-owns:

An 'owning element' is any DOM ancestor of the element, or any element with an aria-owns attribute which references the ID of the element.

Because of this, I’m thinking it’ll be sufficient to swap out “ancestors” for “owning elements” in aria-hidden’s description.

I also editorially removed what I believe is an unhelpful comma.

Test, Documentation and Implementation tracking

Once this PR has been reviewed and has consensus from the working group, tests should be written and issues should be opened on browsers. Add N/A and check when not applicable.


Preview | Diff

@adampage adampage self-assigned this Oct 21, 2022
@adampage adampage added the clarification clarifying or correcting language that is either confusing, misleading or under-specified label Oct 21, 2022
@adampage
Copy link
Member Author

Here’s a Codepen with a few exploratory tests.

@Jym77
Copy link

Jym77 commented Nov 30, 2022

Two points to mention:


I'm encountering a problem similar to #1818 but the other way around, and I'm not sure this PR fully handles it 🤔

The case in #1818 was:

  <ul aria-owns="li"></ul>
  <div aria-hidden="true">
    <li id="li">Child node</li>
  </div>

That is, a not-hidden element aria-owns an element with a hidden DOM ancestor. In that case, this PR says that the li#li element should also be hidden because it has an "owning element" (here, DOM ancestor) which is hidden.

What happens in the reverse case:

<div aria-hidden="true">
  <div id="owner" aria-owns="li" />
</div>
<ul> <li id="li">Item</li> </ul>

In that case, a hidden element aria-owns an element with no hidden ancestor. The clarification in this PR also means that li#li should be hidden because it has an "owning element" (here, an element with aria-owns) which is hidden.
Quick tests, it seems that browsers ignore aria-owns in this case.
So, mostly raising the point to make sure this is the intention of the PR.


Second point is that the language in §7.1 Excluding Elements from the Accessibility Tree uses a lot of "descendant" and other tree-related wording, notably for the case concerning this PR:

user agents SHOULD NOT include the following elements in the accessibility tree:

  • Elements, including their descendants, that have aria-hidden set to true.

Should there be a precision that "descendants" here mean "either descendant in the DOM tree, or descendant in the accessibility tree where aria-owns relations are taken into account"? Most of this Section would benefit from such clarification. Maybe it's a topic for another PR, though 🤔

@pkra
Copy link
Member

pkra commented Nov 30, 2022

For the second point: #1150

@adampage
Copy link
Member Author

adampage commented Dec 6, 2022

In that case, a hidden element aria-owns an element with no hidden ancestor.

Mmm, thanks for poking at this, @Jym77. I hadn’t considered that reverse case.

“Owning element” is currently defined as “any DOM ancestor of the element, or any element with an aria-owns attribute which references the ID of the element.

In this reverse scenario, <div aria-hidden="true"> is neither a DOM ancestor of li#li nor does it use aria-owns to reference li#li directly. It feels a bit lawyerly 🤔, but I think we could then say that <div aria-hidden="true"> is not an “owning element” of li#li. div#owner is hidden because its DOM ancestor is aria-hidden, so its use of aria-owns is no longer exposed to the accessibility API. This leaves li#li in its original location, owned by its parent ul.

@Jym77
Copy link

Jym77 commented Dec 7, 2022

@adampage That actually makes sense 😄
This PR seems to change the behaviour in the related simplified case:

<div aria-hidden="true" aria-owns="li"></div>

<ul> <li id="li">Item</li> </ul>

In that case, the current behaviour is to expose li#li as it has no ancestor with aria-hidden (and Chrome and FF do that), but the new behaviour will be to hide it since it has a owning element with aria-hidden.

I guess it's a bit of a question which of aria-owns or aria-hidden "acts faster". The current behaviour is that aria-hidden hides away the owner before it has time to own anything else, but the new behaviour is that aria-owns is tweaking the tree before the owner gets hidden 🤔


Can get weirder with a case like:

<div aria-hidden="true" aria-owns="foo"></div>
<div id="foo" aria-owns="bar"></div>
<div id="bar"></div>

Now, #foo has a owning element with aria-hidden, so it is hidden. But since it itself does not have aria-hidden, it still owns #bar, which is also not hidden in any way. I feel that is at best confusing and might explode in weird ways 🤔

@adampage
Copy link
Member Author

adampage commented Dec 7, 2022

This discussion has been super helpful, @Jym77, thank you. 😁 I think you hit the nail on the head with the question of which attribute “acts faster”.

Let’s say that this PR now includes some additional text (somewhere appropriate in the spec) to the effect of:

Changes to ownership are resolved in DOM order. If both aria-hidden and aria-owns are specified on the same element, aria-hidden is processed first and aria-owns will not take effect.

Now let’s apply that to the four scenarios that have come up. In the initial #1818 example:

  <ul aria-owns="li"></ul>
  <div aria-hidden="true">
    <li id="li">Child node</li>
  </div>

...the ul steals ownership of #li from div[aria-hidden="true"] because it’s encountered first in DOM order. #li becomes not hidden because ul is not hidden. By the time div[aria-hidden="true"] is encountered, it no longer has a descendant to be affected by its use of aria-hidden. It hides itself, full stop.

In the reverse case:

<div aria-hidden="true">
  <div id="owner" aria-owns="li" />
</div>
<ul> <li id="li">Item</li> </ul>

...div[aria-hidden="true"] is encountered first, so it excludes itself and its descendants from the accessibility API, leaving #owner[aria-owns="li"] altogether ignored. #li continues to be naturally owned by ul and not hidden.

In the follow-up simplified case:

<div aria-hidden="true" aria-owns="li"></div>
<ul> <li id="li">Item</li> </ul>

...div[aria-hidden="true"] is encountered, and it resolves aria-hidden before aria-owns. This prevents the change in ownership of #li. #li continues to be naturally owned by ul, and not hidden.

In the follow-up weirder case:

<div aria-hidden="true" aria-owns="foo"></div>
<div id="foo" aria-owns="bar"></div>
<div id="bar"></div>

...the previous story is essentially true again. div[aria-hidden="true"] is encountered, and it resolves aria-hidden before aria-owns. This prevents the change in ownership for #foo. #foo is then encountered, but is not hidden so its ownership of #bar is resolved and both remain not hidden.


The original intention of this spec clarification was to align with the current Chrome & Firefox behavior, so I built out all four of these scenarios in Codepen and tested them with macOS + VoiceOver + Chrome. Each outcome I described above is correctly demonstrated by that AT combo.

Whew. Having said all that, would you agree that the gist of my additional spec proposal is helpful & accurate? And that it would reasonably lead to the interpretations I described for each of those four scenarios? Are there yet other ambiguous scenarios lurking out there? 😅

Thanks again!

@Jym77
Copy link

Jym77 commented Dec 8, 2022

Changes to ownership are resolved in DOM order. If both aria-hidden and aria-owns are specified on the same element, aria-hidden is processed first and aria-owns will not take effect.

Whew. Having said all that, would you agree that the gist of my additional spec proposal is helpful & accurate? And that it would reasonably lead to the interpretations I described for each of those four scenarios? Are there yet other ambiguous scenarios lurking out there? 😅

Yes, I think that clarification works. Maybe we need to add that change of ownership is only initiated by non-hidden node (that's more or less the second sentence but making it clear that one should stop looking for aria-owns upon encountering a aria-hidden, i.e. the way case 2 works)¹
From a graph-theory point of view, we'd walk the DOM tree in DOM order, stop the current traversal on aria-hidden or already traversed node, and jump to follow aria-owns (which does trigger the "already traversed node" in the previous check).

This also makes it clear that aria-hidden affects the accessibility tree while display: none affects the DOM tree (so replacing aria-hidden by display: none in case 2 does not un-hide the li, it is still not rendered, therefore hidden).

Great clarification 👍

@scottaohara
Copy link
Member

scottaohara commented Dec 8, 2022

was following along with this until:

This also makes it clear that aria-hidden affects the accessibility tree while display: none affects the DOM tree (so replacing aria-hidden by display: none in case 2 does not un-hide the li, it is still not rendered, therefore hidden).

this statement has made me wonder if this actually makes sense or not. since display none does modify the a11y tree... and then i'm not sure what case 2 refers to? the second of Adam's examples in his last comment, or the second odd case you brought up, @Jym77 ?

@Jym77
Copy link

Jym77 commented Dec 8, 2022

Yep, poor writing on my side 🙈 and messing up things badly…

  <ul aria-owns="li"></ul>
  <div style="display: none">
    <li id="li">Child node</li>
  </div>

case 1 not 2 in Adam's list with aria-hidden replaced by display: none.

This does not expose li#li since it hits the "Elements, including their descendent elements, that have host language semantics specifying that the element is not displayed" condition.

That is of course linked to non-interference with the host language (aria-owns cannot expose an element that HTML is hiding, but can expose an element that ARIA is trying to hide (aria-hidden)). Which is more or less what I was trying to say by putting display: none at the DOM level and aria-hidden at the accessibility tree level.
Of course, display: none affects the accessibility tree, but indirectly because it acts on the DOM tree in way that accessibility tree building is concerned, that's why I was writing that it affects the DOM tree.

@scottaohara
Copy link
Member

Thanks for the correction/clarification. Yah that makes sense again now.

@adampage adampage force-pushed the owns-ignores-hidden branch from 3ddc773 to 4d5c3a6 Compare January 11, 2023 02:43
@adampage
Copy link
Member Author

So, I reacquainted myself with the spec’s definition of “hidden:

Indicates that the element is not visible, perceivable, or interactive to any user. An element is considered hidden if it or any one of its ancestor elements is not rendered or is explicitly hidden.

Based on that, I took another pass at the aria-hidden section with the goal of disambiguating its use of the term “hidden” by leveraging the existing “Excluding Elements from the Accessibility Tree” section.

Beyond that, I wordsmithed the clarifications we discussed earlier in this thread and added them to the aria-owns section.

@Jym77
Copy link

Jym77 commented Jan 12, 2023

I feel there is still a small clarification needed to say that aria-owns breaks DOM parent/child relation in addition to create new ones (i.e. the result is indeed an accessibility tree, not a DAG).

Owning element states:

An 'owning element' is any DOM ancestor of the element, or any element with an aria-owns attribute which references the ID of the element.

Because of the "or", and no mention of unicity, in case 1 above

<ul aria-owns="li"></ul>
  <div aria-hidden="true">
    <li id="li">Child node</li>
  </div>

Both the ul and the div are "owning element" for the li (or at least the definition doesn't really tells which to use), and the definition of aria-owns doesn't really answer that question either. (and since any DOM ancestor is a "owning element", it is actually not a problem to have more than one owning element).

So, without disambiguating that, the li has an aria-hidden "owning element" (the div) and should be hidden, which is precisely what we want to avoid.

Should we change the definition of "owning element" to state something like

An 'owning element' is any element with an aria-owns attribute which references the ID of the element or, if none, any DOM ancestor of the element.

Otherwise, I think this is correct and expresses what is needed 👍

@spectranaut
Copy link
Contributor

I just want to note that @Jym77's recommended definition of owned is nice, and, that there is also a 1.3-blocking issue related to inconsistent use the term "owned by", just like how hidden was just revisited:

#1161

The "owned by inconsistencies" can be addressed before of after this PR, but it might also lead to a term change or term update.

@adampage
Copy link
Member Author

@adampage, you are planning to turn some of this into a series of WPT tests for web-platform-tests/interop-accessibility#32 correct? Are you planning to file the implementation bugs you've discovered? Trying to determine how best to cross-reference the trackers from the issue, etc.

Heya @cookiecrook, I just turned all of these tests into comparable (I hope) WPT tests: PR #44822. Using your latest tip, I’ve marked that file as .tentative, but will definitely plan to file any legit implementation bugs once this ARIA spec PR (and that WPT PR, if you think it deserves to be in the test suite) is accepted and merged.

I’d be grateful for your thoughts on my approach for testing this spec change within WPT’s capabilities. My original Codepen tests weren’t accname-focused; I just rendered a bunch of generics and went looking in the a11y tree to see how they all got exposed as static text nodes.

With WPT, I chose to use the test driver’s get_computed_label() function and translated all my test markup scenarios into <button> elements relying on a name from content accname assembly. I’m hoping this is roughly equivalent, but the ARIA spec clarification I’m attempting to make in this PR is bigger than accname.

Copy link
Contributor

@spectranaut spectranaut left a comment

Choose a reason for hiding this comment

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

I think this looks good now, except for the switch from "accessibility parents" to "accessibility ancestors"! I'll mark as approving once that is done :)

@adampage
Copy link
Member Author

Thanks, @spectranaut. I’ve swapped in “accessibility ancestors for “accessibility parents” and resolved the merge conflict.

@adampage adampage requested a review from spectranaut May 29, 2024 04:42
Copy link
Contributor

@spectranaut spectranaut left a comment

Choose a reason for hiding this comment

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

Looks good!

@pkra pkra added the spec:aria label Jun 14, 2024
Copy link

netlify bot commented Apr 22, 2025

Deploy Preview for wai-aria ready!

Name Link
🔨 Latest commit c86961b
🔍 Latest deploy log https://app.netlify.com/sites/wai-aria/deploys/6820c9a1e2712b00082790bf
😎 Deploy Preview https://deploy-preview-1839--wai-aria.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify site configuration.

@spectranaut
Copy link
Contributor

@scottaohara and @cookiecrook -- this is another oldie that I think we can land soon. You both have taken a look and provided feedback and Adam responded, can you take another review?

@adampage
Copy link
Member Author

adampage commented May 8, 2025

Heya @cookiecrook / @scottaohara, could I trouble you for a review of this oldie-but-goodie?

I also recently overhauled a supporting WPT test: web-platform-tests/wpt#44822, with these latest results.

Copy link
Member

@scottaohara scottaohara left a comment

Choose a reason for hiding this comment

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

I'm good with everything that's been added for this PR, but I would like the example I created merged in as well.

@adampage
Copy link
Member Author

adampage commented May 9, 2025

Thanks very much, @scottaohara! I committed your example and then made a couple follow-on editorial tweaks.

Does this read okay to you?:

In the following example, <div id="instructions"> and its text content remain exposed and unmoved in the accessibility tree since the element attempting to reparent it with aria-owns is also hidden from all users.

@scottaohara
Copy link
Member

i'm going to approve as is - but i don't think "also" needed to be added. that made me initially think that the sentence was trying to say that the element that was attempting to be reparented was hidden from the a11y tree - and it made me go back to look if that had been changed

@adampage
Copy link
Member Author

Updated. Thank you, @scottaohara. 🙏🏻

@pkra pkra added the waiting for implementations Cannot be merged until there are two browser impls or one impl + impl commit label May 27, 2025
@pkra
Copy link
Member

pkra commented May 27, 2025

Tentatively labeling this "waiting for implementations" since the thread on WPT leaves me wondering if implementation work is needed after all. Please correct me if I'm getting it wrong (e.g., merge the PR).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
clarification clarifying or correcting language that is either confusing, misleading or under-specified spec:aria waiting for implementations Cannot be merged until there are two browser impls or one impl + impl commit
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Should aria-hidden be effected by aria-owns?
6 participants