aria icon indicating copy to clipboard operation
aria copied to clipboard

ARIAMixin has many integer attributes with string types and uses DOMString? incorrectly

Open annevk opened this issue 6 years ago • 56 comments

It would be nicer if these were reflected as numbers. E.g., aria-level.

cc @alice @domenic

annevk avatar Oct 31 '19 20:10 annevk

If we do this (we probably should), then a choice needs to be made for each one, between the different types of reflection:

  • long
  • long limited to only non-negative numbers
  • unsigned long
  • unsigned long limited to only non-negative numbers greater than zero
  • unsigned long limited only to non-negative numbers greater than zero with fallback
  • unsigned long clamped to the range [min, max]
  • double
  • unrestricted double
  • double limited to numbers greater than zero
  • unrestricted double limited to numbers greater than zero

Probably don't consider the long ones.

domenic avatar Oct 31 '19 20:10 domenic

I have a vague memory of discussing this and concluding that string reflection was the way to go, but I don't remember any details. @cookiecrook may remember?

alice avatar Nov 01 '19 02:11 alice

Listing out non-string types per the ARIA value types (not including token types or true/false/undefined):

  • aria-atomic (true/false, i.e. empty string is invalid but missing value default is false)
  • aria-busy (true/false)
  • aria-colcount (integer >= -1, although zero would be weird)
  • aria-colindex (integer >= 1)
  • aria-colspan (integer >= 1 - this is weirdly inconsistent with aria-rowspan)
  • aria-disabled (true/false)
  • aria-level (integer >= 1)
  • aria-modal (true/false)
  • aria-multiline (true/false)
  • aria-multiselectable (true/false)
  • aria-posinset (integer >= 1)
  • aria-readonly (true/false)
  • aria-required (true/false)
  • aria-rowcount (integer >= -1)
  • aria-rowindex (integer >= 1)
  • aria-rowspan (integer >= 0 - weirdly inconsistent with aria-colspan)
  • aria-setsize (integer >= -1)
  • aria-valuemax (integer)
  • aria-valuemin (integer)
  • aria-valuenow (integer)

The true/false ones are interesting, since they're almost but not quite boolean.

alice avatar Nov 01 '19 03:11 alice

From Domenic It sounded like there was a preference for true/false values to be enumerated values so they can be extended in the future. I kinda wish they would not have used true and false as values then, but so be it. So those probably have to remain strings, though limited to known values would be nice.

HTML's colSpan and rowSpan are unsigned long and colSpan there also has to be at least 1, whereas rowSpan is at least 0. I don't really know the background.

(It seems HTML uses long (for allowing -1) and unsigned long (when non-negative) pretty consistently.)

annevk avatar Nov 01 '19 08:11 annevk

Chatting with @cookiecrook about this just now - he is going to do some research and try to remember why we went with string reflection originally.

alice avatar Apr 07 '20 23:04 alice

Initial experimentation was in AOM threads. https://github.com/WICG/aom/pull/120

More meaty, long history thread inside ARIA tracker starts here: https://github.com/w3c/aria/issues/691#issuecomment-367878962

Most relevant comment is this one: https://github.com/w3c/aria/issues/691#issuecomment-385872627

Excerpt:

Changed all long values to DOMString, since long cannot be nullable. Changed all double values to DOMString, since double-to-string conversion is lossy and imprecise.

And we pulled a few more IDL props prior to shipping ARIA 1.2 to make way for element reflection. https://github.com/w3c/aria/issues/834

cookiecrook avatar Apr 08 '20 19:04 cookiecrook

Why is long not nullable? Pretty sure it is.

And reflection of double to string is defined in HTML and used by a large number of features. The rationale given doesn't seem like a good reason to establish a new API pattern.

annevk avatar Apr 09 '20 07:04 annevk

There is no reflection for nullable long defined in HTML. Instead, when the attribute is not present, a default value is used. (Usually 0 or -1.)

I agree that ARIA sticking to that would be better than inventing a new convention.

domenic avatar Apr 09 '20 13:04 domenic

As I mentioned on IRC, there's no nullable string for reflection either (other than for enumerated attributes), so if that's the argument I'm confused.

annevk avatar Apr 09 '20 14:04 annevk

@domenic wrote:

I agree that ARIA sticking to that would be better

