Borders And Relativity / by Zach Mayer

Today I encountered an interesting bug. Actually no, that's not right. It's a dumb bug that shouldn't have been encountered at all, much less eaten up over an hour of my day. That I'm sitting here writing about it is as sure a sign that the bugs are winning as anything else.

Would You Like To Know More?

I'm still going to write about it because it touches on some undefined behavior in modern browsers and a small gap in the CSS3 spec so strap in kids it's gonna get messy.

The Scenario

The problem presented itself as a graphical hiccup in one of the tables our product features as a first-class feature. It's the first thing you see when you log in so it's kind of important that it not make us look like gibbering howler monkeys with keyboards.

Without showing too much of the yet-to-be-released product, here's what I saw when I opened the ticket:

WAT.

WAT.

Something's clearly wrong. Really quick I'll go down the check-list of things it could be for you:

  1. Errant style on the row highlight class? 
  2. Extra border on the cell renderer?
  3. Something to do with border-collapse weirdness?
  4. A browser update to some relevant render pathway?
  5. Some CSS3 animation overflow?
  6. Positioning/Floating/Nesting of elements in or around the affected column?
  7. Some JS render logic affecting this cell renderer in particular?

You get the idea. The gist of the matter is this is likely something to do with an arcane composition of CSS/CSS3 that isn't playing nice. 

Now before you ask: yes, I did look at the GIT history for the relevant files, and no I didn't immediately see the problem. Unfortunately, this bug snuck up on me and wasn't reported when it first manifested. That means I'd have had to comb through potentially weeks or months worth of changes to maybe spot the problem. Needle meet haystack. Besides, if the suspicion is correct, and this is some weird conjunction of evil spirits it wouldn't just be one easy-to-spot problem, it'd be several innocent looking changes that came together to make Voltron.

But I still got a little help from the ticket itself. It turns out no one noticed it because it didn't appear on every column (first), and it only seemed to appear briefly in the inital rendering of the table (second), while the loading animation for another column is running (third).

So by the first clue, I can safely say this isn't just a css issue affecting the whole table, but one targeted at a particular cell renderer. By the second clue it's apparent that something in the tables JS render pathway is capable of correcting the problem, a by the third clue, it's possible that CSS3 (the animation mechanism for the loading state) is a potential culprit.

Taking these one at a time, I decided to start with the cell renderer for the affected cells. Interestingly, the same cell renderer is used by multiple columns in the table but only some show the bugged behavior. Even moving the columns around in the table makes the problem appear on different cells. Still, the affected cells are all the same type with the same renderer. Something might be wrong with the cell renderer but I don't know exactly what it could be.

Setting aside the cell renderer I started looking more closely at reproducing the problem in a stable state. In order to keep the bugged columns from fixing themselves later in the table's render cycle, I started interrupting the renderer of the whole table at different points to pin down when the problem self-corrects. It turns out the problem is only expressed when the loading animation is present on some rows, and only for rows with different background-colors than white.

So stopping the animation got me to this state:

Progress?

Progress?

Might be progress. CSS3 animation is clearly a factor, but it turns out I can get the same result by simply setting "border-collapse: separate" for the table. Unfortunately, I need the borders to collapse or the positioning of the columns with their headers won't work out and I'll have to go back through some really gnarly table render code to make the change fit.

Surely there's a simpler solution?

Turns out there was. 

When I was first looking at the affected cell renderer I had neglected to look at it's context in the table and as it happens, these particular cells were being rendered with "position: relative;"

Now that's not such a terrible thing under most circumstances. Tables all over the internet have cells with relative positioning for one reason or another, and why these cells were positioned that way I can't go in to but suffice to say that's a pretty innocuous attribute of a table cell.

The solution

Taking a step back, we have three things which might combine to cause problems:

  1. CSS3 Animations in a table cell
  2. Border-collapse
  3. Cells with position:relative

The last two when combined are known to cause problems but it's a pretty well known problem in almost every major browser since the dawn of time. The reason it's an issue at all, and especially why the same problem presents itself differently in every browser is because of this lovely gap in the W3C CSS2.1 Spec:

The effect of 'position:relative' on table-row-group, table-header-group, table-footer-group, table-row, table-column-group, table-column, table-cell, and table-caption elements is undefined.

Neat huh?

There are tricks and work-around out these for this kind of problem, and I'd already implemented a couple to make the table cells render properly in the first place, but with the introduction of the loading animation things got weird again. It turns out that even if you know all the tricks to dealing with border-collapsed cells with defined borders and relative positioning, CSS3 animations can still come through to fuck up your day, presumably because the animation affects the render model (box or otherwise) preventing certain repaints from occurring when a transformation is in play.

The solution? DON'T USE POSITION:RELATIVE ON TABLE CELLS.

In my case I just had to remove the positioning from the cell and move it to an interior container that spanned the height/width of the cell itself.

If you want to play with the problem I've put together a fiddle for you. Enjoy!