読者です 読者をやめる 読者になる 読者になる

sos の 作業メモ

プログラミングや英会話学習、マイルや旅行、日常生活など。最近はWebFormなASP.NETのお守りがお仕事です。

日々の生活にhappyをプラスする|ハピタス Gポイント

IE11で別ウィンドウでのcross-domainのpostMessageを成功させるメモ

ここ数日ずっと悩んでいた問題ですが、なんとか対応できたのでメモしておきます。

HTML5には、 Web Messaging APIという、異なるドメイン間でデータをやり取りするための魔法のような便利な仕組みが用意されているのですが、IE11でも使えるはずなのに動かないという現象で苦しんでいました。

子要素のiframeならcross-domainでもpostMessageできるんですが、別のwindow同士だとNG。 まぁwindow違うってことはインスタンスも違うので・・・ってそんな問題じゃないですよね。

IE11 なんてもう知らねーよってことらしい

HTML5 post message API does not work in All IE versions for cross-domain JavaScript communication between browser tabs/windows.Alternatives like messageChannel API also does not work in this scenario. | Microsoft Connect

で、この現象、2014年にissueとして報告されていますが、結局2016年2月に、Edgeに移行するし IE11はもう対応しねーよってことで終了となっています。

これ読んだ時に思わず「うげ、IEなんて消えてなくなれ」と声に出したのは言うまでもありません。

回避方法

iframeを使う方法だと 親とiframeで スキーマが違う(httpsとhttp)と厄介。見映えもよくないし、何よりセキュリティ的に警告やら何やらで乗り越えないといけないことが多くて面倒。

タブ同士ならOKという話をどこかのサイトで読んだので、ターゲットのリモートのページを開く前にローカルの proxyページをタブでひっそり開いておいて、それを経由してやり取りしちゃおうと目論みました。

11より前のIEでも、同じドメインなら ウィンドウ間でpostMessageが通ります

タブとして開く

ご存知の通り、 window.open() で作成されたwindowが 新規ウィンドウになるか、タブになるかは javascript側からは指定できません。

blogs.technet.microsoft.com

が、いろいろと調べてみたところ、 IEに関してはメニューバーやスクロールバーの有無などのパラメータが同一のものなら、同じタブとして開いてくれるっぽいらしいので、 ここに望みをかけて実装します。

ウィンドウの上書き?乗っ取り?

いろいろ試している最中に、window.openでproxyのページを開いた後、そのまま同じ名称で remoteをopenしてやると、proxyのページを上書きしてうまく異なるドメインからのデータ送信ができるというバグのような挙動を発見。

セキュリティホールのような気もしますが、仲介動作なしで動いてくれるのでこれでよしとします。 注意点としては、ポップアップブロックされるようなurlだと動作に失敗するところ。これはユーザさんに常に許可してもらうようにしてもらうしかないですよね。

サンプルコード

proxyのタブと本来のターゲットのremoteサイトを開き、remoteサイト側から postmessageされたデータを受け取るサンプル

proxy_tab.html

<head>
    <title>PostMessage test</title>
</head>
<body>
     proxy tab
</body>
</html>

呼び出し側の処理

<script>
    function openRemoteWin(remoteurl, winname) {
        var ua = navigator.userAgent.toLowerCase();
        if( (ua.indexOf('msie') > -1) && (ua.indexOf('opera') == -1) ){
           // 11より前のIE
            alert('そんなの使うんじゃない');
            return;
        }
        // IE11
        var isIE11 = (ua.indexOf('trident/7') > -1);
        if(isIE11) {
            // IE11なのでproxy_tab.htmlを開く
            var proxywin = window.open('/proxy_tab.html',winname);
        }
        return window.open(remoteurl, winname); // 対象のremoteページを開く
    }
    // データ受信
    function receiveMessage(e) {
        // 送信元のドメインをチェック
        var loworigin = e.origin.toLowerCase();
        if (loworigin != 'http://hogehoge.hoge.hoge'){ return; }
        // e.data に目的のデータが入っている
    }
    window.addEventListener("message", receiveMessage);
</script>

remoteサイト

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html lang="UTF-8" xml:lang="UTF-8" xmlns="http://www.w3.org/1999/xhtml" debug="true">
<head>
<title>たいとるー</title>
<script>
    // WebMessagingAPIでデータを送信する
    function postData() {
        var openerwin = window.opener;
        if(openerwin){
            openerwin.postMessage(document.getElementById('dtdt').innerHTML, "https://fromfrom.from.from"); // 呼び出し元のドメインを指定してpostMessage
        }
    }
</script>
</head>
<body>
    <span id="dtdt">でたでた</span>
    <p><button onclick="postData();">データ送信</button></p>
</body>
</html>

IE11なんて大嫌い

納期に追われている状況なのでどうなることかと思いましたが、 これでなんとか乗り切れそうです。

あー、しんどかった。

ブラウザでやり取りせず、サーバー上でデータを仲介する実装にすればよかったんじゃ・・・という話は聞かなかったことにしておきます。