For the last few months I’ve been working on a browser extension called Click and Roll as a personal project in my free time. Click and Roll is a free extension that finds the names of NBA players in any web page and colours them in teal. When you hover on a player’s name, their stats will appear:
Click and Roll in action on YouTube search results
Click and Roll in motion
Things I learned
I will spare you all the sales pitch of features etc… because I’m sharing this here more to reflect on the project as a learning exercise for me. So, here are some things I learned on this project, some big, some small:
Safely manipulating text in the DOM is not as easy as it seems
The first technical hurdle I encountered in this project was finding player names and wrapping them in a new <span> element so that I could apply custom styling (the teal colouring).
In initial tests I searched body.innerHTML, added <span> tags around matched names, and then set body.innerHTML to my new version. This was horrible. Consider a name in an alt tag:
alt="LeBron James going for the big dunk" // before search alt="<span class="click-and-roll-wrapper">LeBron James</span> going for the big dunk"</div> // after search
Now I’ve ruined this alt tag and made the web page less accessible as a result.
What about a name that was partly bolded:
This would not be picked up as a match.
Instead of searching body.innerHTML, I ended up searching body.textContent. This was great for finding matches, but when working with textContent you lose the nested structure of the DOM and have no frame of reference for where to insert new nodes. To get around this, I use a TreeWalker to traverse the DOM and locate each match, at which point I can determine if it is safe to wrap (i.e. not an input field, not in a script tag etc…) Here is the implementation.
Premature optimisation can be costly
One of the features I planned for Click and Roll from day one was that it would find player names in the page even as it changed, not just on initial load. This was so that it would be useful on sites like Reddit and Twitter, where most interesting content loads in as you scroll.
I use a MutationObserver to detect DOM changes and re-search. At first I was worried about the performance hit – these modern sites are changing all the time, I’m going to run too many searches! For that reason, I tried to only trigger searches on mutations that met certain criteria like adding or changing text, and only searched the affected nodes.
This was incredibly slow. Because of the number of mutation records a MutationObserver produces for each seemingly small change in the DOM, iterating over mutations and searching individual nodes was more intensive than just re-searching body.textContent whenever it changed. The latter approach, in fact, does not cause noticeable slow down in normal usage of most sites anyway. I wasted a lot of dev time trying to optimise a problem I didn’t even have.
Libraries and frameworks exist for a reason
When you hover on a player’s name, Click and Roll figures out the position of that name in the page and then opens the stat window directly above or below it, depending on where there is more screen space. This was a huge pain in vanilla JS! I think it would probably have been significantly easier using JQuery’s offset method to find the offset of the wrapped name and go from there, rather than the convoluted approach I’ve implemented. I am leaving it as is as a testament to that struggle for now, but may simplify with JQuery in future if I continue to maintain and develop Click and Roll.
Think about data ownership at the start of your projects
Early in its development, Click and Roll directly called the NBA’s stats API each time a user hovered over a name. This was against the NBA’s terms of service, so I was always planning to change it before releasing and publicising the extension. I figured it would be straightforward to swap in another API or build my own stat server.
Well, I was wrong! In the end I have built my own stat server, which was a fun learning exercise but also significant work. Creating and maintaining a stats database is a whole extra layer of work that I did not anticipate at the start of the project, all because I didn’t sufficiently research the free availability of NBA stats. It’s possible I would have chosen a different sport if I had done that research better at the beginning. When starting a project involving any data, make sure you understand who owns the data and how/whether you will be able to serve it.
It is trivial to port extensions to many browsers…
Click and Roll was originally a Chrome extension. All Chromium-based browsers (Chrome, Edge as of very recently, Opera) share the same extensions API. This API is also cross-compatible with Mozilla’s WebExtensions API. This makes porting an extension to a number of major browsers very simple. The exception is Safari, which requires extension background scripts to be written in Swift or Objective-C, as opposed to JS.
…but Chrome is where the users are
At least in my experience, the market for extensions is much stronger on Chrome. Click and Roll has over 2000 users on Chrome and fewer than 200 across the other browsers it is available for.
Firefox handles iframe loading a little differently
The Click and Roll stat window is an iframe attached to the document body. In Chromium-based browsers, an iframe can be manipulated (e.g. adding a style tag) as soon as it is attached to the DOM. In Firefox, you can’t do this until the iframe fires its ‘load’ event. This was one of the only minor headaches of porting to Firefox.
It is extremely satisfying to finish something and get positive feedback
Click and Roll has been well received. I worked on it for a while. It feels great!