Just do it. Customize the place where you spend 99.999% your life. The place that burns your eyes when you try to code a new website.
Chrome extensions allow the user to customize chrome with silly add-ons to productivity increasing ones. Weβll walk through building one that falls closer to the silly side than the productive one. Some of our favorites include:
- Shia Lebeouf - just do it.
- React Developer Tools helps with debugging react.
- GitCheat Sheet for easy access to common Git commands and terminology.
- DarkReader Dark mode for every website.
- Sitemod.io allows you to modify and save changes to any website you visit, with cleaner source code than youβll find in the chrome developer tools.
Chrome extensions can access all the APIβs that a webpage might and some additional ones for interacting with chrome in ways that only extensions can. Extensions are set up much like a webpage, using js, html and css. Just like websites, extensions have a huge range of possibilities.
A chrome extension is a simple and fun way to spruce up your browsing experience, which is where most of us spend our lives, so why not make it yours. It is also the perfect platform for certain projects, the most obvious ones being those that change how we interact with websites or entertainment ones where in-browser is just the right medium.
Today we'll make an extension that looks like this. Yup, that's Tim and those are all quotes from his presentations. They'll stay with you when you need some motivation on our labs or crushing your final project. The quotes display randomly, but after you are done you'll be able to change the extension to show whatever quotes you want, in whatever order, and with whatever picture you want. But for now let's get started π.
Spaces, commas, semi-colons and other syntactical issues will come up if you are not careful! This is our warning to you to make sure to think through the code and TRY NOT TO JUST COPY AND PASTE!
π΄ You can fork this repo to get started.
It doesn't contain any functional code, but it does include the images that we'll refer to, a .gitignore
file, and the quotes.
Chrome extensions are created with a couple files:
manifest.json
: Tells chrome what scripts, resources, and permissions the extension uses.content.js
: Contains the actual base functionality of the extension, like adding a div to the screen or replacing all<img src="" />
.background.js
: Listens for browser events, like reloading the page or clicking the on off button.style.js
: Style your additions with some standard CSS.- Any source files: Images, quotes, any information that your extension includes or uses. (You can also get information from web APIs.).
JSON files store objects. They use the same notation as javascript, except they always put variables/values in quotes "somekey": "somevalue"
. Arrays are indicated with ["entry1", "entry2"]
. Objects with {"key": "value"}
Object:
{
"greeting": "hello, this will be fun.",
"quotes": [
"you got this",
"it's not just copy paste",
"but you can do it"
]
}
- What kind of file? JS, JSON, CSS?
- JS: is everything in the right place, is the function completed?
- JSON: probably missing a comma or you adding something in the wrong spot. Are things in the arrays they should be? How about in the objects?
- CSS: You know how to do this!
- Did you refresh the extension? Remember chrome://extensions/ !
- Run it on Dartmouth.edu (Some websites don't play nice.)
To start with creating any Chrome extension, we need a manifest.json
file.
πΎ In your workshop directory, create manifest.json
and add the following code block:
{
"manifest_version": 2,
"name": "Tim's Thoughts",
"version": "0.1",
"browser_action": {
"default_icon": "img/logo.png"
},
}
You should have an img directory from when you forked this repo and it should contain logo.png. This will be the image that shows up inour chrome toolbar!
This is the initial setup for any chrome extension. In order for the logo png to show up, our extension needs to know that itβs loading those images. Add the below code block to the manifest.json
file underneath the manifest_version code block:
"web_accessible_resources": [
"*.jpg",
"*.jpeg",
"*.png",
"*.json",
"*.png/",
"*.jpg/"
]
Now all the images ending with those listed would be able to show up whenever the extension uses them.
Great! But how does our extension know what to do specifically?
We can tell it by making a content.js
file that will hold the actual actions of the extension.
For this extension, we are making a quote bot with Tim's photo! The content file is specified in manifest.json
so that the browser knows to run the script.
First, add content.js
as a content script in manifest.json
so it knows to load it:
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"]
}
]
content.js
is our main js file and describes the behavior of the extension. Note that in βmatchesβ
the β<all_urls>β
is a descriptor telling chrome that our extension applies to all urls and should always run.
Now that chrome will look to run content.js
we have to create it.
πΎ Create content.js
in your root directory. Let's test it! Inside content.js
add an alert (or a console log):
alert("Hello from your Chrome extension!");
How do we see this test? We upload our extension.
Testing your extension1
Now it's time to upload our extension to chrome! Go to chrome://extensions (copy-paste this directly into Chrome)
-
Turn on Developer mode in the upper right hand corner.
-
Click on Load Unpacked and select the directory that contains your chrome extension.
-
Make sure the extension is turned on (toggle is blue). This is where errors in the extension will be logged.
- β If there is an error in the code, it will give you an error message and not let you turn the extension on. Whenever you have an error, it is convenient to clear the error before reloading to make it clear if the error is from your newest change or just leftover. Wave us down if you have questions!
-
β οΈ From now on, in order to view your edits, you have to refresh the extension on (chrome://extensions).
β Check your progress out! Open a new tab and navigate to another website. We recommend dartmouth.edu. Your alert should popup!
β If dartmouth.edu still doesn't work, try google.com!
TBH this alert is kinda annoying. We want to make our own sprite quote bot. What does this involve?
- Loading an image on the screen
- Adding quotes to it
Working in content.js
...
In order to add the image to the current page, we'll use some js. Take a stab at adding a div and img to the body of the html document, just like you would in a main javascript file for a normal page. This is just like your quiz!
We have included some images in img/
, feel free to add your own photo or just use img/mohawk.jpg
.
Hints:
- create a variable to store your addition
- set its inner html to be a div and image
- append it to the body of the document
Ok, I tried. Show me the answer
var div = document.createElement("div");
var imgPath = chrome.extension.getURL('img/mohawk.jpg');
div.innerHTML = `
<div id="clippy"></div>
<img id="clippyImg" src=${imgPath} />
`;
document.body.appendChild(div);
Phew, let's ditch the js for a second and make that image actually show up on the screen. Feel free to style it how you want or copy our styling.
You also have to make sure the extension knows to load the style sheet.
Where should you put this line of code: "css": ["style.css"]?
In manifest.json
under content_scripts
!
"content_scripts": [
{
"matches": [
"<all_urls>"
],
"js": ["content.js"],
"css": ["style.css"]
}
],
πΎ Ok, now time to make a style.css
in root directory.
If you're making your own style, make sure to have styling for:
- the icon image (#clippyImg)
- the Speech Bubble that will contain the quote (#speech-bubble, #speech-bubble:after)
Our Style
#clippyImg{
width: 100px;
height: 100px;
position: fixed;
bottom: 0;
right: 0;
margin: 30px;
background-color: blue;
border-radius: .4em;
box-shadow: 5px 5px 5px #666;
}
#speech-bubble {
background: #00aabb;
color: white;
font-weight: bold;
border-color: black;
opacity: 0.9;
border-radius: .4em;
width: 150px;
position: fixed;
bottom: 150px;
right: 30px;
padding: 20px;
box-shadow: 5px 5px 5px #666;
}
#speech-bubble p{
opacity: 1;
}
#speech-bubble:after {
content: '';
position: absolute;
bottom: 0;
left: 50%;
width: 0;
height: 0;
border: 20px solid transparent;
border-top-color: #00aabb;
border-bottom: 0;
border-right: 0;
margin-left: -10px;
margin-bottom: -20px;
}
Cool, you should now see the image when you open a new tab. (Remember to refresh the extension.)
π Feel free to comment out the alert at this point!
Now, just a picture of Tim, though worth a thousand words, is worth more when it includes a pearl of wisdom he dropped in class. Fear not, we have compiled some inspirational quotes from class. Youβll find them in a json file called quotes.
This file will have two main parts:
- Loading the quotes
- Picking a quote to display and adding it to the div
Start with loading them from the quotes.json
file in content.js
with the fetch(URL)
method. The fetch method returns a promise with the requested results.
Here is an example of proper fetch use from google (emphasis on example):
fetch('./api/some.json')
.then(
function(response) {
if (response.status !== 200) {
console.log('Looks like there was a problem. Status Code: ' +
response.status);
return;
}
// Examine the text in the response
response.json().then(function(data) {
console.log(data);
});
}
)
.catch(function(err) {
console.log('Fetch Error :-S', err);
});
Try using this method to fetch the quotes and save them.
Ok, so maybe youβd prefer to copy and pasteβ¦THATβS why yOu NEED this ExTimsion. π€
// load the quotes from the json file
const quotesURL = chrome.runtime.getURL('quotes.json');
var quotes;
fetch(quotesURL).then((response) => response.json())
.then((json) => {
quotes = json;
})
.catch((error) => console.log(error, error.message));
Alright, so you got the quotes. Now what? Let's pick one at random and update the sprite to say it at a random interval.
Create a function to randomly pick and append a quote from the quotes passed as its argument whenever called.
Yup, we hid this solution too. "Be the webdev!"
Just one more try?
function sayQuote(qts) {
var quote = qts.quotes[Math.floor(Math.random() * qts.quotes.length)];
document.getElementById("clippy").innerHTML = `<div id="speech-bubble"><p>${quote}</p></div>`;
console.log(quote);
}
Where should we call this function? π
How about... as soon as we load the quotes in content.js
!
// load the quotes from the json file
const quotesURL = chrome.runtime.getURL('quotes.json');
var quotes;
fetch(quotesURL).then((response) => response.json())
.then((json) => {
quotes = json;
// say one right away
sayQuote(quotes);
// set an interval with some randomness to update the quote
setInterval( () => sayQuote(quotes), 5000 + Math.random()*3000);
})
.catch((error) => console.log(error, error.message));
Here we fetch the json file, call the function immediately, and invoke the function periodically.
Now you have a working chrome extension, except it is always onβ¦ And maybe at a certain point youβd rather have Shia Lebeouf yell at you than read a pearl of CS52 wisdom. But alas! On to making the on off button!
To create a button we'll need access browser events, specifically the click when the user clicks on the on off button. To access browser events, like clicks, in a chrome extension, the extension needs another file, a background file that listens for events and responds to them. Once on-off button responds to clicks, we will utilize in browser storage to pass the on-off state to the content script.
The background file listens for browser events and acts on them. It can send information to other files via messages or set browser storage. In our case, it just listens for user clicks on the extensionβs button in the toolbar (top right of your browser).
Add the background script to manifest.json
"background": {
"scripts": ["background.js"]
},
Additionally, the extension requires permission to access the current tab, the browswer storage, notification and any webpage.
"permissions": [
"tabs",
"storage",
"notifications",
"http://*/",
"https://*/"
]
The permissions have to be declared for the program to access these items and so that the user knows what the extension can access.
πΎ Now, create a background.js
file.
The first thing that our background file can do is set some text on the extension's logo to indicate whether it is on or off:
//Setting badge/button of the extension.
chrome.browserAction.setBadgeText({ text: 'OFF' });
Next we'll set the default state for enable
to false
for off and we'll store it in Chrome Storage. By storing it in Chrome storage, we'll be able to access it in the content script.
// boolean for on/off
var enable=false;
// store enable in local storage if you wanted to access in content.js
chrome.storage.sync.set({"enable": enable});
β What do we have to do when the user turns the extension on or off? If this were and onClick
method in react, what would you do?
We will need to:
- toggle the value of
enable
- update the text on the logo to indicate the state
- save the value of enable so that we can access it in
content.js
. (We'll want either run the content script functionality or not based on the value ofenable
.) You were right!
We use chrome.browserAction
to both read events and set the badge, like above. We also use chrome.tabs.executeScript
to reload the page.
You can probably think through the pseudo code for this function, but we've included the code here since it's a bit heavy with new syntax for the chrome API. πͺ
// onclick method
chrome.browserAction.onClicked.addListener((tab) => {
enable = !enable;
// if enabled run content.js else, reload page
if (enable) {
chrome.browserAction.setBadgeText({text: 'ON'});
chrome.tabs.executeScript(null, { file: 'content.js'});
} else {
chrome.browserAction.setBadgeText({ text: 'OFF'});
chrome.tabs.executeScript(tab.id, {code: 'window.location.reload();'});
}
// store state of button
// with callback for debugging
// chrome.storage.sync.set({"enable": enable}, () => console.log('Enable set to: ', enable));
// w/o callback
chrome.storage.sync.set({"enable": enable});
})
Now we only want the content script to run when the extension is enabled. In background.js
we stored the variable enable
in the browser storage, so that we can get it in the content.js
script using the storage sync API. The storage API syntax is:
chrome.storage.sync.get("enable", function(result) {
...
}
Since this method returns a promise, we need to put our content functionality inside of the callback.
Go into your content.js
. All of our functionality should be inside a check of enable, except sayQuote()
which can be defined outside as it is just a function we are using.
So put all of your logic for adding the image, loading the quotes, and calling sayQuote()
inside the callback and only run when enable == true
. Give it a go and see if you can get it to work.
Hidden again? You've got to be kidding me...
Once you are done, your content.js
should look like this:
// alert("Hello from your Chrome extension!");
// if you want to use storage from chrome, you can access this way:
chrome.storage.sync.get("enable", (res) => {
// if enabled run
if (res.enable) {
// add the div for the quotes and the image the page
var div = document.createElement("div");
var imgPath = chrome.extension.getURL('img/mohawk.jpg');
div.innerHTML = `<div id="clippy"></div>
<img id="clippyImg" src=${imgPath}/>`;
document.body.appendChild(div);
// load the quotes from the json file
const quotesURL = chrome.runtime.getURL('quotes.json');
var quotes;
fetch(quotesURL).then((response) => response.json())
.then((json) => {
quotes = json;
// say one right away
sayQuote(quotes);
// set an interval with some randomness to update the quote
setInterval( () => sayQuote(quotes), 5000 + Math.random()*3000);
})
.catch((error) => console.log(error, error.message));
}
});
// Pick a random quote and add it to the page
function sayQuote(qts) {
var quote = qts.quotes[Math.floor(Math.random() * qts.quotes.length)];
document.getElementById("clippy").innerHTML = `<div id="speech-bubble"><p>${quote}</p></div>`;
console.log(quote);
}
Now your extension should only show up and display quotes when you turn it on in the toolbar! Woohoo!! Now you will always have:
Soβ
Muchββ
Motivationβββ
You can use the steps above to run the extension in your own browser, but actually publishing the extension takes a bit more work and πΈ, so we won't cover it here, but if you are interested here are the Chrome instructions for publishing an extension.
- What a Web Platform is
- Basic understanding of Electron
- Basic understanding of Chrome Extensions
- How to code your own Chrome exTIMsion
You just learned how to make your own chrome extension! Hopefully, it was a painless experience π You can use it to create fun extensions like this or to make more powerful ones to change how you browse the web.
Chrome extensions work similarly to websites and can access most of the APIs that a website could. Just like websites they do require a couple different files, but most of the functionality happens in one or two base js files.
π₯ Hot tip: If you don't want this exTIMsion, right click the logo in the chrome toolbar and click remove from chrome
.
- Customize!! Change up the sprite image, the quotes your sprite says, the logo in the chrome toolbar, anything.
- Have your sprite say different quotes on certain events (creativity appreciated)
- Figure out how to position the sprite on websites that are built in interesting ways (on slack, google slides, etc.)
- How do all of the chrome extension files interact?
- Why would you want to use the chrome extension platform for a project instead of making a standalone website, app, or desktop app?
Remember to submit whatever you have, as well as short reflection answers, on Canvas!
- Chrome Extensions Development Page
- Chrome instructions for publishing an extension
- Chrome Storage
- Chrome Extensions with Vanilla JS
The extension loading section is basically copied from last yearβs workshop group. β© Our idea is also closely related to their idea β we π all π love π Tim π
We also used the clippy extension to start working through the building of the extension and as our inspiration for the original idea.
Our tutorial is also inspired by this other tutorial on creating Chrome extensions from scratch!