[Webkit-unassigned] [Bug 250380] New: AX: Voiceover incorrectly changes value of input type="text" with role="spinbutton" when swipe up/swipe down gesture used, value change announces numeric value inserted between percentage value and percent itself

bugzilla-daemon at webkit.org bugzilla-daemon at webkit.org
Tue Jan 10 06:41:49 PST 2023


https://bugs.webkit.org/show_bug.cgi?id=250380

            Bug ID: 250380
           Summary: AX: Voiceover incorrectly changes value of input
                    type="text" with role="spinbutton" when swipe up/swipe
                    down gesture used, value change announces numeric
                    value inserted between percentage value and percent
                    itself
           Product: WebKit
           Version: Safari 16
          Hardware: iPhone / iPad
                OS: iOS 16
            Status: NEW
          Severity: Normal
          Priority: P2
         Component: Accessibility
          Assignee: webkit-unassigned at lists.webkit.org
          Reporter: jartik at gmail.com
                CC: andresg_22 at apple.com,
                    webkit-bug-importer at group.apple.com

Created attachment 464441

  --> https://bugs.webkit.org/attachment.cgi?id=464441&action=review

Sample code

Steps to Reproduce:
1). Please use the following code snippet:
============= Styles =============
    h2 {
      margin-top: 0;
    }

    .spinbutton {
      display: flex;
      flex-direction: row;
      align-items: center;
    }

    .spinbutton button {
      width: 48px;
      height: 48px;
      cursor: pointer;
    }

    [role="spinbutton"] {
      width: 60px;
      text-align: center;
    }

    input[role="spinbutton"] {
      margin: 0 5px;
    }
============= END of Styles =============

============= Code =============
  <h2>Role "spinbutton" on div</h2>
  <div class="spinbutton">
    <button class="decrease" type="button" tabindex="-1" aria-hidden>-</button>
    <div role="spinbutton" tabindex="0" aria-valuenow="1" aria-valuetext="1" aria-valuemin="0" aria-valuemax="200"
      aria-label="Quantity div spinbutton">1</div>
    <button class="increase" type="button" tabindex="-1">+</button>
  </div>
  <hr />
  <h2>Role "spinbutton" on input type text</h2>
  <div class="spinbutton">
    <button class="decrease" type="button" class="decrease" tabindex="-1" aria-hidden>-</button>
    <input type="text" role="spinbutton" aria-valuenow="1" aria-valuetext="1" aria-valuemin="0" aria-valuemax="200"
      value="1" aria-label="Quantity input type text spinbutton" />
    <button class="increase" type="button" tabindex="-1">+</button>
  </div>
  <hr />
  <h2>Role "spinbutton" on input type number</h2>
  <div class="spinbutton">
    <button class="decrease" type="button" class="decrease" tabindex="-1" aria-hidden>-</button>
    <input type="number" step="1" min="0" max="200" role="spinbutton" aria-valuenow="1" aria-valuetext="1" aria-valuemin="0"
      aria-valuemax="200" value="1" aria-label="Quantity input type number spinbutton" />
    <button class="increase" type="button" tabindex="-1">+</button>
  </div>
  <script>
    window.addEventListener('load', _ => {
      const containers = document.querySelectorAll('.spinbutton');
      [...containers].forEach(c => {
        const spinButton = c.querySelector('[role="spinbutton"]');
        const decreaseBtn = c.querySelector('.decrease');
        const increaseBtn = c.querySelector('.increase');
        const minValue = parseInt(spinButton.getAttribute('aria-valuemin'), 10);
        const maxValue = parseInt(spinButton.getAttribute('aria-valuemax'), 10);

        decreaseBtn.addEventListener('click', _ => decreaseValue(spinButton, minValue));
        increaseBtn.addEventListener('click', _ => increaseValue(spinButton, maxValue));

        spinButton.addEventListener('keydown', e => {
          console.log('::Keydown event:: key:', e.key);

          if (isInput(e.currentTarget) && e.currentTarget.type === 'number')
            return;

          if (e.key === 'ArrowUp') {
            increaseValue(e.currentTarget, maxValue);
          } else if (e.key === 'ArrowDown') {
            decreaseValue(e.currentTarget, minValue);
          }
        });

        if (isInput(spinButton)) {
          spinButton.addEventListener('input', e => {
            console.log('::Input event:: inputType:', e.inputType);
            console.log('::Input event:: data:', e.data);
            console.log('::Input event:: value:', e.currentTarget.value);
          });

          spinButton.addEventListener('change', e => {
            console.log('::Change event:: value:', e.currentTarget.value);
            e.currentTarget.setAttribute('aria-valuenow', e.currentTarget.value);
            e.currentTarget.setAttribute('aria-valuetext', e.currentTarget.value);

            const value = parseInt(e.currentTarget.value, 10);
            if (isNaN(value) || value < minValue || value > maxValue) {
              e.currentTarget.setAttribute('aria-invalid', 'true');
            } else {
              e.currentTarget.removeAttribute('aria-invalid');
            }
          });
        }
      });
    });

    function getCurrentValue(spinButton) {
      return parseInt(spinButton.getAttribute('aria-valuenow'), 10)
    }

    function increaseValue(spinButton, maxValue) {
      const currentValue = getCurrentValue(spinButton);
      if (isNaN(currentValue)) {
        setNewValue(spinButton, 0);
        return;
      }

      const newValue = currentValue + 1;
      if (newValue > maxValue)
        return;

      setNewValue(spinButton, newValue);
    }

    function decreaseValue(spinButton, minValue) {
      const currentValue = getCurrentValue(spinButton);
      if (isNaN(currentValue)) {
        setNewValue(spinButton, 0);
        return;
      }

      const newValue = currentValue - 1;
      if (newValue < minValue)
        return;

      setNewValue(spinButton, newValue);
    }

    function setNewValue(spinButton, value) {
      spinButton.setAttribute('aria-valuenow', value);
      spinButton.setAttribute('aria-valuetext', value);
      if (isInput(spinButton)) {
        spinButton.removeAttribute('aria-invalid');
        spinButton.value = value;
      } else {
        spinButton.innerHTML = value;
      }
    }

    function isInput(spinButton) {
      return spinButton.tagName === 'INPUT';
    }
  </script>
