Mozilla Firefox 用のマウスジェスチャーアドオン、Foxy Gestures のユーザースクリプト集です。

Foxy Gestures の概要や特長、スクリプトの書き方などについてはこちらの記事をご覧ください: Firefox 57(Quantum)で動かなくなった「FireGestures」の代替に選ばれたのは、「Foxy Gestures」と往年の名ソフトでした。

ウィンドウ操作

ウィンドウを最大化・規定サイズに戻す

executeInBackground(() => {
  getCurrentWindow().then(cwin => {
    if(cwin.state == "normal"){
      browser.windows.update(cwin.id,{state: "maximized"});
    }else{
      browser.windows.update(cwin.id,{width: 1024, height: 768});
    }
  });
}, []);

 ウィンドウを最大化したり、XGAサイズにしたり。
 スクリプト内の「1024」「768」を好きな値に変更してください。

タブの移動

同じドメインのタブを新しいウィンドウに移動

executeInBackground(ctrl => {
  getActiveTab(tab => {
    var m = tab.url.match(/:\/\/([^\/]+)\/?/);
    if(m){
      let qi = {url: "*://"+m[1]+"/*"};
      if(!ctrl){qi["currentWindow"] = true;}
      var querying = browser.tabs.query(qi);
      querying.then(tabs =>{
        var movetabs = [];
        for (let atab of tabs) {
          movetabs.push(atab);
          browser.tabs.update(atab.id,{pinned: false});
        }
        var creating = browser.windows.create({
          tabId: movetabs[0].id
        }).then(nw => {
          for(var i=1; i<movetabs.length; i++){
            browser.tabs.move(movetabs[i].id, {windowId: nw.id, index: i});
          }
          for(var i=0; i<movetabs.length; i++){
            browser.tabs.update(movetabs[i].id, {
              active: movetabs[i].active,
              pinned: movetabs[i].pinned
            });
          }
        });
      });
    }
  });
}, [mouseDown.ctrlKey]);

 現在のタブと同じドメインのタブを新しいウィンドウに移動させます。サブドメインが違う場合は、同じドメインとはみなしません。
 個人的には、Google検索結果のページが大量に散在している時に使っている気がします。
 Ctrlキーを押しながらで、すべてのウィンドウのタブが対象になります。

ページタイトルを検索して新しいウィンドウに移動

var searchword = window.prompt('マッチしたタブを新しいウィンドウに移動します');
if(searchword){
  executeInBackground(searchword => {
    getCurrentWindowTabs().then(tabs => {
      var movetabs = [];
      for(var i=0; i<tabs.length; i++){
        if(tabs[i].title.toLowerCase().indexOf(searchword)>=0){
          movetabs.push(tabs[i]);
          browser.tabs.update(tabs[i].id,{pinned: false});
        }
      }
      var creating = browser.windows.create({
          tabId: movetabs[0].id
      }).then(nw => {
        for(var i=1; i<movetabs.length; i++){
          browser.tabs.move(movetabs[i].id, {windowId: nw.id, index: i});
        }
        for(var i=0; i<movetabs.length; i++){
          browser.tabs.update(movetabs[i].id, {
            active: movetabs[i].active,
            pinned: movetabs[i].pinned
          });
        }
      });
    });
  }, [searchword.toLowerCase()]);
}

 タイトルを検索して、ヒットしたタブを新しいウィンドウに移動。
 Ctrlキー同時押しで全ウィンドウを対象にします。

現在のタブと同じコンテナータブを新しいウィンドウに移動

executeInBackground(ctrl => {
  getActiveTab(tab => {
    var m = tab.cookieStoreId;
    if(m && m!=="firefox-default"){
      let qi = {cookieStoreId: m};
      if(!ctrl){qi["currentWindow"] = true;}
      var querying = browser.tabs.query(qi);
      querying.then(tabs =>{
        var movetabs = [];
        for (let atab of tabs) {
          movetabs.push(atab);
          browser.tabs.update(atab.id,{pinned: false});
        }
        var creating = browser.windows.create({
          tabId: movetabs[0].id
        }).then(nw => {
          for(var i=1; i<movetabs.length; i++){
            browser.tabs.move(movetabs[i].id, {windowId: nw.id, index: i});
          }
          for(var i=0; i<movetabs.length; i++){
            browser.tabs.update(movetabs[i].id, {
              active: movetabs[i].active,
              pinned: movetabs[i].pinned
            });
          }
        });
      });
    }
  });
}, [mouseDown.ctrlKey]);

 Firefoxのコンテナータブを新しいウィンドウに移動。現在のタブがコンテナータブでない場合(普通のタブの場合)は何もしません。
 これもCtrlキー同時押しで全ウィンドウ対象。

