Over 5 years of developing extensions for the Google Chrome browser, some experience has been accumulated that I would like to share in a series of articles and, if possible, explain some subtleties and pitfalls, and describe how modern front-end technologies were successfully applied.
Part 1.
Introduction
I encountered Google Chrome browser extensions in 2012, when I was actively buying goods from the Amazon showcase and it was awfully awkward to look for sellers who delivered goods to the Russian Federation and from whom it is most advantageous to buy based on the cost of delivery. It was here that I decided to make life easier for myself, as well as for other customers, by creating an extension “Amazon ships to you”, about which there was even
an article on Habré . Over time, it became irrelevant, since In the Amazon showcase, after a couple of years, normal filters were made and I removed it from the publication.
Then it was time to use the
Ya.Muzyka and
Ya.Radio service and I really didn’t have enough control of the player on the Ya.Muzyka website when he plays in the background tab, at least with hot keys. As a result, I, as an experienced person, not having found analogues, decided to make an
extension for this
“Yandex.Music - player control” , which is my hobby to this day, despite the release of the
official extension from Yandex .
')
It is assumed that the reader should already be familiar with the basic structure of the extension elements (
manifest.json ,
background page ,
content script ,
popup ). A brief educational program under the spoiler below.
Likbez on extensionsAn extension is a software package that extends browser functionality. It is distributed through the official
Chrome Web Store , you can also download a local extension in the developer mode on the
chrome: // extensions / page, but Google is struggling with them more and more tightening the nuts.
Previously, it was possible to use local extensions without problems, then the browser began to notify you of the presence of such extensions, but now such extensions are disabled by default when the browser is started and must be activated manually (which has annoyed the developers of extensions during active development).
All metadata extensions are described in the manifest, but nothing special is said about it, everything is described in the
documentation .
The extension consists of a background page (
background page or
event page ), a
pop-up window ,
a settings page , a
content script that is embedded on the target site, and specific
override pages that override the standard browser pages: a history manager, bookmarks, a new page, etc.
From the point of view of the UI, the extension contains a
browserAction (or
pageAction ) element - an icon on the browser panel to the right of the address bar, clicking on which can initiate an action or opens another UI element - a
pop -
up window . Also on this icon you can display a
badge - an icon, for example, with the number of unread letters.
The browser provides extensions with multiple
APIs for various needs, for example, through the
chrome.tabs API, you can access all tabs, close, open new ones, see tabs metadata, etc.
Plus, there are certain
data transfer mechanisms between different extension pages.
Sandboxes
A typical extension task is an extension of the functionality of a certain site, for which it is necessary to implement the code on the landing page. Take for example Ya.Muzyku (hereinafter): to control the player on the site through the pop-up extension window (see screenshot No. 1), we need to track the opening of the page, then embed our code into it, which will later interact with js- code and DOM showcases and transfer all changes to the extension so that when a pop-up window opens, always display the current state of the player on the showcase. This is where the important one appears that I would like to begin by telling and what the title of the section says:
sandboxes .
(screenshot number 1)
The background extension page, being the main extension controller, with proper extensions (
tabs, activeTab ) can track the opening of the
music.yandex.ru page using
chrome.tabs.onUpdated and inject our code (content script) to the storefront via
chrome.tabs.executeScript . However, the embedded code is in its sandbox (B), from which there is access to the showcase DOM document, but not to the showcase js code that runs in its sandbox (A). In theory, it would be possible to track changes in the DOM elements on the storefront to translate the current state into the extension (for example, the track has changed - take the name of the track from the DOM, the performer), but this does not work as we would like: with delays on updating the storefront, always present target DOM elements in the current storefront display and other things that, in actual use of the extension, make the data out of sync between the storefront and the display in the extension. How do we get access to the showcase js-code (sandbox A)?
To implement js code in the sandbox A from the content script, the following “hack” can help us (hereinafter es6 notation):
function injectCode(func, ...args) { let script = document.createElement('script'); script.textContent = 'try {(' + func + ')(' + args + '); } catch(e) {console.error("injected error", e);};'; (document.head || document.documentElement).appendChild(script); script.parentNode.removeChild(script); }
As a result, our content script can inject its code into the sandbox of the showcase js code in order to gain access to the storefront API and at a low level to access objects and events of the player, playlist, and other showcase entities. But that's not all: the access to the API is obtained, for example, the start of playing the track our embedded code “caught”, but how can we transfer it back to the content script, which, in turn, has mechanisms for transferring data to the background extension page ?
Alas, there is no access from sandbox A to sandbox B; in any case, it is unknown to me. But this is not scary, because we remember what the content script has access to from its sandbox B: to the DOM showcase window, hence the exit: embedded in the sandbox A, the code can create arbitrary events:
document.dispatchEvent(new CustomEvent(CUSTOM_EVENT_NAME, {detail: payload}));
And the content script can catch them:
document.addEventListener(CUSTOM_EVENT_NAME, e => { console.log(e.detail);
It turns out this scheme:
After that, the content script sends this event to the background page using one of the mechanisms:
chrome.runtime.sendMessage or
chrome.runtime.connect . Their difference is that the first method opens a channel, transmits data, closes the channel, while the second creates a permanent channel within which data transfer takes place. When playing tracks, there is a constant transmission of progress events (current playback time), so the choice was obvious for me: to raise the permanent communication channel of the content script with the background page, which gives another not obvious bonus: control of loss of communication with the showcase for various reasons, from exceptions in the js code before closing the tab (although it is the closing of the tab that is easily tracked through
chrome.tabs.onRemoved ).
With these sandboxes finished, but that's not all: there is still a sandbox (B) of the background page and a sandbox (D) of the pop-up window. Everything is a bit simpler, these sandboxes have access to each other through the API:
But there is an underwater stone: a pop-up window cannot, for example, add its event listener to the background page, but the above-described methods give access to each other's window object (since they are running in the same thread). For consistency of intercommunications and for forced isolation of the background page code from the pop-up window code (otherwise popup can modify bg objects, which will disrupt the unidirectional flow of data from the content script to the background page to store the current state and from there to the pop-up window) raising the channel as between the content script and the background page.
There is little code and examples, since everything is in the documentation, to which I refer as much as possible for any references to methods and APIs, I don’t have anything to add here, because I don’t pretend on the status of a tutorial (when you can copy all the code and earn something). But the concept itself was worth chewing, because Repeatedly explained to colleagues on the programmer’s workshop. You can summarize the section with the following illustration:
In the next part, I plan to talk about the translation history of the extension
“Yandex.Music - player control" for the now popular React + Redux, which, by the way, came up to display the player status in the pop-up extension window.