============= END of Code =============

2). Open page with provided code snippet on iPhone/iPad with Voiceover enabled. Swipe left/right or tap directly on input type="text" with role="spinbutton". Try to change value by swiping up and/or down.

Actual Results: Instead of "keydown" event two input events are fired - first one with inputType of "deleteContent" which cleans input value, second one of "insertText" which adds text with value which seems like equal to 5% of the range between aria-valuemin and aria-valuemax values (this was determined by number of tests with different min/max values) is been added or substracted from the original value depending on operation type triggered (increment or decrement). Swiping up/down continuously changes applies added or substracted from the original value, change is applied only after input loses focus. On value change it seems like selected value as percentage from the range is been announced, mostly with some number, source of which I can not determine (but is seems like it should be somehow relative to the value change step), announced between percentage value and percentage itself.

Expected Results: Keyboard "keydown" event is fired with "ArrowUp" as a key for increment and "ArrowDown" for decrement AT (assistive technology) event. Value is changed as per code in "keydown" event listener.

Build Date & Hardware: iPhone 11, iOS 16.2

Additional Information:
As per https://github.com/WICG/aom/blob/gh-pages/explainer.md#user-action-events-from-assistive-technology increment and decrement AT event should trigger Keyboard events simulating pressing of "ArrowUp" and "ArrowDown" keys respectively. This works for general "div" element with role="spinbutton" (present in the provided code snippet) - value changes correctly and been announced, but after that again percentage value, undetermined numeric value and 'percentage' word are announced just as in 'Actual Results' section. "keydown" listener does not run at all on input type="text", value change step as was mentioned seems like equal to 5% of the range between aria-valuemin and aria-valuemax values, but it is totally unclear why and if it is always has this value. If role="spinbutton" added to input type="number" with step attribute present, step value is been honored, but still "keydown" listener is not fired and general behavior is the same as for input type="text".
There is also a bug https://bugs.webkit.org/show_bug.cgi?id=221102 already filled for percentage announcement which is still reproduced in iOS 16.2 (original bug was reported for iOS 14).

-- 
You are receiving this mail because:
You are the assignee for the bug.
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.webkit.org/pipermail/webkit-unassigned/attachments/20230110/3d063d57/attachment-0001.htm>


More information about the webkit-unassigned mailing list