GitHubのリンクが同じ画面で開かれるのがしんどかったのでChromeの拡張機能作った

久々のQiita投稿🙌
普段はその辺でエンジニアやってる物です。

個人で使うめちゃくちゃニッチな物を作ったので、せっかくなので記事にしてみました。
そしてここから下、**「cmd + クリックでええやん」**禁止です🙅‍♂️
それすらめんどくさかったので作った所存です。

# 何を作ったの？
GitHub上でリンクをクリックした時に遷移先が同じ画面で開かれるのがしんどかったのでChromeの拡張機能を作った話です。
![スクリーンショット 2020-09-21 11.19.17.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/66057fd4-580f-f2b4-bdb6-56c8b553d14a.png)

作ったものはこちら。
Chromeウェブストアに公開しているので、よかったらダウンロードして使ってみてください。

https://chrome.google.com/webstore/detail/github-target-blank/bhikeadioahfjdfnckphpmfjkpmingah

ちなみに、2020年9月現在のGitHub上のクラスやIDを利用しているので、GitHub上のHTML構造が変更された場合**突然動かなくなることも考えられます。**
メンテを続けるかどうかはまだ決めてません。

# GitHubのしんどさとは
コードレビューをGitHub上で行う時に、PR画面のConversation内にコミットハッシュを書くと自動でリンクになってくれる便利機能があります。
こんな感じですね。

![スクリーンショット 2020-09-15 16.56.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/0339efcb-c55a-d6b8-75e6-81a291def73d.png)


マウスオーバーでコミットログとかみられるんですけど、見たいのはそこじゃない、
**そのcommitでどのファイルをどうやって修正しているか**なんだ。

あとこんな時。
**こんなものに他の方に協力仰ぐのも申し訳なかったので以下全部自演コメントです🙇‍♂️**

![スクリーンショット 2020-09-21 11.12.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/4e5f152a-20b5-68ec-66b5-babc58aacae6.png)

![スクリーンショット 2020-09-21 11.13.07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/535324/f65190ba-6439-3667-3f49-ac4669b4c269.png)


こんなやりとり数多くあったのでGitHubユーザの方ならきっと想像できるはず。

レビュアーとしては、
**「お、修正してくれたんだ〜〜修正内容どんな感じかな〜〜〜〜」**って
**コミットログのリンク**を押して確認しようとすると、どういうわけか**同じタブで画面遷移します**

もちろん、戻るボタンで戻っても入力したコメントとかは消えないですよ？

でも普通に考えると、**別タブで開いて修正履歴じっくり見たい時のが多く無いですか？**

あと、他にもPRの概要を読みながらおもむろに`Files changed`タブとか押して**PR全体のファイル変更履歴とか見たくないですか？**

そんな要望に応えることができるChrome拡張を作りました。
その名も`github-target-blank`です。

# 工夫したところ

アプローチとしてまず考えたのが、
**別タブで開きたいa要素に対して `target='blank'`を動的に付与する**
というやり方。
(rel='no-oppener no-referrer'も一緒に)

GitHubのページの特定のa要素のみ`target='blank'`を付与するだけなので、そこまで大変では無いやり方ですね。

ただこのやり方だと、**開かれたページがアクティブな状態**になります。
そう、PRのConversationを見ているのにリンクを押すと別のタブがアクティブになってしまうわけですね。

違う、違うんだ。
僕が欲しいのは**Google Chromeで cmd + クリックでリンクを押した時のバックグラウンドでタブが開かれるあの動き**なんだ。

というわけでこのアプローチは断念。
別のアプローチをなんか無いかなーと探していたところ、こんなメソッドを発見。

https://developer.mozilla.org/ja/docs/Web/API/MouseEvent


>MouseEvent インターフェイスは、ポインティングデバイス (マウスなど) によるユーザの対話によって発生したイベントを表します。
このインターフェイスを使用する一般的なイベントとして click, dblclick, mouseup, mousedown があります。

ふむふむ。あれこれなんか使えるくね？
っていうか、擬似的にa要素作って、MouseEventで擬似a要素をDispatchしちゃえばいいんじゃね？

って思って探したらありました。さすがはStackOverFlow。
https://stackoverflow.com/questions/10812628/open-a-new-tab-in-the-background

**注意**
ここで紹介されてる`initMouseEvent`はDeprecatedのメソッドのため、利用非推奨です。
代替機能として、`MouseEvent`が推奨されてます。

おーできそう、ってことでコード書いてみました。
メインの部分はこんな感じ。
ChromeのExtensionにはjQueryも使えるっぽいけど、サイト内でバージョン競合とか起こすと嫌なので久々にVanillaJS。

