Developers › Dev.Opera

Opera extensions: messaging

October 27, 2010 · by Shwetank Dixit

Introduction

With extensions, you have the ability to create and add exciting new functionality to the Opera desktop browser. As mentioned in other articles, Opera extensions contain a background process, injected scripts, and sometimes popup script too. In this article, we will take a look at how to communicate between all three.

Contents

Communicating between background process and injected script

Opera uses postMessage() to post a message. If you want to post a message from the background process to the injected scripts, you write the following in the background process:

opera.extension.broadcastMessage("Hello there");

Now you've sent a message, the injected script needs to catch it, which is done like so:

opera.extension.onmessage = function(event){
  var thecatch = event.data; // event.data in this case will contain the string "Hello there"
}

It's as simple as that. In the above code, the background process is firing a message using broadcastMessage to the injected script, and the injected script is listening for it. When it gets the message, the stores it in a variable called thecatch. Keep in mind though, that using broadcastMessage will send the message to all injected scripts and popups, so it should be used sparingly. Normally, you'll want to communicate to just one popup or injected script (the one inside your own extension) using postMessage or message channels.

Let's illustrate this further by taking a look at our first test example.

The background process has an event listener for load, and all the code is inside the function call for it. You can read the opera.postError dumps in Tools > Advanced > Error Console.

window.addEventListener("load", setupConnection, false);

function setupConnection(){
   // When the injected script is activated, it connects with the background process.
   opera.extension.onconnect = function(event){
        // Post message to the source, that is, the thing which connected to us (in this case the injected script)
 	event.source.postMessage("something");
        // Post this message in the opera error console
	opera.postError("sent message to injected script");
   }

   // Listen for messages
   opera.extension.onmessage = function(event){
       	// Post a sentence (which includes the message received) to the opera error console
       opera.postError("This is what I got from injected script: "+event.data);
   }
}

Let's focus first on the following code snippet:

opera.extension.onconnect = function(event){
   event.source.postMessage("something");
   opera.postError("sent message to injected script");
}

onconnect is fired whenever something connects to the background process, in this case, when the injected script is activated on a document, thus creating a connection with the background process. So as soon as the injected script loads, the onconnect event is fired.

The onconnect handler in the background contains a reference back to the injected script in event.source. This message port can then be used to directly communicate back to the injected script, which is done here by sending the string "something" as a message using event.source.postMessage(). We also send a small string to the error console, just to keep track of what's happening.

Let's leave the background process for a while and focus instead now on the injected script:

opera.extension.onmessage = function(event){
  // Get content of incoming message.
  var message  = event.data;
  opera.postError("background process sent: " + message);

  //  Replies back to background process.
  var reply = "background process's message only had " + (message ? message.length : 0) + " characters.";
  event.source.postMessage(reply);
};

Here, onmessage is invoked whenever the injected script receives a message. We store the content of the message in the variable message. Next we use opera.postError to display a sentence in the error console along with the message sent by the background process.

To acknowledge receipt of the message, we post back a message to the background process using the event.source of the incoming message. This always holds true: a message event.source always points back to the sender.

So now, we've seen how messages are received by the injected script, and also how they can be transmitted back to the background process.

The next step is for the background process to catch this message and do something with it. So let's focus on the background process again, this time on the other part of the script:

// Listen for injected script messages
opera.extension.onmessage = function(event){
   // Post a sentence (which includes the message received) to the opera error console.
   opera.postError("This is what I got from the injected script: " + event.data);
}

Here, whenever a message is received, it posts a sentence containing that message data onto the error console.

So in summary, this is what happens in the extension:

  • The background process sends a message to the injected scripts
  • The injected script catches that message and writes something on the error console, which includes the message given by the background process
  • The injected script then replies, sending a message back to background process
  • The background process writes a sentence into a string on the error console, which includes information about the message sent by the injected script.

If you have installed the extension (reload the page for the injected script to take affect) then go to the error console. You should find the messages shown in Figure 1 written into it.

Image of Error console showing the messages sent to it by the extension

Figure 1: The error console, showing the messages sent to it by the extension.

So we've now seen how both background process and injected script can post messages and catch messages. Next, we'll see how this is done with a popup.

We'll be using the second extension example for this. Notice that there is no includes/ folder or any injected scripts — it's just the background process, and the HTML files along with config.xml and an icon.

You already know from previous articles how to create a UIItem like a button, so we won't cover that. Let's focus on the following piece of code in background.js:

opera.extension.onconnect = function(event){
  event.source.postMessage("sending something");
  opera.postError("sent message to popup");
}

As we know from the previous example, this will be run when something connects to the background process, in this case, the popup. The function sends the message "sending something" to the popup, and the message "Sent message to popup" to the error console.

Let's go to the popup page now:

<script>
    window.addEventListener("load", function(){
    opera.extension.onmessage = function(event){
    	event.source.postMessage("do whatever you want with this message");
    	opera.postError("sent from popup to background process");
    	}
    }, false);
</script>

