Skip to content

Latest commit

 

History

History
787 lines (563 loc) · 54.1 KB

explainer.md

File metadata and controls

787 lines (563 loc) · 54.1 KB

アクセシビリティオブジェクトモデル

著者:

Table of Contents

はじめに

この取り組みの目的は、Webプラットフォームへの追加機能を開発して、開発者が支援技術APIに情報を提供できるようにし、ブラウザーがそれらのAPIに提供する情報を理解することです。

モチベーションとなるユースケース

既存のAPIの背景は付録で見つけることができる。

ウェブ上でできることの境界を押し広げているウェブアプリケーションは、APIが不十分なためそれらをアクセシブルにするため苦闘している。特に、ブラウザとやりとりするネイティブAPIと比較して表現力に劣っている。

  1. ページの著者が上書きできるウェブコンポーネントのデフォルトアクセシビリティプロパティの設定
    • 現在、ウェブコンポーネントのデフォルトセマンティクスを定義するにはARIAを使用しなければならない。 これにより、真に詳細な実装であるARIA属性がDOMに「リーク」してしまう。
    • このことは 必要ではなく ウェブコンポーネントに限られる かも 知れない。
  2. IDREFs を必要としない関係属性の設定
    • 現在、いくつかのARIAの関係を示すには、著者が一意のIDを関係の対象となりうる要素に指定しなければならない
    • aria-activedescendantのような場合、 それはUIに応じて、数百ないしは数千もの要素のうち、一つを参照するかもしれない。 この要求は、多くの余分なDOMの属性が必要となり、これらのAPIを複雑にする。
  3. 支援技術からのイベントをリスニングする
    • 現在、組み込み要素 だけ がイベントに反応することができ、 通常、「increment」 のようなユーザーアクションによって引き起こされる。
  4. アクセシビリティツリーにDOMでないノード(仮想ノード)を追加する
    • 例えば、<canvas> 要素で構築された複雑なUIや、<video> 要素を用いたリモートデスクトップのストリーミングなどを表現するなど
    • そのためには、少なくとも要素と同じようなアクセシビリティプロパティや、他の仮想ノードとの親/子/その他の関係性、位置や次元を表す必要がある。
  5. 計算済のアクセシビリティツリーの確認
    • 開発者は現在、ARIAやその他のアクセシビリティプロパティがどのように適用されているかを調べたりテストする方法を持っていない。

アクセシビリティオブジェクトモデル

アクセシビリティオブジェクトモデル (AOM)は、上記のユースケースに取り組むためのHTMLや関連する標準に対する一連の変更である。

(注: 以前のバージョンのAOMに慣れ親しんでいれば、AccessibleNodeに何が起こったのか?と疑問を持つかもしれない。)

ARIA属性を反映する

ARIA 属性をHTML要素に反映する

el.role = "button";
el.ariaPressed = "true";  // aria-pressed は3つのステートを持つ属性
el.ariaDisabled = true;   // aria-disabled は true/false を持つ属性

Spec/implementation status

This is now a part of the ARIA 1.2 spec.

This is shipping in Safari, and implemented behind a flag (enable-experimental-web-platform-features) in Chrome.

要素の参照を反映する

ARIAプロパティの直接的反映。aria-labelledby のような関係属性の文字列に反映させる。

el.ariaDescribedBy = "id1";

結果は

<div aria-describedby="id1"></div>

要素を参照する非反映のプロパティでAPIを拡張することを提案する。

el.ariaDescribedByElements = [labelElement1, labelElement2];
el.ariaActiveDescendantElement = ownedElement1;

Note: the Element or Element suffixes are a naming choice for the reflected property, and do not imply that there will be both string and Element properties for the same attribute.

これにより、関係に属するそれぞれの要素にグローバルに固有なID属性を割り当てることなく要素間のセマンティクスの関係性を示すことができる。

さらにこれは、ShadowRootを使用した著者がShadow DOMの境界を超えて関係性を明示することを可能にする

ユースケース 2: IDREFsを使用することなく関係プロパティを設定する

現在、Shadow DOMの境界を超えて関係を表そうとする著者は次のように aria-activedescendant の使用を試すだろう。

<custom-combobox>
  #shadow-root (open)
  |  <!-- これはうまくいかない! -->
  |  <input aria-activedescendant="opt1"></input>
  |  <slot></slot>
  <custom-optionlist>
    <x-option id="opt1">Option 1</x-option>
    <x-option id="opt2">Option 2</x-option>
    <x-option id='opt3'>Option 3</x-option>
 </custom-optionlist>
