26
Help us understand the problem. What are the problem?

More than 5 years have passed since last update.

posted at

updated at

Chrome拡張でruntime.sendMessageのコールバックがうまく動作しない理由

状況

Chrome拡張で、content_scripts側からBackgroundPage側へsendMessageした時、コールバックの引数がundefinedになってしまう

症状

sendMessage.jpg
上図のように、backgroundPageで非同期処理(今回はchrome.tabs.getSelected)を行い、その結果をコールバック関数の引数に入れたいのですが……

manifest.json
{
    "manifest_version": 2,
    "name": "sendMessage Test",
    "version": "0.1",
    "author": "Tachibana446",
    "description": "hoge",
    "browser_action": {
        "default_icon": {
            "38": "icon_38.png"
        },
        "default_title": "Do it!"
    },
    "permissions": ["tabs", "http://*/*", "https://*/*"],
    "background": {
        "scripts": ["background.js"],
        "presistent": false
    },
    "content_scripts": [{
        "matches": ["https://www.google.co.jp/*"],
        "js": ["content.js"]
    }]
}
content.js
chrome.runtime.sendMessage({
    message: "message"
}, function(response) {
    console.log(response);
    alert(response);
});

background.js
chrome.runtime.onMessage.addListener(
    function(request, sender, callback) {  // 1
        chrome.tabs.getSelected(function(tab) {  // 2
            callback(tab.title);
        });
        // 3
    }
);

(chrome.tabs.getSelectedは現在開いているタブを取得する関数です)
上のサンプルは現在のタブのタイトルをアラートダイアログで表示するだけのプログラムですが、実際に実行するとアラートにはundefinedと表示されてしまいます。

解説

まず、大前提としてjavascriptのなんか引数にfunction渡したりする系のものは非同期なんですね。
その辺を理解していなかったのでなかなかわかりませんでした。

https://developer.chrome.com/extensions/runtime#event-onMessage
そして上のこのページに解説がちゃんとあるんですよね。

Function to call (at most once) when you have a response. The argument should be any JSON-ifiable object. If you have more than one onMessage listener in the same document, then only one may send a response. This function becomes invalid when the event listener returns, unless you return true from the event listener to indicate you wish to send a response asynchronously (this will keep the message channel open to the other end until sendResponse is called).

The sendResponse parameter should be a function that looks like this:

function() {...};

unless you return trueのところを見てください。とは言え筆者は英語ができないのでざっくりですが、
「この(sendMessageの引数として与えられるコールバック)関数は、値を返した時点で無効になります。非同期の処理を同期的に待つときはtrueを返してください。」
というようなことが書いてあります。
つまり、非同期的な処理を行う前に関数の最後に到達してしまい、値(この場合明示的にreturnされていないのでundef)が返されるのでそこで処理が無効になってしまっていたのです。

具体的に説明しましょう。
先ほどのbackground.jsをご覧ください。
// 1のところで、リスナーの匿名関数がスタートします。
// 2のところで、getSelected関数(の引数に与えられた匿名関数)が非同期で行われます。
しかし非同期なので、その処理は一旦おいておいて、そのまま// 3のところへ行きます。
// 3以降、リスナーの関数は続きがないので、ここでリスナーの関数が終了し、既定値(たぶんundefined)が返戻されます。
値が返戻されたので、このリスナー関数は無効になり、待機していた非同期のgetSelectedの引数の匿名関数も処理されません。
そのままコールバックが呼び出されるので、期待していたタブのタイトルを取得できませんでした。

解決法

公式ドキュメントの通り、明示的にtrueを返してあげましょう。
先ほどの// 3のところにreturn trueと書くだけです。

background.js
chrome.runtime.onMessage.addListener(
    function(request, sender, callback) {  // 1
        chrome.tabs.getSelected(function(tab) {  // 2 
            callback(tab.title);
        });
        // 3
        return true;
    }
);
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Sign upLogin
26
Help us understand the problem. What are the problem?