iimon TECH BLOG

iimonエンジニアが得られた経験や知識を共有して世の中をイイモンにしていくためのブログです

chrome extensionのsendMessageを動かしてみた

はじめに

こんにちは。木暮です。 今回はchromeExtensionのruntime.sendMessageについて解説させていただきます。
普段はcontent_script側の実装がメインでService Workerを用いた実装はあまり経験がありませんでした。
業務の中で別タブの情報を取得したいケースがあり、Service Workerを介して別タブに情報を取得しに行く処理を実装することができたので便利でしたので紹介させていただきます。

chromeExtensionの構造

本題に入る前にまず、簡単にcontent_scriptとservice workerがそれぞれどういった役割を担うものなのかを公式ドキュメントの引用となりますが、簡単に説明させていただきます。

Service Worker

developer.chrome.com

拡張機能 Service Worker は必要に応じて読み込まれ、休止状態になるとアンロードされます。読み込まれると、拡張機能 Service Worker は通常、アクティブにイベントを受信している限り実行されますが、シャットダウンする場合があります。ウェブ環境と同様に、拡張機能 Service Worker は DOM にアクセスできませんが、必要に応じて画面外ドキュメントで使用できます。

content_script

developer.chrome.com

コンテンツ スクリプトは、ウェブページのコンテキストで実行されるファイルです。標準のドキュメント オブジェクト モデル(DOM)を使用して、ブラウザがアクセスしたウェブページの詳細を読み取り、変更を加え、親拡張機能に情報を渡すことができます。

Service Workerはchromeブラウザー自体で動く処理
content_scriptは各タブのWebページに埋め込まれて実行される処理
となります。
つまり、Service Workerは必ず1つのみですがcontent_scriptはブラウザのタブごとに存在します。

Service Workerを使用するための準備

developer.chrome.com

上記のドキュメントを参考に manifest.jsonにService Workerの定義を追加します。
backgroundのキーが該当の部分になります。 service_workerの値にService_Workerとして使用する使用するファイルを指定 typeはmoduleとなります。

{
  "manifest_version": 3,
  "name": "hoge",
  "description": "Base Level Extension",
  "version": "1.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  }
}

まずは、background.tsが正しく動作するか確認を行いましょう。 background.tsの処理は以下の様にしています。 Service Workerでconsole.logの内容が表示されれば動作確認はOKです。

console.log("background.ts")

動作確認はextensionの管理画面のServiceWorkerのリンクから確認ができます。

実装した内容が出力されていることが確認できました。

次にcontent_scriptを動かせる様にmanifest.jsonにcontent_scriptsを追加しました。
matchsの値にはcontent_scriptを埋め込むURLを設定。
jsに値には埋め込むcontent_scriptのソースコードを指定します。
それぞれ、配列で設定して複数指定が可能です。

{
  "manifest_version": 3,
  "name": "hoge",
  "description": "Base Level Extension",
  "version": "1.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://example.com/"],
      "js": ["index.js"]
    }
  ]
}

設定ができたのでindex.jsを用意しましょう。
Service Worker同様、console.logが出力されることを確認します。

console.log("content_script");

上記のコードをindex.jsに実装して
https://example.com/にアクセスして開発者ツールを開くと意図したログが出力されていることが確認できます

上記の動作確認完了時のディレクトリ構成は以下の様になります。

$ ls -l
total 40
-rw-r--r--  1 kogure  staff   30  1 16 00:07 background.js
-rw-r--r--  1 kogure  staff   31  1 16 00:07 index.js
-rw-r--r--  1 kogure  staff  351  1 16 00:08 manifest.json

sendMessageの簡単な動作確認

それでは次にsendMessageがどのようにして content_scriptとService Workerを連携しているかを実装を交えて説明します。 sendMessageのドキュメントは以下のリンクです。 developer.chrome.com

実装の全体は以下のようになります

・content_script(index.js)

const createButton = () => {
  const buttonElement = document.createElement("button");
  buttonElement.textContent = "コンテンツスクリプトボタン";
  buttonElement.onclick = function () {
    chrome.runtime.sendMessage({ message: "hello" });
  };
  let target = document.querySelector("body");
  target.appendChild(buttonElement);
};

createButton();

