Writing a Chrome pageAction Extension to Toggle Between GitHub and Waffle.io

At Revelry we use GitHub issues to manage our projects, and Waffle to organize the GitHub issues. We’ve been using Waffle for quite awhile, but as the company and the number of projects in our org have grown, not having a way to toggle GitHub with its corresponding Waffle board got kind of annoying. So I wrote a silly, simplistic Chrome extension to assist.

If you don’t really care about the software development angle and just want to install it, go here to download the latest release. Otherwise, keep on reading. This is pretty beginner-friendly, since it turns out this is the first time I’ve ever written a Chrome extension. I’m going to focus on the parts that took me the most time digging through the Chrome docs to figure out and breeze past the rest.

Creating the Chrome Extension

Let’s define the problem. I want to build an icon that appears in the location bar only when the location is a GitHub repo or a Waffle project. When I click that button, I want it to toggle from one to the other. And vice versa.

An icon that appears in the location bar is a Chrome pageAction. We need to define a manifest.json file to declare that. Here’s waffle-chrome‘s:

{
  "manifest_version": 2,

  "name": "Waffle.io Button",
  "description": "This extension opens the corresponding Waffle.io board for a GitHub repo.",
  "version": "1.1",
  "page_action": {},
  "background": {
    "scripts": ["background.js"]
  },
  "permissions": [
    "tabs"
  ],
  "commands": {
    "switch": {
      "suggested_key": {
        "default": "Ctrl+U",
        "mac": "Command+U"
      },
      "description": "Switch between waffle.io and the github repo"
    },
    "switch_new_tab": {
      "suggested_key": {
        "default": "Ctrl+Shift+U",
        "mac": "Command+Shift+U"
      },
      "description": "Open in a new tab"
    }
  }
}

 

As you can see, we also declare that we’re going to load background.js (packaged with the extension), we require the tabs permission, and there are some keyboard shortcuts you can use.

Now it’s just a matter of writing the background.js file. Most of the time I’m one of those people who wants to use Babel and transpile from nice beautiful (your opinion may vary) ES6 code, but that’s serious overkill for a project that has around 100 lines of source code in total. So, with my sincerest apologies, you’re going to have to read some dirty ES5. Let’s start with a useful function to get the current active browser tab. You’d think that getting a handle to the tab would be simpler than this, but it’s not, which is why this function is useful:

function withActiveTab(callback) {
  chrome.tabs.query({active: true, currentWindow: true}, function(tabs) {
    callback(tabs[0]);
  });
}

So now every time we need the active tab, we can write the much cleaner-reading:

withActiveTab(function(tab) {
  // Do your thing.
});

Here are the actual functions that do the GitHub and Waffle toggling:

function openInActiveTab() {
  withActiveTab(function(tab) {
    chrome.tabs.update(tab.id, {url: sourceUrlToDestination(tab.url)});
  });
}

function openInNewTab() {
  withActiveTab(function(active_tab) {
    chrome.tabs.create({
      url: sourceUrlToDestination(active_tab.url),
      index: active_tab.index + 1
    });
  });
}

I’m not going to go into the details of the function sourceUrlToDestination because it’s just boring, hacky string manipulation, but you can always read the full source code on GitHub. Anyway, let’s wire up those actions:

// The icon click toggle
chrome.pageAction.onClicked.addListener(openInActiveTab);

// The keyboard shortcuts
chrome.commands.onCommand.addListener(function(command) {
  switch (command) {
    case 'switch':
      openInActiveTab();
      break;
    case 'switch_new_tab':
      openInNewTab();
      break;
  }
});

Lastly, we need to make sure the icon shows up only when we’re on a GitHub repo or Waffle board. Again, the functions that make those decisions are boring, but for any pageAction extension, knowing what events you need to consider is key. We want to activate or deactivate the extension icon every time a tab is created or updated:

function setPageActionDisplayForTab(tab) {
  var url = sourceUrlToDestination(tab.url)
  if(url) {
    chrome.pageAction.setIcon({
      tabId: tab.id,
      path: destinationUrlToIconPath(url)
    })
    chrome.pageAction.show(tab.id)
  } else {
    chrome.pageAction.hide(tab.id)
  }
}

function handleTabCreated(tabId, changeInfo, tab) {
  setPageActionDisplayForTab(tab);
}

function handleTabUpdated(tabId, changeInfo, tab) {
  handleTabCreated(tabId, changeInfo, tab);
}

chrome.tabs.onCreated.addListener(handleTabCreated);
chrome.tabs.onUpdated.addListener(handleTabUpdated);

Your turn

That’s pretty much it! Head on over to GitHub to read more or download the extension package.