全てのタブを1つのウィンドウに移動

executeInBackground(ctrl => {
  getActiveTab(tab => {
      var m = true;
      if(m){
          let qi = {};
          var querying = browser.tabs.query(qi);
          querying.then(tabs =>{
              var movetabs = [];
              for (let atab of tabs) {
                  movetabs.push(atab);
                  browser.tabs.update(atab.id,{pinned: false});
              }
              var creating = browser.windows.create({
                  tabId: movetabs[0].id
              }).then(nw => {
                  for(var i=1; i<movetabs.length; i++){
                      browser.tabs.move(movetabs[i].id, {windowId: nw.id, index: i});
                  }
                  for(var i=0; i<movetabs.length; i++){
                      browser.tabs.update(movetabs[i].id, {
                          active: movetabs[i].active,
                          pinned: movetabs[i].pinned
                      });
                  }
              });
          });
      }
  });
}, [mouseDown.ctrlKey]);

 全てのウィンドウの全てのタブを、新規ウィンドウに移動。
 やりたいと思うことはあまりありませんが、いざやろうとすると手間なので。

タブをプライベートウィンドウで開き直す

executeInBackground(() => {
    let q = {currentWindow: true, highlighted: true};
    let querying = browser.tabs.query(q);
    querying.then(tabs =>{
        let urls = [];
        for (let atab of tabs) {
            urls.push(atab.url);
        }
        browser.windows.create({
            incognito: true,
            url: urls
        });
    });
}, []);

 選択したタブ(複数選択可)のURLを、新しいプライベートウィンドウで開きます。
 既にプライベートではない状態で開いたページを改めてプライベートで開き直す意味ってあるのか? と思われそうですが、個人的にはプライベートというより「一時的にアドオンが無効な環境で見たい」という場合に使っています。(特別に許可しない限り、プライベートウィンドウではアドオンは無効になっているので)

タブを次のウィンドウに送る

executeInBackground(() => {
    let q = {currentWindow: true, highlighted: true};
    browser.tabs.query(q).then(tabs => {
        let movetabs_id = [];
        for (let atab of tabs) {
            movetabs_id.push(atab.id);
        }
        browser.windows.getAll().then(all_win => {
            all_win = all_win.filter(function( item ) {
                return !item.incognito;
            });
            if (all_win.length <= 1) {exit;}
            browser.windows.getCurrent().then(current_win => {
                let next_win = false;
                for (let i = 0; i < all_win.length; i ++) {
                    if (all_win[i].id === current_win.id) {
                        let i2 = i + 1;
                        if (i2 >= all_win.length) { i2 = 0; }
                        if (all_win[i2].incognito) { continue; }
                        next_win = all_win[i2];
                        break;
                    }
                }
                if (next_win !== false) {
                    browser.windows.update(next_win.id, {focused: true}).then(active_win => {
                        browser.tabs.move(movetabs_id, {windowId: active_win.id, index: -1}).then(moved_tabs => {
                            browser.tabs.update(moved_tabs[0].id, {
                                active: true
                            });
                        });
                    });
                }
            });
        });
    });
}, []);

 選択したタブ(複数選択可)を、次のウィンドウに送ります。ウィンドウが1つしかない場合は何もしません。
 これ書いてる途中に、次のウィンドウがプライベートウィンドウの場合には送られないことに気付いた。どうしよう。プライベートウィンドウはスキップするように書き直しました。

ナビゲーション

現在のウィンドウの全てのタブをリロードする

executeInBackground(() => {
  getCurrentWindowTabs().then(tabs => {
    //tabs に今のウィンドウのすべてのタブの情報が入る
    //配列なので for ループで回す
    for(var i=0; i<tabs.length; i++){
      browser.tabs.reload(tabs[i].id, {bypassCache: false});
    }
  });
}, []);

 browser.tabs.reload関数の第二引数のオブジェクトのfalse部分をtrueにすると「キャッシュを無視してリロード」になります。

