📜 ⬆️ ⬇️

Universal messaging between pages in extensions

Hello! Today I want to show you my little hobby project, which allows you to greatly simplify the development of extensions in different browsers. I just want to warn you that this is not a framework that does the same thing everywhere, it’s a library that organizes a single way of communicating between all pages of an extension, and to use it you must at least understand the work of api browsers for which you write.
And yes, I almost forgot, it greatly facilitates porting extensions from Chrome!

Main functions:
- Messaging with the background page and the ability to send a response;
- Single repository on all pages.

Introduction


When I was faced with the need to port extensions to all current browsers, I found that everything is different everywhere. And to use a single code, you will have to write a lightweight wrapper that unifies interaction with the repository and pages.

I really wanted to bring everything to the likeness of api chromium. It is very convenient to send messages to the background page and be able to respond. It is convenient when there is a single repository everywhere and you can call it from any page.
')
In general, it is about this unification that will be discussed.

How messaging works


Messaging, as already mentioned, is almost like Chrome, but with no big changes.

The diagram shows the mechanism of interaction between extension pages.

Injected page - the page on which the extension script is connected, can only send messages to the background page and receive a response only through the response function.

Popup page - a popup page, can send messages only to the background page.

Options page - extension settings page, i.e. The html page inside the extension, opens when you click on the settings item (in Chrome for example), can send messages only to the background page.

Background page - background extension page, when it sends a message - the message arrives immediately in both the popup menu and the options page. But it does not come to the Injected page, but can send messages to the active tab.
* In Firefox, sending from the background page in the popup menu and the options page is activated by a separate flag, since This function is almost not needed.

I also note that in Safari and Firefox, the popup page is loaded once and runs continuously, while in Chrome and Opera 12 the page loads when you click on the extension button.

* In Firefox, you cannot send messages to a closed / not active page.

Message receipt code:
mono.onMessage(function onMessage(message, response) { console.log(message); response("> "+message); }); 

Post code:
 mono.sendMessage("message", function onResponse(message) { console.log(message); }); 

The code for sending messages to the active tab (only from the background page):
 mono.sendMessageToActiveTab("message", function onResponse(message) { console.log(message); }); 

In general, everything is as close to Chrome as possible.

Storage


All browsers store different.
Firefox: simple-storage.
Opera: widget.preferences, localStorage.
Chrome: chrome.storage.local, chrome.storage.sync, localStorage.
Safari: localStorage.

The library unifies the interface of working with the repository.

Storage code:
 mono.storage.set({a:1}, function onSet(){ console.log("Dune!"); }); mono.storage.get("a", function onGet(storage){ console.log(storage.a); }); mono.storage.clear(); 


To use chrome sync storage, the code looks a little different, while other browsers will use local storage.
 mono.storage.sync.set({a:1}, function onSet(){ console.log("Dune!"); }); mono.storage.sync.get("a", function onGet(storage){ console.log(storage.a); }); mono.storage.sync.clear(); 


How it works:

The storage works as follows:
browser \ pagebackgroundoptionspopupInjected
ChromelocalStoragelocalStorage via messages
Opera 12 (localStorage)
Safari
Chrome (storage)chrome.storage
FirefoxSimple storageSimple storage via messages
Opera 12widget.preferences

In the table, everything with the prefix "via messages" means that the storage works through sending service messages to the background page, of course the background page should listen to incoming messages. In other cases, working with the repository is direct.

Connection to extension


Chrome, Safari, Opera 12
You need to connect mono.js to each extension page.

Firefox (Addons-sdk only)
Everything is a little more complicated, you need to know how Addons-sdk works.
In lib / main.js, you need through require to connect the monoLib.js file and connect all the other pages to it, as well as background.js (i.e., the background page).

