フロントエンドを考えるということ
みなさんこんにちは。NHN Japanでエンジニアをやってますmalaです。livedoor Readerを作ったりJavaScriptを使ったUI(User Interface ) 、非同期処理、Webアプリケーションセキュリティ周りの仕事をしています。
この連載ではフロントエンド[1] を語るうえでは外せないJavaScriptを中心に、その周辺のUI・Webアプリケーションセキュリティを扱います。そして、ただ理想のフロントエンドを考えるだけでなく、具体的な実装を提案していくこと。これが本連載最大の目的です。
第1回となる今回は、広告やブログパーツ、ウィジェットなど、外部ドメインに貼り付けられるJavaScriptを書くうえでの作法について取り上げます。今まであまり取り上げられなかったテーマですが、フロントエンドを考えるうえで非常に重要です。JavaScriptを提供する側として、最適なものは何か、考えていきましょう。
Webアプリケーションを書く際のJavaScript
まずはブラウザ向け、特にWebサイトやWebアプリケーション向けにJavaScriptを書く際の、筆者が好むスタイルについて紹介します。この節で取り上げるのは外部サイトに貼り付けるJavaScriptとは違い、サイト内で完結したJavaScriptのことです。外部サイト向けJavaScriptの前提知識も含まれますので、ここでしっかり確認しましょう。考えなければならないのは次の2つです。
ブラウザの差異を吸収する
ビルトインオブジェクトを拡張する
ブラウザの差異を吸収する
JavaScriptは柔軟な言語ですので、古いブラウザでサポートされていない機能をJavaScript自身に実装したり、細かい非互換を修正できます。
「古いブラウザで動かないので……」といってモダンな機能を避けるより、まずは互換レイヤを形成し、そのうえでコードを書いたりライブラリを作ることで、いちいちブラウザの差異を気にせずメインのロジックに集中できます。このようなコードのことをPolyfill と呼びます。今は未サポートの機能でも、将来的にブラウザによるネイティブ実装がサポートされたときには、より高速に動作することが期待できます。
たとえばボタンクリック時などの動作を定義するには、Internet Explorer(以下IE) 6~8ではattachEvent、その他のブラウザではaddEventListenerというメソッドを使って行います。どちらのブラウザでも動くように「addEvent」という名前でラッパを書く、ということがよく行われていました[2] 。しかしIE9ではaddEventListenerをサポートしたため、IE9以上をサポートするのであればこのようなラッパを書く必要がなくなりました。ブラウザ間の互換性が高まり、古いブラウザのために書かれたラッパが必要なくなっても、独自の方言「addEvent」を使ったコードは残り続けます。残念ながら古いブラウザでは、そもそもブラウザ組み込みの関数の挙動を変更することが困難であることも多いのですが、可能ならば将来的にサポートされる標準化されたメソッドを使えるよう、ラッパよりもPolyfillを使う/作ることを心がけると良いでしょう[3] 。
[2] Polyfillが「そのブラウザではまだサポートしてない機能を、別の機能を組み合わせて実現する」コードなのに対して、ラッパはブラウザごとに実装が異なる場合、どちらもサポートできるように差異を覆い隠すのに使います。
ビルトインオブジェクトを拡張する
String.prototype、Array.prototypeやFunction.prototypeを拡張することで、JavaScriptをよりパワフルな言語にできます。
ただし、Object.prototypeについては副作用が大きいため、なるべく拡張しないほうが良いでしょう。Object.prototypeを拡張した場合、すべてのオブジェクトに対してメソッドが追加されることになります。for in object構文で列挙されるプロパティの一覧に、Object.prototype経由で追加されたプロパティも含まれてしまいます。hasOwnPropertyメソッドを使うことで、そのオブジェクト自身のプロパティなのか、prototype経由で提供されるプロパティなのかを区別することも可能ですが、多くの既存ライブラリはObject.prototypeが拡張されていることを前提としていないため、不具合が起きてしまいます。Object.defineProperty を使うことでfor inobject構文で列挙されないプロパティを定義することも可能ですが、この場合は動作対象がECMAScript 5に対応しているブラウザに限られます。
広く公開されているライブラリは、ビルトインオブジェクトのprototypeを拡張する行為が「行儀が悪い」部類に入りますので、あまり目にする機会はないかもしれません。しかし、Webサイト、Webアプリケーションでは、どれだけ好き勝手にJavaScriptを改造しても影響が及ぶのはそのサイト内だけです。このあたりのポリシーは人によって異なるでしょうが、フレームワークやアプリケーション自体のコードでは、ビルトインオブジェクトも含めて自由に拡張し、積極的に新しいコーディングスタイルを模索するのも悪くない選択肢だと思います。
外部サイトに貼り付ける際のJavaScriptの考え方
それでは今回のメインテーマ、外部サイトに貼り付けてもらうためのJavaScriptを考えていきましょう。
これまで見てきたように、Webアプリケーション用のJavaScriptは、有用なライブラリを使って「オレオレ言語」に育て上げることが可能でした。しかし外部サイト向けのJavaScriptでは、ほかのライブラリとの干渉を避けるため、なるべく「素のJavaScript」で構成する必要があります。
一定以上コードが複雑になってしまう場合、ライブラリをローカル変数として読み込んで使うのもありかもしれませんが、その場合も利用するライブラリがグローバル変数を汚染しない、ビルトインオブジェクトを拡張しないことを確認したうえで利用する必要があります。ほかに読み込まれるスクリプトの中で、機能がサポートされているか/いないかでブラウザのバージョン判定が行われている可能性もあります。そのためPolyfillの類も使わないほうが望ましいでしょう。
外部JavaScriptの危険性
外部サイトに対してJavaScriptを直接埋め込むのは、本来、そのJavaScriptを書いた本人に、たいへん大きな責任が伴う行為です。外部JavaScriptを貼り付ける(直接読み込む)ことの一番のリスクは、Webサイト内のファイルが勝手に更新されてしまうことです。
次の場合、そのJavaScriptを読み込んでいるすべてのドメインが悪影響を受けます。
JavaScriptを提供する事業者に悪意がある場合
悪意がなくてもドメインを乗っ取られた場合
悪意のあるスクリプトに差し替えられた場合
提供するスクリプト自体に脆弱性=セキュリティホールがあった場合
JavaScriptをホスティングしていたドメインの期限切れで第三者にドメインを乗っ取られた場合
ドメイン管理者のアカウントが乗っ取られた場合
DNS(Domain Name System )キャッシュポイズニングや、ユーザの使用しているDNSサーバを書き換えるようなマルウェアが仕込まれている場合
外部JavaScriptのメリット
危険性もありますが、外部JavaScriptを直接読み込むことによるメリットも、もちろんあります。それは、配布されているJavaScript自体にバグがあった場合、配布元で修正することで、即座にバグを防ぐことができる点です。一度配布されたJavaScriptをこっそり修正することは困難です。それが脆弱性であった場合、脆弱性が修正されたことを告知するためには、悪用方法・攻撃方法も同時に公開されてしまうことになります。
バグや悪用方法の詳細については触れずにセキュリティアップデートとしてJavaScriptライブラリの更新を呼びかける事例もありますが[4] 、ソースが公開されている以上、悪用方法を隠蔽(いんぺい)するのは困難です。
外部サイト向けのポリシー
外部サイトにJavaScriptを貼り付ける際のメリット・デメリットがわかったところで、具体的に外部サイト向けのポリシーを考えていきましょう。外部サイト向けのJavaScript、とりわけ広告、ブログパーツ、ウィジェット、シェアボタン[5] などを作る場合、次のようなポリシーが必要になるでしょう。
本当にJavaScriptを使う必要があるのか検討する
サポートブラウザはやや広めに。古いブラウザでもなるべく動作するようにする
エラーメッセージやデバッグ用のメッセージを出さないようにする
設置する側(外部サイト側)から見て、何を行っているのか明確にわかるようにする
グローバル変数をまったく使用しないか、特定のネームスペースのみを使う
ライブラリをグローバル変数としてインポートしない
ビルトインオブジェクトの拡張を行わない
ブラウザ設定によってはJavaScriptエラーがダイアログで通知されることに注意する
最初に、そもそも本当にJavaScriptを使う必要があるのかどうかを検討しましょう。先ほども少し触れましたが、外部サイトに対してJavaScriptを直接埋め込むのは、コードを書いた本人に、たいへん大きな責任が伴う行為です。JavaScriptを使わず、画像や後述するiframeだけで実現できるのであれば、それに越したことはありません。
このポリシーに則って、具体的な実装を見ていきましょう。外部サイトにJavaScriptを貼り付ける戦略を、速度・セキュリティ・プライバシーの3方面から模索します。