birdどん詰まりまで戻りきったらそのタブを閉じるChrome拡張

普段Androidのスマホなどを利用していると、Webページのリンクの設定でたとえ「新しいタブを開く(target=”_blank”等が指定されている)」となっている場合も、フツーにBackボタンを押せば自動的にそのタブを閉じ、元のタブに戻ってこれます。そのような操作性にすっかり慣れてしまっていると、PC版Chromeで新しいタブが開かれたとき、単にマウスの戻るボタンを押しても戻れないのが地味に煩わしく思っていました。

ちょっと前からそういった機能を実現する設定や機能拡張は無いものかと探していたんですが、Firefox用としてかなり近いものは見つけたものの1、Chrome用にはぴったり来るものが見つけられずにいました。

そんな時ふと、「もしかしたら自分でも作れるんじゃ?」と思いちょっと調べてみたところ、思いの外あっさり作ることが出来たので、ここに記録しておきたいと思います。ただし僕があっさり作ることが出来たのには実はいろいろな幸運が重なっていました。おそらく僕と同じような環境でMac版Google Chromeを使っている人はあまり多くないと思いますので、適用可能なシーンはあまり多くないものと思います。どうもスミマセン。

注意点

僕の場合の幸運、別の言い方をすると特殊事情は以下の通りです。

  • 我が家では、通常Macに利用されているMagic MouseやMagic Trackpadのようなジェスチャーでスクロールや進む/戻るを行えるようなポインティングデバイスではなく、フツーのWindowsマシンに利用されるようなBluetoothの5ボタン+ホイール付きマウスを使っています2。ただしMacの場合、そういったマウスをそのまま繋ぐだけではブラウザの進む/戻るといった機能は利用できないため、Karabinerというツールを使ってマウスの3〜5ボタンを有効にしています。その辺りの顛末はこちらを参照してください。
  • Karabinerでマウスボタンを有効にしている、ということはすなわち、それらのマウスボタンを押した時に発生するイベントがマウスイベントではなくキーボードイベントとなる、ということを意味します。これが今回機能拡張のJavaScriptで簡単にイベントを処理出来た理由でした。Magic Mouse等でのジェスチャー由来の進む/戻るは言わずもがな、Windows版Chromeでのマウスの進む/戻るボタンのイベントをブラウザのJavaScriptで拾うのは実はとても大変なようです(今回そちらは結局分かりませんでした)。

というわけで、ここで説明する機能拡張は「Mac版Chromeで、かつ通常の5ボタンマウスをKarabiner経由で利用している場合」のみ利用可能、ということになります3

機能拡張の詳細

以下に機能拡張の詳細を記録しておきます。これらのファイルをすべて同じディレクトリに置いて、Google Chromeの拡張機能のページを開き、右上の「デベロッパーモード」のチェックボックスをチェックした後、「パッケージ化されていない機能拡張を読み込む」から読み込ませれば利用可能と思います。

manifest.json

{
   "manifest_version": 2,
   "name": "Dead End To Close",
   "description": "dead end to close.",
   "version": "1.0.0",
   "content_scripts": [ {
      "all_frames": true,
      "matches": [ "http://*/*", "https://*/*", "ftp://*/*" ],
      "js": [ "detc.js" ]
   } ],
   "icons": {
      "48": "back48.png",
      "128": "back128.png"
   },
   "permissions": [ "tabs", "https://*/*", "http://*/*" ]
}

これ以上ないくらいシンプルなmanifestなので説明は不要でしょう(^^;。

detc.js

var back_on = false;
var popstate = false;

window.addEventListener('popstate', function(stateEvent) {
    popstate = true;
}, false);

window.addEventListener('keydown', function(keyEvent) {
    if (keyEvent.keyCode == 219 && keyEvent.metaKey == true) {
        back_on = true;
        popstate = false;
    } else
        back_on = false;
}, false);

window.addEventListener('keyup', function(keyEvent) {
    if (keyEvent.keyCode == 91 && back_on)
        setTimeout(function(){
            if (!popstate)
                window.close();
        }, 200);
    back_on = false;
}, false);

実質、このプログラムの本体は上記のみです。やっていることの本質は極めてシンプルで、ブラウザで開くすべてのページにkeyCode = 91のkeyUpイベントが来たら「200ms後にwindows.close();を実行」するタイマーを仕込む、というイベントリスナーを設定してるだけ。

どうしてこれでうまく動くかというと、実際に「戻る」が発生すると元のページに設定されていたイベントリスナーやタイマーは直ちに破棄されるようで、このイベントリスナーが発動することはないんですね。どん詰まりのページ、つまり戻るボタンを押しても何も起きないページでのみ、実際にイベントリスナーによりタイマーが発動し、200ms後にページが閉じます。

どん詰まりのページではunloadやbeforeunloadイベントが発生しないことを利用したもっと凝ったロジックを利用したコードも見つけたりしたんですが、今回僕がいろいろ調べた限り、少なくとも家の環境では上記だけで期待通りに動いているようでした。

なお、タブを閉じるのに利用しているのが「window.close();」なので、そのmethodを使う時の一般的な注意点、つまりscript等により動的に開かれたtab以外は閉じない、といった制約はそのままかかります。ただこの制約は今回の場合むしろusabilityを上げてくれる形で作用しているので(閉じてほしくないページまで閉じてしまうことがない、という意味で)、そのままにしています4

(119 追記) オリジナルのcodeではまさに単純にkeyupイベントでkeyCode = 91が来たらタイマーセット、というものでしたが、それだとコピペ目的などでCommandキーを使った途端に暴発してしまうことに気がついたので(^^;、keydownイベントも監視し「戻る」に相当するキーシーケンス、つまりCommand+[が来た場合のkeyupイベント(keyCode == 91)にのみ反応するようにしました。これで大抵のケースでの暴発は防げるはず。

(1228 追記) 上記修正で総じてごきげんに使えていたのですが、ごくたまにまだ戻れるhistoryがあるのにタブが閉じてしまうページがあって、ナンジャラホイ?と思っていました。年末になって少し時間が取れたので調べてみたところ、どうもpushState/popStateを使ってJavaScriptでhistoryを操作しているページではタブcloseが暴発してしまっていたようでしたので、その対策を入れてみました。あとaddEventListenerの第3引数 (useCapture) もどこかのページからコピペした時のまま意味もわからずtrueにしていたのを(^^;、この例ではfalseでも良いはずなのでfalseに直しました。

画像

back128.png back48.png

おしまい

少しでもお楽しみいただければ幸いでした。


  1. ただしこのExtension(「親のタブに戻る(Back to Owner Tab)」)はマウスの戻るボタンで戻ると子ページを閉じて戻ったあとに親ページも一つ戻ってしまう、という地味に使いづらいバグがありました。
  2. Magic Mouseのスクロールや進む/戻るの暴発に嫌気がさしたので。
  3. Windowsでも何らかのユーティリティ、例えばLogicoolのSetPointのようなものを用いて同様のことを行えば同じ動作を実現することは可能です。ただし監視するキーコードは変更する必要があるでしょう。WindowsではおそらくAlt+←あたりに変換&監視するのが良いのではないかと。実際、会社のPCではそうやって修正した機能拡張を利用していますが同じように動作しています。
  4. Chrome拡張APIでtabを閉じれば容赦なく閉じることも可能です。