canvas要素によるWebページのスクリーンショット保存機能

今年4月に行なわれたMozillaParty7.0において、いくつか有用な情報を得ることができたが、中でもcanvas 要素の toDataURL メソッドで取得した data:URL をファイルへ保存するという Taken さんの情報は、 ScrapBook で保存したWebページのコレクションをサムネイル画像によって一望するというプランを一気に実現へと近づけることができるありがたいものであった。その具体的な方法はTaken SPC : Mozilla Party JP 7.0 に行ってきましたのポストでも説明されているが、これを利用して現在ブラウザに表示されているWebページのスクリーンショット(今回はサムネイルではなく、原寸大のスクリーンショット)をPNG画像として保存する機能を実装してみる。

(1) XUL

html:canvas要素を chrome://browser/content/browser.xul へオーバーレイする。オーバーレイする位置はどこでも構わないが、下記の例ではステータスバー内にオーバーレイしている。また、サイズの大きな canvas 要素を擬似的に隠すために、scrollbox 要素内に押し込んで表示させている。これは PearlCrescent PageSaver で用いられているテクニックである。

<overlay id="myOverlay"
         xmlns="http://www.mozilla.org/keymaster/gatekeeper/there.is.only.xul"
         xmlns:html="http://www.w3.org/1999/xhtml">

    <statusbar id="status-bar">
        <scrollbox width="1" height="1">
            <html:canvas id="myCanvas" display="none" />
        </scrollbox>
    </statusbar>

</overlay>

(2) JavaScript

まずは対象となるWebページのWindowオブジェクトと、Webページのサイズを取得する。ただし、documentElement.clientHeightは、Quirks(後方互換)モードではWebページ全体の高さとなるが、 Standards Compliant(標準準拠)モードでは実際に見えている部分のみの高さとなる。PearlCrescent PageSaver では、どちらのモードにも対応した GetWindowWidth, GetWindowHeight 関数というのを自前で実装している。

var win = window._content;
var w = win.document.documentElement.clientWidth;
var h = win.document.documentElement.clientHeight;

var w = win.document.width;
var h = win.document.height;

「display: none;」となっていたcanvas要素を一時的に表示させ、そのサイズを先ほど取得したWebページのサイズと一致させる。canvas要素は scrollbox 内に押し込まれているので、見かけ上は1×1ピクセルとして表示される。

var canvas = document.getElementById("myCanvas");
canvas.style.display = "inline";
canvas.width = w;
canvas.height = h;

いよいよ、canvas要素へWebページを描画する。

var ctx = canvas.getContext("2d");
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.save();
ctx.scale(1.0, 1.0);    // 1.0なら原寸大
ctx.drawWindow(win, 0, 0, w, h, "rgb(255,255,255)");
ctx.restore();

続いて、PNG画像データをBASE64エンコードしたdata:URLを取得する。これは Firefox 2.0 以外では例外となるので、 try~catch する。さらに、取得した string型の data:URL から nsIURI オブジェクトを生成する。

try {
    var url = canvas.toDataURL("image/png");
} catch(ex) {
    return alert("This feature requires Firefox 2.0.
" + ex);
}
const IO_SERVICE = Components.classes['@mozilla.org/network/io-service;1']
                   .getService(Components.interfaces.nsIIOService);
url = IO_SERVICE.newURI(url, null, null);

ファイルピッカーを使って保存先ファイルを決定する。

var fp = Components.classes['@mozilla.org/filepicker;1']
          .createInstance(Components.interfaces.nsIFilePicker);
fp.init(window, "Save Screenshot As", fp.modeSave);
fp.appendFilters(fp.filterImages);
fp.defaultExtension = "png";
fp.defaultString = "screenshot.png";
if ( fp.show() == fp.returnCancel || !fp.file ) return;

nsIWebBrowserPersist を使って data:URL をファイルへ保存する。
nsIWebBrowserPersist はURIがhttp:プロトコルであるインターネット上のデータをダウンロードするためによく使われるが、data:プロトコルに対しても有効であるというのがミソ。

var wbp = Components.classes['@mozilla.org/embedding/browser/nsWebBrowserPersist;1']
          .createInstance(Components.interfaces.nsIWebBrowserPersist);
wbp.saveURI(url, null, null, null, null, fp.file);

最後に、一時的に表示されたキャンバスを非表示にして後始末する。

canvas.style.display = "none";
canvas.width = 1;
canvas.height = 1;

以上のような処理でおおよそスクリーンショットを保存する機能を実装することができた。あとは、適当なUIを加えれば、 PearlCrescent PageSaver Basic 相当の拡張機能がいとも簡単に実装できてしまう。

しかし、ひとつ厄介な問題がある。スクリーンショットを撮る対象のWebページのサイズが非常に大きい場合、一時的に膨大なメモリを食ってしまうということである。例えば ScrapBook の「リクエスト+バグ」のページのスクリーンショットを保存すると、一時的にメモリ使用量が50MBほど上昇する。PCに搭載されたメモリサイズに依存するが、最悪PCの動作が不安定になり、Firefox を強制終了するしかなくなることもある。原因はもちろん data:URL を取得して string型の変数へ格納した際に発生するのだが、対策は難しいだろう。

TOP

5 Comments to “canvas要素によるWebページのスクリーンショット保存機能”

otokiti:

ここにコメントするのは適切でなくまた、対応していない Minefield での事ですが
ScrapBook 1.2.0.8 と Minefield 20070902 以降で ステータスバー・ボタンの画像が表示されなくなっています。調べてみますと overlay.xul の line:185
src=”chrome://scrapbook/skin/main.png” を
style=”list-style-image: url(chrome://scrapbook/skin/main.png)”
と書き換えるとトリアエズ表示されるようになりました。
Fx3 のそれも trunk ですので対応ドウコウの話ではありませんが単なる情報として
お知らせします。
なお、現在の最新バージョン
Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9a8pre) Gecko/2007090217 Minefield/3.0a8pre(tinderbox-builds)
でも同様です。

報告ありがとうございます。
バグ確認しました。Stylishという拡張機能でもステータスバーアイコンが最近のTrunkで表示されなくなったという報告がありましたので、しばらく様子を見てみます。

この件に関連するバグが Bugzilla に立っていました。
Bug 395099 – src attribute doesn’t work in statusbarpanel object, regression in FFx3

otokiti:

ご返事有難うございます。私の方でも Bug 395099 を確認しました。
そのうちに直るでしょう....お騒がせしました。

otokiti:

もうご存知でしょうが Minefiled/2007091718 で Bug 395099 直ったようです。

TOP

TOP