Create a Simple Web Extension

By  on  
Much like multimedia support in the browser, we initially did browser add-ons all wrong.  For years each browser had its own language and method for installing add-ons, leading to security exploits, performance problems, painful maintenance for developers, and frustrating user experiences.  Today we have the Web Extension API which is supported by most major browsers, a critical step in alleviating many of the problems developers and users experience today. At Mozilla's recent all hands event in Hawaii I set out to create my first web extension; I wanted the extension to be useful but simple, something with a real use case.  In the end I created a very simple web extension that continuously monitors the document.title to replace foul words with asterisks, a safety measure to avoid embarrassment when sharing your screen or having people looking over your shoulder.  Let me walk you through how simple creating a basic web extension is!

Extension Structure

The extension must be contained within a single directory -- this is how I recommend the simple extension be structured:
+ foulmouth-filter // (name of your plugin here)
    + icons
        - 48.png
    - filter.js
    - manifest.json
All of the file names and subdirectories can be named how you like with the exception of manifest.json; that is a standard file name which must be at the root of the extension.  The more complex the extension, the more files and structure will likely be needed.

manifest.json

The manifest.json file holds all of the extension properties including images, title, description, requested permissions, which scripts to run on which hostnames, and more.  The following is the very simple manifest I used for my extension:
{
  "manifest_version": 2,
  "name": "Foulmouth Filter",
  "version": "0.1",
  "icons": {
    "48": "icons/48.png"
  },
  "description": "Filters out filthy words from document titles",
  "content_scripts": [
    {
      "matches": ["<all_urls>"],
      "js": ["filter.js"]
    }
  ]
}
The content_scripts key is important, listing the JavaScript files to include on which hostnames.  For the sake of this simple web extension, I want it to be loaded for every hostname and the meat of the extension will live in filter.js.

filter.js

The filter.js file contains all of the web extension's logic.  The following code searches the document.title for bad words and replaces them with asterisks:
// Google's bad word filter:
// https://gist.githubusercontent.com/jamiew/1112488/raw/7ca9b1669e1c24b27c66174762cb04e14cf05aa7/google_twunter_lol
let badWords = "w3schools|david|walsh|jquery.....".split('|') // loool

// Runs cleanup on the document title
let cleanup = word => {
    document.title = document.title.split(' ').map(word => {
        return badWords.indexOf(word.toLowerCase()) != -1 ? '*'.repeat(word.length) : word
    }).join(' ')
}

// Set up a mutation observer to listen for title changes
// Will fire if framework AJAX stuff switches page title
let createObserver = function() {
    let observer = new MutationObserver((mutations) => {
        // Disconnect the MO so there isn't an infinite title update loop
        // Run title cleanup again
        // Create a new MO to listen for more changes
        console.log('Mutations!', mutations)
        observer.disconnect()
        observer = null
        cleanup()
        createObserver()
    })

    observer.observe(
        document.querySelector('title'),
        { subtree: true, characterData: true, childList: true }
    )
}
createObserver()

// Kick off initial page load check
cleanup()
Note:  You'll notice that I tried to use the MutationObserver API to efficiently listen to changes to the document.title but using a MutationObserver made the browser hang so I'll need to look further into how to prevent that -- setInterval is, sadly, the path of least resistance.  I'm confident that my usage of MutationObserver is the problem, not a browser issue.

Installing the Web Extension for Testing

To install and test the web extension in Chrome:
  1. Open Chrome > Preferences
  2. Click the Extensions tab
  3. Click Load Unpacked Extension , navigate to your extension directory, and click Select
To install and test the web extension in Firefox:
  1. Navigate to about:debugging
  2. Select the Add-ons tab
  3. Click Load Temporary Add-on, navigate to your extension directory, and select any file within the extension directory
Web Extension Add-ons Screen Web Extension Tab The extension will be loaded in each browser and each new tab's document.title will be checked for bad words.  Each browser has different rules for how long the unpacked extension will be active for, so realize if you end a session you may need to enable the local extension again.  You can learn about publishing your web extension here.

Ideas for Improvement

Here are a few additions that would make this extension more awesome:
  • Ability to add custom words, not just bad words from Google (for users, not the dev creating the extension)
  • Ability to toggle the extension on and off quickly, so that you only filter words when on work hours, for example
  • Publish it to the chrome and Firefox Add-on directories!

Resources

The best resource for learning all there is to know about web extensions is MDN.  The web extension we've build here is very simple and doesn't touch any permissions or advanced internal extension APIs, so be sure to read this page to get a complete overview, from start to finish, for creating complete and advanced web extensions. There you have it -- web extensions aren't nearly as complicated or intimidating as they used to be.  This extension took me roughly 30 minutes to put together so if you have a few spare minutes, give web extension creation a try.  You'll be amazed that cross-browser add-ons have become so easy to create!

Recent Features

  • By
    JavaScript Promise API

    While synchronous code is easier to follow and debug, async is generally better for performance and flexibility. Why "hold up the show" when you can trigger numerous requests at once and then handle them when each is ready?  Promises are becoming a big part of the JavaScript world...

  • By
    9 Mind-Blowing Canvas Demos

    The <canvas> element has been a revelation for the visual experts among our ranks.  Canvas provides the means for incredible and efficient animations with the added bonus of no Flash; these developers can flash their awesome JavaScript skills instead.  Here are nine unbelievable canvas demos that...

Incredible Demos

  • By
    Event Delegation with MooTools

    Events play a huge role in JavaScript. I can't name one website I've created in the past two years that hasn't used JavaScript event handling on some level. Ask yourself: how often do I inject elements into the DOM and not add an...

  • By
    DWRequest: MooTools 1.2 AJAX Listener &#038; Message Display

    Though MooTools 1.2 is in its second beta stage, its basic syntax and theory changes have been hashed out. The JavaScript library continues to improve and become more flexible. Fellow DZone Zone Leader Boyan Kostadinov wrote a very useful article detailing how you can add a...

Discussion

  1. Sindhuja

    That was awesome David :)
    AS you rightly quoted, it has not ended up as intimidating it was when I googled about extensions’ creation. My sincere thanks for this post.

    In case you have any article on USB accessing extension creation, kindly ping me :)

  2. Raphael

    Thankz, man. Very helpful ;)

Wrap your code in <pre class="{language}"></pre> tags, link to a GitHub gist, JSFiddle fiddle, or CodePen pen to embed!