Problem/Motivation
The AJAX API has a feature to display a progress message, but it isn't conveyed to assistive tech like screen readers.
'#ajax' => array(
'callback' => 'Drupal\config_translation\FormElement\DateFormat::ajaxSample',
'event' => 'keyup',
'progress' => array(
'type' => 'throbber',
'message' => t('Loading more products'),
),
),Proposed resolution
Find a way to convey the progress message.
Possibilities (use one approach only, not both):
- Mark the visible AJAX message as an ARIA-live region (preferred?), or
- Duplicate the visible message using a Drupal.announce() call.
Other possibilities might be to provide incrementally updated messages, like "Updating... 30%... 80%... finished".
Remaining tasks
- Perform manual testing
- Perform accessibility review
User interface changes
No visual changes. Format the AJAX message so it is conveyed to assistive technology, e.g. so screen readers can announce it.
API changes
Several screenreader related properties have been added to the AJAX Progress settings.
announce- Determines if screenreaders should announce progress for this operationannounceDelay- The delay in milliseconds before the screenreader announces the progressannounceIntervalTime- The interval in milliseconds between each announcementannounceMessage- The message to be announced by the screenreader, if nomessageproperty is set.
Data model changes
No data model changes introduced.
Background reading
- How do you make the Loading icon accessible for screen-readers like JAWS?
- Accessibility: Page Loader indicator using aria-live
- JavaScript and Accessibility: Don't Blame the Language (DrupalCon Nashville 2018 session). Recording and slides from Everett Zufelt's talk.
- JavaScript and Accessibility: Don’t Blame the Language, Everett Zufelt's article version on Medium.
- aria-live: the good, the bad and the ugly . Includes test notes about aria-live support as of 2015.
- Injecting content into the page tests. The same test results from the previous slide deck.
- Live Region Playground. A cool tool from Deque, useful for testing support on current browser/OS/AT combinations.
- Testing ARIA-LIVE with display:none and innerHTML
- ARIA Live Region Test. Terrill Thompson's test page. Not sure what date, but it does have versions.
Steps to reproduce
- Turn on a screen reader (e.g. VoiceOver on macOS).
- In Chrome DevTools, open the Network tab and add a custom throttling profile with about 5000 ms latency, so AJAX requests take longer than 1 second.
- Go to
/admin/structure/views/view/contentand click Update preview (or Add next to Fields). - Without the MR, the progress indicator shows visually, but the screen reader announces nothing while the request is in progress.
Testing the MR
- Check out the MR branch. Add
$settings['extension_discovery_scan_tests'] = TRUE;tosettings.phpand enable theajax_testmodule. - Keep the throttling profile from above enabled.
- Go to
/ajax-test/progress-announce-formand press each button. After 1 second the message is announced, then repeated every 2 seconds until the request finishes:- default: "Processing..."
- custom_message: "Hold on"
- no_message: "Processing..."
- no_announce: nothing
You can also watch the text in
#drupal-live-announcein DevTools. - Repeat the Views UI steps above. "Processing..." is now announced.
Commit credits
@bgrobertson, who reported this issue on the #accessibility channel on Drupal Slack, and researched which bits of the AJAX API were relevant. This issue report is a summary of his comments there.
@ckueda, who provided a patch on the duplicate issue #2907132: Indicate ajax throbber activity to assistive technology, which is broadly similar to the earliest patch here.
| Comment | File | Size | Author |
|---|---|---|---|
| #92 | progress_view.mp4 | 517.37 KB | rkoller |
| #92 | progress_test.mp4 | 1.54 MB | rkoller |
| #69 | 2973140-69.patch | 9.77 KB | jldust |
| #69 | interdiff_68-69.txt | 1.05 KB | jldust |
| #61 | reroll_diff_2973140_56-61.txt | 1.55 KB | ankithashetty |
Issue fork drupal-2973140
Show commands
Start within a Git clone of the project using the version control instructions.
Or, if you do not have SSH keys set up on git.drupalcode.org:
Comments
Comment #2
andrewmacpherson commentedComment #3
andrewmacpherson commentedComment #4
bgrobertson commentedLooking at where the throbber is created in JavaScript `/core/misc/ajax.es6.js:817`:
I wonder if it would be sufficient to add `role="alert"` to the message div. I am testing this locally on my Mac with VoiceOver and it seems to work, but I was thinking the alert role needed to be present on initial page load to be announced to AT.
This is announcing the message to me with VoiceOver on macOS:
Comment #5
andrewmacpherson commentedThanks Ben. This might turn out to be quite easy. Can you turn that into a patch here? We'd need to test it with a wider range of assistive tech of course, but this sounds promising.
role=alertimpliesaria-live="assertive", and i'm not sure we'd always want that. We might have an option to allowaria-live="polite"too, say something like:This would mirror the priority option in Drupal.announce().
Comment #6
bgrobertson commentedHere is a patch making the change I have described above.
Comment #7
andrewmacpherson commentedThanks @bgrobertson. Is this your first core patch? Great contribution.
Comment #8
andypostComment #9
bgrobertson commented@andrewmacpherson, yes it's my first core patch :) Glad I finally was able to submit one!
Comment #10
damienmckennaComment #11
mgiffordI think Everett brings this pattern up (in a positive way) in his DrupalCon talk about Javascript https://www.youtube.com/watch?v=UKkBBSDA3TQ
Great initiative!
Comment #12
andrewmacpherson commentedWe should probably include
aria-live="off"as an option, because some messages may be too trivial. For Drupal.announce(), you just wouldn't call it at all, but here an option for a developer to suppress it could be useful. I thinkaria-live="polite"will be a good default, only need to specify the option if you want "assertive" or "off"This relates to a new success criterion in WCAG 2.1 - 4.1.3 Status Messages.
Comment #14
andrewmacpherson commentedRe. #4 + #6:
Not quite. In general, the ARIA live region needs to exist before you change it's content. A live region that is in the HTML source and is present on page load is just one example of this. It's also possible to create a live region programatically after page load, but it won't make an announcement until after you change it's content, for example the live region created by
Drupal.behaviors.drupalAnnounce.attach().However the exact behaviour differs between browsers; live region implementations are quite inconsistent between various browser/AT combinations. (I have that bookmarked somewhere...).
In the case of your code, I'm a bit surprised that it works. A more robust JS approach is to append an live region element, then insert message text inside it, as two separate DOM changes.
Comment #16
andrewmacpherson commentedClosing #2907132: Indicate ajax throbber activity to assistive technology as a duplicate, in favour of this one.
@ckueda provided a patch there which is broadly similar to what we have here, so they deserve a commit credit transferred to this issue.
Comment #17
andrewmacpherson commentedAdding a background reading section to the issue summary, bringing some links across from the duplicate issue.
Comment #18
andrewmacpherson commentedFurther to #14, the Heydon/on-demand-live-region is a library similar to our own announce.js
The interesting thing here, is it illustrates how to announce the message by creating a live region then adding the message as a 2-step process.
Comment #19
andrewmacpherson commentedComment #20
andrewmacpherson commentedmore background reading.
Comment #21
seanbShould we also add support for an announcement when the AJAX command is finished?
Comment #22
andrewmacpherson commented@seanb - re. #21
There is already support for an announcement when the AJAX command is finished. It's new in 8.7.x. See https://www.drupal.org/node/3021276.
This issue is just about the loading message for the throbber, which is a temporary DIV. It's removed when the AJAX response finishes. The new AnnounceCommand is the way to queue up a message for after the throbber is gone.
Comment #23
tim.plunkettComment #24
finnsky commentedHi!
Added patch which announcing:
Comment #26
finnsky commentedseems for FunctionalJavascript tests passing Dupal.announce should be added as dependency.
Comment #27
finnsky commentedComment #29
andrewmacpherson commentedThanks for getting involved with this @finnsky
I've read your patches since #24, but I haven't manually tested them. I can see that you've taken a different approach to the earlier patches. That's good, because the issue summary has two proposed resolutions to explore. Yours take the approach of using the general purpose Drupal.announce live region.
The next steps will be to make a comparison of the two approaches, and consider:
Overall, I think managing separate live regions in the same location as the visual throbber is the most desirable approach, but it seems fiddlier.
At the moment, this issue isn't considered a must-have for anything else (as far as I recall).
Comment #30
andrewmacpherson commented#77245: Provide a common API for displaying JavaScript messages attempted to provide visible ARIA live regions, but the behaviour is flaky across various browsers and assistive tech, so it uses Drupal.announce too. We might find that improving that API overlaps with this issue.
Comment #32
xjmComment #33
bnjmnmThis builds on the approach in #27. This addresses the test failures and adds settings to determine if progress should be announced (defaults to true) and how long to wait for an Ajax request to be in progress before an announcement is generated (defaults to 1000ms).
Comment #34
wim leersPer @andrewmacpherson's guidance, we now have working implementations (patches passing tests) for both of the proposed approaches:
Drupal.announce()API: #33I defer to the accessibility maintainers, but there are a few things that @andrewmacpherson wrote in #29 that I wanted to chime in on:
This is impossible to know. It depends on both network conditions and server-side capacity and business logic complexity. With varying network conditions, this is literally impossible. Assuming constant network conditions, it'd still depend on the particular Drupal instance, because for example an autocomplete would be super fast on a small site but might take seconds on a site with an enormous amount of content. Even in #33's automatic logic to only announce things after one second, that may still result in an unpleasant experience because if the announcement implies waiting and then there is no need for waiting … that still yields an odd experience.
Comment #35
phenaproximaThis looks pretty good to me, overall. A few changes suggest themselves:
This could just be slightly less verbose, I think; something like:
Also, I think we need to document these new properties. I'm not sure where the contents of
#ajaxare documented, though.This is not exactly accurate, and could use a bit of grammatical polish. I would suggest something like this: "To avoid excessive verbosity, wait a bit (1 second by default) before announcing progress to screen readers, so that very fast AJAX operations are not announced."
This could have accessibility implications of which I'm unaware. I have definitely heard some concerns from @andrewmacpherson about disabling elements in general, so I'm not sure this is what we'd want to do. If push comes to shove, surely we could do something like this?:
Why did this change? Is assertWaitPageContains() insufficient for those purposes?
Comment #36
bnjmnmRe: #35.3
The disabling of
$(this.element)is existing behavior in ajax.es6.js, the patch just adds a conditional that is based on behavior that already exists.This conditional is added so the progress announcement is not triggered if the AJAX request has already completed after the announceDelay. I debugged
thisto see if there were any other means of determining if the AJAX request was complete and couldn't find anything (though I suspect I may have missed something). The best way I could find to do it was checking if$(this.element)is disabled -- something that happens a few lines prior to adding the progress indicator and is reverted after the request completes. Very open to other ways of checking for completion in this scope!At the very least, the comment can be rewritten to make this clearer.
#35.4
Honestly I found something that worked while troubleshooting and just stuck with that. This deserves more explanation so I'll either get to the bottom of it or at worst provide a failing test patch to demonstrate it is necessary.
Comment #37
phenaproximaFair enough. I think that can be considered a pre-existing accessibility sadness in Drupal's AJAX system, and sometimes small, unfortunate hacks are necessary in order to reach a greater good. As long as the comment is updated for clarity, I'm okay with that.
Comment #38
bnjmnm#35.1 Simplified the array and added documented the properties.
#35.2 Comment from a previous approach updated to reflect the current approach.
#35.3 Updated comment to hopefully make things more straightforward.
#35.4 The original test works fine and those changes were reverted. The changes were to address something in patch #26 that has since been changed.
Comment #39
phenaproximaDiscussed with @bnjmnm, and we realized that this.progress.announceDelay might be undefined. However, it seems that we can't change this in Drupal.Ajax's defaults and have it enter this scope (or at least, if we can, it's not clear how we would do that), so this line should probably be changed to the safer
this.progress.announceDelay || 1000.Beyond that, this looks okay and should go in front of the accessibility maintainers. We brainstormed a couple of ways that we might add automated tests for this, but we concluded that testing a "delay" might be a bit harder than it's worth (i.e., prone to random timing failures). So manual testing is probably sufficient for this (it'll need that anyway for accessibility sign-off).
Comment #40
bnjmnmGood call on #39, this makes the change.
Comment #41
andrewmacpherson commentedI also started looking at the issue. I haven't had time for manual testing yet. I'm curious to see how the 1000ms delay works out in practice.
I've read all the comments since I was last there 7 months ago. At that time it wasn't seen as a must-have for anything, but it will be useful for the media library places in media library.
+ Drupal.announce(customMessage);Where does
customMessagecome from? It isn't mentioned anywhere else in the patch. Does this assume a pre-existing visible message? Not all our ajax throbbers have a visible message.Comment #42
micnap commentedCan someone help me with steps to reproduce so that I can test? Here's what I did:
1. Set up a fresh d8 install.
2. Applied patch #40
3. Added a file upload field to the article content type
4. Adjusted php.ini to allow super large uploads so I could get the throbber to run for a decent length of time
5. Created a new article, turning VoiceOver on when uploading a 500+ meg file
I did not hear any progress updates for the throbber. Am I doing something wrong? I'd be happy to test on NVDA too if I can confirm I'm not doing something wrong on VO.
Video of the testing with VO: https://www.youtube.com/watch?v=vDVJIpvvJdQ
Comment #44
rainbreaw commentedLike @micnap, I'm having a very difficult time getting any announcement to come through, and haven't been successful in testing this.
I even set my browser speed to slow 3G, which didn't succeed in getting to any announcement.
Comment #45
chaitanya.maili commentedUpdate the success message and fixed the repeated announcement issue.
Comment #47
bnjmnm@chaitanyamaili it's not clear what the context of your patch is. Is this built on top of earlier patches such as #40. If so, your patch should be the changes from #40 with your additional changes, diffed against the dev branch (currently
8.9.x). Making a drupal patch with git.When changes are made to an earlier patch, it should also be accompanied by an interdiff, which shows what those changes are. Creating an interdiff
In other words:
If your patch was not built on earlier versions and is a new solution, that needs to be made very clear in your comments. If that's the the case then the approach should probably include the additional functionality requested in the issue such as making the announcements optional and providing a delay.
Although I'm not 100% of the context, I can provide some feedback on what is there:
This should be a triple equals
These messages would need to be more generic, "Content has been updated" is not applicable to all situations.
Furthermore, strings need to be wrapped in Drupal.t() to facilitate translatability.
I don't believe this is in scope for this issue.
Comment #48
bnjmnmThe feedback in #42 and #44 was very helpful -- I realized that the announcements were only added to elements rendered with
$element['#ajax']['event']present and with a progress message defined. Among other things, this can't depend on a progress message being defined as in many cases this is needed only the spinner is displayed.This winds up expanding where this occurs significantly, so testing is even more important than before, and should actually provide results...
Comment #49
oknateReview of #48:
1. I don't know what you mean here:
Can you be more specific? What excessive verbosity would happen?
Ah I see: "so that very fast AJAX operations are not announced." Perhaps we can get that in there earlier. How about "To prevent very fast AJAX operations from being announced, wait before announcing progress to screenreaders for the duration of the announceDelay property (1 second by default)."
2. I'm having trouble getting this working. Using /media/add/video with a 70 MB video, It was uploading too fast. So changed the wait time to 10 milliseconds, and it still wasn't working. It turns out the _this.progress.message was empty:

Due to this, this code was failing:
There's no check that
_this.progress.messageis set.If I change it to
Drupal.announce(_this.progress.announceMessage);it work, although it was read at the end.3. Is this being used?
Drupal.announce(customMessage);Andrew brought this up too in #41.Comment #50
bnjmnm#49.1: rephrased that and hope its a little clearer.
#49.2: Looks like there were a few problems here that should be cleared up.
#49.3 It looks like
Drupal.announce(customMessage);came from an earlier iteration of the patch that included error messages in the announcements. This is outside the scope of conveying progress messages, but should probably get a followup issue (particularly since the method of conveying these errors may require some opinionated decisions).Some additional changes were made based on testing aided by Charles Proxy, which allowed me to throttle my network connection to very slow speeds. This opened up the scenarios I could test and it exposed the need to have a repeating message for two reasons:
In order to make announcements repeatable, a non-spoken unicode character is added in different amounts each time so it's interpreted as a new string that should be announced, but the content read is the same.
The times chosen for the announceDelay and announceIntervalTime properties are very rough guesses based on nothing more than they seem like good starting points for necessary research and discussion.
Comment #51
phenaproximaTagging this very important issue to be worked on at DrupalCon Amsterdam.
Comment #55
nishantghetiya commentedRe-roll #50 for 9.3.x
Comment #56
nishantghetiya commentedResolved #55command failed issue.
Comment #59
imyaro commentedI didn’t test all the cases, but it looks good for me.
Comment #60
bnjmnmRE #59 if the most recent patch is fails testing or can't apply, the issue can't be marked RTBC, that's. Those are concrete requirements of having something be "Reviewed and Tested by the Community".
You also mention that
, so that precludes it from being set to RTBC as well. What is VERY helpful is to describe the test cases you did evaluate (plus any relevant screenshots) so another reviewer is aware what parts have already been covered. In general a review needs additional detail beyond "looks good for me.". It needs to be documented so the committer is aware what evaluation took place.
This is also tagged "needs accessibility review", so that needs to happen before RTBC too.
Comment #61
ankithashettyRerolled the patch in #56, thanks!
Comment #65
jldust commentedThe most recent patches were failing so I rerolled the patch presented in #61 against the 9.5.x branch and was able to get it to apply cleanly. Hopefully we can get some additional accessibility review around the effectiveness of these changes.
I would be happy to help with additional test cases & further edits to get this moving along. Should we also start planning for this against the 10.1.x branch as well to be proactive?
Comment #66
jldust commentedLooks like the spelling of "screenreader" is causing a build error so I have adjusted the two places called out to "screen reader".
Comment #67
jldust commentedUpdated build command was missed during reroll, this should account for those changes.
Comment #68
jldust commentedFixed spelling regression after updating ES6
Comment #69
jldust commentedIt looks like passing more than one argument in
WebAssert::responseContainswas deprecated in Drupal 9.1. I've removed the secondary argument as it is also listed in the comments directly above the line.Comment #70
smustgrave commentedCan the issue summary be updated. Number of "tbd" in there.
Looking at the test coverage can we also add a test for the message in the announce. Make sure that is getting the right message.
Comment #71
mgiffordAdding tagging for 4.1.3.
Comment #74
finnsky commentedRebased on 11.x
Comment #75
solideogloria commentedAdding per #70
Comment #77
jeremyvii commentedI rebased the merge request to fix the merge conflicts in #76 and updated the issue summary.
Comment #78
smustgrave commentedLeft some comments on the MR.
Comment #79
jeremyvii commentedI have created kernel and Nightwatch tests for this merge request.
Comment #80
jeremyvii commentedComment #81
kentr commentedI suspect the pipeline failures are related to the changes. I put details in comments on the MR.
Comment #84
duaelfrBack to green!
Comment #85
f0ns commentedComment #86
f0ns commentedComment #87
kentr commentedThe issue summary needs steps to reproduce to help reviewers.
Comment #88
f0ns commentedComment #89
f0ns commentedI've added 'Steps to reproduce' and Testing the MR' to the issue.
Comment #90
f0ns commentedComment #91
f0ns commentedJust to be sure: I tested this again (with 5000ms like in the description) and I can confirm it work based on the steps.
I tested this on Mac OS 27.0.1 with Voice Over on and in Chrome with Network throttling on.
Comment #92
rkollerI've tested MR4913 based on the instructions provided in the issue summary by @f0ns. In the content view (progress_view.mp4) and the test module (progress_test.mp4) were tested on macOS 26.7.1 with VoiceOver active in the latest version of Edge (155.0.4283.45) - trying to throttle Safari seemed too hackish https://www.browserstack.com/guide/how-to-perform-network-throttling-in-.... In both tests only the disabled button got announced but the actual progress was not unfortunately. :/ And I will also try to test the MR in Orca in a virtualized Fedora later tonight.
Comment #93
needs-review-queue-bot commentedThe Needs Review Queue Bot tested this issue. It no longer applies to Drupal core. Therefore, this issue status is now "Needs work".
This does not mean that the patch necessarily needs to be re-rolled or the MR rebased. Read the Issue Summary, the issue tags and the latest discussion here to determine what needs to be done.
Consult the Drupal Contributor Guide to find step-by-step guides for working with issues.
Comment #94
f0ns commentedI couldn't reproduce this (Safari and Chrome) but browser compatibility can be tricky sometimes (understatement).
I've altered the code, the first announcement is now assertive and the repeats stay polite.
This should fix it imo.
@rkoller Would be great if you can give this another go in Edge when you find the time. Thanks for the help!
Comment #95
f0ns commented