Color Invert

I find myself using Google Docs more and more these days. I find it quick, easy to use and has all the tools I need. It highlights all my bad spelling and terrible grammar, it defines words so I can make sure I am using the right one, and saves everything on the cloud for easy access. However, there is no dark mode, which means my poor eyes suffer. I had three possible options.

My first option was to look and see if there was some other document application I could use instead of Google Docs. I'm sorry, but at the moment, nothing comes close, nothing free anyway.

The second option was to use a chrome extension that added dark mode into Google Docs. There are a lot out there and some of them do the job, but not very well. They seem to contain a lot of clutter with extra parts I didn't want. I did see one that simply inverted the colors, which was ideal, but it was a little buggy, and inverted by 100%, which looked a bit off to me.

My third option was to create my own chrome extension. Software developers like to solve problems, even when other solutions already exist, because we like to reinvert the wheel. This wheel, I mean chrome extension, will allow you to create a pseudo dark mode for any web site, including Google Docs.

Problem #1

While creating the extension I came across an interesting problem working with async/await and promises. I would test a web site, turn dark mode on and off, only for it to start misbehaving. It would show the page in dark mode, I then turn it off, but the page would still remain unchanged. The extension adds a CSS file to the site when dark mode is triggered, and will remove it when you turn dark mode off. But I was finding that two copies of the CSS file were being added.

Inside the background source code, we have the following parts, which are triggered when the tab is loading up a new page, updating or being reloaded.

It removes any existing CSS we may have added before, and then inserts the CSS file that contains the dark mode filter parts. Why then, you may be thinking, does it end up adding two copies of the extra.css file?

Well, the onUpdated event is called muliple times at the same time (or near enough). Now you may think that something like this happens.

Sadly not. These are await-ing for the asynchronous functions to do their job, which gives time for the other events to start being performed. So when onUpdated # 1 > await removeCSS(...) begins to wait, onUpdated #2 starts to do its thing too. This is what ends up happening.

As you can see, both the removeCSS functions are being called one after the other, followed by both insertCSS functions being called one after another. This is why we end up with two copies of the CSS script.

To get around this I had to find a way of making sure the remove and add parts are only done in pairs, or if one event is performing the remove and add steps, then no other one can.

I created a global variable tabOnList that is used to stop one event doing the same set of steps as another at the same time (for the same tab).

The first part is to get the tab ID from the list. If it exists then another event is doing the work and it can skip doing anything. If it does not exist, then it adds the tab ID to the list, does the work of removing and adding the CSS, and when all that is done it removes the tab ID off the list.

Problem #2

I had another interesting issue that almost stopped this from ever working. In Google Docs, while you are working away, if you move from one style to another, the page's URL changes, a “#” section is added. The end result is that the onUpdated function is called, which will remove the CSS and then add it in again. We need to remove the CSS first, because we do not know if the event is being called the first time on a new page, or if it is being called again on a page that already has the CSS file inserted.

Anyway, the problem is that the CSS is removed, then added, which creates a white flash to happen. The dark mode CSS file is removed, making the page redraw in normal light mode, then the dark mode CSS file is added, and the page redraws again, back into dark mode.

We only want to insert the CSS if it doesn't already exist, but there is no method available to see what has been added or not. We could create some global variable that checks this, but we have no way of knowing if the page is reloading, or just having the hash part of the URL updated.

I tried all manner of different solutions but nothing seemed to work nicely. Finally, after taking a break, I came up with a simple idea. I needed to add a copy of the CSS file before I removed and added the other CSS, and then when that was done, I removed the copy. This way, the CSS we want shown, is never totally removed. This is what I ended up with.

Final Notes

There are limitations to all this however. The fonts will look a bit blocky compared to normal light mode. This is because the whole page is being filtered, which converts it into a big image, which is processed as a bitmap, and then shown back on the page. In normal light more, text is shown using ClearType (or some other means depending on your OS), which draws the text using sub-pixel shading methods, making it look smoother.

Give it a go. You can find it on the Google chrome web store.

Color Invert