38
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2016-11-14

#状況
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;
    }
);
38
29
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
38
29

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?