Creating a Chrome Extension - A Hands-On Tutorial with YouTube Remaining
Tags: webdev, javascript, tutorial, extensions
Published: 2023-07-15 21:55:33

> We all love the AI generated cover images, right?

.

> This is my first article, so I choose an easy topic and tried to make it a nice read. Feedback is highly appreciated :)

Hey there, Internet Explorer! No, not the browser, you—the adventurous soul who's ventured into the world of Chrome extensions. You've in all likelihood got a bunch of these nifty little tools hanging out in your browser, making your life easier one click at a time. Ever wondered how they're made?

We're going to create a Chrome extension that's so useful, you'll wonder how you ever YouTube-d without it. It's called "YouTube Remaining", and it does exactly what it says on the tin—shows you the remaining time on a YouTube video. No more squinting at the tiny timestamp or doing mental math to figure out if you can squeeze in one more cat video before your next meeting.

And the best part? Making a Chrome extension is as straightforward as your favorite recipe. Think pancakes on a Sunday morning, not a five-course dinner. So, if you can flip a pancake, you're all set to create this extension.

Ready to get started? You can download the finished extension [here](https://chrome.google.com/webstore/detail/youtube-remaining/hpfhjlldglkcnmmkihempjkhleahbjjg). But if you're here for the journey, let's roll up our sleeves and dive in!

## Getting Started: The Ingredients for Your Extension Recipe

Before we start cooking up our Chrome extension, let's make sure we have all the ingredients on hand. Don't worry, we're not talking about exotic spices or hard-to-find items. The recipe for a Chrome extension calls for a few basic things:

1. **A pinch of HTML and CSS knowledge**: You don't need to be a Michelin-starred chef in the world of coding, but knowing your way around HTML and CSS (not really for this extension) is a must.
    
2. **A dash of JavaScript**: This is the secret sauce that brings our extension to life. JavaScript is like that odd ingredient in a Michelin-starred dish — you're not sure what it is, and if you knew, you might be a little grossed out. But somehow, it works in the overall recipe. It's quirky, unpredictable, and, let's face it, a dumpster fire. But just like that peculiar ingredient, JavaScript is essential in a Chrome extension.

3. **Google Chrome**: It might seem obvious, but you'll need the Chrome browser installed on your computer. After all, we're making a Chrome extension!
    
4. **A text editor**: This is where you'll be writing your code. There are many options out there, vi, vim, neovim, etc.

Now that we've gathered our ingredients, let's take a look at the recipe. We'll start by setting up our project and creating the `manifest.json` file, which is like the instruction card for our extension. Then, we'll move on to the `contentScript.js` file, where we'll write the JavaScript code that powers our extension. Finally, we'll test our extension in Chrome and celebrate our culinary—err, coding—achievement.

## The Recipe Card: manifest.json

First up in our Chrome extension cook-off is the `manifest.json` file. Think of this as the recipe card for your extension. It's where you list out all the key details about your extension, like its name, version, and the permissions it needs. It's also where you tell Chrome which scripts to run and when to run them.

In the case of our YouTube Remaining extension, the `manifest.json` file is pretty straightforward. It specifies that the `contentScript.js` file (our main course, which we'll get to in a bit) should be run on YouTube web pages.

```json
{
  "manifest_version": 3,
  "name": "YouTube Remaining",
  "version": "1.0",
  "permissions": ["activeTab", "scripting"],
  "icons": {
    "16": "icon16.png",
    "48": "icon48.png",
    "128": "icon128.png"
  },
  "content_scripts": [
    {
      "matches": ["https://*.youtube.com/*"],
      "js": ["contentScript.js"]
    }
  ]
}
```
Let's break down this recipe:

- **"manifest\_version"**: We're using version 3 of the manifest file format. This is the latest version and introduces some changes from version 2, including different ways to request permissions.
    
- **"name" and "version"**: Our extension is called "YouTube Remaining", and we're at version 1.0. Every time you update your extension, you'll increment this version number.
    
- **"permissions"**: We're asking for the "activeTab" and "scripting" permissions. The "activeTab" permission allows our extension to interact with the active tab when the user invokes the extension, while the "scripting" permission allows us to inject and remove scripts in tabs.
    
- **"icons"**: This specifies the icons for our extension at different sizes. These icons will be displayed in various places like the extension management page and the toolbar.
    
- **"content\_scripts"**: This is where we tell Chrome to inject our `contentScript.js` file into any page that matches the pattern "https://_.youtube.com/_". This means our script will run on any YouTube page.
    
With our `manifest.json` file set up, we've laid the groundwork for our Chrome extension. Next, we'll move on to the main event: the `contentScript.js` file.

## The Conversion Functions: Translating Time

Before we dive into the main course, let's whip up some side dishes that we'll need later on. In our `contentScript.js` file, we have two helper functions that deal with time conversion: `durationToSeconds` and `secondsToDuration`. These functions are like the sous chefs of our extension, working behind the scenes to ensure everything runs smoothly.

### durationToSeconds

Think of this function as our trusty kitchen timer. It takes a duration string in the format "hh:mm:ss" or "mm:ss" and converts it into seconds. This is like breaking down a recipe into simpler steps, making it easier for us to follow along.

```js
function durationToSeconds(duration) {
    const parts = duration.split(':').map(part => parseInt(part, 10));
    let seconds = 0;

    if (parts.length === 3) {
        // Format: hh:mm:ss
        seconds += parts[0] * 3600; // hours
        seconds += parts[1] * 60; // minutes
        seconds += parts[2]; // seconds
    } else if (parts.length === 2) {
        // Format: mm:ss
        seconds += parts[0] * 60; // minutes
        seconds += parts[1]; // seconds
    }

    return seconds;
}
```

### secondsToDuration

Now, let's flip the timer. This function is the reverse of `durationToSeconds`. It takes a number of seconds and converts it back into a duration string in the format "hh:mm:ss" or "mm:ss". It's like reading the cooking time on a recipe, showing us the time in a format we can easily understand.

```js
function secondsToDuration(seconds) {
    const hrs = Math.floor(seconds / 3600);
    const mins = Math.floor((seconds % 3600) / 60);
    const secs = seconds % 60;

    let duration = "";

    if (hrs > 0) {
        // Pad to 2 digits
        duration += String(hrs).padStart(2, '0') + ":";
    }

    // Pad to 2 digits
    duration += String(mins).padStart(2, '0') + ":";
    duration += String(secs).padStart(2, '0');

    return duration;
}
```

With these two functions, we've got our timers set, and we're ready to handle time conversion in our extension.

## The Sous Chef: waitForElement Function

Next up on our menu is the `waitForElement` function. This function is like the sous chef who ensures that all ingredients are ready before the chef starts cooking. In the context of our extension, it makes sure that the elements we need are loaded on the page before we start manipulating them.

```js
function waitForElement(selector, callback) {
    if (document.querySelector(selector)) {
        callback();
    } else {
        setTimeout(() => {
            waitForElement(selector, callback);
        }, 500);
    }
}
```

In a bustling kitchen, timing is everything. You wouldn't want to start sautéing your onions before your garlic is finely chopped, right? Similarly, in a web page, we can't start manipulating elements before they are fully loaded.

The `waitForElement` function takes a CSS selector and a callback function as arguments. It checks if an element matching the selector exists on the page. If it does, it immediately calls the callback function. If not, it waits for half a second (500 milliseconds) and then checks again, repeating this process until the element is found.

This function is crucial for our extension because YouTube is a dynamic website, which means that elements on the page can load at different times. We use `waitForElement` to ensure that the video time elements are present on the page before we try to add the remaining time display.

With our sous chef in place, we're ready to start cooking up the main course of our extension: creating and updating the remaining time display.

## The Main Course: Serving Up the Remaining Time Display

Now that we've got our sous chef keeping an eye on the ingredients, it's time to start cooking up the main course of our extension. This is where we create the remaining time display and serve it up on the YouTube video player.

First, we wait for the current time element to load on the page. Once it's ready, we select the current time, time separator, and duration elements from the page.

```js
waitForElement('.ytp-time-current', () => {
    const currentTimeElement = document.querySelector('.ytp-time-current');
    const timeSeparatorElement = document.querySelector('.ytp-time-separator');
    const durationElement = document.querySelector('.ytp-time-duration');
```

Think of these elements as the main ingredients of our dish. We also get the parent element of these elements, which is like our plate where we'll be arranging our ingredients.

```js
    const parentElement = currentTimeElement.parentElement;
```

Next, we create a new separator and a new span for the remaining time. We add the appropriate classes to these elements and set their text content. For the remaining time element, we initialize it with the full duration of the video, prefixed with a '-' sign to indicate that it's the remaining time.

```js
    const newSeparator = document.createElement('span');
    newSeparator.classList.add('ytp-time-separator');
    newSeparator.textContent = ' / ';

    const remainingTimeElement = document.createElement('span');
    remainingTimeElement.classList.add('ytp-time-remaining');
    remainingTimeElement.textContent = '-' + durationElement.textContent;
```

Finally, we add our new elements to the parent element, effectively serving up our remaining time display on the YouTube video player.

```js
    parentElement.appendChild(newSeparator);
    parentElement.appendChild(remainingTimeElement);
})
```

But we're not done yet! Our main course needs a little more seasoning.

## The Seasoning: Keeping the Display Updated with MutationObserver

Just like a good chef keeps an eye on their dish while it's cooking, adjusting the seasoning and stirring as needed, our extension needs to keep an eye on the YouTube video player and update the remaining time display as the video progresses. This is where the `MutationObserver` comes in.

```js
    const observer = new MutationObserver(() => {
        const totalDuration = durationToSeconds(durationElement.textContent);
        const currentTime = durationToSeconds(currentTimeElement.textContent);
        const remainingTime = totalDuration - currentTime;

        remainingTimeElement.textContent = '-' + secondsToDuration(remainingTime);
    });

    observer.observe(currentTimeElement, { childList: true, characterData: true, subtree: true });
```

Think of the `MutationObserver` as the seasoning in our dish. It's what gives our extension its dynamic flavor, keeping the remaining time display fresh and accurate as the video plays.

The `MutationObserver` watches for changes in the `textContent` of the `currentTimeElement`. Whenever the current time changes, it recalculates the remaining time and updates the `remainingTimeElement`.

## The Cherry on Top: README.md

Just like a good meal isn't complete without a dessert, a good open-source project isn't complete without a README file. It's the cherry on top that gives people an overview of your project, how to use it, and how to contribute.

Here's what you should include in your README:

1. **Project Title**: Give your project a name that clearly describes what it does.
    
2. **Description**: Provide a brief overview of your project. What does it do? Why is it useful?
    
3. **Installation Instructions**: Explain how to get your project up and running. If your project can be installed in multiple ways (like from the Chrome Web Store or manually), be sure to cover all the options.
    
4. **Usage Instructions**: Describe how to use your project. In the case of a Chrome extension, this might include how to enable the extension and what to expect when it's running.
    
5. **How it Works**: Give a high-level overview of how your project works. You don't need to go into the nitty-gritty details (that's what the code is for), but do give readers a sense of the overall architecture and key components.
    
6. **Contributing Guidelines**: If you're open to contributions, provide some guidelines on how others can help. This might include how to submit bug reports, propose new features, or set up a development environment.
    
7. **License Information**: If your project is open source, include information about the license it's released under.

With a well-crafted README, your project is not just a piece of software, but a complete open-source project that others can learn from, use, and contribute to. It's been a pleasure cooking up this extension with you, and I hope you're now feeling ready to create your own Chrome extensions.