Here we listen for a message to arrive, and when it does we send a message back to the source. We also post something to the error console once again. So now the popup script receives a message, and sends a message back to the background process. All that's left now is for the background process to catch it.

Let's go back to the background process again, and study the following code:

opera.extension.onmessage = function(event){
   opera.postError("This is what I got from injected script: " + event.data);
}

Here the background process listens for a message, and when there is one, it posts something to the error console, with the content of the message received as part of the message. As you can see, we approximate and label the message as coming from an injected script here too.

So in summary, this is what happens in the extension:

  • The background process sends a message to the popup script.
  • The popup script catches that message and writes something onto the error console, which includes the message given by background process.
  • The popup then sends a message back to the background process
  • The background process writes something into a string on the error console, which includes the message sent by the popup script.

So the way it behaves is actually pretty similar to the previous example, its just that with this example, we did not have an injected script file at all in the includes/ folder. Rather, we used a popup and the popup document's script communicated with the background process.

If you install the extension, click the extension button to trigger the communication with the popup then open up the error console, you should see the messages shown in Figure 2.

Image of Error console showing the messages sent to it by the extension

Figure 2: the error console showing the messages sent to it by the extension.

Let's now look at how to communicate between a popup and an injected script. The background process will be used initially to make the connection, then both popup and injected script will talk without the background process.

We achieve this by creating a message channel. You can learn more about message channels by looking at the HTML5 cross document messaging specifications, which is what the messaging for extensions is closely based on.

We'll explain this using the third test example, so fire up your text editor and have a look at the code. Let's first look at the background process. You'll notice some familiar things, like the code to add a button and a popup, etc. Let's delve into the interesting bit.

Firstly, we have a global variable called port, which we'll use later on. Let's look at the onconnect event here:

opera.extension.onconnect = function(event){
  if (port){
    event.source.postMessage("Respond to the port", [port]);
  }
}

So whenever the background process connects with something, it will post the message 'Respond to the port'. We have specified a port to post the message to as well, but initially since the port has no value as such, the message will just be sent as it is.

Now look at the injected script:

var channel = new MessageChannel();
opera.extension.postMessage("Respond to this immediately", [channel.port2]);

Here we are creating a new message channel. A message channel will have two ports, and message can be transmitted and received using these two ports. Over here, we make sure the message is transferred using the second port, and received using the first port. We then send a message using postMessage() to the second port of the message channel.

Let's switch back to the background process:

opera.extension.onmessage = function(event){
  if (event.ports){
    port = event.ports[0];
  }
}

Here the background process catches the message sent by the injected script. Now the reference to the second channel in the injected script — available as event.ports[0] in the background process — is stored in the port variable. Now the background is ready for the popup to be loaded. When it does, once again, the onconnect event is fired:


opera.extension.onconnect = function(event){
  if (port){
    event.source.postMessage("Respond to the port", [port]);
  }
}

However, this time, the value of the port variable is not empty. It contains a reference to the second channel.

Now let's look at the popup file:

opera.extension.onmessage = function(event){
  if (event.ports){
    opera.postError("Responding to port");
    event.ports[0].postMessage("Hi from popups side!");
  }
}

Here it checks for a message, and if it contains reference to a port, then it posts something on the error console, and more importantly, posts back a message on that same port. Now it's only a matter of the injected script receiving that message. In the injected script, we have the following code:

channel.port1.onmessage = function(event){
  opera.postError("Here is what i got in the injected script for port1: " + event.data);
}

Here, the injected script listens to port1 of the message channel. Once it gets message from it, it posts a sentence to the error console, which includes the message it received from the popup.

So in summary, the following things happen:

  • The injected script loads
  • The injected script sets up a message channel
  • The injected script sends a message to the background process with the second port
  • The injected script also sets up a message listener to listen for incoming messages
  • The background process listens to the message sent by injected script, but more importantly, stores the reference to this port
  • Now the popup connects to background process
  • The onconnect event is fired again, and this time the message is posted with reference to the port received from the injected script
  • The popup listens to the message, and then checks whether the message has a reference to a port. If so, then it sends a message of its own to that port. (this port being the port2 of the message channel)
  • The injected script's message channel listens to the message! It also then posts something to the error console saying what message it received from the popup.

In this scenario, think of the background process as a matchmaker. First the matchmaker connects with the first person (the injected script). The person gives out his number (second port) to the matchmaker, which the matchmaker stores. Then another person (the popup) connects with the matchmaker: as soon as it does, the matchmaker sends the number of the first person (injected script) to the second person (popup). And now all further communication happens directly between the two people, leaving the matchmaker out of the equation.

Wrapping it up

Communicating between various parts of an extension is pretty easy once you get to know how to use postMessage along with the onconnect and onmessage handlers. You can also use message channels, which is required when it comes to communicating between a popup and an injected script. This article has shed some light on how to pass around messages in three scenarios: between background process and injected script; between background process and popup; and between popup and injected script.

API reference

About this article

Published inDev.Opera
DateOctober 27, 2010
AuthorShwetank Dixit
TagsAPI, messaging, extensions
OriginalInternet Archive