タブを閉じる(Ctrlキー同時押しでピン留めタブも閉じる)

executeInBackground(ctrl => {
  getActiveTab(tab => {
    if(!tab.pinned || ctrl){
      browser.tabs.remove(tab.id);
    }
  });
}, [mouseDown.ctrlKey]);

 普通にタブを閉じます。Ctrlキー同時押しでピン留めタブも強制的に閉じます。
 ※もともと「ピン留めタブは閉じない」スクリプトを書いていましたが、デフォルトもピン留めタブを閉じないようになったらしいので、上記スクリプトに変更。

一つ上の階層(またはサブドメイン)に移動

executeInBackground(() => {
  getActiveTab(tab => {
    let f = true;
    let c_url = tab.url.replace(/\/$/,"");
    let parts = c_url.split("/");
    parts.pop();
    let url = parts.join("/");
    if(/:\/\/?$/.test(url)){
      parts = c_url.split("/");
      let domain = parts.pop();
      let parts2 = domain.split(".");
      parts2.shift();
      if(parts2.length <= 1){
        f = false;
      }else{
        url = parts.join("/") + "/" + parts2.join(".");
      }
    }
    if(f){browser.tabs.update(tab.id, {url: url});};
  });
});

 ディレクトリを登り切ったらサブドメインも登ります。
 なお、.jpとかのトップレベルドメインに到達してしまう場合は何もしません。なぜなら、トップレベルドメインまで突っ切ってしまうと「http://jp」となってしまいますが、この場合Firefoxさんが気を利かせて「http://www.jp.com」という罠しか待ち受けていなさそうなサイトにアクセスしようとするためです。これって仕様としてどうなんだろう。危なくね?

選択範囲

選択範囲をURLとして開く・検索する

var selstr = window.getSelection().toString();
if(selstr){
  //プロトコルで始まる場合は開く それ以外は検索
  if(!selstr.match(/^(http|file)/i)){
    selstr = 'https://www.google.co.jp/search?q=' + selstr;
  }
  executeInBackground(selstr => {
    getActiveTab(tab => browser.tabs.create({
      url: selstr,
      index: tab.index + 1,
      active: true
    }));
  }, [ selstr ]);
}

 選択範囲がURLなら、そのURLを開く。それ以外はGoogle検索。

選択範囲のリンクを開く

var selectionDOM = window.getSelection().getRangeAt(0).cloneContents(),
links = selectionDOM.querySelectorAll('a'), hrefs = [], i;
for(i=0; i<links.length; i++){
  hrefs.push(links[i].href);
}
hrefs = Array.from(new Set(hrefs));
for(i=0; i<hrefs.length; i++){
  var href = hrefs[i];
  if (href && href.match(/^(http|file)/i)) {
    executeInBackground((href,i) => {
      getActiveTab(tab => browser.tabs.create({
        url: href,
        index: tab.index + 1 + i,
        active: false
      }));
    }, [ href, i ]);
  }
}

 選択部分に含まれるリンクを一度に開く。かなり便利。
 http と file で始まるURLのみ開きます。mailto: とか tel: とかは無視。

選択範囲のリンク先の画像を全てダウンロード

var selectionDOM = window.getSelection().getRangeAt(0).cloneContents(),
anchors = selectionDOM.querySelectorAll('a'), hrefs = [],
ctrl = mouseDown.ctrlKey, i, img_src;
for(i = 0; i < anchors.length; i++){
  hrefs.push(anchors[i].href);
}
hrefs = Array.from(new Set(hrefs));
for(i = 0; i < hrefs.length; i++){
  img_src = hrefs[i];
  if (img_src && img_src.match(/^(http|file)/i)) {
    if (ctrl || img_src.match(/\.(jpe?g|gif|png|svg|webp|tif?f|bmp)$/i)) {
      executeInBackground((dl_url) => {
        browser.downloads.download({
          url: dl_url,
          saveAs: false
        });
      }, [img_src]);
    }
  }
}

 選択範囲に含まれるリンクのうち、画像にリンクされている分のみダウンロードします。
 Ctrlキーを押しながらジェスチャーすると、画像かどうかは確認せず全てダウンロードします。
 なお、ダウンロード先のディレクトリは設定画面「一般→ファイルとプログラム→ダウンロード」の「次のフォルダーに保存する」で指定した場所になります。(「ファイルごとに保存先を指定する」にチェックを入れている場合も同じ)

