2017年4月5日水曜日

Chrome extentionを使い、iframe内外でデータ通信

◆ 目的
Webページ(親ページ)とそこからiframeで読み込んだ子ページ間でデータの授受を行う。
ただし、iframe内のコンテンツは親ページのドメイン外に存在する任意のサイトとする。



◆ 課題
解決する課題は2つある。
1. クロスドメイン制約への対応
XMLHttpRequestを投げる際などにも必ず気に掛ける点だろう。
Same Origin Policy(同一生成元ポリシー)を回避しなければならない。

今回は任意の外部サイトをiframeに取り込むことを前提としているため、
CORS(Cross-Origin Resource Sharing)もJSONP(JSON with padding)も今回は使えない。

2. iframe内での読み込み不許可ヘッダへの対応
HTTPのレスポンスヘッダに、iframe内からWebページが読み込まれるのを防止するオプション(X-Frame-Options)が付与されていればiframe内で表示はできない。



◆ 解決策
1. postMessageの利用
クロスドメイン制約への対応として、HTML5で用意されたpostMessageの利用を思いつく。

(親サイト)
<iframe id="ifrm" src="外部サイト"></iframe>

<script type="text/javascript">
window.onload = function() {
  var ifrm = document.getElementById('ifrm').contentWindow
  ifrm.postMessage("hello", '外部サイト')
};
</script>

(外部サイト)
<script type="text/javascript">
window.addEventListener('message', function(event) {
    alert(event.data)
}, false);
</script>

しかし、Chromeが許可しない。
※Safariでは警告なく、実施できた。

(エラー例)
Failed to execute 'postMessage' on 'DOMWindow': The target origin provided ('http://親サイト') does not match the recipient window's origin ('http://外部サイト').


2. iframeを使わない
iframeを使う前提を変え、コンテンツをダウンロードし、外部サイトを親側で再現させる。つまり、前提を変える。
が、しかし、JavaScriptが生成する動的ページのリソースの管理は困難があるため、やはり、iframeは使いたい。前提は戻す。


3. Google Chromeの拡張機能の利用
Chromeに限定されるが、Chromeの拡張機能を使えば対応ができそうである。
データの共有は、バックグラウンドで動作させるスクリプト内でセッションストレージを使えばいいだろう。
また、ヘッダの書き換えも実施できる。



◆ 拡張コード概要
chrome拡張を利用することにした。
拡張コードを有効にするには、最低限以下の3つのファイルを用意し、それらを適当なフォルダに入れ、Chromeブラウザの拡張機能からインポートすれば良い。

○ マニフェスト ファイル
拡張機能に関する情報を与える。

○ コンテンツ スクリプト
ブラウザで表示させるページで読み込むjsとは別空間で実行させるjsである。
このファイルは親サイトと、iframe内の外部サイト、両方に読み込まれる。
空間は分かれているため、コンテンツスクリプト内で利用しているjQueryなどのライブラリがサイトで利用しているバージョンと異なっていても問題は起きない。

○ バックグラウンド スクリプト
Chromeのバックエンド側で処理させるjsである。
表示コンテンツには取り込まれないが、コンテント ファイルとの間でメッセージ通信ができる。


簡易図で表すと下記のような感じである。
   parent
+----------+
|          |← contentScripts.js
|  iframe  |                       
|  +----+  |
|  |    |  |
|  |    |←-|-- contentScripts.js
|  +----+  |
|          |
+----------+
background.js

contentScripts.js、background.jsの名称はmanifest.json内で指定する。



◆ 試験
1. iframe内でマウスを操作。マウスオーバしたタグ要素に色がつくようにしている。
  そのタグ要素でクリックすると、タグ名がセッションストレージへ保存される。

2. 親側でiframe外の要素をクリックする。
  iframeで取得した要素がalert表示されれば成功である。



◆ コード例
○ manifest.json


○ contentScripts.js


○ background.js



◆ コード解説
1点、解説を加えておく。
contentScript.jsは親子両方に読み込まれるため、共通処理以外のjsコードは、親子用で条件を加えている。
// iframe用
if (window != parent) {
  ~snip~
} // 親用
else{
  ~snip~
}