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は使いたい。前提は戻す。
が、しかし、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コードは、親子用で条件を加えている。
○ manifest.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{ | |
"name": "TEST", | |
"manifest_version": 2, | |
"version": "1.3", | |
"description": "test", | |
"permissions": [ | |
"tabs", | |
"storage", | |
"webRequest", "webRequestBlocking", | |
"*://*/*" | |
], | |
"background": { | |
"scripts": [ "background.js" ], | |
"persistent": true | |
}, | |
"content_scripts": [{ | |
"matches": [ "*://*/*"], | |
"all_frames": true, | |
"css": ["common.css", "jquery/jquery-ui-edit.css"], | |
"js": [ "jquery/jquery.js", "jquery/jquery-ui.min.js", "contentScripts.js" ] | |
}], | |
"web_accessible_resources": [ | |
"jquery/images/*.png" | |
] | |
} |
○ contentScripts.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// iframe | |
if (window != parent) { | |
var target_tag = 'address, article, aside, blockquote, canvas, dd, div, dl, fieldset, figcaption, figure, footer, form, h1, h2, h3, h4, h5, h6, header, hgroup, hr, li, main, nav, noscript, ol, output, p, pre, section, table, tfoot, ul, video' | |
var elements = document.querySelectorAll(target_tag) | |
for (var i = 0; i < elements.length; i++) { | |
elements[i].addEventListener('mouseover', function(event) { | |
if (event.target == this) { | |
this.classList.add('iframe_hover') | |
}else{ | |
this.classList.remove('iframe_hover') | |
} | |
}) | |
elements[i].addEventListener('mouseleave', function(event) { | |
if (event.target == this) { | |
this.classList.remove('iframe_hover') | |
} | |
}) | |
elements[i].addEventListener('click', function(event) { | |
if (event.target == this) { | |
setItem("key", event.target.tagName) | |
} | |
}) | |
} | |
} else{ | |
document.addEventListener('click', function(event) { | |
getItem("key") | |
}) | |
} | |
function setItem(key, value) { | |
chrome.runtime.sendMessage({ | |
method : 'setItem', | |
key : key, | |
value : value | |
}, | |
function(response) { | |
alert("setted item") | |
}) | |
} | |
function getItem(key) { | |
chrome.runtime.sendMessage({ | |
method : 'getItem', | |
key : key | |
}, | |
function(response) { | |
$('body').css('background', '#000') | |
alert("getted item") | |
alert(response.data) | |
}) | |
} |
○ background.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
chrome.webRequest.onHeadersReceived.addListener(function(details) { | |
return { | |
responseHeaders : details.responseHeaders.filter(function(header) { | |
return (header.name.toLowerCase() !== 'x-frame-options') | |
}) | |
} | |
}, { | |
urls : [ "<all_urls>" ] | |
}, [ "blocking", "responseHeaders" ]) | |
var ss = sessionStorage | |
chrome.runtime.onMessage.addListener(function(request, sender, callback) { | |
if (request) { | |
if (request.method == 'setItem') { | |
ss.setItem(request.key, request.value) | |
callback({ | |
data : ss.getItem(request.key) | |
}) | |
} else if (request.method == 'getItem') { | |
callback({ | |
data : ss.getItem(request.key) | |
}) | |
} else if (req.method == 'clear') { | |
ss.clear() | |
} | |
} | |
return true | |
}) |
◆ コード解説
1点、解説を加えておく。
contentScript.jsは親子両方に読み込まれるため、共通処理以外のjsコードは、親子用で条件を加えている。
// iframe用 if (window != parent) { ~snip~ } // 親用 else{ ~snip~ }