I will give an example of main.js from the test extension:
main.js
 (function() { var monoLib = require("./monoLib.js"); var ToggleButton = require('sdk/ui/button/toggle').ToggleButton; var panels = require("sdk/panel"); var self = require("sdk/self"); // ,      settingsBtn   -  options.html var simplePrefs = require("sdk/simple-prefs"); simplePrefs.on("settingsBtn", function() { var tabs = require("sdk/tabs"); tabs.open( self.data.url('options.html') ); }); //   port  , .. options.html   mono.js var pageMod = require("sdk/page-mod"); pageMod.PageMod({ include: [ self.data.url('options.html') ], contentScript: '('+monoLib.virtualPort.toString()+')()', contentScriptWhen: 'start', onAttach: function(tab) { monoLib.addPage(tab); } }); //    injected page pageMod.PageMod({ include: [ 'http://example.com/*', 'https://example.com/*' ], contentScriptFile: [ self.data.url("js/mono.js"), self.data.url("js/inject.js") ], contentScriptWhen: 'start', onAttach: function(tab) { monoLib.addPage(tab); } }); //      var button = ToggleButton({ id: "monoTestBtn", label: "Mono test!", icon: { "16": "./icons/icon-16.png" }, onChange: function (state) { if (!state.checked) { return; } popup.show({ position: button }); } }); //     var popup = panels.Panel({ width: 400, height: 250, contentURL: self.data.url("popup.html"), onHide: function () { button.state('window', {checked: false}); } }); //    monoLib * ,   ,    onAttach monoLib.addPage(popup); //   addon    var backgroundPageAddon = monoLib.virtualAddon(); //     monoLib monoLib.addPage(backgroundPageAddon); //   ,   var backgroundPage = require("./background.js"); //   addon   backgroundPage.init(backgroundPageAddon); })(); 

But alas, this is not all. Our common background.js page should be able to work in module mode. And you need to connect there mono.js.

To do this, add the following to the top of the page:
background.js
 (function() { //     if (typeof window !== 'undefined') return; //  window ( ) window = require('sdk/window/utils').getMostRecentBrowserWindow(); //     ,    window.isModule = true; var self = require('sdk/self'); //     data/js mono = require('toolkit/loader').main(require('toolkit/loader').Loader({ paths: { 'data/': self.data.url('js/') }, name: self.name, prefixURI: self.data.url().match(/([^:]+:\/\/[^/]+\/)/)[1], globals: { console: console, _require: function(path) { //   require   mono.js switch (path) { case 'sdk/simple-storage': return require('sdk/simple-storage'); case 'sdk/window/utils': return require('sdk/window/utils'); case 'sdk/self': return require('sdk/self'); default: console.log('Module not found!', path); } } } }), "data/mono"); })(); var init = function(addon) { if (addon) { mono = mono.init(addon); } console.log("Background page ready!"); } if (window.isModule) { //  ,  init . exports.init = init; } else { //    -  init(); } 

After the init function is executed, then everything else that depends on mono can already be started.

* note, in the mode of the module in the scope even do not have a window, so everything must be connected separately.

Crutches


In order to use native api in each browser, methods for their identification are needed.
The library provides the following list of variables.


You can choose which api to pull in the browser using these flags.

Firefox Utilities


In Firefox, any page (if it is not a module, that is, a background page) is the only thing that can send messages. Therefore, I added a number of services that were useful to me.

Sending messages to the popup window:
 mono.sendMessage('Hi', function onResponse(message){ console.log("response: "+message); }, "popupWin"); 

Resize popup page:
 mono.sendMessage({action: "resize", width: 300, height: 300}, null, "service"); 

Opening a new tab:
 mono.sendMessage({action: "openTab", url: "http://.../"}, null, "service"); 

In general, if you take a look at the code, I am sure you will have no difficulty adding your “services” for the convenience of interacting with the API.

Assembly


Library for convenience is divided into several files. Everything is collected using Ant, the build file is in “/ src / vendor / Ant”. In it, you can remove unnecessary browsers.

Conclusion


Here is a plain library. Of course, everyone has any bugs and shortcomings. But it seems to work. I am sure that you will not have much difficulty in understanding the code and where you need what you need to file for yourself.
If it seemed to you all this is too complicated, there is an example of a simple extension in the gita that is built for Chrome, Opera 12, Safari, Firefox. I use mono in several of my extensions and it has become indispensable for me.

Thank you for reading!

Github

Source: https://habr.com/ru/post/246351/


All Articles