Have you ever been on Facebook or Twitter, merrily scrolling down the page, when all of a sudden, the browser appears to freeze? For several long seconds it just hangs there, and you’re not sure if it’s going to crash or not. Then, finally, something gives way, and the page jumps to catch up to your scrolling, disorienting you? That’s called jank, and in Firefox 46 Beta we are well on our way to making jank a thing of the past.
Under the Hood
Browsers traditionally handled user input events on the browser’s main event-handling thread. This meant that it could get blocked by pretty much anything else the browser was doing, including running scripts and painting. In Firefox, the Electrolysis (e10s) project separates the content process from the main browser process. This gives us another path for handling input events. Instead of passing the input events from the parent process to the content process to drive scrolling as usual, the input events are used to drive scrolling in the parent process compositor before they are forwarded to the child process for normal processing and dispatch. This is illustrated in the diagrams below.
In Figure 2 above, we can see how introducing e10s and APZ affects the flow. The input events arrive in the main thread of the parent process, which is free of long-running operations. Therefore, it can quickly pass on the input events to the compositor thread, where the APZ code uses it do asynchronous scrolling. Meanwhile, the input event is also forwarded to the main thread of the child process, where it is delivered to web content and triggers repainting. Even though these operations may still be slow, they no longer block the scroll updates from being composited to the screen.
This approach, although great for responsiveness, requires e10s to be enabled, and also introduces a certain complexity. For instance, the parent process needs to be able to do some amount of hit-testing on child processes to know where the input event landed. It also means that the parent process needs to have a mechanism for handling events on which
preventDefault() is called by web pages. For details on how this all works, refer to the APZ documentation.
Of course, nothing is free, and APZ comes at a cost. APZ does eliminate jank, but in some cases it does so by checkerboarding instead. Checkerboarding is what you get when you scroll faster than the browser can paint the page. When this happens, the content at your new scroll position hasn’t been painted yet, and so we just show a flat background color. (The term checkerboarding comes from the original iPhone implementation, which would show a checkerboard pattern.) Once the painting catches up, the content fills in.
Checkerboarding is inevitable with asynchronous scrolling due to memory limitations; it’s the cost of removing jank. However, in most cases it shouldn’t be noticeable – it only lasts a few hundred milliseconds, and we are working hard to bring that down further. The way we do this is by painting content outside of the visible area ahead of time, so that when the user scrolls it’s ready to be displayed right away. By better predicting where the user is going to scroll we can reduce the amount of checkerboard shown. Overall, trading off jank for checkerboarding results in a better user experience because the browser itself remains responsive and doesn’t appear to freeze or hang.
APZ also has an impact on the way scroll-linked effects work. Because APZ does scrolling asynchronously from the main thread, this necessarily means that the scroll events seen by the web content will be delayed. In other words, there is some propagation delay from when the user scrolls until the scroll event can be dispatched to the web content. If the page uses the scroll event to reposition some content on the page, it will take additional time to process that change and display it to the user. This means that scroll-linked effects can appear delayed and out of sync with the scrolling. Under ideal circumstances, the delay between the user’s scrolling and the scroll-linked effect being visible should be no more than 2 frames (33ms at a frame rate of 60 frames per second). Although this is barely noticeable, in practice it may take a bit longer if the page is tying up the main thread with scripts, or if it is complex and takes a while to repaint. Therefore, it’s still important for the page to remain as responsive as possible by breaking up long-running scripts or using workers.
About Kartikaya Gupta
Kartikaya Gupta is a developer on Mozilla's Platform Graphics team, and works on scrolling-related things in the Gecko rendering engine.