Background
Play or Pause Tab is a Firefox extension I created to help manage the “play” or “pause” states of tabs streaming media content. The user can right-click the tab and select the “Play/Pause” option to control this.
Currently, the implementation is pretty straightforward. This post is to document some of the main things I learned about creating “Play or Pause Tab” in a step-by-step way. Some of the resources I used to help me build this extension was starting with Mozilla Developer Network’s Your First WebExtension guide, and then later referencing their menu-remove-element example for communicating between the extension’s background and content scripts via a context menu item.
“Play/Pause Tab” menu item appears when right-clicking a tab with a supported URL
Creating the extension
I will briefly describe Firefox extension concepts as they are brought up, but I won’t go into too much detail. Below are articles are provided by MDN for those who may want to reference them for more details later on:
The above articles are parts of the “Play or Pause Tab” extension that will be used.
1. Defining manifest.json
The “manifest.json” file contains metadata about the Firefox extension as well as any scripts Firefox will need to load.
// manifest.json
{
"manifest_version": 2,
"name": "Play or Pause Tab",
"version": "0.1",
"description": "Adds a context menu item that will play/pause media on the selected tab.",
"background": {
"scripts": ["background-script.js"]
},
"permissions": ["menus", "activeTab"]
}
Here we define a few details about the extension, such as its name and description.
The most interesting keys here are "background"
and "permissions"
.
The "background"
key tells Firefox what background scripts we need to run
throughout the extension’s lifetime (from when its loaded to when it’s disabled/uninstalled).
The "permissions"
key specifies what special privileges your extension will need to access
parts of the browser. For “Play or Pause Tab”, the menus
and activeTab
permissions are
needed. Respectively, these permissions allow the extension to add items to the browser menus
and inject JavaScript into the tab when the user selects its context menu item.
For more information on what permissions your extension can request, see: MDN’s permissions article.
2. The background script
As defined in the “manifest.json” file, the extension will run a background script. Background scripts can use
WebExtension APIs, which can be accessed from the browser
namespace. This gives the extension a number of
ways to access and manipulate parts of the browser that cannot be done in a normal environment.
In particular, we want the extension to add a new context menu item called “Play/Pause Tab” when the user right-clicks on a tab streaming a video/audio source. To do this, we can use browser.menus.create():
// background-script.js
browser.menus.create({
id: "play_pause_menu_item",
title: "Play/Pause Tab",
documentUrlPatterns: ["https://*.youtube.com/*", "http://*.youtube.com/*"],
contexts: ["tab"],
onclick: (info, tab) => {
executeContentScript(tab.id)
}
});
menus.create()
is given an object that defines properties for the new menu item. Properties such as title
and id
will
tell the browser what text should be displayed in the item along with its unique ID, respectively. For this extension, the most
relevant properties defined for this menu item are "documentUrlPatterns"
, "contexts"
, and "onclick"
:
The "documentUrlPatterns"
property lets us restrict displaying the item to documents whose URLs matches one of the given patterns.
To keep it simple, let’s only create the menu item when a tab whose URL matches any “youtube.com/*” domains is
right-clicked. We can later extend this list to other websites such as Spotify or Twitch.
The "contexts"
property defines what [context type] the menu item should appear in. The “Play or Pause Tab” item will appear within
the “tab” context, which is when the user right-clicks a tab.
The "onclick"
property is a function that’s called when the menu item is clicked. This is where our background script will run a script
on that tab’s page content that will be responsible for “playing” or “pausing” videos. In order to do this, the extension needs to use the
[tab.executeScript()] API to inject some JavaScript code into the tab’s page. We will implement this in a function called executeContentScript
, which will be called from the menu item’s onclick
property.
// background-script.js
async function executeContentScript(tabId) {
await browser.tabs.executeScript(tabId, {
file: "content-script.js"
});
browser.tabs.sendMessage(tabId, { message: "toggle-play-pause" });
}
The JavaScript code the extension will be injecting into the page is from a file called “content-script.js”, which we still need
to create. Once the code has been injected into the page, the extension will use tabs.sendMessage to communicate with
the content script we injected earlier. Here the extension will send the message “toggle-play-pause” to the tab the user is clicking
the context menu item for, which is specified by the tabId
argument.
Note: Background scripts cannot access page content, which is why content scripts are needed to talk to page content via
tabs.sendMessage
.
3. The content script
The background script we created earlier injects a content script, named “content-script.js”, into a tab’s web page using tabs.executeScript()
. A content script has access to a web page’s content, which is how the extension will be able to find the
element on the page to “play” or “pause” it. First, the background script sends a “toggle-play-pause” message to the injected
content script whenever the user selects the “Play/Pause Tab” option from the tab context menu. The content script now needs
to listen for this message from the context of the web page it’s running in. This can be done by adding a listener using
runtime.onMessage.addListener:
// content-script.js
browser.runtime.onMessage.addListener(handleBgScriptMessage);
addListener
takes a callback function that will be run when the content script receives a message from the background script
via tabs.sendMessage
. The function we’ll be passing, handleBgScriptMessage
still needs to be defined:
// content-script.js
async function handleBgScriptMessage(request, sender, sendResponse) {
if (request.message === "toggle-play-pause") {
handleTogglePlayPause();
browser.runtime.onMessage.removeListener(handleBgScriptMessage);
}
}
browser.runtime.onMessage.addListener(handleBgScriptMessage);
The listener function will call another function called handleTogglePlayPause
, which is what will be responsible for finding the
video element on the page and toggling its “play”/“pause” state. After, we need to tell the content script to remove the handleBgScriptMessage
listener. This is because we don’t want the content script adding another listener with handleBgScriptMessage
and causing it to receive the “toggle-play-pause” multiple times. We only want it listen for the message once per click by the user.
The final part of the extension is implementing the handleTogglePause
function our listener calls. The function will select the <video>
element from the page and depending on its video.paused
value, will play/pause the video:
// content-script.js
async function handleTogglePlayPause() {
let video = document.querySelector("video");
if (video.src) {
video.paused ? await video.play() : await video.pause();
}
}
It’s important to note that this approach for playing/pausing videos on a tab works on Youtube and may not always work on every site
that streams media content. One example is Spotify’s web application. At the time of writing this, their media player does not use a <video>
or <audio>
element so it’s up to the extension to find the element specifically responsible for “playing” or “pausing”
content.
Try it out
The “Play or Pause Tab” extension is available on the Mozilla Add-ons store here. It was originally created to help myself manage music I’d be listening to in a background tab. Being able to quickly toggle a video without having to manually switch over to the tab helps me stay focused on other tasks I’d be doing in the browser.
It’s still in development with plans to support other major sites like Spotify and Twitch. The project’s source code is available on a GitHub repo called menu-play-pause-tab.