</custom-combobox>

これは失敗で、なぜならIDREFsはshadowRoot、またはそれらが現れる文書の文脈の範囲に限られる。

要素参照を利用することで著者は代わりにこの関係性をプログラムで示すことができる。

const input = comboBox.shadowRoot.querySelector("input");
const optionList = comboBox.querySelector("custom-optionlist");
input.activeDescendantElement = optionList.firstChild;

このことにより、関係が自然に表現されるようになる。

Spec/implementation status

Default semantics for Custom Elements via the ElementInternals object

We propose that Custom Element authors be able to provide default semantics via the ElementInternals object.

A custom element author may use the ElementInternals object to modify the semantic state of an instance of a custom element in response to user interaction.

The properties set on the ElementInternals object are used when mapping the element to an accessible object.

If the author-provided semantics conflict with the Custom Element semantics, the author-provided semantics take precedence.

Note: this is analogous to setting an "instance variable" - a copy of these semantic properties is created for each instance of the custom element. The semantics defined in each apply only to their associated custom element instance object.

ユースケース1: 非反映のデフォルトアクセシビリティプロパティをウェブコンポーネントに設定する

今、ウェブコンポーネントを制作するライブラリの著者はネイティブ要素にとっては暗黙であるセマンティクスを表すためにARIA属性を「生やす」ことを強いられている。

<!-- ページの著者はカスタム要素をネイティブ要素を使用するように使用する -->
<custom-tablist>
  <custom-tab selected>Tab 1</custom-tab>
  <custom-tab>Tab 2</custom-tab>
  <custom-tab>Tab 3</custom-tab>
</custom-tablist>

<!-- カスタム要素がセマンティクスを表すために余分な属性を「生やす」ことを強制される -->
<custom-tablist role="tablist">
  <custom-tab
    selected
    role="tab"
    aria-selected="true"
    aria-controls="tabpanel-1"
    >Tab 1</custom-tab
  >
  <custom-tab role="tab" aria-controls="tabpanel-2">Tab 2</custom-tab>
  <custom-tab role="tab" aria-controls="tabpanel-3">Tab 3</custom-tab>
</custom-tablist>

Using ElementInternals to set the default semantics, a Custom Element may avoid needing to sprout attributes, and also avoid losing its semantics if authors decide to delete ARIA attributes.

class CustomTab extends HTMLElement {
  constructor() {
    super();
    this._internals = customElements.createInternals(this);
    this._internals.role = "tab";
  }

  // カスタム「active」属性を監視する
  static get observedAttributes() {
    return ["active"];
  }

  connectedCallback() {
    this._tablist = this.parentElement;
  }

  setTabPanel(tabpanel) {
    if (tabpanel.localName !== "custom-tabpanel" || tabPanel.id === "")
      return;  // 静かに失敗する

    this._tabpanel = tabpanel;
    tabpanel.setTab(this);
    this._internals.ariaControls = tabPanel; // 反映されない
  }

  // 属性に反映するカスタムプロパティの setters/getters

  attributeChangedCallback(name, oldValue, newValue) {
    switch (name) {
      case "active":
        let active = newValue != null;
        this._tabpanel.shown = active;

        // カスタム「active」属性が変更された時、
        // アクセシブルな「selected」ステートを同期し続ける
        this._internals.ariaSelected = newValue !== null;

        if (selected) this._tablist.setSelectedTab(this); // 他のタブが 「active」で無いことを保証
        break;
    }
  }
}

customElements.define("custom-tab", CustomTab);

これらの要素を使用する著者は通常通りARIAを用いてデフォルトセマンティクスを上書きすることができる。

例えば、著者は <custom-tablist> 要素の見た目を縦並びに変更できる。彼らはそれを示すために aria-orientation 属性を追加することで、カスタム要素に実装されているデフォルトセマンティクスを上書きすることができる。

class CustomTabList extends HTMLElement {
  constructor() {
    super();
    this._internals = customElements.createInternals(this);
    this._internals.role = "tablist";
    this._internals.ariaOrientation = "horizontal";
  }

  // ...
}

customElements.define("custom-tablist", CustomTabList);
<custom-tablist aria-orientation="vertical" class="vertical-tablist">
  <custom-tab selected>Tab 1</custom-tab>
  <custom-tab>Tab 2</custom-tab>
  <custom-tab>Tab 3</custom-tab>
</custom-tablist>

Spec/implementation status

支援技術からのユーザーアクションイベント

