スクリーンリーダーはどうやってライブリージョンを読み上げるのか

今年 [NVDA 日本語チーム](https://www.nvda.jp/) の代表を退きましたが、ひきつづき NVDA 日本語版の開発に関わっている @24motz / [nishimotz](https://github.com/nishimotz) です。

これは [アクセシビリティ Advent Calendar 2022](https://adventar.org/calendars/7377) の9日目の記事です。

[自分のブログサイト](https://d.nishimotz.com/)がありますが、今年は、いつもお世話になってばかりの Qiita を使ってみます。

## WAI-ARIA

WAI-ARIA とは「HTMLの要素に対し、支援技術に伝えるべき情報を追加」[^解体新書] するもの、あるいは「HTMLのセマンティクスをより強化」[^コーディング] するものです。

[^解体新書]: 太田, 中村: HTML解体新書 仕様から紐解く本格入門, ボーンデジタル, 2022.

[^コーディング]: ピカリング, 太田, 伊原: コーディングWebアクセシビリティ WAI-ARIAで実現するマルチデバイス環境のWebアプリケーション, ボーンデジタル, 2015.

WAI-ARIA には要素の役割を表す「ロール」という概念があります。

例えば main 要素に対応する main ロールがありますが、対応するHTMLの要素が存在しないロールや、特殊な働きをするロールもあります。

## ライブリージョン

WAI-ARIA で定義された役割の中で特殊なもののひとつが「ライブリージョン」ロールです。

これは「内容が更新された際にユーザーに通知される領域」を表しています。

通常、スクリーンリーダーは受動的にページの内容を読み上げます。ユーザーがフォームコントロールを操作するか、スクリーンリーダーそのものの「読む」という機能を使わないと、読み上げは始まりません。

例えば Web サイトに送信ボタンがあり、押されるとページ遷移をしないでページ内のどこかに「送信しました」とか「入力に誤りがあります」といったメッセージを出すとします。

スクリーンリーダーが受動的な読み上げしかできないと、ユーザーはわざわざ「なにかメッセージが出ていないか」探す必要があります。

メッセージを「ライブリージョン」にしておけば、スクリーンリーダーが能動的にメッセージを読んでくれるので、探すことなく、容易に結果を知ることができます。

具体的には以下のロールが「ライブリージョン」に分類されます。

* alert
* status
* timer
* marquee
* log

え、こんなにあったっけ、と思ったのはここだけの話ですが、今回は alert と status に説明をしぼります。

CSSフレームワーク Bootstrap 5.0 では [「アラート」コンポーネント](https://getbootstrap.jp/docs/5.0/components/alerts/) の使用例は alert ロールになっていました。

```html
<div class="alert alert-danger" role="alert">
  A simple danger alert—check it out!
</div>
```

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/4465eff1-5f9a-07db-8a86-3a56941b63e2.png)

また [スピナー](https://getbootstrap.jp/docs/5.0/components/spinners/) の使用例には status ロールが指定され `Loading...` というメッセージを伝えていました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/fe4f4c74-2a10-55cd-ac4c-4b742ec78d4a.png)

```html
<div class="spinner-border text-primary" role="status">
  <span class="visually-hidden">Loading...</span>
</div>
```

alert ロールは WCAG 2.0 [達成基準 3.3.1 エラーの特定](https://waic.jp/docs/WCAG20/Overview.html#minimize-error-identified) に関する達成方法 [ARIA19: エラーを特定するために、ARIA role=alert 又はライブ領域 (Live Regions) を使用する](https://waic.jp/docs/WCAG-TECHS/ARIA19) として解説されています。

status ロールは WCAG 2.1 の達成方法 [ARIA22: ステータスメッセージを提示するために role=status を使用する](https://waic.jp/docs/WCAG21/Techniques/aria/ARIA22) で解説されています。

ところで、ライブリージョンは role 属性ではなく aria-live 属性で定義することもできます。

前述の文献[^コーディング]は「ユーザーエージェントの中には残念ながら同等の属性のどちらかをサポートしていないものがある」ため、両方の属性を指定することを推奨しています。

alert と status のロールでは、暗黙の aria-live の値がそれぞれ assertive と polite だとされています。

aria-live で書くと、さきほどの例はこうなります。

```html
<div class="alert alert-danger" aria-live="assertive">
  A simple danger alert—check it out!
</div>

<div class="spinner-border text-primary" aria-live="polite">
  <span class="visually-hidden">Loading...</span>
</div>
```

本当は role を使いたいのですが、話を簡単にするために、ここからは aria-live 属性だけを使います。

## アクセシビリティ・サポーテッド情報

「ユーザーエージェントの中には」と書かれていますが、実際にはユーザーエージェント（Webブラウザ）と支援技術（スクリーンリーダー）の組み合わせに左右されます。

仕様では `role="alert"` を書けば暗黙的に `aria-live="assertive"` になる、とされていても、支援技術が幅広くサポートしていないなら、仕様ではなくユーザーの現実を考慮しなさい、という話になっています。

[アクセシビリティ・サポーテッド情報(AS情報)](https://waic.jp/guideline/as/)は、どのような組み合わせでどの達成方法（実装のやり方）がちゃんと使えるかを判断するための情報です。

日本では [WAIC WG2](https://waic.jp/committee/wg2/) がAS情報の整備に取り組んでいます。私はNVDA日本語チームとしてWG2に2015年から参加しています。

[WAICのAS情報の最新版](https://waic.jp/docs/as/info/index.html)はまだ達成基準 3.3.1 のテスト作成にもたどり着いていませんね。先は長いです。。

## WAI-ARIA の実装に踏み込む？

Windows 用のオープンソースのスクリーンリーダー [NVDA](https://www.nvaccess.org/) は2009年という早い時期からライブリージョンに対応していました。

[本家チケット246](https://github.com/nvaccess/nvda/issues/246)にその記録がありますが、当時 Bazaar と Trac でプロジェクトが管理されていたので、実装を追うのは大変ですね。。

NVDA はどのようにライブリージョンに対応しているのか、いずれ解説をしたいと思っています。

その準備として、今回は NVDA にこだわらずに、いちばん単純で理想的な場合について説明します。

## UIオートメーション

[Microsoft UI オートメーション(UIA)](https://learn.microsoft.com/ja-jp/windows/win32/winauto/entry-uiauto-win32) はMicrosoft Windows 向けのアクセシビリティ・フレームワークであり、支援技術だけでなく自動テストやRPAなどへの対応も想定されています。

ライブリージョンに関わっているのは [UI オートメーションイベント](https://learn.microsoft.com/ja-jp/windows/win32/winauto/uiauto-eventsoverview) です。以下のように説明されています。

> Microsoft UI オートメーション イベント通知は、スクリーン リーダーやスクリーン 拡大鏡などの支援技術の重要な機能です。 これらの UI オートメーション クライアントは、UI で何かが発生したときに UI オートメーション プロバイダーが発生させるイベントを追跡し、その情報を使用してエンド ユーザーに通知します。
> これらのイベントにサブスクライブしているクライアントがあるか、それともイベントをリッスンするクライアントがなく、サブスクライブしているクライアントがまったくないかに応じて、プロバイダー アプリケーションが選択的にイベントを発生させることで効率が向上します。

[イベント識別子](https://learn.microsoft.com/ja-jp/windows/win32/winauto/uiauto-event-ids) リストには以下の記載があります。

* UIA_LiveRegionChangedEventId ライブ リージョンのコンテンツが変更されたときに発生するイベントを識別します。


## UIAイベントを調べるツール

UIオートメーションイベントを調べるツールを Microsoft が提供しています。

ひとつは[Microsoft Accessibility Insights for Windows](https://accessibilityinsights.io/)です。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/a9c9381b-f747-1b0d-76aa-dfc0aa16be7a.png)

マスコットキャラクターはこんな猫ちゃんです。

<img width="300" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/b968f9fb-cf22-6d88-ac2c-96b41c0c8576.png">

もうひとつは Accessible Event Watcher (AccEvent) です。Windows SDK にずいぶん昔からひっそりと同梱されています。

<img width="500" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/8f3c0cd4-2d1a-5e56-ba80-225169ba1e05.png">

AccEvent は2012年の Steve Faulkner さんの記事 [HTML5 Accessibility Chops: ARIA role=alert browser support](https://www.tpgi.com/html5-accessibility-chops-aria-rolealert-browser-support/) で使われています。

ここでは Accessibility Insights の操作と結果を紹介します。

## LiveRegionChanged の観察

### 手順1

Windows 11 のスクリーンリーダー「ナレーター」を起動します。

ナレーターは Windows+Ctrl+Enter で起動または終了します。

「ナレーター ホーム」画面が邪魔な場合は最小化します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/af4cd789-b60e-eb66-1578-949f4bb8a98c.png)


### 手順2

この状態で Edge ブラウザを起動します。さらに Edge で観察したいWebコンテンツを開きます。

ここでは「freeeアクセシビリティー・ガイドライン」[ステータス・メッセージとスクリーン・リーダー](https://a11y-guidelines.freee.co.jp/explanations/dynamic_content-status.html) の解説にある `aria-live-basic.html` を使います。

この「ARIAライブ・リージョンの基本的な実装例」では `aria-live="polite"` が使われています。

### 手順3

Accessibility Insights を以下のように操作します。

#### (1) イベント取得の対象として Edge アプリを選択する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/c1da893a-449c-c0ae-208a-892f23d42d53.png)

1. What to select: Entire app にする
2. Edge のウィンドウにマウスカーソルを乗せて、すばやく Accessibility Insights ウィンドウにマウスカーソルを戻して、左側 Live Inspect 画面のツリービュー「ウィンドウ デスクトップ 1 」の下にある「ウィンドウ 'Microsoft Edge'」を選ぶ。または、Alt+Tab で Microsoft Edge をアクティブにして、続けて Shift+F9 を押す。

#### (2) Listen to Events を選ぶ

右端の三点リーダーのようなボタンからメニューを開いて Listen to Events を選ぶ。

Listen to Events の表示：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/5df4f491-719e-1811-8ce4-b67c4cb86725.png)

#### (3) LiveRegionChanged だけを選ぶ

右側の "Select the Events you want to listen to:" で、選択済みの項目のチェックを解除し、My Events を開き、Edit My Events で LiveRegionChanged を有効にする。ツリービューに戻って My Events だけが選択された状態にする。

"Select the Events you want to listen to:" の表示：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/6f8983e7-f8b1-1cd3-6b1e-08f48a381196.png)

Event Configuration の表示：

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/48e38319-0fc4-c559-26b3-38edf523d6a4.png)

#### (4) イベントの記録を開始する

Start Recording の赤いボタンを押して、イベントの記録を開始する

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/9439a4bd-8f41-bb15-1b0b-e1f0a83ca63c.png)

最初に EventRecorderNotification という名前のイベントが表示される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/710090c4-407e-d259-7180-c468851b90de.png)


### 手順4

Edge のウェブコンテンツで「ページを更新」ボタンを押します（Tab でフォーカスを移動して Space で押せます）。

ナレーターが「ARIAライブ・リージョンの基本的な実装例 ページが更新されました」と読み上げることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/4d07b22f-ef2e-cbc5-efd1-bed78402c3b9.png)

このとき Accessibility Insights に LiveRegionChanged イベントが表示されることを確認します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/9177/b21f245c-563d-19f3-e46d-393e20cf02a5.png)

Edge で F5 を押してページを再読込して、この手順をやり直します。

ページの再読込のときにも LiveRegionChanged が発生することを確認します。

ナレーターの終了は Windows+Ctrl+Enter または「ナレーター ホーム」画面の「ナレーターを終了」ボタンで行います。

## 考察

### 観察したイベントの中身

Properties を見ると、例えば以下が興味深いです。

* AriaRole: group
* AutomationId: status-region
* FrameworkId: Chrome

また「ページが更新されました」という文字列そのものは LiveRegionChanged には含まれていないようです。

status-region は実装例で `aria-live="polite"` が指定された div 要素の id 属性です。

考えてみれば当たり前なのですが AriaRole: group でした。 status ロールにはならないですね。

MDN には [group ロールの使用](https://developer.mozilla.org/ja/docs/Web/Accessibility/ARIA/Roles/group_role) という記事がありました。

### 選択的なイベント発生

「ブラウザでなにかが起きると、スクリーンリーダーがそれを読み上げる」という処理を素直に考えると、何らかの手段でプロセス間（？）通信が行われていて、適切な方法で観察できるはずです。今回はそれを確認できました。

前述の説明に「イベントをサブスクライブまたはリッスンしているかどうかに応じて選択的にイベントを発生する」などと書かれていました。

実はナレーターが動いていなくてもイベントは確認できます。

どうやら「Edge の LiveRegionChanged イベントの受信者」として Accessibility Insights またはスクリーンリーダーを動かすことが、この UIA イベント送信の条件になるようです。

### 支援技術はマルウェアか？

今回 UIAイベントを受信するツールは、例えば「管理者として実行」のようなことをせず、LiveRegionChanged イベントを受信できました。

UIA イベントを盗み取るマルウェアを作ることは可能なのか、さらに調べてみたいです。
「ライブリージョンでパスワードなどの秘密情報を通知してよいのか」といった議論ができるかも知れません。

### 何を読ませたくないか？

ナレーターはライブリージョンを読むときに Edge のドキュメントのタイトルを読みます。

スクリーンリーダーから見ると「ブラウザのこのコンテンツからこういうイベントが届いた」ということで、イベントの発生源を丁寧に知らせたいのでしょうが、ユーザーは「内容だけを聞きたい」かも知れません。

スクリーンリーダーの使いやすさ・評価は「必要なことを抜け漏れなく読むか」だけではなく、「どういう場合にどういう情報を省略するか」にも左右されます。

今回、ページの読み込みで LiveRegionChanged が飛んでくることがわかりましたね。。
たぶん「要素そのものがない」状態から「要素が追加されたけれど中身がなにもない」という状態に変更したということで、何も読まなくてよい、とナレーターが判断しているのでしょう。。

## 展望

NVDA はどのようにライブリージョンに（限らず WAI-ARIA に）対応しているのか。

本当はこの「ナレーターとEdgeのようにシンプルで効率的な方法」が理想です。

この理想がなかなか実現できなかったのは、Windows という OS において「アプリの実装方法」があまりに多く存在するからです。アクセシビリティに関しても、過去の技術やその派生技術 (MSAA / IA2) が現在まで使われてきて UI オートメーションと共存しています。

高度で複雑な実装、場当たり的な実装を積み重ねて、いろいろなバージョンの Windows やアプリやアクセシビリティ API に対応し続けてきたのが NVDA の歴史です。

紹介した[本家チケット246](https://github.com/nvaccess/nvda/issues/246)はその記念すべき第一歩のあたりでしょう。

そして、複雑で混沌とした状況を、うまく抽象化して、柔軟に対応しながら進化してきたのが、ソフトウェアとしての NVDA の興味深いところです。

一方で「NVDA のライブリージョンの挙動は、他のスクリーンリーダーと違っていて、なにかおかしいのではないか」という話があります。詳細は次の機会に。

## おまけ

NVDA の設定 [オブジェクト表示](https://www.nvda.jp/nvda2022.3.2jp/ja/userGuide.html#ObjectPresentationSettings) には「動的コンテンツの変化の報告」というオプションがあり、ここでライブリージョン対応を無効化できます。

## 最後に

[シュアルタ](https://www.shuaruta.com/) は、家族（私たち夫婦と犬と猫）で設立した株式会社です。
NVDA 日本語チームからの業務委託として 2022年10月から NVDA 日本語版の開発を支援しています。コードサイニング証明書も Shuaruta Inc. で提供しています。ざっくりいうと、開発やリリースは私が続けています。

コミュニティの運営を新しい体制に任せたので、私は NVDA 日本語版の改善、技術的な調査や支援、セキュリティ支援などに取り組んでいく予定です。

高校生から20代までの視覚障害者によるソフトウェアの開発活動の団体 [ACT Laboratory さん](https://actlab.org/)にお願いして、メンバーのみなさんと秘密保持契約をして、定期的な勉強会を始めました。まずは NVDA のソースコードや開発者ドキュメントや GitHub issue を読んで議論、といったことをしています。

NVDA 日本語版の開発環境の改善、過去の実装のモダナイズ、そして最近できていない本家版 NVDA の開発にも貢献したいと思っています。

### 謝辞

A11YJ Slack の q_and_a チャンネルで行われた議論が参考になりました。ありがとうございます。