DIY Adblocker - an introduction to write chrome extensions

Introduction

This article covers the basics of writing chrome extensions by building a simple adblocker. It also explains how adblock detection works.

I rarely feel compelled to use an adblocker. This is partly because most pages I view have few, if any, ads, but the main reason is I'm wary of 3rd party browser extensions. Adblock Plus can "Read and change all data on the website you visit." This makes sense, because the extension is removing elements from the webpages you visit. Unfortunately these permissions allow for plenty of mischief [1]. Even if you trust the extension's authors today, the headline "Firms buy popular Chrome extensions to inject malware, ads" should give you pause [2]. Luckily it does not take much work to write a blocker yourself.

Adblock Plus extension permissions

Writing a chrome extension

Chrome extensions are build using standard web technologies. The primary differences between an extension and a web app are that extensions have access to additional browser specific APIs and run differently that normal pages.

To start off, create a new folder with the name of your extension and add the following file "manifest.json" to it. The manifest is an "index" of the extension and most of the fields are self explanatory.

{
  "name": "dyi-adblocker",
  "version": "1.0",
  "description": "A simple adblocker",
  "permissions": ["webRequest", "webRequestBlocking", "http://*/", "https://*/"],
  "background": {
	"scripts": ["blocked_domains.js", "background.js"]
  },
  "manifest_version": 2
}
The file manifest.json lists the components of the extension and describes what permissions it needs.

The permissions field lists what webpages the extension has permission to access. We want our extension to have access to all pages, hence the wildcard patterns. Note: permission to access an http page is distinct from permission for the https equivalent. The permission field also lists which "special" APIs and feature we wish to make use of.

Our extension will run in the background and automatically intercept and filter requests. To set this up we give a list of scripts to load and run in the background. The order of scrips is important so make sure to list a script after its dependencies. We will fill in the scripts later, but for now just create a blank file for each and add the line console.log("My chrome extension works!!!"); to background.js.

Loading a chrome extension

To test an extension go to chrome://extensions and check the developer mode box. Now click load unpacked extensions and select the folder containing the extension's code and manifest.

loading a chrome extension

Clicking on the background page link should bring up the Developer Tools. Now go to the console tab. Any output or error messages should be displayed here. Additionally you can access the standard debugging tools from here.

chrome developer console

The webRequests API

The plan for the blocker is to intercept requests and block ones to domains hosting ads. Perusing the chrome docs reveals the chrome.webRequest.onBeforeRequest event. The onBefore request "Fires when a request is about to occur. This event is sent before any TCP connection is made and can be used to cancel or redirect requests. "[3] All we have to do is copy and past the example to get version one of our blocker.

chrome.webRequest.onBeforeRequest.addListener(
	function(details) {
		console.log("blocking:", details.url);
		return {cancel: true };
	},
	{urls: ["*://*.youtube.com/"]},
	["blocking"]
);
background.js sets up an event handler to cancel all requests to the specified urls.
given chrome extension access to page
Before trying the extension out, reload it and make sure that it has access to all pages.
requests to the server have been blocked by an extension
It works!

Creating a blacklist

Adblockers are glorified blacklists. uBlockOrigin has all sorts of complicated rules for matching specific page elements such as google.*###rhs_block .mod > .luhb-div > div[data-async-type="updateHotelBookingModule"] [4]. To keep the blocker simple, we will only pay attention to domain names. A list of domains to block is available at github.com/StevenBlack/hosts.

The entries are in the format of a host file ex: 0.0.0.0 tracking.klickthru.com. Chrome expects specific url patterns, so we need convert the entries to look like "*://*.tracking.klickthru.com/*"[5]. Additionally, empty lines and comments need filtering out. Lots of trial and error yields the following magic incantation. In retrospect, writing a python script to do this would have been easier.

grep "^0\.0\.0\.0" hosts | cut -d ' ' -f2 | sed 's/\([^\n]*\)/"*:\/\/*.\1\/*",/' > blocked_domains.js

Now edit the first and last lines of blocked_domains.js so that it is a valid JavaScript array that looks like this:

var blocked_domains = [
"*://*.lb.usemaxserver.de/*",
"*://*.tracking.klickthru.com/*",
...
...
"*://*.zmedia.com/*",
"*://*.zv1.november-lax.com/*"];
blocked_domains.js consists of a single array of blacklisted domains

Also remember to update background.js to use our new list.

chrome.webRequest.onBeforeRequest.addListener(
    function(details) {
        console.log("blocking:", details.url);
        return {cancel: true };
    },
    {urls: blocked_domains},
    ["blocking"]
);

I tried the extension on cnn's website. Looking at the network activity page reveals that the extension is blocking lots of requests. The domains being blocked look like mostly 3rd party ad and analytics domains. The page has no immediately visible ads, so the extension is working.

Failed network requests on cnn.com

How adblock detection works

Most adblockers remove elements that look like ads. To detect an adblocker, a site will set a "honeypot" element that looks like or is an ad and checks to see if the element is removed. Because our blocker blocks requests rather than removing page elements, current blocker blockers do not detect it (go to forbes.com which has ads galore if you want to see this for yourself).

Adding a user interface

There is a possibility that this extension could break the main functionality of some sites. So let's add an enable/disable button. The first step is to update the manifest to specify the location of the html page that will show when the extension's icon is clicked on.

  "browser_action": {
    "default_popup": "popup.html"
  },
Add the following entry to the manifest to specify the control panel page

Now create fill out popup.html with a toggle button. Chrome does not like inline JavaScript in extension pages, so the JavaScript to handle button clicks will be in a separate file.

<html>
<head>
        <script src="popup.js"></script>
</head>
<body>
	<h3>dyi-adblock</h3>
	<input type="button" id="toggle_button" value="Disable" />
	<hr>
	Written by Adrian Stoll
</body>
</html>
Not much here. Just a header, button, and script.
the extension ui

Communicating between the UI page and the background script

We can access the background page from the UI window using the chrome.extensions.getBackgroundPage method. This method gives access to the background script's window object which exposes all global variables.

Source code

Download complete source code from github.com/adrs/dyi-adblocker

final version of background.js
var enabled = true;
chrome.webRequest.onBeforeRequest.addListener(
	function(details) {
		return {cancel: enabled };
	},
	{urls: blocked_domains},
	["blocking"]
);
	
popup.js
window.onload = function () {
	function updateLabel() {
		var enabled = chrome.extension.getBackgroundPage().enabled;
		document.getElementById('toggle_button').value = enabled ? "Disable" : "Enable";
	}
	document.getElementById('toggle_button').onclick = function () {
		var background = chrome.extension.getBackgroundPage();
		background.enabled = !background.enabled;
		updateLabel();
	};
	updateLabel();
}
	
final version of manifest.json
{
  "name": "dyi-adblocker",
  "version": "1.0",
  "description": "A simple adblocker",
  "permissions": ["webRequest", "webRequestBlocking", "http://*/", "https://*/"],
  "background": {
    "scripts": ["blocked_domains.js", "background.js"]
  },
  "browser_action": {
    "default_popup": "popup.html"
  },
  "manifest_version": 2
}
	

So there you have it, a short simple adblocker. The only major problem is keeping the blacklist up to date, but scripts could make that easy. For more examples of chrome extensions see chrome's developer docs. Happy coding!

References