支援技術ユーザーのプライバシーを保護するために、通常、支援技術からのイベントは合成されたDOMイベントを発生させる。The events are determined by platform conventions and partially documented in the ARIA Authoring Practices Guide (APG).

支援技術のイベント ターゲット Orientation/Direction DOMイベント
click or press すべての要素 click MouseEvent
focus all focusable elements focus Event
blur No targets, as blur could potentially 'out' AT users. None
select Elements whose computed role supports aria-selected click MouseEvent
dismiss or escape すべての要素 Escape KeyboardEvent
contextMenu すべての要素 contextmenu MouseEvent
increment Elements w/ computed role progressbar, scrollbar, or slider vertical Up KeyboardEvent
"" horizontal LTR Right KeyboardEvent
"" horizontal RTL Left KeyboardEvent
Elements w/ computed role spinbutton orientation n/a Up KeyboardEvent
decrement Elements w/ computed role progressbar, scrollbar, or slider vertical Down KeyboardEvent
"" horizontal LTR Left KeyboardEvent
"" horizontal RTL Right KeyboardEvent
Elements w/ computed role spinbutton orientation n/a Down KeyboardEvent
scrollByPage TBD (possibly custom scroll views) TBD (possibly PageUp/PageDown)
scrollIntoView TBD No equivalent DOM event
setValue n/a No equivalent DOM event

Notes on the previous table:

  • DOM KeyboardEvent sequences include keyup/keydown.
  • DOM MouseEvent sequences include mousedown/mouseup and touchstart/touchend where relevant.
  • contextmenu sequence may need to include MouseEvents, including mousedown/mouseup/auxclick/contextmenu.
  • Control orientation is determined by the computed value of aria-orientation which defaults to horizontal for progressbar and slider, and defaults to vertical for scrollbar.
  • Natural language direction is determined by the computed value of dir which usualy computes to to ltr (auto in most contexts resolves to ltr), but can be set to rtl for languages such as Arabic and Hebrew.
  • The DOM event target for DOM KeyboardEvent sequences is the currently focused DOM element, regardless if the AT's "point of regard" matches the document.activeElement.
  • If a web author does not cancel the DOM event with Event.preventDefault() and/or Event.stopPropagation(), the DOM event should propagate out of the web view an potentially trigger the platform behavior of the assistive technology event. For example, if an iOS user triggers a native dismiss/escape event but the web author does not capture or cancel the DOM Escape key sequence, the browser or system should execute the default functionality of the native accessibilityPerformEscape() handler.

MouseEvent Object Properties

MouseEvent button target/srcElement which (deprecated)
click 1 TBD 1
contextmenu 2 (secondary click) TBD 3 (legacy right click)

Note: Only send the deprecated which property if the user agent would normally send it on a non-synthesized mouse event.

Note: The target and srcElement properties should match the most likely element in the case of a non-synthesized MouseEvent (a real mouse click). Since AT focus targets and pointer event targets do not always align one-to-one, this event property is currently TBD. For example, users agents might attempt to synthesize a pointer event x/y position near the center of the element in AT focus. If hit-testing at that x/y position does not return a descendant of the element in AT focus, user agents might synthesize the event on the element directly in AT focus.

KeyboardEvent Object Properties

KeyEvent key code location target/srcElement
Escape "Escape" "Escape" DOM_KEY_LOCATION_STANDARD (0) document.activeElement
Left "ArrowLeft" "ArrowLeft" DOM_KEY_LOCATION_STANDARD (0) document.activeElement
Up "ArrowUp" "ArrowUp" DOM_KEY_LOCATION_STANDARD (0) document.activeElement
Right "ArrowRight" "ArrowRight" DOM_KEY_LOCATION_STANDARD (0) document.activeElement
Down "ArrowDown" "ArrowDown" DOM_KEY_LOCATION_STANDARD (0) document.activeElement

The target and srcElement properties should match document.activeElement, which is either the currently focused element or document.body.

Deprecated KeyboardEvent Object Properties (Optional)

Only send these deprecated properties if the user agent would normally send them on non-synthesized keyboard events.

KeyEvent charCode keyCode keyIdentifier keyLocation which
Escape 0 27 U+001B (Unicode Character 'ESCAPE') 0 27
Left 0 37 Left 0 37
Up 0 38 Up 0 38
Right 0 39 Right 0 39
Down 0 40 Down 0 40

Note: These event property tables are intended to assist implementors during the incubation process. This is not intended as a normative specification.

Speculative: New InputEvent types

Note: This section is speculative, as there is now no immediate plan to include InputEvents for Assistive Technology Actions.