画像

画像を新しいタブで開く

var src = data.element.mediaInfo && data.element.mediaInfo.source;
//console.log(data);
if (src) {
  executeInBackground(src => {
    getActiveTab(tab => browser.tabs.create({
      url: src,
      index: tab.index + 1,
      active: !true
    }));
  }, [ src ]);
}

 99%公式サイトからコピペ。
 ただし新しいタブがアクティブにならないように active は false にしてある。

Twitterの画像のオリジナルサイズを別タブで表示

var src = data.element.mediaInfo && data.element.mediaInfo.source;
src = src.replace(/name=[^&]+/,"name=orig");
if (src) {
    executeInBackground(src => {
        getActiveTab(tab => browser.tabs.create({
            url: src,
            index: tab.index + 1,
            active: true
        }));
    }, [ src ]);
}

 昨年話題になったTwitterの見たくない情報を全消しするアドオン「おだやかTwitter」はChrome版のみですが、それより前からある「Minimal Twitter」はFirefox版も用意されているというのを知り(名前の通り「おだやかにする」と「ミニマルにする」というアプローチの違いがありますが、できることはほぼ一緒)、その恩恵を受けて絵師のTwitterを覗けるようになった次第です。なので書いた。
 Twitterにアップされている画像の上でジェスチャーしてください。バックグラウンドで開く場合はactivetruefalseに変更。

ダウンロード

右のタブを全てダウンロード

executeInBackground(ctrl=> {
  getActiveTab(tab => {
    var index = tab.index;
    getCurrentWindowTabs().then(tabs => {
      for(var i=index+1; i<tabs.length; i++){
        browser.downloads.download({
          url: tabs[i].url,
          saveAs: ctrl
        });
      }
    });
  });
}, [mouseDown.ctrlKey]);

 画像まとめブログで真価を発揮するおとこのジェスチャーです。
 デフォルトでは保存ダイアログは出ません。Ctrlキーを押しながらでブラウザーの設定に従います。(ファイルとプログラム>ダウンロード)

ツール

要素を消す

mouseDown.target.parentNode.removeChild(mouseDown.target);

 会員登録を迫るウィンドウがスクロールしてもついてきてウザい時などに。
 あとWeb制作の時にも意外とよく使う。

タイトルとURLをコピー

var ctrl = mouseDown.ctrlKey,
delim_plain = "\n", //タイトルとURLの間に入れる区切り 改行は\n
delim_markdown = "  \n", //上のMarkdown用
promise = executeInBackground((ctrl, dp, dm) => {
  return getActiveTab(tab => {
    if(ctrl){
      return "[" + tab.url + "]" + dm + "(" + tab.title + ")";//Markdown形式
    }else{
      return tab.title + dp + tab.url;
    }
  });
}, [ ctrl, delim_plain, delim_markdown ]);

promise.then(str => {
    var textarea = document.createElement("textarea");
    document.body.appendChild(textarea);
    textarea.value = str;
    textarea.select();
    textarea.focus();
    document.execCommand("copy");
    textarea.parentNode.removeChild(textarea);
});

 Ctrlキーを押しながらやると、Markdown形式でコピーするようにしてみた。
 ただ私はMarkdown形式を使ったことないので、コピペして爆発したらごめん。

同じウィンドウの全てのタブのタイトルとURLをコピー

var delim_title_url = " ", //タイトルとURLの間の区切り 改行は\n
delim_tabs = "\n", //タブとタブの間の区切り
promise = executeInBackground((d1, d2) => {
  return getCurrentWindowTabs().then(tabs => {
    var s = "";
    for(var i=0; i<tabs.length; i++){
      s += (s!=="" ? d2 : "") + tabs[i].title + d1 + tabs[i].url;
    }
    return s;
  });
}, [ delim_title_url, delim_tabs ]);

promise.then(str => {
    var textarea = document.createElement("textarea");
    document.body.appendChild(textarea);
    textarea.value = str;
    textarea.select();
    textarea.focus();
    document.execCommand("copy");
    textarea.parentNode.removeChild(textarea);
});

 上述のジェスチャーを全部のタブでやってると地味につらいので、とりあえず全部取得するタイプ。

更新履歴

2021-11-23
投稿から当ページに分離、目次作成とカテゴリ分け