How I wrote a Single Page Chrome Extension

Aashish Manandhar
Devnetwork
Published in
8 min readJan 21, 2019

When you first hear about Chrome Extensions probably you’d think that it’s got to be written in C, C++, JAVA or some other language which seems overly ‘complicated’ for a newbie. But then when you figure out it’s just simply HTML5, CSS3 and Javascript you would probably think that it’s just a simple website and start belittling those who write a chrome extension. Well it’s easy to write an extension to do a simple job such as scraping data from a website, show bookmarks considering the fact that chrome exposes the API for all those functionalities. But when you start writing a full fledged app for a chrome extension with multiple view pages that’s when you start running into problems.

Problem 1: Popup is disposable

First and foremost problem that you’ll run into is due to how the chrome extension is practically designed to work. The chrome extension is essentially split into two parts which I’d like to call the UI and the background. There is a single background page which could be persistently running script or an event page(non persistent). And another part which I call the UI is the popup which opens when the icon of your extension besides the address-bar is clicked. The problem with the UI is that it is a popup i.e. it gets destroyed or doesn’t exist once it is closed. That means that you can’t have it open while the focus is in a browser tab. I’ll come back to how this posed a great problem in our application development. Also note that we are now talking about extensions which have views and not the ones which are just composed of background scripts and content scripts.

Problem 2: Lack of Frameworks

Another problem that you run into is that you don’t get a framework to write a chrome extension with a popup. When you dig into the code of the majority of the extensions that you can find in the internet, all you find are jQuery based codebase which uses css classes to toggle content on the screen as per the data and every application UI logic crammed up in a single file. I wanted to modularize the code but I couldn’t think of a way to separate the code with jQuery.

I had a task to write a simple timer extension where a user logs in, configures his settings and starts his timer and the timer starts running in the extension. How hard can that be right? Well I too thought the same until I started running into issues of-course. For starters the UI required a login, configuration and a running timer view pages that made it three pages to show. I was quite annoyed to find nothing but the jQuery application examples.

I knew I wanted something better. Well I wanted to use a UI framework but you know how the frameworks in the world of JavaScripts have been growing like mushrooms. Since I wanted the size of the extension to be minimal and my code modularized, I googled for all the lightweight javascript frameworks. The framework that caught my eye was the RIOT.js MVP framework as it was light, easy to learn and most importantly had a minified build specifically targeted to support the Content Security Policy that is enforced in the chrome extensions. The Content Security Policy doesn’t allow inline javascript by default in order to disable cross site scripting attacks. You can disable it by passing a flag in the manifest but with RIOT.js I didn’t have to do so. If you know React then Riot won’t be hard to wrap your head around, it’s based on the same concept.

Implementing the framework was quite easy as it was properly documented and I was happy with how the application was turning out to be. I could make it work and switch views by using simple routing (mounting and unmounting pages) and application view logic was split into several files. That was wonderful. I thought that would be the end of my problems as the application looked complete.

Problem 3: AJAX response not received

Then the extension was then released to about 100 users. Another set of problems started appearing. Our implementation had the AJAX requests sent from the UI (popup). And it’s obvious that we can’t always rely on fast internet connection for users. We were overwhelmed with the problems regarding the user action events often not being executed even if the user had triggered it. Upon inspection what we noticed was that the servers sometimes took more than a few seconds to process the request or due to poor internet connection reasons, the request was sent to the server and the response wasn’t yet received by the UI. We were handling AJAX requests at the popup and when the popup was closed, the listener for the callback was never executed. So the next time they opened the extension they saw a stale running timer that had already been stopped in the server. We thought of solving the problem temporarily by sending an AJAX request for any running timer in the server periodically or at the time the popup was opened but that would mean a lot of requests and would require our servers to scale. Also the users had to wait for the extension to fetch the data before they could use it every single time they opened the extension popup. That hampered the usability of the extension. So we needed a different approach.