Sticking to what would be better? String reflection as-is?

cookiecrook avatar Apr 20 '20 19:04 cookiecrook

No, sorry, sticking to using default values (like 0 or -1), like all other reflected numeric HTML attributes do.

domenic avatar Apr 20 '20 19:04 domenic

Ok then, let's work out the edge cases.

  1. As Alice mentioned, some ARIA attrs are true booleans (@aria-modal), others are true/false/mixed, and still others are true/false/undefined, where the unset value is different that either true or false. One example is @arial-pressed. When used on role="button" the existence of the attribute means it's a toggle button in a pressed (true) or unpressed (false) state. The undefined default (absence of the the attr) means it's a standard non-toggle button. (This might be superseded by the next item.)

  2. Several of the aria attributes are enumerable token values: such as @aria-invalid. Its values are currently true, false, spelling, and grammar though this list is intended to be expandable in future versions of ARIA. If I understand correctly, this would require defining new IDL value types as I attempted in the original issue here: https://github.com/w3c/aria/issues/691#issuecomment-367891305

  3. Many of the number values (@aria-valuenow, etc.) are not integers, but floating point doubles. Double-to-String reflection is lossy due to floating point math. Is this acceptable? Perhaps, but it will surprise some web authors.

  4. Most ARIA attributes can be adopted by the host language as providing equivalent semantics to the host language attribute. @required and @aria-required is an example. A conflicting value between the two results in UA resolution, based on some rules in the host language and ARIA. IIRC, a reflecting these as something other than String may result in both reflecting the same value... Is this acceptable, and if so, is there existing precedent in IDL?

cookiecrook avatar Apr 20 '20 20:04 cookiecrook

  1. true/false/mixed and true/false/undefined sound like enumerated attributes to me, so yeah, subsumed into (2).

  2. We have a model for reflecting enumerated attributes. This doesn't require a new IDL value type; DOMString is good for that.

  3. Double-to-string reflection is well-established on the web platform with a number of existing examples in HTML. This is the main issue at hand, I think.

  4. I'm not sure I understand this, but I'll try to answer. The reflection from typed JS properties (aka IDL attributes) to HTML attribute strings is a bidirectional, mechanical process, that is performed independently for any two attributes. The reflection doesn't change behavior because two attributes are related. And the types of the JS properties (IDL attributes) being strings versus numbers or any such doesn't change this.

domenic avatar Apr 20 '20 20:04 domenic

The reflection doesn't change behavior because two attributes are related. And the types of the JS properties (IDL attributes) being strings versus numbers or any such doesn't change this.

Okay. I recall there were some cases where the existence of the ARIA attribute would "win" over the native, non-nullable boolean, but even if I recall that correctly, I suppose we could set those instances as true/false/undefined enumerables rather than true/false booleans. I think that may resolve any conflict.

cookiecrook avatar Apr 20 '20 20:04 cookiecrook

Also, authors may be confused that they'd need to use boolean values to set some DOM properties, and boolean-like strings for others.

el.ariaModal = true;
el.ariaInvalid = "true";

Presumably this is the fault of ARIA's value patterns, not IDL, but the change from DOMString to other values makes the author confusion more likely.

cookiecrook avatar Apr 20 '20 21:04 cookiecrook

Yeah, that's unfortunate, but I don't see any way around it. There are two different syntaxes used on the HTML side, <el modal> vs. <el aria-invalid="true">. And so we need to use two different syntaxes on the JS side as well, el.modal = true vs. el.ariaInvalid = "true".

(Note that this is all off-topic. This issue is discussing integer attributes. I mention this because other threads where these things have been discussed previously have more context.)

domenic avatar Apr 20 '20 21:04 domenic

Note: I meant ariaModal above (corrected), but yes, the point is the same.

cookiecrook avatar Apr 20 '20 21:04 cookiecrook

Oh, in that case, I think they would both use = "true". Because <el aria-modal> does not work, right? It has to be <el aria-modal="true">?

domenic avatar Apr 20 '20 21:04 domenic

I don't think it's off topic. If the ARIA WG decides to reflect as something other than DOMString, it should reflect as many specific relevant types as possible, and potential for author confusion should weigh into that decision.

cookiecrook avatar Apr 20 '20 21:04 cookiecrook

