In 2013, I worked on the Safari/WebKit team at Apple. One day, I was assigned a bug from the Mac App Store team. They were developing a new version of the store app, built almost entirely on web content, and it was really sluggish when resizing.
I’d been on the team for a few years at that point, and while I was new, I felt confident that I could help the App Store folks improve performance somehow.
Here’s what the app looked like at the time:
Investigating the issue
As with any performance issue, I fired up Instruments and captured a time profile while resizing the App Store window.
The profile showed us spending most of the time in three subsystems:
Layout
Style recalculation
JavaScript execution
I was pretty comfortable with style recalculation in WebKit (much thanks to great mentorship from Antti Koivisto), so I decided to start looking there. Over the next few weeks, I found a number of inefficiencies and constructed optimizations for them.
My optimizations
I went through the WebKit history and found the various things I did to make it faster. In retrospect, I was pleased to discover that they were generally useful optimizations and not specific to the app store use-case.
Let’s go down memory lane and look briefly at the optimizations I added:
Don't check for @media rules affected by viewport changes in every layout.
Before this change, WebKit would synchronously re-evaluate media queries for every step of a resize, instead of allowing them to coalesce with the next visual update.
When updating geometry, send JavaScript resize before layout/paint.
The App Store content had a resize event listener in JavaScript that would sometimes make changes to the page layout. If I recall correctly, it was switching between displaying 3 and 4 apps per row.
Before this change, while resizing the window, WebKit would first relayout the page, then dispatch the resize event. If the event handler did something that changed the layout, we’d do another relayout, and then finally paint the results.
By dispatching the resize event first instead, we only do one relayout, even if the event handler does something that invalidates layout!
Setting an inline style property to "" shouldn't cause style recalc unless the property was present.
Before this change, doing element.style.someProperty="" would always trigger a style recalculation, even if someProperty was not present in the element’s inline style.
CSS attribute selectors cause unnecessary style recalc when setting attribute to same value.
Before this change, setting an element attribute to the same value it already had would cause a style recalculation if there were attribute selectors matching that attribute.
An optimization that didn’t work out
I also attempted this one, but we had to revert it because it broke real websites:
Throttle resize events during live window resize.
The idea was to only send resize events at most every 0.2 seconds while the window was being actively resized. It was a nice idea, but as is often the case with browser optimizations, it broke real world content so we couldn’t do it.
It got better, but still too laggy
While my optimizations did improve performance, they only added up to something like a 20% speed-up in total. Good, but not great. And the App Store was still noticeably laggy.
I had managed to reduce the amount of style recalculation happening, but I hadn’t made much of a dent in layout, and that’s where most of the time was disappearing now.
The App Store content was using heavily nested CSS flexbox layouts (actually the old pre-specification -webkit-box version!), and I vividly remember profiling it and staring at a tree full of WebCore::RenderDeprecatedFlexibleBox functions.
While my comfort zone overlapped with style recalculation, anything layout related was strictly outside of it. This wasn’t unique to me, most of the people working on WebKit were uncomfortable with layout code. Ever since I joined the project, I’d heard people talk about layout (and those who worked on it) as something impossibly, almost mythologically, complicated.
I had totally bought into this mythology, and now that I was staring at a layout performance bug, I felt paralyzed. “There’s no way I can solve this, it’s layout!”
Still I picked at the profiles and poked around the code a bit for another week or so, but I wasn’t really making an effort to understand how it worked.
Rescued by Dave Hyatt
I forget exactly how it happened, but I think at some point I told my manager I was unable to make any more progress on the issue.
They asked one of the layout experts, Dave Hyatt, to take a look. I tried explaining what I had learned to Dave, but in truth I didn’t have much of value to offer. He went off to investigate the issue on his own.
After a few days, Dave had devised a set of optimizations that dramatically improved performance:
Improve the performance of RenderDeprecatedFlexibleBox
The main improvement came from deferring the painting of flex items until they had been placed in their final position by the flex layout algorithm. This avoided a huge chunk of work, and we could finally resize the App Store with much less lag.
At the time, I didn’t understand his changes. It may as well have been magic! And instead of trying to learn from this experience, I quickly moved on to issues where I felt more comfortable.
From regret to redemption
Afterwards, I felt bad about this for a long time. I was angry at myself for not being able to figure out a solution, and I decided to stay away from layout code from then on.
I spent the next few years all over WebKit (and deeper in the macOS/iOS software stacks) to improve browser performance, and learned thousands of things, but none of them about layout.
Flash forward to today, where I’ve implemented most of CSS flexbox (and many other layout modes) myself while working on the new Ladybird browser.
I’ve got a decent grasp on CSS layout, and I could definitely debug that App Store problem and improve their performance! Of course, it’s 10 years too late, but I feel good knowing I’ve repaired something that once made me feel so inadequate.
I don’t think I’ve ever mentioned this, but before I decided to start building a new browser, having to deal with layout was the main reason I kept putting it off. I was afraid that if I start building a browser, I’ll eventually get stuck on the mythologically impossible layout part, and fail.
Thankfully I decided to face my fears, and I’ve spent the last couple of years doing my best to help others not mythologize any aspect of software development by tackling “impossible” projects in public.
In retrospect, I should have ignored the mythologizing and made an effort to understand CSS layout sooner. It’s amazing fun once you get the hang of it!
If you’re interested, come hack on Ladybird with us in the SerenityOS project!
Great story, reminded me a bit of Raymond Chen / The Old New Thing. Also appreciate the reminder not to give in to the fear that surrounds certain mythical areas of a project. You once posted about the simple idea of approaching complex tasks that seem insurmountable by breaking them down into manageable tasks. It sounds so obvious now, but that advice really stuck with me and has been a huge help when I get stuck. Cheers!