I have a nifty trick I wanted to share, since I think Web designers might find it useful when making reductions of bugs to send to browser developers. Basically I had this bug I was working on where content would sometimes shift too far to the right in Safari (don't worry, this bug isn't in 1.0). The bug was timing-related, and it only occurred intermittently when you reloaded the page.
The bug would only happen if layout occurred at a particular time, and usually this happened when a script was stalled loading long enough for the browser to decide it needed to show the user what it had so far.
If a script stalls, it's blocking the parsing of the rest of the content, so if a layout does occur, you're basically guaranteed that you're going to lay out the content of the page exactly up to the opening script tag and no more.
The bug in question would only happen if Safari had to do a layout before the script had loaded. Maciej (the JS guru on our team) came up with a clever idea: instead of pointing the script at an external resource, just make the script do something that would force a synchronous layout. Then you could basically construct test cases that made layout happen at precise chokepoints that you set up yourself! How cool is that?
A trick that works with both Safari and Mozilla is to try to access a property like offsetHeight, so here's an example:
<script>var v = document.body.offsetHeight;</script>
The layout engine basically has to lay out in order to give you a decent answer, so the handy little snippet above can be inserted anywhere in a document to force the layout of a page up to that point!
Armed with this trick I was able to easily fix the bug, since I now had a test case that demonstrated the problem 100% of the time. Thanks, Maciej!
In an earlier blog entry, I mentioned how Safari was having to emulate a quirk in Mozilla in order to make a particular Web site work. When Mozilla encounters a <script/> element with an XML-style self-closing /, it actually closes the tag, even in HTML pages. This is - I believe - incorrect behavior, since in HTML the / is invalid and should simply be discarded, leaving you with an open tag instead.
If you try this in WinIE and Safari 1.0, you'll see that they behave identically. They both ignore the / and leave the script open. Both Mozilla and Opera, however, honor the / and close up the script tag.
With my "fix" for this bug, the latest Safari will now honor the / as well. I wonder if this behavior was deliberate on the part of Mozilla and Opera or just some accidental fallout from their XML implementations... whatever the case, we're all stuck with it now, since Web sites are (amazingly) writing to it.
Let me share with you the pain and suffering that has been the last 10 hours of my life. It all started innocently enough. A few days ago I was visiting Dave Shea's site and I noticed that the three parallelograms at the top (zen garden, blue spark, and modernalus) didn't line up properly in Safari.
I quickly reduced the HTML+CSS and discovered that the problem was really elementary. Safari didn't support relative positioning of floats. It would just ignore any relative position you specified on a float. Oops. Given that the code in question literally looked like this:
if (style()->position() == RELATIVE)
setRelPositioned(true);
else if (style()->floating() != FNONE)
setFloating(true);
... the fix was a no-brainer...
if (style()->position() == RELATIVE)
setRelPositioned(true);
else if (style()->floating() != FNONE)
setFloating(true);
I made the fix, fired up Dave Shea's site, and the problem was solved. One quick code review later, the code was checked in to the Safari code base, and I moved on to other bugs.
Then today Don was idly going over the list of issues at Mark Pilgrim's site to see what we'd fixed since v85. We were chatting on the phone and he said, "Hey, the tabs are mispositioned on Mark's site. There's a vertical gap underneath them."
"No way," I said confidently (haha). I pulled up the site in the latest Safari, and sure enough, Mark's tabs were busted. I rolled back to a version of Safari a day or two earlier, and the tabs were fine.
Sighing, I began reducing the Web site. It seemed simple enough. The top background was a div with an id of logo that ostensibly was supposed to contain the title banner and the tabs. The tabs themselves were list items in an unordered list. I looked at the CSS for the tabs and - aha! - saw that the tabs were floating *and* they were relative positioned!
Since the tabs had a translation offset of -1.32em, they had been pulled up, and that's why a gap had been left. "This isn't a bug!" I stated confidently.
Then I pulled up the site in Mozilla and saw that it was using the same CSS. No gap. "What the hell?" I thought, irritated, and I broke out the trusty DOM inspector to dissect the layout in Mozilla. This is the point at which I began my descent into CSS hell.
You see, the CSS in question tripped over enough unspecified and ambiguous layout behavior to make a grown man cry.
Breaking it down, the first problem I noticed was with an innocent little span that followed the unordered list. Basically the structure was:
<div>
<ul>...</ul>
<span/>
</div>
The span in question had clear:both on it, which meant it was supposed to be pushed underneath the floats. Now in Safari the floats were about 14 pixels tall, and the span cleared them, which meant it was offset 14 pixels from the top of the enclosing div. The assumption Safari made was that the block itself should grow to enclose the span. This meant that the div itself was 14 pixels tall.
Not so in Mozilla. In the latest Mozilla, if a block had no bottom border and the last item in the block was a zero-height block child that cleared the floats, then even though the child itself was offset 14 pixels, the enclosing block had zero height. Make sense? Didn't think so. Welcome to my world.
Now remember that the tabs were relatively positioned at a negative offset, so this meant that there was now a 14 pixel gap in Safari. I tried a simple test in WinIE and discovered that WinIE behaved like Safari! Here's the little test case that shows the difference between Mozilla and WinIE/Safari.
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN">
<div style="background-color:#dddddd">
<div style="float:left; width: 2em; height:2em;background-color:lime"></div>
<div style="clear:both"></div>
</div>
So who was right? I had no frickin' clue, but Safari's behavior sure made more sense to me. I did notice that if you added a border to the enclosing div in the above test case, that then Mozilla would increase the height of the div.
What made this behavior potentially ambiguous was the definition of clear. In the CSS2 specification, clearing was specified as an increase in an object's top margin. Now because the span on Mark's site had no content and zero-height, it was considered a self-collapsing block. Self-collapsing blocks collapsed their top and bottom margins together, since the margins were adjacent.
I tried to rationalize Mozilla's behavior as follows: you could think of the span as having no margins initially, but then when it clears the float, it gets a top margin of 14px. That top margin collapses with its bottom margin, and then the 14px margin collapses with the bottom of the block.
I went and hacked Safari to have the same clear hack that Mozilla had, hoping that it would somehow fix the problem. Then I wrote more tests and discovered that Mozilla was just plain out to lunch.
<div style="background-color:#dddddd">
<div style="float:left; width: 2em; height:2em;background-color:lime"></div>
<div style="clear:both"></div>
</div>
<div style="border-top: 2px solid red"></div>
In the above example, you'd expect the red line to be below the float, but it ended up at the very top of the float, and the fact that a clear was specified was just completely lost.
I tried this interpretation of clear anyway (despite it matching no browser on the planet as far as I could tell) to see if it would fix the problem. When I made the change, suddenly the tabs ended up *outside* of the banner background.
Ok, so now I was officially irritated. I started looking at the render tree dumps in Safari to try to see what was going on. Then I figured it out. Above the div that enclosed the list of floats was a header tag (an h1). This tag had a bottom margin on it.
Since the ul had zero-height and contained only floating content, Safari collapsed the bottom margin of the h1 *through* the ul. This did not match Mozilla's behavior.
The problem I was running into now was that what constitutes a self-collapsing block has never been clarified. Web site developers know that when you have a tag like p by itself that it self-collapses, i.e., its top and bottom margins collapse together. But what if the same p contains a float? That float wouldn't be part of the normal flow, so why shouldn't the p still collapse its margins together? After all, the p itself is zero-height, so its margins are technically adjacent.
So this was my problem. With my hack for clear, all the siblings following the h1 were self-collapsing, so the bottom margin of the h1 actually collapsed into the banner's bottom margin, and so the banner itself ended up being smaller than was obviously expected.
I then tried rewriting Safari's definition of self-collapsing blocks so that objects with floats were no longer considered self-collapsing. Once I had this new definition of self-collapsing blocks, the site now rendered correctly.
Now I had two hacks, neither of which I was happy with, neither of which I considered correct behavior, but they made the site work. I was most concerned about this clear hack, since it didn't match any other browsers, so I decided I'd back it out.
After backing out the clear behavior, the tabs started mis-rendering again. Without both hacks I didn't have a fix. The problem was - from a behavioral standpoint - I didn't agree with any of the code I'd just spent 8 hours writing.
Now I was really confused. I could see why Mozilla rendered the tabs "correctly", since it had the buggy clear behavior combined with the non-self-collapsing block behavior, but why did the other browsers, who didn't have the clear hack render the tabs correctly?
I tried Opera first, and then I noticed that its rendering matched Safari's old rendering pre-relative positioning! I then thought that perhaps Opera didn't honor relative positioning on floats. Then I stumbled onto the use of the *7 hack in Mark's CSS, and it became clear why Opera rendered the tabs correctly. Basically Safari and Opera had the same behavior, but Opera had been given some special CSS love to make it render correctly!
I now believe that Safari's rendering (gap and all) is correct, so until a spec tells me otherwise, I'm changing nothing. NOTHING.
It took me 10 hours just to decide that what I did in the first place was correct. Aren't layout engines fun? ;)
In my previous blog I complained about a behavior in Mozilla where overflow:hidden on the body element is actually applied to the viewport rather than to the body element itself. This is done even in strict mode.
I thought this was a quirk, but then Ian Hickson pointed me to this section in the CSS2.1 specification. It contains the following highlighted green text (presumably this means a proposed change to CSS2).
"HTML UAs may apply the overflow property from the BODY or HTML elements to the viewport."
I think this is ridiculous on several levels. First of all, the language says HTML UAs, which means XHTML UAs are not allowed to do this. So the CSS spec is in effect implying that this quirky behavior in HTML UAs is acceptable by adding this sentence.
I also fail to see the point of using language like "may" when WinIE and Mozilla both implement this quirk. It's obvious that browser makers have no choice but to do this for HTML in order to be compatible with WinIE.
I don't see why the spec should be amended to condone this obviously quirky behavior, especially if it isn't going to apply for XHTML.
I got into an interesting discussion with Don and Darin yesterday about quirks in browsers. What instigated the discussion was a bug where Safari fails to render a particular Web site because it was given invalid HTML by the Web site.
The site in question has a pretty decent browser check. It uses document.all to detect IE, document.layers to detect NS4, and all other browsers are considered standards-compliant.
Interestingly, though, Opera supports document.all, so it ends up in the IE path. Konqueror also supports document.all, so it too ends up in the IE path. Mozilla and Safari don't support document.all or document.layers, and so end up being the two browsers that take the standards-compliant path.
What's interesting about this site is that it then spews out some invalid code that Mozilla (even in strict mode) honors. This code is NOT honored by WinIE, and Safari's behavior when given this invalid HTML matches the behavior of WinIE as well as the specified behavior according to the rules for HTML parsing. Clearly this is desirable.
This is not the first instance of quirks in other standards-compliant browsers biting Safari (and of course Safari has plenty of these bugs as well). There are plenty of other examples of unintentional strict mode quirks in Mozilla (its overflow:hidden quirk on body, designed to match WinIE's odd behavior, is actually enabled in strict mode) that bite Safari, since it follows the same path.
Now we're left with a dilemma. Do we start implementing quirks for browsers other than WinIE? The problem is that for some Web site designers, the standards compliance path has become "the Mozilla path," and these designers don't run their sites through other standards-compliant browsers like Opera and Safari. Thus they may end up making false assumptions as they stumble over hidden bugs in Mozilla or valid differences in browsers' rendering behavior.
The solution for Web designers is clear:
(1) Test your sites in multiple browsers!!
(2) Do not assume that just because a browser misrenders your site that the browser is wrong. Think the problem through, read the spec, consult other designers, and try to determine who is at fault. Remember that it's unfortunately very easy to trip over ambiguities in the specs as well, and you may be running into a situation where there are multiple valid interpretations.
For browser makers, the solution is not so clear. Mac IE introduced the concept of doctype switching, and browsers were quick to jump on board. Safari and Mozilla have identical doctype switching, and the same three modes (quirks, almost strict, and strict).
But if designers still have code paths in strict mode (e.g., .all, .layers, .getElementById) but then only test their code in one browser along the .getElementById path, then other browsers will potentially end up in trouble.
The conclusion that we came to was that we may very well have to introduce quirks into Safari to match bugs in Mozilla as well as bugs in WinIE. In some cases we may have to introduce these quirks despite the fact that they don't even occur in WinIE! What a no-win situation.
Whatever makes the sites work though... that's what we've gotta do. :)
Dave Winer writes about a problem Safari has with Chris Lydon's weblog. This particular bug has been the subject of discussion on other sites as well.
The bug in question was really caused by poor CSS, and, although Safari certainly should have handled this poor CSS in a better fashion, I felt compelled to blog about this in more detail so that Web designers can avoid this coding error in the future.
What causes Safari to get confused is that float:left is specified on a td. This is a rather nonsensical thing to do, and the browser really has two choices regarding how it can handle this scenario.
Choice One, the WinIE way, is to just drop the float property completely and treat the td like a table cell. This results in the rendering that Chris Lydon clearly expects.
Choice Two, the Mozilla way (and definitely the correct way according to the spec), is to honor the float property, which means the td is no longer a table cell but a float, and so the float gets wrapped in an anonymous table cell that encloses it. The specified width is no longer used to participate in table layout, and so that's why Mozilla "misrenders" the links on the right-hand-side of Lydon's weblog.
On to Safari. Safari was trying to give Web designers the best of both worlds. In Safari, I implemented a quirk that was supposed to give you the WinIE behavior in quirks mode, but give you the Mozilla behavior in strict mode. Chris Lydon's weblog (based off the transitional doctype) is in quirks mode.
The problem is that I didn't get the floating bit cleared in all of the places that checked for it, so the table cell ended up being quasi-floating, and thus event handling got broken on the cell.
Anyway, the quirk has been improved so that Lydon's blog will render as WinIE does in quirks mode, and if he decides to ever shift to strict mode it will render like Mozilla does.
In the mean time, the float:left should clearly be removed so that Mozilla can start rendering the site as the designer expected as well.
I fixed the bug where selection copies the raw HTML source and not the rendered source. I know that will make a lot of people very happy. It's especially satisfying fixing this bug, since I'm the one who caused it in the first place. ;)
| Sun | Mon | Tue | Wed | Thu | Fri | Sat |
|---|---|---|---|---|---|---|
| 1 | ||||||
| 2 | 3 | 4 | 5 | 6 | 7 | 8 |
| 9 | 10 | 11 | 12 | 13 | 14 | 15 |
| 16 | 17 | 18 | 19 | 20 | 21 | 22 |
| 23 | 24 | 25 | 26 | 27 | 28 | 29 |
| 30 | 31 |