ローカルファイルでajax風にファイルを読み込む(postMessage)

前回マークダウンで書いた文書を閲覧するためにJavaScriptでパーサを作成した。イメージしてたのは以下のように、マークダウン文書は別ファイルに記述しておき、ビュー用のページからajax的に取得してきて表示するという利用形態。

ローカルファイル取得できない問題

しかし実際に作ってから、ローカル実行(file://~)ではブラウザのセキュリティ制約により外部ファイルを取ってこれないことに気づいた。まあ確かにそれができると、開いただけでPC内のファイルをアップロードし始めるhtmlファイルとか作れてしまう。

少量の文書ならスクリプトもマークダウンも一つのファイルにまとめるのもありだが、そうでなければ内容とビューは基本的に分けたい。

試した方法

XMLHttpRequest

通常のWebならこれで取ってくる。しかし当然Webサーバが無いので不可。一部ブラウザではhttpリクエストの代わりにファイルを読み込んでくれるらしいが、セキュリティ回避などそれぞれのブラウザで必要な対応や操作が変わってくる。

ActiveX(FileSystemObject)

厳密には試していない。昔やったことはある。ActiveXなのでIEでしか動かない。

script src="~"

ローカルファイルでも通常の参照リンクや画像表示は動く。scriptタグの外部参照で読み込んだスクリプトの中身を取得しようかと思ったが、そもそもそんなことできなかった。

iframe経由

対象ファイルをインラインフレームに表示して、その中身を"contentWindow.document"で取得する。表示はされるがスクリプトからはクロスドメイン扱いとなり弾かれた。ファイルのパスとか関係ない。

結論

無理。まあ仮にできてもセキュリティホールだから塞がれてしかるべき方法だな。

対処方法

完全分離はあきらめて、できるだけ文書側に処理を書かずに内容を受け渡す方法として、postMessageを使ってみる。

postMessage

もとは異なるドメインのWebページ間でデータを受け渡すための仕組み。A,BふたつのWebページを開いて、AB間でイベントコールができる。

送り手、受け手の両方が対応していてかつ受け渡す際にドメイン情報が含まれるので、適切に処理していれば安全にやり取りができるはず、という考え方のようだ。正直あまり利用シーンが思い浮かばないが。

送り手(マークダウン記述ファイル)

<!DOCTYPE html><html lang="ja"><head><meta charset="UTF-8" /></head><body>
<script type="text/plain" id="Markdown">

~ここにマークダウンを書く~

</script>
<script type="text/javascript">
    if(parent) parent.postMessage(document.getElementById("Markdown").textContent , "*");
</script></body></html>

ファイルはhtmlファイルにする。これ自体を描画するわけではないのでスタイルやheaderは最小限でいい。

マークダウンは<script type="text/plain">の中に書く。type=text/plain は何も実行しないスクリプトを表す。適当なhtmlタグの要素として記述するとDOM解析されてスペースや改行が整理されちゃったりするのでここに書く。ただしこの場合も</script>が文書内にあるとスクリプトブロックが終わってしまう。

bodyの最後にフレームの親ウィンドウ(後述)に対してpostMessageを送るスクリプトを書く。送信データとして上で記述したマークダウンテキストを渡す。

なおpostMessageの第二引数は宛先を制限する場合にドメインを記述する。今回は特にその必要がないので"*"。

受け手(ビュワー)

<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8" />
    <script src="MDParser.js" type="text/javascript"></script>
    <link rel="stylesheet" type="text/css" href="style.css" />
</head>
<body>
    <div>
        <div id="SideMenu" onmousedown="SlideboxMouseDown(arguments[0])">
            <ul>
                <li><a href="md/page1.html" target="ReaderFrame">概要</a></li>
                <li><a href="md/page2.html" target="ReaderFrame">サンプル</a></li>
                <li><a href="md/page3.html" target="ReaderFrame">表題の書き方</a></li>
                <li><a href="md/page4.html" target="ReaderFrame">リストの書き方</a></li>
            </ul>
        </div>
        <div id="Content">
        </div>
    </div>

    <iframe name="ReaderFrame" style="display:none;">
    </iframe>
    
    <script>
        var parser = new MDParser();
        parser.ImageRoot = "img/";

        window.addEventListener("message", function(e){
            document.getElementById("Content").innerHTML = parser.BuildHtml(e.data);
        });
        
    </script>
</body>
</html>

ビュワー側には描画されないiframeを配置しておく。

リンクなりでiframeに先ほどのページファイルを読み込むと、postMessageの受けである"window.message"イベントハンドラが呼ばれる。後は受け渡されたデータをパーサに突っ込んで、コンテンツ表示部に入れてやれば表示できるという寸法。

引数eには受け渡しデータの他に送信元のドメイン情報も入っている。公開Webで使うときはここをしっかり見ろよということらしい。

まとめ

内容とスクリプトを完全分離はできなかったが、この程度なら共通記述でいいし、ちょっとヘッダとフッタが付いてるぐらいに思えば許容範囲だろうか。マークダウン文書の他での再利用を考慮するとあまり良くはないのだが。

コメント