ChromeExtensionでポップアップからコンテンツへメッセージ連動させる

前回記事[日々の業務をプチハック - Chrome Extension(拡張機能)を作ろう！](./cf983b70d0fa4efd8718)では、ポップアップとコンテンツスクリプトの用意までを確認しました。

今回はポップアップとコンテンツを連動させます。

## 仕様の整理

簡単に、こんな仕様で動作するものを作っていきます。
ポップアップ側で入力した値で、Googleの画面を操作する実装をしていきます。

```mermaid
sequenceDiagram
  ポップアップ->>ポップアップ: 検索ワードを手入力
  ポップアップ->>コンテンツ: ボタン押下で検索ワードを送信
  コンテンツ->>コンテンツ: スクリプトで検索ワードを設定
  コンテンツ->>コンテンツ: スクリプトで検索ボタンを押す
  コンテンツ->>ポップアップ: 処理完了を通知
```

## manifest.jsonの用意

まずは `manifest.json` を用意します。
ポップアップ用(`action`配下)とコンテンツ用(`content_scripts`)を定義しています。

```json:manifest.json
{
  "manifest_version": 3,
  "name": "ez-hacking",
  "description": "chrome拡張のテスト: ポップアップからコンテンツ操作",
  "version": "0.0.1",
  "permissions": ["activeTab", "scripting"],
  "action": {
    "matches": ["https://www.google.com/"],
    "default_popup": "popup.html"
  },
  "content_scripts": [
    {
      "run_at": "document_end",
      "matches": ["https://www.google.com/"],
      "all_frames": true,
      "js": ["background.js"]
    }
  ]
}
```

### ポップアップ画面の作成

検索ワード入力、検索ボタンだけを配置したシンプルな画面を用意します。

```html:popup.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>chrome拡張サンプル</title>
  </head>
  <body>
    <input id="searchWord" />
    <button type="submit" id="searchButton">検索</button>

    <script src="popup.js"></script>
  </body>
</html>
```

なおJavaScriptは `popup.js` に切り出しています。

### ポップアップ側スクリプト

ポップアップ側の JavaScript を記述します。
ポップアップ側で入力した値をコンテンツに送るためのスクリプトです。

```javascript:popup.js
// 検索ボタンクリック時
document.querySelector("#searchButton").addEventListener("click", () => {
  // 検索ワードの取得
  const searchWord = document.querySelector("#searchWord").value;

  // 対象のタブを特定して、コンテンツに送信
  chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
    chrome.tabs.sendMessage(tabs[0].id, searchWord, (responseFromContent) => {
      // コンテンツから受け取ったレスポンスをダイアログ表示
      alert(responseFromContent);
      // ポップアップを閉じる(コンテンツではない)
      window.close();
    });
  });
});
```

`Chrome extension`自体は普通のHTML/CSS/JSを処理できるので、`jQuery`などの外部ライブラリも使えます。
(今回は登場する技術要素を減らすため、生JSを使います)

`chrome.tabs.query` で対象のタブを取得し、クエリ結果の1番目のタブIDを取得して `chrome.tabs.sendMessage` でメッセージを送信します。
この例だと `{ active: true, currentWindow: true }` を条件にしているので、現在のタブに対して操作することになります。
意図しない操作を避けるため、クエリ条件にURLを指定することもできます。

## DOM操作

コンテンツ側の画面操作をするスクリプトを実装していきます。

### コンテンツ側のスクリプト

ポップアップから検索ワードを受け取るリスナーを登録しましょう。
`chrome.runtime.onMessage.addListener`を使います。

- 第1引数: ポップアップから送られるパラメータ(今回は検索ワード)
- 第2引数: イベント情報。今回は使いません。
- 第3引数: ポップアップへメッセージを返すためのコールバック関数

を受け取ります。

```javascript:background.js
// ポップアップからのメッセージを受け取るためのリスナー登録
chrome.runtime.onMessage.addListener((searchWord, _ev, sendResponse) => {
  // 検索ワードをセットして検索
  document.querySelector("input[autofocus]").value = searchWord;
  document.querySelector('input[type="submit"]').click();

  // ポップアップにメッセージを返す
  sendResponse(`search: ${searchWord}`);
});
```

受け取った検索ワードを使って入力ボックスにセットし、検索ボタンを押します。
その後、受け取った検索ワードを加工してポップアップ側に返すようにしています。

### ポップアップから画面へメッセージ送信

ではソースを再読込してから、ポップアップをクリックして検索ワードを手入力します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104999/8fbdae70-1c74-94bc-fffe-67d3b7b8ec52.png)

入力して「検索」ボタンを押すと、コンテンツ側に検索ワードが渡されて検索することができました！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3104999/a43b2337-fe91-4429-7dca-5adce41bf389.png)

検索結果画面に切り替わり、コンテンツ側で加工した文字列がalert表示されたので、ポップアップに渡されていることも確認できます。
これでポップアップとコンテンツのメッセージ通信が実現できました。

## 終わりに

というわけで、ポップアップとコンテンツのメッセージ通信が実現できました。  

職場ではポップアップ側の画面を押すと Salesforce の出勤/退勤ボタンを押し、ポップアップで設定しているチャネルに出退勤あいさつを送る、といった使い方をしています。

HTMLの初歩知識があれば、わりと簡単に`Chrome extension` を作成できるので、単純作業はどんどん自動化していきましょう！


**続編**

常用しているChrome Extensionの記事もあるのでどうぞ！
[ホントは教えたくない、誰かがSlackにリアクションしたら便乗するChrome拡張を作る](./05d2d0805ee247f38826)