#100DaysofCS Chrome Browser Extension

Use Case: To encourage community participation in growing the catalog of resources.

I wanted to make a quicker, easier way to be able to submit new resource links to 100DaysofCS.com so I built a Chrome browser extension! It's free to download and requires only a valid submission ID (to prevent spam and control the quality of the submissions) to push new URLs directly to 100DaysofCS.com.

If you want to learn more about the #100DaysofCS project, you can view this write-up I did previously. Below I'm going to give a brief overview as to how I created this browser extension in Chrome.

Front-End

The browser extension itself doesn't require much front-end design. I created a simple form that automatically populates as much data as possible from the active browser tab into a form that can be submitted directly as a POST request to 100DaysofCS.com.

Additionally, the extension needed to be registered and approved with Chrome in order to be published to the Chrome Web Store.

Back-End

Being able to leverage Chrome's API makes the back-end of the extension fairly brief as well. The most complicated part of browser extensions is having to create scripts that exist in independent environments work together.

A basic browser extension has 3 different environments:

  • The webpage or content existing on the tab
  • The extension's popup window
  • The script working in the background leveraging the browser's API functionality

Each different avenue requires its own script, in my case:

  • The content.js file for the active tab
  • The popup.js file for the extension's window
  • The background.js file for the background API

Each of these scripts must work together to pass data one to another in order to provide the desired functionality for the extension itself.

The content.js file

For this extension, it all started with the content.js file. When a user visits a new webpage I wanted the extension to pull data from the web page on the active tab and send it to other parts of the extension. With the content.js file, I was able to manipulate the DOM of the active tab.

Below is the contents of the file where you'll see simple data collection via the DOM.

// Get current url
var url = window.location.href

// Get current title
var title = ''
if ($("title")) {
	title = $("title").text()
} else if ($("meta[property='og:title']")) {
	title = $("meta[property='og:title']").attr('content')
} else if ($("meta[name='twitter:title']")) {
	title = $("meta[name='twitter:title']").attr('content')
}

// Get current meta description
var metaDesc = ''
if ($("meta[name='description']")) {
	metaDesc = $("meta[name='description']").attr('content')
} else if ($("meta[property='og:description']")) {
	metaDesc = $("meta[property='og:description']").attr('content')
} else if ($("meta[name='twitter:description']")) {
	metaDesc = $("meta[name='twitter:description']").attr('content')
}

// Send page variables to background script
chrome.runtime.sendMessage({
	url,
	title,
	metaDesc
})

Once we retrieve the url, title and description from the active tab, we need to send the data from the tab to the background script. We can do this by leveraging the chrome.runtime API where we'll send an object as a message to the background.js file.

The background.js file

The background.js file is even smaller than the content.js file. All we're doing with this file is accepting the object we sent above and then setting up the object's properties as global variables.

var url
var title
var metaDesc
chrome.runtime.onMessage.addListener(function(request, sender, sendResponse) {
	url = request.url
	title = request.title
	metaDesc = request.metaDesc
})

// Get ID from extension options
var id
chrome.storage.sync.get({'id': 0}, function(items) {
	id = items.id
})

Once again we're leveraging the chrome.runtime API but from the receiving end by creating a listener function to receive the object from content.js. We're setting the declared global variables equal to these values in order to access them next in our popup.js file.

In the background.js file we're also accepting the id value that is stored in the browser extension's option window (I previously mentioned the extension requiring a submission id). We're also setting this variable as a global variable.

The popup.js file

The final piece to the puzzle for our extension is to automatically populate these values in our extension's popup window.

// Retrieve global variables from background
let bgpage = chrome.extension.getBackgroundPage()
let url = bgpage.url
let title = bgpage.title
let metaDesc = bgpage.metaDesc
let id = bgpage.id

// Add title and description to form inputs
$('.name').val(title)
$('.description').val(metaDesc)

// POST to 100daysofcs.com
$('.submit').click(function() {

	let data = {
		href: url,
		name: $('.name').val(),
		type: $('.type').val(),
		description: $('.description').val(),
		keywords: $('.keywords').val(),
		difficulty: $('.difficulty').val()
	}

	$.ajax({
		type: "POST",
		url: 'https://100daysofcs.com/api/links/' + id,
		data: data,
		success: function(data) {
			$('.success-message').text(data)
		}
	})
})

The first thing we're doing in the popup.js script is retrieving the global variables from the background script file. Here we're leveraging the chrome.extension API in order to retrieve the variables set prior.

Next, we're automatically populating our popup form (located on our popup's HTML file) with the title and description values.

Finally, on form submission we're submitting our data via Ajax as a POST request to 100Daysofcs.com and printing a confirmation message. At 100Daysofcs we're checking for a valid submission ID from the user and then pushing the new resource link directly to the Links page.

If you could benefit from enhancing your computer science and programming skills, please consider joining our community. You can contribute either by downloading and using the Chrome extension featured in this post or bysubmitting a pull request on Github. I look forward to hearing from you!

Tweet me @tylerewillis

Or send an email:

And support me on Patreon