```js
hoge.addEventListener("click", function (e) {
  const evt = new MouseEvent("click", {
    metaKey: true, // Macだとcmdキーが metaKeyとして判定される学び
    ctrlKey: true, // Windowsだとバックグラウンドで開く時に使うのがctrlキーなのでこちらも設定
  });
  const a = document.createElement("a");
  a.href = e.currentTarget.url; // 遷移先のURL
  a.dispatchEvent(evt); // ここでMouseEventイベントを擬似的に作ったa要素に向けて発火
  e.preventDefault(); // 本来のa要素での移動をキャンセル
});
```

<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">急に思い立ってChromeのextensionを作り始めた。MacのCmdキーはMetaKey扱いだということを知った。<a href="https://t.co/V5ybBD2mND">https://t.co/V5ybBD2mND</a></p>&mdash; yoshi.@code-plum (@codeplumdev) <a href="https://twitter.com/codeplumdev/status/1302602383470411776?ref_src=twsrc%5Etfw">September 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

Macの場合cmdキーがmetaKeyになるとか知らなんだ。。。20分ぐらいハマりました。

ってなわけで、GoogleChromeでcmd(ctrl) + クリックした時にバックグラウンドでタブが開くあの動きが再現できました、わーい。

# 苦労したところ
**ChromeExtensionの仕組みわからなすぎ問題**
開発にあたり一時間ぐらいこのページとにらめっこしてました。

https://developer.chrome.com/extensions/getstarted

三つの世界の世界観がマジでわからなかったので、@sakaimoさんの記事を参考にしました。圧倒的感謝😭🙇‍♂️💦
https://qiita.com/sakaimo/items/416f36db1aa982d8d00c

特定のページだけで利用したい時は`Content Scripts`、
アイコン(Extensionインストール後に出てくる右上のあれ)クリック時に何かしたい時は`Browser Action (Page Action)`、
Chrome起動中ずっと動かしときたい時は`Event Page`って区分のようです。

今回はGitHub上で動けばよかったので`Content Script`を採用してましたが、**そのうち「これはバックグラウンドでこれは普通に開きたい」とかあるだろうなぁ**という気持ちがあったため、extensionの設定をローカルストレージに保存するようにするために、

popup.js上で設定変更
↓
background.jsにイベント(任意のメッセージ)を送信
↓
background.js内で設定内容をローカルストレージに保存
↓
contents.js発火時にローカルストレージ上の設定をbackground.js経由で取得
↓
有効な設定要素のみ、addEventListenerを実行

っていうフローを取ってます。結局全部使ってる。

**pjax対応**
GitHubのHTMLElementsを眺めてるとどうやらpjaxが利用されているようで、ページ遷移した時にうまく動かないバグがありました。
この辺りは`MutationObserver`を使ってDOMを監視し、変更されたタイミングで再度content.jsを発火するようにしています。

```js
const target = document.getElementById(observerSettings.selector);
// 現状だと github.com がホストに含まれるときは実行するようになっているため、
// 特定ページ意外で発火したときのエラーを防ぐために、getElementByIdで取得した要素が存在する場合のみ
// MutationObserverを利用するようにしている
if (target) { 
  const observer = new MutationObserver((mutations) => {
    // ここで任意の処理を発火することができるので、メイン処理を再度実効
  });
  observer.observe(target, observerSettings.config);
}
```

# さいごに
久々にVanillaのJSでコーディングしたり、Chromeの拡張機能の仕組みに混乱したり、
**「よっしゃできたーーー」ってなってたらinitMouseEventが利用非推奨なことに気がついて全部作り替えること**になったりしましたが、
ChromeのExtensionは手軽にコーディングできる良い環境だと思いました。

なんだか、**GoogleChromeで急に回転するポプ子とピピ美**が見たくなった気がしたので、クソアプリアドベントカレンダーにでもエントリーしようかなと思った今日この頃。

なんやかんや言ってますが楽しくコーディングできました。
皆さんも、是非ChromeのExtensionを作ってみては如何でしょうか。
コードレビュー時の**あーーーーーいまのページ移動しないで欲しいのーーー**も解消されたので、本日はここまでにします。

同じような悩みがある方、是非使ってみてください。
ご意見ご感想PRお待ちしております。


# おまけ
<blockquote class="twitter-tweet"><p lang="ja" dir="ltr">引数15個の公式メソッドは初めて見た。<br>流石にヤバイなと思って調べてたら非推奨の機能でクラス化されてて安心した<a href="https://t.co/SreRgLVtye">https://t.co/SreRgLVtye</a><a href="https://t.co/0v9UtB5xw5">https://t.co/0v9UtB5xw5</a></p>&mdash; yoshi.@code-plum (@codeplumdev) <a href="https://twitter.com/codeplumdev/status/1302609411366481920?ref_src=twsrc%5Etfw">September 6, 2020</a></blockquote> <script async src="https://platform.twitter.com/widgets.js" charset="utf-8"></script>

・・・きっと大変なことがあったんだろうと思いましたまる