mozilla

DOM オブジェクトを Canvas に描画する

セキュリティ上の理由から簡単ではありませんが、HTML など DOM の内容を canvas に描画することができます。この記事は Robert O'Callahan によるブログ記事をもとに、セキュアに、安全に、そして仕様に準拠したかたちでそれを実現する方法を紹介します。

概要

HTML をそのまま canvas に描画することはできません。なので代わりに、描画したい内容を含む SVG 画像を利用します。描画したい HTML を含む <foreignobject> を作り、その SVG 画像を canvas に描画するのです。

描画へのステップ

唯一厄介なのが、SVG を作ることです。SVG のための XML を含んだ文字列を作り、次のものを含んだ を生成することです。

  1. "image/svg+xml" という Blob の MIME タイプ
  2. <svg> 要素
  3. svg 要素内の <foreignobject> 要素
  4. foreignObject 要素内の well-formed な HTML

オブジェクト URL を使うことで、外部のソースを読み込まずに HTML を埋め込むことができます。もちろん、描画する文書と origin が同じであれば、外部ソースを読み込む方法も利用できます。

<!DOCTYPE html>
<html>
<body>
<p><canvas id="canvas" style="border:2px solid black;" width="200" height="200"></canvas>
<script>
var canvas = document.getElementById("canvas");
var ctx = canvas.getContext("2d");
var data = "<svg xmlns='http://www.w3.org/2000/svg' width='200' height='200'>" +
             "<foreignObject width='100%' height='100%'>" +
               "<div xmlns='http://www.w3.org/1999/xhtml' style='font-size:40px'>" +
                 "<em>I</em> like <span style='color:white; text-shadow:0 0 2px blue;'>cheese</span>" +
               "</div>" +
             "</foreignObject>" +
           "</svg>";
var DOMURL = self.URL || self.webkitURL || self;
var img = new Image();
var svg = new Blob([data], {type: "image/svg+xml;charset=utf-8"});
var url = DOMURL.createObjectURL(svg);
img.onload = function() {
    ctx.drawImage(img, 0, 0);
    DOMURL.revokeObjectURL(url);
};
img.src = url;
</script>
</body>
</html>

上記のソースコードの実行結果が次になります。

example.png

変数 data は、Canvas に描画したい SVG 画像の内容 (HTML が含まれる) を参照します。

SVG 画像を作った後、new Image() を呼び出し HTML の img 要素を作成したうえで、そこに data を追加します。そして、オブジェクト URL を割り当て、画像の読み込みが終わった時に、drawImage() で画像をコンテキストに描画します。

セキュリティ

Canvas からセンシティブなデータを読み出される可能性を考えた時、この方法がどうして安全なのか疑問に思う方もいるでしょう。答えはこうです。この方法は、SVG 画像の実装がとても厳しい制限下で行われていることを利用しているのです。SVG 画像は外部リソースを読み込むことができません。たとえそのリソースが同じドメインにあってもです。JPEG などのラスター画像や <iframe> などは、data: URLにして埋め込まなければいけません。

加えて、SVG 画像にはスクリプトを含められません。ですので、他のスクリプトからその DOM にアクセスすることに危険性はありません。また、SVG 画像の DOM 要素は入力イベントを受け取れません。ですので、フォームコントロールに入力された特権的な情報 (ファイル入力の <input> 要素に出力されたファイルのフルパスなど) を読み込み、そのピクセル情報から情報を引き出すといったこともできません。

訪問済リンクのスタイルも SVG 画像に描画されたリンクには適用されませんので、履歴情報を取得することはできません。また、プラットフォームのネイティブテーマは SVG 画像に描画されません。ですのでユーザーのプラットフォームを知ることも難しくなっています。

画像が書き出された Canvas は origin clean であるはずです。何を意味するかというと、その Canvas から toBlob(function(blob) {...}) を呼び出し オブジェクトを生成することや、toDataURL() を呼び出し Base64 でエンコードされた data: URL を取得することが可能ということです。

注意: 現時点で、Chrome ではこうした Canvas は origin clean ではありません。これは、オブジェクト URL や data: URL から読み込まれた文書がもとの文書と別 origin になるという、WebKit のバグに起因するものです。

HTML の描画

SVG は valid な XML でなければならないので、埋め込む HTML も well-formed なものでなければいけません。次のコードを実行すれば、簡単に HTML を well-formed なものに変換できます。

var doc = document.implementation.createHTMLDocument("");
doc.write(html);

// You must manually set the xmlns if you intend to immediately serialize the HTML
// document to a string as opposed to appending it to a <foreignObject> in the DOM
doc.documentElement.setAttribute("xmlns", doc.documentElement.namespaceURI);

// Get well-formed markup
html = (new XMLSerializer).serializeToString(doc);

参考

ドキュメントのタグと貢献者

タグ: 
Contributors to this page: myakura, ethertank
最終更新者: ethertank,