いくつかのInput Event タイプを追加する:

  • increment
  • decrement
  • dismiss
  • scrollPageUp
  • scrollPageDown

これらのイベントは上記の表に記載された合成キーボードイベントと共に支援技術のイベントから引き起こされ、また上記の関連する支援技術のイベントに対応した有効なターゲットの文脈の中で発生したときに合成される。

例えば、もしある文脈で支援技術を使用していないユーザーが Escape キーを押した場合、キーが押されたシーケンスで dismiss タイプを伴った input イベントが発火する。

もし同じユーザーが <input type="range"> または (計算された slider ロールを含む)slider ロールを持った要素上にフォーカスがある Up キーを押した場合、キーが押されたシーケンスで increment タイプを伴った input イベントがフォーカスされた要素で発火される。

ユースケース3: 支援技術からのイベントをリッスンする

例えば:

  • ユーザーは音声制御ソフトウェアを利用しており、ウェブページの中のどこかのボタンの名前を読み上げられる。音声制御ソフトウェアはアクセシビリティツリーの中に該当する名前があるボタンを見つけ、クリックすることで アクション を送信することができる。
  • 同じユーザーはあるページをスクロールダウンする音声コマンドを発行する。その音声制御ソフトウェアはウェブページのルート要素を見つけ、スクロール アクション を送信する。
  • モバイルスクリーンリーダーユーザーは、スライダーに移動し、範囲に基づいたコントロールを増加させるジェスチャーを実行することができる。スクリーンリーダーはアクセシビリティツリーの中のスライダー要素にブラウザーに増加 アクション を送信する。

現在、ブラウザーはネイティブHTML要素の組み込みサポートを実装することによって、アクセシビリティアクションを一部実装している。(例えばネイティブHTMLの <input type="range"> はすでに増加・減少アクションをサポートし、テキストボックスは値を設定したりテキストを挿入するアクションをサポートしている。)

しかしウェブページの著者がカスタムエレメント上のアクセシビリティアクションをリッスンする方法が無い。 例えば、slider ロールを持ったカスタムスライダー 上ではiOSのVoiceOverでは増加・現象のためにスワイプアクションを促す提案がされるが、いかなるウェブAPIもそのセマンティックイベントを扱うことができない。

開発者はセマンティックイベントを補足するようなキーボードイベントまたは入力イベントをリッスンすることができるようになるだろう。

例えば、著者はカスタムスライダーを実装するために ARIA Authoring Practices guideで推奨されている UpDown キーイベントを扱うことができ、同じように支援技術のイベントも扱うことができる。

