Rails4 の Turbolinks について最低限知っておきたいこととその他

Rails4 で Turbolinks という仕組みが導入されることは比較的広く知られつつあります。

これは画面遷移の高速化を目的としたもので、

  1. リンクのクリックを全部乗っ取る
  2. 同一ドメイン内での遷移だったら Ajax リクエストをして body 内をまるっと書き換える
  3. history.pushState して状態を保存

という (少々いかがわしい) 動作をするものです。なお history.pushState が使えない環境では普通のリンクとして動作します。 Rails 3.2 環境などでも Gemfile に

gem 'turbolinks'

などと書いておけば使うことができます。気軽に画面遷移が高速化できるのですが、最低限以下の一項目だけは知っておく必要があります。

同一ドメインにオープンリダイレクタが存在する場合かつユーザーコンテンツが作成可能な場合 XSS が発生する

同一ドメインにオープンリダイレクタが存在し、そのオープンリダイレクタ経由で別ドメインのサイトに飛ぶリンクがある場合、Turbolinks は特定の条件の下それを同一ドメインへのリンクだと思い込み、外部の別ドメインのサイトを平然と表示します。

ユーザーがコンテンツを作成できるサイトの場合、ユーザーがサイト内にそういうリンクを組み込んでしまう可能性があります。

まとめると、オープンリダイレクタ (redirect_to params[:url] とかそのようなもの) がある CGM サイトの場合 Turbolinks を使用してはいけません。

XHR ではリダイレクトかどうかなどを判別する手段がないので、これはもうどうにもなりません。

オープンリダイレクタがあるサイトでは Turbolinks の使用はやめましょう。さらにオープンリダイレクタは潰すべきです。

その他いくつかのこと

それ以外にも注意する必要があることを書きます。

JavaScript について

Ajax で body を書き換えるという設計上、ページを読み込んだときに当然 jQuery.ready が発火しません。ページ更新時に発火する page:change イベントというのがあるのでこれを代用するのがよいです。

この辺りのことを自動でやってくれる jquery.turbolinks というライブラリがあるのですが、正直あまりおすすめできません。理由は以下の通りです。

  • ページ更新毎に実行したい処理と、アプリケーションとして一回実行したい処理が分けられない

アプリケーションで jQuery.ready の中で実行したい処理は、要素へのイベントのバインドなどページ読み込み毎に実行すべきものと、なんらかのループなどアプリケーションとして一回実行しておけばよいものに分けられると思います。

そういう問題を解決するために jQuery.ready の中は一回実行すればよいものを書いて、イベントは jQuery.ready の外で $(document).on() などでイベントを live するという手法を勧める記事が多いのですが、大量にイベントを live するのはパフォーマンス上の問題になります (本当にアッサリと重くなります)。

ゆえに以下のようにするのがよいと思います (CoffeeScript で例示します)。

ready = ->
  "ページ読み込みのタイミングで実行したい処理"
$(document).ready(ready)
$(document).on('page:change', ready)

$ ->
  "アプリケーションとして一度だけ実行すればよい処理"

こういう書き方の方が単純になると思います。Turboklinks では複数のページが一つのページ、一つのアプリケーションになるため、上記のような考え方の導入が必要と思います。

HTML の書き換えについて

先述の通り、Turbolinks では現在のところ書き換えをしてくれるのは body の中身だけです。title 要素を除いて head の中身の面倒は見てくれないので、それは page:change 内での処理などで自分でなんとかする必要があります。

ただし head の中身も処理してくれる pull request がきているため、もしこれがマージされればそのあたりは気にする必要はなくなります (されるかは分かりません)。

外部サイトへのリンクについて

Twitter などの OAuth 認証など、外部サイトへのリンクの場合、Turbolinks が動いてはいけないリンクの場合、リンクに data-no-turbolink 属性をつける必要があります。外部サイトへのリンクは先述の XSS の問題などに関連して高い確率で Turbolinks ではなく通常遷移として動作しますが、万一 Turbolinks での遷移になってしまうとおかしなことになりますので、このような対応が必要です。

Turbolinks 雑感

はっきりいって非常に邪悪な高速化手法です。ユーザーの負担は巨大です。これが標準になるのは狂気の沙汰です。オフにした方が幸せになれることが多いと思います。

ですが、比較的 「シンプルな」 JavaScript しか要さないサービスとか、最初からこれを使うことを前提に設計していたサービスなどでは軽い負担で非常に大きな高速化効果を得られると思います。リスクとメリットをきちんと計算して使用しましょう。