Oh, in that case, I think they would both use = "true". Because <el aria-modal> does not work, right? It has to be <el aria-modal="true">?

In that case, are you saying we'd need an enumerated boolean: "true"/"false" in addition to the tristate "true"/"false"/"undefined"?

cookiecrook avatar Apr 20 '20 21:04 cookiecrook

In that case, are you saying we'd need an enumerated boolean: "true"/"false" in addition to the tristate "true"/"false"/"undefined"?

Correct.

domenic avatar Apr 20 '20 21:04 domenic

Thanks @domenic.

@jnurthen @joanmarie Should we discuss these changes in the Thursday ARIA call?

cookiecrook avatar Apr 20 '20 21:04 cookiecrook

@domenic and @annevk thanks for your help on this. @sinabahram reminded me yesterday that there are few numeric attributes where the absence of the attribute is meaningful. For example, a progress bar without aria-valuenow is considered an "indeterminate" progress indicator, equivalent to what some UI represents visually as a spinning gear.

<div role="progressbar" aria-valuemin="0" aria-valuemax="100">
  <!-- [sic, no aria-valuenow] -->
</div> 

Can you advise if it's possible to define aria-valuenow as a reflected nullable double?

cookiecrook avatar Apr 24 '20 19:04 cookiecrook

It is not possible; in those cases there's a default value that is used when the attribute is absent. For example, for HTML's progressEl.position, that default value is -1 for indeterminate progress bars. For HTML's progressEl.value, that default value is 0 for indeterminate progress bars.

domenic avatar Apr 24 '20 19:04 domenic

So potentially the following could default to -1

  • aria-posinset
  • aria-rowcount
  • aria-rowindex
  • aria-setsize

aria-value* is a bit more complicated. ARIA has some other computed defaults in the case of authoring errors. Would this table need to change, or are each of these expressible in IDL, too?

Excerpt from: https://www.w3.org/TR/wai-aria-1.2/#authorErrorDefaultValuesTable

WAI-ARIA role Required Attribute Fallback value
scrollbar aria-valuenow If missing or not a number,(aria-valuemax - aria-valuemin) / 2. If present but less than aria-valuemin, the value of aria-valuemin. If present but greater than aria-valuemax, the value of aria-valuemax.
slider aria-valuenow If missing or not a number,(aria-valuemax - aria-valuemin) / 2. If present but less than aria-valuemin, the value of aria-valuemin. If present but greater than aria-valuemax, the value of aria-valuemax.
spinbutton aria-valuemax A value indicating that the spinbutton has no upper bound (accessibility API dependent).
spinbutton aria-valuemin A value indicating that the spinbutton has no lower bound (accessibility APIdependent).

cookiecrook avatar Apr 25 '20 00:04 cookiecrook

The equivalent attributes on input (min, max, value) are strings, though they can also handle date values and such.

