Firefox Extension Development: Storage and Browser Button

In this article I am going to expand on our knowledge of Firefox Extension Development from last article. In that article we looked at content scripts and web requests. If you are new to Firefox Extension Development, I recommend starting there. In this article we will look at using the storage API and adding a browser button with a popup. Our extension will track what domains the user visits and the amount of time they visit them for. Then, it will expose that information in the browser button interface.

Step 1: Manifest.json

To start, we will create the manifest.json file for our extension. Insert the following code into a file called manifest.json in the folder you are going to put your extension files in:

{

  "manifest_version": 2,
  "name": "YT Browse Time",
  "version": "2.0",

  "description": "Tracks your browsing time on any given domain",

  "background": {
    "scripts": ["background.js"]
  },

  "permissions": [
    "webNavigation",
    "storage"
  ],

  "browser_action": {
    "default_icon": "icon.png",
    "default_title": "Browse Time",
    "default_popup": "view.html"
  }

}

The manifest.json describes basic extension details, as well as references the scripts our extension will contain. Notice the background script background.js. This will run every time our extension is started and throughout the Firefox session. The browser_action item will put an icon on the toolbar that will open the HTML file listed every time it is opened. Note that the default_icon field is mandatory. It would be nice if Firefox could offer a default icon but no dice – you’ll need an icon before you can test any code. You might notice that the browser_action doesn’t mention a JS file – we’ll load it in the HTML file.

Step 2: background.js

Next, we’ll add our background.js file. This is the file that will use the webNavigation and storage APIs to track which sites users visit – and log the seconds they stay there. The source is as follows:

let currentTab = null;

browser.tabs.onActivated.addListener((event) => currentTab = event.tabId);

setInterval(updateBrowseTime, 1000);

async function updateBrowseTime() {
  if (!currentTab)
    return;

  let frames = null;
  try {
    frames = await browser.webNavigation.getAllFrames({ 'tabId': currentTab});
  } catch (error) {
    console.log(error);
  }

  let frame = frames.filter((frame) => frame.parentFrameId == -1)[0];

  if (!frame.url.startsWith('http'))
    return;

  let hostname = new URL(frame.url).hostname;

  try {
    let seconds = await browser.storage.local.get({[hostname]: 0});
    browser.storage.local.set({[hostname]: seconds[hostname] + 1});
  } catch (error) {
    console.log(error);
  }
}

When the script starts, it starts an interval that runs each second adding time to the current domain. The script stores the current tab ID which is updated each time it changes by the onActivated event listener. From that tab ID, each time updateBrowseTime runs, the current URL is retrieved. The browser can contain multiple frames (such as an iframe) so the topmost one must be found using filter.

Screenshot of loading the extension into Firefox.

Visit the about:debugging page and load the extension using Load Temporary Add-on…

Screenshot of the storage API in action.

Then, visit a regular site for a while and go back to about:debugging and click Inspect. You should see the domain and the seconds you visited listed under Extension storage if the extension is working properly.

Step 3: Popup Files

Screenshot of the finished popup.
Our finished popup.

view.html

<!DOCTYPE html>
<html lang="en" dir="ltr">
  <head>
    <meta charset="utf-8">
    <link rel="stylesheet" href="style.css"/>
  </head>
  <body>
    <div id="popup-content">
      <div id="sites">
        <div class="item" id="item_1">
          <p class="name">Name</p>
          <div class="bar"></div>
          <p class="time">Time spent</p>
        </div>
        <div class="item" id="item_2">
          <p class="name">Name</p>
          <div class="bar"></div>
          <p class="time">Time spent</p>
        </div>
        <div class="item" id="item_3">
          <p class="name">Name</p>
          <div class="bar"></div>
          <p class="time">Time spent</p>
        </div>
        <div class="item" id="item_4">
          <p class="name">Name</p>
          <div class="bar"></div>
          <p class="time">Time spent</p>
        </div>
        <div class="item" id="item_5">
          <p class="name">Name</p>
          <div class="bar"></div>
          <p class="time">Time spent</p>
        </div>
      </div>
      <p class="attribution">Icons by Font Awesome</p>
    </div>
    <script src="popup.js"></script>
  </body>
</html>

Above is the code for view.html. It defines the interface for our popup with the help of the linked style.css file. The JavaScript file linked at the bottom of the body will retrieve the information from storage and modify the DOM to display the correct information.

style.css

body {
  font-family: sans-serif;
  text-align: center;
}

#popup-content {
  width: 600px;
}

.item {
  display: flex;
  margin: 10px 0px;
  align-items: center;
}

.item .bar {
  width: 350px;
  background-color: royalblue;
  border-radius: 5px;
  height: 30px;
}

.item .name {
  width: 150px;
  padding: 10px;
}

.item .time {
  width: 100px;
  padding: 10px;
}

Above is the stylesheet for the browser button. You will note that it uses fixed widths. This is because our popup will fit the size of the document and will not change, so there isn’t a need to make it responsive or use relative values.

popup.js

browser.storage.local.get().then(function (result) {
  let results = [];

  for (const key of Object.keys(result)) {
    results.push({ 'site': key, 'time': result[key]});
  }

  results.sort((x, y) => y.time - x.time);
  if (results.length > 5) {
    results = results.slice(0, 5);
  }

  for (let i = 0; i < 5; i++) {
    let line = document.getElementById('item_' + (i+1));
    if (i < results.length) {
      line.getElementsByClassName('name')[0].textContent = results[i].site;
      line.getElementsByClassName('time')[0].textContent = Math.floor(results[i].time / 3600) + "h "
      + Math.floor((results[i].time % 3600) / 60) + "m " + results[i].time % 60 + "s";
      line.getElementsByClassName('bar')[0].style.width = ((results[i].time / results[0].time) * 350) + "px";
    } else {
      line.style.display = 'none';
    }
  }
});

Above is the code for popup.js. This starts by retrieving the browser’s entire local storage for our extension. Then, the result is organized into an array that we can use. The items we created in our HTML file are filled with the information retrieved from storage, or set to display: none if there is not enough information.

Conclusion

Place all the files in the same directory as your manifest.json and you are good to go. The extension should work as shown in the popup screenshot.

One thought on “Firefox Extension Development: Storage and Browser Button”

Comments are closed.