We knew we had to move the AJAX requests of the extension to the persistent background so that closing the popup would not effect the reliability of the extension. That’s when we decided to do a complete revamp of the extension. We moved all the async tasks to the background and made the UI dumb or just presentational components. So now the UI part of the extension would dispatch a message for any start/stop events that needs to happen and the Background would now send the request to the server and get the response, store it in the local storage of the chrome extension and send it back as a callback response to the message dispatched by the UI.

Problem 4: Chrome runtime messaging port gets disconnected

We used chrome’s internal messaging api to communicate between background and the popup. So a stop button press would dispatch a stop action in the popup which would be sent through the chrome’s api to the persistent background. The background receives the action and dispatches the AJAX request which when completes sends the response to the popup via callback (sendResponse function in the API). We also needed to show a loader in the popup until the request has been processed so we set the loader then dispatch the action and only when the response is received the loader is removed.
But there was a big flaw in this implementation as well. We were awaiting the response from the server to hide the loader but when the popup is closed even the chrome’s internal messaging api port which is awaiting a response is destroyed so no response would be received by the UI even if the request succeeds thus making the view data unreliable. It would throw an error in the background of port being disconnected as the UI expecting the response is no longer present. Even if the popup is opened again(it’s not the same instance as before so doesn’t receive the response).
So I had to further simplify the approach by dispatching the request to the background from the UI but not expecting the response. Now UI would just set the loader in UI and dispatch the message. Background would then receive the message and set the loading to true in local storage until the response is received. Then once the request is served the background script would update the local storage with the loader set to false and dispatch an update message to the UI with the new parameters required to update the page. Now it solved both of my problems. If the UI is open then the dispatched message is received and updated and if it was closed when the message was dispatched it doesn’t receive the message but the next time it is opened it would fetch the data from the local storage.

Problem 5: Writing Tests

Now it worked perfectly as intended but what about the test coverage? Well I tried a lot of node packages as I couldn’t find any examples that matched my use-case. I tried Mocha, Karma and a lot of other test frameworks and faced a lot of hardship figuring out which one to choose. Since all the heavy-lifting was now done in the background and the UI popup just dealt with displaying stuff I knew that I at-least needed to unit test the background script. Long story short I ended up using Mocha along with sinon-chrome to stub the chrome apis and test the background script and used Jest to do the snapshot testing for the UI part by mounting the views.

Problem 6: Testability and Maintainability with callbacks

Well now the extension was working and testable as well but the maintainability was still an issue as I had a series of callbacks being executed as the extension needed to have a sequence of asynchronous requests to fetch the user information and toggle the timer all one after another. We had managed to make it work passing callbacks from one function to another but that resulted in a nested code so deep that it was surprising not to find Adele there. Just debugging the code would require me to traverse the code almost entirely as the callbacks would make be jump from one function to another. It was like playing hot potato. Don’t even get me started on how hard it was writing tests with all those callbacks in place. So I eventually switched to promises, THEN life got a lot easier (Did you see what I did there.. I hope you didn’t CATCH me doing that). It was a lot of rework but the payoff was great. Who wouldn’t want a modular code with easily testable functions. I’ve written a separate article on promises if that piques your interest.

SUMMARY

Here is a brief summary of the above problems and their solutions:

  • Popups are disposable and should be presentational only.
  • Use frameworks like RIOT.js or React to modularize your codebase.
  • Asynchronous operations such as AJAX requests should be seperated from the presentational components or views and should be delegated to the persistent background.
  • Chrome runtime messaging port gets disconnected on popup close and should only be used to send messages and await response if the action to be dispatched is synchronous. Else if an asynchronous task is to be dispatched there should be only one way flow (sendResponse should not be used)
  • Background should be unit tested and snapshot testing should suffice for the popup but you can always write unit tests for the popup too.

Thus in this way we wrote a fully functional testable extension using a UI framework/library. It does have some minor hiccups from time to time but nothing is perfect! If you’d like to use the same method that I used to build the chrome extension you could use the boilerplate that I’ve created to get started with it.

--

--

Aashish Manandhar
Devnetwork

Just another guy who wants to make world a better place!