I think it would make sense to let reflection handle your situation (e.g., perhaps default could be a value or algorithm; note that we haven't formalized this to the level of IDL yet in the specification: https://github.com/whatwg/html/issues/3238#issuecomment-601383619) or failing that define the logic for those IDL attributes yourself in prose. It seems like a more useful API for web developers if these return numeric values.

annevk avatar Apr 25 '20 05:04 annevk

I think there is a meta-issue here, so hopefully there is a 1-to-1 mapping to a meta-solution so we don’t need to treat things individually, and we can zoom out and address the entire problem space, or at least enough of it to cover what we care about. Alternatively, I could be completely off, so just let me know if that’s the case.

For any numeric type, where I’m loosely defining numeric to be int, float, double, decimal, whatever you like. If numbers are used to represent it, we have 2 cases:

1: We have an unsigned range e.g. aria-posinset, aria-setsize, etc.

2: We have a signed range e.g. tabindex. See note 1.

Now, across both 1 and 2 above, one of the following conditions must also be true:

Conditions:

A: A specific value, almost always either -1 or 0, has the same meaning as the absence of the attribute.

B: the absence of the attribute is not mappable to a numeric representation, but instead must be represented by null e.g. -1 or 0 are meaningful, so they can’t be used as default values. See note 1 again. Also, see note 2.

Some questions:

  1. So, my first series of questions are, and please forgive my lack of having read the IDL spec, because I haven’t read it, but does this mapping even support the idea of nullable numerics? What does that even mean? I could see a representation in some programming languages, but in most of them, especially those inherited from c/pascal, primitive types do not take null, right? So, int, float, double in c, c++, java, etc. all have a 0-like value as their default, no? So, I just literally don’t understand what a nullable int is. I know I want it to exist, but what does it mean? Is this an implementation detail that’s just made the problem of whomever is tasked with implementing this mapping e.g. if a value exists, reflect, otherwise …. Uhh, “don’t?” or something. Again, having trouble understanding how this would be represented. Obviously in loosely-typed languages, this is not as much of a concern, and maybe that’s all we care about? just let me know.

Oh, and let me be clear, I’m not saying I couldn’t see how to code it. I could see something silly like a pointer being set to 0 to represent null, and if it is non-zero, then and only then it is dereferenced, and obviously once ddereferenced, it maps to the 32-bit or 64-bit representation of whatever numeric it represents. I get how to code it, and I’m sure there’s better ways, but is that what’s done? Forcing a conditional per dereference seems pretty suboptimal.

  1. My next question is, will we do a proper treatment of default mappings as part of this discussion? See note 1. That’s super-important. Or, is that all out of scope? For example, when only condition A is true above, then 0 or -1 can be used to represent the equivalence of null, which is fine, but obviously that doesn’t work when condition B is true.

Notes:

  1. Fun fact, it was brought up on the ARIA WG call that some browsers have a default value for tabindex, the property, when there is no tabindex, the attribute, present, which seems …. Quite surprising, in my opinion. If we’re going to do that, then why in the world are we forcing thousands of developers to slap a useless tabindex of -1 on components just so skip links and scriptable focus can be achieved? This simple default actually being realized across all browsers would save thousands of hours of development and tens of thousands of hours of user frustration for people who need it the most, arguably switch-users. So, I really hope we resolve this ASAP. The suggestion was made to just make anything focusable by a script, to which I say, that I totally agree with that. No clue why that wasn’t the default from day 1. Maybe there’s problems, though?

  2. I am completely avoiding the empty string, epsilon, because that’s meaningless since everything starts off as a string, and then is actually reflected by the browser and then again presumably for our purposes, so really the empty string is not helpful, because these are numeric types once we wish to reason about them, and empty string is not valid for anything that is numeric e.g. tabindex=”” …. Maybe that means something. I have no idea what it means/I think it’s invalid, or at least should be.

I am absolutely positive I have missed something above, but that’s just how I’m thinking about this problem.

Thoughts?

sinabahram avatar Apr 25 '20 17:04 sinabahram

I’m unclear how aria-required, a Boolean, is being mapped to -1 instead of a third state in an enumerated type?

Per my last message, the same conditions are true here, no?

For Boolean fields, either they are absence=false or not, if their absence is not the same as false, then they aren’t actually Booleans. They are trinary state fields e.g. aria-expanded has three states: null, false, and true, so an enumerated type is needed, not a -1 representation, right?

sinabahram avatar Apr 25 '20 17:04 sinabahram

For the web platform we only (have to) care about how IDL maps to JavaScript. How other languages want to implement those types is up to them.

The tabindex content attribute can be in a state you cannot discern from the tabIndex IDL attribute, that might be why it's confusing. As to how it generally works and how browsers are supposed to parse it (including the empty string), I recommend reading https://html.spec.whatwg.org/multipage/interaction.html#the-tabindex-attribute.

E.g., in the example below the first div can be focused with a mouse, despite both div elements returning the same value for their tabIndex IDL attribute.

<style>:focus { background:red }</style>
<div tabindex=-1>test</div>
<div>test</div>
<script>
alert(document.querySelectorAll("div")[0].tabIndex === document.querySelectorAll("div")[1].tabIndex)
</script>

I don't think we should copy that. It's useful if the IDL attribute can represent the full set of states.

annevk avatar Apr 26 '20 12:04 annevk

Thanks for the additional info/context.

Yeah, if we can’t distinguish between those two divs, then I don’t see the point. this has profound implications for the user at the end of the day. it’s not just a pedantic or abstract point in a spec somewhere, so I hope we can get this right e.g. distinguish between these states.

sinabahram avatar Apr 26 '20 20:04 sinabahram