Google chrome Extension(v3)でユーザスクリプト/ユーザスタイルシートを使いたい

Google Chrome の manifest v2 は 2023 年に廃止([参考](#参考)#1)が予定されている。
manifest v3 対応の独自 chrome 拡張の作成が必要になったが、自分の中でも上手く理解できていない部分もあるため、備忘を含めて最もシンプルな構成の拡張機能と目的に合致した構成の作り方について記載する。
最終的にタイトル記載のようにユーザスクリプトやユーザスタイルシートを利用しつつjQueryライブラリを併用する。

# 本記事の対象ユーザ
* シンプルな構成の manifest v3 対応の chrome 拡張を見たい人
* ユーザスクリプト(スタイルシート)を使っているが、後述の`代替手段`の拡張機能を利用できない人

# 環境や用語について
* Chromium 系について Google Chrome と表記する。
* Google chrome の拡張機能について chrome 拡張と表記する。
* manifest v2 を MV2、manifest v3 を MV3 と表記する。
* Firefox のアドオンも本記事ではまとめて拡張機能と表記する。

# 本記事の代替手段について
そもそもではあるが、本記事で行いたい事は下記拡張機能によって実現可能なので無理をして作る必要はない。これらは検索すれば多くの知見が存在するので環境構築も容易なので導入を検討されるのが良い。

1. ユーザスクリプト
    1. Google Chrome
       Tampermonkey: https://chrome.google.com/webstore/detail/tampermonkey/dhdgffkkebhmkfjojejmpbldmpobfkfo?hl=ja
    1. Firefox
       Greasemonkey: https://addons.mozilla.org/ja/firefox/addon/greasemonkey/
1. ユーザスタイルシート
    1. Google Chrome
       https://chrome.google.com/webstore/detail/stylish-custom-themes-for/fjnbnpbmkenffdnngjfgmeleoegfcffe?hl=ja
    1. Firefox
       https://addons.mozilla.org/ja/firefox/addon/stylish/

なお、Stylish は過去にプライバシーの取り扱いで行儀の良くない動作をさせた問題があったが、現在どのようになっているは確認していない。上記拡張機能を導入する際はセキュリティ上の懸念も検討や調査を行ってからインストールされるのが望ましい。

# 確認・実装の流れ
MV3 は公式のサンプルファイル群が [Github](https://github.com/GoogleChrome/chrome-extensions-samples)([参考](#参考)#2) に公開されているため、それらを交えて最小の構成から順を追って作成する。

1. 自作の chrome 拡張(パッケージ化されていない拡張機能) の登録方法
1. 公式サンプルの Hello, world
1. 公式サンプルの reading-time
1. ユーザスクリプト、ユーザスタイルシートを利用する最小の構成

## 自作の chrome 拡張(パッケージ化されていない拡張機能) の登録方法
まず、Google Chrome では `chrome://extensions/` にアクセスしてデベロッパーモードを有効にするとパッケージ化されていない拡張機能を登録することが可能である。
登録は自作の chrome 拡張の manifest.json を格納しているフォルダを指定して行う。

詳細については本記事の内容と離れてしまうため下記リンク先を参照。
* 自作Chrome拡張機能をテストする方法
  https://note.affi-sapo-sv.com/chrome-extent-test.php

## 公式サンプルの Hello, world
公式サンプルの [Hello, world](https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/examples/hello-world) について記す。
この chrome 拡張はインストールされた際に Hello, world というページを新タブで表示させる。
なお、下記コードの権利は github.com/GoogleChrome に帰属する。

```:構成
hello-world/
  + manifest.json  (1)
  + background.js  (2)
  + hello.html     (3)
```

```json:(1) manifest.json
{
  "name": "Hello, World!",
  "version": "1.0",
  "manifest_version": 3,
  "background": {
    "service_worker": "background.js"
  },
  "action": {}
}
```

> Manifest V3 replaces background pages with service workers.
https://developer.chrome.com/docs/extensions/mv3/intro/mv3-overview/#service-workers

MV2 との大きな相違点として、MV3 では常駐スクリプトが background pages から service_worker に変更された。
Service Worker は主にブラウザのキャッシュに関する概念のようだが、詳細が気になる方は参考のリンク先をご確認ください([参考](#参考)#3,4)。
なお、service_worker では起動対象スクリプトを複数記載できない。本記事では取り扱わないがスクリプトファイルを複数に分割している場合は `service_worker` のスクリプトから他のスクリプトファイルを `importScripts` で読み込む必要がある([参考](#参考)#5)。

```javascript:(2) background.js
chrome.runtime.onInstalled.addListener(async () => {
  let url = chrome.runtime.getURL("hello.html");
  let tab = await chrome.tabs.create({ url });
  console.log(`Created tab ${tab.id}`);
});
```
`background.js` は機能する部分のみ抜粋しているが、元ソースのコメントにてコードの要旨が書かれているため、気になる方は hello-world のソースコードを参照いただくのが良い。

`chrome.runtime.*` について、`chrome` は chrome 拡張機能に関する API のグローバルな名前空間([hello-worldコメント](https://github.com/GoogleChrome/chrome-extensions-samples/blob/main/examples/hello-world/background.js#L4))で、`runtime` はマニフェストの詳細情報の取得やイベントを Listen し応答するする機能を提供する名前空間([chrome.runtime - Chrome Developers](https://developer.chrome.com/docs/extensions/reference/runtime/))とのこと。

```html:(3) hello.html
<!DOCTYPE html>
<html>
<head>
  <title>Hello, World!</title>
</head>
<body>
  <p>Hello, World!</p>
</body>
</html>
```

拡張機能をインストールした結果は以下の通りとなる。
![スクリーンショット 2022-05-03 075046.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/186225ce-0473-b1c5-83cf-7ea7c1eff481.png)
`(1) manifest.json` でどのように動作するか定義し、`(2) background.js` で拡張機能登録時に `(3) hello.html` を新タブで開くことを記述していることが分かる。

## 公式サンプルの reading-time
公式サンプルの [reading-time](https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/tutorials/reading-time) について記す。
この chrome 拡張は chrome 開発ドキュメントの閲覧予測時間を表示する。
なお、下記コードの権利は github.com/GoogleChrome に帰属する。

```:構成
reading-time/
  + manifest.json  (1)
  + images/
    + icon-128.png
    + icon-16.png
    + icon-32.png
    + icon-48.png
  + scripts/
    + content.js  (2)
```

```json:(1) manifest.json
{
  "manifest_version": 3,
  "name": "Reading Time",
  "version": "1.0",
  "description": "Add the reading time to Chrome Extension documentation articles",
  "icons": {
    "16": "images/icon-16.png",
    "32": "images/icon-32.png",
    "48": "images/icon-48.png",
    "128": "images/icon-128.png"
  },
  "content_scripts": [
    {
      "js": [
        "scripts/content.js"
      ],
      "matches": [
        "https://developer.chrome.com/docs/extensions/*",
        "https://developer.chrome.com/docs/webstore/*"
      ]
    }
  ]
}
```
`content_scripts.matches` の条件に合致する URL で `content_scripts.js` の実行を定義している。

```javascript:(2) scripts/content.js
const article = document.querySelector("article");
if (article) {
  const text = article.textContent;

  const wordMatchRegExp = /[^\s]+/g;
  const words = text.matchAll(wordMatchRegExp);

  const wordCount = [...words].length;
  const readingTime = Math.round(wordCount / 200);
  const badge = document.createElement("p");

  badge.classList.add("color-secondary-text", "type--caption");
  badge.textContent = `⏱️ ${readingTime} min read`;

  const heading = article.querySelector("h1");
  const date = article.querySelector("time")?.parentNode;

  (date ?? heading).insertAdjacentElement("afterend", badge);
}
```
`scripts/content.js` については機能する部分のみ抜粋している。
`(1) manifest.json` の `content_scripts.matches` の条件に合致するページが開かれたらページ内のワード数をカウントし何分ぐらいで読めそうかの概算を提示している。
![スクリーンショット 2022-05-03 170040.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/3e4f2494-e7db-5277-0316-a6c52bc4dbc9.png)

## ユーザスクリプト、ユーザスタイルシートを利用する最小の構成
ここまでの内容から最小の構成として下記のファイルを作成してみる。
機能としては google.co.jp を開いた際に開発コンソールログに Hello, world と表示される事と、検索ボックスが黄色くなることの2点。

```:構成
sample\
  + manifest.json (1)
  + scripts\
    + onlaod_msg.js (2)
  + css\
    + test.css
```

```json:(1) manifest.json
{
  "manifest_version": 3,
  "name": "Google Top Page Test",
  "version": "1.0",
  "description": "Googleトップページでユーザスクリプト/ユーザスタイルシートテスト",
  "content_scripts": [
    {
      "js": [
        "scripts/onload_msgbox.js"
      ],
      "css": [
        "css/test.css"
      ],
      "matches": [
        "https://www.google.co.jp/*"
      ]
    }
  ]
}
```

```javascript:(2) scripts\onload_msg.js
console.log("Hello, world");
```
このスクリプトは表示対象のページのDOMツリー構築後に呼び出される。
もし、DOMツリー構築前にスクリプトを起動したい場合は `manifest.json` の `content_scripts.run_at`(今回は未使用) の値を `document_start` に設定する。なお、`run_at` のデフォルト値は `document_idle` である。

```css:(3) css/test.css
input[name=q] {
   background-color: yellow;
}
```

何てことはないがこのように作用させる事ができた。
![スクリーンショット 2022-05-03 174543.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/124578/b0d98afb-c991-aa3d-9cfa-147c93e767cb.png)

なお、`content_scripts.js` では複数の JavaScript ファイルを指定できる。順次処理される為必要なライブラリを先行して読み込ませる事も可能となる。
```json:manifest.json(一部)
      "js": [
        "scripts/jquery-2.2.4.min.js",
        "scripts/onload_msgbox.js"
      ],
```

# 参考
記事内で参照しているもの、参照していないが参考とさせていただいたもの。

1. Google、「Manifest V2」準拠のChrome拡張を段階的に廃止 ～2023年には利用不能に - 窓の杜
   https://forest.watch.impress.co.jp/docs/news/1353788.html   
1. GitHub - GoogleChrome/chrome-extensions-samples: Chrome Extensions Samples
   https://github.com/GoogleChrome/chrome-extensions-samples
1. Service worker overview - Chrome Developers
   https://developer.chrome.com/docs/workbox/service-worker-overview/
1. サービスワーカーの使用 - Web API | MDN
   https://developer.mozilla.org/ja/docs/Web/API/Service_Worker_API/Using_Service_Workers
1. Service Worker のインポートを理解する - 30歳からのプログラミング
   https://numb86-tech.hatenablog.com/entry/2021/05/26/122742
1. Welcome to Manifest V3 - Chrome Developers
   https://developer.chrome.com/docs/extensions/mv3/intro/
1. Migrating to Manifest V3 - Chrome Developers
    https://developer.chrome.com/docs/extensions/mv3/intro/mv3-migration/
1. Chrome拡張機能 manifest.json Ver.3の書き方 - Qiita
   https://qiita.com/shiro1212/items/12f0a767494a7b2ab0b3
1. [Chrome拡張機能]manifest.json(ver3)と対応する要素まとめ - Qiita
   https://qiita.com/n_yamadamadamada/items/4e469073486e11babddb

# 記事内で利用しているコードについて
記事内には github.com/GoogleChrome/chrome-extensions-samples リポジトリのソースコードを引用、抜粋させていただいた。該当のリポジトリの内容は [Apache License 2.0](https://github.com/GoogleChrome/chrome-extensions-samples/blob/main/LICENSE)として公開されている。

# 最後に
もう少しつまずくかと思ったが先駆者も多く特に困る事もなかった。既に多くの方が記事にしており書き終わる頃にはそもそも今回書く必要すらないように感じたが、あとで自分が忘れた時の足がかりにしたく公開する。
内容に誤りがあればコメントをいただけると幸いです。