Use querySelector in place of IDREF

15 views
Skip to first unread message

Dominic Mazzoni

unread,
Mar 10, 2015, 4:33:19 PM3/10/15
to browser-acce...@googlegroups.com
We discussed this on PFWG but it didn't go anywhere yet.

Suppose we redefine the ARIA spec such that anywhere we have an IDREF in an attribute, you're allowed to specify some selector syntax. I'm proposing something like "querySelector(#foo)" or something shorter like "select(#foo)". In theory this could break backwards compatibility, but I think the odds that someone was using an id with the name "select(#foo)" are pretty slim.

The advantage of this approach is that the selector would update automatically, with no JavaScript required. For example, I could implement a listbox like this:

<div role="listbox" aria-activedescendant="select([tabindex="0"])">
  <div role="option" tabindex=0>Apple</div>
  <div role="option" tabindex=-1>Orange</div>
  <div role="option" tabindex=-1>Banana</div>
</div>

Now when the selected item changes, all I need to do is update the tabindex, and the activedescendant changes automatically. The browser would implement this using its existing style recalculation logic, which already knows when a document might have changed in such a way that a selector has changed.

We may have to add separate syntax for a query selector relative to the current element, vs relative to the document.

Some benefits of this proposal:
* Less work / less syntax for developers - no need to keep updating many ARIA attributes
* Developers don't need to add ids to so many elements
* Performance: the browser doesn't need to compute anything if accessibility is off
* Web components: this provides a way to reference an element inside shadow dom

Any thoughts on this idea?

I'd like to try implementing this. I think we'd have to cache the current results so we know if we need to fire an "active descendant changed" notification in the example above.

- Dominic

Alexander Surkov

unread,
Mar 11, 2015, 8:06:02 AM3/11/15
to Dominic Mazzoni, Boris Zbarsky, browser-acce...@googlegroups.com
As I commented on PFWG I like the idea. I'm not sure if there are any pitfalls on implementation way, cc'ing Boris for his opinion.
Thanks.
Alexander.

--
You received this message because you are subscribed to the Google Groups "Browser Accessibility Development" group.
To unsubscribe from this group and stop receiving emails from it, send an email to browser-accessibil...@googlegroups.com.
To post to this group, send email to browser-acce...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/browser-accessibility-dev/CAFz-FYxav8NWzSqmc_LSuvuJhmv--Vj4QLHH8A5HWZoFWTrLeA%40mail.gmail.com.
For more options, visit https://groups.google.com/d/optout.

Alexander Surkov

unread,
Mar 11, 2015, 9:26:45 AM3/11/15
to Boris Zbarsky, Dominic Mazzoni, browser-acce...@googlegroups.com
I believe the processing can be async, but scope may be a document. I assume the proposal is not restricted to aria-activedescendant attribute and applicable to stuff like aria-owns, aria-labelledby etc that are not scoped to their subtrees.

On Wed, Mar 11, 2015 at 9:09 AM, Boris Zbarsky <bzba...@mozilla.com> wrote:
On Tue, Mar 10, 2015 at 4:33 PM, 'Dominic Mazzoni' via Browser
Accessibility Development <browser-accessibility-dev@googlegroups.com
<mailto:browser-accessibility-d...@googlegroups.com>> wrote:

    <div role="listbox" aria-activedescendant="select([tabindex="0"])">
       <div role="option" tabindex=0>Apple</div>
       <div role="option" tabindex=-1>Orange</div>
       <div role="option" tabindex=-1>Banana</div>
    </div>

    Now when the selected item changes, all I need to do is update the
    tabindex, and the activedescendant changes automatically. The
    browser would implement this using its existing style recalculation
    logic

Just so we're all on the same page, this logic involves various cache data structures that are not particularly small.   That's OK if they're basically per-page singletons, but if you have to have many of them per page that becomes a problem.  Furthermore, this logic is designed for the styling case (so "find all the selectors this element matches"), not for the use case you have here ("find all elements this selector matches").  It would be quite a poor fit for this use case.

Browsers can probably get away with using a somewhat simpler and less memory-intensive setup here, subject to the following constraints:

1)  Any change to the DOM inside the element with the aria-activedescendant attribute will require re-evaluation of the selector.  If the active descendant is not required to update synchronously, this should be OK: just set a dirty bit and do the re-evaluation async.  If the active descendant is required to update synchronously, this may be a problem depending on how easy it is to detect whether it has updated or not.

2)  If the selector is not by-design scoped to the element with the aria-activedescendant attribute, then any change in the document might involve having to re-evaluate the selector.  I strongly recommend scoping the selector.


    We may have to add separate syntax for a query selector relative to
    the current element, vs relative to the document.

