Ever had an idea for a great Chrome Extension ? Did you know that a chrome extension is just javascript ? And where there’s Javascript, we can write some CoffeeScript !

This post aims to give you an overview of building a chrome extension wrote in CoffeeScript. While being familiar with the latest is mandatory to understand what’s going on there, no previous experience with Google Chrome is needed.

{% end_excerpt %}

Our chrome extension will be a fully fonctional tab switcher that mimics Command-T feature of Textmate (also known as fuzzy finding).

Why doing it in CoffeeScript ?

Coffee Script is a thin and elegant syntaxic layer on top of Javascript, allowing you to write cleaner and concise code and still outputting almost readable javascript. Why should we avoid a such nice tool to write a chrome extension ! Plus it’s fun to write, it will remind you Ruby and Python, while still letting you do Javascript wizardry.

For french readers, I gave a talk at a recent Paris.rb event, you can read my slides until we get the video online.

Our goal

Command-T is a battle-tested quick-file-access method that proved to be efficient. It should be useful to have it available in Chrome, especially if you often have more than 20 tabs opened, where they all look like pinned ones. Typing a few letter of the URL is clearly faster than hammering like a monkey the next tab hotkey !

Couldn’t we port that great feature in Chrome ?

Dissecting an extension

Chrome being a popular browser, it is as expected from a modern browser, pretty easy to extend. Google’s starter guide is a good resource and gives you a quick intro.

Skipping implementation details, it’s basically the following :

  • A content script is executed in the context of the current page, having access to the DOM
  • An extension script is executed in what you could call chrome context, meaning it can manipulate chrome objects like tabs, windows
  • The background page include the extension script
  • These two contexts are sandboxed, meaning you can’t collide with the scripts running on the page
  • Communication between them are made through message passing

Get confortable

The absolute minimum is the following structure :

tabswitcher               # Repository root
      /background.html    # Extension's 'main view'
      /manifest.json      # Extension settings

Coffee Script need to be compiled in the first place, automating it brings two benefits : it’s comfortable to develop with, a contributor can just check out your sources and run your command to build the whole thing. This lower the entry barrier for contributing to our extension =).

The simplest way to handle compilation easily is to build a Cakefile (a Rakefile or Makefile in CoffeeScript).

We’ll write it to take *.coffee input from /src and output javascript in /build using this command. Our goal is to do the following to build our extension :

$ cake build

But while in development, it’s easier to have our files monitored to reflect changes as we save them. So To watch the src/ folder and reflect any changes made there, there’s the watch command :

$ cake build

coffee -h tells us these commands are directly available :

$ coffee --output build/ --compile src/

Good. It’s time to bake this into a Cakefile. Below are the interesting parts of it :

