LocalStorage, simple web API to store persistent key/value pairs, is very favorite among web developers for its simplicity of use thanks its synchronous design. But in current versions of Firefox is one of most serious culprits of UI janks. The browser UI may simply stop reacting for a short time when localStorage data is written as well as read, mainly on mobile devices with poor flash memory performance.
A month ago I started rewrite of the DOM storage code in the Mozilla Platform from scratch. Except the performance and memory consumption problems other motivation for it is simply a strong need for code cleanup. The work on the new implementation is currently in stage of a pending review to get it into to the tree in mozilla bug 600307. It’s planned to land for Firefox 22, since 21 soon transits to the Aurora channel.
The main difference in the new implementation is that writes, but also reads, are completely moved to a background thread so it will not freeze your browser. When a web content script touches localStorage it only works with a memory cache and UI then cannot be blocked on waiting for disk writes to complete. All data changes are posted to and flushed in regular short intervals on the background I/O thread.
However, before localStorage can be used, data for the page origin has to be there – loaded into the memory cache. I implemented an early pre-load of the data when we start navigation to a web page. This is so far trivial: when prefetch doesn’t make it on time to load all data quickly enough, we block UI on access to localStorage until it is done. There are obvious and less obvious reasons to load all the data: if you want to read a key it simply needs to load from your disk first, but there is also quota usage checking when data are added or modified and StorageEvent providing the previous key value before modification.
So, there is still a room for more optimizations here I plan as a followup work to the core patch. To shortly explain, prefetch on the background thread pushes localStorage keys and its values one by one to the cache. Access to localStorage is blocked until all data is loaded. To make that time as short as possible I plan following further optimizations:
- Obviously, when reading a single key and that key is already in the cache, just provide it without waiting for the rest of the data to load.
- However, when a key is not in the cache, we have two options: wait just for that one key to get loaded or, when WAL mode on the SQLite database connection is enabled, read just that one key synchronously from the database. Both approaches, however, have to block the UI. Fortunately, our telemetry data (that I must thank you all for submitting it!) shows that reads from the database are generally very fast even for a whole domain data.
- When a key is about to write, we don’t need to block, yet quota checking is the enemy here. We must lower our demands on its precision slightly. Other complication is the StorageEvent that has to fire after a data modification. The event has to report the previous value of a modified key. Fortunately, the StorageEvent can be asynchronous. Hence, we can just fire it later after the key value has loaded from the database.
Before I jump to write these more changes I first want to collect telemetry data on which operation and for how long we still may block.
I wrote a fairly technical overview of how the whole new implementation works at this MDC document.
I want to thank Vladan Djeric for his help with this work. He is the one to review that huge patch and he also gave me inspiration for some aspects of the new implementation, like WAL usage and task batching, through his work on intermediate localStorage optimization in bug 807021.
I’ve made an experimental Firefox Nightly build with my patch. I intend to use it to get some first telemetry data. If anyone is brave enough and wants to help improve, then feel free to install it as well, enable telemetry in about:telemetry and report any crashes that may happen. I will sync these builds with latest Firefox Nightlies from time to time and expose links to them here.
Disclaimer: the patch didn’t go though a code or security review, only through mozilla automated testing. Any data loss is at your own risk!