[webkit-dev] optimizing browser handling of Facebook Timeline scrolling
styoung.bills at gmail.com
Sat Feb 11 22:02:21 PST 2012
[cross posting from mozilla's dev lists]
I'm on the Timeline team at Facebook, which is going to be the new
format for everyone's profiles real soon now.
https://www.facebook.com/about/timeline We'd like to improve its
browser performance, so I'd appreciate any suggestions for things we
should change to speed it up. In particular, we'd like to make
scrolling down through new content smoother. There are often brief
(e.g. 300 ms) browser lockups, and other times there just seems to be
a general feeling of heaviness.
I'm going to list some of the specific issues we've identified, which
we are debating how best to fix, but I'm also very interested to hear
whatever anyone else thinks are the biggest perf bottlenecks.
A few problems:
(1) HTML / DOM size and CSS
Our HTML is huge. About half of it is coming from the light blue
"like/comment" widgets at the bottom of most stories. Within those
widgets, a major portion of it is always the same. (Some of that is
only needed once the user clicks into the widget, but we don't want
another server round trip to fetch it.) We also have a lot of CSS
rules, and applying all that CSS to all those DOM nodes gets
expensive. Experimentally, removing all like/comment widgets from the
page does give noticeably smoother scrolling, although it doesn't
completely fix the problem.
Related: We've also noticed that if you scroll very far down a
content-rich timeline, and then open and close the inline photo
viewer, this causes a noticeable lag, as it re-renders all existing
content on the page. To fix this, we investigated dynamically removing
offscreen content from the DOM and replacing it with empty divs of
the same height, but we decided it wasn't worth the code complexity
There are several fixed elements on the page like the blue bar at the
top, the side bar, and our date navigator with the months/years.
Chrome's --show-paint-rects flag showed that under most circumstances
these fixed-position elements forced full-screen repaints instead of
incremental repaints. The rules for what triggers a repaint vary from
browser to browser, but we would ideally like to fix this everywhere.
The cost of full page repaints also sometimes varies dramatically even
comparing Chrome on two fairly newish Mac laptops.
We dynamically load timeline sections (e.g. a set of stories from
2009) using our BigPipe system
(https://www.facebook.com/note.php?note_id=389414033919) in an iframe.
In a nutshell, the HTTP response to the iframe is sent with chunked
encoding, a <script> tag at a time. Each script tag contains some code
and and HTML content that is passed up to the parent window, which
requests the CSS and JS associated with that HTML content. Once the
CSS is downloaded, the HTML (timeline story markup) is inserted into
an offscreen DOM element. Then, once the JS is loaded, we do some
fairly complicated work before we actually display the content.
First, we lay out the timeline stories in an offscreen element
(position:absolute; left:-9999px) before inserting them into the
viewable page. We then have JS which checks the heights of all the
stories on in the offscreen element so it can swap stories back and
forth between the two columns, to keep things sorted by time going
down the page. To do this, we query and cache the stories' offsetTop
values all at once where possible. Probably, we could eliminate all
this height-checking and column balancing if we implemented a machine
learning algorithm to predict the height of each unit in advance, on
the server side.
Next, in an attempt to reduce user-percieved browser freezing while
scrolling, our JS does not add new content in to the bottom of the
main column as soon as it comes back from the server. Instead, we
queue it up until the user stops scrolling and add it in then. We use
document fragments where possible to insert elements. Web Inspector's
profiler showed improvements when dynamically inserting many <link
rel=stylesheet> tags in this fashion since we stopped thrashing
between "style recomputation" and JS execution for each stylesheet,
and instead just had one longer style recomputation segment.
We throttle scroll/resize events so they fire every 150 ms
All the while this is happening, we're potentially receiving more
<script> tags in the iframe and doing the same thing for other pieces
We would love any pointers you guys have.
More information about the webkit-dev