読者です 読者をやめる 読者になる 読者になる

心魅 - cocoromi -

半角スペース時々全角

extension内のパーツやextension間で通信するためのAPI

extensionには様々なパーツがありますが。
それぞれのパーツ間でメッセージングを行うAPIが用意されています。


Message Passing
http://code.google.com/chrome/extensions/messaging.html

Communication between extensions and their content scripts works by using message passing. Either side can listen for messages sent from the other end, and respond on the same channel. A message can contain any valid JSON object (null, boolean, number, string, array, or object). There is a simple API for one-time requests and a more complex API that allows you to have long-lived connections for exchanging multiple messages with a shared context. It is also possible to send a message to another extension if you know its ID, which is covered in the cross-extension messages section.


たぶん、


メッセージのやりとりをするとextensionおよびcontent script間で通信できるよ!
片方は他からのメッセージが来るのを待っても良いし、同じチャンネルを使えば返事も送れるよ!
メッセージっていうのはJSON形式で表現できる物なら何でも突っ込めるよ!
one-time requestとlong-lived connectionなAPIがあるよ!
あとextension間で通信するAPIがあるよ!でも相手のID判ってないとダメだけどね!ハハ!


って書いてある。
JSON形式って書いてあるのでfunctionは渡せない。

先に言い訳

  • 英訳っぽいところは大いに間違ってる可能性がありますので信用しないでください

主要なAPIと引数

  • one-time requests
    • chrome.extension.sendRequest( param , callback )
    • chrome.extension.onRequest.addListener( callback( request, sender, sendResponse) {} )

onRequestのコールバックの第3引数のコールバックは、用が無くても何か返した方が良いみたい?


  • Long-lived connections
    • var port = chrome.extension.connect( param )
    • chrome.extension.onConnect.addListener( callback( port ){} )
    • port.postMessage( param )
    • port.onMessage.addListener( callback( param ) {} )

複数回のメッセージングが予期される場合はこちら


  • Cross-extension messaging
    • chrome.extension.onRequestExternal.addListener( callback( request, sender, sendResponse) {} )
    • chrome.extension.onConnectExternal.addListener( callback( port ) {} )
    • chrome.extension.sendRequest(laserExtensionId, param , callback(response) {} )
    • var port = chrome.extension.connectExternal(laserExtensionId)

エクステンションに公開APIを持たせたい時などに使う。

Simple one-time requests

If you only need to send a single message to another part of your extension (and optionally get a response back), you should use the simplified chrome.extension.sendRequest() or chrome.tabs.sendRequest() methods.
This lets you send a one-time JSON-serializable message from a content script to extension, or vice versa, respectively. An optional callback parameter allows you handle the response from the other side, if there is one.

やりとりするメッセージが1回だけならchrome.extension.sendRequest()かchrome.tabs.sendRequest()を使ってね。てか使え。
コレを使うとJSONに変換可能なメッセージを1回だけ送れるよ。
コールバックを使えば返事も受け取れるよ!やったね!

Sending a request from a content script looks like this:

コンテントスクリプトからは、こうやってメッセージを送るのさ!

contentscript.js
================
chrome.extension.sendRequest({greeting: "hello"}, function(response) {
  console.log(response.farewell);
});

Sending a request from the extension to a content script looks very similar, except that you need to specify which tab to send it to.
This example demonstrates sending a message to the content script in the selected tab.

エクステンションから送る場合だってほとんど同じだよ!
ただ送り先のタブが判ってないとダメだけどね!
次のサンプルはコンテントスクリプトから選択されているタブにメッセージを送るよ!

background.html
===============
chrome.tabs.getSelected(null, function(tab) {
  chrome.tabs.sendRequest(tab.id, {greeting: "hello"}, function(response) {
    console.log(response.farewell);
  });
});

On the receiving end, you need to set up an chrome.extension.onRequest event listener to handle the message. This looks the same from a content script or extension page. The request will remain open until you call sendResponse, so it is good practice to call sendResponse with an empty object to allow the request to be cleaned up.

メッセージを受け取る側はchrome.extension.onRequestイベントを監視してね!
コンテントスクリプトもエクステンションもこれは絶対だよ!
リクエストはsendResponseを呼び出すまで開きっぱなしになってるよ、
リクエストを綺麗にするために空っぽのオブジェクトを引数にsendResponseを呼び出すのは良い例だ!


最後のところ良くわからなかった。

chrome.extension.onRequest.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"});
    else
      sendResponse({}); // snub them.
  });

Long-lived connections

Sometimes it's useful to have a conversation that lasts longer than a single request and response. In this case, you can open a long-lived channel from your content script to an extension page, or vice versa, using chrome.extension.connect() or chrome.tabs.connect() respectively. The channel can optionally have a name, allowing you to distinguish between different types of connections.

何回もメッセージのやりとりをしたくなるときもあるよね?
そんなときchrome.extension.connect()かchrome.tabs.connect()を使ってlong-livedチャンネルを開くんだ!
他の通信と区別するためにチャンネルに名前を付けることも出来るよ。

One use case might be an automatic form fill extension. The content script could open a channel to the extension page for a particular login, and send a message to the extension for each input element on the page to request the form data to fill in. The shared connection allows the extension to keep shared state linking the several messages coming from the content script.

