Building Dynamic Firefox Themes
Mozilla is running a contest called the Firefox Quantum Extensions Challenge for building extensions that take advantage of new capabilities in WebExtension APIs. One of the categories is “Best Dynamic Theme.” I love adding color to my setup, so I was pretty motivated to participate. I ended up creating three extensions.
Chromatastic
Chromatastic (source code) continuously cycles through colors using a setInterval in the background. This one was inspired by the lighting effects on my Razer keyboard. I keep mine in what I like to call Nyan Cat mode:
I considered trying to replicate the effect, but it seems like it would be too obnoxious in the browser. I’m also not sure if it’s feasible with the current API. The only method I can think of is to use an animated SVG, which is possible as of Firefox 59. I decided to do spectrum cycling instead:
Here’s what it looks like:
The color transitions occur fairly slowly, so the overall effect shouldn’t be distracting.
Color Tailor
Color Tailor (source code) changes the browser theme to the current website’s “primary” color, as determined by the meta theme-color element or by the dominant color in the favicon. The dominant color is derived by color quantization using the node-vibrant package.
Color Tailor was inspired by an iTunes feature that would change UI colors on the fly to match album covers. Chrome on Android does a similar thing with the toolbar color, and there is an open issue to do the same for Firefox.
Here’s the result:
Picture Paint
Picture Paint (source code) uses the color palette of the current National Geographic Photo of the Day.
Popup coloring:
Scroll for picture details:
Preferences:
What I learned
These extensions are relatively simple, but I still learned quite a few things in the process of making them.
web-ext
Mozilla has created a command line tool called
web-ext to streamline extension
development.
I added it as a development dependency to use its lint
and build
commands.
I also wanted to use the run
command for source file watching and automatic
extension reloading, but it doesn’t work from within Docker. There is an open
issue to allow web-ext to
attach to a remote Firefox instance, which would also fix this issue.
Webpack
Firefox extensions have a default Content Security
Policy
that disallows using unsafe practices such as
eval()
.
However, many of the Webpack source map
options use eval. I didn’t want
to change the extension’s CSP just for source maps, so I ended up using the
inline-source-map
option.
Theme API
The theme API offers a lot of granularity in terms of coloring individual browser elements, and using it is straightforward. As of now, Firefox is the only browser that supports dynamically updating the theme.
Getting the favicon
At first, I tried to get the favicon location by looking for an icon
reference
in the page’s head element and defaulting to /favicon.ico
otherwise. This
worked for the most part, but then I learned that the extensions API provides a
favIconUrl
property on the tab
object.
The caveat is that it might be an empty string if the tab is loading. As a
workaround, Color Tailor listens for
updates
to the tab info.
HSV
My first thought for creating a rainbow effect with Chromatastic was to cycle through RGB combinations. But it turns out it’s easier to create smooth transitions using HSV values.
Getting the National Geographic photo
There doesn’t seem to be an official API to get the National Geographic Photo
of the Day. I found an unofficial
API on npm, but it uses
a hardcoded API user/key. I dove into the Photo of the Day page’s source code
to look for an alternative. I found a reference to
https://www.nationalgeographic.com/photography/photo-of-the-day/_jcr_content/.gallery.2018-04.json
,
which looked promising. I tried retrieving it from the extension using
fetch, and it
worked without any authorization.
I also tried removing the .2018-04
part from the URL, and to my mild
surprise, it worked! Leaving it off returns the current month, so Picture Paint
does that for the daily updates.
Bad themes
During development, I tried to set a theme and forgot the textcolor
property. Apparently this property is
required,
but
theme.update
seems to fail silently when it is omitted. The theme doesn’t change, and the
Promise resolves without any errors. It took some debugging before I re-read
the documentation and found the source of the problem. I would expect the
Promise to reject if the theme is invalid.
Color quantization
Until I worked on these extensions, I didn’t know that the technique of getting color palettes from images is called color quantization. I tried many JS color quantization libraries and ended up picking two.
Picture Paint uses image-q, while Color Tailor uses node-vibrant. In my experience, using default settings, image-q produces more reasonable color palettes but also takes much longer to run. node-vibrant uses the Modified Median Cut Quantization (MMCQ) method, while image-q uses NeuQuant (overview), which builds a Kohonen neural network.
Speed is more important for Color Tailor because the theme needs to update as quickly as possible when the user loads a new page. Picture Paint, on the other hand, can afford to take a few seconds to processs the image because it happens in the background.
Canvas permission issue
The issue that took me the longest to resolve occurred when I first tried to
use the color quantization libraries. I was getting a DOM security error, and I
eventually figured out that I
needed the "<all_urls>"
permission
in order to fully use the canvas API (which the libraries use). I stumbled on
the solution while reading through this bug
report regarding using
canvas in an extension.
Popup coloring
Firefox 60 will significantly increase the number of UI elements that can be themed. I changed my code to set popup colors. It generally looks good for things like the Firefox menu, but when I tried using a native date input, the datepicker looked very bad in many cases and was sometimes downright illegible. I considered not theming the popup, but I decided to keep using Pikaday instead of switching to the native input.
This seems to be a general issue with theming. When I used themes in the past, I never stuck with them for long because images can clash badly with favicons and extension icons. Solid colors work much better in my opinion, but there are still occasions when an icon gets washed out by the theme. I’d love to see a good solution to this problem. Maybe just adding a very small white margin to the icons would be sufficient.
Imperative UI
It was nice to work on something with a very limited scope. Instead of using a UI framework for something so small, I manually manipulated the DOM with vanilla JS. After working in React for a while, it is striking how backwards imperative manipulation feels now. Dealing with error and loading states meant mental bookkeeping in terms of hiding and unhiding the correct elements on each possible event. It’s totally manageable at this scale, but it still gave me flashbacks to dealing with jQuery spaghetti.
Photon design system
Firefox has a new design system called Photon. It’s like Google’s Material Design. I referenced it for things like color values when I was building popup and option pages. However, I was surprised that there isn’t a drop in stylesheet like Bootstrap or Semantic UI. I found this issue with a great question of “How do you actually use this?”, and it links to a Photon Extension Kit repo that seems to be what I was looking for. It has an open pull request to “photonize” the CSS.
Packaging source code
Firefox has a review process for extensions, and providing the source
code is
required when the final code is obfuscated or minified. I have a release
command that I run before uploading a new version, but I wasn’t sure how to
package the source mode. It would be easy to create a ZIP of the directory, but
I wanted to avoid including files and directories that are in the .gitignore
,
such as node_modules
. It turned out that Git has an archive
command that does exactly what I need.
Conclusion
Building these extensions was fun. Please feel free to contact me or open an issue if you have any suggestions or issues with any of them. Picture Paint turned out better than I had anticipated, and that’s the one I’ll personally use for now. Some of the National Geographic photos are truly stunning, and I like getting a new theme every day.
I hope Mozilla has been getting a lot of submissions, and I look forward to seeing what other people made!