タブのHTMLにボタンを埋め込んでボタンを押下するとsendMessageが実行され、Service Workerへイベントを発生させるプログラムです。 特にcssはいじっていないので変な位置にボタンが出ていますね。

・Service Worker(background.js)

chrome.runtime.onMessage.addListener((request) => {
  console.log(request.message);
});

snedMessageのイベントハンドラーです。 sendMessageの引数で設定された値を拾えます。 今回は、単純にsendMessageに設定したメッセージをコンソールに出力するスクリプトになります。

では実行してます。

想定通りの動きが確認できました。 sendMessageを用いることでこのようにService Workerとcontent_scriptの連携ができるようになります。

別タブから値を取得する

sendMessageがどのようにしてService Workerとcontent_scriptの連携をしているかはわかったかと思います。 では、先ほどの実装をベースに別タブから情報を取得する実装を行います。

実装は以下の内容になります。

・content_script(index.js)

const createButton = () => {
  const buttonElement = document.createElement("button");
  buttonElement.textContent = "コンテンツスクリプトボタン";
  buttonElement.onclick = async () => {
    request().then((res) => {
      console.log(res);
    });
  };
  let target = document.querySelector("body");
  target.appendChild(buttonElement);
};

export const request = (): Promise<string> =>
  new Promise((resolve) => {
    chrome.runtime.sendMessage({ message: "sendMessage" }, (response) => {
      resolve(response);
    });
  });

chrome.runtime.onMessage.addListener(
  (request: { message: string }, sender, callback) => {
    if (request.message === "getDocumentTitle") {
      callback(document.title);
    }
    return true;
  }
);

createButton();

content_scriptにはrequest関数とリスナーを追加しました。

request関数はメッセージを飛ばした後にresolveを用いてService Workerからの返り値を受け取れるように実装しています。
リスナーをここに書く理由としては前述の通り、各タブごとにcontent_scriptが存在するためです。
リスナーの処理は特定のメッセージをイベントリスナーで拾った時にページのタイトルを取得して返却する処理となります。

・Service Worker(background.js)

chrome.runtime.onMessage.addListener((request, sender, callback) => {
  chrome.tabs.query({ currentWindow: true }, (tabs) => {
    const targetTab = tabs.find((tab) => {
      return tab.url === "https://www.google.co.jp/";
    });
    chrome.tabs.sendMessage(
      targetTab.id,
      {
        message: "getDocumentTitle",
      },
      (response) => {
        callback(response);
      }
    );
  });
  return true;
});

Service WorkerではsendMessageのイベントを発生させるタブの指定を行い、指定したタブに対して
メッセージを送り返り値を受け取ってさらにイベントを発生させたタブのcontetne_scriptに返り値をバケツリレーのようにを返すつくりとなっています。
今回メッセージを飛ばすタブはhttps://www.google.co.jp/にアクセスしているタブにしました。
ここで注意する点としてはService Workerからcontent_scriptに対してメッセージを飛ばす際はruntime.sendMessagedではなくtabs.sendMessageになります。

・manifest.json

{
  "manifest_version": 3,
  "name": "hoge",
  "description": "Base Level Extension",
  "version": "1.0",
  "background": {
    "service_worker": "background.js",
    "type": "module"
  },
  "content_scripts": [
    {
      "matches": ["https://example.com/", "https://www.google.co.jp/"],
      "js": ["index.js"]
    }
  ],
  "permissions": ["tabs"]
}

content_scriptsのmatchesにgoogleのURLを追加しました。
これによりgoogleのページにイベントリスナーを設定することができるようになりまた。

そしてpermissionsにはtabsを追加して各タブの情報をchromeExtensionを参照できるようにしました。

developer.chrome.com

では上記のプログラムを動かしたときの動作を確認しましょう。

別タブのdocument.titleがログ出力されました。
簡単な説明となりましたがこのようにsendMessageをつかえば別のタブでjsを発火させるて値を取得することが可能になります。

終わりに

chromeExtensionのAPIはまだまだ使いこなせていないので他にどんなAPIがあって何が実現できるのか 機会がありましたら深く勉強してみたいと思いました!

最後まで読んでくださりありがとうございます!

この記事を読んで興味を持って下さった方がいらっしゃればカジュアルにお話させていただきたく、是非ご応募をお願いします。
iimon採用サイト / Wantedly / Green