アクセシブルじゃないクリックイベントを発見する

2 min read

次のスクリプトをDevToolsのコンソールから実行してください。

(() => {
  "use strict";

  const elements = Array.from(document.querySelectorAll("*"));

  const clickEvents = elements
    .map((element) => {
      const listeners = getEventListeners(element);
      const clickListeners = listeners.click || [];
      clickListeners.forEach((event) => (event.owner = element));
      return clickListeners;
    })
    .flat();

  for (const event of clickEvents) {
    if (event.owner.matches("button, a[href]")) {
      continue;
    }

    event.owner.style.outline = "3px solid red";
    event.owner.classList.add("I_AM_NOT_ACCESSIBLE");
  }
})();

アクセシビリティチェック、そしてデバッグ用のスクリプトです。アクセシブルじゃないクリックイベントを持った要素を探すことができます。 見た目では赤い罫線を要素に強制的につけます。また、I_AM_NOT_ACCESSIBLEというクラスも付与するので、視覚に頼らないで開発している方はそれを参照してください。

Google Chromeでしか動作確認をしていません。Google ChromeのDevToolsから実行することを想定したスクリプトです。

なぜこのスクリプトを書いたのか

button要素以外にクリックイベントを実装するのはリスクがあります。要素がフォーカスを受け取らないために、キーボードに操作が不可能になります。そして(たとばtabindex属性で)フォーカスを受け取ることができたとしても、エンターキーによるイベントの発火が起こりません。button要素であればエンターキーでもクリックイベントが発火します。

また、例外としてhref属性付きのa要素は除外しています。おそよSPAやウェブアプリケーションではルーティングの実装でクリックイベントが定義されるからです。なぜhref属性付きでないものは除外しないのかというと、href属性がないとa要素はフォーカスを受け取らないからです。href属性なしでルーティング処理を持っていたとしても、JavaScriptオフ環境でリンクとしてフォールバックできない問題を孕んでいるからです。まあこれは考えすぎかもわからないですが、とにかくフォーカスを受け取らないのはクリティカルな問題です。

この問題は初歩的ながらわりとやりがちな問題です。ただAxeに検出機能がなく気軽に見つける方法を探した結果、この方法に辿り着きました。

なぜブックマークレットやChrome拡張にしないのか

ブックマークレットは無理でした。DevToolsのコンソールでしか実行できないgetEventListenersという特別な関数を利用しているので、ブックマークレットからは呼び出せませんでした。

Chrome拡張は、もしgetEventListenersが呼び出せるなら、そのうち作って見るかもしれません。

自動テストならacotを使おう

このスクリプトは開発中にブラウザで実行することを目的にしていますが、同様のことを自動テストで知りたい方も多いと思います。その場合はacotを使いましょう。acotでは既に同じ問題を発見できるinteractive-supports-focusというルールが備わっています。これ機に導入してはいかがでしょうか?