chrome拡張機能は3つの世界を駆使して開発する

先日、chrome拡張機能を開発したので、その中で理解するのに時間のかかった「chrome拡張機能の3つの世界」について私が理解している範囲で説明します。

作成した拡張機能の紹介

chrome拡張ポップアップ画面

まずは、作成した拡張機能の紹介をさせてください。

仕事をしていく上で、meet上でうまくタイムマネジメントができないことに悩んでいたので、meet専用の拡張機能を作成してみました。

この拡張機能は特定の時間になったら全員に残り時間をコメント欄で通知するという単純なものです。

この機能の良い点はタイムマネジメントする人が参加者全員に対して残り時間を通知できるところです。

今までは画面共有者がタイムマネジメントをしなければならないことが多く、負担が大きかったのでこの拡張機能を使えばうまく役割分担できます。

まだまだ改善点があるので地道にアップデートしていきます。

こんな機能が欲しいと要望がある場合はレビューなどで指摘してください。

chrome拡張機能の仕組み

chromeの拡張機能を開発するには、manifest.jsonを書く必要があるのですが、注意したいのがversionです。

現在の最新がv3なのですが、世の中に溢れているさまざまな情報がv2のものなのです。

公式の発表で近い将来v2で新しく拡張機能を公開することができなくなるようなので、特別な理由がない場合はv3で開発する方が良いでしょう。

v2とv3の違いは以下の記事がわかりやすいので気になった人は読んでみてください。

chrome拡張の3つの世界

v2とv3ですこし異なりますが、chrome拡張機能の3つの世界について説明します。

Browser Action (Page Action) v3では区別がない

Chrome右上に表示されているアイコンをクリックしたときに何かの処理をさせることができます。

クリックしたことをトリガーにポップアップを表示したり、クリップボードやブックマークに対する処理を実行したりできます。

詳しくは公式ドキュメントで何ができるか確認してみてください。

Content Scripts

ブラウザ上のDOM操作をするときに使用します。

Browser ActionやBackground Pageから受け取った情報を元に現在表示されているページに対して様々な操作を行うことができます。

公式ドキュメントでもある通り、このContent Scripts はセキュリティ上アクセスできるAPIに制限があったり、他の領域と異なるところで動いているため既存のページで動くJavaScriptと競合しないなどの特徴があります。

service_worker(v2ではEvent PageやBackground Pageと呼ばれている)

chromeを起動してから、chromeを閉じるまで裏で動いているスクリプトです。

例えば、特定のurlの時のみ拡張機能を有効にするなどの処理をこの領域で実現します。

chromeAPIを使って様々なことができます。

詳しくは公式ドキュメントを読んでみてください。

3つの世界をつなぐ

拡張機能を開発していく際には、上記で説明した3つの世界にそれぞれ適切な処理を記述し、それぞれの世界をchromeAPI経由で通信を行う必要があります。

例えば、Browser Actionでのクリックを検知して、Content Scriptsに情報を送り、ページ内のdomに要素を挿入するという拡張機能を作りたい場合には、Browser Action → Content Scriptsへの通信が必要になります。

メッセージを異なる世界に送信するには、runtime.sendMessagetabs.sendMessage2つのAPIを用いることで実現できます。

そして、そのリクエスト送信を受け取る側は、runtime.onMessagを使用してイベントリスナーを設置します。

コンテンツスクリプトからリクエスト

コンテンツスクリプトからのリクエストの送信は以下のようになります。(v3からはasync awaitが使えるようになっています。)

// コンテンツスクリプトからの送信
(async () => {
  const response = await chrome.runtime.sendMessage({greeting: "hello"});
  // do something with response here, not outside the function
  console.log(response);
})();

コンテンツスクリプトへのリクエスト

コンテンツスクリプトへリクエストを送信する場合はchrome.tabs.queryを使います。

どのタブに向けて送信するか判定する必要があることに注意してください。

(async () => {
 // タブを選択
  const [tab] = await chrome.tabs.query({active: true, lastFocusedWindow: true});
 // 選択したタブのコンテンツスクリプトにリクエストを送信
  const response = await chrome.tabs.sendMessage(tab.id, {greeting: "hello"});
  // レスポンスが返った際の処理を記述
  console.log(response);
})();

イベントリスナー

各領域でイベントリスナーを設定する場合は、以下のように設定します。

chrome.runtime.onMessage.addListener(
  function(request, sender, sendResponse) {
    console.log(sender.tab ?
                "from a content script:" + sender.tab.url :
                "from the extension");
    if (request.greeting === "hello")
      sendResponse({farewell: "goodbye"});
  }
);

3つの世界の繋ぎ方

3つの世界への通信方法をまとめると以下の図のようになります。

chrome拡張3つ世界間のやり取りまとめ

私が作成した拡張機能では、Browser Actionからコンテンツスクリプトへリクエストを送らなければいけなかったので、実際に下記のようにリクエストを送っています。

// v2での書き方
chrome.tabs.query({ active: true, currentWindow: true }, function (tabs) {
    const tab = tabs[0];
    if (tab.id) {
      chrome.tabs.sendMessage(tab.id, {
        message: "startTimer",
        value: totalMinutes,
      });
    }
  });
}

sendMessage内ではkey: valueの形でリクエストを送信することが可能で、私の場合はこれを使いどのボタンが押されたのか判定しています。

まとめ

chrome拡張機能を開発する上で、欠かせない3つの世界についてざっくりと解説しました。

chrome拡張機能を開発するには、まずchrome APIでやりたいことが実現できるのかを調べた上でmanifest.jsonの書き方、3つの世界でどう役割を分担させるかを考えて開発していきます。

また、拡張機能を公開するには5ドル支払う必要がありますが結構簡単にできるので挑戦してみても良いかもしれません。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です