ハッピーホリデー!id:cockscombです。この記事ははてなエンジニアAdvent Calendarの8日目のエントリです。
今年1月、はてなスターのリニューアルを行いました。リニューアルの内容は告知をご参照ください。
はてなスターのリニューアルでは、クロスオリジンの問題を解決するために特別な実装をしています。今回は、ホリデーシーズンをお祝いして、そのひみつを詳 らかにします。
はてなスターとクロスオリジン
はてなスターは、はてなブログなどに埋め込んで利用されます。はてなブログは hatenablog.com
や hatenadiary.jp
などのサブドメインを利用しており、さらにはてなブログProでは独自のドメインを設定できます。 はてなスターは複数の異なるドメイン名のサイトから利用される、ということです。
要するにはてなスターはクロスオリジンで利用されます。一方ではてなスターをつけるには資格情報が必要なので、何か少し難しい仕組みが必要です。このような難しさは今年10月にも話題になりましたね。
はてなスターとiframe
はてなスターでは、s.hatena.ne.jp
のAPIを呼び出すため、s.hatena.ne.jp
のページをiframeで埋め込んでおり、クロスオリジンを回避しています。ただし、スターが横に伸びていくというレイアウトの都合から、スターの表示はiframeの外側で行なっています。iframe内にはスターの追加を行うスターボタンがあり、APIとの通信はiframeの内部で行います。
iframeの内外では MessageChannel
を介して postMessage()
でコミュニケーションします。Window
の postMessage()
を使わずに MessageChannel
を利用するのは、ドキュメント中に複数のはてなスターが埋め込まれるときに、混線させないためです。
また postMessage()
はComlinkを使って抽象化しています。
はてなスターとサードパーティCookie
前述のようにはてなスターではiframeを使っていますが、iframe内はいわゆるサードパーティCookieを利用するような状態になります。サードパーティCookieはページ訪問者の詳細なトラッキングに利用される場合があり、近年ではブラウザのプライバシー保護機能によって単純には利用できなくなってきています。
リニューアル以前のはてなスターは、Safariなどでプライバシー保護機能を有効にしていると正常に動作しない問題がありました。リニューアル後のはてなスターは、利用者のプライバシーと利便性を両立するため、Storage Access APIを使用しています。
Storage Access APIは、利用者がクリックなどの明示的な操作を行なった際に(場合によってはさらにダイアログを表示し)、特別にサードパーティCookieなどを利用できる権限を得る、というものです。はてなスターでは、スターボタンが押下された際に権限を要求します。スターボタンを押下したということは、利用者が明確にはてなスターを利用する意思を持っているということなので、ちょうどいいタイミングのはずです。
Storage Access APIはもともとSafariがIntelligent Tracking Protection (ITP)の導入を進める中で提案していたものですが、最近ではFirefoxやEdgeにも実装されています。
はてなスターと状態の同期
はてなスターには、カラースターパネルというウインドウがあります。カラースターの追加やスターの削除を行うためのインターフェースです。カラースターパネルでスターが追加されたり削除されたりした際に、それがちゃんと元のページに反映されないと、変な感じになってしまいます。
はてなスターでは次の図のように、スターボタンの内部状態が元のページに反映され、スターボタンとカラースターパネルは内部状態が同期している、というふうになっています。
この状態の同期をどうするとうまく実現できるでしょうか。ウインドウやiframeの間のやりとりは、全て postMessage()
を介して行なっています。postMessage()
では構造化複製アルゴリズムでコピーできるオブジェクトだけをやりとりできます。
はてなスターでは、状態を管理するのにReduxを使うことにしました。ReduxのStoreはウインドウ単位で保持し、Actionをウインドウ間で複製することにしました。Actionはピュアなオブジェクトとして表すのが推奨されているため、ふつうは構造化複製アルゴリズムでコピーできます。原理的には、Reducerの実装が同じであれば、Actionを複製すると、Storeの状態を同期できるはずです(厳密にはActionの順序まで一致する必要があります)。実装上は、Reduxにミドルウェアを追加して、Storeが受け取ったActionを異なるウインドウにレプリケーションしています。
この仕組みで、スターボタンとカラースターパネルの間では完全に状態が同期されるようにして、スターボタンと元のページでは最低限の状態を同期しています。元のページでは第三者のスクリプトが実行されているので、ページのオーナーが知っていい情報しか持たせない、という意図です。
はてなスターのまとめ
はてなスターのリニューアルでは、ここまで紹介してきたテクニックを使って、クロスオリジンでもうまく動作するようにしました。セキュリティやプライバシーが侵害されず、また利便性も損なわないように、なるべく注意を払っています。今回紹介した以外にも、表示が少しでも早くなるように(ページの表示に悪影響を与えないように)実行タイミングを調整したり、軽量化のためにReactではなくpreactを使うなど、工夫をしています。
ブラウザには継続的にセキュリティやプライバシーのための改善が行われており、その中にはiframeのサンドボックス化やサードパーティCookieの制限があります。一方で、はてなスターのようにクロスオリジンで機能するサービスのために、ウインドウ間メッセージングやStorage Access APIが用意されています。はてなでは引き続き、こういった仕組みに適応して、サービスの改善に努めてまいります。
【PR】はてなは、ブラウザが大好きな仲間を募集しています。
お疲れさまでスター!
id:cockscomb
加藤尋樹。アプリケーションエンジニア。iOSを中心としたスマートフォンアプリの開発からWebアプリケーションまで幅広く手掛ける。サマーインターンを経て2012年11月入社。現在は組織・基盤開発本部サービスプラットフォームチームに所属し、技術グループでチーフエンジニアを兼務。共著に『iOS 12 Programming』(2018年、PEAKS)など。
Twitter: @cockscomb
GitHub: cockscomb
blog: cockscomblog?