I don't think you should have the latter at all.


    * Web components: this provides a way to reference an element inside
    shadow dom

This part is not clear yet, because the interaction of shadow DOM and selectors is kinda up in the air.

-Boris

Dominic Mazzoni

unread,
Mar 11, 2015, 12:10:46 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 6:09 AM, Boris Zbarsky <bzba...@mozilla.com> wrote:
On Tue, Mar 10, 2015 at 4:33 PM, 'Dominic Mazzoni' via Browser
Accessibility Development <browser-accessibility-dev@googlegroups.com
<mailto:browser-accessibility-d...@googlegroups.com>> wrote:

    <div role="listbox" aria-activedescendant="select([tabindex="0"])">
       <div role="option" tabindex=0>Apple</div>
       <div role="option" tabindex=-1>Orange</div>
       <div role="option" tabindex=-1>Banana</div>
    </div>

    Now when the selected item changes, all I need to do is update the
    tabindex, and the activedescendant changes automatically. The
    browser would implement this using its existing style recalculation
    logic

Just so we're all on the same page, this logic involves various cache data structures that are not particularly small.   That's OK if they're basically per-page singletons, but if you have to have many of them per page that becomes a problem.  Furthermore, this logic is designed for the styling case (so "find all the selectors this element matches"), not for the use case you have here ("find all elements this selector matches").  It would be quite a poor fit for this use case.

Browsers can probably get away with using a somewhat simpler and less memory-intensive setup here, subject to the following constraints:

1)  Any change to the DOM inside the element with the aria-activedescendant attribute will require re-evaluation of the selector.  If the active descendant is not required to update synchronously, this should be OK: just set a dirty bit and do the re-evaluation async.  If the active descendant is required to update synchronously, this may be a problem depending on how easy it is to detect whether it has updated or not.

Async sounds fine to me. In both Chrome and WebKit, we fire almost all accessibility notifications asynchronously. I'm assuming this wouldn't be "after some long delay" but just a posted task to run the next event loop.
 
2)  If the selector is not by-design scoped to the element with the aria-activedescendant attribute, then any change in the document might involve having to re-evaluate the selector.  I strongly recommend scoping the selector.
    We may have to add separate syntax for a query selector relative to
    the current element, vs relative to the document.

I don't think you should have the latter at all.

Relative to the document is important because while aria-activedescendant typically points to a descendant, things like aria-labelledby usually point to something more like a sibling, and aria-owns often points to something far away. On the flip side, aria-owns and aria-labelledby are the least likely to change dynamically.

I think it's important to keep a couple of things in mind while talking about performance:

1. This code *only* runs if accessibility is enabled - this is only 1% of users, and for the other 99% they should see a small boost in performance because the author no longer has to update these attributes frequently using JavaScript.
2. ARIA attributes that change typically do so only in response to a user event such as changing focus, clicking, or typing - not once per animation frame. If necessary, we could try to optimize this by queueing up ARIA query selector invalidations and rate-limiting them when not in response to a user event. I wouldn't suggest doing that first, I'm just suggesting that there are plenty of opportunities for optimization.
 
    * Web components: this provides a way to reference an element inside
    shadow dom

This part is not clear yet, because the interaction of shadow DOM and selectors is kinda up in the air.

Fair enough.

Dominic Mazzoni

unread,
Mar 11, 2015, 12:25:32 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 6:32 AM, Boris Zbarsky <bzba...@mit.edu> wrote:
On 3/11/15 9:26 AM, Alexander Surkov wrote:
I believe the processing can be async, but scope may be a document. I
assume the proposal is not restricted to aria-activedescendant attribute
and applicable to stuff like aria-owns, aria-labelledby etc that are not
scoped to their subtrees.

In that case, I would like to have a clearer understanding of what exactly would be being selected and under what conditions the selector would need to be re-evaluated before I can comment intelligently on this.

Here are all of the ARIA attributes that take an IDREF or IDREF list:

aria-activedescendant
aria-controls
aria-describedby
aria-flowto
aria-owns
aria-labelledby

Of these, aria-activedescendant is the only one specifically designed to change dynamically. I think we could safely optimize our code assuming the others rarely or never change, but we do have to support them being re-evaluated.

For the attributes that aren't expected to change, the advantage to the web developer is eliminating the need to add an 'id' to so many elements on the page. For example, I've seen cases of aria-owns where the author uses aria-owns to create an accessible parent-child relationship between a list element and list items when they're siblings in the DOM, like this:

