Table of contents
I have been afraid of creating an extension for years because I expected lots of effort and trouble. The reality turned out to be more comfortable. There is a comprehensive guide with lots of examples on the Chrome’s page: documentation. It’s worth going through it at least to grasp some ideas and keywords. The most important ones are listed below.
Only two files are necessary to create the simplest extension:
The extension can either work in browser or page mode:
- Browser mode is for extensions that work in most or all pages and can display badges onto their icons. The extension is always available.
- Page mode is for extensions that work only in a few sites. You have to provide a condition that enables the extension for the page (otherwise it’s grayed out). And you cannot use badges.
onclick="...") or scripts (
<script>...</script>). You’ll have to work on more or less unsafe content security policies to make them work. It’s much easier to put all code in a
.js file – see below for an example.
Content script is code that will be injected into the browsed page to enable some integration with it. It can be either provided in the manifest or injected using the
Background script can listen to events both from the content script and from the Chrome API.
Background script can communicate with the content script via messages.
It’s fairly easy to create the extension’s options page. Again, it’s just a HTML file and you have access to Chrome storage to save the settings.
Extensions can also change some parts of the Chrome’s interface, like extend context menu, add new keywords to the search bar, change New Tab or History page, and so on.
It is possible to test and run locally developed extensions without any need for packing, signing or publishing them.
First version – background script
I started with the ViolentMonkey script from the previous post. I removed the UserScript comments at the beginning and renamed it to
background.js. I had to do something with three external scripts included in that metadata:
Knowing little at the beginning and being confused by the browser and page mode, I downloaded these files and included them, along with the new script, as a background script.
I also created simple icon in four resolutions: 16px (used in the toolbar), 32px, 48px, 128px. At first, I used an online icon and logo editors but I quickly gave them up to Inkscape. I created three versions of the icon varying with details and extracted them to PNG files.
At last, I created the manifest file:
version attributes are obvious.
background attribute declares scripts that will be executed in the background.
browser_action attribute defines the icons used in the toolbar, whereas the
icons attribute lists the icons used in the Chrome extension page.
manifest_version identifies the format of this file and should be equal to
As I mentioned earlier, it’s extremely easy to run own extension in Chrome.
First, open the Extension Management page by selecting More Tools > Extensions the Chrome menu.
Then enable the Developer mode switch, click LOAD UNPACKED button and select the folder with the extension’s files.
The extension immediately appeared in the toolbar. There are also useful buttons on the Extensions page that allow to: Remove, show Errors, Reload and Disable the extension.
Just after loading the extension, I saw a popup with error:
Failed to init Database: FirebaseError: [code=permission-denied]: Missing or insufficient permissions.
Then I decided to read some documentation and review some samples. Although the cause of this problem was missing permissions to access remote scripts, the bigger issue was a wrong approach. The script was supposed to access the user’s page, so it couldn’t be run in the background script. At least not entirely.
My next approach was to run the script inside the extension’s popup. At that time I didn’t know yet what was the popup and I expected it to be displayed inside the web page the user was browsing.
So, I created a simple popup which barely imported all scripts:
I also updated the manifest to remove the background script and add the popup:
Now the extension loaded correctly, but after refreshing a sample page (let it be Highbrow) and clicking the extension’s icon, a small empty popup appeared below the icon (I’d rather call it dropdown). It probably started working, but only within that small popup which had no access to the page’s DOM. As a side note, it is possible to right-click that popup and select Inspect to open the devtools in the extension’s context.
I returned to the documentation and read the rest of it and reviewed some samples. I understood I had to create a content script that would be injected in the user’s page. There were two options to inject it, by providing the configuration in
manifest.json or calling
chrome.tabs.executeScript. I expected the first option to inject the script (and access the database) on every page I visit, 99.9% of which were not intended by my script. I didn’t want to define the list of allowed pages either in the manifest or in other configuration or options. Instead, I assumed that the script would be injected after I press my extension’s icon. I started with as little changes as possible:
Clicking the extension resulted with the following error:
Refused to execute inline script because it violates the following Content Security Policy directive: “script-src ‘self’ blob: filesystem:”. Either the ‘unsafe-inline’ keyword, a hash (‘sha256-0xt4Ta8so3hyvkftCUrpDxElMmx2iTHEEMmio/VzsOM=’), or a nonce (‘nonce-…’) is required to enable inline execution.
I had to update my knowledge on Content Security Policy on W3C pages. The easiest solution would be adding
window.close() will make the popup disappear immediately. So its only purpose was to inject the code into the current page.
Again, nothing happened. Instead, an error appeared in the Extension Management page:
Unchecked runtime.lastError: Cannot access contents of the page. Extension manifest must request permission to access the respective host.
So far, the extension declared no permissions. It appears I needed at least one – to access the current page. Easy thing to fix, just add to the manifest:
Unfortunately, the extension still did not work. The latest problem is visible in the page’s Console panel (which means that the script was correctly injected into the page):
background.js:24 Uncaught TypeError: firebase.auth is not a function
I suspected that the four js files included in
popup.js were loaded asynchronously, so
background.js tried to access
firebase-auth.js before it was loaded. As a hacky workaround, I put the last line loading
background.js after a timeout of a second, but it failed to run.
The better solution was merging all dependencies into one file. I quickly created a build script that concatenated the Firebase libraries and
background.js into one file called
I ran it by
Finally, the popup scripts file just loaded the dist file and closed popup:
Background script again
Although that code works, there is a bit simpler and cleaner solution. But first, the name
background.js is confusing, as it’s actually content script, so it should be called
content.js. I renamed the files and the reference to them in the build script.
Now, instead of opening an empty popup and hiding it when the user clicks the icon, I can add a handler for that click in a background script. It is very short:
background.js file should be included in the build script, whereas both
popup.js should be deleted (from the project and build script).
Finally, the manifest should be changed. Instead of:
there should be:
This is not the best and safest solution for an extension that works like a Greasemonkey / Tampermonkey / ViolentMonkey script. It is very quick to implement though and easy to extend.
To sum it up, the simplest Chrome extension could be created by adding the following files:
content.js– any script that should be executed