In the past I have written a couple of small extensions for chrome but never got a chance to write an extension for firefox. In the last couple of weeks, I tried to write one. Since I use firefox personally, I wanted to create something that would be useful for me.
There were two specific usecases that I could think of to write an extension.
- A remote bookmark system: I don’t use firefox’s sync devices feature so I need something to synchronize the bookmarks among my own devices.
- An extension to create video library for technical talks (mostly from conferences like pycon, gophercon, cpp-con etc.) that are available on youtube.
Both of the applications are based on a simple principle,
- You have the extension installed on the browser.
- You have a web service running somewhere and it is accessible to the browser.
- Using the extension, you send the URLof the page to the web service.
- The web service recieves the URLand depending upon your usecase, it takes an appropriate action. In the case of remote bookmark, it should dump it in adatabase/key-valstore and in the case of video library; it should download that video using a tool likeyoutube-dl.
Here is how I created it.
As you can see that there are two parts to it,
- Firefox addon.
- A web service.
Firefox addon
Here is how I thought of the addon,
- It registers a new context menu on the page, so that when a user right-clicks on the page it will give user an option to send the current URL to the server.
- On the right-click, it reads the title of the page and the current URL and sends it to the server.
To implement this.
We need two specific things,
- manifest.json: Browser uses this file to get all the details required about the addon.
- background.js: This could be any- .jsfile. This is where the actual logic of this addon resides.
manifest.json
In this case manifest.json looks like
{
    "manifest_version": 2,
    "name": "remote-lib-bookmark",
    "version": "1.0",
    "description": "a simple firefox addon.",
    "icons": {
        "48": "icons/border-48.png"
    },
    "content_scripts": [
        {
            "matches": [
                "*://*/*"
            ]
        }
    ],
    "background": {
        "scripts": [
            "background.js"
        ]
    },
    "permissions": [
        "tabs",
        "contextMenus"
    ]
}In this json file we are telling browser
- Name of the addon - { "name": "remote-lib-bookmark" }
- Content scripts - { "content_scripts": [ { "matches": [ "*://*/*" ] } ] }- This tells browser to use this plugin to all of the URLs. More about match patterns can be found on developer.mozilla.org docs 
- Background scripts - { "background": { "scripts": [ "background.js" ] } }- This script will be running through out the addon life time. (typically addon starts on the browser starts and it stops with the browser exits). 
- Permissions - { "permissions": [ "tabs", "contextMenus" ] }- In this case we need these two specific permissions, to use different APIs for - tabsand the- contextMenus.
background.js
The background.js looks like
// connect to the websocket server
var ws_con = new WebSocket("ws://localhost:8888/ws");
//console.log(ws_con);
// set the call-back for onopen
ws_con.opopen = function(event){
	ws_con.send("hello from firefox addon");
}
// set the callback for onmessage
ws_con.onmessage = function (event){
	console.log(event.data);
}
// set the callback for onerror
ws_con.onerror = function(error){
	console.log(error);
}
// log the message that the addon has started.
console.log("remote-lib-bookmark addon started");
// create the context menu
browser.contextMenus.create({
  id: "trial-context",
  title: "remote-bookmark",
  contexts: ["page",  "browser_action", "page_action"]
}, function(e){console.log("menu created");});
// add the onClicked listener to the context menu.
browser.contextMenus.onClicked.addListener(function(info, tab) {
  // We receive the tab and the info about the clicked context here.
  console.log(info);
  console.log(tab);
  // check which context menu has been clicked.
  switch (info.menuItemId) {
    case "trial-context":
      console.log(info.pageUrl);
      // get the page url and the title and send it to the we service (over web socket).
      var bookmark_data = {"title": tab.title, "url": info.pageUrl};
      ws_con.send(JSON.stringify(bookmark_data));
      break;
  }
});
The web service
Since this is a web service running seperately, I chose to write it in python. I used tornado for writing it. Also since I wanted to try out leveldb this time, I used plyvel here.
The code for this looks like
main.py
import tornado.ioloop
import tornado.web
from tornado import websocket
import level_db_connect as ldb
import json
# handler for endpoint `/`
class MainHandler(tornado.web.RequestHandler):
    def get(self):
        bookamrks = ldb.get_all_bookmarks()
        self.write(json.dumps(bookamrks))
# websocket handler for `/ws` endpoint
class EchoWebSocket(tornado.websocket.WebSocketHandler):
    def open(self):
        print("WebSocket opened")
    def on_message(self, message):
        print("received: ", message)
        self.write_message(u"You said: " + message)
        bookmark_data = json.loads(message)
        ldb.add_bookmark(bookmark_data)
    def on_close(self):
        print("WebSocket closed")
    def check_origin(self, origin):
        return True
def make_app():
    return tornado.web.Application([
        (r"/", MainHandler),
        (r"/ws", EchoWebSocket),
    ])
if __name__ == "__main__":
    ldb.init_db() 
    app = make_app()
    app.listen(8888)
    tornado.ioloop.IOLoop.current().start()The web service code is fairly simple, it is a modified version of the sample code of web socket demo on tornado’s docuentation
level_db_connect.py
import plyvel
config = {}
def init_db():
    db = plyvel.DB('./db/', create_if_missing=True)
    config["db"] = db
def add_bookmark(bookmark_data):
    print("bookmark data", bookmark_data)
    config["db"].put(
        bytes(bookmark_data["title"], "utf-8"),
        bytes(bookmark_data["url"], "utf-8"))
def get_all_bookmarks():
    bookmarks = {}
    for key, val in config["db"]:
        bookmarks[key.decode("utf-8")] = val.decode("utf-8")
    return bookmarksAt this point the service just receives the bookmark data over web socket
and serves the json with all of the book marks at the end point /.
This is a very crude implementation of both the firefox addon and the web service
but I think it is good enough to give the reader a basic idea about the subject.
I also live streamed these coding sessions.
If you are interested, please have a look:
and