<div role="list" class="list" aria-owns="li0 li1 li2"></div>
<div role="listitem"id="li0">Apple</div>
<div role="listitem"id="li1">Orange</div>
<div role="listitem"id="li2">Banana</div>

When there are many items in the list, this gets ridiculous as every item needs its own id, and the aria-owns attribute is a big long list of ids. This could be replaced with a single query string instead. That would mean less HTML to parse and far fewer ids in the global map for 99% of users, and hopefully a negligible performance difference when accessibility is enabled.

Dominic Mazzoni

unread,
Mar 11, 2015, 1:12:39 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 9:59 AM, Boris Zbarsky <bzba...@mit.edu> wrote:
On 3/11/15 12:25 PM, Dominic Mazzoni wrote:
> aria-owns

OK, so for example for this one.

Say someone writes:

  aria-owns="selector([foo='bar'])"

Just to make sure I understand, this would require reevaluating the selector whenever the "foo" attribute is changed to/from the value "bar" on any element in the document (as a mimimum), right?

Yes.
 
What about:

  aria-owns="selector(.mythings)"

This would have to be re-evaluated if any element gains or loses the class "mythings", or if an element with that class appears or disappears from the page, right?

Thinking about it some more, aria-owns allows for multiple results, so we may have to have up to four variants: local querySelector, document querySelector, local querySelectorAll, document querySelectorAll.

Dominic Mazzoni

unread,
Mar 11, 2015, 1:15:08 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 10:02 AM, Boris Zbarsky <bzba...@mozilla.com> wrote:
On 3/11/15 12:10 PM, Dominic Mazzoni wrote:
1. This code *only* runs if accessibility is enabled - this is only 1%
of users

Do we have hard data on that?  Because on recent Windows, all sorts of things will enable accessibility as I understand, starting with hooking up various input devices.

Chrome definitely distinguishes between an input device and something more like a screen reader, because the latter adds a significant performance penalty. My understanding is that Firefox does the same thing.
 
2. ARIA attributes that change

The issue is not the ARIA attribute changing.  The issue is whatever is being selected on changing, no?

Yes, but what I meant is that the author typically uses them to link one static element to another static element, and neither of them will change throughout the lifetime of the document. Again, this is just "typically". Not sure how much that helps us optimize it.

Dominic Mazzoni

unread,
Mar 11, 2015, 1:25:27 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 10:15 AM, Boris Zbarsky <bzba...@mit.edu> wrote:
On 3/11/15 1:12 PM, Dominic Mazzoni wrote:
This would have to be re-evaluated if any element gains or loses the
class "mythings", or if an element with that class appears or disappears
from the page, right?

OK.  So in other words, you either have to mark it dirty on all DOM mutations or you have to do a bunch of work on every DOM mutation to see whether it affects this selector.

Again, as long as it's OK to end up re-evaluating the selector (100% guaranteed, since DOM mutations _will_ happen) per frame or at whatever async frequency the ARIA state is supposed to get updated at, that seems to be fine.  It'll be pretty easy to create selectors that take a long time to evaluate and blow out frame budgets, but there's not much we can do about that; people will just need to not do it.

OK.

It sounds like a selector that's scoped to a small subtree (like the aria-activedescendant case) is much less of a performance concern.

I wonder if it would be safe to just spec that changes to ARIA attributes with selectors are evaluated *asynchronously* and authors should not depend on the sequence in which they are evaluated.


Dominic Mazzoni

unread,
Mar 11, 2015, 1:47:03 PM3/11/15
to Boris Zbarsky, Alexander Surkov, browser-acce...@googlegroups.com
On Wed, Mar 11, 2015 at 10:40 AM, Boris Zbarsky <bzba...@mit.edu> wrote:
On 3/11/15 1:25 PM, Dominic Mazzoni wrote:
I wonder if it would be safe to just spec that changes to ARIA
attributes with selectors are evaluated *asynchronously* and authors
should not depend on the sequence in which they are evaluated.

Do authors have a way to query the state (as in, see whether the set of elements being selected has been updated)?

Not currently, but this is a possibility in the future.
 
If so, you need to specify exactly when this state is updated.

Could it be similar to how layout is handled? For example, when I change the style of the page, layout is queued up and not computed immediately, but if I query an element's offsetLeft that forces it to be computed immediately. Similarly, we could say that if you query element.ariaLabelledBy (future proposed api), it will be computed immediately, but if you don't explicitly request it, any action that changes its value will *asynchronously* trigger firing the accessibility event indicating it changed.

Reply all
Reply to author
Forward
0 new messages