Today we're going to be building a simple Chrome extension with options to keep your display awake, allow the display to turn off but not let the system go to sleep, or default to your system settings. We'll explore the Chrome Extensions API, learn about callback functions, and code some JavaScript.
A quick note: this is a Chrome extension, so it won't work most other browsers. It will work on Brave (the best browser).
We'll be using some images and starting with some skeleton code that you'll need to have in a directory somewhere on your system. The easiest way to do this is to clone this repository. If you have an tokens set up to clone with either HTTP or SSH, go this route. If you have no idea what that means, no worries - just manually download the files from this repo into a single directory.
First, make sure you have an IDE where you're going to edit your code. If you don't have one, you can download the popular VSCode here.
The best way to clone is with SSH. If you don't have an SSH key set up, you can use HTTP if you have a personal access token set up. If you have neither of these and still want to try cloning, call one of us over and we'll help you set up a token.
- On the main page of this repository, click the green "Clone" button near the top right.
- In the SSH tab, copy the URL it gives you.
- Open up your shell (Terminal on Mac, Powershell on Windows, or your IDE's built-in shell) and
cdinto the directory you want your project to live. Where it is doesn't really matter, as long as you know where on your system you put it. - Type
git clone [url], whereurlis the cloning URL you copied in step 2. If you cloned with SSH, this should successfully create a directorysunset-chrome-extwith the same filestructure as in the Git repo. If you cloned with HTTP, it will prompt you for your Git username and a password, which should be your personal access token.
If you don't have access keys set up, you can add our files to your system manually.
- Create a directory called
sunset-chrome-ext. Make sure you remember where you put it. - From the Git repo, download the files
background.jsandmanifest.jsoninto yoursunset-chrome-extdirectory. - In your
sunset-chrome-extdirectory, create a directoryimages. From Git, download all the image files intosunset-chrome-ext/images
An API, or Application Programming Interface, is how software communicate and interact with each other. Think of an API as a waiter at a restaurant: you look at the menu and give the waiter your order, the waiter takes it back to the kitchen, waits for the dish to be made, then delivers it back to your table.
Chrome has a large set of APIs for extensions, of which we will be using three: power, storage, and action.
chrome.power allows us to override the system's power management features. 4 It has two methods we will
use:
-
requestKeepAwake()takeslevelas a parameter.levelis the degree to which power management should be disabled and must be part of theLevelenum predefined by Chrome. TheLevelenum has only two values: "system", which prevents the system from sleeping, and "display", which prevents the display from being turned off or dimmed. -
releaseKeepAwake()releases a request previously made byrequestKeepAwake().
chrome.storage allows us to store, retrieve, and track changes to user data. 5 There are a few different
layers of storage, but the only one we'll need to use is local. It has two methods we care about:
set()takes as a parameter a series of JSON objects (for our purposes, the same thing a Python dictionary) and stores them in local storage.get()takes a key as a parameter and fetches the object associated with that key from storage, if it exists. It also will hold a function in its parameters, but we'll get more into that later.
chrome.action is used to control the extension's icon in the toolbar. 6 Using action, we'll be able to
set a default name and image for our extension in our manifest.json, and use two of the API's methods:
setIcon()to set the extension icon.onClicked.addListener()that will tell the extension what to do when someone clicks on it.
The manifest.json file is the only file that every extension using WebExtension APIs must contain.
Using manifest.json, you specify basic metadata about your extension such as the name and version, and can also
specify aspects of your extension's functionality (such as background scripts, browser actions, and API
permissions).1
We're starting with a half filled in manifest. Let's go through each key in the file (the string before each colon), see what they're doing for us, and fill in what's missing.
Code time! Enter your background.js file.
Let's start with defining some constants. First, we'll need a variable to act as a key to use for storing the current state in local storage. Recall JavaScript syntax for variable definitions:
const STATE_KEY = 'state';(We're only putting this variable in all caps because it's a global constant.)
We also need to define our three possible states: display on, system on, and disabled (revert to system settings). What kind of data structures could we use for this?
const States =
...
?Next, we're going to write a function to switch to a new state.
function setState(newState) {
// The prefix for the image we're going to use. By default it should be system settings, which is night
let imagePrefix = 'night';
// Define what text will appear when you hover over the icon
let title = '';
// Switch between possible states.
// For each, set the appropriate image prefix, title, and make the appropriate chrome.power API request
// Set the current state in storage using the chrome.storage API
// Set the new icon and title using the chrome.action API
}Awesome. This one function is going to take care of most of what we want our program to do.
This is where things get a little spicy. Because we defined our extension to be a background extension (as it should be;
we don't want to take up valuable CPU time with something that doesn't need to be done immediately), we have to make it
asynchronous. If you recall previous workshops, we typically do this by defining a function with the async keyword,
and awaits and Promises to get a result. This time, we're instead going to use something called a callback
function.
A callback function is any function that is called by another function that takes the first function as a parameter. It's typically called when some event happens from the user, like when you click on the extension. Instead of telling the program to wait until the user clicks on the extension, which would block anything else from being executed, we can pass a whole function as a parameter to a listener that will only call it when something happens.
Okay. So this means we need some event listeners and a function for our listeners to call when their event happens.
Let's call this function loadSavedState. It's going to take a function as a parameter, making whatever function gets
passed in the callback function.
function loadSavedState(callback) {
// Get the current state from storage
chrome.storage.local.get(STATE_KEY, function (items) {
let savedState = items[STATE_KEY];
// Check that the state we stored is part of our States dictionary
for (let key in States) {
if (savedState == States[key]) {
callback(savedState);
return;
}
}
// Default case is disabled
callback(States.DISABLED);
});
}Next, let's write our listener functions. We need one that's going to launch on start, that will call loadSavedState.
What should be passed into loadSavedState's parameters?
chrome.runtime.onStartup.addListener(function () {
loadSavedState(?);
});Finally, we need an onClick listener to change the state of the extension when you click on it. This one is going to be
a little more complicated, because we have to switch states. We can do this by defining a new function
in loadSavedState's parameters!
chrome.action.onClicked.addListener(function () {
loadSavedState(?);
});Follow this guide to load your extension. Happy never sleeping!
- https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/manifest.json
- https://www.w3schools.com/js/js_callback.asp
- https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/storage/StorageArea/get
- https://developer.chrome.com/docs/extensions/reference/api/power
- https://developer.chrome.com/docs/extensions/reference/api/storage#property-local
- https://developer.chrome.com/docs/extensions/reference/api/action
- https://github.com/GoogleChrome/chrome-extensions-samples/tree/main/api-samples/power