Introduction to the Resource Timing API
SitePoint has published some good articles on performance recently. In particular, Craig Buckler wrote an article entitled Average Page Weights Increase by 32% in 2013 along with another one discussing some easy wins to reduce the weight of a website.
But improving the speed of our websites and apps isn’t just a matter of minifying JavaScript files and employing an image compressor. In many cases we have to deal with external resources (libraries from a CDN, videos from YouTube, images from Flickr, and so on) or internal resources that need to be accurately measured to establish what’s slowing down the loading of our pages.
I’ve previously covered some APIs that can help in testing the performance of a website. For example, the High Resolution Time API and the User Timing API. In this article, I’ll introduce you to another API in this category: The Resource Timing API.
What’s the Resource Timing API?
The Resource Timing API allows you to collect complete timing information related to resources in a document. It enables you to measure user latency, which is crucial to benchmark web pages. This API is a W3C Candidate Recommendation so we can be sure that significant features are mostly locked, but there could be minor changes to the spec in the future.
Using the Resource Timing API allows us to retrieve and analyze a detailed profile of all the network timing data for every resource on the page. We’ll cover what these data are in the next section. For the moment, it’s important that you understand how this API can help us in tracking the performance of the resources of our page, and determine how many and what resources we have to optimize.
Now that we know what this API is, let’s delve into its features.
Methods, Properties, and Events
The Resource Timing API is exposed through the performance
property of the window
object. We can retrieve the information collected for each resource using the getEntriesByType()
method. If this sounds familiar to you, this is because it’s the same method used by the User Timing API. The difference is that to retrieve information about these resources we have to pass the string resource
to getEntriesByType()
.
The getEntriesByType()
method returns a list of PerformanceResourceTiming
objects, which extend the PerformanceEntry
interface. Because of this, each of these objects expose the following inherited properties:
name
– The resolved URL of the requested resourceentryType
– The value is always equal toresource
startTime
– The time immediately before the user agent starts to queue the resource for fetchingduration
– The difference betweenresponseEnd
andstartTime
In addition to these properties, the Resource Timing API exposes another property called initiatorType
. If the request has been initialized by an element on the page, its value is equal to the name of the tag (e.g. img
for <img>
, script
for <script>
, and so on). Other possible values are css
(for CSS resources downloaded using the url()
syntax — e.g. background: url(...)
) and xmlhttprequest
(for XMLHttpRequest
objects).
Each PerformanceResourceTiming
object provides the following specific and read-only attributes:
initiatorType
redirectStart
redirectEnd
fetchStart
domainLookupStart
domainLookupEnd
connectStart
connectEnd
secureConnectionStart
requestStart
responseStart
responseEnd
The following image offers a graphical representation of these attributes. Those that are underlined may not be available when fetching resources from different origins:
Resources fetched from a third-party must provide an additional HTTP header (Timing-Allow-Origin: *
) to allow the site to gather detailed network timing data. If the header is absent, the only available data is the total duration of the request. This may seem like an important limit to the use of this API. However, as Ilya Grigorik wrote in his post Measuring network performance with Resource Timing API, several websites like Google, Facebook, and Disqus, have implemented the header to offer this information.
As we’ve seen, the Resource Timing API provides a lot of attributes that we can read to understand where the time is spent for each resource. However, it also provides two methods: clearResourceTimings()
and setResourceTimingBufferSize()
. The former clears the buffer used to store the current list of PerformanceResourceTiming
resources.
The latter sets the maximum number of objects stored in the buffer. It accepts an integer to specify the limit. If the method isn’t called explicitly, the specification states that the user agent should store at least 150 PerformanceResourceTiming
resources. When the limit of the resources to store is reached, the API fires an event called onresourcetimingbufferfull
.
Now that we’ve learned the attributes, methods, and events exposed, let’s see how many browsers support this API.
Browser Compatibility
Browser support for this API is decent on desktop as it has been implemented in Chrome 25+, Internet Explorer 10+, and Opera 15+. On mobile, the situation is very similar with the addition of the latest versions of the Android browser.
Testing if a browser supports this API is a bit tricky because we have to test for several conditions. First we have to test for the presence of the performance
property of the window
object. Then, we have to test for the presence of the getEntriesByType()
method and that it’s able to collect resources. This last condition can be verified by checking that the call to getEntriesByType('resource')
returns an array.
Turning this description into code results in the following snippet:
if ( !('performance' in window) ||
!('getEntriesByType' in window.performance) ||
!(window.performance.getEntriesByType('resource') instanceof Array)
) {
// API not supported
} else {
// API supported. Hurray!
}
Building a Demo
In this section we’ll build a simple demo that allows us to see this API in action and the information it provides. The demo loads two resources: an image from SitePoint.com included via an <img>
tag, and the jQuery library from the Google CDN via a <script>
tag.
While both are external resources, the latter allows us to gather timing information thanks to the Timing-Allow-Origin: *
header. This means that even if it’s an external resource, we’ll obtain all the information exposed by the API.
With the markup in place, the first thing we have to do is to test if the browser supports the Resource Timing API. If the API isn’t supported, we display the message “API not supported”. In case the browser implements the API, we attach a listener to the load
event of the window
object. In this way we’re sure to execute any action after all the resources are loaded. Inside the handler, we create a list on-the-fly to show the measures obtained through the API.
Our demo page will use the following HTML:
<span id="rt-unsupported" class="hidden">API not supported</span>
<div>
<h2>Test 1 - jQuery</h2>
<ul id="script-list">
</ul>
</div>
<div>
<h2>Test 2 - Image</h2>
<img src="https://www.sitepoint.com/wp-content/themes/sitepoint/assets/svg/sitepoint.svg" />
<ul id="img-list">
</ul>
</div>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
And here’s the JavaScript that will display some data that we can examine, which will show us what the API can do:
if ( !('performance' in window) ||
!('getEntriesByType' in window.performance) ||
!(window.performance.getEntriesByType('resource') instanceof Array)
) {
document.getElementById('rt-unsupported').classList.remove('hidden');
} else {
window.addEventListener('load', function() {
var resources = window.performance.getEntriesByType('resource');
for(var obj in resources) {
var list = '';
for(var properties in resources[obj]) {
list += '<li>' + properties + ': <span class="value">' + resources[obj][properties] + '</span></li>';
}
document.getElementById(resources[obj].initiatorType + '-list').innerHTML = list;
}
});
}
You can view the code live here.
Conclusion
As we’ve seen, using this API shouldn’t be too difficult for you to employ it in a future project. Unfortunately, the support among browsers is not ideal, but the fact that three of the major browsers (Chrome, Opera, and Internet Explorer) supports it is still good news.
There are no more excuses for failing to improve the performance of your website, and this new API will make it that much easier.