customSlider.addEventListener('keydown', (event) => {
  switch (event.code) {
  case "ArrowUp":
    customSlider.value += 1;
    return;
  case "ArrowDown":
    customSlider.value -= 1;
    return;
});

Spec/implementation status

Not yet specced or implemented anywhere.

仮想アクセシビリティノード

仮想アクセシビリティノード は著者に、支援技術に特定のDOM要素に直接関係のない 仮想 のアクセシビリティノードに触れさせられることができる。

このメカニズムは、著者がカスタム描画APIのアクセシビリティをよりきめ細かくコントロールするためにネイティブアクセシビリティAPIによく存在する。

  • attachAccessibleRoot() を実行することで AccessibleNodeNode に関連付けられる。
    • リターンされた AccessibleNode は仮想アクセシビリティツリーのルートを形成する。
    • そのノードのDOM子要素は、AccessibleRoot が付与されると、暗黙のうちに無視され、DOMの子要素と仮想アクセシブルノードが混在することはない。
  • ShadowRoot のように、一つの要素は一つの関連付けられた AccessibleRoot しか持たない。
  • AccessibleNode だけが子に AccessibleNodes 持ち、 AccessibleNodeAccessibleNode のみ子として持つ。

ユースケース4; DOMでない仮想のノードをアクセシビリティツリーに追加する

例えば、<canvas> 要素から構築された複雑なUIを表すには:

// canvas ベースのスプレッドシートのセマンティクスを実現する
canvas.attachAccessibleRoot();
let table = canvas.accessibleRoot.appendChild(new AccessibleNode());
table.role = "table";
table.colCount = 10;
table.rowcount = 100;
let headerRow = table.appendChild(new AccessibleNode());
headerRow.role = "row";
headerRow.rowindex = 0;
// などなど

仮想ノードは通常位置と寸法を明示的に設定する必要がある。

cell.offsetLeft = "30px";
cell.offsetTop = "20px";
cell.offsetWidth = "400px";
cell.offsetHeight = "300px";
cell.offsetParent = table;

もし offsetParent を設定しない場合、アクセシブルノードの親を基準として解釈される。

ノードをフォーカスできるようにする場合、 focusable 属性を設定することができる。これは tabIndex=-1 をDOM要素に設定するのに似ている。

virtualNode.focusable = true;

仮想アクセシブルノードはデフォルトではフォーカスできない。

最後に、アクセシビリティノードにフォーカスするには、 focus() メソッドを実行する。

virtualNode.focus();

仮想アクセシブルノードがフォーカスされたとき、DOMのフォーカスは変わらない。アクセシビリティノードのフォーカスは、支援技術や他のアクセシビリティAPIクライアントに伝わるが、DOMイベントは発火せず document.activeElementも変わらない。

DOM要素のフォーカスが変わった時、アクセシブルフォーカスも追従し、DOM要素に関連付けられたアクセシブルノードがフォーカスされる。

Spec/implementation status

Not yet specced or implemented anywhere.

ComputedAccessibleNode によるアクセシビリティツリーの完全な確認

This API is still being considered.

It may be approached initially as a testing-only API.

ユースケース5: 計算されたツリーを確認する

計算されたアクセシビリティツリー APIは著者に、完全な計算されたアクセシビリティツリーにアクセスすることを許可する。それぞれのDOM要素に関連付けられたアクセシビリティノードのすべての計算されたプロパティに加え、仮想ノードを含む計算された木構造を走査可能にする。

このことは次のことを可能にする:

  • ページ、または要素のセマンティックプロパティが確からしいかを確認するプログラムによるテストを書くこと
  • ブラウザーベースの信頼できる支援技術の構築。例えば、アクセシビリティツリーを利用したスクリーンリーダー、スクリーンルーペ、または他の支援機能を持ったブラウザー拡張やページ内ツール。
  • アクセシビリティプロパティが(ARIAなどを通じて)要素に正しく適用されたかを検査する。例えば、ブラウザが特定のバージョンのARIAに対応しているかを検査できる。
  • アクセシビリティツリーの問題をコンソールベースでデバッグしたりチェックしたりする。
  • アクセシビリティツリーの状態に反応する。例えば要素に表されたロールを検査したり、アクセシブルなヘルプテキストを変更したりなど。

Spec/implementation status

A purely experimental implementation exists in Blink, via the command-line flag --enable-blink-features="AccessibilityObjectModel".

This adds a method to Window, getComputedAccessibleNode(node), which returns the computed accessible properties for the given node.

This implementation is not reliable and may be removed at any point.

なぜ最終的に計算されたプロパティにアクセスするのか

一貫性 現在、アクセシビリティツリーはブラウザ間で標準化されていない。それぞれのアクセシビリティツリーの計算は実装がわずかに異なる。

このAPIが役に立つには、ブラウザ間で一貫して動作することが必要で、開発者はそれぞれに特別なコードを書く必要がない。

我々はツリーがどのように計算され表現されるかについて合意ができるよう十分な時間をかけていきたいと考えている。

パフォーマンス 多くのアクセシビリティプロパティを計算するにはレイアウトが必要となる。ウェブ制作者に単純なプロパティアクセスと同期してアクセシビリティプロパティの計算された値を調べることを許可すると、パフォーマンスのボトルネックに混乱をもたらしてしまう。

このことから accessibleNode インターフェースの一部を意味しない非同期的なメカニズムを作成したいと考えている。

ユーザー体験 前の3つと比較して計算されたアクセシビリティツリーにアクセスすることはユーザーに与える影響が最小限となる。優先順位の構成要素の考え方の元、このことに最後に取り組むことは理にかなっている。

このAPIの対象者

このAPIは主に、ウェブアプリの多くを動かしているJavaScriptフレームワークやウィジェットライブラリーを制作、メンテナンスしている比較的少数の開発者に興味を持たれるだろう。

それらのフレームワークやライブラリーにとってアクセシビリティは、可能な限り幅広く多様な文脈で使用されるようになるため、重要な目標の一つである。

低レベルのAPIによって彼らは制約やバグを回避し、彼らの制作したコンポーネントを使用する開発者のためにクリーンな高レベルのインターフェースを提供できるようになる。

このAPIはウェブプラットフォームの境界を押し広げるような巨大なフラッグシップウェブアプリの開発者をも対象としている。

これらのアプリの開発チームでは、Canvasのような低レベルAPIを利用してでもパフォーマンスを向上させたがっていることがしばしばある。

そのような開発チームだと、アクセシビリティを優先するだけのリソースもあるが、既存のAPIでは非常に面倒である。

AccessibleNodeに何が起こったのか?

当初の意図としては、これまでのユースケースを読み書き可能なAPIにまとめるものだった。それはそれぞれのDOMの Element に、アクセシビリティプロパティを読み込んだり書き込んだりできる AccessibleNode が関連付けられているという点でDOMに似ていた。

これはドキュメントオブジェクトモデルと似たようなアクセシビリティオブジェクトモデルと命名された。

しかし、議論が進むにつれこのモデルにはいくつかの問題があることがわかってきた。

  • アクセシビリティツリーを計算することは、それを変更するために必要にはならない。書き込むための AccessibleNode を取得することは、計算済のプロパティを取得することに依存してはならない
  • 計算されたアクセシビリティツリーを表示するにはブラウザ間で計算方法の標準化が必要となる
  • もし ElementAccessibleNode の計算済のプロパティを表示しない場合、このオブジェクトの「プロパティの入れ物」を超えるという目的がぼやける
  • ARIAプロパティと AccessibleNode プロパティの優先度の決定について、明確で「正しい」答えがなかった
  • 同様に、AccessibleNode を排他的に使用して関係性を表現することは分かりにくかった

これらの問題により、APIをもともと対処するつもりだったユースケースに基づいて再評価、そして簡素化することとなった。

次のステップ

アクセシビリティオブジェクトモデルの開発は、現在の主要なブラウザベンダーを代表した編集者のチームがリードしている。

GitHub上で問題を報告することができる。

https://github.com/WICG/aom/issues

インキュベーション

Web Platform Incubator Community Group (WICG)の一部としてこの仕様の開発は継続するつもりだが、時が経てば自身のコミュニティグループに開発が移るかもしれない。

このグループの活動は、ARIAなどのWeb Accessibility Initiativeの現在の活動からは全体的にほとんど独立している。

ARIAがWeb上のアクセシビリティプロパティのための構造的なマークアップとセマンティクスを定義している際には、しばしば支援技術のベンダーやネイティブプラットフォームのAPIと調整をしなければならないが、AOMは単に開発者のためにより低レベルの制御ができる並列のJavaScriptのAPIを提供し、ウェブプラットフォームのギャップを埋めるにとどまり、新しいセマンティクスを導入することは無い。

謝辞

価値のあるフィードバックや助言、ツールを提供してくれた次の方々に多大な感謝を。

  • Alex Russell
  • Anne van Kesteren
  • Bogdan Brinza
  • Chris Fleizach
  • Chris Hall
  • Cynthia Shelley
  • David Bolter
  • Domenic Denicola
  • Elliott Sprehn
  • Ian Hickson
  • Joanmarie Diggs
  • Marcos Caceres
  • Meredith Lane
  • Nan Wang
  • Robin Berjon
  • Rossen Atanassov
  • Ryosuke Niwa
  • Tess O'Connor

マイクロソフトの Bogdan Brinza と Cynthia Shelley は現在は積極的に参加していないが、この仕様の最初の草案に貢献した。

付録

背景: 支援技術とアクセシビリティツリー

この仕様の中で支援技術は、アプリケーションの既存のUIを補強したり置き換えたりする第三者のアプリケーションのことを指す。よく知られた例の一つとしてスクリーンリーダーがある。スクリーンリーダーは視覚的でポインターベースのUIを、聴覚出力(音声、およびトーン)と、キーワード、ジェスチャーの両方、またはいずれかによる入力メカニズムに置き換える。

多くの支援技術は、アクセシビリティAPIを通じてウェブページとやりとりする。たとえば、Windows上のUIAutomationや、OS XのNSAccessibilityなどがある。これらのAPIを利用するとアプリケーションのインターフェースを示すオブジェクトのツリーを公開することができる。アプリケーションウィンドウのことを示すルートノードから、個々のインタラクティブな要素まで様々なレベルのノードがグループ化されている。これは アクセシビリティツリー と呼ばれている。

支援技術がアクセシビリティツリーを使って代替インターフェースを作り、ユーザーの命令で発火したインタラクションイベントが支援技術にルーティングされ、支援技術のユーザーはほぼこのAPIを通じてアプリケーションとやりとりをする。

アプリケーションUIからアクセシビリティツリー、支援技術、ユーザーまでの流れ

代替インターフェースの 出力 (例えば、音声やトーン、点字ディスプレイの更新、スクリーンルーペのフォーカスの移動)、また 入力 (例えば、キーボードショートカット、ジェスチャー、点字ルーティングキー、スイッチ機器、音声入力など)は完全に支援技術の責務で、アプリケーションの役割ではない。

例えば、OS Xのネイティブアプリケーションを使用するVoiceOverユーザーは、「control、option、スペースバー」キーの組み合わせ押す。それはスクリーンリーダーが現在いるUI要素をクリックするということを示す。

UIの要素からアクセシビリティノード、支援技術、ユーザー、ユーザーのキー操作、アクセシビリティAPIのアクションをUI要素に戻す往復

これらのキー押下はアプリケーションに渡されることはないが、スクリーンリーダーが受け取り、accessibilityPerformPress()関数を該当のUI要素を示すアクセシビリティノード上で実行する。アプリケーションはプレスアクションを処理し、通常はクリックイベントを処理するコードにルーティングされる。

アクセシビリティAPIはテストや自動化でもよく利用される。それらは堅牢で包括的な方法でアプリケーションの状態を調査、またプロセスの外からUIを操作する方法を提供する。通常、アクセシビリティAPIの主な動機は障害を持つユーザーのための支援技術だが、これらのAPIが一般的で多くの用途があることを理解することは重要である。

アクセシビリティノードプロパティ

アクセシビリティツリーの各ノードは アクセシビリティノード と呼ばれる。

アクセシビリティノードは常に一つのセマンティックな目的を示す ロール を持つ。 これは単に他のノードを包含することを示すグループ化するロールであってもよいし、または "button" のようなインタラクティブなロールであってもよい。

アクセシビリティツリーの中のアクセシビリティノード。ロール、名前、状態とプロパティを示している。

ユーザーは支援技術を介して、様々なレベルでアクセシビリティツリーを探索することができる。かれらは、ランドマーク要素のようなユーザーのページのセクションへの移動を助けるグループ化されたノードとやり取りすることができたり、ボタンのようなインタラクティブなノードとやり取りすることができる。

それらの双方の場合において、文脈の中でのノードの役割を示すため、ノードは通常 ラベル (しばしば 名前 と呼ばれる)を必要とする。例えば、ボタンは「OK」や「メニュー」というラベルを持つ。

アクセシビリティノードはまた現在の (範囲であれば "10" だったり、テキスト入力であれば "ジェーン" であったり)のような他のプロパティを持ったり、(チェックボックスの "checked" または "focused"など)の状態の情報を持つことができる。

インタラクティブなアクセシビリティノードは、それらに対して実行される特定の アクション も持つことができる。例えば、ボタンは "press" アクションを公開するし、スライダーは "increment""decrement" アクションを公開する。

これらのプロパティとアクションはノードの セマンティクス と呼ばれる。各アクセシビリティAPIにおいて少しずつ違いはるが、概念的にはほとんど似ている。

背景: DOMツリー、アクセシビリティツリー、そしてプラットフォームのアクセシビリティAPI

ウェブはアクセシブルなアプリケーションを制作するのに多くのサポートを提供しているが、それらは 宣言的 なAPIを通じてのみである。

DOMツリーは並行して主にページの視覚的な表現とアクセシビリティツリーに変換される。アクセシビリティツリーは、一つまたは複数の プラットフォーム固有の アクセシビリティAPIを介してアクセスされる。

HTMLがDOMツリーに変換され、されに視覚的なUIとアクセシビリティツリーに変換される

異なるプラットフォーム間で複数のアクセシビリティAPIをサポートしているブラウザもあれば、特定のアクセシビリティのみをサポートしているブラウザもある。しかし、少なくとも1つのネイティブアクセシビリティAPIをサポートするブラウザはセマンティックの情報構造ツリーを公開するためのメカニズムを持っている。このAPIの目的のため、実装の詳細は気にせずそれらのAPIを参照する。

ネイティブHTMLをアクセシビリティツリーにマッピングする

ネイティブHTML要素はアクセシビリティAPIに暗黙的にマップされる。例えば <img> 要素は自動的に image ロールを持つアクセシビリティノードにマップされ、 alt 属性(があれば)によってラベリングされる。

<img>ノードが、ページ、またアクセシビリティノード上の image として変換される

ARIA

また、ARIAを利用することで開発者が属性によって要素に注釈をつけ、要素のデフォルトのロールやセマンティックプロパティを上書きすることができるが、アクセシビリティアクションは公開できない。

<div role=checkbox aria-checked=true> は視覚的なUIとDOMノードに変換される

どちらの場合も、DOMノードとアクセシビリティツリー内のノードは1対1で対応しており、対応するアクセシビリティノードのセマンティクスにたいする最小限の細やかな制御ができる。

付録: AccessibleNode の命名

仮想アクセシビリティツリー内の一つのノードを示すクラスの名前を AccessibleNode とした。

この名前を選ぶ際、簡潔さと明確さ、そして普遍性の間のバランスを取ろうとした。

  • 簡潔さ: 名前は可能な限り短くある必要がある
  • 明確さ: 名前は、分かりにくい略語や短縮形を利用せず、APIの機能を反映している必要がある
  • 普遍性: 名前は仕様の範囲を制限したり狭め過ぎたりしてはいけない

以下に、真剣に提案されたすべての名前のそれぞれの長所と短所を簡潔にまとめた。

まだ以下の名前への投票や違う名前の提案は歓迎しているが、まずは既存の提案とその懸念点を慎重に考慮してほしい。すでに大筋で合意には至っており、完璧な命名にしようとするよりはむしろ世に出すよう努めていきたい。

提案された名前 長所 短所
Aria 短い; すでにアクセシビリティと関連している ARIAは仕様の名前であり、アクセシビリティツリー内のノードの名前でないため混乱する
AriaNode 短い; すでにアクセシビリティと関連している AOMはARIA属性のみを公開することを暗示していて制限的すぎる
A11ement 短い; Element に近い 読みにくい; 数字が含まれている; 要素と必ずしも関連付けられていない; 分かりにくい
A11y 非常に短い; DOMの関連団体について主張しない 読みにくい; 数字が含まれている; 分かりにくい
Accessible 一つの完全な単語である; タイプが難しくない 名詞ではない
AccessibleNode 非常に明確; 読むのが簡単 長い; 混乱する可能性がある (他の Node はアクセシブルではない?)
AccessibleElement 非常に明確 さらに長い; 混乱する (他の Element はアクセシブルではない?)
AccessibilityNode 非常に明確 非常に長い; 初めてのときに 'accessibility' を正確にタイプできる人はこの星にいない
AccessibilityElement 非常に明確 笑えるほど長い; まだ 'accessibility' とタイプしないといけない

Appendix: Partial proposed IDL for virtual accessibility nodes

// An AccessibleNode represents a virtual accessible node.
interface AccessibleNode {
    attribute DOMString? role;
    attribute DOMString? name;

    attribute DOMString? autocomplete;
    // ... all other ARIA-equivalent attributes

    // Non-ARIA equivalent attributes necessary for virtual nodes only
    attribute DOMString? offsetLeft;
    attribute DOMString? offsetTop;
    attribute DOMString? offsetWidth;
    attribute DOMString? offsetHeight;
    attribute AccessibleNode? offsetParent;

    // Only affects accessible focus
    boolean focusable;

    // Tree walking
    readonly attribute AccessibleNode? parent;
    readonly attribute ComputedAccessibleNode? firstChild;
    readonly attribute ComputedAccessibleNode? lastChild;
    readonly attribute ComputedAccessibleNode? previousSibling;
    readonly attribute ComputedAccessibleNode? nextSibling;

    // Actions
    void focus();

    // Tree modification
    AccessibleNode insertBefore(AccessibleNode node, Node? child);
    AccessibleNode appendChild(AccessibleNode node);
    AccessibleNode replaceChild(AccessibleNode node, AccessibleNode child);
    AccessibleNode removeChild(AccessibleNode child);
};
partial interface Element {
  AccessibleNode attachAccessibleRoot();
}

Appendix: partial proposed IDL for ComputedAccessibleNode

interface ComputedAccessibleNode {
    // Same set of attributes as AccessibleNode, but read-only
    readonly attribute DOMString? role;
    readonly attribute DOMString? name;

    readonly attribute DOMString? autocomplete;
    // ... all other ARIA-equivalent attributes

    // Non-ARIA equivalent attributes
    readonly attribute DOMString? offsetLeft;
    readonly attribute DOMString? offsetTop;
    readonly attribute DOMString? offsetWidth;
    readonly attribute DOMString? offsetHeight;
    readonly attribute AccessibleNode? offsetParent;
    readonly boolean focusable;

    readonly attribute AccessibleNode? parent;
    readonly attribute ComputedAccessibleNode? firstChild;
    readonly attribute ComputedAccessibleNode? lastChild;
    readonly attribute ComputedAccessibleNode? previousSibling;
    readonly attribute ComputedAccessibleNode? nextSibling;
};
partial interface Window {
  [NewObject] ComputedAccessibleNode getComputedAccessibleNode(Element el);
}