Chrome Extensions

Redouane Karzazi
5 min readJan 26, 2023

--

Let’s enhance our browser user experience.

Being a developer or a simple user, we always found ourselves using chrome extensions. From font detection, OCR translations to page screening, extensions exist for any domain we may think of.

As anyone else, I usually found myself saying if only there was a shortcut to do this or that in an easy and fast way. Although many times, I found some chrome extensions already in place that fulfill my need, but in many more times I did not.

Thus, I decided to learn how to write my own extensions. That way, whenever I need something, I can easily write some code to help me do it.

So, as for now, let’s dive into the coding part.

PS: The following article concerns chromium extensions.

The Architecture of chrome extensions

  • Manifest.json

Manifest.json, the core file, is the entrypoint of every extension. It contains all the necessary info the browser needs to know to load the extension properly. To enumerate a few: name, version, description, background scripts, content scripts, logo and so more.

{
"name": "Favicon API in content scripts",
"version": "1.1",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content.js"],
"css": ["style.css"]
}
],
"background": {
"service_worker": "background.js"
},
"permissions": ["favicon"],
"web_accessible_resources": [
{
"resources": ["_favicon/*"],
"matches": ["<all_urls>"]
}
]
}
  • Content scripts

They are js files that you write and that the browser loads into each page meaning they have the whole access to the page’s DOM. They can access the localstorage, get an element by id or anything a normal js script can do. Their context is limited though to the tab’s level, they cannot access the other tabs’ info.

With content scripts only, we can make for example a font detector that gives the font of the hovered text.

  • Background scripts

Here comes the boss. Similar to content scripts, background scripts are just js files but this time they have their own context independently of the browser’s tabs. They are used generally to establish a communication between tabs or to do some heavy work behind the scenes.

They are able to do some extra query calls to inject some script to a tab, remove it, make it active… These query calls may be performed based on some options like the id, the index, the url or title and even whether the tab is audible or not.

Communication between tabs/background

The idea is so simple, the extension runtime api exposes two methods, sendMessage and onMessage which we can call from content/background script to publish a message or consume it respectively.

To do so, here is an example from the chrome official documentation.

Content script:

// 1. Send a message to the background servic requesting the user's data
chrome.runtime.sendMessage('get-user-data', (response) => {
// 3. Got an asynchronous response with the data from the service worker
console.log('received user data', response);
initializeUI(response);
});

Background script:

// Example of a simple user data object
const user = {
username: 'demo-user'
};

chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
// 2. A page requested user data, respond with a copy of `user`
if (message === 'get-user-data') {
sendResponse(user);
}
});

We can also use the extension tabs api to send a message from the background to a certain tab.

chrome.tabs.sendMessage(
tabId: number,
message: any,
options?: object,
callback?: function,
)

To understand more the concept, let’s see a more concrete example.

Pauser

The extension that we’ll develop is as follow. Let’s say we have youtube open on a tab different than the active one and we want to pause the playing video and maybe resume it later. On a “ctrl+space” press, for example, the video is paused and with another press it is resumed.

The idea here is having a keydown listener on every page. The listener’s role is to send a message to the background script when “ctrl+space” is pressed.

The following content script should do the trick.

document.onkeydown = (e) => {
if(e.Code === "Space" && e.ctrlKey)
chrome.runtime.sendMessage({"id":"pause-ytb"})
}

And then the background should execute a consumer when it receives the message.

chrome.runtime.onMessage.addListener(
function(request, sender, _sendResponse) {
if(request.id === "pause-ytb"){
//script to execute
doTask()
}
return true;
}
);

The next thing that we need to do is to implement the doTask method that gets the id of youtube tab and injects a script in it that pauses the video.

chrome.tabs.query({"audible":true}, function(tabs) {
let ytbTab = tabs.filter((tab)=>tab.url.startsWith("https://www.youtube.com/"))[0]
if(!ytbTab.id)return;
chrome.scripting
.executeScript({
target : {tabId : ytbTab.id},
func : pauseFunc,
})
});

Nb: the “Tab” type is an object representing a chrome tab with its multiple info.

Tab: {
active: boolean,
audible?: boolean,
id?: number,
incognito: boolean,
index: number,
mutedInfo?: MutedInfo,
openerTabId?: number,
pinned: boolean,
title?: string,
url?: string,
windowId: number,
...
}

Some properties such as title and url are present only if the the extension’s manifest includes the “tabs” permission.

Last thing is the injected funtion. The function that will be executed in the targetted page.

function pauseFunc() { 
if(document.getElementsByClassName('video-stream')[0].paused)
document.getElementsByClassName('video-stream')[0].play()
else
document.getElementsByClassName('video-stream')[0].pause()
}

Sum up:

To sum up, the content script waits for a ctrl+space keydown to send a message request to the background. The background script then queries the audible tabs and filters them to get the youtube tab. Finally, it injects the snippet that, executed in the page, will pause the video.

Improvement:

To add the resume feature, we may think of saving the youtube tab’s id in the localstorage of the background. Like that, whenever we press ctrl+space the youtube video is paused and its tab’s id is saved under the name “pause-vid” for example. On another keydown, the doTask function tries first to check if the localStorage contains any element with the name “pause-vid” otherwise it executes the previous query/filter. Finally, the injected function “pauseFunc” will resume the video.

‘’ Time is gold. Everything that can be automated should be automated ‘’. 😉

See you next time.

KARZAZI REDOUANE

--

--