Yappli Tech Blog

株式会社ヤプリの開発メンバーによるブログです。最新の技術情報からチーム・働き方に関するテーマまで、日々の熱い想いを持って発信していきます。

重複するページを開く時に確認するChrome拡張を作った

こんにちは、サーバサイドエンジニアの中川です。
この記事を執筆しているのは2022年12月なのですが、12月といえばアドベントカレンダーですよね!
というわけでこの記事は ヤプリ #1 Advent Calendar 2022 の5日目の記事になります。

自分はChromeをよく使いますがChrome拡張はいくつかインストールしてお世話になってるものの自分で作ったことはなく、 どうやって作るのか知らなかったのでアドベントカレンダーの執筆とChrome拡張の勉強を兼ねて作ってみようと思いました!

作ったもの

下記のキャプチャをみてもらったらわかると思うのですが、同一ウィンドウで同じページを開こうとすると確認用ダイアログを出すChrome拡張を作りました。

なぜ作ったか

エンジニアあるあるだと思うのですが、作業中にいつの間にかブラウザのタブを開きすぎてページタイトルが隠れてしまうことがしばしばありました。
それによって困ることとして「さっき開いていたページをみたいのにどのタブで開いているかわからない」という点がありました。1つ1つ探すのも面倒なのでまた新規タブを開いてアクセスしたりしていました。
また自分は作業の区切りがいいときに一度タブを一気に消すこともあるのですが、その時に意図せず同じページを開いていたりすることがありました。
そこで「同一ウィンドウ上で同じページを開こうとしたらモーダルを出して確認する」ことができれば嬉しいと思い、作ってみました。

実装

ディレクトリ構成

project
├ manifest.json
├ background.js
└ content_script.js

ソースコード

manifest.json

{
    "name": "同じページチェッカー",
    "description": "同一ウィンドウで複数の同じページを開くか確認します",
    "version": "1.0.0",
    "action": {},
    "manifest_version": 3,
    "background": {
        "service_worker": "background.js"
    },
    "permissions": [
        "tabs"
    ],
    "content_scripts": [
        {
            "run_at":"document_end",
            "matches": ["<all_urls>"],
            "js": [
                "content_script.js"
            ]
        }
    ]
}

background.js

chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
  chrome.tabs.query({ active: true, currentWindow: true })
    .then(activeTab => {
      switch (msg.name) {
        case 'pageCheck':
          chrome.tabs.query({ currentWindow: true })
            .then(tabs => {
              tabs.forEach(tab => {
                if (activeTab[0].id === tab.id) {
                  return;
                }
                if (activeTab[0].url === tab.url) {
                  sendResponse({
                    'currentTabID': activeTab[0].id,
                    'tabID': tab.id
                  });
                  return;
                }
              });
            });
          break;
        case 'removeTab':
          chrome.tabs.remove(msg.currentTabID);
          chrome.tabs.update(msg.tabID,{ selected:true });
          break;
    }
  });
  return true;
})

content_script.js

chrome.runtime.sendMessage({ name: 'pageCheck' })
  .then((response) => {
    if (response !== undefined) {
        const res = window.confirm('同じページを別のタブで開いているけどそっちでみる?');

        if (res) {
            chrome.runtime.sendMessage({ name: 'removeTab', tabID: response['tabID'], currentTabID: response['currentTabID'] });
        }
    }
  }
).catch(console.log);

やってること

言葉で言うと下記の流れです。

1. manifest.jsonにページを開くとcontent_script.jsを実行するように定義
2. content_script.js:1 にてbackground.jsにメッセージを送信する
3. メッセージを受け取ったbackground.jsが現在アクティブなウィンドウ内のすべてのタブのURLと開いたページのURLを比較する
4. 3でもし開いたページと同じURLが既にウィンドウ内のタブに含まれていればレスポンスとして現在開いているページとウィンドウ内に存在しているページのタブIDをcontent_script.jsに返す
5. content_script.jsはbackground.jsからレスポンスが返ってきた場合、確認用アラートを表示
6. 重複するタブを閉じる操作が選択された場合、4.で受け取ったIDを再度background.jsに渡す
7. background.jsにて現在開いているページを閉じ、アクティブなページをウィンドウ内に存在しているページに設定する

とこんな感じです。言葉で説明するとわかりづらいかもしれませんが、要は同じページを開こうとしたらアラートを出して確認し、元々開いているページで閲覧する場合は開こうとしたページと入れ替えています。

使い方

1. 上記のディレクトリ構成に沿って各ファイルを作成します
2. chrome://extensions/ にアクセスします。
3. 右上の [デベロッパー モード] をオンにします。
4. [パッケージ化されていない拡張機能を読み込む] をクリックします。
5. 1.で作成したディレクトリを読み込みます
6. Chrome で新しいタブを開き拡張機能が読み込まれ正常に動作していることを確認します。

本当はストア公開までやろうと思ったのですがどうやら手数料として5ドルかかるみたいなので、もう少し作り込んでから公開する予定です!

ハマったこと

自分は今回初めてchrome拡張を作ったのですが、content_scriptsとbackground.jsで呼び出せるメソッドが違うことに特にハマりました。
例えば window.confirm() はcontent_script.jsからだと呼び出せるがbackground.jsからは呼び出せないなどです。 そういう時にどうすればいいか迷ったのですが、sendMessage()sendResponse()をつかってメッセージの送受信をしながら実現していくことを学べました。 また、今回はmanifest_version = 3で開発をしていたのですが、v2やv3の情報が混在しており、v2だと使えるがv3だと使えなかったり名前が変わっていたりするメソッドもあったので戸惑いました。

まとめ

今回は作り方を学びながら同じページを開こうとすると確認ダイアログを出すChrome拡張を作りました。
たまたまなのですが、この記事を執筆していた時に無意識に同じタブを開こうとして検知してくれたのに感動しましたw
Chrome拡張の作り方を学ぶのが目的だったのですが、自分の解決したい課題の解決にもつなげられてよかったです。
初めて作ったのでコードの中にお粗末な点があるかもしれませんが、その際にはコメントにてご指摘いただけると幸いです。

参考