Sep 26, 2007

Chromeウィンドウを最前面にする・最背面にする

Chromeウィンドウでは、window.open()window.openDialog()の第3引数でalwaysRaisedフラグを指定することで、「常に最前面に表示」状態のウィンドウを開くことができる(通常のスクリプトではこのフラグを使うには特権が必要)。じゃあ、すでに開かれているウィンドウを最前面にすることはできないのか? というのが今日のお題。

フォクすけクロックを使ってみて、Firefoxのウィンドウの下に時計が隠れてしまうのは不便きわまりないと思ったので、これをどうにかしたかった。最初は、前述のalwaysRaisedを使った方法でいけるかなと思ったんだけど、Firefoxのアドオンとして動作する時はこれでいいけどXULRunnerアプリとして動作する時には「開く元の親ウィンドウ」が無いからこれじゃダメだ、と気がついた。

んでちょっと調べてみた所によると、どうもnsIXULWindowインターフェースのzLevelというプロパティをいじることで、ウィンドウの重ね合わせの優先順位を動的に変更できるようだということが分かってきた。

以下のようにいくつかのインターフェースを経由することで、Chromeウィンドウ(nsIDOMWindow)からnsIXULWindowのインターフェースに辿り着くことができる。

var Ci = Components.interfaces;
var XULWindow = window
    .QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIWebNavigation)
    .QueryInterface(Ci.nsIDocShellTreeItem)
    .treeOwner
    .QueryInterface(Ci.nsIInterfaceRequestor)
    .getInterface(Ci.nsIXULWindow);

XULWindow.zLevel = Ci.nsIXULWindow.raisedZ;

zLevelに指定可能な値はnsIXULWindowインターフェースにおいて定数プロパティとして定義されていて、以下の種類がある。

プロパティ実際の値意味
Ci.nsIXULWindow.lowestZ 0 すべてのウィンドウの最背面
Ci.nsIXULWindow.loweredZ 4 すべてのChromeウィンドウの最背面
Ci.nsIXULWindow.normalZ 5 通常
Ci.nsIXULWindow.raisedZ 6 すべてのChromeウィンドウの最前面
Ci.nsIXULWindow.highestZ 9 すべてのウィンドウの最前面

lowestZloweredZの違いは、前者がエクスプローラその他Windowsネイティブのアプリケーションのウィンドウも含めてすべての最背面になるのに対して、後者はあくまでFirefoxのウィンドウの中での最背面になるだけであるということ。loweredZを指定したChromeウィンドウが他のネイティブアプリのウィンドウの下にある時、そのChromeウィンドウをクリックすると、そのChromeウィンドウがネイティブアプリのウィンドウの上に表示されるようになると同時に、それに「押し上げられる」形で、FirefoxのウィンドウすべてがそのChromeウィンドウより前面に表示される。

highestZraisedZもそれと同様に、後者を指定したウィンドウは上にネイティブアプリのウィンドウが重なりうるけど、前者にはいかなるウィンドウも上には重ならない……のかと思いきや、こちらはどっちを指定してみてもraisedZで期待される通りの挙動にしかならなかった(Firefoxのウィンドウの中での最前面になるだけで、他のネイティブアプリのウィンドウが前面にくると、その下に隠れてしまう)。これってバグ?

ちなみに、数値で1~3を指定した場合はloweredZ(4)を指定したのと同じ挙動になるようだ。多分7~8はraisedZ(6)と同じで、それ以上はすべてhighestZ(9)と同じになるんだろうと思うけど……前述の通りhighestZraisedZは実際の挙動に違いが全然無いので、それを確認することはできなかった。

あと、ずっと前にEz Sidebarでこの問題にぶち当たってから気になってるもののずっと直ってないみたいなんだけど、raisedZ以上が指定されたウィンドウがあると、通常のウィンドウで開いたモーダルダイアログが親ウィンドウの下に潜ってしまうという問題が起こる。パスワードの入力を求めるダイアログ等がFirefoxのウィンドウの下に潜ってしまって使い物にならないので、これはマジで困る。どうにかならんものだろうか。

――ということで対策を考えてみた。

var XULWindow = window
     .QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIWebNavigation)
     .QueryInterface(Ci.nsIDocShellTreeItem)
     .treeOwner
     .QueryInterface(Ci.nsIInterfaceRequestor)
     .getInterface(Ci.nsIXULWindow);
var observer = {
  observe : function(aSubect, aTopic, aData)
  {
    if (aTopic == "xul-window-registered") {
      XULWindow.zLevel = Ci.nsIXULWindow.normalZ;
      window.setTimeout(function() {
        XULWindow.zLevel = Ci.nsIXULWindow.highestZ;
      }, 250);
    }
  }
};

var ObserverService = Cc["@mozilla.org/observer-service;1"]
      .getService(Ci.nsIObserverService);
ObserverService.addObserver(MakeOnTopObserver,
                            "xul-window-registered", false);

新しいウィンドウが開かれる直前にウィンドウのzLevelを元に戻して、ウィンドウが開かれた後にまた最前面に戻す。こうすれば最後の問題は回避できる。

エントリを編集します。

wikieditish message: Ready to edit this entry.











拡張機能