Getting your Message Across with the Crossrider Extensions API
Messages come in many forms, contexts, and architectures and when writing extensions, it's often necessary to pass information between scopes. This can be for many reason such as information that's only available in one scope (e.g. only the extension scope has access to the page HTML, or the background scope having access to browser bookmarks), and you want to pass it to another scope.
The information can be shared either passively via the extension's local storage (appAPI.db.async) or actively via the messaging (appAPI.message) mechanism. Furthermore, the exchange of information can be unidirectional (one-way) or bidirectional (two-way), but regardless of the scopes and the directions involved, the mechanism is same.
Note: When choosing an appropriate mechanism, it's worth considering the relative pros and cons of each method. For example, sharing a large amount of data via messaging may present performance issues and limitation (e.g. Internet Explorer is limited to 8KB per message) whilst sharing via the local database lacks immediacy.
The process for sharing via messaging is simple, one scope initiates the sending of a message and, as long as the receiving scope has a listener to receive the message, the massage passes from the sender to the receiver.
Unidirectional
For example, let's take the following unidirectional scenario where the extension scope wants to pass the page title to the background scope, so that the background scope can use it to add the page as a browser bookmark (appAPI.bookmarks):
extension.js:
appAPI.ready(function($) { // Send message to background scope appAPI.message.toBackground({ request: 'save-bookmark', url: window.location.href, title: $('title').text() }); });
background.js:
// Make sure bookmark plugin is added to extension appAPI.ready(function($) { // Message listener to receive messages from any scope appAPI.message.addListener(function(msg) { if (msg.request === 'save-bookmark') appAPI.bookmarks.getDefaultFolder(function(node) { appAPI.bookmarks.create({ title: msg.title, url: msg.url, parentFolder: node }); }); }); });
Bidirectional
Often, a bidirectional communication is required and is implemented in the same manner as the unidirectional example, except that senders and listeners are added to both scopes as required. Take the following example, where the popup scope requires the page URL. The popup scope itself cannot directly access this information, but the extension scope can. In this scenario, the popup scope initiates the bidirectional communication by sending a message to the extension scope requesting the page URL, and then the extension scope responds with the information.
extension.js:
appAPI.ready(function($) { // Message listener to receive messages from any scope appAPI.message.addListener(function(msg) { if (msg.request === 'get-page-url') // Send information to popup appAPI.message.toPopup({ request: 'get-page-url', url: window.location.href }); }); });
background.js:
// Make sure bookmark plugin is added to extension appAPI.ready(function($) { // Message listener to receive messages from any scope appAPI.message.addListener(function(msg) { if (msg.request === 'get-page-url') console.log('Received page URL:'+msg.url); }); // Initiate request for page URL appAPI.message.toActiveTab({ request: 'get-page-url' }); });
Between Iframe and Parent Window
You can even use messaging to communicate between an iframe and the page that contains it. For example, if an extension needs to know the URL of iframes that are ready, the instance of the extension scope running in an iframe can send a message to the instance of the extension scope running in its parent page.
Let's take a moment to understand how this works. An instance of the extension.js code runs in the scope of the parent page and another completely independent instance runs in the iframe. However, they are both using the same extension.js file. So how does each scope know what code it needs to run? We distinguish between the code for each scope using appAPI.dom.isIframe, like in the following example:
extension.js:
// Make sure to enable: Settings > Run in iframes appAPI.ready(function ($) { if (appAPI.dom.isIframe()) { // Code run in iframe appAPI.message.toCurrentTabWindow({ request: 'report-url', url: window.location.href }); return; } // Code run in iframe parent window appAPI.message.addListener(function(msg) { if (msg.request === 'report-url') console.log('Iframe ready:: URL: '+msg.url); }); });
Unified Listener
Of course, it isn't necessary or recommended to add a separate listener to receive messages for each scope, a single listener for all scope is sufficient and more efficient. Taking the examples above, in the extension.js code we can have a single listener to handles all the different requests. For example, we can combine the previous 2 examples (the popup and iframe listeners) as follows:
extension.js:
// Make sure Settings > Run in Iframes is enabled appAPI.ready(function ($) { if (appAPI.dom.isIframe()) { // Code run in iframe appAPI.message.toCurrentTabWindow({ request: 'report-url', url: window.location.href }); return; } // Code run in iframe parent window appAPI.message.addListener(function(msg) { // iframe listener if (msg.request === 'report-url') console.log('Iframe ready:: URL: '+msg.url); // popup listener else if (msg.request === 'get-page-url') // Send information to popup appAPI.message.toPopup({ request: 'get-page-url', url: window.location.href }); }); });
Sharing Large Data
Finally, you can share large data using a combination of messaging and the local database, as follows:
extension.js:
appAPI.ready(function($) { // Message listener to receive messages from any scope appAPI.message.addListener(function(msg) { if (msg.request === 'get-large-data') // Get data from local database and use appAPI.db.async.get('large-data', function(value) { console.log('Got large data: ', value); }); }); // Request the background scope to retrieve data from a remote site appAPI.message.toBackground({ request: 'get-large-data', url: 'http://example.com/largedata.php' }); });
background.js:
appAPI.ready(function($) { // Message listener to receive messages from any scope appAPI.message.addListener(function(msg) { if (msg.request === 'get-large-data') { // Initiate request for large data appAPI.db.async.setFromRemote( msg.url, "large-data", appAPI.time.minutesFromNow(10), function(response) { // Send trigger to extension scope to handle data appAPI.message.toActiveTab({ request: 'get-large-data' }); } ); } }); });
It’s that simple and a step forward in sharing data between scopes. Give it a try, feel free to improve the code, and see for yourself just how easy it is to use our platform.
If you are not already a Crossrider developer, click the following link to join our cross-browser extension development framework.
Disclaimer: All code examples provided in this article come as-is and must be fully tested in your own extensions.












