chrome.runtime.sendMessage() の備忘録

ここ最近 Chrome 拡張機能を作って遊んでいますが、Chrome 拡張機能 APIの『chrome.runtime.sendMessage()』というAPIメソッドを使った拡張機能のコードをテスト中に、

Unchecked runtime.lastError: The message port closed before a response was received.

というエラーが出て来たのですが、どう対処して良いのか当初はこのメッセージからは直ぐに理解できなかったので、ここに備忘録としてメモしておきます。


セキュリティー上の仕様

Chrome拡張機能ではセキュリティー上、各「ユーザーインターフェース (UI)」、 「バックグラウンドプロセス」とWebページ上に埋め込まれる「コンテント (Content) スクリプト」のプロセスは、各自、違うプロセスで処理されて、メモリ領域も分離されています。 なので、拡張機能のバックグラウンドページで宣言されている変数の値をポップアップを処理するスクリプトから直接アクセスする事は出来ません。

拡張機能のポップアップやオプションページのスクリプトの場合だと、バックグラウンドページに限っては拡張機能APIの『chrome.runtime.getBackgroundPage()』メソッドを使うとバックグラウンドページのインスタンスが取得出来るので、バックグラウンドページのプロセスで任意のメソッドを実行出来ます。

例として、ポップアップ又はオプションページのスクリプト内に:

chrome.runtime.getBackgroundPage().console.log("ログメッセージ");

という行を加えると、「getBackgroundPage().」以降に続く「console.log(...)」がバックグラウンドページのプロセスで処理されるので、バックグラウンドページのコンソールのログに「ログメッセージ」と出力されます。

でも、このAPIはコンテクストスクリプトでは使う事が出来ないので、他のプロセスと情報交換をする場合は拡張機能APIのchrome.runtimeクラスの「sendMessage」メソッドと「onMessage」イベントを使うと、プロセス間でメッセージのやり取りをする事が出来ます。

: タブへメッセージを送る場合は「chrome.runtime.sendMessage」でなく、「chrome.tabs.sendMessage」を使う必要があります。


プロセス間の対話

sendMessage」は、名前の通り、他のプロセスにメッセージを送るメソッドで、「onMessage」はメッセージを受け取った時に発生するイベントですが、受け取る側のプロセスで事前に「onMessage」イベントの「addListener(...)」メソッドでイベントリスナーを宣言しておく必要があります。

又、sendMessageは、送ったメッセージの受け取り側からの返答メッセージを受け取る為のコールバック(Callback)を設定する機能もあるので、他のプロセスに情報を送った後に、受け取り側からの返答として情報を受け取る事も出来ます。


chrome.runtime.sendMessage

sendMessageメソッドの構文はchrome.runtime APIのページによると次の様になります:

chrome.runtime.sendMessage(
                  string extensionId,
                  any message,
                  object options,
                  function responseCallback);

データ型名前説明
string extensionId [省略可] 受け取り側のID
any message JSON形式のメッセージ内容
object options [省略可] boolean:TLSチャンネル IDを送る(T)かどうかを指定。 TLS=Transport Layer Security
function responseCallback [省略可]
返信メッセージ受け取り用コールバック
function(any response) {...};
「response」はJSON形式の返信メッセージ

使用例は次の様に:

  var msg = {message: "hello", num: 12345}; 
  chrome.runtime.sendMessage(msg, function (val) {
    if (!val) { return; }
    if (!val.content1) { .... }
  });

この例だと、msgというJSON形式の値をメッセージに添付して送った後、返信受け取り用コールバックとなる「function(val)」の後に続く波括弧カッコ内のコードが返信メッセージとして受け取ったJSON形式の変数「val」の値を処理します。

: タブへメッセージを送る場合は「chrome.runtime.sendMessage」でなく、「chrome.tabs.sendMessage」を使う必要があります。


chrome.runtime.onMessage

onMessageイベントの構文はchrome.runtime APIのページによると:

chrome.runtime.onMessage.addListener(function callback);

引数にあるコールバックの「callback」は次の様な形式になります。

function(any message, MessageSender sender, function sendResponse) {...};
データ型名前説明
any message [省略可] 受け取ったJSON形式のメッセージ
MessageSender sender 送り主の情報 (chrome.runtime.MessageSender)
function sendResponse 返信用のコールバック

使用例としては:

  var value;
  chrome.runtime.onMessage.addListener(function (request, sender, response) {
    if (sender.tab) {
      console.log('Message from a tab: ' + sender.tab.url);
      return false;
    }
    if (request.message != "hello") { retunrn false; }
    if (request.num) { value = request.num; }
    return true;
  });

この例の場合、メッセージ送信元がブラウザーのタブの場合は表示中のページのURLをログに書き出してfalseを返します。 メッセージに添付されていたJSON形式の値は「request」という変数として渡されますが、送信元がブラウザーのタブ以外の場合は、この「request」変数の内容がチェックされて、request.messageの値が"hello"であればrequest.numの値がvalueに代入した後に、イベントリスナーがtrueを返して終了します。

chrome.runtime.onMessageのドキュメントによると、イベントリスナーがtrueを返す場合は、非同期で処理されるそうです。


[メモ]

contextscriptでonMessageのリスナーを設定する場合、複数の拡張機能が同じタブを対象にリスナーを設定する可能性もあるので、違う拡張機能からのメッセージを間違って横取りしてしまわない様にする仕組みが必要かと。。


return true

個人的に開発していたChrome拡張機能に「sendMessage」と「onMessage」を組み込んだ際、当初は問題無く使えていたのですが、コードをいじっているうちに動作がおかしくなってしまい、ブラウザの拡張機能のページ(chrome://extensions)で確認した処、次の様なエラ-がログされていました:

Unchecked runtime.lastError: The message port closed before a response was received.

何がいけないのか判らず、いろいろと検索した後にとたどりついたのが次のスレッド

結果を先に書くと、onMessageのコールバックでtrueを返していなかった為にsendMessageがメッセージが受け取られなかったと判断されてエラーになっていました。。 onMessageのコールバックでtrueを返して非同期処理になる様にコードを修正した処、正常に動作する様になりましたが、一度経験していないと、メッセージを読んだだけでは何が原因で発生したのか理解出来ないエラーでした。

たぶん、JavaScriptは原則、シングルスレッドで、並行処理に対応していないので、送り側も受け取り側も同じJS環境で処理されている場合は、非同期でメッセージ処理をしないと、送り側の処理が並行して実行されないので、エラーが出てしまうみたいです。

上記のエラーメッセージを読んだだけでは初心者には何が起きているのかが理解出来ても、何が原因なのか判りにくかったという事例でしたw😗


まとめ

一度経験すれば、すぐ気が付くバグだとは思いますが、未経験の場合は上のエラーメッセージを見ただけでは。。



コメント