ブラウザウィンドウのサムネイルを描画する

タブカタログ拡張機能のようにWebページのサムネイル画像を表示する拡張機能の多くは、 html:canvas 要素の二次元描画コンテクストの drawWindow メソッドへWebページの window オブジェクトなどを引数で渡してサムネイルの描画を行っています(canvas を使って図形を描く – MDC)。

この drawWindow メソッドの引数にWebページの window オブジェクトではなく、ブラウザウィンドウの ChromeWindow オブジェクトを渡すことで、ブラウザウィンドウのサムネイル画像を描画することも可能です。しかし、以下のようにブラウザタブ内に表示されたWebページまでは描画されず、背景色で塗りつぶされたようになってしまいます。

この問題を解決するには、ブラウザウィンドウのサムネイルの上に、Web ページのサムネイルを重ねて描画する必要がありそうです。ここでは、 Tab Flick 拡張機能で実際に使っている、 html:canvas 要素を2個重ねる方式を紹介します。

XUL

ブラウザウィンドウのサムネイル描画用とWebページのサムネイル描画用の、2つの html:canvas 要素を xul:stack 要素で重ねます。

<stack>
	<html:canvas id="outerCanvas" /><!-- ブラウザウィンドウ用 -->
	<html:canvas id="innerCanvas" /><!-- Webページ用 -->
</stack>

JavaScript

最初にサムネイルの拡大/縮小率を定めておきます。ここでは50%に縮小することとします。

const scale = 0.5;

サムネイル描画の対象とするブラウザウィンドウとして、 nsIWindowMediator を使って直近のブラウザウィンドウの ChromeWindow オブジェクトを取得します。

var win = Components.classes["@mozilla.org/appshell/window-mediator;1"].
          getService(Components.interfaces.nsIWindowMediator).
          getMostRecentWindow("navigator:browser");

簡便のため、2つの html:canvas 要素への参照を取得しておきます。

var oCanvas = document.getElementById("outerCanvas");
var iCanvas = document.getElementById("innerCanvas");

ブラウザウィンドウに対する現在のブラウザ(xul:browser 要素)の位置の相対関係から、Webページ用の html:canvas 要素の位置を調整します。

var rect = win.gBrowser.mCurrentBrowser.getBoundingClientRect();
iCanvas.style.left = Math.round(rect.left * scale) + "px";
iCanvas.style.top  = Math.round(rect.top  * scale) + "px";

この時点で2つの html:canvas 要素に枠線を付加して表示してみると、以下のようになっています。
なお、この時点では html:canvas 要素のサイズはまだ調整されていません。

次の処理へ移る前に、あらかじめ関数を作っておきます。引数で指定したウィンドウのサムネイルを、指定した html:canvas 要素に描画する関数です。

// @param aWindow ウィンドウのオブジェクト
// @param aCavas  html:canvas要素
// @param aScale  サムネイルの拡大/縮小率
function drawPreviewForWindow(aWindow, aCanvas, aScale) {
	var w = aWindow.innerWidth  || aWindow.document.documentElement.clientWidth;
	var h = aWindow.innerHeight || aWindow.document.documentElement.clientHeight;
	aCanvas.width  = Math.round(w * aScale);
	aCanvas.height = Math.round(h * aScale);
	var ctx = aCanvas.getContext("2d");
	ctx.clearRect(0, 0, aCanvas.width, aCanvas.height);
	ctx.save();
	ctx.scale(aScale, aScale);
	ctx.drawWindow(aWindow, aWindow.scrollX, aWindow.scrollY, w, h, "rgb(255,255,255)");
	ctx.restore();
}

先ほど作った関数を使い、ブラウザウィンドウと、そのブラウザウィンドウの現在のタブに表示されたWebページの両方のサムネイルを描画します。なお、 win.contentwin に対応するブラウザウィンドウの現在のタブに表示されたWebページの window オブジェクトを表します。

drawPreviewForWindow(win,         oCanvas, scale);
drawPreviewForWindow(win.content, iCanvas, scale);

以上のコードに適当に肉付けして実行してみると、以下のようにブラウザタブ内のWebページも含めてブラウザウィンドウのサムネイルが描画されます。

なお、最終的に2つの html:canvas 要素は以下のような位置関係・サイズになっています。

別の方式…1個の html:canvas 要素で実現可能か?

今回は2つの html:canvas 要素を重ねる方式を紹介しましたが、別の方式として、1個の html:canvas 要素に対して2回 drawWindow メソッドを使用してブラウザウィンドウとWebページの2つのサムネイルを重ねて描画する方式も思いつきました。しかし、筆者が調べた限りでは、 drawWindow メソッドは必ず html:canvas 要素の左上位置(座標 0, 0)から描画を開始するため、Webページのサムネイルを正しい位置に重ねることができないようでした。したがって、別の方式は断念しました。

2 件のコメント

  1. hatena.ne.jp/piro_or/ :

    > 別の方式…1個の html:canvas 要素で実現可能か?
    可能です。translate()で原点を移動してやる事で、任意の位置にスクリーンキャプチャを描画させる事ができます。
    http://www.cozmixng.org/repos/piro/fx3-compatibility-lib/trunk/fullScreenCanvas.xul
    こちらがその実装例です。

  2. Gomita :

    > translate()で原点を移動してやる
    おお!サンクスです。
    最初はてっきりmoveToがそのためのメソッドなのかと思ったら違ったんで、諦めてました。
    やはり1個のcanvasで実現できたほうが気持ちいいですね。
    近日中に修正版の記事をアップします。