This post by Will Nuckles originally appeared on the LookFar blog.
Recently, I’ve been having some issues with Facebook.
Well, really with spoilers, and how freely my friends share them. As a father of two young children, it’s not always easy to stay up to date. My Facebook feed, unfortunately, was becoming a spoiler minefield. I realized I had three choices:
- Stay off Facebook, and deprive everyone of my snarky wit.
- Unsubscribe from those friends, but miss their snarky wit.
- Get a Chrome extension to block only some of that snarky wit.
Options 1 and 2 were obviously unacceptable, so I started looking into number 3. After a bit of searching, I realized that while plenty of these extensions existed, most of them were bloated and didn’t really let you block custom words or phrases. Being a DIY sort of dude, I decided to createmy own Chrome Extension that allows blocking custom terms in Facebook.
Like any good project post, I’ll let you take a look at the finished extension before we start digging into the step-by-step of building it. It’s an example of a fairly simple, lightweight Chrome extension, so go ahead and check it over on Github before we move on.
Going forward, I’m assuming a couple of things about you. I assume you know what an extension is, that you have a little coding experience (JavaScript & HTML/CSS), and zero tolerance for that one friend who posted their reaction to the Governor killing Hershel (go through these steps, and you’ll be able to block spoilers on the LookFar blog as well).
Alright? Alright. Let’s get our hands dirty.
Building the Manifest
There are two key components to a Chrome Extension: the manifest and the backend code. The manifest will tell Chrome how to use the backend, and the backend tells Chrome what you want to be done.
Let’s start with the Manifest. The manifest will give Chrome some details about your extension (name, description, version number), along with some instructions on how Chrome should use it (what files to use, what sites to use those files on, permissions). Here’s what my manifest looks like:
Name, description, and version are all self-explanatory. “manifest_version” refers to the version of the manifest that Chrome itself recognizes. Anything after Chrome 18 should say 2.
In “browser_action”, I’m specifying a couple of things. First is the “default_icon” that will show in the upper right corner of Chrome.
We’re also specifying what HTML file to use to render the popup when that icon is clicked. You won’t always need a popup when creating extensions, but our spoiler blocker needs an interface to enter keywords, so we’re telling it to look at “popup.html” for rendering. We’ll chat more about this later.
Next is the permissions section. I’m telling Chrome that I’d like to access the Active Tabpermission to gain access to that tab, and we’re getting a place to keep keywords by using theStorage permissions API.
The “content_scripts” section is an important one. This is where we set what websites this extension will work on and what files to use to make the manipulations. For the No Spoiler extension, we set the “matches” section to “*://*.facebook.com/*”, and use the jQuery and “spoiler.js” files when we are on any website in the “matches” section.
That wraps things up for our manifest. Take a look at Google’s Manifest File Format page to see a list of everything that is available for use in the manifest file.
Next, we have the popup.html file. This file dictates what is rendered on screen when the Spoiler extension’s icon is clicked in the toolbar. This file is written exactly how you’d write an HTML web page – with CSS, JavaScript, and whatever other trickery you’d like to inject. In mine, I have a simple input box, a submit button, and a list that will be populated with our own spoiler references. Since the HTML file will be directly interacting with my JavaScript file, I also have our JS file references.
Now we move on to the meat of the extension.
JavaScript Walkthrough
First things first, we need to allocate some storage to save our list. Chrome has an awesome storage API that allows you to create and access a small chunk of storage space, so we don’t have to fight JavaScript when it comes to saving files locally. We have two options for storage: local and sync. Local storage allows up to 5mb to save on your local machine. Sync only allows 100kb, BUT it will be synced to all of the machines with your extension installed, provided that your Chrome user is logged into them as well.
PRETTY FREAKING NEAT!!!
Since we’re only saving simple key/value pairs that probably won’t get over 100kb, I’m going to go with sync.
We’ll start by initializing a variable called ‘spoilerList’. I’m going to pass this one around a bit, so I want it to be available to everyone in my JS file. Next, let’s ask chrome.storage.sync if we have something in memory called ‘spoilerItem’. Once the call responds in all its asynchronous goodness, run the function to check if anything came back. If the results are good, then no problem; we’ve loaded up our list, and we’re free to go. If the results come back null, let’s just throw this spoiler list object into our ‘spoilerList’ variable and then save.
Protip: the chrome.storage system saves in key/value pairs where ‘spoilerList’ is our key, and an array will be our value.
Calling ‘saveSpoilerList()’ uses the same storage.sync API, but with a ‘set’ method. This sets your sync storage’s ‘spoilerItem’ key to hold the contents of whatever you push. In our case, it’s the ‘spoilerItem’ array that’s in our ‘spoilerList’ object.
Now that our storage is set, let’s go take a look at our live code. This group of code is a bunch of listeners that execute a function when their specific element being watched is acted upon. If you didn’t notice in the popup.html file, I’m referencing jQuery; some of the following action items will look a little foreign to you if you haven’t had experience with jQuery yet. If that’s the case, do yourself a favor and block some time out to get friendly with it. It makes developing in JavaScript much easier.
$(function() { is jQuery for “run this when the page loads”. First off, we hit a couple of function calls. We’ll take a look at these two later after we add our first spoiler.
The first listener is for the submit button on the popup.html page. After we type a word into the input field and click submit, a lowercase version of the word gets pushed into our ‘spoilerItem’ array. We then save and update the list that shows all our spoiler terms being blocked.
The ‘updateListView()’ function takes our ‘spoilerItem’ array and injects it into our popup.html page. If ‘spoilerList’ contains info, we empty the html div that contained the old list and generate a new list to be injected in its place. Once your word is added to the list, it’ll show up in the popup to let you know all your active terms.
Now, back to the submit buttons listener for the next function call. Buckle your seat belt, because this one is a doozy.
This little guy is what this extension is all about. Here, we parse through our list of spoilerItems, and search the DOM for anyone mentioning anything that matches any of your terms in your list. We do this by using a forEach loop. Before we proceed, you should know that you shouldn’t just blindly scan the ENTIRE page. This can get really expensive, especially as the page gets larger and larger from its endless scrolling feature. Instead, we’re just going to search the entire DOM for all of the <p> elements.
Why the <p> elements? Well, that’s because that’s where the spoilers live. A quick peek behind the scenes of Facebook shows that every “OMG” your friends type out is contained in a <p> element. With jQuery, all we need to do is call $(‘p’). This will create an array of every <p> element in the DOM. To make it that much better, jQuery has a neat little function that allows you to gather all of the <p> elements that meet some specific criteria.
Our requirement for including an element is that it must contain a term in our spoiler list. To do this, we call $(‘p:contains(item)’), where ‘item’ is an individual search term. This finds a <p> tag, and looks inside of it to see if one of your terms is inside of it. To keep adding to the AWESOMENESS that is jQuery, we can chain together all of our items in our array so that we search the entire DOM once to gather each <p> tag that contains our spoiler text. HOLY EFFECIENCY BATMAN!!!
So we’ve found our spoilers. Now what? That’s up to you to decide, but I took the simple road and decided to blur them out with some CSS. So after our $(searchString), we append .parents(‘userContentWrapper’). This tells jQuery that you want to target that particular parent of the <p> element so we can blur out the entire post, and not just the words. Then, we toss on our CSS modifier with .css(‘-webkit-filter’, ‘blur(5px)’). You can replace my blur with whatever your little heart desires. Use your imagination! To see if you’ve got any spoilers to block, refresh the page to let the JavaScript search the page. (Backlog: add slick auto update function to auto scan the page without having to refresh.)
One More Problem to Solve
The Facebook team, in their infinite wisdom, have implemented an “endless scrolling” feature that cuts down on data transmission rates all the way around. This is good for the user…but a pain in the rear for developers who want to manipulate things on the page. Unless explicitly told not to, JavaScript runs once when the page loads and sits until it’s called. We’ll need to add in a nifty piece of technology called the Mutation Observer to compensate for this. The Observer allows the JavaScript file to watch the DOM for any changes. You can catch blanket changes, or small specific ones.
We set up the mutation observer by declaring a new MutationObserver that will fire off searchForSpoilsers() when certain criteria are met. We then define those criteria down below with observe.observe and add in a specific element to watch. Our blocker needs to watch the element whose id begins with “topnews_main_stream_”, because we know that’s where the feed lives and that’s what will be updated. We set the subtree option to true, so we can watch everything inside of the watched element, and the attribute option to true, so we can watch every element’s attributes too.
Tying It All Together
Any time an update is made to our “topnews_main_stream_” element, our mutation observer will fire off searchForSpoilers(), and catch all of the freshly updated stories!
Testing out our spoiler blocker. Believe it or not, most posts contain the letter “a”.
Once you’ve finally watched the season finale, and are ready to join your friends’ discussion of the latest GoT casualty, you can remove some of your terms. We’ve got two ways of doing this. We can remove a single term by simply clicking it, or we can remove them all at once by clicking “clear”.
Once we have all of our files created, all we have to do is load them into Chrome and set them loose. In your Chrome browser, go to “chrome://extensions” and make sure “Developer mode” is checked on in the upper right hand corner. Click “Load unpacked extension…”, navigate to your containing folder, and click “OK”. If you need to fiddle with any of the code, save your changes and make sure to click “Reload (Ctrl+R)” on the extensions page to refresh the blocker.
It’s that easy! Extensions have the ability to tailor your online user experience to your own needs. If you want a feature that will enhance the way you interact with a website but know the developers probably won’t ever create that feature, BUILD AN EXTENSION! It’s fun, it’s creative, and it forces you to exercise your coding skills!
JUST DO IT! BE THE CHANGE! WIN!
EXTENSIONS!