task 'build', 'Build extension code into build/', ->
  if_coffee ->
    ps = spawn("coffee", ["--output", JAVASCRIPTS_PATH,"--

compile",COFFEESCRIPTS_PATH]) ps.stdout.on(‘data’, log) ps.stderr.on(‘data’, log) ps.on ‘exit’, (code)-> if code != 0 console.log ‘failed’

If you’ve alreay wrote any Rakefile, it’s quite similar. If not, we basically declare the command build to be invokable through cake build. We handle if the coffee binary is available or not in the $PATH and finally execute our coffee command as expected.

A small overview

Manipulating the DOM through the standard API bores me to death, so let’s grab Zepto to do the big work for us. We could have used JQuery but we don’t need all the browser compatibility stuff, so Zepto with its minimal features set is a perfect match. Let’s store it in /libs.

Our final structure is the following :

tabswitcher               # Repository root
      /build              # Generated Javascripts end there
      /libs               # Dependencies
      /src                # Our code
      /background.html    # Extension's 'main view'
      /manifest.json      # Extension settings
      /Cakefile           # Starts build task

Ok, we’re now ready to spill some coffee into Chrome :

$ cake watch

The extension itself

Our extension is quite simple in its behavior :

  • listen for keyboard events if ctrl-\ was pressed
  • if pressed, insert some html in the page containing our UI
  • display opened tabs
  • wait for user input
  • on enter in the input, go to that tab

So in these steps, those two are calls to chrome api :

  • list all opened tabs, we’ll name it getTabs
  • go to a tag, as switchTab

Our content script that run in the current page, it will send these two messages to the background script, which is the only one that can make these calls.

We end with the following process :

The red arrows are message passed from the content script to the background page ( message passing ). It’s similar to firing custom events with JQuery and listening for them, but with a particular API.

Implementation

The content script is src/content.coffee and background script lives in src/background.coffee

First things first : a tab. It’s simpler than what you may have expected

  tab =
    id : 43
    windowId : 4
    url: "http://google.com"
    title: "Google"

We don’t need to handle them directly as the Chrome API will do the job for us, but it’s a starting point.

Let’s examine the content script, which is where all the work happens.

An Application class encapsulates the main logic. It setups the UI, binds the callbacks and pass messages to the background page.

class Application
  constructor: ->
    # Inject our html into the view
    @injectView()

    # Install a listener for our input
    @element().find('input').keyup (event)=>
      @onInput(event)

    # Spawn a view that handle results display
    @tabListView = new TabListView @element().find('ul')

  element: ->
    # Return our base div
    @element_ ||= $('#tabswitcher-overlay')

  onInput: (event)->
    # When something is entered is the input, filter tabs !
    candidates = fuzzy(@tabs(), event.target.value)

    # Update tabs that match
    @tabListView.update candidates

    # If enter
    if event.keyCode == 13
      # Go to that tab
      @switchTab candidates[0].tab iftes?

  hide: ->
    # ...
  show: ->
    # ...

  switchTab: (tab)->
    # We're switching tab, hide the UI before leaving
    @hide()

    # Send message to the background script
    chrome.extension.sendRequest(message:"switchTab", target:tab)

  hotKeyListener: (event)->
    # Listen for ctrl-\
    if event.keyCode
      if event.ctrlKey && event.keyCode == 220 # Ctrl + \
        # Send message to background script, ask for list of tabs
        chrome.extension.sendRequest {message: "getTabs"},
          (response)=>
            @tabs_ = response.tabs
            @show()

      else if event.keyCode == 27 # ESC
        @hide()

  injectView: ->
    # Inject our UI in the DOM
    $('body').append ...

app = new Application()

# Attach our handler
window.addEventListener("keyup", (e)->
  app.hotKeyListener(e), false)

After defining Application we just instanciate it and bind our listener, to grab keyboard events. For the sake of readability, I’ve skipped the fuzzy filter implementation, which is kind of naive but do the job as expected. Bold stuff as you can see in the screenshot in the beginning of the post is handled in another class named TabView.

Let’s now see the script running on the background page that respond to calls made from the content script :

# Install the message listener
chrome.extension.onRequest.addListener (request, sender,

sendResponse)-> # Select the right response given the message switch request.message # Grab all tabs when “getTabs” chrome.windows.getCurrent (window)-> chrome.tabs.getAllInWindow window.id, (tabs)-> # We’ve collected all tabs, let’s send them back sendResponse(tabs:tabs) break when “switchTab” chrome.tabs.update(request.target.id, selected:true) sendResponse({}) break else sendResponse({})

Pretty straight-forward, we just take incoming message and handle them. Only the message ‘getTabs’ sends back a response : an array of tabs returned by Chrome.

What now ?

Well, beside some crappy HTML to render tabs, there’s nothing left. The complete code of this extension is available on GitHub where you can explore it, fork it as you want !

Remember that you need to enable developer’s mode in chrome extensions to install it directly from the sources.

You can also install the released version.

Coffee Script is available everywhere you can use javascript, with some tooling to kick in compilation ! Set up two tasks, adjust .gitignore and there it works.

Chrome extensions are way simpler to write than I thought ! Next, understanding how Chrome handles security and isolation through sandboxing and still sharing DOM access is pretty impressive.

Once you grasped the big picture, it’s finally just like building any web app interactive UI !