Creating A Simple Firefox Addon

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.

Both of the applications are based on a simple principle,

Here is how I created it.

As you can see that there are two parts to it,

Firefox addon

Here is how I thought of the addon,

To implement this.

We need two specific things,

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

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 bookmarks

At 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