position:fixed in Firefox Mobile

It seems, somehow, for the last few months, I’ve been working on layout. I’m not quite sure how it happened, as anyone who’s spoken to me or follows me on Twitter would know that I have a very healthy fear of the Gecko layout code. I still have that fear, but I’d like to think now that it’s coupled with a tiny amount of understanding; understanding that has, dare I say it, even let me have fun while working on this code!

Those of you that have used browsers on mobile phones (all of you?), especially not the very latest phones, may be familiar with an annoying problem. That is, elements that have position:fixed in their style tend to jump around the place like they’ve had too much coffee. You commonly see this on sites that have a persistent bar at the top or bottom of the page, or floating confirmation notifications, things like this. Brad Frost wrote about this far more eloquently than I could here. This has always annoyed me, especially after learning more about how browsers work. Certainly in Gecko, we have all of the context we need for this not to happen. It also ended up that this problem had been worked on long before I even joined Mozilla last year, so that made it doubly surprising that we suffered from this problem in all releases of Firefox Mobile.

When I first came across this last year, I discovered that the support was already there in the old Firefox Mobile, but disabled by default due to it causing test failures. I was working on other things then, and wasn’t at all acquainted with layout code, so I let it be. Revisiting it for the new, native Firefox Mobile though, these test failures didn’t exist anymore. Enabling this basic support that would let position:fixed elements move and zoom with user input correctly was not too big a deal – just flip an environment variable and write a small amount of support code. This landed in Firefox 15 and is tracked in Bug 607417. Just this is enough for a lot of mobile sites to start using position:fixed (I’m looking at you, Twitter and Facebook!).

This wasn’t enough for me though. Around this time, Android 3.x (Honeycomb) tablets had been around for quite a while and the Galaxy Nexus with Android 4.0 (Ice-cream Sandwich) had just come out, both with even better support for position:fixed. Not to mention the iPhone, which has excellent support. A problem with our implementation in Firefox 15, is that anything fixed to the bottom or right of the screen, or anything that doesn’t anchor to the top-left in any way, may become inaccessible after zooming in. In recent versions of the Android stock browser, not only do these remain accessible, but they zoom very smoothly too. Not wanting to be one-upped by what could be considered our main competition, I started to work on more comprehensive position:fixed support. This work was tracked in Bug 758620.

When zooming in our browser, we don’t change how the page is laid out, but fixed position elements are still rendered relative to the viewport. What we want (at least, for now) is for fixed position elements to lay out with respect to this viewport so that they always remain visible, and to transition smoothly between these states. To achieve this, I changed layout so that fixed position elements are laid out to what we call the scroll-port. When we zoom in, we change the scroll-port, otherwise you wouldn’t be able to scroll to the bottom-right of the page, but this only changes scrolling behaviour and nothing else. This change also made it so that fixed-position children of the document would be relayed out when this scrollport was changed. This fixed the inaccessibility problem, but left nasty-looking transitions when zooming in.

To fix the transitions was quite a bit more comprehensive and lead me down a long path of causing and fixing various layout bugs. When a page is rendered, the DOM tree is parsed into a frame tree, which better represents the layout of the page. This frame tree is then parsed into a display-list, which represents how to draw the page. This display-list is then optimised and parsed/drawn into a layer tree, which is the final representation before we composite it. There’s cleverness to make sure that layers aren’t recreated unnecessarily, but that’s another subject for another time. We wanted to be able to annotate the layer tree so that when compositing, we have enough information to determine how to place fixed-position layers when zooming. This involved creating a new display-list item with the information about how the element is fixed (to the top? left? right? bottom?), and also that would guarantee that the items representing this element would end up on their own layer. Once this was done, code in the compositor was added to leverage this information and draw layers in the right place.

This is an area that a lot of browsers have difficulty with, so it was a fun problem to work on. Try out a couple of my testcases if you’re interested, they expose how different browsers handle this situation, and in the case of a few of them, some bugs 🙂 We’re still not perfect, but we’re better than we were before – and to my feeling, we’re adequate now. This work landed in Firefox 16.

So is there work left to do? Well, unfortunately, yes. I’ve just finished off support for fixed backgrounds and backgrounds with multiple fixed/non-fixed layers, and this will arrive in Firefox 18. This is tracked in Bug 786502. I also think that the best behaviour would be for fixed position elements to layout to whichever is largest of the CSS viewport or the scroll-port, and for scrolling to be within the CSS viewport and push the edges when you reach them. I’m told this is what happens in IE10 on Windows 8, and is similar (but slightly better) to what gets done in Safari on iOS. I think it’s about time for a break from this particular feature for me, though.