1つのユースケースとして自動的にフォームを埋めるエクステンションがある。コンテントスクリプトはログインを行うためにエクステンションページに向かってチャンネルを開き、ページのinput要素毎に、フォームを埋めるデータを要求するためにエクステンションにメッセージを送信するだろう。
共用コネクションはエクステンションがコンテントスクリプトからやってくるいくつかのメッセージと関連する共通状態保持することを許可する。


言ってることは判るんだけど旨く訳せなかった。

When establishing a connection, each end is given a Port object which is used for sending and receiving messages through that connection.

コネクションを確立するときに、コネクションを通してメッセージをやりとりするPortオブジェクトが返ってくるよ。

Here is how you open a channel from a content script, and send and listen for messages:

コンテントスクリプトからチャンネルを開く例

contentscript.js
================
var port = chrome.extension.connect({name: "knockknock"});
port.postMessage({joke: "Knock knock"});
port.onMessage.addListener(function(msg) {
  if (msg.question == "Who's there?")
    port.postMessage({answer: "Madame"});
  else if (msg.question == "Madame who?")
    port.postMessage({answer: "Madame... Bovary");
});

Sending a request from the extension to a content script looks very similar, except that you need to specify which tab to connect to. Simply replace the call to connect in the above example with chrome.tabs.connect(tabId, {name: "knockknock"}).

エクステンションから送る場合もだいたい同じ。
connectのところをchrome.tabs.connect( tabId , {name:"knockknock"} )に替えてね


In order to handle incoming connections, you need to set up a chrome.extension.onConnect event listener. This looks the same from a content script or an extension page. When another part of your extension calls "connect()", this event is fired, along with the Port object you can use to send and receive messages through the connection. Here's what it looks like to respond to incoming connections:

メッセージを受信する方はchrome.extension.onConnectイベントを監視してね。
コレはコンテントスクリプトもエクステンションも同じだよ。
片方からconnect()を呼び出すと、onConnectイベントが発生するよ。
その時Portオブジェクトが引数で渡されてくるからそいつを使って受信したり送信したりすればいいよ。

受信したメッセージに返信するサンプル

chrome.extension.onConnect.addListener(function(port) {
  console.assert(port.name == "knockknock");
  port.onMessage.addListener(function(msg) {
    if (msg.joke == "Knock knock")
      port.postMessage({question: "Who's there?"});
    else if (msg.answer == "Madame")
      port.postMessage({question: "Madame who?"});
    else if (msg.answer == "Madame... Bovary")
      port.postMessage({question: "I don't get it."});
  });
});

You may want to find out when a connection is closed, for example if you are maintaining separate state for each open port. For this you can listen to the Port.onDisconnect event. This event is fired either when the other side of the channel manually calls Port.disconnect(), or when the page containing the port is unloaded (for example if the tab is navigated). onDisconnect is guaranteed to be fired only once for any given port.

なんやかんやあって、コネクションが終了したかどうかを知りたい時ってあるよね?Port.onDisconnectイベントを監視すれば出来るよ。
手動でPort.disconnect()が呼ばれるか、Portを使ってるページがアンロードされた時に発生するよ。
onDisconnectはPort毎に絶対に1回しか発生しないって保証されてるよ!よかったね!


Cross-extension messaging

In addition to sending messages between different components in your extension, you can use the messaging API to communicate with other extensions. This lets you expose a public API that other extensions can take advantage of.

あとメッセージングAPIを使うと別のエクステンションにもメッセージを送れるよ。
コレはエクステンションに公開用のAPIを持たせたりするときに使えるよ。

Listening for incoming requests and connections is similar to the internal case, except you use the chrome.extension.onRequestExternal or chrome.extension.onConnectExternal methods. Here's an example of each:

受信のしかたは、chrome.extension.onRequestExternalかchrome.extension.onConnectExternalを使う以外、内部用のメッセージングとほとんど同じだよ。
サンプルだよ。

// For simple requests:
chrome.extension.onRequestExternal.addListener(
  function(request, sender, sendResponse) {
    if (sender.id == blacklistedExtension)
      sendResponse({});  // don't allow this extension access
    else if (request.getTargetData)
      sendResponse({targetData: targetData});
    else if (request.activateLasers) {
      var success = activateLasers();
      sendResponse({activateLasers: success});
    }
  });

// For long-lived connections:
chrome.extension.onConnectExternal.addListener(function(port) {
  port.onMessage.addListener(function(msg) {
    // See other examples for sample onMessage handlers.
  });
});

Likewise, sending a message to another extension is similar to sending one within your extension. The only difference is that you must pass the ID of the extension you want to communicate with. For example:

メッセージも送る方もだいたい同じで、エクステンションのIDが必要なとこだけ違うよ。
サンプルだよ。

// The ID of the extension we want to talk to.
var laserExtensionId = "abcdefghijklmnoabcdefhijklmnoabc";

// Make a simple request:
chrome.extension.sendRequest(laserExtensionId, {getTargetData: true},
  function(response) {
    if (targetInRange(response.targetData))
      chrome.extension.sendRequest(laserExtensionId, {activateLasers: true});
  });

// Start a long-running conversation:
var port = chrome.extension.connectExternal(laserExtensionId);
port.postMessage(...);

まとめ

エクステンションのパーツ間通信は2種類x2種類。one-timeかlong-lived。内部か外部。
通信相手にかかわらず単発のメッセージングですむならone-timeを使う。
外部のエクステンションと通信も可能だが相手のIDが必要。
これを利用して、開発したエクステンションにパブリックなAPIを持たせることが出来る。