Building Browser Extensions
Building Browser Extensions
Browser
Extensions
Create Modern Extensions for
Chrome, Safari, Firefox, and Edge
—
Matt Frisbie
Foreword by Stefan Aleksic and Louis Vilgo,
cofounders of Plasmo
Building Browser
Extensions
Create Modern Extensions
for Chrome, Safari, Firefox,
and Edge
Matt Frisbie
Foreword by Stefan Aleksic and Louis Vilgo,
cofounders of Plasmo
Building Browser Extensions: Create Modern Extensions for Chrome,
Safari, Firefox, and Edge
Matt Frisbie
California, CA, USA
To Jordan.
Your unwavering support advances the world
of web development and keeps the corners of our
carpets nice and flat.
Table of Contents
About the Author�����������������������������������������������������������������������������xxiii
v
Table of Contents
vi
Table of Contents
vii
Table of Contents
chrome_settings_overrides�������������������������������������������������������������������������116
chrome_url_overrides���������������������������������������������������������������������������������124
commands���������������������������������������������������������������������������������������������������125
content_capabilities������������������������������������������������������������������������������������131
content_scripts�������������������������������������������������������������������������������������������131
content_security_policy������������������������������������������������������������������������������134
converted_from_user_script�����������������������������������������������������������������������136
cross_origin_embedder_policy�������������������������������������������������������������������136
cross_origin_opener_policy������������������������������������������������������������������������137
declarative_net_request�����������������������������������������������������������������������������138
default_locale����������������������������������������������������������������������������������������������139
description���������������������������������������������������������������������������������������������������140
developer�����������������������������������������������������������������������������������������������������140
devtools_page���������������������������������������������������������������������������������������������141
differential_fingerprint��������������������������������������������������������������������������������141
event_rules��������������������������������������������������������������������������������������������������142
externally_connectable�������������������������������������������������������������������������������142
file_browser_handlers��������������������������������������������������������������������������������143
file_system_provider_capabilities��������������������������������������������������������������144
homepage_url���������������������������������������������������������������������������������������������145
host_permissions����������������������������������������������������������������������������������������146
icons������������������������������������������������������������������������������������������������������������146
incognito������������������������������������������������������������������������������������������������������147
key���������������������������������������������������������������������������������������������������������������148
manifest_version�����������������������������������������������������������������������������������������149
minimum_chrome_version�������������������������������������������������������������������������150
nacl_modules����������������������������������������������������������������������������������������������150
name�����������������������������������������������������������������������������������������������������������150
viii
Table of Contents
oauth2���������������������������������������������������������������������������������������������������������151
offline_enabled��������������������������������������������������������������������������������������������152
omnibox�������������������������������������������������������������������������������������������������������152
optional_host_permissions�������������������������������������������������������������������������153
optional_permissions����������������������������������������������������������������������������������153
options_page�����������������������������������������������������������������������������������������������154
options_ui����������������������������������������������������������������������������������������������������154
page_action�������������������������������������������������������������������������������������������������157
permissions�������������������������������������������������������������������������������������������������157
platforms�����������������������������������������������������������������������������������������������������158
replacement_web_app�������������������������������������������������������������������������������158
requirements�����������������������������������������������������������������������������������������������159
sandbox�������������������������������������������������������������������������������������������������������159
short_name�������������������������������������������������������������������������������������������������160
storage��������������������������������������������������������������������������������������������������������161
system_indicator�����������������������������������������������������������������������������������������161
tts_engine���������������������������������������������������������������������������������������������������162
update_url���������������������������������������������������������������������������������������������������163
version���������������������������������������������������������������������������������������������������������163
version_name����������������������������������������������������������������������������������������������164
web_accessible_resources�������������������������������������������������������������������������164
Summary����������������������������������������������������������������������������������������������������������166
ix
Table of Contents
x
Table of Contents
Common Patterns���������������������������������������������������������������������������������������������207
Event Handler����������������������������������������������������������������������������������������������208
Secret Management and Authentication�����������������������������������������������������210
Message Hub�����������������������������������������������������������������������������������������������211
Storage Manager�����������������������������������������������������������������������������������������214
Injecting Scripts������������������������������������������������������������������������������������������215
Sniffing Web Traffic�������������������������������������������������������������������������������������216
Installed/Updated Events�����������������������������������������������������������������������������217
Opening Tabs�����������������������������������������������������������������������������������������������218
Forcing Service Worker Persistence�����������������������������������������������������������������220
Implementation�������������������������������������������������������������������������������������������220
Drawbacks and Limitations�������������������������������������������������������������������������223
Summary����������������������������������������������������������������������������������������������������������224
xi
Table of Contents
xii
Table of Contents
xiii
Table of Contents
Privacy���������������������������������������������������������������������������������������������������������338
Idle���������������������������������������������������������������������������������������������������������������339
Devtools�������������������������������������������������������������������������������������������������������339
Extension Introspection�������������������������������������������������������������������������������340
Extension Management�������������������������������������������������������������������������������341
System State�����������������������������������������������������������������������������������������������341
Enterprise Only��������������������������������������������������������������������������������������������342
Firefox Only�������������������������������������������������������������������������������������������������342
Chrome OS Only������������������������������������������������������������������������������������������343
Deprecated��������������������������������������������������������������������������������������������������344
Summary����������������������������������������������������������������������������������������������������������344
xiv
Table of Contents
background�������������������������������������������������������������������������������������������������363
bookmarks���������������������������������������������������������������������������������������������������363
browserSettings������������������������������������������������������������������������������������������364
browsingData����������������������������������������������������������������������������������������������364
captivePortal�����������������������������������������������������������������������������������������������364
certificateProvider���������������������������������������������������������������������������������������364
clipboardRead���������������������������������������������������������������������������������������������365
clipboardWrite���������������������������������������������������������������������������������������������365
contentSettings�������������������������������������������������������������������������������������������365
contextMenus����������������������������������������������������������������������������������������������366
contextualIdentities�������������������������������������������������������������������������������������366
cookies��������������������������������������������������������������������������������������������������������366
debugger�����������������������������������������������������������������������������������������������������366
declarativeContent��������������������������������������������������������������������������������������367
declarativeNetRequest��������������������������������������������������������������������������������367
declarativeNetRequestFeedback�����������������������������������������������������������������367
declarativeWebRequest�������������������������������������������������������������������������������367
desktopCapture�������������������������������������������������������������������������������������������368
Devtools page����������������������������������������������������������������������������������������������368
dns���������������������������������������������������������������������������������������������������������������368
documentScan��������������������������������������������������������������������������������������������368
downloads���������������������������������������������������������������������������������������������������369
downloads.open������������������������������������������������������������������������������������������369
enterprise.deviceAttributes�������������������������������������������������������������������������369
enterprise.hardwarePlatform����������������������������������������������������������������������369
enterprise.networkingAttributes������������������������������������������������������������������370
enterprise.platformKeys������������������������������������������������������������������������������370
experimental������������������������������������������������������������������������������������������������370
xv
Table of Contents
fileBrowserHandler��������������������������������������������������������������������������������������370
fileSystemProvider��������������������������������������������������������������������������������������370
find��������������������������������������������������������������������������������������������������������������371
fontSettings�������������������������������������������������������������������������������������������������371
gcm�������������������������������������������������������������������������������������������������������������371
geolocation��������������������������������������������������������������������������������������������������371
history���������������������������������������������������������������������������������������������������������372
Host Permissions�����������������������������������������������������������������������������������������372
identity���������������������������������������������������������������������������������������������������������374
idle���������������������������������������������������������������������������������������������������������������374
loginState����������������������������������������������������������������������������������������������������374
management�����������������������������������������������������������������������������������������������374
menus����������������������������������������������������������������������������������������������������������375
menus.overrideContext�������������������������������������������������������������������������������375
nativeMessaging�����������������������������������������������������������������������������������������375
notifications�������������������������������������������������������������������������������������������������376
pageCapture������������������������������������������������������������������������������������������������376
pkcs11���������������������������������������������������������������������������������������������������������376
platformKeys�����������������������������������������������������������������������������������������������377
power����������������������������������������������������������������������������������������������������������377
printerProvider���������������������������������������������������������������������������������������������377
printing��������������������������������������������������������������������������������������������������������377
printingMetrics��������������������������������������������������������������������������������������������378
privacy���������������������������������������������������������������������������������������������������������378
processes����������������������������������������������������������������������������������������������������378
proxy������������������������������������������������������������������������������������������������������������378
scripting������������������������������������������������������������������������������������������������������379
search����������������������������������������������������������������������������������������������������������379
xvi
Table of Contents
sessions�������������������������������������������������������������������������������������������������������379
signedInDevices������������������������������������������������������������������������������������������379
storage��������������������������������������������������������������������������������������������������������379
system.cpu��������������������������������������������������������������������������������������������������380
system.display���������������������������������������������������������������������������������������������380
system.memory�������������������������������������������������������������������������������������������380
system.storage��������������������������������������������������������������������������������������������380
tabCapture���������������������������������������������������������������������������������������������������380
tabGroups����������������������������������������������������������������������������������������������������381
tabHide��������������������������������������������������������������������������������������������������������381
tabs��������������������������������������������������������������������������������������������������������������381
theme����������������������������������������������������������������������������������������������������������382
topSites�������������������������������������������������������������������������������������������������������382
tts����������������������������������������������������������������������������������������������������������������382
ttsEngine�����������������������������������������������������������������������������������������������������382
unlimitedStorage�����������������������������������������������������������������������������������������383
vpnProvider�������������������������������������������������������������������������������������������������383
wallpaper�����������������������������������������������������������������������������������������������������383
webNavigation���������������������������������������������������������������������������������������������383
webRequest�������������������������������������������������������������������������������������������������384
webRequestBlocking�����������������������������������������������������������������������������������384
Summary����������������������������������������������������������������������������������������������������������384
xvii
Table of Contents
Content Scripts��������������������������������������������������������������������������������������������388
Background Scripts�������������������������������������������������������������������������������������389
Pinning an Extension ID������������������������������������������������������������������������������������389
Authentication Styles����������������������������������������������������������������������������������������392
No Authentication����������������������������������������������������������������������������������������392
Content Script Spoofing�������������������������������������������������������������������������������393
Cookie Authentication����������������������������������������������������������������������������������393
Json Web Token Authentication�������������������������������������������������������������������394
OAuth and OpenID���������������������������������������������������������������������������������������394
OAuth, OpenID, and the Identity API������������������������������������������������������������������395
OAuth API Methods��������������������������������������������������������������������������������������396
OAuth Redirect URLs�����������������������������������������������������������������������������������396
Configuring the Authorization Platform�������������������������������������������������������397
Additional Help��������������������������������������������������������������������������������������������403
OAuth and OpenID Examples����������������������������������������������������������������������������403
Google OAuth with getAuthToken()��������������������������������������������������������������404
Google OpenID with launchWebAuthFlow( )������������������������������������������������406
Manual Github OAuth with launchWebAuthFlow()���������������������������������������409
Networking APIs������������������������������������������������������������������������������������������������412
The webNavigation API��������������������������������������������������������������������������������413
The webRequest API������������������������������������������������������������������������������������416
The declarativeNetRequest API�������������������������������������������������������������������420
Summary����������������������������������������������������������������������������������������������������������427
xviii
Table of Contents
Error Monitoring������������������������������������������������������������������������������������������439
Extension Reloads���������������������������������������������������������������������������������������441
Automated Extension Tests�������������������������������������������������������������������������������442
Unit Tests�����������������������������������������������������������������������������������������������������443
Integration Tests������������������������������������������������������������������������������������������449
Additional Reading��������������������������������������������������������������������������������������450
Publishing Extensions���������������������������������������������������������������������������������������451
Store Listing������������������������������������������������������������������������������������������������451
Privacy Practices�����������������������������������������������������������������������������������������452
Review Process�������������������������������������������������������������������������������������������������452
Updating Extensions�����������������������������������������������������������������������������������������452
Update Considerations��������������������������������������������������������������������������������452
Cancelling Updates��������������������������������������������������������������������������������������453
Automated Update Publishing���������������������������������������������������������������������454
Tracking User Activity����������������������������������������������������������������������������������������454
Dashboard Metrics��������������������������������������������������������������������������������������455
Analytics Libraries���������������������������������������������������������������������������������������455
Setting Up Google Analytics�������������������������������������������������������������������������456
Install and Uninstall Events�������������������������������������������������������������������������457
Summary����������������������������������������������������������������������������������������������������������458
xix
Table of Contents
xx
Table of Contents
Firefox Idiosyncrasies���������������������������������������������������������������������������������������505
Manifest Versions����������������������������������������������������������������������������������������505
Sidebars������������������������������������������������������������������������������������������������������506
API Additions������������������������������������������������������������������������������������������������507
Summary����������������������������������������������������������������������������������������������������������508
xxi
Table of Contents
Index�������������������������������������������������������������������������������������������������533
xxii
About the Author
Matt Frisbie has worked in web development
for over a decade. During that time, he's been
a startup co-founder, an engineer at a Big
Four tech company, and the first engineer at
a Y Combinator startup that would eventually
become a billion-dollar company. As a Google
software engineer, Matt worked on both the
AdSense and Accelerated Mobile Pages (AMP)
platforms; his code contributions run on
most of the planet's web browsing devices. Prior to this, Matt was the first
engineer at DoorDash, where he helped lay the foundation for a company
that has become the leader in online food delivery. Matt has written three
books, Professional JavaScript for Web Developers, Angular 2 Cookbook,
and AngularJS Web Application Development Cookbook, and recorded
two video series, "Introduction to Modern Client-Side Programming" and
"Learning AngularJS." He speaks at frontend meetups and webcasts, and is
a level 1 sommelier. He majored in Computer Engineering at the University
of Illinois Urbana-Champaign. Matt's Twitter handle is @mattfriz.
xxiii
About the Technical Reviewer
Jeff Friesen is a freelance software developer
and educator conversant in multiple
operating systems, programming languages,
and numerous technologies. He is currently
exploring bare metal programming for
the Raspberry Pi and developing his own
assembler and associating tooling to facilitate
the development of a simple Pi-based
operating system.
xxv
Acknowledgments
I’d like to acknowledge the tremendous work done by editors Divya Modi,
Shonmirin P.A., James Markham, and everyone else involved with the
book’s production. A capable and agile staff does so much to improve
the quality of the product. You were all invaluable resources, and it was a
pleasure working with you.
I’d also like to thank the book’s technical reviewer Jeff Friesen.
Technical authors are nothing without this meticulous work behind the
scenes, and his contributions were nothing short of outstanding.
Finally, I would like to thank Apress for publishing this book with me.
This was my first title where the book was entirely my own concept, and I
am thankful that it found a home with such a terrific publisher.
xxvii
Foreword
We are Stefan and Louis, co-founders of Plasmo. We first started our
company building browser extensions for cyber security, experiencing
firsthand the many difficulties extension developers face. We quickly
realized that the process of building an extension was way too difficult.
We decided to open source our framework, and dedicated our company’s
vision to solving the toughest problems in browser extension development.
As of today, our framework is the most popular browser extension SDK
worldwide, and our fast-growing extension product suite is used by some
of the world’s leading extension developers.
A large community soon gathered and shared even more issues
blocking their development with us. We were excited to hear that Matt was
writing a book on the subject! Extension development is tricky and filled
with poor documentation, difficulty getting started, a massive transition
from one extension version to another looming, and much more. There
has never been a comprehensive guide to extension development, but
Matt's book is changing that.
People use browsers for most of their lives. You might use your browser
to read the news or find a good breakfast burrito recipe when you wake
up. Software engineers might use their browsers to review pull requests
and test their front end. Sales representatives use their browsers to send
outbound messages to prospective clients on LinkedIn, and security
engineers use their browsers to review new phishing alerts. These use
cases are different, but each person uses the same tool to do their job.
Browser extensions augment the browser and transform it from a
generic tool into a highly specialized one. A sales representative has
different needs compared to a security engineer. In a few years, sales
xxix
Foreword
xxx
Introduction
The world of browser extensions has far more than meets the eye. Consider
the following:
When I saw there were 0 relevant Amazon search results for “build
chrome extension,” I nearly fell out of my chair. I knew at once that this
book must be written.
Building Browser Extensions: Create Modern Extensions for Chrome,
Safari, Firefox, and Edge covers all the knowledge you will need to write
cross-browser extensions with the latest web development tools. Browser
extensions are given access to extremely powerful APIs. I believe most
developers are blind to that power – and unaware of just how much it is
within their reach.
This book is designed to enlighten web developers and illuminate the
true potential of the browser extension software platform. It is geared for
developers who have experience building websites and can apply their
knowledge to a new software domain. This book is not ideal for people
new to programming – it would be like an inexperienced cook starting off
by learning to make a sauce.
xxxi
Introduction
xxxii
CHAPTER 1
Whatever lies ahead for browser extensions, it seems that they will not
be going away any time soon.
2
Chapter 1 What Are Browser Extensions?
3
Chapter 1 What Are Browser Extensions?
4
Chapter 1 What Are Browser Extensions?
5
Chapter 1 What Are Browser Extensions?
6
Chapter 1 What Are Browser Extensions?
7
Chapter 1 What Are Browser Extensions?
address the widespread use of blocker extensions. Some pages now will
only render their content if the ad and tracker scripts load; they assume
that a failed request is always because a blocking extension killed the
request. Some ad and tracking servers serve their scripts using “CNAME
cloaking,” where the ad or tracking script request pretends to be regular
content loaded from the page’s domain. Furthermore, Google Chrome is
using its dominant extension market share to champion a transition to a
new manifest format, manifest v3, that will severely reduce the ability of
extensions to effectively block ad and tracker requests.
Password Managers
Extensions can manage a JavaScript execution environment that is
completely separated from any web page. This sandboxed environment is
ideally suited to load and store secret information, where it can be securely
passed to and from web pages on demand. Password managers leverage
this to record username/password combinations, save them locally or
remotely, and type them in for the user.
A password manager extension allows the user to log in to the
password manager service inside a sandboxed extension interface. Once
credentialed, they will load the user’s encrypted passwords from a remote
server and decrypt them locally. With the passwords now available in
memory, the extension manager is granted permissions that allow it to
effectively manage a user’s passwords:
8
Chapter 1 What Are Browser Extensions?
9
Chapter 1 What Are Browser Extensions?
Accessibility Tools
The primary way to consume content on the Internet is via text, and not
everyone has an easy time reading a computer screen. Just as smart writing
management extensions can read and change input text, accessibility
extensions can take the existing page text and format it in a way to make
it more accessible to the user. This can take the form of automatically
translating page text, piping the page text to screen reading software,
reformatting the page text to make it more legible (by increasing size,
contrast, or choosing alternate fonts), or by offering quick access to a
dictionary.
10
Chapter 1 What Are Browser Extensions?
Note Extensions that use these HTML5 APIs still require the user to
explicitly give permission. Permission-gated APIs such as the Screen
Capture API will still generate a browser dialog box asking the user to
grant access when a browser extension requests it.
11
Chapter 1 What Are Browser Extensions?
Developer Tools
Modern web development could not have happened without developer
browser extensions. In the early days of the web, developers were
desperate for ways to more easily debug the websites they were
developing. One of the earliest solutions to this was a Firefox addon
released in 2006 called Firebug, which allowed developers to view and
tease apart the web page they were currently viewing (Figure 1-2). This
greatly simplified their debug process for HTML, JavaScript, and CSS,
which without the addon would incur a patchwork mess of using print
statements and viewing the raw page source.
12
Chapter 1 What Are Browser Extensions?
Browsers quickly caught on that there was incredible demand for this
sort of tooling, and they began to roll out native versions of debuggers that
matched the feature set of Firebug. Having become redundant, the Firebug
extension was shut down in 2017.
With the mass adoption of single page applications like React, demand
for developer tooling has followed in turn. The native browser debugger is
not well-suited for debugging these sorts of applications, as the logic and
architecture governing how the page renders is sequestered in large blobs
of third-party JavaScript that is very difficult to debug.
To address this issue, many teams that work on these single page
application frameworks also release a companion browser extension. The
browser extension is intimately familiar with the internals of how a specific
single page application behaves and is equipped with a broad toolkit that
allows the developer to peer into the framework and understand what
it is doing. This affords developers a much richer insight into what is
happening on the page – and more importantly, how to fix it.
13
Chapter 1 What Are Browser Extensions?
Summary
Browser extensions have sneakily become an essential component of
modern computing. From the humble of beginnings of the mainframe
computer, they have evolved over decades into a veritable Swiss Army
Knife for the modern browser.
14
Chapter 1 What Are Browser Extensions?
Credits
Figure 1-1: https://commons.wikimedia.org/wiki/File:Univac9060.jpg
Source: United States Navy
15
CHAPTER 2
Fundamental
Elements of Browser
Extensions
As with most software platforms, browser extensions can be conceptually
divided into a handful of discrete pieces. Developers new to the world
of browser extensions may find the idiosyncrasies of these pieces tricky
to internalize, as some of the behavior is redundant or not intuitive.
Understanding how the pieces of browser extensions fit together is critical
for becoming an expert at browser extension development.
18
Chapter 2 Fundamental Elements of Browser Extensions
requests after the initial HTML loads: the page head will load assets like
CSS, images, and additional scripts, and the scripts can execute additional
requests to load assets and send and receive data.
Browser Tabs
Let’s extend the previous diagram to show when multiple tabs are open in
a browser (Figure 2-2).
• Cookies
• LocalStorage
• IndexedDB
19
Chapter 2 Fundamental Elements of Browser Extensions
• Shared Workers
• Service Workers
• HTTP Cache
Same-Origin Policy
Origins are also a consideration when applying the Same-Origin Policy
(SOP). In brief, the SOP is a set of security policies implemented by all
browsers that control access to data between web applications. When two
web applications have different origins, the browser automatically applies
a set of restrictions to protect potentially sensitive information from an
untrusted origin. Web developers typically encounter the SOP in three
places: when sending network requests to foreign origins, when executing
scripts from a different origin, and when accessing storage APIs from
different origins.
20
Chapter 2 Fundamental Elements of Browser Extensions
21
Chapter 2 Fundamental Elements of Browser Extensions
• Tabs API
• Bookmarks API
• Downloads API
• Browser History API
22
Chapter 2 Fundamental Elements of Browser Extensions
23
Chapter 2 Fundamental Elements of Browser Extensions
Extension Manifest
The manifest is the rulebook for the browser extension. Every extension
is required to provide this manifest as JSON data in a manifest.json file.
Some examples of what is contained in the manifest
24
Chapter 2 Fundamental Elements of Browser Extensions
Manifest v2 and v3
The world of browser extensions is currently in a transitional period
between two versions of the manifest.json file: version 2 (v2) and
version 3 (v3). The new manifest v3 significantly alters how extensions
execute, and it changes the APIs that they can access. Google Chrome is
spearheading the transition to manifest v3; due to their market dominance,
other browser vendors are for the most part following in turn.
The transition to manifest v3 will be a major theme in this book.
The changes it makes are highly controversial in the browser extension
developer community, and they broadly impact a number of extremely
popular browser extensions.
Background Scripts
The primary purpose of background scripts is to handle browser events.
These events might be extension lifecycle events such as an install or
uninstall, or they might be browser events like navigating to a web page or
adding a new bookmark. Background scripts can be defined in JavaScript
files, or as a single HTML file that runs the background scripts via one or
more <script> tags in a headless page.
25
Chapter 2 Fundamental Elements of Browser Extensions
26
Chapter 2 Fundamental Elements of Browser Extensions
Popup pages are rendered just like regular web pages, but their dialog-
like nature means they are disposable: the popup will be freshly rendered
each time the popup is opened, and unloaded when the popup is closed.
Like background scripts, popup pages can access the WebExtensions API,
meaning they have the same set of capabilities.
27
Chapter 2 Fundamental Elements of Browser Extensions
28
Chapter 2 Fundamental Elements of Browser Extensions
29
Chapter 2 Fundamental Elements of Browser Extensions
Content Scripts
The term content script broadly refers to any content that is injected into a
web page. JavaScript can either be injected declaratively in the manifest,
or programmatically from an extension page or background script via
the WebExtensions API. This content can be JavaScript, CSS, or both. The
JavaScript is sandboxed in its own separate runtime, meaning it cannot
read JavaScript variables or properties in the primary web page runtime,
but it still shares access to the same DOM as the web page itself. This
means that content scripts are fully capable of reading and writing the
page, enabling things like in-page widgets or full integration with the
web page.
Content scripts have limited access to the WebExtensions API, so they
are incapable of many actions that are possible in the popup page, options
page, or background script. They can, however, exchanges messages with
other extension elements like background scripts. Therefore, content
scripts can still indirectly use the WebExtensions API by delegating actions
to a background script.
30
Chapter 2 Fundamental Elements of Browser Extensions
31
Chapter 2 Fundamental Elements of Browser Extensions
Like the popup page, this is a fully featured web page, but like content
scripts, it has limited access to the WebExtensions API. However, it is
provided access to the additional Devtools API that can be used to perform
actions like inspecting the page and observing the page’s network traffic.
32
Chapter 2 Fundamental Elements of Browser Extensions
Honey
Honey is a browser extension that can detect when the user is checking
out on an ecommerce website and automatically apply coupon codes
(Figure 2-9).
33
Chapter 2 Fundamental Elements of Browser Extensions
Here, Honey is rendering a widget over the page using a content script.
It also uses a content script to programmatically submit coupon codes
when the user is in the checkout flow.
Furthermore, note that the toolbar icon is displaying a badge
containing “15,” which is Honey telling us there are 15 possible coupon
codes to try on this domain. Honey knows this because it can assess what
domain the user is visiting and checking that against the Honey database
of matching coupon codes. The extension is then able to use its ability to
dynamically set what the toolbar icon shows and render the Honey logo
with the “15” badge (Figure 2-10).
34
Chapter 2 Fundamental Elements of Browser Extensions
LastPass
LastPass is a browser extension that can securely store and autofill a user’s
passwords, as well as record new passwords when they are submitted
(Figure 2-11).
35
Chapter 2 Fundamental Elements of Browser Extensions
36
Chapter 2 Fundamental Elements of Browser Extensions
37
Chapter 2 Fundamental Elements of Browser Extensions
Grammarly
Grammarly is a browser extension that observes the text a user types into
the browser and automatically offers suggestions on how to fix or improve
it (Figures 2-14 and 2-15).
38
Chapter 2 Fundamental Elements of Browser Extensions
39
Chapter 2 Fundamental Elements of Browser Extensions
Grammarly also offers toggles for extension settings in the popup page.
40
Chapter 2 Fundamental Elements of Browser Extensions
41
Chapter 2 Fundamental Elements of Browser Extensions
The extension uses the Devtools API to create a custom panel in the
browser’s developer tools interface. It parses the page content and unpacks
it into a browseable component tree inside the panel page.
Summary
As the name suggests, browser extensions are more of an extension of the
browser rather than the page. The WebExtensions API allows extensions to
perform actions that web pages cannot normally accomplish.
Browser extensions are composed of a collection of elements. The only
required element is the manifest. Optional elements include background
scripts, the popup page, the options page, devtools pages, and content scripts.
The next chapter will take you through a crash course covering how to
build and install a basic browser extension.
42
CHAPTER 3
Browser Extension
Crash Course
Chapters later in this book will cover various aspects of browser extensions
in great detail. This chapter, on the other hand, will guide you through the
quickest way to get up and running with browser extensions. The crash
course will cover building a trivial browser extension from scratch. It will
not use any third-party libraries or dependencies, just vanilla HTML, CSS,
and JavaScript.
This chapter is geared toward developers that have never built an
extension before; however, even if you are already somewhat comfortable
with extension development, this chapter may yet prove useful as
it touches upon some corners of the domain that you may not have
experimented with before.
Note For the sake of simplicity, the crash course will only focus
on developing a manifest v3 browser extension for Google Chrome.
Creating a manifest v2 browser extension, or supporting other
browsers, requires steps that are not covered in this crash course.
extension-crash-course/
└─ manifest.json
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3
}
44
Chapter 3 Browser Extension Crash Course
45
Chapter 3 Browser Extension Crash Course
Figure 3-1. Opening the Chrome Extensions page via browser menu
46
Chapter 3 Browser Extension Crash Course
47
Chapter 3 Browser Extension Crash Course
With Developer mode enabled, you can then load the extension by
clicking “Load unpacked” and selecting the extension-crash-course
directory that contains manifest.json (Figure 3-4).
48
Chapter 3 Browser Extension Crash Course
49
Chapter 3 Browser Extension Crash Course
Figure 3-5. Google Chrome with the crash course extension newly
installed
Note For ease of access, you should pin the extension toolbar icon
so that it always displays. Click on the puzzle piece extension icon,
and then click the pin button.
50
Chapter 3 Browser Extension Crash Course
There are several different points at which portions of the extension are
reloaded:
51
Chapter 3 Browser Extension Crash Course
Note For the purposes of this crash course, reloading the extension
and the extension page will be sufficient to reflect any changes you
have made.
extension-crash-course/
├─ manifest.json
└─ background.js
background.js
52
Chapter 3 Browser Extension Crash Course
The manifest doesn’t know this file exists, so let’s update it so it loads
this script file as a background service worker:
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
After reloading the extension, you will notice that the card on the
Chrome Extensions page shows a link to the service worker (Figure 3-6):
53
Chapter 3 Browser Extension Crash Course
This link will open a developer tools console for the background
service worker, where you will see the output of the console.log
(Figure 3-7).
54
Chapter 3 Browser Extension Crash Course
background.js
chrome.runtime.onMessage.addListener((msg) => {
console.log(msg.text);
});
You’ll note that the card in the Chrome Extension page may show the
service worker as inactive after a period of time. In the interest of freeing
unused system resources, Google Chrome decided that the service worker
was idle and automatically unloaded it. The worker is reloaded when
needed again, such as when an extension message is sent.
55
Chapter 3 Browser Extension Crash Course
extension-crash-course/
├─ manifest.json
├─ background.js
└─ popup/
├─ popup.html
├─ popup.css
└─ popup.js
popup/popup.html
<!DOCTYPE html>
<html>
<head>
<link href="popup.css" rel="stylesheet" />
</head>
<body>
<h1>This is the popup page!</h1>
<script src="popup.js"></script>
</body>
</html>
56
Chapter 3 Browser Extension Crash Course
popup/popup.css
body {
width: 400px;
margin: 2rem;
}
popup/popup.js
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup/popup.html"
}
}
After reloading the extension, click the toolbar extension icon to open
the popup page (Figure 3-8).
57
Chapter 3 Browser Extension Crash Course
Next, let’s send a message from the popup to the background script.
Update the following files:
popup/popup.html
<!DOCTYPE html>
<html>
<head>
58
Chapter 3 Browser Extension Crash Course
<script src="popup.js"></script>
</body>
</html>
popup/popup.js
document.querySelector("#btn").
addEventListener('click', () => {
chrome.runtime.sendMessage({ text: "Popup" });
});
chrome.runtime.onMessage.addListener((msg) => {
document.body.innerHTML += `<div>${msg.text}</div>`;
});
This new code adds a button to the popup that sends a message to
the rest of the extension. It also adds a listener for incoming messages.
Reload the extension, click the new button in the popup, and inspect the
background script to see the message logged (Figure 3-9).
59
Chapter 3 Browser Extension Crash Course
Figure 3-9. Background script logs the message from the popup page
Note that, even though we set a listener for messages in the popup
page, it did not handle an incoming message. Although the extension
message infrastructure works as a broadcast, the source that sends the
message will not also receive it.
extension-crash-course/
├─ manifest.json
├─ background.js
├─ popup/
│ ├─ popup.html
│ ├─ popup.css
│ └─ popup.js
└─ options/
├─ options.html
├─ options.css
└─ options.js
60
Chapter 3 Browser Extension Crash Course
options/options.html
<!DOCTYPE html>
<html>
<head>
<link href="options.css" rel="stylesheet" />
</head>
<body>
<h1>This is the options page!</h1>
<script src="options.js"></script>
</body>
</html>
options/options.css
body {
margin: 2rem;
}
options/options.js
document.querySelector("#btn").
addEventListener('click', () => {
chrome.runtime.sendMessage({ text: "Options" });
});
chrome.runtime.onMessage.addListener((msg) => {
document.body.innerHTML += `<div>${msg.text}</div>`;
});
61
Chapter 3 Browser Extension Crash Course
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup/popup.html"
},
"options_page": "options/options.html"
}
Reload the extension, and open the extension options page by right
clicking on the toolbar icon and selecting “Options” (Figure 3-10).
62
Chapter 3 Browser Extension Crash Course
63
Chapter 3 Browser Extension Crash Course
extension-crash-course/
├─ manifest.json
├─ background.js
├─ popup/
│ ├─ popup.html
│ ├─ popup.css
│ └─ popup.js
├─ options/
64
Chapter 3 Browser Extension Crash Course
│ ├─ options.html
│ ├─ options.css
│ └─ options.js
└─ content-scripts/
├─ content-script.css
└─ content-script.js
The content scripts will add a styled custom container into the page:
content-scripts/content-script.css
#container {
position: absolute;
background-color: gray;
color: white;
padding: 2rem;
top: 0;
left: 0;
}
content-scripts/content-script.js
document.body.innerHTML += `
<div id="container">
<h1>This is the content script!</h1>
<button id="btn">Send content script message</button>
</div>
`;
document.querySelector("#btn").
addEventListener('click', () => {
chrome.runtime.sendMessage({ text: "Content script" });
});
65
Chapter 3 Browser Extension Crash Course
chrome.runtime.onMessage.addListener((msg) => {
document.querySelector('#container').innerHTML +=
`<div>${msg.text}</div>`;
});
Configure the manifest to inject these content scripts into all valid
web pages:
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup/popup.html"
},
"options_page": "options/options.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": ["content-scripts/content-script.css"],
"js": ["content-scripts/content-script.js"]
}
]
}
66
Chapter 3 Browser Extension Crash Course
In this state, you will be able to click the content script button and have
the messages show up in the extension views, such as on the options page.
67
Chapter 3 Browser Extension Crash Course
popup/popup.js
document.querySelector("#btn").
addEventListener('click', () => {
chrome.runtime.sendMessage({ text: "Popup" });
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
chrome.tabs.sendMessage(
tabs[0]?.id,
{ text: "Popup" }
);
});
});
chrome.runtime.onMessage.addListener((msg) => {
document.body.innerHTML += `<div>${msg.text}</div>`;
});
After reloading the extension, you should now see that messages sent
from the popup page’s button will show up in the content script view, but
only on the active tab.
68
Chapter 3 Browser Extension Crash Course
extension-crash-course/
├─ manifest.json
├─ background.js
├─ popup/
│ ├─ popup.html
│ ├─ popup.css
│ └─ popup.js
├─ options/
│ ├─ options.html
│ ├─ options.css
│ └─ options.js
├─ content-scripts/
│ ├─ content-script.css
│ └─ content-script.js
└─ devtools/
├─ devtools.html
├─ devtools.js
├─ devtools_panel.html
└─ devtools_panel.js
69
Chapter 3 Browser Extension Crash Course
devtools/devtools.html
<!DOCTYPE html>
<html>
<body>
<script src="devtools.js"></script>
</body>
</html>
devtools/devtools.js
chrome.devtools.panels.create(
"Devtools Panel",
"",
"/devtools/devtools_panel.html"
);
Next, configure the devtools panel to log the URL of all outgoing
network activity from the page using the Devtools API:
devtools/devtools_panel.html
<!DOCTYPE html>
<html>
<body>
<h1>This is the devtools panel!</h1>
<script src="devtools_panel.js"></script>
</body>
</html>
devtools/devtools_panel.js
70
Chapter 3 Browser Extension Crash Course
chrome.devtools.network.onRequestFinished.addListener(
(request) => {
document.body.innerHTML +=
`<div>${request.request.url}</div>`;
}
);
manifest.json
{
"name": "Extension Crash Course",
"description": "Browser extension created from scratch",
"version": "1.0",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {
"default_popup": "popup/popup.html"
},
"options_page": "options/options.html",
"devtools_page": "devtools/devtools.html",
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": ["content-scripts/content-script.css"],
"js": ["content-scripts/content-script.js"]
}
]
}
71
Chapter 3 Browser Extension Crash Course
You’ll need to reload the extension as well as close and reopen any
open developer tools windows to see the changes. Visiting any web page
will log its traffic (Figure 3-13).
Summary
In this chapter, you were guided through the process of creating a simple
Chrome extension. The purpose of this crash course was to provide step-
by-step instructions on how to create an extension that touched all the
different user interface elements that browser extensions can use. After
completing the crash course, you should have a good understanding of
how various user interfaces are produced from their source code, as well as
how a manifest should be laid out to achieve these ends.
72
Chapter 3 Browser Extension Crash Course
73
CHAPTER 4
Browser Extension
Architecture
Understanding how the various elements of browser extensions work
is important, but even more important is understanding how they
work together. The architecture of browser extensions is unusual and
multifaceted:
Architecture Overview
The browser extension architecture can be more easily understood
visually. Consider the following diagram displaying how all the pieces of
browser extensions fit together (Figure 4-1).
76
Chapter 4 Browser Extension Architecture
77
Chapter 4 Browser Extension Architecture
78
Chapter 4 Browser Extension Architecture
79
Chapter 4 Browser Extension Architecture
80
Chapter 4 Browser Extension Architecture
browser detects the service worker is idle and restarted on demand (e.g.,
when the browser detects an incoming event with a handler). The browser
will restart the service worker when the extension is updated.
Note For more details on how popup and options pages behave,
refer to the Popup and Options Pages chapter.
81
Chapter 4 Browser Extension Architecture
Devtools Pages
The devtools page is rendered exactly once each time the browser’s
devtools interface is opened. Therefore, because there can be only one
developer tools interface per window, there can be exactly one devtools
page per window. This page and the child pages it creates, consisting of
panels and sidebars, will persist as long as the developer tools interface is
opened. Importantly, these developer tools pages will not be affected when
the extension is updated and so they are susceptible to becoming stale.
Note For more details on how devtools pages behave, refer to the
Devtools Pages chapter.
Content Scripts
Content scripts will be injected into web pages in a manner defined by the
extension manifest. Furthermore, multiple content scripts can be injected
into a single web page. For an extension with M content scripts defined,
and a browser with N tabs open, the total number of content scripts
running at any given time is bounded by M x N. Content scripts can be
injected into the page upon different pageload events, but all of these are
approximately when the page is initially loaded. They behave identically to
regular web page scripts and will execute in the same fashion. Importantly,
content scripts will not be affected when the extension is updated and so
they are susceptible to becoming stale.
Note For more details on how content scripts behave, refer to the
Content Scripts chapter.
82
Chapter 4 Browser Extension Architecture
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.js"]
}
],
"web_accessible_resources": [
{
"resources": ["fetch-page.js"],
"matches": ["<all_urls>"]
}
]
}
83
Chapter 4 Browser Extension Architecture
import "./fetch-page.js";
console.log("background.js");
console.log("content-script.js");
import(chrome.runtime.getURL("fetch-page.js"));
const el = document.createElement("script");
el.src = chrome.runtime.getURL("fetch-page.js");
document.body.appendChild(el);
console.log("fetch-page.js");
fetch(chrome.runtime.getURL("extra.html"));
<!DOCTYPE html>
<html>
<body>
<h1>Extra Page</h1>
<script src="fetch-page.js"></script>
</body>
</html>
84
Chapter 4 Browser Extension Architecture
To understand how the extension file server works, we’ll unpack this
extension one file at a time. Begin by loading the extension in Google
Chrome and inspecting the background service worker console output by
clicking the service worker link in the extension card (Figure 4-2).
85
Chapter 4 Browser Extension Architecture
Figure 4-2. Clicking the service worker link will open the background
console output
86
Chapter 4 Browser Extension Architecture
87
Chapter 4 Browser Extension Architecture
Reload the extension to trigger these network requests. You will see
two successful network requests: one for the imported JS file and one for
the fetched HTML file. Inspecting the JS request reveals the following
(Figure 4-5).
88
Chapter 4 Browser Extension Architecture
• pmnbhnikfammdjefkbgngjgojkbgkdnk is the
extension ID. Your extension’s ID will be different.
This ID is used to uniquely identify this instance of
the extension both inside the browser as well as when
published in the extension marketplace. The ID format
89
Chapter 4 Browser Extension Architecture
Compare the two requests and note the presence or absence of the
Access-Control-Allow-Origin and Cross-Origin-Resource-Policy
headers. These are automatically added because fetch-page.js is listed
under web_accessible_resources. Compare this to the request for extra.
html: you will notice these headers are absent, as extra.html is not listed
as a web accessible resource.
90
Chapter 4 Browser Extension Architecture
Next, let’s copy the URL of the extra.html request (yours will differ
from the screenshot above) and open it in a new browser tab. Open the
developer tools once the page is loaded (Figure 4-7).
91
Chapter 4 Browser Extension Architecture
92
Chapter 4 Browser Extension Architecture
Figure 4-8. The console output from the extension’s content scripts
First off, note that the content script is not being served via network
request. Rather than being loaded from the extension file server, the
browser is injecting it into the page directly.
Observe that the content script is attempting to fetch the JS file in two
different ways, and each throws an error for a different reason:
93
Chapter 4 Browser Extension Architecture
Although content scripts still can access the extension file server, its
access is considerably more restricted.
Sandboxed Pages
In manifest v3, the values allowed in the Content Security Policy (CSP) is
considerably more limited when compared to v2. It newly disallows the
following in extension pages:
• Inline scripts
• eval()
• User-provided scripts
94
Chapter 4 Browser Extension Architecture
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "popup.html"
},
"sandbox": {
"pages": ["popup.html"]
}
}
<!DOCTYPE html>
<html>
<body>
<h1>Popup</h1>
<script>
eval(`document.body.innerHTML += '<div>Foobar</div>'`);
95
Chapter 4 Browser Extension Architecture
</script>
</body>
</html>
Summary
In this chapter, you were presented with an overview of how all the
elements of a browser extension work in concert. With this knowledge, you
should now be able to analyze how a given extension moves information
around and makes API calls. This chapter also equips you to more
effectively be able to plan for how to take an idea for a browser extension
and turn it into actual code. You should also have a good understanding of
how the browser extension file server can deliver files in various ways, as
well as the tradeoffs involved when declaring a sandboxed page.
The next chapter will cover all the fields that can appear in an
extension manifest as well as how each field’s definition can control the
behavior of an extension.
96
CHAPTER 5
The Extension
Manifest
The extension manifest is the blueprint of the browser extension. Broadly
speaking, it is a config file containing a collection of key-value pairs that
dictate what the extension can do and in what fashion.
The content of a manifest differs between manifest versions and
browsers. Not all properties can be used by all browsers; some browsers
will partially support a property, or even not support it at all. Between
manifest v2 to manifest v3, new properties are added, others are removed;
and some change in how they are defined.
MDN offers a very good table of manifest property support here:
https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/
WebExtensions/manifest.json#browser_compatibility.
simple-extension-directory/
└─ manifest.json
Note This file is a standard JSON file, with the notable exception
that //-style comments are allowed. If your text editor is using a
standard JSON linter, it may indicate that comments are not allowed
in manifest.json – ignore this error. All browsers and extension
stores allow comments in the manifest.
The JSON file contains a single object with a large number of required
and optional properties that configure how the extension behaves. There
are only three required properties:
98
Chapter 5 The Extension Manifest
manifest.json
{
"name": "MVP Extension",
"version": "1.0",
"manifest_version": 3
}
localized-extension-directory/
├─ manifest.json
└─ _locales/
├─ en/
│ └─ messages.json
└─ fr/
└─ messages.json
99
Chapter 5 The Extension Manifest
manifest.json
{
"name": "__MSG_extensionName__",
"version": "1.0",
"manifest_version": 3,
"default_locale": "en",
}
_locales/en/messages.json
{
"extensionName": {
"message": "Hello world!"
}
}
_locales/fr/messages.json
{
"extensionName": {
"message": "Bonjour le monde!"
}
}
100
Chapter 5 The Extension Manifest
Note The WebExtensions i18n API and extension CSS files can also
use locale strings from these messages.json files. Refer to the
Extension and Browser APIs chapter for details.
URL Globs
Properties such as content_scripts can use globs to add additional
filtering to URL match patterns. Globs are an extension of URL match
patterns; their syntax allows for the use of * wildcards to match zero to
many characters, as well as ? to match exactly one character:
102
Chapter 5 The Extension Manifest
Manifest Properties
This section contains a comprehensive list of manifest properties and
how their values should be defined. Many of these properties are linked to
additional browser permissions or parts of the WebExtensions API; each
section will direct you to the portion of the book detailing the interplay of
the manifest property value, required permissions, and the involved APIs.
If you wish to inspect the browser source code involved with parsing
manifest properties, use the following resources:
103
Chapter 5 The Extension Manifest
action
This property defines an object containing values which dictate the
behavior and appearance of the extension icon in the browser toolbar
(Figure 5-1). This is the manifest v3 replacement for the browser_action
and page_action properties in manifest v2.
Figure 5-1. The extension toolbar icon with hover title revealed
Note This property can be only used in manifest v3. If writing for
manifest v2, use browser_action.
104
Chapter 5 The Extension Manifest
The property with is shown below with a fully defined example value:
{
...
"action": {
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"64": "icons/icon-64.png"
},
"default_popup": "popup/popup.html",
"default_title": "Building Browser Extensions",
"browser_style": true,
"theme_icons": [
{
"light": "icons/icon-16-light.png",
"dark": "icons/icon-16.png",
"size": 16
},
{
"light": "icons/icon-32-light.png",
"dark": "icons/icon-32.png",
"size": 32
}
]
},
...
}
105
Chapter 5 The Extension Manifest
default_icon
This property tells the browser where to locate icons for the toolbar via relative
paths to image files. There are two ways to define this property: either
provide PNG or JPEG image icons and allow the browser to intelligently
select which icon it would prefer, or provide a single vector image.
Providing one or multiple image icons allows the browser to decide
which icon is best for the current hardware configuration. (For example, an
Apple Retina display prefers an icon with twice the resolution.) Providing
icons of 16x16px, 32x32px, and 64x64px is sufficient to cover all possible
cases. The following is an example of how this property can be used:
{
...
"action": {
"default_icon": {
"16": "icons/icon-16.png",
"32": "icons/icon-32.png",
"64": "icons/icon-64.png"
},
...
},
...
}
If you wish to provide a vector instead, the browser only needs a single
vector file to account for all rendering configurations:
{
...
"action": {
106
Chapter 5 The Extension Manifest
"default_icon": "icons/icon.svg"
...
},
...
}
default_popup
This property tells the browser where to locate the HTML file that should
render as a popup when the extension’s toolbar icon is clicked. This
can also be set or changed via the WebExtension API’s chrome.action.
setPopup() method. The following is an example of how this property can
be used:
{
...
"action": {
"default_popup": "components/popup/popup.html"
...
},
...
}
107
Chapter 5 The Extension Manifest
default_title
This property tells the browser the text that should render as a tooltip
when the extension’s toolbar icon is hovered over. This can also be set or
changed via the WebExtension API’s chrome.action.setTitle() method.
The following is an example of how this property can be used:
{
...
"action": {
"default_title": "Open Explorer menu"
...
},
...
}
Note If this property is not defined, the browser will use the
extension name property as the hover text.
browser_style
(Firefox only) This property is a boolean that tells the browser if it should
inject a stylesheet in the popup to style it consistently with the browser.
These stylesheets can be viewed in any Firefox browser at chrome://
browser/content/extension.css, or chrome://browser/content/
extension-mac.css on macOS. The property defaults to false. The
following is an example of how this property can be used:
Example value for browser_style
108
Chapter 5 The Extension Manifest
{
...
"action": {
"browser_style": true
...
},
...
}
theme_icons
(Firefox only) This property allows you to define alternate icons based
on the current active Firefox theme. It effectively extends the behavior
of default_icon. The following is an example of how this property can
be used:
{
...
"action": {
...,
"theme_icons": [
{
"light": "icons/icon-16-light.png",
"dark": "icons/icon-16.png",
"size": 16
},
{
"light": "icons/icon-32-light.png",
"dark": "icons/icon-32.png",
"size": 32
}
109
Chapter 5 The Extension Manifest
],
...
},
...
}
author
This property provides an author name for display in the browser. The
following is an example of how this property can be used:
{
...
"author": "Matt Frisbie"
...
}
automation
(Chromium browsers only) This property allows developers to access the
accessibility tree for the browser. This property is only needed in cases
where the extension is directly targeting assistive devices. Per MDN,
“Browsers create an accessibility tree based on the DOM tree, which is
used by platform-specific Accessibility APIs to provide a representation
that can be understood by assistive technologies, such as screen readers.”
110
Chapter 5 The Extension Manifest
background
This property allows you to define what file or files will be used for the
background script. This property is used in both manifest v2 and v3, but the
value of the property has significant differences between the two versions.
{
...
"background": {
"scripts": [
"scripts/background-1.js",
"scripts/background-2.js"
]
},
...
}
111
Chapter 5 The Extension Manifest
{
...
"background": {
"page": "background.html"
},
...
}
This strategy has several advantages. It allows the user to use import
statements by adding type="module", as well as to add content to the
HTML headless page.
112
Chapter 5 The Extension Manifest
{
...
"background": {
"page": "background.html",
"persistent": true
},
...
}
{
...
"background": {
"service_worker": "scripts/background.js"
113
Chapter 5 The Extension Manifest
},
...
}
{
...
"background": {
"service_worker": "scripts/background.js",
"type": "module",
},
...
}
browser_action
(Manifest v2 only) The browser_action property was replaced by action
in manifest v3. The property values and behaviors are effectively identical.
If using manifest v2, refer to the action property for details on how to
assign a value for this property.
114
Chapter 5 The Extension Manifest
browser_specific_settings
This property defines values that are relevant only to certain browsers.
It is rare to have the need to use this property at all. The property takes
the form of an object with keys that allocate values to particular browser
vendors. Common keys are edge, gecko, and safari, which correspond to
browsers matching those vendors/engines. The following is an example of
how this property can be used:
{
...
"browser_specific_settings": {
"edge": {
"browser_action_next_to_addressbar": false
},
"gecko": {
"id": "[email protected]",
"strict_min_version": "51.0",
"strict_max_version": "62.*",
"update_url": "https://example.com/updates.json"
},
115
Chapter 5 The Extension Manifest
"safari": {
"strict_min_version": "21",
"strict_max_version": "28"
}
},
...
}
chrome_settings_overrides
This property allows the extension to override the default settings for
certain browser interfaces. Its value is an object with the following optional
properties:
116
Chapter 5 The Extension Manifest
The browser will indicate that these settings are applied in the
extension management interface (Figure 5-2).
Custom Homepage
The browser homepage is the URL that the browser directs the user to
when they click the “home” button in their browser (Figure 5-3). All
major browsers no longer display a home button by default, but it can be
easily enabled in the browser’s settings menu. The homepage URL can be
overridden as follows:
117
Chapter 5 The Extension Manifest
{
...
"chrome_settings_overrides": {
"homepage": "https://www.buildingbrowserextensions.com"
},
...
}
118
Chapter 5 The Extension Manifest
119
Chapter 5 The Extension Manifest
Figure 5-6. Typing in a search query into the address bar shows it
being routed to the custom search engine
120
Chapter 5 The Extension Manifest
{
...
"chrome_settings_overrides": {
"search_provider": {
"name": "Brave Search Engine",
121
Chapter 5 The Extension Manifest
"search_url": "https://search.brave.com/
search?q={searchTerms}",
"encoding": "UTF-8",
"is_default": true
}
} ...
}
• alternate_urls
• favicon_url
• image_url
• image_url_post_params
• instant_url
• instant_url_post_params
• keyword
• prepopulated_id
• search_url_post_params
• suggest_url
• suggest_url_post_params
122
Chapter 5 The Extension Manifest
{
...
"chrome_settings_overrides": {
"startup_pages": [
https://www.buildingbrowserextensions.com
]
123
Chapter 5 The Extension Manifest
},
...
}
chrome_url_overrides
This property allows the extension to override the default page for certain
browser interfaces. Each extension may override exactly one of the
following browser pages:
• History page
• Bookmarks page
{
...
"chrome_url_overrides": {
"newtab": "components/newtab/newtab.html"
},
...
}
124
Chapter 5 The Extension Manifest
commands
This property is used to map keyboard commands to perform various
tasks in your extension. An example of this is opening and closing the
extension popup when the user presses Ctrl+Shift+F. Whereas mapping
multi-key commands in JavaScript involves setting listeners for keydown or
similar events, the commands property allows you to natively map multi-key
keyboard commands using a simple and intuitive syntax. You can define
two types of commands: shortcuts, which associate a keyboard command
with a predefined set of native browser behaviors, or custom commands,
which associate a keyboard command with a special event that can be
handled via the WebExtensions API.
Command Syntax
All commands have the same syntax. Each takes the form of an object
with two properties: a suggested_key object, which defines the multi-key
shortcuts that should invoke the command, and a description string,
which will appear in the extension keyboard shortcut management UI. The
following is an example of how to define a shortcut for a generic foobar
command:
{
...
"commands": {
125
Chapter 5 The Extension Manifest
"foobar": {
"suggested_key": {
"default": "Ctrl+Shift+F"
},
"description": "Run 'foobar' on the current page."
}, ...
}
Once the extension is loaded, the browser will reflect this shortcut in
its keyboard shortcut management UI (Figure 5-9).
126
Chapter 5 The Extension Manifest
127
Chapter 5 The Extension Manifest
• Ctrl+Shift+F
• Alt+Q
• MacCtrl+Shift+F12
• Alt+MediaPlayPause
Multi-browser Support
The suggested_key object at minimum should define a default key
shortcut. To support different operating systems, the following properties
can additionally be defined:
• mac
• linux
• windows
• chromeos
• android
• ios
If these values are provided and match the host OS, the browser will
automatically select them instead of the default value.
Reserved Commands
The browser will give some command names special treatment:
128
Chapter 5 The Extension Manifest
The following command would open the popup when the user uses
the Ctrl+Shift+F shortcut:
{
...
"commands": {
"_execute_action": {
"suggested_key": {
"default": "Ctrl+Shift+F"
}
}
},
...
}
Custom Commands
For all command identifiers that do not match the reserved commands,
the key shortcut will dispatch a command event to all listeners of command.
onCommand().
129
Chapter 5 The Extension Manifest
{
...
"commands": {
"foobar": {
"suggested_key": {
"default": "Ctrl+Shift+F"
},
"description": "Run 'foobar' on the current page."
}
},
...
}
chrome.commands.onCommand.addListener((command) => {
console.log(`Command: ${command}`); // Command: foobar
});
Global Commands
By default, when the browser is not focused on the device, keyboard
shortcuts will not dispatch any extension commands. Chromium browsers
support an optional global property for commands which allows
commands which use only shortcuts matching Ctrl+Shift+[0..9] to be
triggered even when the browser does not have focus. The following is an
example of this:
{
...
"commands": {
"foobar": {
130
Chapter 5 The Extension Manifest
"suggested_key": {
"default": "Ctrl+Shift+0"
},
"description": "Run 'foobar' on the current page.",
"global": true
}, ...
}
content_capabilities
This property is obsolete and should not be used. Before the release of the
Clipboard API and the Persistent Storage API, it was useful for granting
extensions the ability to access the clipboard or unlimited storage. With
access to these new APIs, this property is no longer needed.
content_scripts
This property defines which files should be injected as content scripts,
where to inject them, and in what fashion. Its value is an array of objects,
each of which defines a separate set of content scripts and rules for those
scripts. Each object contains the following:
131
Chapter 5 The Extension Manifest
132
Chapter 5 The Extension Manifest
{
...
"content_scripts": [
{
"matches": [
"*://example.com/*"
],
"include_globs": [
"*archive*"
],
"exclude_matches": [
"*://example.com/experimental/*"
],
"exclude_globs": [
"*?display=legacy*"
],
"js": [
"scripts/content-script.js"
],
"css": [
"styles/content-script.css"
],
"run_at": "document_end",
133
Chapter 5 The Extension Manifest
"match_about_blank": true,
"all_frames": true
}
],
...
}
content_security_policy
This property defines the content security policies (CSP) of the extension.
This property was significantly altered between manifest v2 and v3,
including in structure and in allowed values.
{
...
134
Chapter 5 The Extension Manifest
{
...
"content_security_policy": {
"extension_pages": "script-src 'self'",
"sandbox": "script-src 'self' https://example.com; object-
src 'self'"
},
...
}
135
Chapter 5 The Extension Manifest
converted_from_user_script
This property was used as part of Google’s supported transition from
userscripts in 2016. Its purpose was so that Chrome Apps, which shared
APIs with web extensions, could direct their users to a replacement
Progressive Web Application via a installReplacementWebApp() method.
No modern extensions will have a use for this property.
cross_origin_embedder_policy
(Chromium browsers only, manifest v3 only) This property defines the
Cross-Origin-Embedder-Policy (COEP) header value for requests to the
extension’s origin. Because the extension behaves in many ways like a web
server, it can be vulnerable to the same origin-based security issues that
web servers are. If you wish to enable features that are only accessible with
cross-origin isolation, this property is used to set the COEP header value
to behave in such a manner. This property must be set in conjunction
with the cross_origin_opener_policy property to enable cross-origin
isolation.
136
Chapter 5 The Extension Manifest
{
...
"cross_origin_embedder_policy": {
"value": "require-corp"
},
...
}
cross_origin_opener_policy
(Chromium browsers only, manifest v3 only) This property defines the
Cross-Origin-Opener-Policy (COOP) header value for requests to the
extension’s origin. Because the extension behaves in many ways like a web
server, it can be vulnerable to the same origin-based security issues that
web servers are. If you wish to enable features that are only accessible with
cross-origin isolation, this property is used to set the COOP header value
to behave in such a manner. This property must be set in conjunction
with the cross_origin_embedder_policy property to enable cross-origin
isolation.
137
Chapter 5 The Extension Manifest
{
...
"cross_origin_opener_policy": {
"value": "same-origin"
},
...
}
declarative_net_request
(Manifest v3 and Chromium browsers only) This property defines the
rulesets to be used for the Declarative Net Request API. The value is an
object with a single rule_resources key, which defines an array of all the
ruleset objects provided in the extension. Each ruleset object must define a
unique id string, an enabled boolean controlling if the browser should use
the ruleset, and path string with relative path to the ruleset JSON file. The
following is an example of how this property can be used:
{
...
138
Chapter 5 The Extension Manifest
"declarative_net_request": {
"rule_resources" : [
{
"id": "ruleset_1",
"enabled": true,
"path": "ruleset_1.json"
}, {
"id": "ruleset_2",
"enabled": false,
"path": "ruleset_1.json"
}
]
},
...
}
default_locale
This property defines the default locale string. The property must be
present if and only if there is a _locales directory as described in the
locales section earlier in this chapter. The following is an example of how
this property can be used:
Example value for default_locale
{
...
139
Chapter 5 The Extension Manifest
"default_locale": "en",
...
}
description
This property defines the formal description of the extension. This value
is displayed in the browser as well as in the extension marketplaces. The
following is an example of how this property can be used:
{
...
"description": "A collection of browser extension examples",
...
}
developer
This property defines an object containing an author name and/or extension
homepage URL for display in the browser. Both the name and url properties
are optional. The following is an example of how this property can be used:
{
...
"developer": {
"name": "Matt Frisbie",
"url": "https://www.buildingbrowserextensions.com"
},
...
}
140
Chapter 5 The Extension Manifest
devtools_page
This property tells the browser where to locate the HTML file that should
be used as the entrypoint to your extension’s developer tools content.
This page is used to bootstrap the developer utilities, but it renders as
a headless page. The following is an example of how this property can
be used:
{
...
"devtools_page": "components/devtools/devtools.html",
...
}
differential_fingerprint
This property is an internal key used by the Chrome Web Store for
extension update distribution. You should not set this property in any
circumstance.
141
Chapter 5 The Extension Manifest
Note The Chrome Web Store generates the property when sending
out a differential extension update. The property uniquely identifies
only the files that changed in the new version of that extension. The
key is automatically stripped out when the extension is installed.
event_rules
(Chromium browsers only) This property is deprecated; it defines rules
that can modify in-flight network requests using declarativeWebRequest
or take actions depending on the page content – all without requiring
permission to read the page’s content using declarativeContent.
declarativeWebRequest is deprecated in favor of declarativeNetRequest,
so this property should not be used.
externally_connectable
This property defines which foreign extensions or web pages can interact
with this extension via runtime.connect() or runtime.sendMessage().
If not defined, the default behavior is to allow all foreign extensions to
communicate via these methods, but to disallow all web pages from
communicating via these methods. The property value is an object which
can define the following:
142
Chapter 5 The Extension Manifest
{
...
"externally_connectable": {
"ids": [
"allowmeabcdefabcdefabcdefabcdefa"
],
"matches": [
"https://*.example.com/*"
],
"accepts_tls_channel_id": true
},
...
}
file_browser_handlers
(Manifest v3 and Chrome/Chrome OS only) This property defines an array
containing file browser handler objects that can extend the Chrome OS file
browser. This is only applicable on Chrome OS devices. The following is an
example of how this property can be used:
{
...
"file_browser_handlers": [
{
143
Chapter 5 The Extension Manifest
"id": "upload",
"default_title": "Save File",
"file_filters": [
"filesystem:*.*",
]
}
],
...
}
Note The Chrome OS file browser handler is out of the scope of this
book. For documentation, refer to https://developer.chrome.
com/docs/extensions/reference/fileBrowserHandler/.
file_system_provider_capabilities
(Manifest v3 and Chrome/Chrome OS only) This property defines an
object containing properties that govern how the extension can interact
with the host device’s filesystem via the File System Provider API. This is
only applicable on Chrome OS devices. The following is an example of how
this property can be used:
{
...
"file_system_provider_capabilities": {
"configurable": true,
"watchable": false,
"multiple_mounts": true,
144
Chapter 5 The Extension Manifest
"source": "network"
},
...
}
homepage_url
This property provides an extension homepage URL for display in the
browser. The following is an example of how this property can be used:
{
...
"homepage_url": "https://www.buildingbrowserextensions.com"
...
}
145
Chapter 5 The Extension Manifest
host_permissions
(Manifest v3 only) This property defines the host match patterns that the
extension requires to run. If the web page matches one or more patterns in
this list, the extension will have the ability to read or modify host data, such
as cookies, webRequest, and tabs.executeScript. The following is an
example of how this property can be used:
{
...
"host_permissions": [
"*://developer.mozilla.org/*",
"*://developer.chrome.com/*"
],
...
}
icons
This property defines an object containing values which dictate the
extension’s primary icon. This icon is used both during installation as well
as in various extension marketplaces. At minimum, you should define
146
Chapter 5 The Extension Manifest
a 128x128 raster image (JPEG, PNG, BMP, or ICO image). If you wish
to support all marketplaces and browsers, you should define four PNG
images with dimensions 16x16, 32x32, 48x48, and 128x128.
{
...
"icons": {
"16": "assets/icons/icon-16.png",
"32": " assets/icons/icon-32.png",
"48": " assets/icons/icon-48.png",
"128": " assets/icons/icon-128.png"
},
...
}
incognito
This property allows you to define how the extension can interact with private
browsing windows. The following is an example of how this property can be used:
{
...
"incognito": "spanning"
...
}
147
Chapter 5 The Extension Manifest
key
(Chromium browsers only) This property explicitly defines for the browser
what the 32-character extension ID should be. If key is not provided, the
browser will automatically generate it for you.
{
...
"key": "hdokiejnpivrijdhajhdlcegeplioahd"
...
}
148
Chapter 5 The Extension Manifest
Note This is only needed in the rare circumstance where you wish
to explicitly define the extension ID for local development at load
time. This value is not used when deploying to a web store.
manifest_version
This property defines which indicates to the browser how the manifest
should be interpreted. As described earlier in the chapter, this integer
has significant implications for how the overall manifest file must be
structured. The following is an example of how this property can be used:
{
...
"manifest_version": 3
...
}
Note Some browsers like Firefox are still in the process of rolling
out support for manifest v3, but other browsers like Chrome are
actively phasing out support for manifest v2. Your application will
need to generate multiple manifest.json files to support multiple
marketplaces.
149
Chapter 5 The Extension Manifest
minimum_chrome_version
(Chromium browsers only) This property defines the minimum version
of Chrome required for the extension. Non-Chromium browsers will
ignore this property. The following is an example of how this property can
be used:
{
...
"minimum_chrome_version": "90"
...
}
nacl_modules
This property was used for Native Client support (NaCl), its use is
deprecated.
name
This property defines the formal name of the extension. This value is
displayed in the browser as well as in the extension marketplaces. This
name should not exceed 45 characters. The following is an example of how
this property can be used:
150
Chapter 5 The Extension Manifest
{
...
"name": "Building Browser Extensions",
...
}
oauth2
(Chromium browsers only) This property is used to register your extension
as an OAuth2 client. Its value is an object with the OAuth2 client ID
and scopes:
{
...
"oauth2": {
"client_id": "oAuthClientID.apps.googleusercontent.com",
"scopes": [
"https://www.googleapis.com/auth/contacts.readonly"
],
},
...
}
151
Chapter 5 The Extension Manifest
offline_enabled
This property is deprecated and should not be used. In legacy codebases, it
indicated if the extension was expected to work offline.
omnibox
This property enables the browser’s native omnibox interface by defining
its entry keyword. The value is an object containing a single keyword
property. The following is an example of how this property can be used:
{
...
"omnibox": {
"keyword": "bbx",
},
...
}
152
Chapter 5 The Extension Manifest
Note The extension can control the omnibox behavior with the
Omnibox API. This is covered in depth in the Extension and Browser
APIs chapter.
optional_host_permissions
(Manifest v3 and Chromium only) This property defines the host match patterns
that the extension does not explicitly require to run, but that the user can opt
into. The match pattern behavior and syntax are identical to host_permissions.
The permission granting user interface is identical to optional_permissions.
optional_permissions
This property defines the permissions that the extension does not require
to run correctly, but that it can prompt the user for access at runtime. This
property is often used to add extension permissions in subsequent releases
without requiring that all users grant access to a new permission. Not all
permission types are eligible to appear in this array. The following is an
example of how this property can be used:
153
Chapter 5 The Extension Manifest
{
...
"optional_permissions": [
"cookies",
"history",
"notifications"
],
...
}
options_page
This is a deprecated property. Use options_ui instead.
options_ui
This property tells the browser where to locate the HTML file that should
render as the options page when opened either programmatically, by URL,
or via the toolbar context menu. It is an object that typically only contains
a single page property, but also supports additional properties in some
browsers. The following is an example of how this property can be used:
{
...
154
Chapter 5 The Extension Manifest
"options_ui": {
"page": "components/options/options.html"
},
...
}
browser_style
(Firefox only) This property is a boolean that tells the browser if it should
inject a stylesheet in the options page to style it consistently with the
browser. These stylesheets can be viewed in any Firefox browser at
chrome://browser/content/extension.css, or chrome://browser/
content/extension-mac.css on macOS. The property defaults to false.
The following is an example of how this property can be used:
{
...
"options_ui": {
"page": "components/options/options.html",
"browser_style": true,
},
...
}
open_in_tab
(Firefox only) This property is a boolean that tells the browser if it
should open the options page in a normal browser tab, rather than in an
embedded options page (Figure 5-11). The property defaults to true.
155
Chapter 5 The Extension Manifest
{
...
"options_ui": {
"page": "components/options/options.html",
"open_in_tab": false,
},
...
}
156
Chapter 5 The Extension Manifest
page_action
(Manifest v2 only) The page_action property was replaced by action in
manifest v3. The property values and behaviors are effectively identical. If
using manifest v2, refer to the action property for details on how to assign
a value for this property.
permissions
This property defines the permissions that the extension requires to run
correctly. All permission types are eligible to appear in this array. Upon
installation and updates, all additions to this array will require that users
explicitly grant access to the newly added permission in a popup dialog.
The following is an example of how this property can be used:
{
...
"permissions": [
"activeTab",
"declarativeNetRequest",
"geolocation"
],
...
}
157
Chapter 5 The Extension Manifest
platforms
This property was used for Native Client support (NaCl), its use is deprecated.
replacement_web_app
This property was used as part of Google’s supported transition from
Chrome Apps in 2016. Its purpose was so that Chrome Apps, which
shared APIs with web extensions, could direct their users to a replacement
Progressive Web Application via a installReplacementWebApp() method.
No modern extensions will have a use for this property.
158
Chapter 5 The Extension Manifest
requirements
(Chromium browsers only) This property defines the browser technologies
required by the extension. If a user’s device does not meet the
requirements, the Chrome Web Store will inform them that their device
lacks the needed technology to run the extension. Currently, the only
actively supported property is “3D.” The following is an example of how
this property can be used:
{
...
"requirements": {
"3D": {
"features": [
"webgl"
]
}
},
...
}
sandbox
(Chromium browsers only) This property defines which pages should
render in sandbox mode. Its value is an object with a single page array
containing paths to each file that should render in sandbox mode. The
following is an example of how this property can be used:
{
...
159
Chapter 5 The Extension Manifest
"sandbox": {
"pages": [
"components/popup/popup.html",
"components/options/options.html",
]
},
...
}
short_name
This property defines the secondary name of the extension that will be
used in contexts where the name property is too long. This name should
not exceed 12 characters. If this is not provided, the browser will simply
truncate the name property. The following is an example of how this
property can be used:
{
...
"short_name": "BBX",
...
}
160
Chapter 5 The Extension Manifest
storage
This property specifies the schema file for managed storage, which is
involved with the storage.managed API. The following is an example of
how this property can be used:
{
...
"storage": {
"managed_schema": "schema.json"
},
...
}
system_indicator
This property is deprecated and should not be used.
Note Refer to https://bugs.chromium.org/p/chromium/
issues/detail?id=142450 for a history of this property.
161
Chapter 5 The Extension Manifest
tts_engine
(Chromium browsers only) This property is used to declare all the voices
and voice configurations the extension wishes to use for the browser’s text-
to-speech engine. The following is an example of how this property can
be used:
{
...
"tts_engine": {
"voices": [
{
"voice_name": "Alice",
"lang": "en-US",
"event_types": ["start", "marker", "end"]
},
{
"voice_name": "Pat",
"lang": "en-US",
"event_types": ["end"]
}
]
},
...
}
162
Chapter 5 The Extension Manifest
update_url
(Chromium browsers only) This property defines the URL from which the
browser should request updates. It is only used for Chrome extensions that
are not hosted on the Chrome Web Store. The following is an example of
how this property can be used:
{
...
"update_url": "https://example.com/updates.xml",
...
}
version
This property defines the formal version number of the extension. It is
used to both uniquely identify different releases of your extension, as well
as to determine ordinality of those releases.
Browsers set different requirements for this value; for example, the
Chrome version validator is more restrictive than Firefox. For simplicity
and cross-browser compatibility, it is strongly recommended that you
conform to the semantic versioning standards described at https://
semver.org/. The following is an example of how this property can
be used:
163
Chapter 5 The Extension Manifest
{
...
"version": "1.5.0"
...
}
version_name
This property defines a descriptor for the extension version. It allows
you to add in handles like beta or rc1 that offer additional information
about the release but do not conform to the browser’s extension version
validators. The following is an example of how this property can be used:
{
...
"version_name": "1.5.0 beta"
...
}
web_accessible_resources
This property defines which files in the extension can be accessed outside
the extension, either the web page or another extension. Any files that
are part of the extension are eligible to be listed here, but this property is
164
Chapter 5 The Extension Manifest
intended to allow for JS, CSS, HTML, and image files in the extension to be
added into the page or a foreign extension. The property is used in both
manifest v2 and v3, but the manifest v3 specification adds extra controls
for managing an access control list for each resource.
M
anifest v2
The manifest v2 version of web_accessible_resources is very simple:
simply an array of pattern strings. If a resource matched this pattern string,
web pages or remote extensions were granted read access. The following is
an example of how this property can be used:
{
...
"web_accessible_resources": [
"scripts/widget.js",
"assets/images/*"
]
...
}
M
anifest v3
Manifest v3 extends this property to allow for control of which origins or
extensions have access to certain assets, as well as the option to enable
dynamic resource URLs. The new format is an array of objects with the
following properties:
• resources is the list of patterns that match files in
the extension. This is equivalent to the full web_
accessible_resources property in manifest v2. This
property is required
165
Chapter 5 The Extension Manifest
Summary
In this chapter, you were guided through what a manifest does and how to
create one. The chapter took you through all possible manifest properties,
what they do, how they should be defined, what APIs they work with, and
which browsers can use them. When you come across a manifest property,
you should be able to refer to the relevant section in this chapter to quickly
decipher what that property is doing.
The next chapter will cover the transition from manifest v2 to v3. It
explores the differences between the two versions and how it will affect the
world of browser extensions.
166
CHAPTER 6
Understanding
the Implications
of Manifest V3
The world of browser extensions is undergoing a major transformation.
Google Chrome is spearheading a transition that changes how extensions
work and what they are capable of doing, and the rest of the major
browsers are likely to follow suit. Some of these changes are controversial,
as they have significant implications for many very popular chrome
extensions. The changes are embodied within a refashioning in how the
manifest.json is written (by defining a new manifest_version 3); thus,
these changes are commonly referred to as “manifest v3.”
Note The transition is far from over, and many details of where the
ecosystem will end up remain to be finalized. This chapter will mostly
cover information and examples that are certain.
Security
Manifest v2 allowed extensions to execute JavaScript that was loaded from
remote URLs or provided by the user. This was judged to be problematic,
as a malicious script executing with access to extension APIs and
permissions could cause a lot of damage. To address this security hole,
manifest v3 restricts extensions to only be able to execute scripts which are
included in the extension package itself.
Performance
Several manifest v2 features had the potential to introduce performance
problems in the browser.
Migration to DeclarativeNetRequest
The manifest v2 webRequest API allowed developers to run JavaScript at
different points in the lifecycle of a network request. When used in the
extreme, extensions that managed all network traffic on all pages would
therefore execute blocking JavaScript for every single network request that
the browser makes.
Manifest v3 addresses this by moving the blocking scripts into a static
set of declarative rules for how to manage network traffic (block, redirect,
etc.). The browser loads these static rules and natively executes them
as needed, thereby eliminating the need to run extra JavaScript for each
network request.
168
Chapter 6 Understanding the Implications of Manifest V3
Revenue
It is no secret that many tech giants rely heavily on revenue from web
advertisements. Some of the most popular browser extensions are “ad
blockers,” extensions which are exceptionally adept at preventing users
169
Chapter 6 Understanding the Implications of Manifest V3
from seeing ads and eliminating any potential for ad revenue. Despite the
dominance of mobile web traffic, desktop web traffic continues to be a
significant slice of all web traffic – and a large percentage of this slice uses
ad blockers.
The companies that lose this ad revenue to ad blockers are the same
ones that support the browser extension APIs and browser extension
marketplaces that power these ad blockers. To resolve this conflict,
manifest v3 takes aim at the extension APIs that ad blockers rely upon. The
changes in manifest v3 don’t entirely wipe out the ability to block ads, but
it is significantly diminished.
Implications of Background
Service Workers
In manifest v2, background scripts could be defined as either a persistent
script or an event page. The persistent script was initialized and kept alive
for the entire time that the browser remained open. This allowed the
background script to run in parallel with the page, perform work in the top
level of the script, open long-lived network connections like websockets,
listen for incoming events from external sources, and perform tasks
at short intervals, all without the threat of the script being terminated.
Extensions that did not have a need for this persistent script state could
elect to instead run their background script as an event page. The browser
would run the background script to initialize listeners for browser events
and then suspend the background script when it was deemed to be idle.
When a browser event with a handler was fired, the event page would wake
up and run the handler.
Manifest v3 discards the duality of persistent scripts and event pages
in favor of service workers. In many ways, the background service worker
is analogous to an event page. Even so, background scripts in manifest v2
and manifest v3 have several crucial differences:
170
Chapter 6 Understanding the Implications of Manifest V3
DOM
In manifest v2, background scripts were created as a headless browser
page, including full access to a DOM. In manifest v3, the service worker
cannot access the DOM or any DOM APIs. However, it still has access to
the OffscreenCanvas API.
XMLHttpRequest
In manifest v2, background scripts were able to make network requests
using XMLHttpRequest. In manifest v3, because all background scripts are
service workers, network requests must be made via fetch().
Timer API
In manifest v2, a persistent script could use a timer API method like
setTimeout() or setInterval() in the top level of the script and have the
handler reliably execute. The following is an example of a timeout handler
which would reliably run in a manifest v2 extension with a persistent
background:
171
Chapter 6 Understanding the Implications of Manifest V3
In this reworked example, the service worker will wake up for the
alarm event. Therefore, it is guaranteed that the handler will run as
expected. When comparing these two strategies, you will notice that the
Alarms API is a poor substitute for higher frequency events.
Note The Alarms API is covered in the Extension and Browser APIs
chapter. Refer to the Background Scripts chapter for strategies to
address high-frequency timers.
Event Handlers
Because service workers are expected to start and stop on a regular basis,
the background script must be organized in a particular fashion to ensure
correct behavior. Keep the following behavior in mind when writing your
service worker:
172
Chapter 6 Understanding the Implications of Manifest V3
Event handler registration which may not handle all events correctly
// Bad
setTimeout(
() => chrome.action.onClicked.addListener(() => console.
log("click")),
10
);
// Good
chrome.action.onClicked.addListener(
() => console.log("click"));
173
Chapter 6 Understanding the Implications of Manifest V3
Tip The rule of thumb is to always register events at the top level of
the background script.
background.js which logs to console until the service worker is shut down
const t0 = performance.now();
setInterval(() => {
const t1 = performance.now();
console.log(`Alive for ${Math.round((t1 - t0) / 1e3)}s`);
}, 1e3);
174
Chapter 6 Understanding the Implications of Manifest V3
175
Chapter 6 Understanding the Implications of Manifest V3
Even with the interval timer, the browser will consider this script idle.
Typically, you will see the timer log for 30 seconds before the browser stops
the service worker.
Open long-lived connections like extension messaging ports or
websockets will delay the shutdown of the service worker, but Chrome
will still stop the service worker after 5 minutes and sever the connections.
Therefore, manifest v3 extensions should be organized as to avoid relying
on long-lived connections from the background script.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"],
"persistent": true
176
Chapter 6 Understanding the Implications of Manifest V3
},
"browser_action": {}
}
let count = 0;
chrome.browserAction.onClicked.addListener(() => {
console.log(`Clicked ${++count} times`);
});
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {}
}
let count = 0;
chrome.action.onClicked.addListener(() => {
console.log(`Clicked ${++count} times`);
});
177
Chapter 6 Understanding the Implications of Manifest V3
This will work correctly until the service worker stops. At that time,
the global variable will reset, as any variables in the global scope are lost.
The recommended solution is to use the Storage API, which will persist
through service worker stops and starts. The following code shows this
example refactored to use this API:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"action": {},
"permissions": ["storage"]
}
chrome.action.onClicked.addListener(() => {
chrome.storage.local.get(["count"], ({ count = 0 }) => {
console.log(`Clicked ${++count} times`);
Note For more details on the Storage API, refer to the Extension and
Browser APIs chapter.
178
Chapter 6 Understanding the Implications of Manifest V3
<!DOCTYPE html>
<html>
<body>
<h1>Popup</h1>
179
Chapter 6 Understanding the Implications of Manifest V3
<script src="popup.js"></script>
Implications of DeclarativeNetRequest
The change from webRequest to declarativeNetRequest isn’t strictly
part of manifest v3, but the transition is occurring simultaneously,
and its impact is just as significant. With webRequest, the browser
intercepts and routes network traffic to the extension, and the extension
180
Chapter 6 Understanding the Implications of Manifest V3
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"permissions": [
"webRequest",
"webRequestBlocking",
"<all_urls>"
]
}
chrome.webRequest.onBeforeRequest.addListener(
() => {
return { cancel: true };
},
{ urls: ["*://*.google.com/logos/*"] },
["blocking"]
);
181
Chapter 6 Understanding the Implications of Manifest V3
This extension will kill all requests for network requests matching
*://*.google.com/logos/*. Load the extension and visit google.com
to test.
This behavior can be replicated in manifest v3 as follows:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"permissions": ["declarativeNetRequest"],
"host_permissions": ["<all_urls>"],
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules.json"
}
]
}
}
[
{
"id": 1,
"priority": 1,
"action": { "type": "block" },
"condition": {
"urlFilter": "*.google.com/logos/*",
182
Chapter 6 Understanding the Implications of Manifest V3
"resourceTypes": ["image"]
}
}
]
Figure 6-3. google.com with the request for the logo blocked
183
Chapter 6 Understanding the Implications of Manifest V3
184
Chapter 6 Understanding the Implications of Manifest V3
Summary
The changeover from manifest v2 to manifest v3 has major implications
for the browser extension ecosystem. The most dramatic changes are
service workers and declarativeNetRequest, both of which have broad
implications for how extensions behave. With these changes, extensions
become more secure and performant, but at the same time it appears that
some valuable features are being killed entirely.
The next chapter will explore background scripts. It will cover in depth
how they work, how best to build them, and how they behave as the nerve
center of browser extension.
185
CHAPTER 7
Background Scripts
Browser extension developers often find the need to execute JavaScript in
the background of a browser. Background scripts, as their name suggests,
are the answer to this problem: they are a separate JavaScript runtime
that can communicate with all the different pieces of the extension,
add handlers for many different browser events, and operate without
dependence on any web page or extension user interface.
// Assets to precache
const precachedAssets = [
'/img1.jpg',
'/img2.jpg',
'/img3.jpg'
];
if (isPrecachedRequest) {
// Grab the precached asset from the cache
event.respondWith(caches.open(cacheName).then((cache) => {
return cache.match(event.request.url);
}));
188
Chapter 7 Background Scripts
} else {
// Go to the network
return;
}
});
This simple web page service worker defines a cache for some images.
On an install event, it preloads the images into the cache. When it sees
a fetch event matching the URL for one of those images, it intercepts the
request and returns the value from the cache.
const filter = {
url: [
{
urlMatches: 'https://www.example.com/',
},
],
};
189
Chapter 7 Background Scripts
This simple extension service worker sets listeners for the runtime.
onInstalled, bookmark.onCreated, and webNavigation.onCompleted
events. Each handler prints a log statement to the console when those
events are fired.
Similarities
The W3C specification for service workers begins with the following high-
level characterization:
The core of this specification is a worker that wakes to
receive events.
In this respect, a web page service worker and an extension service
worker are serving the same purpose. What’s more, the distinction
between a “web page” service worker and an “extension” service worker
only describes how the service worker is deployed in that context; the
underlying service worker platform remains unchanged.
When comparing the two examples from above, you may have noticed
the following:
190
Chapter 7 Background Scripts
Single Occupancy
For both web page service workers and extension service workers, there
will only ever be a single service worker per script. When installing updates
to service workers, the browser will carefully swap out old service workers
for new ones while ensuring there is only ever a single service worker
active at any given time.
This is critically important in both contexts: multiple service workers
would cause complete chaos with caching, network interception, and
event handling.
191
Chapter 7 Background Scripts
turn of the event loop, and then fire the queued event in the service worker.
If the handler for that event is not present at the end of that turn, the event
may pass through unhandled.
Async Messaging
Because all service workers are strictly asynchronous, both must use a
form of asynchronous messaging to communicate with other parts of the
browser.
Differences
Although web page service workers and extension service workers use the
same underlying platform, they differ in a handful of important ways.
Registration
Web page service workers must be registered from a page-level script. An
example of this is shown below:
if ('serviceWorker' in navigator) {
navigator.serviceWorker.register('/sw.js')
.then((registration) => {
registration.addEventListener('updatefound', () => {
console.log('A new service worker is being installed:');
});
192
Chapter 7 Background Scripts
})
.catch((error) => {
console.error(`Service worker registration failed:
${error}`);
});
} else {
console.error('Service workers are not supported.');
}
For extension service workers, the only step needed to register a service
worker is to specify the background.service_worker script in the manifest.
Web page service worker registration includes the installing/waiting/
active states that are not meaningful for extension service workers.
Purpose
The most important difference between web page service workers and
extension service workers is their primary purpose. Web page service
workers are equipped to act as a cache, and this is their most common use
case. They’re able to conditionally intercept network requests and return
cached content. Service workers can also be used to build progressive
web applications (PWAs), web pages that emulate app behavior with
installation, offline capabilities, and server-sent push notifications.
Because browser extension scripts are assets and all served from the
browser, there is no longer a need to cache these resources, as they are not
being loaded from a remote server. Furthermore, interfaces such as popup
pages and options pages are perfectly capable of operating normally offline
193
Chapter 7 Background Scripts
Manifest v2 manifest.json
{
"manifest_version": 2,
...
"background": {
"scripts": ["bg1.js", "bg2.js", "bg3.js"]
}
...
}
194
Chapter 7 Background Scripts
These scripts were loaded in the order they appeared in the array,
and all executed inside the same background page. In manifest v3, the
background script is a single service worker:
Manifest v3 manifest.json
{
"manifest_version": 3,
...
"background": {
"service_worker": "bg.js"
}
...
}
JavaScript Imports
In manifest v2, the only way to use a JavaScript import was to use the
background.page property and to use script tags in the background
page HTML:
{
"manifest_version": 2,
...
"background": {
"page": "bg.html"
}
...
}
bg.html
<html>
195
Chapter 7 Background Scripts
<body>
<!-- imports allowed in this JS file -->
<script type="module" src="bg.js"></script>
</body>
</html>
{
"manifest_version": 3,
...
"background": {
"service_worker": "bg.js",
"type": "module"
}
...
}
196
Chapter 7 Background Scripts
Nonpersistent
Manifest v2 allowed for background pages to be “persistent,” which
effectively meant they would run indefinitely as long as the host browser
program was open. In manifest v3, this ability is removed. This has several
implications:
197
Chapter 7 Background Scripts
Tip There are some tricks later on in this chapter that can extend
the life of a service worker.
198
Chapter 7 Background Scripts
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
}
}
chrome.runtime.onInstalled.addListener((object) => {
console.log("Installed background script!");
});
199
Chapter 7 Background Scripts
You can inspect the background script by clicking the service worker
link (Figure 7-2).
200
Chapter 7 Background Scripts
201
Chapter 7 Background Scripts
Note This page will show all service workers your browser has
installed – not just for extensions. Recall that the browser does
not make a distinction between a web page service worker and an
extension service worker.
Note For this section, you must begin with the extension installed
as shown in Example 7-1 and all devtools windows closed.
202
Chapter 7 Background Scripts
chrome.runtime.onInstalled.addListener((object) => {
console.log("Installed background script!");
});
Once the background script is saved, click the reload button in the
extension card. You will see the service worker immediately becomes
inactive and an Errors button appears (Figure 7-4).
Click the Errors button to reveal the error view (Figure 7-5).
203
Chapter 7 Background Scripts
You will see the expected “foo” error readout at the bottom, but also
that the browser is telling us that the service worker failed to register. This
is very important: when a service worker throws an error in the first
turn of the event loop, the service worker will fail to register.
204
Chapter 7 Background Scripts
Note For this section, you must keep all devtools windows closed
to allow the service worker to become idle.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {},
"background": {
"service_worker": "background.js"
}
}
chrome.runtime.onInstalled.addListener((object) => {
console.log("Installed background script!");
});
chrome.action.onClicked.addListener(() => {
console.log("Clicked toolbar icon!");
});
let elapsed = 0;
setInterval(() => console.log(`${++elapsed}s`), 1000);
205
Chapter 7 Background Scripts
This background script will log to the console every 1000ms until the
service worker is terminated. (Recall that setInterval() will not prevent
a service worker from becoming idle.) It will also log to the console
whenever the toolbar icon is clicked.
After reloading the extension, open the chrome://serviceworker-
internals page to observe the log output. The browser sees that this
extension is not actively doing work, and therefore is considered to be idle.
After approximately 30 seconds, you will see the browser stop the service
worker (Figure 7-6).
Figure 7-6. The browser stops the service worker after being idle for
30 seconds
The extension management page will also reflect this (Figure 7-7).
206
Chapter 7 Background Scripts
Because you set a handler for the action.onClicked, the browser will
wake the service worker, rerun the script, and restart the counter once you
click the toolbar icon.
Note Carefully observe the console output after clicking the toolbar
icon button. You will notice that the global state in the service worker
has been reset.
Common Patterns
In this section, we will explore a handful of patterns that background
service workers are frequently used for.
207
Chapter 7 Background Scripts
Event Handler
The WebExtensions API allows the browser to send a huge range of
different events into the extension to be handled. Since the API is
accessible in content scripts, popups, options pages, and background
scripts, these events can technically be handled in any of these places.
However, since content scripts, popups, and options pages are ephemeral,
only the background script guarantees that the event will be handled.
The following example extension sets up a background script to handle a
myriad of different API events:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js"
},
"permissions": ["alarms", "tabs"],
"action": {},
"commands": {
"foobar": {
"suggested_key": {
"default": "Ctrl+Shift+J",
"mac": "MacCtrl+Shift+J"
},
"description": "Perform foobar action"
}
}
}
208
Chapter 7 Background Scripts
209
Chapter 7 Background Scripts
If the service worker is terminated, the browser will restart the service
worker, run one turn of the event loop to set all the handlers, and only
then will it fire all the events inside the service worker. In this way, you can
guarantee event handling by assigning handlers at the top level.
210
Chapter 7 Background Scripts
Message Hub
Any piece of an extension can use the extension messaging API, but the
background script is the only entity that guarantees the message will be
heard. Furthermore, it also guarantees that the message will be received
exactly once. In this way, it is often useful for an array of content scripts to
all connect to a single background service worker and use it as a message
hub. The following example extension will create a message port on each
page that is opened and repeatedly send messages to the background.
211
Chapter 7 Background Scripts
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": [],
"js": ["content-script.js"]
}
]
}
212
Chapter 7 Background Scripts
function initializeCountdown(currentTabId) {
// This will fire the runtime.onConnect event
// in the background
const port = chrome.runtime.connect({
name: `Tab ${currentTabId}`,
});
213
Chapter 7 Background Scripts
}
});
Storage Manager
For extensions that need to store large amounts of data, the background
service worker can make use of IndexedDB. Popups, options pages,
and content scripts can all use the messaging API to read and write to
IndexedDB. The advantage of using the background as the IndexedDB
manager is versioning: the browser guarantees one background service
worker per extension at any time, so any IndexedDB migrations you
need to run can safely execute inside the service worker without risking a
versioning conflict.
214
Chapter 7 Background Scripts
Injecting Scripts
Background service workers can programmatically inject content scripts
into the page. This is very useful when you need to inject a content script
conditionally or asynchronously. The following example injects a small
content script when the toolbar icon button is clicked:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {},
"permissions": ["activeTab", "scripting"]
}
chrome.action.onClicked.addListener((tab) => {
chrome.scripting.executeScript({
target: { tabId: tab.id },
func: wipeOutPage,
});
});
215
Chapter 7 Background Scripts
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {},
"permissions": ["webNavigation"]
}
const filter = {
url: [
{
urlMatches: "https://www.example.com/",
},
],
};
216
Chapter 7 Background Scripts
chrome.webNavigation.onCompleted.addListener(() => {
console.log("Visited the special site!");
}, filter);
chrome.webNavigation.onDOMContentLoaded.
addListener((details) => {
console.log(`Loaded ${details.url}!`);
});
Installed/Updated Events
Developers often need to run a piece of code only when the extension is
updated or installed. The runtime.onInstalled event should be used for
this purpose. It is capable of differentiating between install events and
different update events via the reason property. The following background
script identifies when each install reason will execute:
background.js
chrome.runtime.onInstalled.addListener((details) => {
switch (details.reason) {
case chrome.runtime.OnInstalledReason.INSTALL:
console.log("This runs when the extension is newly
installed.");
break;
case chrome.runtime.OnInstalledReason.CHROME_UPDATE:
console.log("This runs when a chrome chrome update
installs.");
break;
case chrome.runtime.OnInstalledReason.SHARED_MODULE_UPDATE:
console.log("This runs when a shared module update
installs.");
217
Chapter 7 Background Scripts
break;
case chrome.runtime.OnInstalledReason.UPDATE:
console.log("This runs when an extension update
installs.");
break;
default:
break;
}
});
Opening Tabs
Content script can’t open extension URLs, but they can delegate the
opening to the background script. The following example adds two buttons
to the page: one will fail to open the extension URL indirectly, and one
sends a message to the background script to open the URL.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": [],
"js": ["content-script.js"]
218
Chapter 7 Background Scripts
}
],
"permissions": ["tabs"]
}
chrome.runtime.onMessage.addListener((msg) => {
chrome.tabs.create({ url: msg.url });
});
document.body.appendChild(root);
document.querySelector("#direct-open").
addEventListener("click", () => {
window.open(url);
});
document.querySelector("#indirect-open").
addEventListener("click", () => {
chrome.runtime.sendMessage({ url });
});
219
Chapter 7 Background Scripts
<!DOCTYPE html>
<html>
<body>
<h1>Foobar</h1>
</body>
</html>
Implementation
All the forced persistence workarounds all rely on the same overall
strategy. When an extension message port is opened between a content
script and the background, the browser will not terminate the service
worker for 5 minutes. There does not need to be any message passing
going on, the port just needs to remain open. Once these 5 minutes are
up, the browser may decide the service worker is idle and terminate it. If,
however, a new port is connected before 5 minutes is up, the browser will
restart the 5-minute timer. If the extension continually re-opens new ports,
the background service worker can theoretically remain active indefinitely.
220
Chapter 7 Background Scripts
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"]
}
let lifeline;
221
Chapter 7 Background Scripts
return;
} catch (e) {}
}
}
chrome.runtime.onConnect.addListener((port) => {
if (port.name == "KEEPALIVE") {
lifeline = port;
222
Chapter 7 Background Scripts
keepAlive();
let age = 0;
setInterval(() => console.log(`Age: ${++age}s`), 1000);
223
Chapter 7 Background Scripts
Summary
In this chapter, you learned all about how extension code can run in
the background in the browser. You were shown the similarities and
differences between service workers that run in a web page vs. in an
extension, as well as all the important changes between background
scripts in manifest v2 and v3. Next, we went through all the basics of
inspecting and debugging background scripts. Finally, you were shown a
variety of common patterns that developers use in background scripts, as
well as a clever strategy for extending the lifetime of a service worker.
In the next chapter, you will learn about the two primary user interface
components of extensions: the popup and options pages.
224
CHAPTER 8
Popup Pages
As its name suggests, the popup page is a web page that renders inside
a container that “pops up” over the browser’s web page. It is completely
controlled by the browser extension and guaranteed to overlay the
browser’s current web page (Figure 8-1).
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {
226
Chapter 8 Popup and Options Pages
"default_popup": "popup.html"
}
}
<!DOCTYPE html>
<html>
<head>
<link href="popup.css" rel="stylesheet" />
</head>
<body>
<h1>This is a popup page!</h1>
<div id="url"></div>
<div id="xid"></div>
<a href="popup.js">popup.js</a>
<script src="popup.js"></script>
</body>
</html>
document.querySelector("#url").innerHTML = `
<pre>Page URL: ${window.location.href}</pre>
`;
document.querySelector("#xid").innerHTML = `
<pre>Extension ID: ${chrome.runtime.id}</pre>
`;
body {
text-align: center;
227
Chapter 8 Popup and Options Pages
min-width: 20rem;
padding: 4rem;
}
228
Chapter 8 Popup and Options Pages
The popup container will be sized to fit the content of the popup
page, but browsers will handle the sizing rules slightly differently. For
example, Google Chrome sets no upper bound on the maximum size of the
popup, allowing it to even expand larger than the browser window itself.
Conversely, Mozilla Firefox limits the popup to be no larger than 800px
by 600px.
Tip To handle these variable popup size limits, you should either
make the popup page responsive or limit the amount of content that
appears inside the popup.
229
Chapter 8 Popup and Options Pages
Once the popup is open, there are many more ways to trigger a close.
From popup page’s perspective, closing the popup is identical to a tab
being closed. The following actions will trigger a popup close:
230
Chapter 8 Popup and Options Pages
chrome.action.setPopup({
popup: chrome.runtime.getURL("new-popup.html")
});
If you wish to detect if a view is being rendered inside a popup, you can
perform a simple check against the window object:
231
Chapter 8 Popup and Options Pages
Suggested Use
When developing popup pages, you should follow these strategies:
• Popup pages are best used for user interfaces that the
user needs to access quickly without losing the context
of the current page that they are on.
232
Chapter 8 Popup and Options Pages
Options Pages
The options page is a web page that renders in one of two ways: as a
standalone tab, or inside a modal container over the browser’s extension
manager page (Figures 8-3 and 8-4). It is completely controlled by the
browser extension.
Figure 8-3. The options page rendered inside the extension manager
page modal
233
Chapter 8 Popup and Options Pages
234
Chapter 8 Popup and Options Pages
"default_popup": "popup.html"
},
"options_ui": {
"open_in_tab": false,
"page": "options.html"
}
}
<!DOCTYPE html>
<html>
<body>
<h1>Popup</h1>
<button id="opts">Options</button>
<a href="options.html" target="_blank">options.html</a>
<script src="popup.js"></script>
</body>
</html>
document.querySelector("#opts").addEventListener(
"click",
() => chrome.runtime.openOptionsPage());
<!DOCTYPE html>
<html>
<body>
<h1>Options</h1>
235
Chapter 8 Popup and Options Pages
<a href="options.js">options.js</a>
</body>
</html>
236
Chapter 8 Popup and Options Pages
• Call chrome.runtime.openOptionsPage()
237
Chapter 8 Popup and Options Pages
Suggested Use
When developing options pages, you should follow these strategies:
238
Chapter 8 Popup and Options Pages
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "popup.html"
},
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
"permissions": ["scripting"],
"host_permissions": ["<all_urls>"]
}
<!DOCTYPE html>
<html>
<body>
<h1>Popup</h1>
239
Chapter 8 Popup and Options Pages
<script src="popup.js"></script>
</body>
</html>
function openOptionsWithUrl() {
window.open(chrome.runtime.getURL("options.html"));
}
function openOptionsWithApi() {
chrome.runtime.openOptionsPage();
}
chrome.scripting.executeScript({
target: { tabId: tab.id },
function: fn,
});
}
document.querySelector("#popup-api").addEventListener(
"click",
() => openOptionsWithApi());
document.querySelector("#popup-url").addEventListener(
"click",
() => openOptionsWithUrl());
240
Chapter 8 Popup and Options Pages
document.querySelector("#cs-api").addEventListener(
"click",
() => sendFnToActiveTab(openOptionsWithApi));
document.querySelector("#cs-url").addEventListener(
"click", () => sendFnToActiveTab(openOptionsWithUrl));
<!DOCTYPE html>
<html>
<body>
<h1>Options</h1>
</body>
</html>
The popup page’s four buttons each offer one of the four combinations
of popup origin or content script origin, and API open or URL open.
Initially, nothing here should appear out of the order. When you click the
two popup buttons, the options page will correctly open without incident.
However, after clicking each of the content script buttons, the
options page will not correctly open and you will see the errors shown in
Figures 8-5 and 8-6.
241
Chapter 8 Popup and Options Pages
Figure 8-5. The error shown after clicking the Content Script
API button
Figure 8-6. The error shown after clicking the Content Script
URL button
242
Chapter 8 Popup and Options Pages
S
ummary
In this chapter, you learned about the two major building user interface
building blocks of browser extensions: popup pages and options pages.
The chapter took you through the details of how each one operates inside
the browser, how they can fit into the user flow, and the advantages and
disadvantages of using each one.
In the next chapter, you will learn about how content scripts can
complement and enhance popup and options pages, as well as enable an
entirely new extension domain in which to architect user interfaces.
243
CHAPTER 9
Content Scripts
Contents scripts are one of the most powerful tools available in browser
extensions. They allow you to inject JavaScript and CSS into any web
page and modify it with almost no restrictions. The injected content can
be as simple as a few visual tweaks, or as complicated as entire single
page application frameworks. Extensions can also use them for nonuser
interface reasons: content scripts can read and modify the page DOM, as
well as send network requests as the authenticated user.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": ["content-script.css"],
"js": ["content-script.js"]
}
],
"permissions": []
}
console.log(window.jQuery);
body {
background-color: red !important;
}
Injecting CSS
Injecting CSS content scripts into a host page is relatively straightforward.
If the URL is a match, the script will be injected as though it were listed as
an extra <link> element. Install the previous example extension and visit
any website to see the CSS injection result (Figure 9-1).
246
Chapter 9 Content Scripts
247
Chapter 9 Content Scripts
248
Chapter 9 Content Scripts
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": [],
"js": ["content-script.js"]
}
],
"permissions": []
}
249
Chapter 9 Content Scripts
Load this extension and navigate to any web page. You will notice that
all the CSS styling will be completely stripped out.
Page Automation
Although a content script cannot access event handlers, it can dispatch
events on shared DOM nodes. Event handlers that the host page assigned
in the host JavaScript context will be called by events dispatched from the
content script JavaScript context. The following example demonstrates this
by automating a search on Wikipedia.org.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"content_scripts": [
{
"matches": ["https://*.wikipedia.org/*"],
250
Chapter 9 Content Scripts
"css": [],
"js": ["content-script.js"]
}
],
"permissions": []
}
setTimeout(() => {
document.querySelector('button[type="submit"]').click();
}, 3000);
content-script.js
function typeOrSubmit(idx = 0) {
const char = typedValue[idx];
if (!char) {
setTimeout(() => form.submit(), 500);
251
Chapter 9 Content Scripts
} else {
input.value = input.value + char;
typeOrSubmit();
}, 2000);
}
Reload the extension and the Wikipedia page. The content script will
progressively type out the search term. Also note here that the content
script is submitting the form directly, not just clicking the search button.
This touches upon an important concept to keep in mind when
writing content scripts: you are at the mercy of the host page. In the above
example, we are using selectors defined by the host page to locate the
elements we want to interact with. If the host page changes these selectors
even a small amount, the content script will break. Furthermore, note
that the content scripts are using built-in event dispatching methods
like click(), focus(), and submit(). These are handy because they are
succinct and partially allow you the ability to avoid dispatching events
manually.
Sometimes, dispatching events manually is required. A content
script can’t see which elements have event handlers attached, but
the developer can find this information out ahead of time using the
getEventListeners() method. For example, on https://wikipedia.
org, you can use this method to find all the event listeners on the “Read
Wikipedia in your language” button (Figure 9-3).
252
Chapter 9 Content Scripts
We see here there is an event listener set by the host page. We can then
check what this handler is doing by just dispatching a test click event in the
developer console – and you should see it toggle the language menu. To
automate the opening of this menu, we could certainly call click() in the
content script, but for the sake of this example let’s instead open the menu
by manually dispatching a click event:
content-script.js
setTimeout(() => {
const el = document.querySelector("#js-lang-list-button");
// Scroll down to the button so we can see the click work
el.scrollIntoView();
el.dispatchEvent(new Event("click"));
}, 2000);
253
Chapter 9 Content Scripts
Reload the extension and the Wikipedia page. You should see
the content script scroll the page down and automatically open the
language menu.
Because uncaught errors thrown in the content script are still part
of the extension context, these error messages will also surface in the
extension’s error view (Figure 9-5).
254
Chapter 9 Content Scripts
• chrome.i18n.*
• chrome.storage.*
• chrome.runtime.connect
• chrome.runtime.getManifest
• chrome.runtime.getURL
• chrome.runtime.id
255
Chapter 9 Content Scripts
• chrome.runtime.onConnect
• chrome.runtime.onMessage
• chrome.runtime.sendMessage
If you require other parts of the API, you can delegate to the
background service worker by sending a message to trigger a remote
procedure call. The background service worker can use the full API and
send back the result if needed.
One unusual aspect of content scripts is, although they can use
runtime.getURL(), they are prevented from opening tabs of extension
URLs. The tab will open, but the page will not render and instead show
a browser error. Content scripts run in an untrusted environment, and
therefore using <a href> or window.open(chrome-extension://...) to
access extension pages would necessarily mean that the host pages could
open those URLs too. As shown in the Background Scripts chapter, the
solution to this is to send a message to the background directing it to open
a new tab with the desired URL.
Bundling
Most extension development is done using sophisticated build tools like
Parcel, Webpack, or Plasmo, and these are usually configured to smush
the entire content script code graph into a single file, thereby eliminating
the need for using imports in the top-level content script. Traditional
256
Chapter 9 Content Scripts
web applications have more of a need for lazy loading, since from a
performance perspective loading data from a remote server is expensive.
Since browser extensions are exclusively served from the extension file
server on the local device, there is a negligible penalty for bundling the
entire content script into a gigantic monolithic script.
Dynamic Imports
Content scripts may not be able to use static imports, but they are perfectly
happy using dynamic imports and loading a secondary module that can
use static imports. Doing so requires an additional network request to the
extension file server, but this penalty is negligible and therefore acceptable.
Of course, any static or dynamic import in a content script will
generate a network request for that module. Therefore, to allow these
modules to be imported, they must be listed as under web_accessible_
resources. This is demonstrated in the following example extension,
which performs a dynamic import. Load the extension and inspect the
console output:
257
Chapter 9 Content Scripts
"permissions": [],
"web_accessible_resources": [
{
"resources": ["*.js"],
"matches": ["<all_urls>"]
}
]
}
import(url).then((fooModule) => {
fooModule.bar();
});
258
Chapter 9 Content Scripts
259
Chapter 9 Content Scripts
Reload the extension and you will see the screen shown in Figure 9-7.
Figure 9-7. Dynamic script tag network requests and console output
260
Chapter 9 Content Scripts
You may encounter some special situations where dynamic script tag
creation is useful, but overall it is less useful than using dynamic imports.
• run_at
• match_about_blank
• match_origin_as_fallback
• exclude_matches
• include_globs
• exclude_globs
• all_frames
261
Chapter 9 Content Scripts
Programmatic Injection
The manifest is not the only way to inject content scripts. It is also possible
to programmatically inject JavaScript and CSS into the page using the
chrome.scripting API. Consider the following example that injects both
JS and CSS after the toolbar icon is clicked:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["scripting", "activeTab"],
"action": {}
}
chrome.action.onClicked.addListener((tab) => {
const target = {
tabId: tab.id,
};
chrome.scripting.executeScript({
target,
func: () => {
document.body.innerHTML = `Hello, world!`;
},
});
262
Chapter 9 Content Scripts
chrome.scripting.insertCSS({
target,
css: `body { background-color: red !important; }`,
});
});
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["scripting", "activeTab"],
"action": {}
}
263
Chapter 9 Content Scripts
chrome.action.onClicked.addListener((tab) => {
const target = {
tabId: tab.id,
};
chrome.scripting.executeScript({
target,
files: ["content-script.js"],
});
chrome.scripting.insertCSS({
target,
files: ["content-script.css"],
});
});
264
Chapter 9 Content Scripts
function wipeOutPage(bg) {
// Record the typeof inside the content script
const cs = typeof outerVar;
document.body.innerHTML = `${bg} -> ${cs}`;
}
const css = `
body {
background-color: red !important;
}`;
chrome.action.onClicked.addListener((tab) => {
const target = {
tabId: tab.id,
};
chrome.scripting.executeScript({
target,
func: wipeOutPage,
// This array of values will be curried
// into `func` (similar to Array.apply)
args: [backgroundTypeof]
});
chrome.scripting.insertCSS({
target,
css,
});
});
265
Chapter 9 Content Scripts
Reload this extension. You will see that the page content is string ->
undefined. This indicates that the variable reference has been lost when
the function is evaluated in the page.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["scripting", "activeTab"],
"action": {}
}
266
Chapter 9 Content Scripts
const id = "1";
chrome.action.onClicked.addListener(async () => {
const activeScripts = await chrome.scripting.
getRegisteredContentScripts();
267
Chapter 9 Content Scripts
body {
background-color: red !important;
}
Summary
In this chapter, you learn about how content scripts allow you to exert
almost total control over host web pages. Although they have limited
access to the WebExtensions API, content scripts can coordinate with the
background service worker to add profoundly powerful enhancements
to the user’s browser experience. Finally, you were shown how content
scripts can be dynamically added and removed from the page.
In the next chapter, you will learn how to build custom devtools pages
into your browser’s developer tools interface. You will also learn how to
use the custom devtools APIs that are only accessible from an extension’s
devtools pages.
268
CHAPTER 10
Devtools Pages
Modern browsers offer developers a rich suite off developer tools for
debugging, profiling, and inspecting web pages. Browser extensions are
able to supplement these tools by providing custom interfaces that live
inside the developer tools interface and can access special developer
tools APIs.
Web developers are the intended audience for devtools pages, so
consumer facing extensions will likely not need to make use of them. In
general, browser extensions will only need devtools pages if they wish to
use the Devtools API to inspect or profile the web page.
Note There are two separate concepts in this chapter with similar
names: “devtools pages” and “developer tools”. The term “developer
tools” refers to the browser’s native interface that is opened by right
clicking on a web page and selecting “Inspect”. The term “devtools
pages” are extra interfaces added inside the developer tools. Browser
extensions use the Devtools API to add these extra interfaces.
270
Chapter 10 Devtools Pages
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"devtools_page": "devtools.html"
}
<!DOCTYPE html>
<html>
<body>
<script src="devtools.js"></script>
</body>
</html>
271
Chapter 10 Devtools Pages
Note The headless devtools page only runs each time the developer
tools opens. This means that, when you make changes to the added
panels, they will not re-render even if the extension is updated or
the page is reloaded. When making updates to devtools pages, you
must close and reopen the devtools interface or reload the updated
devtools frames.
272
Chapter 10 Devtools Pages
Adding a Panel
To add a new panel to the developer tools, use the chrome.devtools.
panels.create() method. Modify the previous example as follows:
<!DOCTYPE html>
<html>
<body>
<h1>I'm the foo panel!</h1>
</body>
</html>
chrome.devtools.panels.create("Demo Devtools",
"",
"foo_panel.html");
The create() method takes a title, an icon URL (here it is left blank),
and a panel HTML file. With this extension loaded, close and reopen the
developer tools and look to your panel menu. You will notice there is a new
option called “Demo Devtools” (Figure 10-2).
273
Chapter 10 Devtools Pages
274
Chapter 10 Devtools Pages
275
Chapter 10 Devtools Pages
Adding a Sidebar
Sidebars are slightly different from panels. Whereas a panel is a top-level
interface in the developer tools, sidebars are interfaces that live alongside
one of several existing interfaces inside devtools. Currently, there are two
interfaces that you can add sidebars to:
• Elements
• Sources
chrome.devtools.panels.sources.createSidebarPane(
"Demo Sources Sidebar",
(sidebar) => {
sidebar.setPage("sources_sidebar.html");
}
);
chrome.devtools.panels.elements.createSidebarPane(
"Demo Elements Sidebar",
(sidebar) => {
sidebar.setPage("elements_sidebar.html");
}
);
276
Chapter 10 Devtools Pages
<!DOCTYPE html>
<html>
<body>
<h1>I'm the elements sidebar!</h1>
</body>
</html>
<!DOCTYPE html>
<html>
<body>
<h1>I'm the sources sidebar!</h1>
</body>
</html>
Install this extension, and reopen the developer tools. In the Elements
and Sources tabs, you will see a new child option (Figures 10-4, 10-5, 10-6,
and 10-7).
277
Chapter 10 Devtools Pages
278
Chapter 10 Devtools Pages
279
Chapter 10 Devtools Pages
280
Chapter 10 Devtools Pages
281
Chapter 10 Devtools Pages
Of course, these views don’t do anything useful yet. In the next section,
we will cover the special methods these views can access.
Note The panel and sidebar views will re-render the HTML page
each time the tab is opened. For example, if you were to click into
your custom panel, click over to the Elements tab, and click back over
to the original panel, it will have rendered two separate times. Keep
this in mind should you need to preserve state.
• chrome.devtools.inspectedWindow is used to
inspect the current web page in ways that the normal
WebExtensions API cannot. It can evaluate JavaScript
expressions in the context of the page as well as view a
list of resources (such as a document, script, or image)
that the current web page is using.
282
Chapter 10 Devtools Pages
{
"_initiator": {
"type": "other"
},
"_priority": "VeryHigh",
"_resourceType": "document",
"cache": {},
"connection": "769999",
"pageref": "page_2",
283
Chapter 10 Devtools Pages
"request": {
"method": "GET",
"url": "https://www.wikipedia.org/",
"httpVersion": "http/2.0",
"headers": [
{
"name": ":method",
"value": "GET"
},
{
"name": ":path",
"value": "/"
},
{
"name": ":scheme",
"value": "https"
},
...
],
"queryString": [],
"cookies": [
...
],
"headersSize": -1,
"bodySize": 0
},
"response": {
"status": 304,
"statusText": "",
"httpVersion": "http/2.0",
"headers": [
284
Chapter 10 Devtools Pages
{
"name": "content-encoding",
"value": "gzip"
},
{
"name": "content-type",
"value": "text/html"
},
...
],
"cookies": [],
"content": {
"size": 75189,
"mimeType": "text/html"
},
"redirectURL": "",
"headersSize": -1,
"bodySize": 0,
"_transferSize": 1145,
"_error": null
},
"serverIPAddress": "208.80.154.224",
"startedDateTime": "2022-08-24T15:28:35.006Z",
"time": 45.61999998986721,
"timings": {
"blocked": 3.410000022381544,
"dns": -1,
"ssl": -1,
"connect": -1,
"send": 0.2370000000000001,
"wait": 41.07399999056011,
285
Chapter 10 Devtools Pages
"receive": 0.8989999769255519,
"_blocked_queueing": 1.8070000223815441
}
}
For developers who have spent lots of time in the Network panel, you
will quickly recognize this as the JSON data dump of all the information
displayed in the panel.
The Devtools API only has one method and two events for you to use,
but this is sufficient for you to be able to architect your own version of a
network inspector. The following example creates a very simple custom
network panel:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"devtools_page": "devtools.html"
}
<!DOCTYPE html>
<html>
<body>
<script src="devtools.js"></script>
</body>
</html>
286
Chapter 10 Devtools Pages
<!DOCTYPE html>
<html>
<head>
<script src="traffic_panel.js" defer></script>
</head>
<body></body>
</html>
function logRequest(har) {
document.body.innerHTML += `
<div>
${har.request.method} ${har.request.url}
(${har.response.status})
</div>`;
}
287
Chapter 10 Devtools Pages
Load this extension, open the developer tools, and select the new
“Devtools Traffic” panel. Navigate to any web page and you will see all the
requests dump out into this panel, as shown in Figure 10-8.
288
Chapter 10 Devtools Pages
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"devtools_page": "devtools.html"
}
<!DOCTYPE html>
<html>
<body>
<script src="devtools.js"></script>
</body>
</html>
289
Chapter 10 Devtools Pages
<!DOCTYPE html>
<html>
<head>
<script src="inspect_panel.js" defer></script>
</head>
<body>
<button id="check">CHECK FOR JQUERY</button>
</body>
</html>
document.querySelector("#check").
addEventListener("click", () => {
chrome.devtools.inspectedWindow.eval(
`({
'url': window.location.href,
'usesJquery': !!window.jQuery
})`,
null,
(result) => {
const div = document.createElement("div");
div.innerText = `${result.url} uses jQuery: ${result.
usesJquery}`;
document.body.appendChild(div);
290
Chapter 10 Devtools Pages
}
);
});
Load this extension and open the devtools on jquery.com, which has
a jQuery global object, and wikipedia.org, which does not (Figure 10-9).
Clicking the button on each site will accurately record inside the devtools
panel whether or not the site uses jQuery.
chrome.devtools.inspectedWindow.eval(
`(() => {
const url = window.location.href;
291
Chapter 10 Devtools Pages
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"devtools_page": "devtools.html"
}
<!DOCTYPE html>
<html>
<body>
<script src="devtools.js"></script>
</body>
</html>
292
Chapter 10 Devtools Pages
chrome.devtools.panels.elements.createSidebarPane(
"Devtools Inspector",
(sidebar) => {
sidebar.setPage("inspect_panel.html");
}
);
<!DOCTYPE html>
<html>
<head>
<script src="inspect_panel.js" defer></script>
</head>
<body>
<button id="inspect">INSPECT IMG</button>
<button id="tagname">ACTIVE TAG NAME</button>
</body>
</html>
document.querySelector("#inspect").
addEventListener("click", () => {
chrome.devtools.inspectedWindow.eval(
`inspect(document.querySelector('img'))`
);
});
document.querySelector("#tagname").addEventListener("click",
() => {
chrome.devtools.inspectedWindow.eval(
293
Chapter 10 Devtools Pages
`$0?.tagName`,
null,
(result) => {
const div = document.createElement("div");
div.innerText = result;
document.body.appendChild(div);
});
}
);
Load this extension, open the developer tools, select the Elements tab,
and open the Devtools Inspector sidebar.
In this example, the “INSPECT IMG” button is calling inspect() on
the first image to appear in the document. Because this is called from a
sidebar inside the Elements panel, you will see this immediately reflected
in the native browser’s DOM inspector (Figure 10-10).
294
Chapter 10 Devtools Pages
Clicking the ACTIVE TAG NAME button will use the $0 token to
reference whichever DOM inspector node was last inspected and print its
tag name (Figure 10-11).
295
Chapter 10 Devtools Pages
296
Chapter 10 Devtools Pages
297
Chapter 10 Devtools Pages
Tip The Google Chrome team has an excellent writeup on this here:
https://developer.chrome.com/docs/extensions/mv3/
devtools/#solutions.
298
Chapter 10 Devtools Pages
S
ummary
In this chapter, you learned about how an extension can extend the
browser’s native developer tools interface. We went through the unusual
way that an extension injects user interfaces into the developer tools, as
well as all the major features of the Devtools API that only devtools pages
have access to.
In the next chapter, we will cover all the major WebExtensions APIs
and how they can be used.
299
CHAPTER 11
Extension
and Browser APIs
The WebExtensions API is the secret sauce of browser extensions. It allows
them to reach into the web page and the browser to make changes, inspect
and modify network traffic, and control aspects of the native browser’s user
interface.
302
Chapter 11 Extension and Browser APIs
Error Handling
Errors thrown from WebExtensions API methods must be handled differently
based on your usage. If you use callbacks, when the method fails the chrome.
runtime.lastError property will be defined only inside the callback handler:
// or
try {
await chrome.tabs.executeScript(tabId, details);
} catch(e) {
// Error handling here
}
Context-restricted APIs
Not all APIs can be used everywhere:
• Some APIs such as tabCapture and fileSystem are
restricted to the foreground, meaning they cannot be
accessed from a background service worker.
303
Chapter 11 Extension and Browser APIs
Events API
The “Events API” refers to the general pattern of how you can add event
listeners to events fired in browser extensions. Most WebExtensions APIs
utilize this format.
Format
Each event type has an interface to add, remove, and inspect listeners.
For example, the chrome.runtime.onMessage interface has the following
methods:
• chrome.runtime.onMessage.addListener()
• chrome.runtime.onMessage.dispatch()
• chrome.runtime.onMessage.hasListener()
• chrome.runtime.onMessage.hasListeners()
• chrome.runtime.onMessage.removeListener()
304
Chapter 11 Extension and Browser APIs
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {},
"permissions": []
}
let count = 0;
function handler() {
console.log("Handler executed!");
// false
console.log(chrome.action.onClicked.hasListener(handler));
chrome.action.onClicked.addListener(handler);
// true
console.log(chrome.action.onClicked.hasListener(handler));
305
Chapter 11 Extension and Browser APIs
This example sets a listener for onClicked events, listens for five
events, and then de-registers itself.
Event Filtering
If you wished to restrict an event handler to a certain domain, you would
need to perform filtering inside the handler:
chrome.webNavigation.onCommitted.addListener((event) => {
if (!event.url.match(/^https:\/\/*.wikipedia\.org/)) {
return;
}
Instead, you can instruct the browser to declaratively filter events that
match certain URLs. The browser natively performing this filtering will
give a considerable performance boost. The above code can be refactored
as follows:
chrome.webNavigation.onCommitted.addListener((event) => {
// Handle filtered event
}, {
url: [
{ hostSuffix: "wikipedia.org" }
]
});
306
Chapter 11 Extension and Browser APIs
Permissions
• Permissions API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
permissions/
chrome.action.onClicked.addListener(() => {
// Can only request permissions after user action
const permissionsGranted =
await chrome.permissions.request({
permissions: ['tabs']
});
});
307
Chapter 11 Extension and Browser APIs
Messaging
• Chrome Messaging Overview – Chrome Developers:
https://developer.chrome.com/docs/extensions/
mv3/messaging/
One-off Messages
The chrome.runtime.sendMessage() method can be used to send single
messages between extension components. It can only be used to send
messages from a content script. Listeners can send a message back inside
the message handler.
// Sender
chrome.runtime.sendMessage(msg, (response) => {
// Handle response
});
308
Chapter 11 Extension and Browser APIs
// Receiver
chrome.runtime.onMessage.addListener((msg, sender,
sendResponse) => {
// Handle initial message from sender
sendResponse(responseMsg);
});
// Sender
chrome.tabs.sendMessage(tabId, msg, (response) => {
// Handle response
});
// Endpoint 1
const port1 = chrome.runtime.connect({name: "portName"});
// Outgoing
port1.postMessage(msg);
// Incoming
port1.onMessage.addListener((msg) => {});
309
Chapter 11 Extension and Browser APIs
// Endpoint 2
let port2 = null;
chrome.runtime.onConnect.addListener((p) => {
port2 = p;
});
// Outgoing
port2.postMessage(msg);
// Incoming
port2.onMessage.addListener((msg) => {});
// Endpoint 1
const port1 = chrome.tabs.connect(tabId, {name: "portName"});
// Outgoing
port1.postMessage(msg);
// Incoming
port1.onMessage.addListener((msg) => {});
310
Chapter 11 Extension and Browser APIs
chrome.runtime.sendNativeMessage(
applicationName, msg, (response) => {});
chrome.tabs.connectNative(
applicationName, {name: "portName"});
// Sender
chrome.runtime.sendMessage(extensionId, msg, (response) => {
// Handle response
});
311
Chapter 11 Extension and Browser APIs
// Extension 1
const port1 = chrome.runtime.connect(extensionId, {name:
"portName"});
// Outgoing
port1.postMessage(msg);
// Incoming
port1.onMessage.addListener((msg) => {});
// Extension2
let port2 = null;
chrome.runtime.onConnectExternal.addListener((p) => {
port2 = p;
});
// Outgoing
port2.postMessage(msg);
// Incoming
port2.onMessage.addListener((msg) => {});
This method also allows for regular web pages (not content scripts)
to send messages to your extension in the exact same way. The API is
exposed in the web page’s JavaScript runtime, and your extension can
receive messages if the following conditions are true:
312
Chapter 11 Extension and Browser APIs
Storage
• storage API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
storage/
313
Chapter 11 Extension and Browser APIs
chrome.storage.onChanged.addListener(console.log);
Authentication
• identity API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
identity/
314
Chapter 11 Extension and Browser APIs
(token) => {
if (token) {
const info = await chrome.identity.getProfileUserInfo(
{ accountStatus: "ANY" }
);
}
}
);
Network Requests
• declarativeNetRequest API – Chrome Developers:
https://developer.chrome.com/docs/extensions/
reference/declarativeNetRequest/
315
Chapter 11 Extension and Browser APIs
316
Chapter 11 Extension and Browser APIs
},
condition: {
urlFilter: "https://www.wikpedia.org",
},
}]
});
Internationalization
• i18n API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/i18n/
317
Chapter 11 Extension and Browser APIs
chrome.i18n.getUILanguage();
chrome.i18n.getMessage("foo_message");
Power
• power API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/power/
The chrome.power API allows the extension to prevent the host system
from going to sleep.
318
Chapter 11 Extension and Browser APIs
Omnibox
• omnibox API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
omnibox/
const suggestions = [
{
content: url1,
description: `
${title1}
<dim>${subtitle1}</dim>
<url>${url1}</url>`,
},
{
319
Chapter 11 Extension and Browser APIs
content: url2,
description: `
${title2}
<dim>${subtitle2}</dim>
<url>${url2}</url>`,
}
];
chrome.omnibox.onInputChanged.addListener(
(text, suggest) => {
suggest(suggestions.filter(s => s.content.includes(text))
}
);
chrome.omnibox.onInputEntered.addListener(
(text, disposition) => {
// Navigate to selected url
chrome.tabs.update(activeTabId, { url: text });
});
Action
• action API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/action/
320
Chapter 11 Extension and Browser APIs
Figure 11-2. The toolbar icon button showing the title text
chrome.action.setPopup({
popup: chrome.runtime.getURL("new_popup.html")
});
Notifications
• notifications API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
notifications/
321
Chapter 11 Extension and Browser APIs
chrome.notifications.create(
"NOTIFICATION_ID",
{
type: "basic",
iconUrl: "/img.png",
title: "My notification!",
message: "This is the message",
priority: 2
}
);
Context Menu
• contextMenus API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
contextMenus/
322
Chapter 11 Extension and Browser APIs
chrome.contextMenus.create({
id: "demo-radio",
title: "Select a radio button...",
contexts: ["all"],
});
323
Chapter 11 Extension and Browser APIs
chrome.contextMenus.create({
id: "demo-media",
title: "You right clicked a media element",
contexts: ["image", "video", "audio"],
});
324
Chapter 11 Extension and Browser APIs
325
Chapter 11 Extension and Browser APIs
Proxy
• proxy API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/proxy/
chrome.proxy.settings.set(
{
value: {
mode: "fixed_servers",
rules: {
proxyForHttp: {
scheme: "socks5",
host: "192.168.1.2",
},
bypassList: ["wikipedia.org"],
},
},
scope: "regular",
326
Chapter 11 Extension and Browser APIs
},
() => {}
);
Font Settings
• fontSettings API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
fontSettings/
chrome.fontSettings.getFont(
{ genericFamily: 'standard', script: 'Egyp' },
(details) => {}
);
chrome.fontSettings.setFont(
{ genericFamily: 'serif', script: 'Jpan',
fontId: 'Comic Sans' }
);
Content Settings
• contentSettings API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
contentSettings/
327
Chapter 11 Extension and Browser APIs
• automaticDownloads
• camera
• cookies
• fullscreen
• images
• javascript
• location
• microphone
• mouselock
• notifications
• plugins
• popups
• unsandboxedPlugins
Cookies
• cookies API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
cookies/
328
Chapter 11 Extension and Browser APIs
chrome.cookies.set({
"name": "foo",
"url": "https://www.wikipedia.org",
"value": "bar"
});
chrome.cookies.remove(
"name": "foo",
"url": "https://www.wikipedia.org"
});
Bookmarks
• bookmarks API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
bookmarks/
chrome.bookmarks.create(
{'title': 'Wikipedia', 'url': 'https://www.wikipedia.org'}
);
329
Chapter 11 Extension and Browser APIs
const matchingNodes =
await chrome.bookmarks.search({ query: 'wikipedia' });
History
• history API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
history/
chrome.history.deleteAll();
Downloads
• downloads API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
downloads/
330
Chapter 11 Extension and Browser APIs
// Start a download
chrome.downloads.download({
url: "https://www.wikipedia.org/portal/wikipedia.org/assets/
img/[email protected]",
});
Top Sites
• topSites API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
topSites/
This API allows an extension to read the “top sites” (most visited sites)
displayed on the new tab page.
Browsing Data
• browsingData API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
browsingData/
331
Chapter 11 Extension and Browser APIs
chrome.browsingData.remove({
// Last 24 hours
"since": (Date.now() - 24 * 60 * 60 * 1E3)
}, {
"appcache": true,
"cache": true,
"cacheStorage": true,
"cookies": true,
"downloads": true,
"fileSystems": true,
"formData": true,
"history": true,
"indexedDB": true,
"localStorage": true,
"passwords": true,
"serviceWorkers": true,
"webSQL": true
});
Sessions
• sessions API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
sessions/
332
Chapter 11 Extension and Browser APIs
const recentlyClosed =
await chrome.sessions.getRecentlyClosed();
333
Chapter 11 Extension and Browser APIs
These APIs allow a browser to totally control the tabs and windows
that appear inside a browser. It can perform tasks such as create, remove,
inspect, modify, rearrange, pin, and mute.
chrome.tabs.create({
active: false,
url: "https://www.wikipedia.org",
});
// Close a tab
chrome.tabs.remove(tabId);
// Reload a tab
chrome.tabs.reload(tabId);
// Close a window
chrome.windows.remove(tabId);
334
Chapter 11 Extension and Browser APIs
Debugger
• debugger API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
debugger/
chrome.debugger.attach(
tabId,
"1.0",
() => {}
);
chrome.debugger.sendCommand(
tabId,
"Debugger.enable"
);
chrome.debugger.sendCommand(
tabId,
"Debugger.setBreakpointByUrl",
{
lineNumber: 10,
url: 'https://www.wikipedia.org/script.js'
});
Search
• search API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/search/
335
Chapter 11 Extension and Browser APIs
chrome.search.query(
{ disposition: "NEW_TAB", text: "wikipedia" });
Alarms
• alarms API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/alarms/
Scripting
• scripting API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
scripting/
336
Chapter 11 Extension and Browser APIs
function fooScript() {}
// Inject a function
chrome.scripting.executeScript({
target: { tabId: 123 },
function: fooScript
});
337
Chapter 11 Extension and Browser APIs
DOM
• dom API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/dom/
chrome.dom.openOrClosedShadowRoot(el);
Text to Speech
• tts API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/tts
The tts API allows you to instruct your browser to speak text aloud
using it’s native text-to-speech engine.
Privacy
• privacy API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
privacy/
This API allows an extension to get and set a user’s privacy controls.
338
Chapter 11 Extension and Browser APIs
chrome.privacy.services.searchSuggestEnabled.get(
{},
(details) => {
if (details.levelOfControl ===
'controllable_by_this_extension') {
chrome.privacy.services.searchSuggestEnabled.set({
value: true }
);
}
});
Idle
• idle API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/idle/
This API indicates when the host system has gone idle.
Devtools
• Devtools API – Chrome Developers: https://
developer.chrome.com/docs/extensions/mv3/
devtools/
• Devtools API – MDN: https://developer.mozilla.
org/en-US/docs/Mozilla/Add-ons/WebExtensions/
Extending_the_developer_tools
339
Chapter 11 Extension and Browser APIs
The Devtools API allows you to add custom interfaces to the browser’s
developer tools interface. These interfaces are granted special access to
APIs only accessible from inside the developer tools.
Extension Introspection
• extension API – Chrome Developers: https://developer.
chrome.com/docs/extensions/reference/extension/
340
Chapter 11 Extension and Browser APIs
Extension Management
• management API – Chrome Developers: https://
developer.chrome.com/docs/extensions/reference/
management/
System State
• system APIs – Chrome Developers:
• https://developer.chrome.com/docs/
extensions/reference/system_cpu/
341
Chapter 11 Extension and Browser APIs
• https://developer.chrome.com/docs/
extensions/reference/system_display/
• https://developer.chrome.com/docs/
extensions/reference/system_memory/
• https://developer.chrome.com/docs/
extensions/reference/system_storage/
The system API allows you to inspect certain details about the host
operating system. The cpu, display, memory, and storage properties are all
getters that return a JavaScript object filled with whatever the host system
provides.
Enterprise Only
The following APIs are only usable by extensions force-installed by
enterprise policy and are not covered in this chapter:
• enterprise.deviceAttributes
• enterprise.hardwarePlatform
• enterprise.networkingAttributes
• enterprise.platformKeys
Firefox Only
The following APIs are only usable on Firefox and are not covered in this
chapter:
• captivePortal
• contextualIdentites
• dns
342
Chapter 11 Extension and Browser APIs
• find
• menus
• pcks11
• sidebarAction
• theme
• userScripts
Chrome OS Only
The following APIs are only usable on ChromeOS and are not covered in
this chapter:
• accesibilityFeatures
• audio
• certificateProvider
• fileBrowserHandler
• fileSystemProvider
• input.ime
• loginState
• platformKeys
• printing
• printingMetrics
• restart
• restartAfterDelay
• vpnProvider
343
Chapter 11 Extension and Browser APIs
Deprecated
The following APIs are deprecated and should not be used:
• instanceID
• gcm
Summary
In this chapter, we covered all the various idiosyncrasies of the
WebExtensions API, including promises and callbacks, error handling, and
the events API. Finally, we went through each of the APIs, learning a bit
about what they are for and seeing a code snippet for each.
In the next chapter, we will discuss the permissions that are required
to enable these APIs as well as the best ways to manage permissions in an
extension.
344
CHAPTER 12
Permissions
Browser extension permissions are conceptually identical to mobile
app permissions. Both software platforms have the ability to access very
powerful APIs, but to protect the end user permission to access these
APIs must be explicitly requested on a piecewise basis. For both apps and
extensions, permissions are declared by the developer in the codebase –
for extensions, this is the extension manifest. In both cases, requesting
access to trivial permissions will not require explicit user permission, but
very powerful permissions will require the user to explicitly grant access.
Furthermore, adding additional permissions in a subsequent update will
typically require the user to explicitly accept this change in scope.
When building browser extensions, choosing permissions carefully
is critically important. It has implications on how the marketplace listing
will appear, the install and update flow for your extension, and how your
extension will be reviewed by the marketplace.
Permissions Basics
To understand the basics of adding permissions, let’s examine a very basic
scenario that requires permissions. Begin with the following extension:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"options_ui": {
"open_in_tab": true,
"page": "options.html"
}
}
<!DOCTYPE html>
<html>
<body>
<script src="options.js"></script>
</body>
</html>
Note This example uses an options page for two reasons: 1) because
it is easier to access the console and reload the script, and 2) because
when an error is thrown in the background page in the first turn of the
event loop, the browser will mark the service worker as permanently
idle and refuse to open the background worker developer tools.
346
Chapter 12 Permissions
After loading this extension and opening the options page, you will see
this in the browser console (Figure 12-1).
The reason for this error is that the chrome.storage API requires
the storage permission. Because the permission was not requested, the
browser simply does not define the storage property on the chrome global
object, and the attempted sync() call throws the above error.
This might surprise you, as the entire extension was loaded
without issue. It is important to understand that the browser makes no
assumptions about what permissions might be needed when loading the
extension, or if a requested permission will be needed in the codebase at
all. All permissions handling occurs at runtime.
347
Chapter 12 Permissions
To fix this example, all you must to is add the storage permission as
shown below:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
"permissions": ["storage"]
}
After reloading the extension, the options page will no longer throw the
error. This is because you have successfully added the needed permissions
to use the chrome.storage API.
Checking Permissions
You can programmatically read all the permissions the current execution
context has access to via the chrome.permissions.getAll() method.
Update the options.js script from above to log the extension’s
permissions:
chrome.permissions.getAll().then(console.log);
348
Chapter 12 Permissions
349
Chapter 12 Permissions
350
Chapter 12 Permissions
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
"optional_permissions": ["storage"]
}
351
Chapter 12 Permissions
<!DOCTYPE html>
<html>
<body>
<button id="save">Save</button>
<script src="options.js"></script>
</body>
</html>
const permissions = {
permissions: ["storage"],
};
document.querySelector("#save").addEventListener(
"click",
async () => {
if (!(await chrome.permissions.contains(permissions))) {
await chrome.permissions.request(permissions);
}
chrome.storage.sync.set({ foo: "bar" });
});
document.querySelector("#save").addEventListener(
"click",
async () => {
352
Chapter 12 Permissions
await chrome.permissions.request(permissions);
chrome.storage.sync.set({ foo: "bar" });
});
Host Permissions
Beginning in manifest v3, permissions and host_permissions were
split out into separate fields, with a mirrored split between optional_
permissions and optional_host_permissions. Conceptually, the
difference is straightforward: permissions are the APIs you need access
to, and host_permissions are the origins you need to use the APIs in.
As discussed in the Extension Manifest chapter, the host permissions list
effectively creates a whitelist of domains with extra privileges.
According to MDN, an origin matching the whitelist grants access to
the following:
353
Chapter 12 Permissions
Permissions Lifetime
Once a permission is granted, the extension has that permission for the
entire lifetime of the extension. However, it is possible that the user can
explicitly revoke that permission via the browser’s extension management
page. If the extension is uninstalled and then reinstalled, all the optional
permissions that were formerly granted are lost.
Permissions Warnings
For permissions such as alarms and storage, the APIs they enable are of
little consequence to the user and therefore they can be accessed silently.
Conversely, some permissions are sufficiently powerful and therefore
require the user to explicitly grant access to the extension. This takes the
form of a dialog box with a description of what the permissions entail.
For example, the following is what displays in Google Chrome when
requesting the tabs permission (Figure 12-3).
354
Chapter 12 Permissions
355
Chapter 12 Permissions
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"permissions": ["tabs"]
}
356
Chapter 12 Permissions
357
Chapter 12 Permissions
358
Chapter 12 Permissions
Tip I have found in my experience that this queue will increase your
extension review time from less than 24 hours to a few days.
359
Chapter 12 Permissions
Auto-Disable Updates
One critically important consideration is what happens when required
permissions that will show warnings are added to an extension in an
update. The browser silently updates extensions in the background, so it
won’t show the warning dialog as soon as the update is installed. Instead,
it will silently disable the extension and add a small notification to the
browser toolbar, indicating action is required. To re-enable the extension,
the user must open this notification and accept the new permissions
(Figures 12-8 and 12-9). This flow is shown in the following.
360
Chapter 12 Permissions
Permissions List
The following is a list of all permissions, what they enable, and the warning
message they will incur (if any).
Note For consistency, the APIs are displayed inside the chrome
namespace. This namespace is valid in all Chromium and Firefox
browsers.
activeTab
The activeTab permission is commonly misunderstood – largely in part to
its name. Extension developers were experiencing the following issues:
361
Chapter 12 Permissions
• Executing an action
• Get the URL, title, and favicon for that tab via an API
that returns a tabs.Tab object (essentially the same as
the tabs permission).
362
Chapter 12 Permissions
These extra permissions only last until the tab is navigated or is closed.
This permission is available in Chromium browsers and Firefox.
alarms
• Gives your extension access to the chrome.alarms API
background
• Makes Chrome start up early and shut down late, so
that extensions can have a longer life
bookmarks
• Grants your extension access to the chrome.
bookmarks API
363
Chapter 12 Permissions
browserSettings
• Enables an extension to modify certain global
browser settings. Each property of this API is a
BrowserSetting object, providing the ability to modify
a particular setting
browsingData
• Gives your extension access to the
chrome.browsingData API
captivePortal
• Determine the captive portal state of the user’s
connection. A captive portal is a web page displayed
when a user first connects to a Wi-Fi network
certificateProvider
• Gives your extension access to the chrome.
certificateProvider API
364
Chapter 12 Permissions
clipboardRead
• Required if the extension uses document.
execCommand('paste')
clipboardWrite
• Indicates the extension uses document.
execCommand('copy') or document.
execCommand('cut')
contentSettings
• Grants your extension access to the chrome.
contentSettings API.
365
Chapter 12 Permissions
contextMenus
• Gives your extension access to the chrome.
contextMenus API
contextualIdentities
• List, create, remove, and update contextual identities,
more commonly known as “containers”
cookies
• Gives your extension access to the chrome.cookies API
debugger
• Grants your extension access to the
chrome.debugger API
366
Chapter 12 Permissions
declarativeContent
• Gives your extension access to the chrome.
declarativeContent API
declarativeNetRequest
• Grants your extension access to the chrome.declarati
veNetRequest API
declarativeNetRequestFeedback
• Grants the extension access to events and methods
within the chrome.declarativeNetRequest API which
return information on declarative rules matched
declarativeWebRequest
• Gives your extension access to the chrome.declarativ
eWebRequest API
367
Chapter 12 Permissions
desktopCapture
• Grants your extension access to the
chrome.desktopCapture API
Devtools page
• Technically not a permission, this is an extra warning
that displays in Firefox when devtools_page is used in
the manifest
dns
• Enables an extension to resolve domain names using
the dns API
• Available for Firefox
documentScan
• Gives your extension access to the
chrome.documentScan API
368
Chapter 12 Permissions
downloads
• Grants your extension access to the
chrome.downloads API
downloads.open
• Grants your extension access to only the chrome.
downloads.open API
enterprise.deviceAttributes
• Gives your extension access to the chrome.enterprise.
deviceAttributes API
enterprise.hardwarePlatform
• Gives your extension access to the chrome.enterprise.
hardwarePlatform API
369
Chapter 12 Permissions
enterprise.networkingAttributes
• Gives your extension access to the chrome.enterprise.
networkingAttributes API
enterprise.platformKeys
• Gives your extension access to the chrome.enterprise.
platformKeys API
experimental
• Required if the extension uses any chrome.
experimental.* APIs
fileBrowserHandler
• Gives your extension access to the chrome.fileBrowse
rHandler API
fileSystemProvider
• Gives your extension access to the chrome.fileSystem
Provider API
370
Chapter 12 Permissions
find
• Enable the extension to find text in a web page and
highlight matches with the find API
fontSettings
• Gives your extension access to the
chrome.fontSettings API
gcm
• Gives your extension access to the chrome.gcm API
geolocation
• Allows the extension to use the HTML5 geolocation API
without prompting the user for permission
371
Chapter 12 Permissions
history
• Grants your extension access to the
chrome.history API
Host Permissions
Host permissions can either be requested globally, or for only a subset
of hosts. These permissions can be requested on all browsers, and the
warning message displayed may appear in several different ways.
• http://*/*
• https://*/*
• *://*/*
• <all_urls>
372
Chapter 12 Permissions
The warning displayed will vary based on how many hosts are covered.
A simple single host matcher for foobar.com will display the following
warnings:
373
Chapter 12 Permissions
If more than one host is covered, the browser will adjust the warning
message based on how many hosts are covered. For example, Firefox also
will show the following messages:
identity
• Gives your extension access to the chrome.identity API
idle
• Gives your extension access to the chrome.idle API
loginState
• Gives your extension access to the
chrome.loginState API
management
• Grants the extension access to the
chrome.management API
374
Chapter 12 Permissions
menus
• Add items to the browser’s menu system with the
menus API
menus.overrideContext
• Hide all default Firefox menu items in favor of
providing a custom context menu UI
• Available for Firefox
nativeMessaging
• Gives the extension access to the native messaging API
375
Chapter 12 Permissions
notifications
• Grants your extension access to the
chrome.notifications API
pageCapture
• Grants the extension access to the
chrome.pageCapture API
pkcs11
• The pkcs11 API enables an extension to enumerate
PKCS #11 security modules and to make them
accessible to the browser as sources of keys and
certificates
376
Chapter 12 Permissions
Per Wikipedia:
platformKeys
• Gives your extension access to the
chrome.platformKeys API
power
• Gives your extension access to the chrome.power API
printerProvider
• Gives your extension access to the
chrome.printerProvider API
printing
• Gives your extension access to the
chrome.printing API
377
Chapter 12 Permissions
printingMetrics
• Gives your extension access to the
chrome.printingMetrics API
privacy
• Gives the extension access to the chrome.privacy API
processes
• Gives your extension access to the
chrome.processes API
• Available for Chromium browsers
proxy
• Grants the extension access to the chrome.proxy API
378
Chapter 12 Permissions
scripting
• Gives your extension access to the
chrome.scripting API
search
• Gives your extension access to the chrome.search API
sessions
• Gives your extension access to the
chrome.sessions API
signedInDevices
• Gives your extension access to the
chrome.signedInDevices API
storage
• Gives your extension access to the chrome.storage API
379
Chapter 12 Permissions
system.cpu
• Gives your extension access to the
chrome.system.cpu API
system.display
• Gives your extension access to the chrome.system.
display API
system.memory
• Gives your extension access to the chrome.system.
memory API
system.storage
• Grants the extension access to the chrome.system.
storage API
tabCapture
• Grants the extensions access to the
chrome.tabCapture API
380
Chapter 12 Permissions
tabGroups
• Gives your extension access to the
chrome.tabGroups API
tabHide
• Allows the extension to use the tabs.hide() API
tabs
• Grants the extension access to privileged fields of the
Tab objects used by several APIs including chrome.tabs
and chrome.windows
• Available for Chromium browsers and Firefox
381
Chapter 12 Permissions
theme
• Enables browser extensions to update the
browser theme
topSites
• Grants the extension access to the
chrome.topSites API
tts
• Gives your extension access to the chrome.tts API
ttsEngine
• Grants the extension access to the
chrome.ttsEngine API
382
Chapter 12 Permissions
unlimitedStorage
• Provides an unlimited quota for storing client-side data,
such as databases and local storage files. Without this
permission, the extension is limited to 5 MB of local storage
vpnProvider
• Gives your extension access to the
chrome.vpnProvider API
wallpaper
• Gives your extension access to the
chrome.wallpaper API
webNavigation
• Grants the extension access to the
chrome.webNavigation API
383
Chapter 12 Permissions
webRequest
• Gives your extension access to the
chrome.webRequest API
webRequestBlocking
• Required if the extension uses the chrome.webRequest
API in a blocking fashion
Summary
In this chapter, you learned about what permissions are, what they do, and
how to declare them. The chapter also covered the difference between
required, optional, and host permissions. It also explained all the tricky
implications with selecting and changing permissions.
In the next chapter, we will cover all the different aspects of how
extensions can use and modify the browser’s network capabilities, as
well as how it can creatively use content scripts to act as an agent of the
authenticated user. This chapter also covers the important differences
between webRequest and declarativeNetRequest.
384
CHAPTER 13
Networking
As you wade deeper into browser extension development, you will quickly
realize that there are some interesting idiosyncrasies when it comes
to sending network requests. Although browser extensions and web
pages use the same APIs and network layers to send requests, there are
fundamental differences that you will need to surmount when building a
well-formed browser extension. This chapter will cover some core areas of
extension networking: dispatching network requests, authentication, and
browser extension networking APIs.
Remote assets Remote assets can Remote assets must be loaded with a
be served from the cross-origin policy correctly configured.
same origin without Scripts cannot be executed from
configuring a cross- remote origins.
origin policy.
Page types All web pages Popup/options pages, content scripts,
on a website can and background scripts all have
send requests and different restrictions for sending
authenticate in requests, for example, content scripts
consistent fashion. are subject to the host page’s cross-
origin restrictions, but popups are not.
Server requests The website can send If a browser extension uses a backend
same-origin requests to server, the requests will always be
the backend. cross-origin.
Authentication No restrictions: the Not all forms of authentication
website can use cookie work everywhere, for example,
auth, JWT auth, or service workers can’t use cookie
OAuth anywhere. authentication.
Long-running Any long-running Long-running requests in background
requests requests will stay alive service workers, popups, and content
while a tab is open. scripts may all be unexpectedly
terminated, for example, a popup is
closed or a service worker is terminated.
Cross-browser The website origin The browser extension origin
remains consistent is different across browsers:
across browsers. chrome-extension://
extensionID vs. moz-
extension://uuid.
386
Chapter 13 Networking
Networking Architecture
When it comes to sending network requests, different components of
browser extensions are better than others for certain tasks. How you
dispatch your network requests will depend on the nature of your browser
extension. This section will cover some things to consider when designing
your extension.
Options Pages
Options pages are nearly identical to a traditional website, and therefore
they are a natural choice for many developers when building a browser
extension. If your entire user interface is built inside options pages, you
will not have any problem using any form of authentication, be it cookie
auth, JSON Web Token (JWT) auth, or OAuth. Unless the options page is
rendered as a modal (open_in_tab=false), it will enjoy the same lifecycle
as a browser tab, so long-running requests can be safely dispatched from
an options page without risk of early termination.
The options page is a good option as a supplement for browser
extensions that wish to rely heavily on a background script, but also wish
to execute long-running requests. The background script can kick open an
options tab as a vehicle for the long-running requests.
387
Chapter 13 Networking
Content Scripts
Content scripts are a particularly interesting tool for sending network
requests. Because they are running on the host page, they are subject
to the same cross origin restrictions. This means talking to your own
server will likely require delegating that request to the background
script. However, because content script requests are treated as host page
requests, they can use the host page’s cookies – meaning you can send
requests as the authenticated user.
Authenticating inside the content script is tricky, as the host page
can see anything placed into the DOM or in a shared API. Furthermore,
long-running network requests are obviously dependent on the host page
remaining open; if the tab closes or the user navigates away, the request or
websocket will be abruptly terminated.
388
Chapter 13 Networking
Background Scripts
The transition from manifest v2 to manifest v3 was especially challenging
for background scripts, as the network request paradigm changed in
substantial and breaking ways. Previously, the background script existed
as a persistent headless web page, meaning that cookie auth, long-running
requests, and auth dialog windows were allowed. Now, all these things
have materially changed:
Background scripts are still an excellent and very capable tool, but with
manifest v3 they are certainly more limited. Extensions that use JWT or
OAuth authentication and do not rely on long-running requests will be able
to seamlessly utilize background service worker networking without issue.
Pinning an Extension ID
In the context of extension networking, it is useful to be able to predict and
control the origin of a browser extension. When developing locally, the
browser will assign your extension an ID automatically; when uploading
to the Chrome web store, it will assign your extension an entirely different
ID. For developers that wish to have a consistent ID, this is problematic.
389
Chapter 13 Networking
390
Chapter 13 Networking
Figure 13-1. The Chrome Web Store package page including the
public key link. Note the ID beginning with “jnof”
391
Chapter 13 Networking
Authentication Styles
The web wouldn’t have gotten far without the ability to authenticate.
Browser extensions can use the same foundational technologies as
websites to authenticate. However, due to the innate architecture of
extensions, the way you go about authenticating must be carefully
considered. This section discusses different modalities of authentication.
No Authentication
Browser extensions don’t necessarily need to authenticate. Often, a clever
application of a small portion of the WebExtensions API is all that is
needed to make an extension useful. What’s more, when a content script
392
Chapter 13 Networking
interacts with the raw DOM, the browser extension’s ability to interact with
authenticated sessions is totally disjoint from any concept of a logged in
state. In other words, the host website can do the heavy lifting of logging in
the user, the browser extension can wait for a logged in state, and then the
content script can read from and write to the DOM.
Some APIs allow you to take advantage of native browser identity
separation without managing authentication. For example, the storage API
allows you to use chrome.storage.sync, which is attached to the user’s
browser account and will be synced to their account, or chrome.storage.
local, which is localized to the current browser instance.
Cookie Authentication
Cookie authentication is technically possible for browser extension user
interfaces, but I don’t recommend it. This style of authentication works best
when the web page is rendered directly by a server, and for browser extensions
this is never the case. Furthermore, a cookie-based authenticated state can’t be
directly accessed by the background service worker. Overall, because it involves
so much unnecessary overhead to implement, I recommend you avoid cookie
authentication unless you have a particularly compelling reason to do so.
393
Chapter 13 Networking
394
Chapter 13 Networking
395
Chapter 13 Networking
396
Chapter 13 Networking
In other words, the browser gives this URL special treatment in order to
support authentication in the context of browser extensions. All chromium
browsers and Firefox support this method.
Tip You can see this special URL handling logic in the chromium
source code: https://chromium.googlesource.com/
chromium/chromium/+/master/chrome/browser/
extensions/api/identity/web_auth_flow.cc.
397
Chapter 13 Networking
398
Chapter 13 Networking
Not all platforms will support this distinction. In the case of Google
OAuth, both “Chrome app” and “Web application” can be used for browser
extensions.
399
Chapter 13 Networking
400
Chapter 13 Networking
401
Chapter 13 Networking
{
"installed": {
"client_id": "<some_id>.apps.googleusercontent.com",
402
Chapter 13 Networking
"project_id": "browser-extension-explorer",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/
oauth2/v1/certs"
}
}
Additional Help
Setting up OAuth is extremely confusing. I’ve hand-selected some links
that walk you through setting it up in various ways:
• https://developer.chrome.com/docs/extensions/
mv3/tut_oauth/
• https://blog.plasmo.com/p/firebase-chrome-
extension
403
Chapter 13 Networking
• Note that nowhere in this flow are any URLs other than
what is provided in the manifest. The redirect URL and
OAuth endpoints are all automatically handled by the
browser!
404
Chapter 13 Networking
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {},
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["identity", "identity.email"],
"oauth2": {
"client_id": "YOUR_CLIENT_ID",
"scopes": [
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile"
]
}
}
chrome.action.onClicked.addListener(() => {
chrome.identity.getAuthToken(
{
interactive: true,
},
(token) => {
if (token) {
chrome.identity.getProfileUserInfo(
{ accountStatus: "ANY" },
(info) => console.log(info)
405
Chapter 13 Networking
);
}
}
);
});
// Console output:
// { "email": <google_email>, "id": <google_gaia_id> }
Note This section assumes you have basic knowledge of how the
OpenID Connect protocol works. For an introduction to OpenID, Auth0
has an excellent writeup on it: https://auth0.com/intro-to-
iam/what-is-openid-connect-oidc/.
406
Chapter 13 Networking
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {},
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["identity"]
}
chrome.action.onClicked.addListener(() => {
const clientId = "YOUR_CLIENT_ID";
const extensionRedirectUri = chrome.identity.
getRedirectURL();
const nonce = Math.random().toString(36).substring(2, 15);
407
Chapter 13 Networking
chrome.identity.launchWebAuthFlow(
{
url: authUrl.href,
interactive: true,
},
(redirectUrl) => {
if (redirectUrl) {
// The ID token is in the URL hash
const urlHash = redirectUrl.split("#")[1];
const params = new URLSearchParams(urlHash);
const jwt = params.get("id_token");
console.log(token);
}
}
);
});
// Console output:
// {
// "iss": "https://accounts.google.com",
// "azp": "...",
408
Chapter 13 Networking
// "aud": "...",
// "sub": "...",
// "email": "[email protected]",
// "email_verified": true,
// "nonce": "...",
// "name": "Matt Frisbie",
// "picture": "...",
// "given_name": "Matt",
// "family_name": "Frisbie",
// "locale": "en",
// "iat": ...,
// "exp": ...,
// "jti": "..."
// }
409
Chapter 13 Networking
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {},
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["identity"]
}
chrome.action.onClicked.addListener(() => {
const clientId = "YOUR_CLIENT_ID";
const extensionRedirectUri = chrome.identity.getRedirectURL();
authUrl.searchParams.set("client_id", clientId);
authUrl.searchParams.set("redirect_uri", extensionRedirectUri);
chrome.identity.launchWebAuthFlow(
{
url: authUrl.href,
interactive: true,
},
410
Chapter 13 Networking
console.log(await r.json());
411
Chapter 13 Networking
}
}
);
});
// Console output:
// {
// "login": "msfrisbie",
// "id": ...,
// "node_id": "...",
// "avatar_url": "...",
// "name": " Matt Frisbie",
// "blog": "https://www.mattfriz.com",
// "location": "Chicago, IL",
// "bio": "Software engineer, bestselling author",
// "twitter_username": "mattfriz",
// "url": "https://api.github.com/users/msfrisbie",
// ...
// }}
Networking APIs
Browser extensions are granted access to some powerful APIs that can be
used to inspect and modify traffic flow inside the browser. These methods
are the reason ad blocking extensions are so effective. This section will
examine three APIs:
412
Chapter 13 Networking
• onBeforeNavigate
• onCommitted
• onCompleted
• onCreatedNavigationTarget
• onDOMContentLoaded
• onErrorOccurred
413
Chapter 13 Networking
• onHistoryStateUpdated
• onReferenceFragmentUpdated
• onTabReplaced
Most of the time, extensions will only care to set handlers for the
primary lifecycle events fired by normal browser navigation:
1. onBeforeNavigate
2. onCommitted
3. onDOMContentLoaded
4. onCompleted
414
Chapter 13 Networking
The following simple extension will log to the extension console any
time a tab in the browser visits a new URL. Load the extension, open a new
tab, and navigate to any website to see navigation events in real time.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"permissions": ["webNavigation"]
}
chrome.webNavigation.onCompleted.addListener((details) => {
console.log(details);
});
415
Chapter 13 Networking
// "url": "https://en.wikipedia.org/wiki/Main_Page"
// }
• onActionIgnored
• onAuthRequired
• onBeforeRedirect
• onBeforeRequest
• onBeforeSendHeaders
• onCompleted
• onErrorOccurred
• onHeadersReceived
• onResponseStarted
• onSendHeaders
416
Chapter 13 Networking
Modifying Requests
If you merely wish to inspect the request lifecycle event data, only the
webRequest permission is needed. If, however, you wish to potentially
modify these requests, you will need an additional webRequestBlocking
permission. This grants you the ability to perform the following tasks:
417
Chapter 13 Networking
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 2,
"background": {
"scripts": ["background.js"]
},
"permissions": [
"webRequest",
418
Chapter 13 Networking
"webRequestBlocking",
"<all_urls>"
]
}
chrome.webRequest.onBeforeRequest.addListener(
() => {
return {
cancel: true,
};
},
{
urls: [
"*://www.wikipedia.org/portal/wikipedia.org/assets/img/*"
],
},
["blocking"]
);
Fragmentation
The browser extension space is headed in a bizarre direction. At the time
this book was written, it appears that Chromium browsers have decided
to end support for webRequest in manifest v3, but Firefox may continue
to support the API in manifest v3. It is unclear where the space will end
up, but at the moment I would not recommend building an extension that
relies directly upon this API.
419
Chapter 13 Networking
Permissions
The declarativeNetRequest API can be used with three different
permissions:
420
Chapter 13 Networking
• The declarativeNetRequestWithHostAccess
permission can be used instead of the
declarativeNetRequest permission. It allows you
to block or upgrade requests on any origin without
requesting host permissions. Redirects or header
modification will still require specific host permissions.
421
Chapter 13 Networking
Whether static or dynamic, a DNR rule is treated the same by the browser.
Static Rulesets
Let’s examine static rulesets by examining the following simple extension
that can block images on Wikipedia:
{
"name": "MVX",
"version": "0.0.1",
422
Chapter 13 Networking
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"declarative_net_request": {
"rule_resources": [
{
"id": "ruleset_1",
"enabled": true,
"path": "rules_1.json"
}
]
},
"action": {},
"permissions": ["declarativeNetRequest"],
"host_permissions": [
"*://*.wikipedia.org/*",
"*://*.wikimedia.org/*"
]
}
chrome.action.onClicked.addListener(async () => {
const enabled_rulesets =
await chrome.declarativeNetRequest.getEnabledRulesets();
if (enabled_rulesets.includes(RULESET_ID)) {
chrome.declarativeNetRequest.updateEnabledRulesets({
disableRulesetIds: [RULESET_ID],
423
Chapter 13 Networking
});
} else {
chrome.declarativeNetRequest.updateEnabledRulesets({
enableRulesetIds: [RULESET_ID],
});
}
console.log("Toggled ruleset");
});
[
{
"id": 1,
"priority": 1,
"action": {
"type": "block"
},
"condition": {
"domains": ["wikipedia.org", "wikimedia.org"],
"resourceTypes": ["image"]
}
}
]
424
Chapter 13 Networking
You may notice that a new _metadata directory has appeared inside
your local development folder (Figure 13-13). As the name suggests, the
binary file inside the directory is how the browser keeps track of active
rulesets. Feel free to ignore it but ensure you do not commit it to version
control or add it to a production build.
D
ynamic Rules
Dynamic rules are very similar to static rulesets. In similar fashion, they
can be queried, added, and removed on demand. In this example, let’s
instead write a dynamic redirect rule:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"action": {},
"permissions": ["declarativeNetRequest"],
425
Chapter 13 Networking
const RULE_ID = 1;
const RULE_1 = {
id: RULE_ID,
priority: 1,
action: {
type: "redirect",
redirect: {
url: "https://upload.wikimedia.org/wikipedia/commons/
thumb/b/b1/Hot_dog_with_mustard.png/1920px-Hot_dog_with_
mustard.png",
},
},
condition: {
domains: ["wikipedia.org", "wikimedia.org"],
resourceTypes: ["image"],
},
};
chrome.action.onClicked.addListener(async () => {
const dynamic_rules = await
chrome.declarativeNetRequest.getDynamicRules();
426
Chapter 13 Networking
} else {
chrome.declarativeNetRequest.updateDynamicRules({
addRules: [RULE_1],
});
}
console.log("Toggled rule");
});
Summary
In this chapter, we explored a variety of modalities in which browser
extensions can send network requests. We reviewed all the different
components and how they can and should send network requests. Next,
we discussed authentication strategies, including in-depth coverage on
how to implement OAuth in browser extensions. Finally, we discussed how
to use the major WebExtensions APIs that can inspect and manipulate
traffic in the browser.
In the next chapter, we will cover how to develop an extension locally,
publish it on extension marketplaces, and deploy updates.
427
CHAPTER 14
Extension
Development
and Deployment
The process of developing, testing, and publishing a browser extension
is significantly different from developing a traditional website. The
steps involved more closely resemble that of a mobile app than a web
technology. Gaining a better understanding of how to effectively develop,
publish, and distribute your extension is critical to mastery of the subject.
Local Development
You’ll spend quite a lot of time developing your extension locally, so it’s
worth getting familiar with how all the parts fit together. In this section,
we’ll explore how a locally loaded extension works and all the different
ways you can look under the hood.
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"background": {
"service_worker": "background.js",
"type": "module"
},
"options_ui": {
"open_in_tab": true,
"page": "options.html"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
"css": [],
"js": ["content-script.js"]
}
],
430
Chapter 14 Extension Development and Deployment
"permissions": [
"scripting",
"declarativeNetRequest",
"tabs"
],
"host_permissions": ["<all_urls>"]
}
<!DOCTYPE html>
<html>
<body>
<h1>Options</h1>
<script src="options.js"></script>
</body>
</html>
console.log("Initialized options!");
console.log("Initialized background!");
Once this extension is loaded locally, your browser will add a card
inside the browser extension management view. In Google Chrome, the
URL for this page is chrome://extensions. An example card is shown in
Figure 14-1.
431
Chapter 14 Extension Development and Deployment
From this card, you’re able to view the details page, uninstall or disable
the extension, view the extension’s errors page, and reload the extension.
Clicking to open the details page will reveal something like Figure 14-2.
432
Chapter 14 Extension Development and Deployment
433
Chapter 14 Extension Development and Deployment
434
Chapter 14 Extension Development and Deployment
Note here that the console is showing log output for both the current
tab console output and the background service worker. If you wish to split
out console outputs, the developer console allows you to select specific
sources (Figure 14-4).
435
Chapter 14 Extension Development and Deployment
Chrome offers a real-time view of your service worker status and log
output at chrome://serviceworker-internals/ (Figure 14-6). This page
will not interfere with how your service workers behave, allowing them to
go idle.
436
Chapter 14 Extension Development and Deployment
437
Chapter 14 Extension Development and Deployment
File Changes
In the extension detail view shown above, it indicates where in the
filesystem the extension was loaded from. This is because the extension
is serving files directly from that directory! Any changes you make to the
source files will be immediately reflected the next time the extension loads
them via network request. For example, a modification to popup.html will
show the very next time the popup is opened. This does not apply to files
that the extension uses only on install such as manifest.json. Updates to
these files will only be reflected upon a formal extension reload.
438
Chapter 14 Extension Development and Deployment
Error Monitoring
Monitoring errors during development can be tricky. For views that have
HTML interfaces, errors thrown in the page can be monitored via the
normal developer tools console. All errors thrown in an extension, no
matter where they are thrown, will appear in the extension error view
(Figures 14-8, 14-9 and 14-10).
Figure 14-8. The Errors button will only appear when the extension
throws its first error. This button will open the extensions error view
439
Chapter 14 Extension Development and Deployment
440
Chapter 14 Extension Development and Deployment
Extension Reloads
There are several different points at which portions of the extension are
reloaded:
441
Chapter 14 Extension Development and Deployment
442
Chapter 14 Extension Development and Deployment
Unit Tests
Because unit tests don’t rely on extension components rendering inside
their native browser containers, these tests can be written in a nearly
identical fashion to regular web page unit tests. To stub out the chrome
extension APIs, the sinon-chrome package is excellent: https://github.
com/acvetkov/sinon-chrome. This package allows you to stub out the
callback values for extension APIs in a very clean manner.
A popular unit test setup for extensions is to use Parcel as the main
build tool, React as the framework, and Jest as the test runner. The
following example sets up a very simple extension with a popup that reads
from the chrome storage API. The example includes some unit tests that
make sure the popup renders as expected:
{
"name": "MVX",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html"
},
"permissions": ["storage"]
}
{
"scripts": {
"start": "parcel watch manifest.json –host localhost",
"build": "parcel build manifest.json",
"test": "jest –watch"
},
443
Chapter 14 Extension Development and Deployment
"dependencies": {
"@types/chrome": "^0.0.196",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
"devDependencies": {
"@babel/preset-env": "^7.18.10",
"@babel/preset-react": "^7.18.6",
"@parcel/config-webextension": "^2.7.0",
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"jest": "^29.0.2",
"jest-environment-jsdom": "^29.0.2",
"parcel": "^2.7.0",
"process": "^0.11.10",
"sinon-chrome": "^3.0.1"
},
"jest": {
"testEnvironment": "jsdom"
}
}
<!DOCTYPE html>
<html>
<body>
<div id="app"></div>
<script type="module" src="index.tsx"></script>
</body>
</html>
444
Chapter 14 Extension Development and Deployment
root.render(<Popup />);
{
"extends": "@parcel/config-webextension",
"transformers": {
"*.{js,mjs,jsx,cjs,ts,tsx}": [
"@parcel/transformer-js",
"@parcel/transformer-react-refresh-wrap"
]
}
}
{
"presets": [
["@babel/preset-env", { "targets": { "node":
"current" } }],
"@babel/preset-react"
]
}
445
Chapter 14 Extension Development and Deployment
useEffect(() => {
chrome.storage.sync.get(["count"], (result) => {
if (typeof result.count !== "number") {
result.count = 0;
}
setCount(result.count);
});
}, []);
describe("Popup", () => {
beforeAll(() => {
global.chrome = chrome;
});
446
Chapter 14 Extension Development and Deployment
expect(chrome.storage.sync.get.withArgs(["count"]).
calledOnce).toBe(true);
});
render(<Popup />);
render(<Popup />);
afterAll(() => {
chrome.flush();
});
});
Tip If you are setting this example up from scratch, running yarn
install should get this setup working.
447
Chapter 14 Extension Development and Deployment
For developers already familiar with how typical React unit testing
looks, most of this should not surprise you. Some things to note about this
implementation
With all the packages installed, you should find that npm run start
will start up a Parcel development build, and npm test will run your unit
test suite (Figure 14-11).
448
Chapter 14 Extension Development and Deployment
Integration Tests
Integration testing browser extension is made possible via Puppeteer
(https://pptr.dev/). It allows you to programmatically control a
Chromium browser, including navigating to URLs and dispatching user
events. It cannot perfectly replicate all the different ways that a browser
extension will render its views, but it is by far the best tool available to
power an integration test suite.
Puppeteer can be configured to load an extension via the --load-
extension flag. This flag should be pointed to a local directory on your
machine that contains your extension code. You will also need to disable
headless mode, as extension cannot be loaded in a headless browser:
This will boot up a Chromium browser, but you’ll need to direct the
browser to a URL for your extension. Of course, you will need to know
ahead of time what the extension’s ID is. A good strategy for this is to
control the extension ID via the manifest’s key field:
449
Chapter 14 Extension Development and Deployment
Because all extension views, including popup pages, can be loaded via
direct extension URL, all your extension pages can be tested in this way.
Puppeteer can be used to run any Chromium browser, so an integration
test suite can target browsers like Google Chrome, Microsoft Edge,
or Opera.
Additional Reading
Setting up automated testing is tricky, and frankly, annoying. The following
are some links to blog posts and documentation about various extension
testing setups:
• https://www.streaver.com/blog/posts/testing-
web-extensions
• https://github.com/clarkbw/jest-
webextension-mock
• https://www.npmjs.com/package/jest-chrome
• https://medium.com/information-and-technology/
unit-testing-browser-extensions-bdd4e60a4f3d
• https://medium.com/information-and-technology/
integration-testing-browser-extensions-with-
jest-676b4e9940ca
• https://gokatz.me/blog/automate-chrome-
extension-testing/
450
Chapter 14 Extension Development and Deployment
• https://tweak-extension.com/blog/complete-
guide-test-chrome-extension-puppeteer/
• https://pptr.dev/#working-with-chrome-
extensions
Publishing Extensions
To publish an extension to the Chrome Web Store, you’ll first need to pay
the one-time $5 fee for a developer account. Once this account is set up,
you’re ready to submit. Extensions are always uploaded as a zip file of your
extension’s files and assets.
Store Listing
To publish an extension, you’ll need to specify how it should appear to
users in the Chrome Web Store. Some items are automatically extracted
from the manifest:
• Extension title
• Extension summary
• Icon
• Description
• Extension Category
451
Chapter 14 Extension Development and Deployment
Note These fields can be changed later, but each change will make
the entire extension undergo a slow manual review – even if the code
was not changed.
Privacy Practices
In the Chrome Web Store, you will need to provide a high-level description
of what this extension is doing, as well as a justification for each
permission category that is considered sensitive (identity, scripting, etc.).
You will also need to indicate if and how you are tracking your users, and
in what way.
Review Process
Your extension will undergo manual review the first time it is submitted to
ensure it meets Chrome Web Store standards. Once it’s approved, it will be
available for anyone to install!
Updating Extensions
Once your extension is published, the process of updating it is more or less
identical to the initial publishing process. You’ll upload a zip file with an
updated version number in the manifest. Any additional permissions will
require additional justification.
Update Considerations
Once you have an existing userbase and want to push out an update, there
are some things you should keep in mind.
452
Chapter 14 Extension Development and Deployment
Update Delays
Updates are not rolled out immediately. Once you submit an update, it
must be reviewed and approved before a user’s browser will be able to
install it. The user’s browser will periodically send out update checks every
few hours and download the update if there is one available. However, the
browser will lazily apply that update if the extension is active, so there is
sometimes a considerable delay between when an update is published and
when it is installed. This delay can be between a few minutes and upwards
of a week.
Auto-Disabling
Suppose you have a collection of users with your extension installed. If
a new version is published that requires a new permission that needs
explicit user approval, your extension will be disabled until the user grants
that permission. The browser will not pop up a dialog when the extension
is disabled, only a small icon will appear. This means that you may suffer
unintentional user attrition when adding addition permissions. Using
optional permissions can solve this problem.
Cancelling Updates
When this book was written, it was unfortunately still not possible to
cancel an update once it is submitted for review. Be very careful when
submitting for review, as a mistake may mean you must wait days before a
fix can be submitted.
453
Chapter 14 Extension Development and Deployment
https://developer.chrome.com/docs/webstore/using_webstore_api/
Once you have the required credentials, you can manage a significant
portion of your extension via API, documented here:
https://developer.chrome.com/docs/webstore/api_index/
Interacting with the API via command line is clunky. Instead, use an
NPM package to push your updates:
https://github.com/simov/chrome-webstore
454
Chapter 14 Extension Development and Deployment
Dashboard Metrics
The Chrome Developer Dashboard shows you several important metrics
about your extension, including:
• Users/day
• Impressions/day
• Installs/day
• Uninstalls/day
Note The number of weekly users you see in the Chrome Web
Store is the amount of users whose Chrome browser has checked for
an update of your app within the last week. It is not the number of
people who have installed your item.
Analytics Libraries
Tracking user activity in a browser extension is mostly the same as tracking
web page activity. There are a few wrinkles to consider:
• Events in a content script should be tracked in
a background service worker. Outgoing network
requests to send analytics data are subject to
adblockers and cross-origin restrictions.
455
Chapter 14 Extension Development and Deployment
Set Up ga.js
For a manifest v3 extension, you will need to download the ga.js script
(https://www.googletagmanager.com/gtag/js?id=GA_TRACKING_ID) and
save it as a local script inside your extension.
Google Analytics checks the URL protocol of the active page before
deciding to send analytics pings. If it isn’t http or https, it will decline to
send. When using the GA3 script, it was possible to override this behavior
via checkProtocolTask:
After doing this, you should be able to set up Google Analytics as follows:
456
Chapter 14 Extension Development and Deployment
script.async = true;
script.src = "/ga.js";
document.body.appendChild(script);
This is a bit of a hack, but at the time this book was published it is the
only known way of getting the GA4 script to work nicely with browser
extensions.
background.js
chrome.runtime.onInstalled.addListener((details) => {
457
Chapter 14 Extension Development and Deployment
You won’t be able to execute code on an uninstall, but you are able
to direct the user to a URL of your choosing with chrome.runtime.
setUninstallURL:
chrome.runtime.setUninstallURL("https://foobar.com/survey");
Summary
In this chapter, we discussed a number of topics involved with building
and publishing an extension. We discussed a range of tactics that can be
used to better understanding what is going on inside your local extension.
Next, we went through the different testing formats that work well for
browser extensions. We went through all the important parts involved with
publishing and updating extensions in the Chrome Web Store. Finally, we
discussed how analytics tools can be effectively folded into your browser
extension.
In the next chapter, we will cover all the details involved with building
a browser extension that targets more than one browser.
458
CHAPTER 15
Cross-Browser
Extensions
When it comes to supporting multiple platforms, browser extensions
exist somewhere in between websites and mobile apps. One end of the
spectrum is the website, which enjoys a “write-once-run-anywhere”
developer experience. On the other end of the spectrum is the mobile app,
which is written in a proprietary language and will only work on a specific
operating system and a subset of device types. In this chapter, we will
discuss what challenges and tradeoffs are involved when supporting an
extension on multiple browsers.
Browser Share
Fortunately, for extensions that are just getting started, you can very easily
support a majority of users with a single codebase. Consider the following
metrics for desktop browser share in 2022 (Figure 15-1).
460
Chapter 15 Cross-Browser Extensions
461
Chapter 15 Cross-Browser Extensions
API Probing
Not all browsers support the full WebExtensions API. If a particular
method is critical to your extension, and a browser does not support it,
then there is little you can do. In situations like these, it is probably best
to not support the browser, and encourage the user to use a supported
browser.
If, however, the API you wish to use is not critical, and the extension
can function without it (even in a degraded state), then you can safely
probe for an API or a method to check if it is available. For example:
if (chrome.storage.session) {
// API is available, proceed normally
} else {
// API is not available. UX options:
// - Fall back to similar API
// - Disable or hide buttons
// - Notify user
// - Silently degrade
}
462
Chapter 15 Cross-Browser Extensions
Differential Manifests
When working in development mode, you will find that most browsers are
relatively permissive when it comes to manifest structure. However, when
deploying to a marketplace, you may run into a situation where there are
strict incompatibilities between what two different marketplaces allow or
disallow.
To address this, you will need to generate differential manifests for
each deployment bundle. A common pattern is to maintain a single
“master” manifest, and then perform ad-hoc modifications specific to each
marketplace when generating the deployment bundles.
Manifest v2/v3
At the time this book was published, extension developers are stuck in
an unenviable position where some marketplaces like the Chrome Web
Store are only accepting manifest v3, and others like the Firefox Addon
marketplace are only accepting manifest v2. It seems very likely that this
status quo is transitory, and soon all major marketplaces will coalesce
around manifest v3. I would not recommend splitting support between
manifest v2 and v3, the chasm between how the two behave is not worth
bridging; you should target support for manifest v3.
463
Chapter 15 Cross-Browser Extensions
Extension Marketplaces
Just as mobile apps must be installed from an app store, extensions must
be installed from an extension marketplace. All major browsers maintain
their own extension store.
Marketplace Similarities
The following is a list of features that are consistent between extension
marketplaces:
464
Chapter 15 Cross-Browser Extensions
Marketplace Differences
The following is a list of features that are different between extension
marketplaces:
465
Chapter 15 Cross-Browser Extensions
466
Chapter 15 Cross-Browser Extensions
467
Chapter 15 Cross-Browser Extensions
468
Chapter 15 Cross-Browser Extensions
Opera Addons
https://addons.opera.com/
It is free to publish extensions to the Opera extensions marketplace
(Figure 15-6).
469
Chapter 15 Cross-Browser Extensions
Mobile Extensions
Support for extensions is limited on mobile devices. Major browsers like
Google Chrome do not support it on any OS, and Apple forbids non-Safari
browsers from installing third-party extensions on iOS. However, on both
Android and iOS devices it is still possible to install browser extensions
if you are using the appropriate browser. API support is mixed on mobile
devices.
470
Chapter 15 Cross-Browser Extensions
and render. However, due to the mobile form factor, there are some UX
differences to consider:
Kiwi Browser
If you’re looking to install Chrome extensions on an Android device, the
best solution is to use the Kiwi Browser (Figure 15-7). The browser is based
on Webkit and Chromium, and you can directly install extensions from the
Chrome Web Store. Using extensions in the Kiwi browser is only possible
on Android.
471
Chapter 15 Cross-Browser Extensions
Firefox Mobile
Extensions published on the Firefox marketplace can choose to be
installable for Firefox mobile (Figures 15-8 and 15-9). You can directly
install extensions from the Firefox Add-ons marketplace. Extensions on the
Firefox Mobile browser are only possible on Android.
472
Chapter 15 Cross-Browser Extensions
473
Chapter 15 Cross-Browser Extensions
iOS Safari
Safari extensions can be installed on iOS devices through the App Store.
These are structured differently than other mobile extensions. Publishing
474
Chapter 15 Cross-Browser Extensions
Figure 15-10. The Safari browser menu showing the options for an
installed Extension
475
Chapter 15 Cross-Browser Extensions
Automated Deployment
As of 2022, all major extension marketplaces support automated
deployment. The details of deploying to each store are out of the scope
of this book, but you can find links to the documentation for each
marketplace:
476
Chapter 15 Cross-Browser Extensions
477
Chapter 15 Cross-Browser Extensions
Prerequisites
Safari browser extensions are developed and installed as apps and
published to the App Store. Therefore, they must be built on Apple’s app
development infrastructure. You will need the following to develop and
publish a Safari extension:
478
Chapter 15 Cross-Browser Extensions
Architecture
According to the Safari developer documentation:
A Safari web extension consists of three parts that operate
independently in their own sandboxed environments:
–– A macOS or iOS app that can have a user interface
–– JavaScript code and web files that work in
the browser
–– A native app extension that mediates between the
macOS or iOS app and the JavaScript code
This may sound confusing at first. To better understand how each of
these parts works together, let’s generate a new Xcode extension project,
install it on an Apple device, and analyze each piece.
479
Chapter 15 Cross-Browser Extensions
480
Chapter 15 Cross-Browser Extensions
• iOS (App) and macOS (App) are the formal apps that
the OS will install. On iOS, this will show up as an app
tile on the home screen. The generated version of these
apps is just a simple web view.
481
Chapter 15 Cross-Browser Extensions
Note Xcode has separate build targets for iOS and macOS. This
should make sense; although these apps share code, you are
developing two entirely separate apps for two separate platforms.
• Use manifest v3
• Exchange message between the popup, background,
and content script
{
"manifest_version": 3,
"default_locale": "en",
482
Chapter 15 Cross-Browser Extensions
"name": "__MSG_extension_name__",
"description": "__MSG_extension_description__",
"version": "1.0",
"icons": {
"48": "images/icon-48.png",
"96": "images/icon-96.png",
"128": "images/icon-128.png",
"256": "images/icon-256.png",
"512": "images/icon-512.png"
},
"options_ui": {
"page": "options.html"
},
"background": {
"service_worker": "background.js"
},
"content_scripts": [{
"js": [ "content.js" ],
"matches": [ "<all_urls>" ]
}],
"action": {
"default_popup": "popup.html",
"default_icon": {
"16": "images/toolbar-icon-16.png",
"19": "images/toolbar-icon-19.png",
"32": "images/toolbar-icon-32.png",
"38": "images/toolbar-icon-38.png",
"48": "images/toolbar-icon-48.png",
"72": "images/toolbar-icon-72.png"
}
},
483
Chapter 15 Cross-Browser Extensions
options.html
<!DOCTYPE html>
<html>
<head>
<meta name="viewport" content="width=device-width">
<meta charset="UTF-8">
</head>
<body>
<h1>I'm the options page!</h1>
</body>
</html>
popup.html
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="popup.css">
<script type="module" src="popup.js"></script>
</head>
<body>
<h1>I'm the popup page!</h1>
484
Chapter 15 Cross-Browser Extensions
popup.js
document.querySelector("#set-bg-color").
addEventListener("click", () => {
const randomColor = "#" + Math.floor(Math.random()*(2 **
24 - 1)).toString(16);
chrome.tabs.query({
active: true,
currentWindow: true
}, (tabs) => {
chrome.tabs.sendMessage(
tabs[0].id,
{ backgroundColor: randomColor },
(response) => console.log(response)
);
});
});
document.querySelector("#send-native-message").
addEventListener("click", () => {
chrome.runtime.sendNativeMessage(
"com.matt-frisbie.MVX",
{msg: "foobar"},
(response) => console.log(response)
);
});
content.js
485
Chapter 15 Cross-Browser Extensions
chrome.runtime.onMessage.addListener((request, sender,
sendResponse) => {
console.log("Content script received request: ", request);
console.log("Sender: ", sender);
if (request.backgroundColor) {
document.body.style.backgroundColor = request.
backgroundColor;
}
});
background.js
chrome.runtime.onMessage.addListener((request, sender,
sendResponse) => {
console.log("Background received request: ", request);
console.log("Sender: ", sender);
import SafariServices
import os.log
486
Chapter 15 Cross-Browser Extensions
context.completeRequest(returningItems: [response],
completionHandler: nil)
}
}
Most of this code should match what you’ve seen elsewhere in this
book. For the most part, this is a conventional background/popup/
options/content script setup for an extension. Bolded in the code are the
portions needed to send messages from the extension JavaScript code to
the native app Swift code. Some things to note here:
487
Chapter 15 Cross-Browser Extensions
Testing on macOS
Let’s first test this on desktop Safari. In Xcode, build the app for macOS
(Figure 15-15).
488
Chapter 15 Cross-Browser Extensions
When the build finishes, you will see the extension app open
(Figure 15-16).
The view you are seeing is the native OS app displaying a web view.
Xcode will have automatically installed the extension for Safari, so click
this “Quit and Open…” button to open the browser’s preferences. Check
the box next to “MVX” to enable the extension (Figure 15-17).
489
Chapter 15 Cross-Browser Extensions
490
Chapter 15 Cross-Browser Extensions
Click the “Preferences” button, and your options page will open
(Figure 15-18).
Next, navigate to a valid web page like wikipedia.org. Open the console
to verify the content script is completing the message handshake with the
background script (Figure 15-19).
491
Chapter 15 Cross-Browser Extensions
492
Chapter 15 Cross-Browser Extensions
493
Chapter 15 Cross-Browser Extensions
Testing on iOS
In Xcode, build the app for iOS, and use a simulator (Figure 15-22).
494
Chapter 15 Cross-Browser Extensions
The simulator will dump you right into the native app (Figure 15-23).
The view you are seeing is the native OS app displaying a web view.
495
Chapter 15 Cross-Browser Extensions
Xcode will have automatically installed the extension. You can see this
app by viewing the home screen (Figure 15-24).
496
Chapter 15 Cross-Browser Extensions
497
Chapter 15 Cross-Browser Extensions
498
Chapter 15 Cross-Browser Extensions
Next, navigate to a valid web page like wikipedia.org. When iOS Safari
has extensions installed, the URL bar will display a puzzle piece. Click
this to reveal the menu, which should contain an MVX extension option
(Figure 15-28).
499
Chapter 15 Cross-Browser Extensions
This MVX option functions as the toolbar button, and selecting it will
open the extension’s popup page. Click the “Change background color”
button to test that the content script is functioning (Figure 15-29).
500
Chapter 15 Cross-Browser Extensions
You can track the log messages created by clicking the “Send native
message” button through the Console app in the same way as macOS
Safari (Figure 15-30).
501
Chapter 15 Cross-Browser Extensions
Figure 15-30. The os_log output from the iOS extension’s native app
502
Chapter 15 Cross-Browser Extensions
503
Chapter 15 Cross-Browser Extensions
Tip More details on this CLI tool can be found here: https://
developer.apple.com/documentation/safariservices/
safari_web_extensions/converting_a_web_extension_
for_safari.
504
Chapter 15 Cross-Browser Extensions
F irefox Idiosyncrasies
Other than Safari, which is covered thoroughly in the previous extension,
the Firefox addon ecosystem has some notable differences that you should
be aware of when developing for it.
M
anifest Versions
At the time this book was written, Firefox is in a transitional period. The
organization is attempting to soften the messy transition from manifest
v2 to v3 by extending support for APIs like webRequestBlocking that
developers wish to cling to. This state of things will likely not persist for far
into the future, but for the time being, keep the following things in mind:
• Currently, Firefox is the only major browser that does
not allow you to load manifest v3 extensions by default.
It can be enabled, but its support is spotty.
• Currently, Firefox is currently the only major webstore
that does not accept manifest v3 extensions.
• Firefox indicated that it will adopt manifest v3, but also
preserve features that developers still need and use.
What this ultimately will look like is unclear.
505
Chapter 15 Cross-Browser Extensions
manifest.json
{
...
"browser_specific_settings": {
"gecko": {
"id": "browserextensionexplorer@
buildingbrowserextensions.com"
}
},
...
}
S
idebars
Firefox sidebars are extension-controlled containers that are defined
similarly to popups. They are declared in the manifest under the sidebar_
action key and appear alongside the active web page on either side
(Figure 15-34).
506
Chapter 15 Cross-Browser Extensions
A
PI Additions
Firefox is unlike other browsers in that it adds a significant number of APIs
to the extension API namespace:
• captivePortal
• contextualIdentites
• dns
• find
• menus
• pcks11
507
Chapter 15 Cross-Browser Extensions
• sidebarAction
• theme
• userScripts
S
ummary
In this chapter, we covered all the complicated aspects of creating an
extension that supports multiple browsers. We discussed how an extension
built on the Chrome Web Store can support a vast majority of desktop
browsers that are based off the Chromium project. We also discussed all
the different places you can deploy a browser extension as well as different
ways extensions can be used on mobile.
This chapter also covered various browser idiosyncrasies. It described
in detail how to develop and publish extensions for Safari on iOS and
macOS, as well as all the unique aspects of developing for Firefox.
In the next chapter, we will cover all the different tools, platforms,
and frameworks that you can use to make extension development and
deployment easier.
508
CHAPTER 16
Tooling
and Frameworks
Modern browser extensions are rarely written from scratch. There is a
wealth of developer tools at your disposal that allow you to efficiently build
and test extensions in your chosen JavaScript framework and seamlessly
publish them to multiple extension marketplaces.
This will churn out a basic application structure that is designed for the
web. We will need to customize it to make it usable in an extension. Begin
by adding the type definitions for the WebExtensions API:
510
Chapter 16 Tooling and Frameworks
manifest.json
{
"name": "MVX React",
"description": "Minimum Viable Extension - React",
"version": "0.0.1",
"manifest_version": 3,
"action": {
"default_popup": "index.html"
},
"icons": {
"16": "logo192.png",
"48": "logo192.png",
"128": "logo192.png"
}
}
index.css
body {
...
width: 400px;
min-height: 400px;
}
This isn’t ready to run yet. When building your application, CRA will
try to place JavaScript inline in the page. Manifest v3 extensions explicitly
disallow this, so you will need to configure the CRA build process to
only load scripts from a URL. You can prevent CRA from inlining scripts
511
Chapter 16 Tooling and Frameworks
package.json
"scripts": {
"start": "INLINE_RUNTIME_CHUNK=false react-scripts start",
"build": "INLINE_RUNTIME_CHUNK=false react-scripts build",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
...
}
512
Chapter 16 Tooling and Frameworks
513
Chapter 16 Tooling and Frameworks
Often, you may only need to share a small piece of state, such as
authentication data, between multiple extension views. Things like route
state, server-loaded data, and other view-centric information are likely
bound to a specific extension view, and therefore persisting it in something
like localStorage may be acceptable. For example, a popup that loads a
user’s preferences from the server is safe to store that data locally if it will
not be displayed anywhere else.
In situations where state must be shared between components, it is
possible to configure Redux to use chrome.storage as an asynchronous
store. The chrome.storage.onChanged event means that each view can
react to other views mutating the store. There are two popular GitHub
repositories that implement this:
• h ttps://github.com/ssorallen/redux-persist-
webextension-storage
• https://github.com/robinmalburn/redux-persist-
chrome-storage
514
Chapter 16 Tooling and Frameworks
Tip If you’re not familiar with Redux, read up on the React Redux
library here: https://react-redux.js.org/.
Routing
When managing view state for a complex single page application, most
developers will reach for a routing solution such as React Router. There are
two primary challenges in the context of browser extensions:
extension-protocol://path/to/file.html
All major single page application routers support some form of URL
hash routing, which will not interfere with the extension path and can
survive page reloads:
extension-protocol://path/to/file.html#/your/app/route
515
Chapter 16 Tooling and Frameworks
For example, React Router can implement this routing strategy using
HashRouter.
If you want the popup to always open the most recent URL, you can
dynamically update your popup URL via chrome.action.setPopup() and
include the hash route.
516
Chapter 16 Tooling and Frameworks
Mozilla Tools
Mozilla maintains a suite of tools geared for developing browser
extensions. To a certain extent they are geared toward developing for
Firefox, but in general they can be effectively used to develop for any
browser.
web-ext
https://github.com/mozilla/web-ext
https://github.com/hiikezoe/web-ext-webpack-plugin
517
Chapter 16 Tooling and Frameworks
webextension-polyfill
https://github.com/mozilla/webextension-polyfill
518
Chapter 16 Tooling and Frameworks
Parcel
https://parceljs.org/
manifest.json
{
...
"content_scripts": [
{
"matches": ["<all_urls>"],
"js": ["content-script.tsx"],
519
Chapter 16 Tooling and Frameworks
"css": ["content-script.scss"]
}
],
...
}
popup.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-
scale=1.0" />
<link href="popup.scss" rel="stylesheet" />
</head>
<body>
<div id="app"></div>
<script type="module" src="popup.tsx"></script>
</body>
</html>
Webpack
https://webpack.js.org/
520
Chapter 16 Tooling and Frameworks
• https://github.com/lxieyang/chrome-extension-
boilerplate-react
• https://github.com/sszczep/chrome-
extension-webpack
Plasmo
https://www.plasmo.com/
521
Chapter 16 Tooling and Frameworks
High-level Overview
The Plasmo framework builds opinionated abstractions on top of Parcel
that considerably improves the extension development experience.
Like Parcel, Plasmo features a convention-over-configuration design
philosophy. For example, if you want to add an options page to your
extension, you can just create an options.tsx React component in the
project’s root directory, and Plasmo will do the rest: automatically compile
that file into JS/CSS/HTML assets, generate a new manifest.json with the
new HTML file as the options page, and reload the extension.
522
Chapter 16 Tooling and Frameworks
JavaScript Frameworks
Plasmo is unopinionated when it comes to JavaScript frameworks. It
supports React, Vue, and Svelte. By default, the CLI init command will
generate a React application, but switching over to a different framework is
trivial:
https://docs.plasmo.com/
https://docs.plasmo.com/quickstarts
https://github.com/PlasmoHQ/examples
523
Chapter 16 Tooling and Frameworks
Furthermore, Plasmo can create a zip of these builds that can be directly
handed off to the extension marketplaces. This is done with the --zip flag:
# Generates build/chrome-prod-mv3.zip
pnpm build --zip
# Generates build/firefox-prod-mv2.zip
pnpm build --target=firefox-mv2 --zip
524
Chapter 16 Tooling and Frameworks
Icon Generation
Browser extensions display its icon throughout the browser, and the icons
must be tediously listed in multiple places inside the manifest. As shown
below, Plasmo will take a single source 512x512 icon in the assets/
directory, generate multiple resized icons, and automatically include them
in the manifest wherever needed (Figure 16-2).
525
Chapter 16 Tooling and Frameworks
Environment Variables
Plasmo features an intuitive solution for environment variables based on
the dotenv NPM package. Environment variables can be placed in a .env
file in the root directory of your project, and they will become available:
• In import URLs
.env
PLASMO_PUBLIC_FOO=foo
PLASMO_PUBLIC_GTAG_ID=123456789
PLASMO_PUBLIC_CRX_PUBLIC_KEY=asdf-1234-asdf-1234
526
Chapter 16 Tooling and Frameworks
popup.tsx
function IndexPopup() {
return <div>{process.env.PLASMO_PUBLIC_FOO}</div>;
}
manifest.json
{
"key": "$CRX_PUBLIC_KEY"
}
527
Chapter 16 Tooling and Frameworks
content.tsx
function IndexContentUI() {
return (
<h1
style={{
backgroundColor: "blue",
color: "white",
padding: "12rem"
}}>
I'm the content script widget!
</h1>
)
}
This widget will be injected on every web page. Note below that
Plasmo is injecting the component inside a Shadow DOM container to
prevent CSS interference (Figure 16-4).
528
Chapter 16 Tooling and Frameworks
529
Chapter 16 Tooling and Frameworks
530
Chapter 16 Tooling and Frameworks
Useful Sites
• https://buildingbrowserextensions.com is
the companion website of this book. It includes a
link to the Browser Extension Explorer, a Chrome
extension that consists of open source demos for each
extension API.
531
Chapter 16 Tooling and Frameworks
Summary
In this chapter, we discussed a wide variety of automated tools that you can
use to speed up and simplify extension development. First, we discussed
how React can be configured to neatly fit into an extension project, as
well as how to integrate some popular React libraries. Next, we covered
a variety of popular open source build tools that can be used to manage
the inherent complexity of browser extensions. Finally, we covered all the
different features that the Plasmo platform has to offer.
532
Index
A Internet Explorer, 5
Microsoft Edge, 6
Action, 320
Mozilla Firefox, 6, 12
activeTab, 361–363
Opera, 6
Ad blocker, 7, 169
Browser settings, 364
Add-on, 4
Browsing data, 331
Add-ons for Firefox, 466
Alarms, 172, 336, 363
Android, 460, 471 C
API probing, 462 Callbacks, 302
App Bundle Identifier, 487 captivePortal, 364
Apple Developer Account, 468 certificateProvider, 364
App Store, 468, 502 Chrome Apps, 158
Authentication, 210, 211, 314, 385 Chrome Developer
Automated deployment, 530 Dashboard, 455
Chrome OS, 343
Chrome web store, 1, 4, 6, 44, 163,
B 389, 451, 461, 465
Babel, 448 Chromium, 449, 460
Background, 363 clipboardRead, 365
Background scripts, 25, 52, clipboardWrite, 365
78, 187 Closure, 264
Background service worker, 436 Console API, 292
Bookmarks, 329, 363 Content scripts, 30, 64, 78, 82,
Browser Platform Publish, 530 245, 437
Browsers Content Script UI, 527
Apple Safari, 6 Content security policies, 94, 134,
Google Chrome, 4, 6, 8 179, 180
534
INDEX
K
H Keyboard shortcuts, 80
Headless page, 70, 271 Kiwi Browser, 471
Headless webpage, 194
History, 330, 372
Host permissions, 353, 372 L
Hot module replacement, 518, 529 Local development, 429
HTTP Archive, 282, 283 Locales, 99–101, 139
loginState, 374
I
Icons, 106 M
Identity, 374, 394 macOS, 488–493
Identity API, 395 Management, 341, 374
Idle, 339, 374 Manifest, 24, 44, 97, 199
Import, 195 v2, 25
Incognito, 147 v3, 25
IndexedDB, 214 manifest.json, 98, 167
inspect(), 294 action, 104, 105
Inspecting, 436 author, 110
Install, 457, 458 automation, 110
Integration tests, 449 background, 111
Internationalization, 101, 317 browser_action, 114, 115
iOS, 460, 494–501 browser_specific_settings, 115
iOS Safari, 474 chrome_settings_overrides, 116
chrome_url_overrides, 124
commands, 125
J content_capabilities, 131
JavaScript, runtime, 18, 21 content_scripts, 131
Jest, 442 content_security_policy, 134
535
INDEX
536
INDEX
537
INDEX
U
Uninstall, 51, 442, 457, 458 X, Y, Z
Unit tests, 443–448 Xcode, 478, 504
538