WICG / aom

Accessibility Object Model
http://wicg.github.io/aom/
Other
575 stars 59 forks source link

Expressing relationships as DOM properties #102

Open alice opened 6 years ago

alice commented 6 years ago

As discussed, we would like to propose that we have a common pattern for all non-tree relationships (possibly including htmlFor):

el.htmlFor = 'id-for-my-input';
el.htmlForElement = myInput;  // overrides htmlFor

myCustomInput.ariaActiveDescendant = 'id-for-my-label';
myCustomInput.ariaActiveDescendantElement = myLabel;  // overrides ariaActiveDescendant

myCustomInput.ariaOwns = 'id-for-autocomplete-1';
myCustomInput.ariaOwnsElements = [autocomplete1, autocomplete2]; // overrides ariaOwns

This would supersede the API proposed in #78.

caridy commented 6 years ago

@alice is the new convention for myElement.aria* properties specified somewhere? I know that this is the new direction, but I haven't seen any formalization of the convention in this repo.

davidturissini commented 6 years ago

What is the type of myLabel, autocomplete1, and autocomplete2? Are they DOM nodes or some other kind of accessible node?

caridy commented 6 years ago

@alice, in the example above, when you mention that ariaActiveDescendantElement overrides ariaActiveDescendant, are you referencing to the internal representation used by SR? If the answer to that question is "yes", then the following should be accurate:

el1.ariaActiveDescendant = 'id-for-my-label';
el1.ariaActiveDescendantElement = myLabel;  // overrides ariaActiveDescendant
el1.ariaActiveDescendant === 'id-for-my-label'; // yields `true`

additionally, I will like to clarify the following:

el2.ariaActiveDescendant = 'id-for-my-label';
el2.ariaActiveDescendantElement;  // yields `null` or `undefined`? 

which means that by setting the id value in ariaActiveDescendant, it does not auto-link on ariaActiveDescendantElement.

In other words, these properties are not reflective.

caridy commented 6 years ago

By looking at what is specified today, it seems that the list of properties that will be related to this issue are:

          partial interface Element {
            attribute DOMString ariaActiveDescendant;
            attribute Node ariaActiveDescendantElement;
            attribute DOMString ariaControls;
            attribute NodeList ariaControlsElements;
            attribute DOMString ariaDescribedBy;
            attribute NodeList ariaDescribedByElements; // another suggestion: ariaDescriptionElements
            attribute DOMString ariaFlowTo;
            attribute NodeList ariaFlowToElements;
            attribute DOMString ariaLabelledBy;
            attribute NodeList ariaLabelledByElements; // another suggestion: ariaLabelElements
            attribute DOMString ariaOwns;
            attribute NodeList ariaOwnsElements; // another suggestion: ariaOwnedElements
          };

Can you confirm? I wasn't sure if those should be NodeList or HTMLCollection. Additionally, some of those names are a little bit weird, maybe it is just me.

rniwa commented 6 years ago

What are semantics of NodeList attributes here? Do they return the same node list each time? That would make them live node lists, which we don't want to add to the platform anymore. If they're not [SameObject], then it's weird that [Get] of these properties return a new object each time.

@domenic @annevk @bzbarsky

domenic commented 6 years ago

Right, I think they should use FrozenArray<Element>, as is done in most modern APIs.

alice commented 6 years ago

@domenic Could you point us at some examples of APIs that use FrozenArray<Element>, to give us an idea how it looks in practice? It sounds like a good idea, I just don't know how it works.

bzbarsky commented 6 years ago

Is the UA supposed to do something with these arrays other than storing them on set and returning on get? If so, having them specified as FrozenArray introduces various complications in both standardization and implementation, unfortunately. If they're just being stored and echoed back then FrozenArray should be fine.

domenic commented 6 years ago

@alice sorry for the delay. Not too many things I've seen use FrozenArray<Element> yet, but lots of things use FrozenArray<>; see e.g. this code search.

Spec-wise, the FrozenArray part is pretty easy; as @bzbarsky alludes to, you just reflect it back and forth. The more fun part is the interaction between that and the existing content attribute. Here's a draft of what I imagine, although I admit to not looking at the ARIA spec and how it currently talks about things being labeled:


partial interface Element {
 attribute FrozenArray<Element>? ariaLabelledByElements;
}

Each element has a list of property-set aria-labelledby elements (a list or null). It is initially null. [Better name requested...]

The ariaLabelledByElements attribute's getter returns this element's property-set aria-labelledby elements. Its setter sets this element's property-set aria-labelledby elements to the given value.

An element element's computed aria-labelledby elements is a list of elements determined by the following algorithm:

  1. If element's property-set aria-labelledby elements list is not null, return the element's property-set aria-labelledby elements.
  2. Let contentAttributeValue be the result of getting an attribute value given element and "aria-labelledby".
  3. Let ids be the result of splitting contentAttributeValue on ASCII whitespace.
  4. Let result be an empty list.
  5. For each id of ids:
    1. Let element be the first element, in tree order, within element's tree, whose ID is equal to id. If no such element exists, continue.
    2. Append element to result.
  6. Return result.

Then you'd make sure that every part of the spec's processing model that talks about something being labeled by something for a11y purposes makes use of the "computed aria-labelledby elements" concept.

bzbarsky commented 6 years ago

Its setter sets this element's property-set aria-labelledby elements to the given value.

This part needs more explanation. In particular, the setter is not handed an infra list (which is what I assume you mean by "list" in your definition of property-set aria-labelledby elements. It's handed an ES Array, per https://heycam.github.io/webidl/#es-frozen-array

Which means you need to define how to convert the ES Array to an infra list. For example, you could use https://heycam.github.io/webidl/#create-sequence-from-iterable but that has the drawback of iterating twice (once to convert the input iterable to an ES Array and once to convert that ES Array to a list), which is observably different from iterating once...

In an ideal world, we would have a type with the following properties:

1) On setting, is reflected into the setter as a sequence. 2) On "first" getting, is returned as a frozen Array based on the then-current value of the sequence. 3) Later gets get the last-gotten value until the underlying sequence actually changes.

That would make this case really easy to define...

I guess that can be done by hand via taking object and doing the various conversion and caching bits by hand, but asking people to do that seems unfortunate.

domenic commented 6 years ago

Oh, right, I kind of thought frozenarray handled that for you, but it doesn't. It seems like non-readonly FrozenArray attributes are pretty broken right now. Fortunately I don't see too many uses of them, so we should probably just fix FrozenArray in Web IDL to behave similar to what you describe? https://cs.chromium.org/chromium/src/third_party/blink/renderer/modules/mediasession/media_metadata.idl?q=FrozenArray+file:.*%5C.idl&dr=C&l=16 is the only IDL I can find in my codesearch.

bzbarsky commented 6 years ago

For what it's worth, Gecko doesn't actually support FrozenArray yet; we instead have a thing that has semantics more similar to what I describe. Those semantics are not observably different from FrozenArray if you don't touch the array from inside the browser, because the actual array object creation is not observable.

Anyway, looking for non-readonly web-exposed (as opposed to extension-exposed or browser-internal) cases that use this stuff in Gecko I see nothing that is non-readonly. We don't seem to implement the MediaMetadata interface.

Looking at the spec that defines it, at https://wicg.github.io/mediasession/#dom-mediametadata-artwork, it has the same problem on set: it is treating the value being set as if it were a sequence (and in particular hands it to the same algorithm as the constructor, and the constructor does get a sequence). So it would in fact want the different semantics too, ideally...

bzbarsky commented 6 years ago

Oh, and I would be fine with switching to the model I describe, of course. But I'm biased, because it's already what Gecko implements. ;)