Half a year ago Google announced an updated version of Gmail interface. Despite multiple complaints from people, this is the default interface now.
Among the other issues, people noted performance problems with the new interface, especially on low-end machines. Let’s see how could this happen and what can be that heavy in the web-interface of the e-mail client. We will use Google Chrome’s DevTools, so this article will also be a good reminder about features that they have.
Initial setup
First of all, let’s look at Gmail’s performance. Chrome Devtools have a built-in tool, Lighthouse, that shows a nice and clear report about different aspects of your website, including performance. Gmail performance score is 2 out of 100:
To be honest, this is not the result you might expect from a Google product. Nevertheless, let’s look deeper at this situation. After disabling cache and reloading the page, on the Network tab of dev tools will be displayed all requests have been done to load this page. For me, it was 6.9 Mb in total. This is a lot, especially considering that Webpack, for example, by default warns you when your assets size is more than 244 Kb. Here we have 27 times more.
It is worth mentioning here that modern web services (including Gmail) are using Service Workers for advanced assets caching. So, in order to get adequate numbers for the cold start payload you should not only disable the cache but also reset Service Workers, which also holds some resources cached. After that, your numbers will show the full size of assets.
Now let’s look at the page load in slow motion. There is an explanation in the documentation for Google Chrome as how to do that. We will get a number of screenshots, showing page at different loading stages:
It shows that the page becomes more or less usable after 9 seconds.
Loading page again with the cache and Service Workers enabled shows a better picture. There are only 250 Kb of requests, but it doesn’t actually make the page faster, we still have to see a loading screen for 9 seconds. There is something else makes the page load slower, not the network trade-offs.
Blocking some resources
If we will look at the list of resources, there will be a lot of them:
Maybe some of them are not really needed for the page work? Let’s try blocking resources and see how will this affect the page. It is easy to do with the built-in dev tools feature.
It turned out, that requests to `https://mail.google.com/_/scs/*` are crucial for the Gmail interface, but some others can be blocked:
www.gstatic.com/og/*
— Google API Сlient Library, to make requests to API of different Google servicesssl.gstatic.com/inputtools/*
— Google Input Tools — screen keyboard widgethangouts.google.com
— embedded Hangouts widget
After blocking these scripts the page loads in 4 seconds, but the essential functions such as reading and writing emails are still working. If you also experience performance issues in Gmail interface, blocking these URLs might be a solution for you.
Profiling
At this point, we have cut off some resources, but the page still loads slow. We should look, what is happening during these 4 seconds. Chrome has a built-in Javascript profiler, that will show us the following picture:
Apparently, the browser was busy executing Javascript throughout this time. It is interesting to observe what sort of code this is. Javascript is usually shipped to clients in a minified form without formatting and with mangled function and variable names.
De-minifying the source code
Let’s try to understand the minified code. We will download resources and pass them through Prettier, a code formatting tool. Alternatively, you could use a built-in feature of Chrome DevTools.
Code becomes more readable, but it is still very hard to understand without original names, what these functions do. Fortunately, strings are preserved and we can use these clues to restore the source code. There are some strings like closure_uid_
or type_error:TrustedResourceUrl
. We can search for them on the internet in hope of finding some open-source framework they are coming from.
The search would lead us to Google Closure Library. It seems like we have seen some minified pieces of this framework. Further compare reveals some matches between minified code and sources of Closure Library:
Two if
statements, one short and one long, then assignment and finally a function call. This must be the same code.
Going further we also meet strings like GwtExecutor
, ListenableFuture
, DaggerComponentFactory
, that look like parts of Java-stack, possibly compiled to Javascript. A related Google Inbox project is featured on “projects using GWT" page and it is possible that it shared some code with the main Gmail interface.
This technology stack is very unexpected. In the light of the fact that Google actively promotes web components and their framework on top of that (Polymer) it is very surprising to see in 2018 a redesign of a Google project that doesn’t use them.
Instead, we have a very old technology stack with a lot of artifacts from past days where all browsers did not follow standards and you had to write a special treatment for each browser. The leftovers of it are still there, in 2018 edition of Gmail code:
- AJAX via ActiveXObject, a hack for IE6, because it did not have XMLHttpRequest support
- Event listeners via attachEvent, were needed for IE8 and below, they did not have standard `addEventListener` method.
This code is included in the bundle for no practical reason, because Gmail officially supports only IE10+, there are no consumers for these hacks.
In addition to legacy overhead, this stack has outdated components paradigm. It is based on class hierarchy — an approach with a big number of heavy abstractions, which are getting created for every component that significantly slows down the rendering when you have hundreds of components on a page. Modern UI-frameworks don’t do long inheritance chains, their classes are light and the rendering work is being performed by the framework core. This reduces an overhead of components, so you can have more components without performance penalty.
It is definitely contributing to slow page initialization: there are thousands of classes are being instantiated during page load that slows down page rendering.
Thus, despite the updated visual look, the code of Gmail carries the legacy of old technologies, the weight cannot be hidden by the new visual shell.
Checking code coverage
Another useful feature of DevTools is Code coverage. It helps to understand which parts of the code were not executed by the browser and possibly can be loaded on demand.
About half of the code was not used. There are can be legit pieces, like event listeners and other async operations. But it is worth checking unused parts anyway, we can find some code that might be thrown away.
In addition to already mentioned hacks for older versions of Internet Explorer, we have found a couple more interesting pieces:
Leftovers of Google Picasa
The service has been shut down, but the memory still lives in GMail code.
Screen keyboard
There is a bunch of classes responsible for the feature, that you probably don’t use every day, but they are loaded with every Gmail page visit.
Integration with Google Calendar
Same as above: you might not use Google Calendar, but the nice meeting representation will be loaded.
Conclusion
This review may clarify some reasons why Gmail interface is slow. Unfortunately, it is out of our hands, to make Google improve the performance of their interface. But we can learn some lessons for our own web applications:
- Google Closure Compiler is very powerful. That was a real puzzle to restore the source code, it currently gives the best compression rate comparing to any other JS minifier.
- Check your project for unused code, for example, hacks for older browsers. Review your code and get rid of things, which are not actual anymore. If you use Babel, consider providing browserlist config to include only transformations that make sense for your target audience.
- Abstractions are not free. If you would like to solve a task using an elegant programming pattern, first think about the cost of it. There may be a simpler solution available.
- Do not include secondary page elements in the initial payload, use code-splitting. In the case of Gmail, the Hangouts widget is being loaded immediately, slowing down main resources. It might be loaded later, after the crucial functionality of the page — the list of emails.
- Do not neglect modern technologies. They might contain fundamentally different solutions for your issues, more performant and convenient.
- Finally, do not forget to pay attention to the performance of your app. Audit it from time to time, or set up a CI integration for it.
Thanks to Oli Steadman for proofreading the article