Recently, I've been investigating Web Assembly (WASM), Rust and Yew. To reproduce my problem, I've added the code of my project below. There's nothing wrong with the code per se, so you can skip it if you don't want to reproduce the setup first.
setup
The following is a copy-paste of this Yew example - I left everything as it is on the linked page.
cargo.tompl
:
[package]
name = "yew-app"
version = "0.1.0"
authors = ["Yew App Developer <name@example.com>"]
edition = "2018"
[lib]
crate-type = ["cdylib", "rlib"]
[dependencies]
yew = "0.17"
wasm-bindgen = "0.2.67"
src/lib.rs
:
use wasm_bindgen::prelude::*;
use yew::prelude::*;
struct Model {
link: ComponentLink<Self>,
value: i64,
}
enum Msg {
AddOne,
}
impl Component for Model {
type Message = Msg;
type Properties = ();
fn create(_: Self::Properties, link: ComponentLink<Self>) -> Self {
Self {
link,
value: 0,
}
}
fn update(&mut self, msg: Self::Message) -> ShouldRender {
match msg {
Msg::AddOne => self.value += 1
}
true
}
fn change(&mut self, _props: Self::Properties) -> ShouldRender {
// Should only return "true" if new properties are different to
// previously received properties.
// This component has no properties so we will always return "false".
false
}
fn view(&self) -> Html {
html! {
<div>
<button onclick=self.link.callback(|_| Msg::AddOne)>{ "+1" }</button>
<p>{ self.value }</p>
</div>
}
}
}
#[wasm_bindgen(start)]
pub fn run_app() {
App::<Model>::new().mount_to_body();
}
static/index.html
:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Yew Sample App</title>
<script type="module">
import init from "./wasm.js"
init()
</script>
</head>
<body>
</body>
</html>
With all that set and located in the yew-app project directory, you can compile it:
wasm-pack build --target web --out-name wasm --out-dir ./static
Then use any server to make this available, eg.:
miniserve ./static --index index.html
The problem
When visiting 127.0.0.1:8080
(or whichever port the app runs on) afterwards, all looks good. I'm positive everything works as it should for sighted people. For blind JAWS users not so much, though.
This is what I see on the page:
Yew Sample App
+1 Btn
0
When clicking on the button, nothing appears to happen. Using the JAWS cursor (essentially the mouse pointer) yields the following output:
+1
0
And even when simulating mouse clicks using JAWS, nothing appears to happen. I say "appears", because in fact, the counter updates as it should.
If I switch away from the website using ALT + TAB
, then switch back to it, then use the JAWS cursor again, I can see the updated counter. The counter in the virtual document which JAWS created based on the website remains the same, though. JAWS did capture the dynmamic content inserted by the init()
-function so the problem isn't dynamic WASM-content per se. Rather, the way the content in the paragraph gets updated appears to be problematic.
I switched to Narrator and initially encountered the exact same issues. I then switched to NVDA and everything worked as it should - the counter updating instantly, even without using any special cursor.
I made changes to the paragraph element:
// snip
<p role="status" aria-live="polite">{ self.value }</p>
// snip
And now Narrator handled the update correctly as wel; additionally, both Narrator and NVDA now automatically announced the counter update, as they should according to aria-live="polite"
. JAWS did none of these things.
I then tried to implement something similar in JavaScript:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Test page</title>
</head>
<body>
<script>
var counter = 0;
document.body.innerHTML += `<div>
<button onclick="addOne()">+1</button>
<p id="counterP">${ counter }</p>
</div>`;
function addOne() {
counter += 1;
document.getElementById("counterP").innerHTML = counter;
}
</script>
</body>
</html>
All screen readers, including JAWS, handled this correctly.
It's difficult determining where the culprit is here. It looks like JAWS handles the (initial) rendering of components correctly, but updates of (parts of) the components don't get handled properly. Narrator has the same isues, but there are (though unelegant) workarounds. NVDA and VoiceOver seem to be the only screen readers which perform properly here out of the box.
It might be an issue with WASM - I would need to implement something like this using another WASM compiler to be sure. It could also be a problem with wasm-pack (perhaps a few accessibility measures JavaScript and other WASM compilers do did not get included in the wasm-pack implementation?) or it could be a Yew-specific rendering problem; though, since the WASM is already compiled and Yew got no real business at that point anymore, I doubt that. Does anyone have more expertise in this area, or even just suggestions what else I could try out to pinpoint the cause of these issues?
Since NVDA works fine, I could of course tell Windows users to use that, but JAWS is still pretty popular and widespread (I use it for most things, too). So getting this issue to the right address and fixing it would be best.