Firefoxのソースを探索してマウスジェスチャーを追加する模索記

# 動機

[FireGestures](http://www.xuldev.org/firegestures/)が使える[WaterFox Classic](https://www.waterfox.net/download/)を愛用していたけど、そろそろFirefoxにするか。
↓
Foxy Gesturesの反応がなかったり遅れたりする時がある（キーボード操作だと即座に実行される）。
web extensionが悪そう。
Firefoxのソースにマウスジェスチャーを仕込みたい。
とりあえずビルドから←[前回の記事](https://qiita.com/horyu/items/b39f79b2ec1cf155061b)


# 闇雲

## マウスクリックを制御しているコードを含むディレクトリを探す

Firefox ほど大きいプロジェクトならば、ディレクトリ構成を解説しているページがあるでしょう

あった [Firefox Source Code Directory Structure — Firefox Source Docs documentation](https://firefox-source-docs.mozilla.org/contributing/directory_structure.html)

> __browser__
> Contains the front end code (in XUL, Javascript, XBL, and C++) for the Firefox browser. Many of these files started off as a copy of files in xpfe/.

かなり上にありましたがビンゴでしょう。

## browser ディレクリでマウスクリックを制御しているコードがあるか確かめる

マウスジェスチャーということで、 `right click` や `context menu` とかで適当に検索してみる。

```
PS C:\mozilla-source\mozilla-unified\browser> rg "right click"
locales\en-US\chrome\browser\accounts.properties
41:# Displayed in the Send Tab/Page/Link to Device context menu when right clicking a tab, a page or a link.
45:# Displayed in the Send Tab/Page/Link to Device context menu when right clicking a tab, a page or a link.
49:# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link
62:# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link
71:# Displayed in the Send Tabs context menu when right clicking a tab, a page or a link

components\urlbar\UrlbarSearchOneOffs.jsm
346:    // Ignore right clicks.

components\urlbar\UrlbarView.jsm
2088:      // Ignore right clicks.
2106:      // Ignore right clicks.

components\urlbar\UrlbarInput.jsm
430:      // Do nothing for right clicks.

components\search\content\searchbar.js
532:        // Ignore right clicks

components\search\content\autocomplete-popup.js
63:          // Ignore right clicks.

components\search\SearchOneOffs.jsm
1186:      return; // ignore right clicks.

base\content\test\general\browser_save_link-perwindowpb.js
29:      info("right clicked!");

base\content\test\contextMenu\browser_contextmenu_loadblobinnewtab.js
14:// Helper method to right click on the provided link (selector as id),

base\content\nsContextMenu.js
1599:    // setting up a new channel for 'right click - save link as ...'

base\content\browser.js
6819:    // right click

base\content\browser-ctrlTab.js
619:          // Control+click is a right click on macOS, but in this case we want

components\newtab\lib\ToolbarBadgeHub.jsm
105:      // ignore right clicks

components\places\content\places.js
408:   * Single Left click, right click or modified click do not result in any

components\preferences\preferences.js
167:        // Ignore right clicks.

components\customizableui\test\browser_984455_bookmarks_items_reparenting.js
39: *        right click on in order to open the context menu.
```

パスと1行だけだと分からん。
VS Codeでbrowserディレクトリを開き、`right click`で検索しながらファイルを眺めていく。

そもそもJSMファイルって何？大体JSファイルの見た目をしている。

- [javascript - .jsm vs .js files - Stack Overflow](https://stackoverflow.com/questions/2591368/jsm-vs-js-files)
- [JavaScript コードモジュールの利用 - Mozilla | MDN] (https://developer.mozilla.org/ja/docs/Mozilla/JavaScript_code_modules/Using)

なるほど、他のJSファイルやJSMファイルにインポートされるモジュールなJSファイル。

VS Codeに戻ってファイルを見ていく。

1つ目 `base\content\browser-ctrlTab.js`

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186959/0fbee166-c2ce-49f4-91d2-fca7d837d974.png)

ファイル名や変数からして明らかにタブ周りで違う。でもタブクリックはマウスジェスチャーのように遅延とかないから、後で参考になるかも。


2個目の `browser\base\content\browser.js` がビンゴか？

```js
/**
 * Handles clicks on links.
 *
 * @return true if the click event was handled, false otherwise.
 */
function handleLinkClick(event, href, linkNode) {
  if (event.button == 2) {
    // right click
    return false;
  }

  var where = whereToOpenLink(event);
  if (where == "current") {
    return false;
  }

  var doc = event.target.ownerDocument;
  let referrerInfo = Cc["@mozilla.org/referrer-info;1"].createInstance(
    Ci.nsIReferrerInfo
  );
  if (linkNode) {
    referrerInfo.initWithElement(linkNode);
  } else {
    referrerInfo.initWithDocument(doc);
  }

  if (where == "save") {
    saveURL(
      href,
      linkNode ? gatherTextUnder(linkNode) : "",
      null,
      true,
      true,
      referrerInfo,
      doc.cookieJarSettings,
      doc
    );
    event.preventDefault();
    return true;
  }
```

`handleLinkClick` の使用箇所へジャンプ

```js
/**
 * Called whenever the user clicks in the content area.
 *
 * @param event
 *        The click event.
 * @param isPanelClick
 *        Whether the event comes from an extension panel.
 * @note default event is prevented if the click is handled.
 */
function contentAreaClick(event, isPanelClick) {
```

`contentAreaClick` の使用箇所へジャンプ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/186959/65fd4a96-cf7e-c9f6-38d8-a23faf87838c.png)

グローバルなブラウザー初期化のonLoad処理でシステムのイベントリスナーに登録。
マウス制御はここ（`browser\base\content\browser.js`）でしょ。

UI周りはC++ではなくJSやSMで制御してるんだ。


## ロッカージェスチャー（コードジェスチャー）の追加を目指す

右クリック押しっぱなしでマウスを移動するよくあるマウスジェスチャーをできるようにしたいが、
いきなりは難しいのでロッカージェスチャー（右ボタン押しっぱで左ボタンクリックやその逆）ができるようにする。
あらためて `contentAreaClick` を見てみる。

```js
/**
 * Called whenever the user clicks in the content area.
 *
 * @param event
 *        The click event.
 * @param isPanelClick
 *        Whether the event comes from an extension panel.
 * @note default event is prevented if the click is handled.
 */
function contentAreaClick(event, isPanelClick) {
  if (!event.isTrusted || event.defaultPrevented || event.button != 0) {
    return;
  }
```

`event.button != 0` 左クリックではないのなら return ということで右押しっぱで左クリックはできなさそう。
というかJSDocも関数名も見たら `click event` とあるしクリックイベントが対象だったこれ。
クリック終了したら普通に中でクリック処理が完了してしまうだろうし違う。
`mousedown event` や　`mouseup event`を探す。

色々調べても見つけられないので方針転換。
右クリックのmouseupかclickでコンテキストメニューを開くはずだから、その部分を探す。

探すも見つけられず。

今のFirefoxのページ表示部分はページ読込中は右クリックしても反応しないが、タブ領域とかであれば反応する。
アプローチを変えたほうがよさそう。
など考えながら `browser\base\content\browser.js` を適当に眺める。

```browser\base\content\browser.js
var gBrowserInit = {
// 略
  onDOMContentLoaded() {
    // This needs setting up before we create the first remote browser.
    window.docShell.treeOwner
      .QueryInterface(Ci.nsIInterfaceRequestor)
      .getInterface(Ci.nsIAppWindow).XULBrowserWindow = window.XULBrowserWindow;
    window.browserDOMWindow = new nsBrowserAccess();

    gBrowser = window._gBrowser;
    delete window._gBrowser;
    gBrowser.init();
```

DOMを読み込み終わるまでFoxy Gestures(webextensions)が反応しないのは、onDOMContentLoaded() とかあるし多分ここか。
`onDOMContentLoaded()` が終わったらwebextensionsとの橋渡しが終るのかな。

前後を眺める。

```browser\base\content\browser.js
var gBrowserInit = {
// 略
  onLoad() {
    gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
    window.addEventListener("AppCommand",HandleAppCommandEvent, true);
// 略
}

function HandleAppCommandEvent(evt) {
  switch (evt.command) {
    case "Back":
      BrowserBack();
      break;
// 略
      case "Close":
      BrowserCloseTabOrWindow();
      break;
```
DOMの読み込み前にキーボード操作は受け付けるのは、onLoad() で gBrowser に AppCommand のイベントリスナーを登録しているからか。
ここのonLoad() にマウス操作を監視する機能をつけられたら、マウスジェスチャーを登録できそう。

テストもかねて onLoad() を以下のようにしてみる。

```diff:browser\base\content\browser.js
var gBrowserInit = {
// 略
  onLoad() {
    gBrowser.addEventListener("DOMUpdateBlockedPopups", gPopupBlockerObserver);
    window.addEventListener("AppCommand",HandleAppCommandEvent, true);
+   window.addEventListener("mousedown", BrowserCloseTabOrWindow, true);
```

 `./mach build && ./mach run` ビルドして実行


🎉🎉🎉🎉ブラウザのどこマウスクリックしてもウィンドウかタブが閉じられるようになった🎉🎉🎉🎉

## ロッカージェスチャーを仕込む

以下を実装します。

- 右クリック押しっぱなしのときに左クリックでタブかウィンドウを閉じる
- 左クリック押しっぱなしのときに右クリックで直前にフォーカスしたタブに切り替える

コード例はこちら。

```js
let isDownLeftButton = false;
let isDownRightButton = false;
let disalbeContextMenu = false;
window.addEventListener("contextmenu", (e) => {
  if (disalbeContextMenu) {
    disalbeContextMenu = false;
    e.preventDefault();
    e.stopPropagation();
  }
}, true);
window.addEventListener("mousedown", (e) => {
  if (e.button === 0) {
    if (isDownRightButton === true) {
      BrowserCloseTabOrWindow();
    }
    isDownLeftButton = true;
  }
  else if (e.button === 2) {
    if (isDownLeftButton === true) {
      disalbeContextMenu = true;
      let tabs = gBrowser.visibleTabs;
      if (tabs.length > 2) {
        gBrowser.selectedTab = ctrlTab.tabList[1];
      } else if (tabs.length == 2) {
        let index = tabs[0].selected ? 1 : 0;
        gBrowser.selectedTab = tabs[index];
      }
    }
    isDownRightButton = true;
  }
}, true);
window.addEventListener("mouseup", (e) => {
  if (e.button === 0) {
    isDownLeftButton = false;
  }
  else if (e.button === 2) {
    isDownRightButton = false;
  }
}, true);
```

上記のコードを `gBrowserInit` の `onLoad()` 中に追記。

`./mach build && ./mach run` を実行したらロッカージェスチャーが動作🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉🎉

タブ周りの探索はかなり疲れたので端折ります。以下のような流れ。

- `ctrl-tab-recently-used-order` で検索

```components\preferences\main.inc.xhtml
    <checkbox id="ctrlTabRecentlyUsedOrder" data-l10n-id="ctrl-tab-recently-used-order"
              preference="browser.ctrlTab.recentlyUsedOrder"/>
```

- `recentlyUsedOrder` で検索して `base\content\browser-ctrlTab.js` を見つける
- 適当に眺めて以下のコードで訝しむ

````base\content\browser-ctrlTab.js
  onKeyDown(event) {
    let action = ShortcutUtils.getSystemActionForEvent(event);
    if (action != ShortcutUtils.CYCLE_TABS) {
      return;
    }

    event.preventDefault();
    event.stopPropagation();

    if (this.isOpen) {
      this.advanceFocus(!event.shiftKey);
      return;
    }

    if (event.shiftKey) {
      this.showAllTabs();
      return;
    }

    Services.els.addSystemEventListener(document, "keyup", this, false);

    let tabs = gBrowser.visibleTabs;
    if (tabs.length > 2) {
      this.open();
    } else if (tabs.length == 2) {
      let index = tabs[0].selected ? 1 : 0;
      gBrowser.selectedTab = tabs[index];
    }
  },
```

- 最後の方の切り替えていそうな部分を `browser\base\content\browser.js` に仕込んでタブの切り替えだと分かる。
- ぱくって実行したらBINGO


# 後書き

当たりをスパスパとは引けず、結構疲れた。
どう探せば良いのか分からなくなって、適当に周りのコードを読んでるときに偶然処理を見つけられて助かった。

`browser\extensions` にフォルダを作って追加した方が作法としては良さそうな印象を受けたが、
でも自分専用だから `browser\base\content\browser.js` をいじるだけになりそう。

ロッカージェスチャーだけではマウスジェスチャーと呼ぶにはあまりにも足りない。
普通のよくあるマウスジェスチャーの方向検知などはどう処理しているのか参考にしたい。
例えばVivaldiは組み込みでマウスジェスチャーを実装しているから、参考にできるかもしれない。
[Custom Mouse Gestures in Vivaldi 1.2 | Vivaldi Browser](https://vivaldi.com/blog/vivaldi-1-2-with-mouse-gestures/)