Is Safari faster?

At the release of Safari 3 beta for Windows, there were several claims about Safari's page load performance. It was giving results that showed it was clearly faster than other browsers. Since I generally like to check things for myself instead of trusting what companies say, I gave the new Safari a whirl. Sure enough it was very fast. Its CSS was quick, its page loading was quick, its JavaScript was quick. Perhaps quick, but certainly not as fast as they seemed to claim it was.

I have a variety of machines that I tested it on, and all showed the same thing; it was fast, but not really faster than the competition (Opera, for example, but other browsers were also faster in some cases). But when tests used JavaScript to check the page loading speed, Safari produced numbers far smaller than other browsers, even though the actual page load was not faster when tested with a stopwatch. Simple CSS float tests would produce times of less than 100ms, while the actual page load (from disk, so no network lag is involved) could take as much as 5 seconds.

(Note: before any of you ask, I will not be adding results to the browser speed tests article - it is retired.)

When does onload fire?

The tests all relied on the onload event. That has always seemed trustworthy until now. However, Safari does not fire onload at the same time as other browsers. With most browsers, they will wait until the page is loaded, all images and stylesheets and scripts have run, and the page has been displayed before they fire onload . Safari does not.

In Safari, it seems onload fires before the page has been displayed, before layout has been calculated, before any costly reflows have taken place. It fires before images have completed decoding (this can also happen in rare cases in Opera, but Safari seems to do it everywhere), meaning that a substantial part of the load time is not included (note that part of this bug is marked as fixed in their bug tracking system, and the fix could turn up in a future release, but is not yet in any stable releases). So basically, onload is not trustworthy in Safari for checking page loading times.

It is possible to force Safari to layout the page before checking for the time. To do this, check for the offset values of any element, such as the offsetWidth of the body :

window.onload = function () { var ignoreMe = document.body.offsetWidth; var endTime = new Date(); };

Note, however, that this still does not include actually displaying the page, only calculating what will be displayed, so it is not perfect, but it does make it slightly closer to the behaviour of other browsers.

You can force the browser to scroll using window.scrollBy(0,1) to make Safari display the page (this is what iBench uses), but then this is no longer testing display speed. It tests display and scroll (with associated repaint) speed in browsers that have already displayed the page. This means it actually penalises other browsers to make up for Safari's strange behaviour, making the results more unreliable. In some cases, other browsers may display the page fast enough, but take much longer to actually scroll - an example would be testing opacity and fixed backgrounds in Opera.

Does progressive rendering play a part?

Many browsers, Opera and Firefox included, use progressive rendering during initial load. As soon as they start to receive page data, they start to parse it, generate the DOM, and display it. Scripts run as soon as they are received, while loading continues in the background. This means that if you start timing in an inline script at the start of the page, Opera and Firefox will start the timer before the page has completed loading, and therefore rest of the page load is taken into account by the timer.

A server that intentionally fed the test page slowly to the browser caused Opera and Firefox to show the expected long difference between the initial time, and the onload time. In Safari, onload fired about when it was expected, just before the final displaying of the page. But the time difference between the initial time and the onload time was very small; close to 0ms. So it would seem that Safari waits for the page to load before starting the parsing and script interpretation, meaning that it only showed the time to parse the DOM, not the time to load or display the page, while the other browsers would show the complete time.

Of course, progressive rendering itself does take a few more reflows, so it can take more time than a single render, but it also means you can begin to read the page earlier, so which one feels faster to a user may differ. However, I am only referring to completed load times here.

Can pages really load that fast?

How quickly can a browser load a page over a network? Can it establish a network connection, load a page, parse the HTML, initialise the script engine, and create a date object, all within 0ms? If it can, what about if the network lags a little; what happens if the network introduces several milliseconds worth of delays into the page load - can it still load within 0ms? Safari seems to think so.

To avoid the problems of progressive rendering vs. offscreen buffering, it is fairly easy to start a timer in one page, and then load the test page into an iframe. The test page can then check the difference between the completed page load time, and the time stored by the parent page. It would be nice if it were that simple. However, this shows a bug in Safari.

Safari does not update the date objects correctly. It appears to pause the date object while iframe src changing, page loading, and parsing is taking place, so the next request for a date object produces one with the same time as the one in the parent page. Even when forcing it not to cache the page, this still happens (and yes, I tested many times since I did not believe it myself). The page load itself could be seen to take about half of a second when timed with a stopwatch. Clearly, Safari has a bug with date objects here.

Was Safari cheating on the test?

Well, its results are almost certainly wrong, and it will appear a lot faster than it really is, if JavaScript is used to time it. The results are completely unreliable. As for whether or not it was intentional, I doubt it. The paused date object is almost certainly a bug that they need to fix. The offscreen buffering is intentional as it can improve load times on small pages or exceptionally long pages. It makes script timers unreliable, but there is nothing that says that a browser has to use progressive rendering.

As for onload firing early, that feels like a bug, but the spec does not say what counts as "loaded" - it does not say that the page has to be displayed (however, all images should have loaded, so that part is definitely a bug). Safari is not wrong to fire it before layout, but it is inconsistent with the other browsers. In some cases, this is useful, as a developer may want to manipulate the page before it displays, at the end of parsing, and with Safari's behaviour, it can do that without needing any extra reflows. Safari then adds in the offset measuring behaviour to allow for authors whose scripts expected the other behaviour. It works for real Web pages, but it is not good for performance timing. Firefox and Opera have a better approach, which is to use onload normally, and to use the DOMContentLoaded event to say what parsing is complete.

Are there other ways to test?

Since the browser itself has proven to be so unreliable in giving page loading times, it is not possible to use JavaScript alone to test page load/display times. Something more trustworthy needs to be used. The obvious solution is the trusty old stopwatch. It introduces reaction time errors, but it's not too bad for most tests as long as the test takes sufficiently long on its own.

To automate the tests, only a server is reliable enough. Use an outer page with an iframe initially displaying about:blank . Then do an XMLHttpRequest to the server to retrieve the time on the server (this is best done with a server on localhost to avoid network lag). The server can return the timestamp as a number (such as with PHP's microtime(true) function), and should return it with a mimetype of text/plain to make sure the browser does not waste time trying to parse it. Make sure the response cannot be cached. Change the src attribute of the iframe to be the test page (which should also not be cached).

When onload fires, check if there were any images, and if so, make sure they are all complete by checking their complete property. If not, wait for onload to fire for each of them before continuing. Once loading is complete, check the offsetWidth of the body , then make a second request to get the timestamp from the server. Check the difference to get the time. Note that this still does not include time to display the page, and I do not know any useful way to get around that limitation. It also includes the time used to make the XMLHttpRquests, which will alter the results slightly, though certainly not as much as Safari's own failure to use onload and date objects correctly.

So is Safari actually slow?

No. Safari is not slow. It is still a very fast browser, but it is certainly not as fast as it has been claimed to be. This is not an excuse for other browsers, and it is not a complaint. It is simply a warning; do not rely on performance benchmarks in Safari. They are wrong if they ever use JavaScript to detect page load times, which most of them do. Until the Webkit project can fix these problems, Safari benchmarks are not accurate or reliable.

This may not cause a problem for you (and I am not interested in receiving any emails saying that problems do not exist or are not really problems for you - they do exist and they are problems for others), but they are a problem for benchmarks. If you know a good workaround for them that can produce reliable benchmarks in Safari, please feel free to share. One of my readers has suggested using a video capture to time the load and then crop the video to test the time between link click and page load. As long as the capture software runs on another machine (to avoid CPU usage swaying the time), this should work, but it will not be easy to automate.