November 23, 2020

'Play or Pause Tab' Firefox Extension

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.

Context menu displaying the ‘Play/Pause Tab’ option. “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.

© Micah Tigley 2020

Powered by Hugo & Kiss.