Web フロントセクションの星野と申します。ドワンゴの教育事業には 1 年ほど前から参画しております。 数ヶ月前に「マウスの戻る・進むボタンをリンクの上で使用するとナビゲーションが起きてしまう」という事象の調査をおこないました。 本記事では調査の結果、わかった原因と解決方法、および調査の内容についてまとめてみました。
何が起こっていたのか
Web 版 ZEN Study をご利用いただいている方から以下のようなお問い合わせをいただきました。
マウスの戻る・進むボタンをリンクの上で使用すると、戻る・進むではなくリンク先に遷移してしまう
実際に確認したところ、確かに上記の通りの挙動となっていることが分かりました。この挙動は開発チームの意図したものではありませんでした。(現在は解消しております)
ZEN Study 上には、受講中のコースや教材の選択画面や、提出を要するレポートの確認画面など多くの画面があります。そのためリンク(react-router の Link)の数もそれなりにあり、画面の多くの面積をリンクが占めていると言えます。 また、戻る・進むボタンは「マウスから手を離さず、カーソルも動かさず、ブラウザのナビゲーションを実行できる」という点で便利であるため、この事象はユーザ体験を損なうものであると感じられました。
原因と解決方法
色々調べた結果、上記の事象は Google Tag Manager (以下、GTM)が挿入するスクリプトによって引き起こされていたことが分かりました。具体的には次のようなことが起こっていました。
- gtm.js 内で
clickauxclickイベントを document で listen している- マウスの戻る・進むボタンを押すと
auxclickイベントが発生することがある(デバイス・OS・ブラウザの組み合わせによって挙動が異なる)
- マウスの戻る・進むボタンを押すと
- 上記のハンドラ内では、GTM 上の設定で以下の条件が成立する場合に、イベントが発生した a タグの
href属性の値でwindow.location.hrefを変更する処理が実行される- 「リンクのみ」のトリガーがある
- 上のトリガーの 1 つ以上で「タグの配信を待つ」が設定されている
GTM のヘルプ内コミュニティ投稿にも類似の事象に関する投稿があり、自分の方で gtm.js の内容と照合し、原因として確からしいことを確認しています。 その上で、ZEN Study に関連するアナリティクスを専門で担当しているチームに協力を依頼し、上記の条件が成立しなくなるように GTM のトリガー設定を調整することで事象が解決することを確認しています。 (具体的には「タグの配信を待つ」が設定されているトリガーが無い状態にしました)
今回の場合は幸いなことに、該当するトリガー設定の調整が可能だったので、本番環境にも設定調整を適用していただき、事象を解決できました。
解決に至るまでにやったこと
原因と対処は上記の通りあっさりしたものですが、事象から原因にたどり着くまでに試行錯誤がありました。 ここからはその試行錯誤の内容について書いていきたいと思います。
マウスの戻る・進むボタンで発生するイベントを特定する
お問い合わせいただいた内容から「マウスの戻る・進むボタンだけ、何か特殊なイベントが発生するのではないか?」という予想を立て、検証をおこないました。 検証には、先に Solid.js の空プロジェクトを立てたうえで、Claude Code に次のプロンプトで実装してもらったアプリケーションを利用しました。(Solid.js は自分の趣味で個人的に使っているものです)
マウスの特殊ボタンを押したときにどのようなイベントが発生するかを検証したいと考えています。 次の機能を `src/ClickTest.tsx` にコンポーネントとして実装し、`src/App.tsx` へ統合してください。 - ClickTestArea コンポーネント - 640x480 px の領域を持つ - この領域内で起こったクリックイベントを捕捉し、コンソールに出力する - AddHistoryButton コンポーネント - クリックすると pushState を用いてヒストリーを追加する
通常のナビゲーションが発生することで JavaScript 実行環境が終了してしまうと各種イベントを観測できない可能性があると考え、ヒストリーの追加には pushState を使用しています。
この時点で 1 つ発見がありました。Claude Code に作ってもらった実装には次のイベントハンドリングが含まれていました。
- click
- contextmenu
- auxclick
click contextmenu には見覚えがありましたが、auxclick はここで初めて知りました。
詳しい情報は MDN の記事を参照していただければと思いますが、おおまかには auxclick はマウスの第 1 ボタン以外のボタンでクリック操作をした場合に発生するイベントです。
たまにこのような自分の頭の中にないものが出力されるのは、AI 系のコーディングツールのおもしろいところだな、と思いました。
作ってもらった検証用アプリケーションを用いて確認したところ、ブラウザ・OS・デバイスの組み合わせで挙動に違いがあったものの、マウスの進む・戻るボタンを押した際に auxclick イベントが発生する環境があることがわかりました。
そして、その環境においてお問い合わせいただいた事象が発生する、ということが分かりました。
(これは余談ですが、MacOS x Chrome の組み合わせで事象の再現が取れず困っていたところ、自作キーボード(with QMK Firmware)のマウスキー機能を使って事象の再現確認を取ることができました。自作キーボードはいいぞ。)
auxclick イベントを捕捉している主体を見つける
auxclick が事象の発生に関連している可能性が高いことが分かったので、このイベントを捕捉している実装を探します。
これが、Web 版 ZEN Study 特有の状況により、思ったよりも大変でした。
Chrome Dev Tools に備わっているイベントリスナーを一覧できる機能(Elements > Event Listeners タブで利用できます)を用いると、親要素まで含めて listen されているイベントとハンドラの詳細を閲覧できます。この中に auxclick も含まれていました。
内容を見てみると、ハンドラの発生源が ZEN Study の実装である、と出ていました。ところが、全文検索してみても auxclick をハンドルしているコードが見つかりません。
ライブラリから発生している可能性を疑い、node_modules 内も全文検索してみましたが、それでも出てきません。
この「ハンドラの発生源が自分たちが書いたコードかのように見えてしまう」件の原因は調査の後になってわかりました。
ZEN Study では障害の調査などの目的で Sentry というサービスを利用しています。
Sentry の Web フロントエンド向け統合が組み込まれると、イベントハンドラをラップする処理が追加されるようでした。
このラップする処理はメインバンドルに含まれているため、結果としてハンドラの発生源が自分たちが書いたコードかのように見えてしまう状態となっていました。
(なおこの場合、ハンドラ内の __sentry_original__ というフィールドに格納されている関数が本来のイベントハンドラを指しています。そのため、あとはその関数を右クリックし Show function definition メニューを選ぶことでハンドラの発生源を調べることができます)
ここから様々に迷走してしまったのですが、その過程で、この事象が本番環境でしか発生しないことが分かりました。
このことから「本番環境だけで読み込まれているスクリプトに原因があるのではないか」という予測を立てました。
そこで、実際に本番環境で配信されている ZEN Study で script タグにより読み込まれているスクリプトを 1 件ずつ読み込みブロックしてみる、という検証をおこないました。具体的には Chrome Dev Tools > Network タブで該当の GET リクエストを探し、右クリックメニューから Block request domain を選択してブロックをおこないました。(URL の表記揺れでブロックをすり抜けることを懸念しドメイン単位でブロックしましたが、Block request URL した後にパターン編集する、としても良いと思います)
この検証の結果、gtm.js をブロックした場合に事象が発生しなくなることがわかり、実際に配信されている gtm.js の内容を全文検索すると auxclick の記述が見つかりました。
gtm.js で何がおこなわれているのかを深堀りする
gtm.js 内に auxclick の記述が見つかったので、内容を調べていくことにしました。
gtm.js は minify が掛かった状態で配信されているため、そのままの状態で読み解くことは困難です。
このときの自分は以下のようにして解読を進めました。
- とりあえず prettier でフォーマットを掛ける
- 圧縮できない文字列で検索する
- 例えば
auxclickという文字列はイベントハンドラの登録に使用するので、変数宣言で別名を付けることはできても、実装中のどこかには記載しないといけない - 別名が付いていることが特定できたら、エディタ支援を駆使して参照を辿ることができる
- 例えば
今振り返ってみると、やれば良かったなと思ったこともあります。 また機会があれば実践していきたいと考えています。
- 正体が分かった変数を rename する (手動 minify 解除)
- 原因らしき関数などをある程度特定できたら、その解説や minify 解除を AI コーディングツールに依頼してみる
ここまでで、ひとまず auxclick イベントを確かに listen しており、a タグから発生したイベントを観測したときにそのタグの href 属性で window.location.href を操作していることまでは分かりました。
ただ、その処理の前に条件分岐がいくつかあり、それらの正体がまだつかめていませんでした。
なんとか情報を得たいと思い、gtm.js 内の参照を辿る中で見つけた文字列定数でのウェブ検索を併用して調査を進めました。
gtm autoEventsSettings で検索したとき、記事の上の方でも紹介しました類似事象が記載されたヘルプコミュニティへの投稿が見つかりました。
この投稿に書かれた情報を再確認する方針で解読を進めることで、事象を回避する GTM 上のトリガー設定を見出すことができました。
おわりに
今回、なんとか原因特定および事象の解決までたどり着くことができましたが、多くのもの・人に助けられてようやくたどり着けたと感じています。 具体的には、以下のどれかが欠けていたら、そこで諦めていたかもしれないと思っています。
- Claude Code が
auxclickを扱う検証用コードを出力した - Chrome Dev Tools を用いてスクリプト 1 つずつの単位で読み込みブロックができた
- ツールやエディタ支援を受けることで minify 済みのコードでもある程度解読ができた
- GTM のヘルプフォーラムに同種の事象を投稿してくださっていた方がいた
- GTM 上での検証が必要なときにアナリティクス専門チームの助力を得られ、設定調整もしていただけた
ツールの進化によってこの手の調査はかなり楽になっている、と感じました。その中に AI 系のツールも入っているのも印象的でした。
また、本事象の解決にあたり、アナリティクス専門チームの皆様には大変お世話になりました。 ほとんど初めてのコミュニケーションだったにも関わらず、迅速かつ優しくご対応いただき、とても動きやすかったと感じています。この場を借りて感謝を記します。ありがとうございました!
