<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">

  <title><![CDATA[Andy Davies]]></title>
  <link href="https://andydavies.me/atom.xml" rel="self"/>
  <link href="https://andydavies.me/"/>
  <updated>2022-02-02T14:58:57+00:00</updated>
  <id>https://andydavies.me/</id>
  <author>
    <name><![CDATA[Andy Davies]]></name>
    
  </author>
  <generator uri="http://octopress.org/">Octopress</generator>

  
  <entry>
    <title type="html"><![CDATA[Bypassing Cookie Consent Banners in Lighthouse and WebPageTest]]></title>
    <link href="https://andydavies.me/blog/2021/03/25/bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/"/>
    <updated>2021-03-25T17:33:34+00:00</updated>
    <id>https://andydavies.me/blog/2021/03/25/bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest</id>
    <content type="html"><![CDATA[<p>When it comes to testing pages using Lighthouse, WebPageTest or other similar tools, cookie and similar consent banners are a pain!</p>

<p>They can cause layout shifts, on mobile the banner is sometimes the element for Largest Contentful Paint (LCP), they visually obscure other parts of the page, and if implemented correctly only some of the page's resources will be fetched.</p>

<p>When analysing sites one of my first steps is to work out how to bypass any consent banners so I can get a more complete  view of page performance.</p>

<p>This post covers some of the performance issues related to consent banners, how I bypass the banners, and my approach to working out which cookies or localStorage items I need to set to bypass them.</p>

<!--more-->


<p><a href="https://twitter.com/simonhearne">Simon Hearne</a> wrote about <a href="https://simonhearne.com/2020/testing-behind-consent/">Measuring Performance behind Content Popups</a> in May 2020, and while there's some overlap I'd recommend you read Simon's post too.</p>

<h1>Challenges that Banners Bring</h1>

<h2>Layout Shifts</h2>

<p>Some banners are displayed at the top of the page before other content and there's a danger that if the banner is inserted after rendering starts, the content below them will shift downwards.</p>

<p>In a filmstrip of gov.uk there's layout shift at 0.7s when the consent banner is displayed (they have plans to address the issue in the future)</p>

<p><img src="https://andydavies.me/blog/images/2021-03-25-bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/gov.uk.png" alt="Filmstrip showing the layout shift when the consent banner loads on gov.uk" />
<a href="https://www.webpagetest.org/result/210322_Xi41_580a579520caaf848e6fc4ca6f16412b/">gov.uk – London, Chrome, Cable</a></p>

<p>One way of eliminating this shift would be to adopt the <a href="https://www.zachleat.com/web/layout-shift/">approach Zach Leatherman took with a banner on Netlify</a> and include them directly in page but hide then when they're not required.</p>

<p>Another is to use a pop-over approach where the consent banner is positioned over the page content.</p>

<h2>Largest Contentful Paint</h2>

<p>On some pages, and particularly on mobile, parts of the consent banner get detected as the Largest Content Paint.</p>

<p>Currys PC World have this issue on their category pages, but not on their product pages, so removing the banner is important   if we want get comparable measurements between the different page types.</p>

<p><img src="https://andydavies.me/blog/images/2021-03-25-bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/currys.co.uk.png" alt="Filmstrip showing the cookie banner as Largest Contentful Paint on currys.co.uk" />
<a href="https://www.webpagetest.org/result/210323_Xi37_38d499ba61fcd7fc18994a49becdade4/">Currys PC World – London, Chrome, Cable</a></p>

<p>There are other sites with very similar banners but yet another  element is counted as the Largest Contentful Paint, so checking which element is being used is important.</p>

<h2>Obscure Key Content</h2>

<p>Filmstrips are a powerful way to convey performance to anyone regardless of their web performance knowledge and I rely on them to help clients understand what the current experience and to demonstrate how it improves as optimisations are implemented.</p>

<p>And as consent banners can cover up the key content, they just get in the way!</p>

<h2>Partial Measurement</h2>

<p>In countries where opt-in consent is required for 3rd-party data collection, the banner should delay the load of such scripts until consent is given.</p>

<p>These extra scripts influence performance – at the very least they'll increase the total bytes downloaded but often they'll also introduce long tasks, layout shifts and other behaviour that affects performance metrics too.</p>

<h1>Bypassing Cookie Consent</h1>

<p>Some sites add a 'developer option' to their consent banners, for example  a query string parameter that prevents the banner from being displayed.</p>

<p>This makes testing with and without consent banners much easier but often this option doesn't exist, and sometimes banners are provided by a third-party services so typically in these cases cookies need to be set to avoid them.</p>

<h2>Lighthouse</h2>

<p>By default, Lighthouse doesn't support cookies, so <a href="https://web.dev/measure/">web.dev/measure</a>, <a href="https://developers.google.com/speed/pagespeed/insights/">Page Speed Insights</a> etc. will test the site with the consent banner shown.</p>

<p>And if the consent banner is configured correctly then in opt-in e.g. GDPR, regions the Lighthouse score will be based on a partial page load i.e. without any 3rd-party tags.</p>

<p>But <a href="https://developers.google.com/publisher-ads-audits">Publisher Ad Audits for Lighthouse</a> does allow cookies to be set – click on the <em>Advanced Settings</em> button, and paste cookie in the relevant field.</p>

<p><img src="https://andydavies.me/blog/images/2021-03-25-bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/lighthouse-for-ads.png" alt="Screenshot of Lighthouse for Ads" /></p>

<p>If you need a score or advice on how to improve using this version of Lighthouse is a quick way to get that.</p>

<p>Cookies can also be set in <a href="https://github.com/GoogleChrome/lighthouse-ci/blob/main/docs/configuration.md">Lighthouse CLI and CI</a></p>

<h2>WebPageTest</h2>

<p>Often I want more than Lighthouse provides…</p>

<p>I want waterfalls so I can dig into network performance, filmstrips to demonstrate the benefits of optimisations, and Request Maps help understand the 3rd-parties on a page.</p>

<p>And most of the time I use WebPageTest to generate this data with one of these two approaches for bypassing consent banners.</p>

<p>I either set cookies via the <code>setCookie</code> script command:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>setCookie https://%HOST% cookie_name=...
</span><span class='line'>navigate %URL%</span></code></pre></div></figure>


<p><em>Note:</em> <code>%URL%</code> and <code>%HOST%</code> are WebPageTest script variables that will be replaced with the relevant part of the URL being tested. One day I'll submit a PR to add <code>%ORIGIN%</code> so <code>https://%HOST%</code> can be replaced with something more friendly.</p>

<p>Or via an injected script:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span><span class="o">=</span><span class="s1">&#39;cookie_name=...&#39;</span><span class="p">;</span>
</span></code></pre></div></figure>


<p>Something to consider is there's a slight timing difference as to when the cookie is set between the two WebPageTest approaches.</p>

<p>In the first approach, the cookie is set before navigation, so the cookie is available to any inline scripts that are early in the page, or any server-side processes that might generate an inline cookie banner.</p>

<p>Whereas the second approach sets the cookie after the browser starts to receive the HTML content, which might be too late for some cookie banners but allows extra commands such as setting localStorage items to be added.</p>

<p>Another advantage of the injected script approach is the script can also be tested in the DevTools console – clear storage, execute the script in the console, then reload the page and if the script's correct the consent banner shouldn't appear.</p>

<h1>Sometimes Cookies aren't Enough</h1>

<p>Some consent banners – IAB EU Consent Management Providers (CMPs) such as Quantcast Choice – also use local storage so just setting cookies isn't enough to bypass them.</p>

<p>To bypass these types of consent banners, I rely on injecting a custom script in WebPageTest.</p>

<p>For Quantcast Choice, the injected script looks like this (values omitted):</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;CMPList&#39;</span><span class="p">,</span> <span class="s1">&#39;...&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;noniabvendorconsent&#39;</span><span class="p">,</span> <span class="s1">&#39;...&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nx">localStorage</span><span class="p">.</span><span class="nx">setItem</span><span class="p">(</span><span class="s1">&#39;_cmpRepromptHash&#39;</span><span class="p">,</span> <span class="s1">&#39;...&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="s1">&#39;euconsent-v2=...;&#39;</span>
</span><span class='line'><span class="nb">document</span><span class="p">.</span><span class="nx">cookie</span> <span class="o">=</span> <span class="s1">&#39;addtl_consent=...;&#39;</span>
</span></code></pre></div></figure>


<p>Creating these scripts for each site that uses Quantcast got pretty boring pretty quickly, so I've started using a <a href="https://github.com/andydavies/webpagetest-cookie-consent-scripts/blob/main/scripts/quantcast-choice.md">DevTools snippet to generate the script</a>.</p>

<p>Running the snippet in DevTools produces a script ready to paste into WebPageTest's Inject Script field (which is at the bottom of the <em>Advanced</em> tab)</p>

<p>I'll probably add snippets for other common CMPs as I come across them at clients and prospects, but Pull Requests are also very welcome!</p>

<h1>Determining the Combination of Cookies and localStorage Items</h1>

<p>One remaining challenge is determining which cookies, and localStorage items need to be set.</p>

<p>Originally I relied on a combination of trial and error – inspecting storage, debugging minified scripts in DevTools and then testing in DevTools and WebPageTest – to work this out.</p>

<p>But one day, while lying awake in the early hours, I realised there might be an easier way to determine what's needed, and so now my process starts like this:</p>

<ol>
<li>Open a guest mode window in Chrome</li>
<li>Open DevTools</li>
<li>Load the site so the cookie banner is visible</li>
<li>In DevTools <em>Network</em> panel, switch to _Offline</li>
<li>In the <em>Application</em> panel, <em>Clear site data</em> (it's under the <em>Storage</em> section), and remember to check <em>including third-party cookies</em></li>
<li>Consent to the cookies via the cookie banner</li>
<li>Inspect <em>Local Storage</em>, and <em>Cookies</em> to see what items have been set</li>
<li>Take an informed guess at which of the cookies and localStorage entries set are for the consent banner</li>
<li>Write a script and test it</li>
</ol>


<p>It's not a foolproof method, but it's certainly helped me get a head start with many sites I analyse.</p>

<p>Some sites make a network request as part of the consent process, and this fails in offline mode so not all the cookies and localStorage items get set – <a href="https://www.theguardian.com/uk">The Guardian</a> is one site that throws up this issue.</p>

<h1>Wrapping Up</h1>

<p>Although I've concentrated on bypassing consent banners, testing with them in place is still important as it helps to understand our visitors' initial experience.</p>

<p>After all, if our visitors have a bad initial experience they may abandon without even interacting with the consent banner.</p>

<p>One thing I've noticed across multiple sites is how late many consent banners load – sometimes banner's are delayed because they depend on an external script loading, other times they wait for events such as <code>DOMContentLoaded</code> before being shown.</p>

<p>I'm not sure whether the aim should be to display the consent banner as soon as possible, or whether displaying content and then covering it with a banner is OK, and I couldn't find any research that helped clarify the issue.</p>

<p>Tests that display a consent banner only provide a partial view – particularly when testing is done from countries that require opt-in – so bypassing the banner helps us to build a more complete view of performance within testing and monitoring processes.</p>

<p>Revisiting the page from Currys when consent has been given; the Largest Contentful Paint gets faster, there's not much difference in Total Blocking Time, but the tags that execute  introduce several Layout Shifts.</p>

<p><img src="https://andydavies.me/blog/images/2021-03-25-bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/lcp-tbt-comparison.png" alt="Comparison of Largest Contentful Paint and Total Blocking Time with and without Consent Banner" /></p>

<p><img src="https://andydavies.me/blog/images/2021-03-25-bypassing-cookie-consent-banners-in-lighthouse-and-webpagetest/cls-comparison.png" alt="Comparison of Cumulative Layout Shift with and without Consent Banner" /></p>

<p>The full comparison of the <a href="https://www.webpagetest.org/video/compare.php?tests=210325_XiCc54_fda5e846beeed3a496ee5e4a23b5bb1c%2C210325_XiBF_6426cd43ec8d28a266a14db42273ec55&amp;thumbSize=200&amp;ival=500&amp;end=visual">Currys page with and without the Consent Banner</a> is available on WebPageTest.</p>

<p>Although I've focused on Lighthouse and WebPageTest, the techniques for determining what cookies (and localStorage items) are needed to bypass consent banners should also work with other tools – many support setting headers and some, DebugBear for example, support injecting scripts.</p>

<h1>Further Reading</h1>

<p><a href="https://simonhearne.com/2020/testing-behind-consent/">Measuring Performance behind consent popups</a>, <a href="https://twitter.com/simonhearne">Simon Hearne</a>, May 2020</p>

<p><a href="https://developers.google.com/publisher-ads-audits">Publisher Ad Audits for Lighthouse</a></p>

<p><a href="https://www.zachleat.com/web/layout-shift/">Ruthlessly Eliminating Layout Shift on netlify.com</a>, <a href="https://twitter.com/zachleat">Zach Leatherman</a>, Nov 2020</p>

<p><a href="https://github.com/andydavies/webpagetest-cookie-consent-scripts">WebPageTest Cookie Consent Scripts</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[The Case Against Anti-Flicker Snippets]]></title>
    <link href="https://andydavies.me/blog/2020/11/16/the-case-against-anti-flicker-snippets/"/>
    <updated>2020-11-16T15:27:57+00:00</updated>
    <id>https://andydavies.me/blog/2020/11/16/the-case-against-anti-flicker-snippets</id>
    <content type="html"><![CDATA[<p>I still remember the first time I came across an anti-flicker snippet…</p>

<p>A client had asked me to look at the speed of their sites for countries in South East Asia, and South America.</p>

<p>The sites weren’t as fast as I thought they should be but then they weren’t horrendously slow either but, something that troubled me was how long they took to start displaying content.</p>

<p>I was puzzled…</p>

<!--more-->


<p>When I examined the waterfall in WebPageTest I could see the hero image being downloaded at around 1 second but yet nothing appeared on the screen for 3.5 seconds!</p>

<p>I switched to DevTools and sure enough saw the same behaviour.</p>

<p>Profiling the page showed that even though multiple images were being downloaded quickly,  the browser wasn’t even attempting to render them for several seconds.</p>

<p>Hunting through the source I found this snippet (I’ve unminified it to make it easier to read)</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'>
</span><span class='line'>   <span class="c1">// pre-hiding snippet for Adobe Target with asynchronous Launch deployment</span>
</span><span class='line'>
</span><span class='line'>   <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">g</span><span class="p">,</span><span class="nx">b</span><span class="p">,</span><span class="nx">d</span><span class="p">,</span><span class="nx">f</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span><span class="nx">c</span><span class="p">,</span><span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>           <span class="k">if</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>               <span class="kd">var</span> <span class="nx">e</span> <span class="o">=</span> <span class="nx">b</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&quot;style&quot;</span><span class="p">);</span>
</span><span class='line'>               <span class="nx">e</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">c</span><span class="p">;</span>
</span><span class='line'>               <span class="nx">e</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">d</span><span class="p">;</span>
</span><span class='line'>               <span class="nx">a</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span>
</span><span class='line'>           <span class="p">}</span>
</span><span class='line'>       <span class="p">})(</span><span class="nx">b</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s2">&quot;head&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="s2">&quot;at-body-style&quot;</span><span class="p">,</span> <span class="nx">d</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>       <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>           <span class="kd">var</span> <span class="nx">a</span><span class="o">=</span><span class="nx">b</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s2">&quot;head&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>           <span class="k">if</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>               <span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">b</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&quot;at-body-style&quot;</span><span class="p">);</span>
</span><span class='line'>               <span class="nx">c</span> <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span>
</span><span class='line'>           <span class="p">}</span>
</span><span class='line'>       <span class="p">},</span><span class="nx">f</span><span class="p">)</span>
</span><span class='line'>   <span class="p">})(</span><span class="nb">window</span><span class="p">,</span> <span class="nb">document</span><span class="p">,</span> <span class="s2">&quot;body {opacity: 0 !important}&quot;</span><span class="p">,</span> <span class="mi">3</span><span class="nx">E3</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>The snippet does two things:</p>

<ul>
<li>Injects a style element that hides the <code>body</code> of the document by setting it’s <code>opacity</code> to <code>0</code></li>
<li>Adds a function that gets called after 3 seconds to remove to the style element. This is a fallback in incase the Adobe Target script fails or takes a longer than 3 seconds to reach the point where it would remove the style element.</li>
</ul>


<p>Google Optimize, Visual Web Optimizer (VWO) and probably others adopt a similar approach.</p>

<p>Urgh…</p>

<h2>Impact of Anti-Flicker Snippets</h2>

<p>Proponents argue that we need anti-flicker snippets because the potential of visitors seeing the page change as experiments are executed is a poor experience and visitors knowing they are part of an experiment can influence the results a.k.a <a href="https://en.wikipedia.org/wiki/Hawthorne_effect">The Hawthorne Effect</a>.</p>

<p>I’ve not found any studies that validate this argument, but it may have merit as one of the Core Web Vitals, Cumulative Layout Shift, aims to measure how much elements move around during a page’s lifetime. And the more they move the worse the visitor’s experience is presumed to be.</p>

<p>As a counterpoint lets examine the experience that anti-flicker snippets deliver.</p>

<p>Here’s a filmstrip from WebPageTest that simulates a visitor navigating from Gymshark’s home page to a category page.</p>

<p><img src="https://andydavies.me/blog/images/2020-11-16-the-case-against-anti-flicker-snippets/gymshark-annotated.jpeg" alt="Filmstrip showing the effect of an anti-flicker snippet as Gymshark loads" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=201113_Di0D_b5092d30ce212641d0e3b3b3b87f36b6-r%3A1-c%3A0-s%3A2&amp;thumbSize=200&amp;ival=1000&amp;end=visual">Gymshark – navigating from home to category page – London, Chrome, Cable</a></p>

<p>There are some blank frames in the middle of the filmstrip where the anti-flicker snippet hides the page for a few seconds (it’s actually 1.7s if you use the link above to view the test result)</p>

<p>By default Google Optimize uses a timeout of 4 seconds so in this case we can determine that the experimentation script completed before the timeout.</p>

<p>Compare this to a test where the anti-flicker snippet has been removed (<a href="https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/">using a Cloudflare Worker</a>) and we can see the page renders progressively so at least in this case hiding the page doesn’t add to the visitors experience.</p>

<p>The blank frames also indicate to visitors that they may be part of an experiment, whether they realise this or not they may be aware the experience is different compared to other sites.</p>

<p><img src="https://andydavies.me/blog/images/2020-11-16-the-case-against-anti-flicker-snippets/gymshark-no-anti-flicker.jpeg" alt="Filmstrip showing Gymshark loading without the anti-flicker snippet" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=201113_DiKD_466502f0f6a5869efb1d891efd055c91-r%3A1-c%3A0-s%3A2&amp;thumbSize=200&amp;ival=1000&amp;end=full">Gymshark – navigating from home to category page with anti-flicker snippet removed – London, Chrome, Cable</a></p>

<p>When the experimentation script doesn’t finish execution before the timeout expires then the visitor will get the ‘worst of both worlds’ – they’ll see a blank screen for a long time and then potentially see the effect of the experiment as it executes.</p>

<p>This might be because a visitor has a slower device or network connection, or the experimentation script being large and so taking too long to fetch and execute, or because other scripts in the page are delaying the experimentation scripts.</p>

<p>What we don’t know is how long visitors are staring at a blank screen!</p>

<h2>Measuring the Anti-Flicker Snippet</h2>

<p>Ideally, the experimentation frameworks would communicate when key events occur perhaps by firing events, posting messages or creating <a href="https://developer.mozilla.org/en-US/docs/Web/API/User_Timing_API">User Timing</a> <code>marks</code> and <code>measures</code>.</p>

<p>But in common with many other 3rd-party tags they seem reluctant to do this, so we have to create our own methods for measuring them.</p>

<p>All three examples below use the <a href="https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver">MutationObserver API</a> to track when either the class that hides the document, or the style element with the relevant styles in is removed (different products adopt slightly different approaches to hiding the page).</p>

<p>Each example sets a User Timing <code>mark</code>, named <code>anti-flicker-end</code>, when the anti-flicker styles are removed.</p>

<p>How long the page is hidden could be measured by adding a start <code>mark</code> to the snippet that hides the page and then using <code>performance.measure</code> to calculate the elapsed duration.</p>

<p>Some RUM products can be configured to collect the marks and measures created, others rely on explicitly calling their API (as do analytics products).</p>

<p>In my testing so far I’ve found the measurement scripts almost match the blank periods in WebPageTest filmstrips but they often measure a couple hundred milliseconds before the blank period actually ends. This isn’t surprising as once the styles have been updated, the browser still has to layout and render the page. In the future perhaps in the Element Timing might provide a more accurate measurement.</p>

<p>Although I’m working towards deploying these with a client, we’ve not deployed it yet, so treat them as prototypes and test them in your own environment!</p>

<p>Also I spend more time reading other people’s code than writing my own, so feel free to suggest ways to improve the measurement snippets, checks for Mutation Observer support could be added for example.</p>

<p>I’ve created a <a href="https://github.com/andydavies/tag-timing-snippets">GitHub repository to track the scripts</a> (I plan on adding more for other 3rd-parties) and pull requests are very welcome!</p>

<ul>
<li><strong>Google Optimize</strong></li>
</ul>


<p>Google Optimize <a href="https://developers.google.com/optimize#the_anti-flicker_snippet_code">adds an <code>async-hide</code> class to the html element</a> so the script detects when this class is removed.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">callback</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">mutationsList</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="c1">// Use traditional &#39;for loops&#39; for IE 11</span>
</span><span class='line'>   <span class="k">for</span><span class="p">(</span><span class="kr">const</span> <span class="nx">mutation</span> <span class="nx">of</span> <span class="nx">mutationsList</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">mutation</span><span class="p">.</span><span class="nx">target</span><span class="p">.</span><span class="nx">classList</span><span class="p">.</span><span class="nx">contains</span><span class="p">(</span><span class="s1">&#39;async-hide&#39;</span><span class="p">)</span> <span class="o">&amp;&amp;</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">attributeName</span> <span class="o">===</span> <span class="s1">&#39;class&#39;</span> <span class="o">&amp;&amp;</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">oldValue</span><span class="p">.</span><span class="nx">includes</span><span class="p">(</span><span class="s1">&#39;async-hide&#39;</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>           <span class="nx">performance</span><span class="p">.</span><span class="nx">mark</span><span class="p">(</span><span class="s1">&#39;anti-flicker-end&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>           <span class="nx">observer</span><span class="p">.</span><span class="nx">remove</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>           <span class="k">break</span><span class="p">;</span>
</span><span class='line'>       <span class="p">}</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;html&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="p">{</span> <span class="nx">attributes</span><span class="o">:</span> <span class="kc">true</span><span class="p">,</span> <span class="nx">attributeOldValue</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span><span class='line'>
</span></code></pre></div></figure>


<ul>
<li><strong>Adobe Target</strong></li>
</ul>


<p>Adobe Target <a href="https://experienceleague.adobe.com/docs/target/using/implement-target/client-side/at-js/manage-flicker-with-atjs.html?lang=en#managing-flicker-when-loading-at.js-asynchronously">adds a style element with an id of <code>at-body-style</code></a> and the script below detects when this element is removed</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">callback</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">mutationsList</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="c1">// Use traditional &#39;for loops&#39; for IE 11</span>
</span><span class='line'>   <span class="k">for</span><span class="p">(</span><span class="kr">const</span> <span class="nx">mutation</span> <span class="nx">of</span> <span class="nx">mutationsList</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="k">for</span><span class="p">(</span><span class="kr">const</span> <span class="nx">node</span> <span class="nx">of</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">removedNodes</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>           <span class="k">if</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s1">&#39;STYLE&#39;</span> <span class="o">&amp;&amp;</span> <span class="nx">node</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="s1">&#39;at-body-style&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>               <span class="nx">performance</span><span class="p">.</span><span class="nx">mark</span><span class="p">(</span><span class="s1">&#39;anti-flicker-end&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>               <span class="nx">observer</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>               <span class="k">break</span><span class="p">;</span>
</span><span class='line'>           <span class="p">}</span>
</span><span class='line'>       <span class="p">}</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></code></pre></div></figure>


<ul>
<li><strong>Visual Web Optimizer</strong></li>
</ul>


<p>VWO adds a <a href="https://help.vwo.com/hc/en-us/articles/900000743746-SmartCode-Checker-in-VWO">style element with an id of <code>_vis_opt_path_hides</code></a> and as with Adobe Target the script detects when this element is removed.</p>

<p>During testing I also observed VWO add other temporary styles to hide other page elements too.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">const</span> <span class="nx">callback</span> <span class="o">=</span> <span class="kd">function</span><span class="p">(</span><span class="nx">mutationsList</span><span class="p">,</span> <span class="nx">observer</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="c1">// Use traditional &#39;for loops&#39; for IE 11</span>
</span><span class='line'>   <span class="k">for</span><span class="p">(</span><span class="kr">const</span> <span class="nx">mutation</span> <span class="nx">of</span> <span class="nx">mutationsList</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="k">for</span><span class="p">(</span><span class="kr">const</span> <span class="nx">node</span> <span class="nx">of</span> <span class="nx">mutation</span><span class="p">.</span><span class="nx">removedNodes</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>           <span class="k">if</span><span class="p">(</span><span class="nx">node</span><span class="p">.</span><span class="nx">nodeName</span> <span class="o">===</span> <span class="s1">&#39;STYLE&#39;</span> <span class="o">&amp;&amp;</span> <span class="nx">node</span><span class="p">.</span><span class="nx">id</span> <span class="o">===</span> <span class="s1">&#39;_vis_opt_path_hides&#39;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>               <span class="nx">performance</span><span class="p">.</span><span class="nx">mark</span><span class="p">(</span><span class="s1">&#39;anti-flicker-end&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>               <span class="nx">observer</span><span class="p">.</span><span class="nx">disconnect</span><span class="p">();</span>
</span><span class='line'>
</span><span class='line'>               <span class="k">break</span><span class="p">;</span>
</span><span class='line'>           <span class="p">}</span>
</span><span class='line'>       <span class="p">}</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'><span class="p">};</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">observer</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">MutationObserver</span><span class="p">(</span><span class="nx">callback</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">node</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>
</span><span class='line'><span class="nx">observer</span><span class="p">.</span><span class="nx">observe</span><span class="p">(</span><span class="nx">node</span><span class="p">,</span> <span class="p">{</span> <span class="nx">childList</span><span class="o">:</span> <span class="kc">true</span> <span class="p">});</span>
</span></code></pre></div></figure>


<h2>Anti-Flicker Snippets are a Symptom of a Larger Issue</h2>

<p>Once we’ve started collecting data on how long the page is hidden for, we can experiment with reducing the timeout, or even removing the anti-flicker snippet completely.</p>

<p>After all, these are experimentation tools so we should experiment with how their implementation affects visitors’ experience and behaviour!</p>

<p>But, fundamentally, the anti-flicker snippet is a symptom of a larger issue, and that issue is that testing tools finish their execution too late.</p>

<p>Revisiting the first Gymshark test and zooming into the filmstrip at 100ms frame interval we can see the page was revealed at 3.2s.</p>

<p><img src="https://andydavies.me/blog/images/2020-11-16-the-case-against-anti-flicker-snippets/gymshark-100ms.jpeg" alt="Filmstrip showing Gymshark loading with anti-flicker snippet, 100ms frame interval" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=201113_Di0D_b5092d30ce212641d0e3b3b3b87f36b6-r%3A1-c%3A0-s%3A2&amp;thumbSize=200&amp;ival=100&amp;end=visual">Gymshark – navigating from home to category page – London, Chrome, Cable</a></p>

<p>Or to view it another way… Google Optimize finishes execution at around 3.2s… and as Largest Contentful Paint needs to happen within 2.5s to be considered ‘good’, this suggests the delays caused by experimentation tools may be testing our visitors’ patience.</p>

<p>(In Gymshark’s case there are some other factors that further contribute to the delay too)</p>

<p>Shrinking the size of the testing script will help to reduce how long it takes to fetch and execute, and I mentioned some factors that can help with this in <a href="https://andydavies.me/blog/2020/10/02/reducing-the-site-speed-impact-of-third-party-tags/">Reducing the Site-Speed Impact of Third-Party Tags</a>.</p>

<blockquote><p>The size of tags for testing services often depends on the number of experiments included, number of visitor cohorts, page URLs, sites etc. and reducing these can reduce both the download size and the time it takes to execute the script in the browser.</p>

<p>Out of data experiments or A/A tests that are being used as workarounds for CMS issues or development backlogs, and experiments for different sites (staging and live etc.) in the same tag are some of the aspects I look for first.</p></blockquote>

<p>Recently I came across an example where base64 encoded images were being included in the testing scripts, making them huge, so avoid that too!</p>

<p>But there’s also another challenge…</p>

<p>Vendors recommend anti-flicker snippets because their testing scripts are loaded asynchronously – so the browser can continue building the page while the script is being fetched – and browsers, particularly Chrome, <a href="https://addyosmani.com/blog/script-priorities/">deprioritise async scripts</a>.</p>

<p>In Chrome, this deprioritisation means the fetch (even from cache) of asynchronous scripts in the head is delayed into the second-phase of the page load.</p>

<p>An example of this can be seen in the waterfall below, where request 14 has been deprioritised because it’s async.</p>

<p><img src="https://andydavies.me/blog/images/2020-11-16-the-case-against-anti-flicker-snippets/impact-of-async.jpeg" alt="WebPageTest waterfall illustrating how async scripts are deprioritised in Chrome" /></p>

<p><a href="https://www.webpagetest.org/result/200708_D0_7410f3d8304c72e7030ef284c775f012/8/details/#waterfall_view_step1">Prioritisation Test Page – Dulles, Chrome, Cable</a></p>

<p>So not only are the scripts finishing too late they’re also starting too late!</p>

<p>To compound the delay some testing scripts are loaded via a Tag Manager, and as <a href="https://twitter.com/SimoAhava">Simo Ahava</a> <a href="https://www.simoahava.com/analytics/optimize-anti-flicker-snippet-delay-test/">demonstrated with Google Optimize this increases the delay</a> even further.</p>

<p>The alternative, at least for client-side testing, is to adopt a blocking script approach that products like Optimizely, and Maxymiser use.</p>

<p>But then we face the issue that the browser must wait for the testing script to be fetched and executed before it can continue building the page, and if the script host is inaccessible that can be a long wait.</p>

<p>We’re stuck between ‘a rock and a hard place’!</p>

<p>There is a third option… and that’s to create the page variants server-side before the HTML is even received by the browser.</p>

<p>Unfortunately too few publishing and ecommerce platforms have built-in support for experimentation so implementing this isn’t as easy as the current client-side options.</p>

<p>Some Content Delivery Networks (CDNs) already have the capability to provide test variants from their nodes, and as Edge Computing offerings from Akamai, Cloudflare and Fastly et al. mature I expect to see AB / MV Testing vendors offer ‘experimentation at the edge’ as a capability.</p>

<p>Several of the testing vendors currently support server-side testing but they still depend on sites to do the heavy lifting of implementing variants themselves.</p>

<h2>Closing Thoughts</h2>

<p>The default timeout values on anti-flicker snippets are set way too high (3 seconds plus) especially when we consider the limits that are placed on metrics like Largest Contentful Paint.</p>

<p>If you’re using anti-flicker snippets as part of your experimentation toolset, you should measure how long visitors are being shown a blank screen, with the aim of reducing the timeout values or even removing the anti-flicker snippet completely.</p>

<p>In his post on <a href="https://www.simoahava.com/analytics/simple-way-measure-a-b-test-flicker-impact/#installing-the-optimize-library-and-callback">measuring the impact of Google Optimize’s anti-flicker snippet</a>, Simo highlights some of the events that Google Optimize exposes during its lifecycle and these can be used as hooks for getting a more complete picture of when tests are running and their impact on your visitors experience.</p>

<p>If your vendor doesn’t expose timings or events for key milestones, point them at Google Optimize as a competitor and ask them to. It really is unacceptable that 3rd-parties tags don't already expose this data.</p>

<p>As ever with client-side scripts reducing their size will reduce their impact on site speed so monitor their size, and clean them up regularly.</p>

<p>Fundamentally we need to move this work out of the browser so track what your vendors are doing to support server-side experiments, particularly when it comes to integration with the CDNs Edge Compute platforms as this is going to be the most practical way for many sites to implement this.</p>

<h1>Further Reading</h1>

<p><a href="https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/">Exploring Site Speed Optimisations With WebPageTest and Cloudflare Workers</a>, Andy Davies, Sep 2020</p>

<p><a href="https://www.simoahava.com/analytics/optimize-anti-flicker-snippet-delay-test/">Google Optimize Anti-flicker Snippet Delay Test</a>, <a href="https://twitter.com/SimoAhava">Simo Ahava</a>, May 2020</p>

<p><a href="https://addyosmani.com/blog/script-priorities/">JavaScript Loading Priorities in Chrome</a>, <a href="https://twitter.com/addyosmani">Addy Osmani</a>, Feb 2019</p>

<p><a href="https://andydavies.me/blog/2020/10/02/reducing-the-site-speed-impact-of-third-party-tags/">Reducing the Site-Speed Impact of Third-Party Tags</a>, Andy Davies, Oct 2020</p>

<p><a href="https://www.simoahava.com/analytics/simple-way-measure-a-b-test-flicker-impact/">Simple Way To Measure A/B Test Flicker Impact</a>, <a href="https://twitter.com/SimoAhava">Simo Ahava</a>, May 2020</p>

<p><a href="https://github.com/andydavies/tag-timing-snippets#timing-snippets-for-3rd-party-tags">Timing Snippets for 3rd-Party Tags</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Strengthening the Link Between Site Speed and Business Outcomes]]></title>
    <link href="https://andydavies.me/blog/2020/10/12/strengthening-the-link-between-site-speed-and-business-outcomes/"/>
    <updated>2020-10-12T15:00:00+01:00</updated>
    <id>https://andydavies.me/blog/2020/10/12/strengthening-the-link-between-site-speed-and-business-outcomes</id>
    <content type="html"><![CDATA[<p>Improving site speed comes with a cost, it might be the opportunity cost of switching from developing features to working on performance improvements, the cost of buying or deploying performance tools, engaging consultants or the direct cost of work itself — especially when a site relies on an external development partner.</p>

<p>As performance advocates we’d champion the idea that improving performance adds value, sometimes the value is tangible – increased revenue for a retailer, or increased page views for a publisher – other times it may be less tangible – improvements in brand perception, or visitor satisfaction for example.</p>

<p>But as important and valuable we might believe speed to be, we need to persuade other stakeholders to prioritise and invest in performance, and for that we need to be able to demonstrate the benefit of speed improvements versus their cost, or at least how slow speeds have a detrimental effect on the factors people care about – visitor behaviour, revenue etc.</p>

<p>“Isn’t that case already made” you might ask?</p>

<!--more-->


<p>What about all the case studies that <a href="https://twitter.com/tameverts">Tammy Everts</a> and <a href="https://twitter.com/tkadlec">Tim Kadlec</a> curate on <a href="https://wpostats.com/">WPOStats</a>?</p>

<p>Case studies are a great source of inspiration, but it’s not unusual to hear objections of "our proposition is different, our site is different, or our visitors are different" and often there’s truth to these objections.</p>

<p>The chart below shows how conversion rates vary by average load time across a session for three UK retailers.</p>

<p>(Ideally I’d like a chart that doesn’t rely on averages of page load times but sometimes you’ve got to work with the data you have)</p>

<p><img src="https://andydavies.me/blog/images/2020-10-12-strengthening-the-link-between-site-speed-and-business-outcomes/speed-vs-conversion.png" alt="Graph showing conversion rates decreasing for three retailers as site gets slower" /></p>

<p>Although the trend for all three retailers is similar – visitors with faster experience are more likely to convert – the rate of change is different for each retailer, and so the value of improved performance will also vary.</p>

<p>As examples of how this value varies, one retailer I worked with saw a 5% increase in revenue from a 150ms improvement in iOS start render times, another increased Android revenue by 26% when they cut 4 seconds from Android load times and a third saw conversion rates improve for visitors using slower devices when they stopped lazy-loading above the fold images.</p>

<p>Other clients have had visitors that seemed very tolerant of slower experiences calling into the question the principle that being faster makes a difference for all sites, and what value investing in performance would deliver.</p>

<p>If case studies aren’t persuasive enough or maybe not even applicable for some sites, how do we help to establish the value of speed?</p>

<h1>Identifying the Impact of Performance Improvements</h1>

<p>Determining what impact a performance improvement had on business metrics can be challenging as the data is often split across different products, and a change in behaviour may only be visible for a subset of visitors, perhaps those on slower devices, or with less reliable connections.</p>

<p>This difficulty can lead us to depend on what <a href="https://mobile.twitter.com/sophiebits">Sophie Alpert</a> describes as ‘<a href="https://sophiebits.com/2018/12/04/metrics-by-proxy.html">Proxy Metrics</a>’ such as file size, number of requests, or scores from tools like Lighthouse – if our page size or number of requests decreases, or our Lighthouse score increases then we’ve probably made a positive difference.</p>

<p>Haven’t we?</p>

<p>Relying on proxy metrics brings a danger that we celebrate improvements without knowing whether we actually made a difference to our business outcomes, and the risk that changes in business metrics are credited to other sources, or worse still, we remove something that actually delivers more value than it costs.</p>

<p>In Designing for Performance, <a href="https://twitter.com/lara_hogan">Lara Hogan</a> advocates the need for organisational cultures that value site speed at the highest levels rather than relying on <a href="http://designingforperformance.com/changing-culture/#performance-cops-and-janitors">Performance Cops and Janitors</a>.</p>

<p>Linking site speed to the metrics that matter to senior stakeholders is a key part of that but as a performance industry / community I think we probably rely on an ad-hoc approach to making that link.</p>

<h1>Relationship Between Site Speed and Business Outcomes</h1>

<p>In June 2017, I had a bit of a realisation…</p>

<p>At the time I was employed by a web performance monitoring company, and one of our ongoing debates was about what data our Real User Monitoring (RUM) product should collect.</p>

<p><a href="https://twitter.com/simonhearne">Simon Hearne</a> and I worked with clients to identify and implement performance improvements, and then post implementation we were trying to quantify the value of those improvements.</p>

<p>As we identified gaps between the data our RUM product collected and the data we wanted, we would ask for new data points to be added but kept running into resistance from our engineering team who were often skeptical about the value of the new data.</p>

<p>We were in a ‘chicken and egg’ situation – our engineering team didn’t want to collect the data unless we could prove it had value, but Simon and I couldn’t establish whether it had value until we started collecting it.</p>

<p>We were missing a framework that might help us make these decisions, one that could help everyone understand what data we could collect, and what questions that data might help answer.</p>

<p>At some point while I was thinking about our challenge, I created a deck with a slide similar to this one:</p>

<p><img src="https://andydavies.me/blog/images/2020-10-12-strengthening-the-link-between-site-speed-and-business-outcomes/andydavies-web-performance-mental-model.png" alt="My mental model of web performance – context influences visitor experience, experience influences visitor behaviour, and behaviour influences business success" /></p>

<p>Concepts such as “how we build pages influence our visitors’ experience”, “the experience visitors get influences how they behave”, and “how visitors behave influences our sites’ success”  are commonplace in web performance.</p>

<p>After all, these are concepts that underpin many of the case studies on WPOStats.</p>

<p>But… I’m not sure I’d ever seen them written down as an end-to-end model before.</p>

<p>And writing them down on one slide helped me realise what my mental model of web performance actually looked like.</p>

<p>It’s also become a lens through which I view one of Tammy’s questions in <a href="https://www.amazon.com/Time-Money-Business-Value-Performance/dp/1491928743">Time is Money</a> – “How Can We Better Understand the Intersection Between Performance, User Experience, and Business Metrics?”</p>

<p>Other slides in the deck explained those categories, and what metrics might be included within them:</p>

<ul>
<li><strong>Context</strong></li>
</ul>


<p>What a page is made from, how those resources are delivered, the device it’s viewed on and the networks it was transmitted over are all fundamental to how fast and smooth a visitor’s experience is.</p>

<p>Some of those factors – browser, device, and network etc. – have a crucial impact on how long our scripts take to execute, how soon our resources load etc., but as they are all outside our control, they’re really constraints we need to design for.</p>

<p>Other factors such as the resources we use, whether they’re optimised, how they’re delivered and composed into a page also have a huge effect on a visitor’s experience and this second set of factors is largely within our control, after all these are the things we change when we’re improving site speed.</p>

<ul>
<li><strong>Visitor Experience</strong></li>
</ul>


<p>From a performance perspective visitor experience is synonymous with speed – when did a page start to load, when did content start to become visible, how long did key images take to appear, when could someone interact with the page, were their interactions responsive and smooth etc.</p>

<p>We’ve plenty of metrics to choose from, some frustrating gaps and the ability to synthesise our own via APIs such as User Timing too.</p>

<p>There are other factors we might want to consider under the experience banner too – are images appropriately sized, does the product image fit within the viewport or does the visitor need to scroll to see it, what script errors occur etc.</p>

<ul>
<li><strong>Visitor Behaviour</strong></li>
</ul>


<p>How visitors behave provides signals as to whether a site is delivering a good or bad experience.</p>

<p>At a macro level, a visitor buying the contents of their shopping basket is seen as a positive signal, whereas someone navigating away, or closing the tab before the page has even loaded would be a negative one.</p>

<p>Then at the micro level there are behaviours such as whether a visitor reloads the page, rotates their device, or perhaps zooms in, how long they wait before interacting, how much they interact etc.</p>

<p>There are also other, non-performance factors that influence visitors behaviour – their intent, the marketing mix, social demographics factors – that we may or may not want to include when we’re considering behaviour.</p>

<ul>
<li><strong>Business Outcomes</strong></li>
</ul>


<p>Individual user behaviour can be aggregated into metrics we use to run our businesses – conversion rates, bounce rates, average order values, customer lifetime value, cost of acquisition etc. – and ultimately revenue, costs and profit.</p>

<h1>Limitations of the Model</h1>

<p>“All models are wrong; some models are useful”, George E. P. Box</p>

<p>Every model has limitations, and while site speed might be our focus, it isn’t the only driver of a site's success.</p>

<ul>
<li>A visitor’s experience is much more than just how fast it is – content, visual design, usability, accessibility, privacy and more all contribute to the experience.</li>
<li>The type of visitor, their intent, the marketing mix (product, price, promotion etc.) and more influence how people behave.</li>
<li>Factors such as cost of acquisition, product margin, returns etc. affect the success of our business</li>
</ul>


<p>Our challenge is identifying what role site speed played in influencing the outcomes.</p>

<p>I’ve also got outstanding questions about how design techniques that improve perception of performance, such as those <a href="https://mobile.twitter.com/WalterStephanie">Stephanie Walter</a> covers in <a href="https://speakerdeck.com/stephaniewalter/mind-over-matter-optimize-performance-without-code">Mind over Matter: Optimize Performance Without Code</a> fit into the model too.</p>

<p>There are also questions about whether it’s possible, desirable, and even acceptable to gather some of the data points we might want.</p>

<p>But…</p>

<p>Even with its limitations, I still find the model very useful as a tool to help communication and build understanding.</p>

<p>It helps facilitate discussions around the metrics we’re capturing (or could start capturing) and what those metrics actually represent.</p>

<p>It’s handy when making changes as we can discuss what effect we expect to see from a change – which metrics should move, in what direction, and how that might affect visitor behaviour.</p>

<p>I’ve also had some success tracking changes in how pages were constructed all the way to changes in business outcomes, particularly revenue but often the data on speed, visitor behaviour and business performance is stored in separate products and the gap between them can make analysis hard.</p>

<h1>Bridging the Gap</h1>

<p>If our thesis is that the speed of a visitor’s experience influences their behaviour then we need tools that allow us to capture, and analyse data on both visitors’ experience and their behaviour in the same place.</p>

<p>This is where the gaps in analytics and performance tools start to show:</p>

<ul>
<li>Analytics products tend to focus on visitor acquisition and behaviour but generally don’t capture speed data. Those that do collect speed data, only support limited metrics, have low sample rates and expose the data as averages.</li>
<li>Real-User Monitoring (RUM) products tend to focus on speed, some capture a wider range of metrics and a few capture some data on visitor behaviour such as conversion, bounce and session length.</li>
<li>Some Digital Experience Analytics products (Session Replay, Form Analytics etc) collect speed data alongside visitor behaviour but only one product exposes the cost of speed in their product.</li>
<li>Performance Analysis tools such as WebPageTest and Lighthouse give us a deeper view into page performance including construction and delivery but can’t capture data on either real-visitors’ experience or their behaviour.</li>
</ul>


<p>There are limits to what data it’s possible, practical or acceptable to collect and store but ideally I’d also like data on how a page is constructed and delivered, along with some business data to be stored in the same place too.</p>

<p>Although it’s easy to think of all RUM products as comparable, some are more capable than others and a few RUM products have tried to close some of the gaps but there is still much to be done.</p>

<p>I track the features and capabilities of over thirty RUM products and too many of them focus on just monitoring how fast a site is, often only using a few aggregated metrics (DOMContentLoaded / Load).</p>

<p>Other products support perceived performance metrics such as paint and custom timings, and some include data on page composition too – number of DOM nodes, scripts etc.</p>

<p>Very few products capture data on visitor behaviour, some plot conversion or bounce rates  alongside speed metrics, others build predictive models showing the value speed improvements could bring – reduced bounce rates, higher conversion rates, increased session lengths etc.</p>

<p>For filtering and segmentation products tend to focus on contextual dimensions such as browser make and version, operating system, ISP, device type, country etc., rather than behavioural dimensions such as whether a visitor converts or bounces etc.</p>

<p>This focus on technical dimensions and metrics rather than visitor behaviour means it can often be hard to answer the questions I often want to ask.</p>

<p>Questions such as, how does performance differ between visitors who convert, and those that don’t?</p>

<p>Or which visitors have slow experiences and why?</p>

<p>Or what areas of a site should we focus first on when improving performance?</p>

<p>And much more…</p>

<p>Ultimately I want to be able to segment based on a variety of factors including how visitors behave, the experience they have and how the pages they view are constructed.</p>

<p>I want to be able to highlight not just how speed impacts visitors’ behaviour and what it costs, but why some visitors have slower experiences and perhaps even what changes can be made to improve them.</p>

<p>And once we’ve made improvements I want to be able to link changes in behaviour, and gains in business metrics back to those performance improvements.</p>

<h1>Closing Thoughts</h1>

<p>It’s easy to get excited about new techniques for measuring and improving site speed but this focus on the technical side of performance can lead us to think of speed as a technical issue, rather than a business issue with technical roots.</p>

<p>As <a href="https://mobile.twitter.com/csswizardry">Harry Roberts</a> said in his 2019 Performance.Now talk – “<a href="https://speakerdeck.com/csswizardry/from-milliseconds-to-millions-a-look-at-the-numbers-powering-web-performance?slide=9">Our job isn’t to make the fastest site possible, it’s to help make the most effective site possible</a>”</p>

<p>But to help make more effective sites we need tools that make it easier to understand how speed is influencing visitors' behaviour, easier to identify key areas where performance needs to improve and perhaps even recommend actions they can take to improve it.</p>

<p>We also need models that help link the way sites are constructed and delivered to the business outcomes, so that we understand how the changes affect visitors and allow for features that might make pages a bit larger, a little slower but improve engagement and deliver higher revenues.</p>

<p>My mental model is still a work in progress and I’m not wedded to it, so feel free to suggest alternatives, poke at its gaps or better still, suggest ways we can fill the gaps.</p>

<p>Ultimately, there are too many RUM products that just measure how fast or slow a visitors experience is, and are unable to link that experience to a site's success.</p>

<p>If we want to make the web faster we've got to close that gap.</p>

<h1>Thanks</h1>

<p>I started this post a couple of years ago while I was taking some time off after helping sell NCC Group's web performance business.</p>

<p>Since then I've talked to quite a few people about the ideas in it and I'm grateful to them for sharing their challenges and experience or giving me feedback.</p>

<p>Finally, <a href="https://mobile.twitter.com/colinbendell">Colin</a>, <a href="https://mobile.twitter.com/davepeiris">Dave</a>, <a href="https://twitter.com/malchata">Jeremy</a>, <a href="https://twitter.com/simonhearne">Simon</a> and <a href="https://twitter.com/tkadlec">Tim</a> were kind enough to my read my draft post, spot my typos and poke at its weak points.</p>

<h1>Further Reading</h1>

<p><a href="http://designingforperformance.com">Designing for Performance</a>, <a href="https://twitter.com/lara_hogan">Lara Hogan</a></p>

<p><a href="https://speakerdeck.com/csswizardry/from-milliseconds-to-millions-a-look-at-the-numbers-powering-web-performance">From Milliseconds to Millions: A Look at the Numbers Powering Web Performance</a>, <a href="https://mobile.twitter.com/csswizardry">Harry Roberts</a></p>

<p><a href="https://sophiebits.com/2018/12/04/metrics-by-proxy.html">Metrics by Proxy</a>, <a href="https://mobile.twitter.com/sophiebits">Sophie Alpert</a></p>

<p><a href="https://speakerdeck.com/stephaniewalter/mind-over-matter-optimize-performance-without-code?slide=47">Mind over Matter: Optimize Performance Without Code</a>, <a href="https://mobile.twitter.com/WalterStephanie">Stephanie Walter</a></p>

<p><a href="https://tammyeverts.wordpress.com/2016/06/06/woohoo-my-book-is-out/">Time is Money</a><a href="https://mobile.twitter.com/tameverts">Tammy Everts</a></p>

<p><a href="https://wpostats.com">WPOStats</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Reducing the Site-Speed Impact of Third-Party Tags]]></title>
    <link href="https://andydavies.me/blog/2020/10/02/reducing-the-site-speed-impact-of-third-party-tags/"/>
    <updated>2020-10-02T07:25:09+01:00</updated>
    <id>https://andydavies.me/blog/2020/10/02/reducing-the-site-speed-impact-of-third-party-tags</id>
    <content type="html"><![CDATA[<p>At BrightonSEO I talked about Third-Party tags, their impact on site-speed, and some of the approaches I encourage my clients to use to reduce this impact.</p>

<p>As it’s hard to fit everything into a twenty minute talk, this post expands on the talk and includes some of the points I didn’t have time to cover.</p>

<p>From Analytics to Advertising, Reviews to Recommendations, and more, it’s common for sites to rely on Third-Party tags to provide some of their key features.</p>

<p>But there’s also a tension between the value tags bring and the privacy, security and speed costs they impose.</p>

<!--more-->


<p>I’m focusing on speed but if you want to learn more about the other aspects, <a href="https://twitter.com/LauraKalbag">Laura Kalbag</a> and <a href="https://twitter.com/WolfieChristl">Wolfe Christl</a> often cover the privacy concerns, and <a href="https://mobile.twitter.com/Scott_Helme">Scott Helme</a> sometimes covers the security issues.</p>

<h1>What does a Tag Cost?</h1>

<p>When I’m helping clients to improve the speed of their sites one of my first steps is <a href="https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags/">to test the site with and without tags using WebPageTest</a> (you can also use this approach to test the impact of individual tags).</p>

<p>This gives me an indication of what gains might be made by optimising the implementation of tags.</p>

<p>Using OPI, the nail varnish company as an example… when third-party tags are removed their pages get faster – on product pages the key image appears about a second sooner, and other content such as the heading text, and brand logo also appear sooner.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/opi-no-3rd-parties.png" alt="Filmstrip from WebPageTest showing OPI with third-party tags block and as loaded normally" />
<a href="https://www.webpagetest.org/video/compare.php?tests=190919_JS_cb750d310d78149452c232bc33af339a,190919_MD_47ee80c030c66bbf2ae0ee36203906dd">OPI with 3rd-Party Tags blocked (top), and as loaded normally (bottom)</a></p>

<h1>How Tags Impact Site-Speed</h1>

<p>There are two ways tags impact site-speed – they compete for network bandwidth and processing time on visitors’ devices, and depending on how they’re implemented they can delay HTML parsing</p>

<p>This partial waterfall from WebPageTest illustrates the costs for fetching and executing the script.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/waterfall-3rd-party.png" alt="WebPageTest waterfall showing phases of a third-party tag loading" /></p>

<p>First there’s a 300ms delay while the browser connects to the third-party (cyan, orange and magenta segments), then the download of the script takes a further 1,100ms (beige segment), and then the script execution takes a further ~200ms (pink segments on the right)</p>

<p>The dark sections of the beige line are where data is being received, and the light section where there’s no data – these extended light sections are an indication that this tag is competing for the network or the server it’s hosted on is slow.</p>

<p>If the script is cacheable then the cost of the network connection and download should only affect the first time it’s loaded in a session but the cost of the execution time will apply to all pages that include it.</p>

<p>Tags can also trigger further downloads, sometimes these may be calls to an API, other times they may be adding extra scripts, stylesheets etc to the page.</p>

<p>Expanding the above example we can see it makes many further calls (the chart only shows a few) to an API (grey bars). These API calls are likely to be made on every page, and again the light areas in the grey bars indicate either network contention or a slow server.So</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/waterfall-extra-requests.png" alt="WebPageTest waterfall showing further requests made by third-party tag" /></p>

<p>Tags are generally implemented as scripts, and a second aspect to consider is what effect they have on blocking HTML parsing.</p>

<p>By default script elements (such as the one below) stop the browser from parsing HTML until the script has been fetched and has finished running.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;https://cdn.example.com/third-party-tag.js&quot;</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>We want to avoid implementations that use blocking tags as much as possible due to the delay they cause, which if the third-party isn’t reachable for some reason can be over 30 seconds.</p>

<p>There are a few ways to make scripts non-blocking.</p>

<p>Adding the <code>async</code> attribute tells the browser to not to wait while the script is fetched but will block the browser when the script executes.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;https://cdn.example.com/third-party-tag.js&quot;</span> <span class="na">async</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>Non-blocking scripts can also be added via a small inline script snippet that inserts another script into the page. This example is for Google Tag Manager, but it’s a very common pattern.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'>    <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">w</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">s</span><span class="p">,</span> <span class="nx">l</span><span class="p">,</span> <span class="nx">i</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="nx">w</span><span class="p">[</span><span class="nx">l</span><span class="p">]</span> <span class="o">=</span> <span class="nx">w</span><span class="p">[</span><span class="nx">l</span><span class="p">]</span> <span class="o">||</span> <span class="p">[];</span>
</span><span class='line'>        <span class="nx">w</span><span class="p">[</span><span class="nx">l</span><span class="p">].</span><span class="nx">push</span><span class="p">({</span>
</span><span class='line'>            <span class="s1">&#39;gtm.start&#39;</span><span class="o">:</span> <span class="k">new</span> <span class="nb">Date</span><span class="p">().</span><span class="nx">getTime</span><span class="p">(),</span>
</span><span class='line'>            <span class="nx">event</span><span class="o">:</span> <span class="s1">&#39;gtm.js&#39;</span>
</span><span class='line'>        <span class="p">});</span>
</span><span class='line'>        <span class="kd">var</span> <span class="nx">f</span> <span class="o">=</span> <span class="nx">d</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="nx">s</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span>
</span><span class='line'>            <span class="nx">j</span> <span class="o">=</span> <span class="nx">d</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="nx">s</span><span class="p">),</span>
</span><span class='line'>            <span class="nx">dl</span> <span class="o">=</span> <span class="nx">l</span> <span class="o">!=</span> <span class="s1">&#39;dataLayer&#39;</span> <span class="o">?</span> <span class="s1">&#39;&amp;l=&#39;</span> <span class="o">+</span> <span class="nx">l</span> <span class="o">:</span> <span class="s1">&#39;&#39;</span><span class="p">;</span>
</span><span class='line'>        <span class="nx">j</span><span class="p">.</span><span class="nx">async</span> <span class="o">=</span> <span class="kc">true</span><span class="p">;</span>
</span><span class='line'>        <span class="nx">j</span><span class="p">.</span><span class="nx">src</span> <span class="o">=</span> <span class="s1">&#39;https://www.googletagmanager.com/gtm.js?id=&#39;</span> <span class="o">+</span> <span class="nx">i</span> <span class="o">+</span> <span class="nx">dl</span><span class="p">;</span>
</span><span class='line'>        <span class="nx">f</span><span class="p">.</span><span class="nx">parentNode</span><span class="p">.</span><span class="nx">insertBefore</span><span class="p">(</span><span class="nx">j</span><span class="p">,</span> <span class="nx">f</span><span class="p">);</span>
</span><span class='line'>    <span class="p">})(</span><span class="nb">window</span><span class="p">,</span> <span class="nb">document</span><span class="p">,</span> <span class="s1">&#39;script&#39;</span><span class="p">,</span> <span class="s1">&#39;dataLayer&#39;</span><span class="p">,</span> <span class="s1">&#39;GTM-XXXX&#39;</span><span class="p">);</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>Another form of non-blocking scripts use the <code>defer</code> attribute to instruct the browser that it doesn’t need to wait for the script to download but that it should only execute the script when all the HTML has been parsed.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&quot;https://cdn.example.com/third-party-tag.js&quot;</span> <span class="na">defer</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>Avoid document.write as it stalls the browser – the browser can’t discover the external script until <code>document.write</code> executes, and then the browser must wait for the script to be downloaded and run before it can carry on parsing the HTML.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="err">‘</span><span class="o">&lt;</span><span class="nx">script</span> <span class="nx">src</span><span class="o">=</span><span class="s2">&quot;https://cdn.example.com/third-party-tag.js&quot;</span><span class="o">&gt;&lt;</span><span class="err">/script&gt;’);</span>
</span></code></pre></div></figure>


<p>Tag Managers generally inject tags using a non-blocking approach but occasionally I come across one that still uses <code>document.write</code>.</p>

<h1>Reducing the Impact of Tags</h1>

<p>Our goal is to minimise the impact tags have on visitors’ experience, while still retaining the value those tags provide.</p>

<h2>Catalogue the Tags that are Currently Deployed</h2>

<p>There are a few ways to catalogue the tags on a page… from inspecting the contents of a tag manager container, through free tools like WebPageTest to commercial tools such as Ghostery and ObservePoint.</p>

<p>One of my favourite places to start is <a href="https://twitter.com/simonhearne">Simon Hearne’s</a> <a href="https://requestmap.herokuapp.com">Request Map</a> – it’s built on top of WebPageTest and visualises the third-parties on a page along with details on their size, type and what triggered their load.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/request-map-opi.png" alt="Request Map for OPI Product Page" />
<a href="https://requestmap.herokuapp.com/render/200930_Di5Q_2e4812771ed171977d8d867739624b08/?legend=0&amp;dark=0#">Request Map for OPI Product Page</a></p>

<p>I often create these for different types of pages across a site and also combine the WebPageTest data to build a cross site view.</p>

<h2>Consolidate by Identifying Tags that can be Removed</h2>

<p>Once I’ve got an idea of what’s on the page I start analysing and asking questions.</p>

<p>Initially, I aim to identify services where the subscription has lapsed, no-one is using it, or where there’s more than one product providing similar features.</p>

<p>When a subscription expires, some providers helpfully return an error e.g. HTTP 403, others serve an empty script but many carry on serving their full script, so sometimes it can take a bit of digging to identify them.</p>

<p>A few years ago (pre-GDPR) one of the European airlines audited their tags and found that subscriptions had expired for around a third of the tags on their pages, and they couldn’t find anyone who used some of the others.</p>

<p>So immediately with a bit of tidying up they were able to reduce the impact.</p>

<p>Occasionally I come across tags from different vendors that provide similar features, for example session replay services like Mouseflow and HotJar, or analytics services such as Google and Adobe.</p>

<p>Reducing this duplication and consolidating on a single choice is better than having multiple solutions (for both cost and visitor experience) but sometimes there can be good reasons to keep more than one analytics service but try to deduplicate where possible,</p>

<p>Another question to ask at this point is whether the tag is actually needed on the current page, for example I’ve seen the Google Maps script included in every page across a site when there was only a map on one or two pages.</p>

<h2>Reduce the Cost of Remaining Tags</h2>

<p>When the unused tags have been removed and the duplicates consolidated I start exploring ways to reduce the impact of the remaining tags.</p>

<p>Initially I’ll identify which tags might be replaced with smaller, faster alternatives, and then how we can reduce the cost of the remaining ones.</p>

<h3>Lighter Weight Alternatives</h3>

<p>Typical wins are replacing the standard embedded YouTube player with a <a href="https://css-tricks.com/lazy-load-embedded-youtube-videos/">version that delays loading the player script</a> until a visitor interacts with the video.</p>

<p>Or replacing social sharing buttons with <a href="https://tosbourn.com/replacing-social-media-share-buttons-with-non-javascript-counterparts/">lightweight JavaScript free versions</a> or even removing them entirely and just relying on visitors using the sharing features built into their browser.</p>

<p>Switching providers in another option worth considering – one of my clients switched their chat provider from ZenDesk to Olark as it was half the size!</p>

<h3>Experimentation Frameworks and Tag Managers</h3>

<p>The impact of AB / MV Testing services and Tag Managers can often be reduced by simplifying their work.</p>

<p>The size of tags for testing services often depends on the number of experiments included, number of visitor cohorts, page URLs, sites etc. and reducing these can reduce both the download size and the time it takes to execute the script in the browser.</p>

<p>Out of data experiments or A/A tests that are being used as workarounds for CMS issues or development backlogs, and experiments for different sites (staging and live etc.) in the same tag are some of the aspects I look for first.</p>

<p>Similarly the more tags and rules there are in a tag container the larger it’s going to be, and  so the greater its impact on the visitors experience.</p>

<p>A client I worked with last year was using a single container for each geographical region and it contained the tags for every brand site in that region. All these tags were being shipped to every visitor even when most of them wouldn’t be used and the size of the container had a noticeable impact on visitors' experience.</p>

<p>I encouraged the client to switch to one container per brand to reduce its size and improve visitor experience. The challenge for them was that this increased the number of tag containers they needed to manage – there’s always a complexity tradeoff somewhere!</p>

<h3>Tracking Pixel and Server-Side Tag Management</h3>

<p><a href="https://twitter.com/tunetheweb">Barry Pollard</a>’s approach of replacing some tags with <a href="https://www.tunetheweb.com/blog/adding-controls-to-google-tag-manager/#pixels">their fallback tracking pixel</a> instead of using the full tag, is an interesting idea that I’ve not tried with any clients yet.</p>

<p>Server-side tag management helps in a similar way, as the tag manager collates the events and distributes them to other services without including the scripts from those services directly in the page.</p>

<h3>Libraries from Public CDNs</h3>

<p>And although they’re not tags I also examine what 3rd-party resources – scripts, stylesheets and fonts such as jQuery, FontAwesome etc. – are being loaded from public CDNs such as jsdelivr or ajax.googleapis.com etc., with the aim of self-hosting them.</p>

<p>Self-hosting allows for more efficient use of network connections, especially if a site is already using a CDN and HTTP/2.</p>

<h2>Choreograph when Tags Load</h2>

<p>In the performance world we often refer to page load as a journey with milestones along the way… is anything happening, when does a page become useful or usable?</p>

<p>Third-party tags should fit into that journey…</p>

<p>Which ones must be loaded before the page can start displaying content to a visitor, which ones can be delayed until later, and what about the ‘bit in the middle’?</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/choreography.jpeg" alt="Illustration of milestones during page load" /></p>

<p>The point at which a tag needs to be loaded depends on what features it provides and when that feature is required.</p>

<p>But too often I see tag managers injecting tags as soon as possible.</p>

<p>Generally I try to delay the load of tags for as long as practical but it depends on the tag’s purpose – is it just collecting data for business use, does it affect or provide content and features that the visitor sees and when does the visitor need to see them?</p>

<h3>Before Useful</h3>

<p>Tags that are loaded early in the page have an outsized impact on visitor experience, often they’re included in the <code>&lt;head&gt;</code>, and browsers tend to prioritise resources included there.</p>

<p>The key question to answer is “does this tag need to be loaded before the visitor can see content, and what’s the impact if it’s loaded later?”</p>

<p>AB / MV Testing, Tag Managers, Personalisation tools and Analytics are some of the tags that are often loaded in this phase – I tend to leave them embedded in the page, but aim to have as few as possible and slim them down to minimise their impact.</p>

<p>Testing / experimentation tools often have a large impact in this phase.</p>

<p>They tend to take one of two approaches – block the page from rendering until the tag has loaded, or load non-blocking and hide the page using an anti-flicker snippet – and both of these have challenges.</p>

<p>Choosing a blocking approach stops the parsing of HTML until the script has downloaded and been executed.</p>

<p>With the non-blocking approach, anti-flicker scripts hide the page until either the testing framework has executed or a timeout value is exceeded (3 seconds in the case of this example for Adobe Target):</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script&gt;</span>
</span><span class='line'>    <span class="c1">//prehiding snippet for Adobe Target with asynchronous Launch deployment </span>
</span><span class='line'>    <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">g</span><span class="p">,</span> <span class="nx">b</span><span class="p">,</span> <span class="nx">d</span><span class="p">,</span> <span class="nx">f</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>        <span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">a</span><span class="p">,</span> <span class="nx">c</span><span class="p">,</span> <span class="nx">d</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>            <span class="k">if</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>                <span class="kd">var</span> <span class="nx">e</span> <span class="o">=</span> <span class="nx">b</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s2">&quot;style&quot;</span><span class="p">);</span>
</span><span class='line'>                <span class="nx">e</span><span class="p">.</span><span class="nx">id</span> <span class="o">=</span> <span class="nx">c</span><span class="p">;</span>
</span><span class='line'>                <span class="nx">e</span><span class="p">.</span><span class="nx">innerHTML</span> <span class="o">=</span> <span class="nx">d</span><span class="p">;</span><span class="nx">a</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">e</span><span class="p">)</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>        <span class="p">})(</span><span class="nx">b</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s2">&quot;head&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">],</span> <span class="s2">&quot;at-body-style&quot;</span><span class="p">,</span> <span class="nx">d</span><span class="p">);</span>
</span><span class='line'>        <span class="nx">setTimeout</span><span class="p">(</span><span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
</span><span class='line'>            <span class="kd">var</span> <span class="nx">a</span><span class="o">=</span><span class="nx">b</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s2">&quot;head&quot;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>            <span class="k">if</span><span class="p">(</span><span class="nx">a</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>                <span class="kd">var</span> <span class="nx">c</span> <span class="o">=</span> <span class="nx">b</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="s2">&quot;at-body-style&quot;</span><span class="p">);</span>
</span><span class='line'>                <span class="nx">c</span> <span class="o">&amp;&amp;</span> <span class="nx">a</span><span class="p">.</span><span class="nx">removeChild</span><span class="p">(</span><span class="nx">c</span><span class="p">)</span>
</span><span class='line'>            <span class="p">}</span>
</span><span class='line'>        <span class="p">},</span> <span class="nx">f</span><span class="p">)</span>
</span><span class='line'>    <span class="p">})(</span><span class="nb">window</span><span class="p">,</span> <span class="nb">document</span><span class="p">,</span> <span class="s2">&quot;body {opacity: 0 !important}&quot;</span><span class="p">,</span> <span class="mi">3</span><span class="nx">E3</span><span class="p">);</span>
</span><span class='line'><span class="nt">&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>I’m not a fan of snippets that hide the page – visitors are familiar with pages loading incrementally, and hiding the page interferes with their perception of speed.</p>

<p>Some will argue anti-flicker snippets avoid a poor visitor experience, and that if visitors see experiments making significant changes to the page it may alter their behaviour.</p>

<p>In the example above, even if the experimentation framework hasn’t finished its work the page is going to be revealed after 3 seconds, so visitors having slow experiences will potentially still see changes as they’re applied anyway.</p>

<p>I’d advise experimenting with whether you really need an anti-flicker snippet, reducing the timeout values, and also measuring the delay the anti-flicker snippet introduces (<a href="https://www.simoahava.com/">Simo Ahava</a> has a post on <a href="https://www.simoahava.com/analytics/optimize-anti-flicker-snippet-delay-test/">how to do measure it for Google Optimize</a>)</p>

<p>There are methods to reduce the impact of blocking testing frameworks too.</p>

<p>Casper removed the network connection time by self-hosted their Optimizely script and <a href="https://medium.com/caspertechteam/we-shaved-1-7-seconds-off-casper-com-by-self-hosting-optimizely-2704bcbff8ec">reduced the delay before content appeared by 1.7s</a></p>

<p>As an alternative to self-hosting, Optimizely <a href="https://help.optimizely.com/Set_Up_Optimizely/Content_Delivery_Networks_(CDNs%29_and_Optimizely_X">provides instructions on how to proxy their tag through your own CDN</a>, but <a href="https://calendar.perfplanet.com/2019/self-hosting-third-party-resources-the-good-the-bad-and-the-ugly/">proxying can bring security concerns</a>, and you will need additional CDN configuration such as stripping the cookies you don’t want to forward to a third-party.</p>

<p>Testing frameworks are big bundles of JavaScript that need to be downloaded and executed so simplifying them will reduce their impact.</p>

<p>But ideally the work of large or blocking tags should be completed before the page reaches the visitor so explore how you can implement experiments server-side or CDN-side so they execute before the page is delivered.</p>

<h4>Analytics / Attribution Fallbacks</h4>

<p>One last thing to watch out for is fallbacks for visitors who have JavaScript disabled – many attribution tags use an image or iframe fallback wrapped in a <code>noscript</code> element.</p>

<p>The fallback for Bing Ads for attribution is one example:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;noscript&gt;</span>
</span><span class='line'>    <span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;https://bat.bing.com/action/0?ti=xxxxxxx&amp;Ver=2&quot;</span> <span class="na">height=</span><span class="s">&quot;0&quot;</span> <span class="na">width=</span><span class="s">&quot;0&quot;</span> <span class="na">style=</span><span class="s">&quot;display:none; visibility: hidden;&quot;</span><span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/noscript&gt;</span>
</span></code></pre></div></figure>


<p>These fallbacks should be placed in the <code>body</code> of the page as <code>img</code> and <code>iframe</code> aren’t valid elements in the head.</p>

<h3>After Usable</h3>

<p>Some tags provide features that aren’t much use until a visitor can interact with the page – chat and feedback widgets, session replay services etc. – so I tend to delay their load.</p>

<p>Often these tags are loaded much earlier than needed and their download competes for the network often delaying far more important resources such as product images.</p>

<p>I’ll delay the addition of these types of tags using the Window Loaded Adobe Launch event / GTM trigger. Delaying them reduces competition for the network and allows the more important resources to complete sooner.</p>

<h3>Between Useful and Usable</h3>

<p>It’s often clear which tags need to be loaded early and which can be delayed but there’s a grey area between the page starting to render and the page becoming usable.</p>

<p>And as yet, I’ve not developed a clear approach on how to handle the tags that fit into this section.</p>

<p>Often I’m guided by whether the tag provides content the visitor sees, for example I’ll include the tag for a reviews service just before the point in the page where the reviews appear. Inserting it earlier than that may delay more important content, but adding it later can result in the page reflowing once that tag has loaded.</p>

<p>Tags that don’t provide content – analytics, attribution, remarketing etc. – are a bit more tricky.</p>

<p>A TagMan study from several years ago demonstrated that the later a tag was fired, the greater the risk of data loss as visitors abandoned the page before the tag had fired.</p>

<p>These types of tag are ideal candidates for server-side tag management where only one tag needs to be fired, and the server-side code can distribute out the data further (clean up PII etc on the way).</p>

<p>But overall, the faster a page is, the less data loss there’s going to be.</p>

<h1>Cut Connection Delays</h1>

<p>The last area I explore is whether some of the delays caused by creating new network connections can be reduced.</p>

<p>Preconnect Resource Hints are commonly added via a HTTP headers, or the directly in the page using <code>link</code> element:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">”preconnect”</span> <span class="na">href=</span><span class="s">”https://www.example.com”</span><span class="nt">&gt;</span>
</span></code></pre></div></figure>


<p>By default browsers wait until they’re about to request a resource before they make a connection to a server (assuming one doesn’t already exist) and <a href="https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header/">making this connection ahead of time can bring forward the download of a resource</a>.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/before-preconnect.png" alt="WebPageTest waterfall showing request without preconnect" />
Without preconnect – image download starts at ~1.55s</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/after-preconnect.png" alt="WebPageTest waterfall showing request with preconnect" />
With preconnect – image download starts at ~0.95s</p>

<p>Preconnects are cheap but they’re not free (creating a HTTPS connection consumes bandwidth in the certificate exchange) so don’t overuse them.</p>

<p>For tags later in the page, you can use a tag manager to inject preconnect directives at an appropriate point – for example if a tag is being injected using the Window Loaded trigger, I’ll experiment with injecting the preconnect using DOM Ready trigger.</p>

<p>Not every domain needs a preconnect and if you find the need to preconnect to many domains then you’re probably using too many tags.</p>

<h1>Taming Tags Delivers Wins</h1>

<p>When he was at The Daily Telegraph, Gareth Clubb wrote about <a href="https://medium.com/the-telegraph-engineering/improving-third-party-web-performance-at-the-telegraph-a0a1000be5">the approach they adopted and the experience they had reducing the impact of third-party tags</a>.</p>

<p>Several years ago I was working with a UK fashion retailer, and <a href="https://noti.st/andydavies/dCBdI2/fast-fashion-how-missguided-revolutionised-their-approach-to-site-performance">we found that one of their 3rd-party tags was slowing down visitors who used Android phones by around four seconds</a>. The retailer decided to disable this tag for those visitors and saw a 26% increase in revenue from them.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/android-performance.jpeg" alt="Graph showing impact of performance gains on Android" /></p>

<p>Encouraged by this early gain the retailer went on to make improvements right across their site and reduced the median load time for Android visitors from over 14 seconds to under 6.</p>

<p>What about OPI?</p>

<p>To see what gains OPI could make if they improved the implementation of their third-party tags I used a <a href="https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/">Cloudflare Worker as proxy to rewrite the page and tested the changes with WebPageTest</a>.</p>

<p>Consolidating and choreographing just a few 3rd-party tags reduced the delay before the product image appeared by one second, and there’s still plenty of opportunity for further improvements to both the base page, and their tag implementation.</p>

<p><img src="https://andydavies.me/blog/images/2020-10-02-reducing-the-site-speed-impact-of-third-party-tags/opi-3rd-parties-choreographed.png" alt="WebPageTest illustrating performance gains from choreographing OPI's tags" /></p>

<h1>Summary</h1>

<p>Although I’ve described a sequential process, in reality I adopt a ‘pick and mix’ approach.</p>

<p>Persuading clients to implement ‘quick wins’ such as replacing the YouTube player, or delaying the load of chat and feedback widgets early on in an engagement is a great way of kickstarting an overall performance improvement process.</p>

<p>And like many things performance related, even small incremental improvements soon add up to make a larger difference.</p>

<p>Next time you’re thinking about the impact third-party tags are having on site-speed keep these five principles in mind:</p>

<ul>
<li><strong>Catalogue</strong> the tags that are being served to your visitors</li>
<li><strong>Consolidate</strong> to remove expired and unused tags, reduce duplication and ensure tags are only included on the pages they are used on</li>
<li>Reduce the <strong>cost</strong> of tags by adopting lightweight alternatives, slimming down testing frameworks and Tag Managers. Self-host libraries instead of fetching them from public CDNs.</li>
<li><strong>Choreograph</strong> when tags are loaded so that the most important content gets shown to your visitors sooner</li>
<li><strong>Cut</strong> delays caused by connecting to tag domains</li>
</ul>


<p>They’re not an exhaustive list of all the things you should consider when managing tags but they’ll help you move in the right direction.</p>

<p>And if you’d like help taming your third-party tags, or generally improving the speed of your site feel free to <a href="mailto:hello@andydavies.me">Get In Touch</a>.</p>

<h1>Further Reading</h1>

<p><a href="https://noti.st/andydavies/V3yMym/reducing-the-speed-impact-of-third-party-tags">Reducing the Speed Impact of Third-Party Tags (slides)</a></p>

<p><a href="https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags/">Measuring the Impact of 3rd-Party Tags With WebPageTest</a></p>

<p><a href="https://www.tunetheweb.com/blog/adding-controls-to-google-tag-manager/">Adding controls to Google Tag Manager</a>, <a href="https://twitter.com/tunetheweb">Barry Pollard</a></p>

<p><a href="https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/">Exploring Site Speed Optimisations With WebPageTest and Cloudflare Workers</a></p>

<p><a href="https://noti.st/andydavies/dCBdI2/fast-fashion-how-missguided-revolutionised-their-approach-to-site-performance">Fast Fashion… How Missguided revolutionised their approach to site performance</a></p>

<p><a href="https://www.simoahava.com/analytics/optimize-anti-flicker-snippet-delay-test/">Google Optimize Anti-flicker Snippet Delay Test</a>, <a href="https://www.simoahava.com/">Simo Ahava</a></p>

<p><a href="https://medium.com/caspertechteam/we-shaved-1-7-seconds-off-casper-com-by-self-hosting-optimizely-2704bcbff8ec">How we shaved 1.7 seconds off casper.com by self-hosting Optimizely</a></p>

<p><a href="https://help.optimizely.com/Set_Up_Optimizely/Content_Delivery_Networks_(CDNs%29_and_Optimizely_X">Content Delivery Networks (CDNs) and Optimizely</a></p>

<p><a href="https://calendar.perfplanet.com/2019/self-hosting-third-party-resources-the-good-the-bad-and-the-ugly/">Self-hosting third-party resources: the good, the bad and the ugly</a></p>

<p><a href="https://requestmap.herokuapp.com">Request Map Generator</a></p>

<p><a href="https://medium.com/the-telegraph-engineering/improving-third-party-web-performance-at-the-telegraph-a0a1000be5">Improving third-party web performance at The Telegraph</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Exploring Site Speed Optimisations With WebPageTest and Cloudflare Workers]]></title>
    <link href="https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/"/>
    <updated>2020-09-22T18:04:00+01:00</updated>
    <id>https://andydavies.me/blog/2020/09/22/exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers</id>
    <content type="html"><![CDATA[<p>One of the common questions I'm often asked by clients is "What difference will the changes you're recommending make to our site's speed"?</p>

<p>And too often that can be a hard question to answer…</p>

<p>I can be pretty sure of the 'direction of travel' – shrinking resources should make them download faster, delaying 3rd-parties should make content appear sooner – but page load can be non-deterministic and un-sharding domains, re-ordering resources or other changes sometimes leads to unexpected results.</p>

<p>Knowledge, experience and lots of testing can help us to prioritise what we think are the appropriate optimisations but often we have to wait until those changes make it to staging (or even live) before we can check the results.</p>

<!--more-->


<p>WebPageTest and DevTools can give us clues that we're heading in the right direction but there's a gap that neither of them quite fill – a reliable testing environment that allows us to experiment and make changes to the page being tested.</p>

<p>When we worked together, <a href="https://twitter.com/simonhearne">Simon Hearne</a> prototyped a proxy using mod_pagespeed that optimised pages and illustrated potential performance gains to customers (and <a href="https://twitter.com/simonhearne/status/1255459490192928768">accidentally siphoned away a UK airline's search traffic</a>) but it's optimisations were limited and it wasn't easy to use.</p>

<p>So, last year when Pat Meenan, and Andrew Galloni started demonstrating what was possible using Cloudflare Workers as a proxy I guessed it might be a solution to fill the gap.</p>

<p>But it's taken me a little while to get around to experimenting with them...</p>

<h1>Cloudflare Workers</h1>

<p>Service Workers are often described as a programmable proxy in the browser – they can intercept and rewrite requests and responses, cache and synthesise responses, and much more.</p>

<p><a href="https://workers.cloudflare.com">Cloudflare Workers</a> are a similar concept but instead of running in the browser they run on CDN edge nodes.</p>

<p>In addition to intercepting network requests, there's a HTMLRewriter class that targets DOM nodes using CSS selectors and triggers a handler when there's a match. The handlers can alter the matched elements, for example changing attributes, or even replacing an elements contents.</p>

<p><a href="https://twitter.com/dot_js">Andrew Galloni</a>'s post –  <a href="https://calendar.perfplanet.com/2019/prototyping-optimizations-with-cloudflare-workers-and-webpagetest/">Prototyping optimizations with Cloudflare Workers and WebPageTest</a> – for the 2019 Performance Advent Calendar gives a good overview and guide to get started with them.</p>

<h1>How I'm Using Them</h1>

<p>Key to the approach I'm using is WebPageTest's <a href="https://github.com/WPO-Foundation/webpagetest-docs/blob/master/user/Scripting.md#overridehost"><code>overrideHost</code></a> script command. It allows requests to one domain to be rewritten to another, and sets an <code>x-host</code> HTTP header on the revised request.</p>

<p>In the example script below any requests to <code>example.com</code> are rewritten to <code>demo-proxy.asteno.workers.dev</code> and the <code>x-Host</code> header set to <code>example.com</code> for those requests.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>overrideHost www.example.com demo-proxy.asteno.workers.dev
</span><span class='line'>navigate https://example.com/test-page.html</span></code></pre></div></figure>


<p>I start with a simple boilerplate worker and as the transforms tend to be bespoke for each site, I create a separate worker for each site I'm testing.</p>

<p>The boilerplate script for the worker follows this pattern:</p>

<ol>
<li>serves a robots.txt that disallows crawlers</li>
<li>returns an error if the <code>x-host</code> header is missing</li>
<li>if the request is for a predefined site, the browser is expecting a HTML response and the <code>x-bypass-transform</code> header isn't set to <code>true</code> the proxy uses a HTMLRewriter to modify the response</li>
<li>Otherwise just proxy the request</li>
</ol>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="cm">/* Started from Pat&#39;s example in https://www.slideshare.net/patrickmeenan/getting-the-most-out-of-webpagetest */</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/*</span>
</span><span class='line'><span class="cm"> * TODO</span>
</span><span class='line'><span class="cm"> * Add mimetype to robots.txt</span>
</span><span class='line'><span class="cm"> * Add a better doc check, perhaps use a header instead?</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'>
</span><span class='line'><span class="kr">const</span> <span class="nx">site</span> <span class="o">=</span> <span class="s1">&#39;www.example.com&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="nx">addEventListener</span><span class="p">(</span><span class="s1">&#39;fetch&#39;</span><span class="p">,</span> <span class="nx">event</span> <span class="o">=&gt;</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">event</span><span class="p">.</span><span class="nx">respondWith</span><span class="p">(</span><span class="nx">handleRequest</span><span class="p">(</span><span class="nx">event</span><span class="p">.</span><span class="nx">request</span><span class="p">))</span>
</span><span class='line'><span class="p">});</span>
</span><span class='line'>
</span><span class='line'><span class="nx">async</span> <span class="kd">function</span> <span class="nx">handleRequest</span><span class="p">(</span><span class="nx">request</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'> <span class="kr">const</span> <span class="nx">url</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">URL</span><span class="p">(</span><span class="nx">request</span><span class="p">.</span><span class="nx">url</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// Disallow crawlers</span>
</span><span class='line'>
</span><span class='line'> <span class="k">if</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">pathname</span> <span class="o">===</span> <span class="s2">&quot;/robots.txt&quot;</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="k">return</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="s1">&#39;User-agent: *\nDisallow: /&#39;</span><span class="p">,</span> <span class="p">{</span><span class="nx">status</span><span class="o">:</span> <span class="mi">200</span><span class="p">});</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// When overrideHost is used in a script, WPT sets x-host to original host i.e. site we want to proxy</span>
</span><span class='line'>
</span><span class='line'> <span class="kr">const</span> <span class="nx">host</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;x-host&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>   <span class="c1">// Error if x-host header missing</span>
</span><span class='line'>
</span><span class='line'> <span class="k">if</span><span class="p">(</span><span class="o">!</span><span class="nx">host</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="k">return</span> <span class="k">new</span> <span class="nx">Response</span><span class="p">(</span><span class="s1">&#39;x-host header missing&#39;</span><span class="p">,</span> <span class="p">{</span><span class="nx">status</span><span class="o">:</span> <span class="mi">403</span><span class="p">});</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="nx">url</span><span class="p">.</span><span class="nx">hostname</span> <span class="o">=</span> <span class="nx">host</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'> <span class="kr">const</span> <span class="nx">bypassTransform</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;x-bypass-transform&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'> <span class="kr">const</span> <span class="nx">acceptHeader</span> <span class="o">=</span> <span class="nx">request</span><span class="p">.</span><span class="nx">headers</span><span class="p">.</span><span class="nx">get</span><span class="p">(</span><span class="s1">&#39;accept&#39;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// If it&#39;s the original document, and we don&#39;t want to bypass the rewrite of HTML</span>
</span><span class='line'> <span class="c1">// TODO will also select sub-documents e.g. iframes, from the same site :-(</span>
</span><span class='line'>
</span><span class='line'> <span class="k">if</span><span class="p">(</span><span class="nx">host</span> <span class="o">===</span> <span class="nx">site</span> <span class="o">&amp;&amp;</span>
</span><span class='line'>   <span class="p">(</span><span class="nx">acceptHeader</span> <span class="o">&amp;&amp;</span> <span class="nx">acceptHeader</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">&#39;text/html&#39;</span><span class="p">)</span> <span class="o">&gt;=</span> <span class="mi">0</span><span class="p">)</span> <span class="o">&amp;&amp;</span>
</span><span class='line'>   <span class="p">(</span><span class="o">!</span><span class="nx">bypassTransform</span> <span class="o">||</span> <span class="p">(</span><span class="nx">bypassTransform</span> <span class="o">&amp;&amp;</span> <span class="nx">bypassTransform</span><span class="p">.</span><span class="nx">indexOf</span><span class="p">(</span><span class="s1">&#39;true&#39;</span><span class="p">)</span> <span class="o">===</span> <span class="o">-</span><span class="mi">1</span><span class="p">)))</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>   <span class="kr">const</span> <span class="nx">response</span> <span class="o">=</span> <span class="nx">await</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">toString</span><span class="p">(),</span> <span class="nx">request</span><span class="p">)</span>
</span><span class='line'>
</span><span class='line'>   <span class="k">return</span> <span class="k">new</span> <span class="nx">HTMLRewriter</span><span class="p">()</span>
</span><span class='line'>     <span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;selector&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">exampleElementHandler</span><span class="p">())</span>
</span><span class='line'>     <span class="p">.</span><span class="nx">transform</span><span class="p">(</span><span class="nx">response</span><span class="p">)</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'>
</span><span class='line'> <span class="c1">// Otherwise just proxy the request</span>
</span><span class='line'>
</span><span class='line'> <span class="k">return</span> <span class="nx">fetch</span><span class="p">(</span><span class="nx">url</span><span class="p">.</span><span class="nx">toString</span><span class="p">(),</span> <span class="nx">request</span><span class="p">)</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="cm">/*</span>
</span><span class='line'><span class="cm"> *</span>
</span><span class='line'><span class="cm"> */</span>
</span><span class='line'>
</span><span class='line'><span class="kr">class</span> <span class="nx">exampleElementHandler</span> <span class="p">{</span>
</span><span class='line'> <span class="nx">element</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="c1">// Do something</span>
</span><span class='line'> <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<h1>Example Transforms</h1>

<p>The transforms I'm using are fairly straightforward and mainly consist of unsharding domains, changing the order of the page, or delaying when a resource loads.</p>

<p>Sometimes it's possible to manipulate an existing element in the page, sometimes an element has to be deleted and a replacement inserted elsewhere in the page.</p>

<ul>
<li>Unsharding Domains</li>
</ul>


<p>Requesting frameworks, libraries etc from 3rd-party CDNs such as cdnjs, jsdelivr etc. is still very common across many of the customers I work with.</p>

<p>Requesting these from another origin involves creating a new connection, and then as HTTP/2 prioritisation only works across a single connection they may compete for the network with other resources.</p>

<p>One of the first tests I try is directing these requests through the proxy, so they're on the same origin as the page too:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="nx">overrideHost</span> <span class="nx">www</span><span class="p">.</span><span class="nx">example</span><span class="p">.</span><span class="nx">com</span> <span class="nx">demo</span><span class="o">-</span><span class="nx">proxy</span><span class="p">.</span><span class="nx">asteno</span><span class="p">.</span><span class="nx">workers</span><span class="p">.</span><span class="nx">dev</span>
</span><span class='line'><span class="nx">overrideHost</span> <span class="nx">ajax</span><span class="p">.</span><span class="nx">googleapis</span><span class="p">.</span><span class="nx">com</span> <span class="nx">demo</span><span class="o">-</span><span class="nx">proxy</span><span class="p">.</span><span class="nx">asteno</span><span class="p">.</span><span class="nx">workers</span><span class="p">.</span><span class="nx">dev</span>
</span><span class='line'><span class="nx">navigate</span> <span class="nx">https</span><span class="o">:</span><span class="c1">//example.com/test-page.html)</span>
</span></code></pre></div></figure>


<p>The proxy could be improved to cache these libraries on Cloudflare to remove the request origin for them – one of Pat Meenan's workers has an example of how to do this.</p>

<ul>
<li>Deferring inline scripts</li>
</ul>


<p>Clients often use 3rd-party services that don't need to be loaded until the visitor has a usable page – sometimes these provide outward facing features such as chat or feedback widgets, other times they may be internal facing, session replay for example.</p>

<p>I'll often defer the load for these types of services by moving them into a Tag Manager, and initiating their insertion using the <code>Window.Loaded</code> trigger in Google Tag Manager (GTM).</p>

<p>In one recent example, HotJar was loaded via an async snippet at the start of the head:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">(</span><span class="kd">function</span><span class="p">(</span><span class="nx">h</span><span class="p">,</span><span class="nx">o</span><span class="p">,</span><span class="nx">t</span><span class="p">,</span><span class="nx">j</span><span class="p">,</span><span class="nx">a</span><span class="p">,</span><span class="nx">r</span><span class="p">){</span>
</span><span class='line'>  <span class="nx">h</span><span class="p">.</span><span class="nx">hj</span><span class="o">=</span><span class="nx">h</span><span class="p">.</span><span class="nx">hj</span><span class="o">||</span><span class="kd">function</span><span class="p">(){(</span><span class="nx">h</span><span class="p">.</span><span class="nx">hj</span><span class="p">.</span><span class="nx">q</span><span class="o">=</span><span class="nx">h</span><span class="p">.</span><span class="nx">hj</span><span class="p">.</span><span class="nx">q</span><span class="o">||</span><span class="p">[]).</span><span class="nx">push</span><span class="p">(</span><span class="nx">arguments</span><span class="p">)};</span>
</span><span class='line'>  <span class="nx">h</span><span class="p">.</span><span class="nx">_hjSettings</span><span class="o">=</span><span class="p">{</span><span class="nx">hjid</span><span class="o">:</span><span class="nx">xxxxxx</span><span class="p">,</span><span class="nx">hjsv</span><span class="o">:</span><span class="nx">x</span><span class="p">};</span>
</span><span class='line'>  <span class="nx">a</span><span class="o">=</span><span class="nx">o</span><span class="p">.</span><span class="nx">getElementsByTagName</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">)[</span><span class="mi">0</span><span class="p">];</span>
</span><span class='line'>  <span class="nx">r</span><span class="o">=</span><span class="nx">o</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;script&#39;</span><span class="p">);</span><span class="nx">r</span><span class="p">.</span><span class="nx">async</span><span class="o">=</span><span class="mi">1</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">r</span><span class="p">.</span><span class="nx">src</span><span class="o">=</span><span class="nx">t</span><span class="o">+</span><span class="nx">h</span><span class="p">.</span><span class="nx">_hjSettings</span><span class="p">.</span><span class="nx">hjid</span><span class="o">+</span><span class="nx">j</span><span class="o">+</span><span class="nx">h</span><span class="p">.</span><span class="nx">_hjSettings</span><span class="p">.</span><span class="nx">hjsv</span><span class="p">;</span>
</span><span class='line'>  <span class="nx">a</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">r</span><span class="p">);</span>
</span><span class='line'> <span class="p">})(</span><span class="nb">window</span><span class="p">,</span><span class="nb">document</span><span class="p">,</span><span class="s1">&#39;https://static.hotjar.com/c/hotjar-&#39;</span><span class="p">,</span><span class="s1">&#39;.js?sv=&#39;</span><span class="p">);</span>
</span></code></pre></div></figure>


<p>To delay HotJar loading and simulate it being implemented via GTM I wrapped the HotJar snippet with a native event handler for window onload.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">class</span> <span class="nx">deferInlineScript</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">element</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">wrapperStart</span> <span class="o">=</span> <span class="s2">&quot;window.addEventListener(&#39;load&#39;, function() {&quot;</span><span class="p">;</span>
</span><span class='line'>    <span class="kr">const</span> <span class="nx">wrapperEnd</span> <span class="o">=</span><span class="s2">&quot;});&quot;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">element</span><span class="p">.</span><span class="nx">prepend</span><span class="p">(</span><span class="nx">wrapperStart</span><span class="p">,</span> <span class="p">{</span><span class="nx">html</span><span class="o">:</span> <span class="kc">true</span><span class="p">});</span>
</span><span class='line'>    <span class="nx">element</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">wrapperEnd</span><span class="p">,</span>  <span class="p">{</span><span class="nx">html</span><span class="o">:</span> <span class="kc">true</span><span class="p">});</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<ul>
<li>Moving Third-Party Tags</li>
</ul>


<p>Qubit's SmartServe is quite a large tag and even when loaded async competes for network bandwidth and CPU time in ways that impact performance.</p>

<p>One site I tested implemented the SmartServe tag near the top of the &lt;head>, before any stylesheets.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;script </span><span class="na">src=</span><span class="s">&#39;//static.goqubit.com/smartserve-xxxx.js&#39;</span> <span class="na">async</span> <span class="na">defer</span><span class="nt">&gt;&lt;/script&gt;</span>
</span></code></pre></div></figure>


<p>Its fetch was initiated soon after the page started loading and was competing with higher priority render blocking resources so I wanted to move the element to much later in the &lt;head>.</p>

<p>This type of change becomes a two stage process where one handler removes the script element and then a second reinserts it (just before the end of the head).</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;script[src=&quot;//static.goqubit.com/smartserve-xxxx.js&quot;]&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">removeSmartServe</span><span class="p">())</span>
</span><span class='line'><span class="p">.</span><span class="nx">on</span><span class="p">(</span><span class="s1">&#39;head&#39;</span><span class="p">,</span> <span class="k">new</span> <span class="nx">reinsertSmartServe</span><span class="p">())</span>
</span></code></pre></div></figure>




<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kr">class</span> <span class="nx">removeSmartServe</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">element</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="nx">element</span><span class="p">.</span><span class="nx">remove</span><span class="p">();</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="kr">class</span> <span class="nx">reinsertSmartServe</span> <span class="p">{</span>
</span><span class='line'>  <span class="nx">element</span><span class="p">(</span><span class="nx">element</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="kd">var</span> <span class="nx">text</span> <span class="o">=</span> <span class="s1">&#39;&lt;script src=&quot;//static.goqubit.com/smartserve-xxxx.js&quot; async defer&gt;&lt;/script&gt;&#39;</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'>    <span class="nx">element</span><span class="p">.</span><span class="nx">append</span><span class="p">(</span><span class="nx">text</span><span class="p">,</span> <span class="p">{</span><span class="nx">html</span><span class="o">:</span> <span class="kc">true</span><span class="p">});</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<h1>Testing</h1>

<p>In initial testing I tend to start with host overrides in WebPageTest, then switch to curl or a browser when developing the HTML rewriting script, and finally switching back to WebPageTest to check before and after comparisons.</p>

<p>It's also an iterative process where I'll make a some initial changes, test and refine until I'm happy with  their impact and then start around the loop again.</p>

<ul>
<li>curl</li>
</ul>


<p>To test the HTML rewriting using <code>curl</code> both the <code>x-host</code>, and <code>accept</code> headers need to be set appropriately.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>curl -H <span class="s2">&quot;x-host: www.example.com&quot;</span> -H <span class="s2">&quot;accept: text/html&quot;</span> https://demo-proxy.asteno.workers.dev/test-page.html
</span></code></pre></div></figure>


<p>Piping curl's output to a file or util like <code>less</code> makes it easier to read.</p>

<ul>
<li>Browser</li>
</ul>


<p>For in-browser testing of HTML rewriting I've been using Chrome, setting the <code>x-host</code> header with the <a href="https://chrome.google.com/webstore/detail/modheader/idgpnmonknjnojddfkpgkljpfnnfcklj?hl=en-GB">ModHeader Extension</a> and then loading the page via the proxy i.e. https://demo-proxy.asteno.workers.dev/test-page.html</p>

<p>This approach only allows the initial host to be overridden, so can't be used to unshard domains.</p>

<ul>
<li>WebPageTest</li>
</ul>


<p>Finally when I'm happy with the host overrides and HTML rewrites I switch back to WebPageTest and generate before (baseline) and after tests.</p>

<p>I've found that some sites get faster when proxied through Cloudflare's network, so I still used the proxy when I'm generating a baseline for comparison but set the <code>x-bypass-transform</code> header to true so the HTML transforms aren't applied.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>setHeader x-bypass-transform: <span class="nb">true</span>
</span></code></pre></div></figure>


<h1>Gotchas</h1>

<p>A few issues have tripped me up while I was writing and testing proxies:</p>

<ul>
<li>overrideHost and Service Workers</li>
</ul>


<p>WebPageTest's <code>overrideHost</code> command doesn't seem to work with requests dispatched from a Service Worker and the request always seems to default back to the original host.</p>

<p>Reading the code and talking to Pat, it appears it should but I've not had time to debug this issue further yet.</p>

<ul>
<li>overrideHost and non-Chromium browsers</li>
</ul>


<p>I could only get <code>overrideHost</code> to work in Chromium based browsers – Chrome, Mobile Chrome and Edge.</p>

<ul>
<li>Fragile Selectors</li>
</ul>


<p>When rewriting the HTML, I sometimes have to rely on fragile DOM queries, for example this selector to target the first script element in the head: <code>head &gt; script:nth-of-type(1)</code>.</p>

<p>And as there's currently no way to extract the contents of an element I can't test that the element that's been passed to the handler is the one I wanted to target.</p>

<p>Specific selectors for example, that use an id, or src attribute etc., are more robust.</p>

<ul>
<li>Differing DOMs</li>
</ul>


<p>The DOM that HTMLRewriter is operating on is not the same DOM as viewed in the Elements tab in DevTools as the rewriter doesn't execute scripts, so by default the DOM queries can't be tested in the browser.</p>

<p>Using DevTools to block all requests except the one for the source HTML document and then checking the queries from the console is one way around this.</p>

<h1>Closing Thoughts</h1>

<p>Even though I've only used the combination WebPageTest and Cloudflare Workers with a few sites, it's clear that it's a powerful combination and it's likely to become a regular part of my client workflow.</p>

<p>At BrightonSEO I'm talking about <a href="https://2020.brightonseo.com/talks/reducing-the-speed-impact-of-third-party-tags/">Reducing the Speed Impact of Third-Party Tags</a> and as much as I can talk about the theory, nothing beats a good demo.</p>

<p>For my demo I used a worker to re-write parts of the page and choreograph how 3rd-party tags were loaded. The changes improved Largest Contentful Paint by a second for OPI's product page (top row).</p>

<p><img src="https://andydavies.me/blog/images/2020-09-22-exploring-site-speed-optimisations-with-webpagetest-and-cloudflare-workers/opi-filmstrip.jpeg" alt="WebPageTest filmstrip comparing Opi before and after third-party tags have been choreographed" /></p>

<p>The filmstrip is for an uncached view of the page, and although there's still plenty of room for improvement in the initial render time, it illustrates how a proxy can be used to quickly evaluate changes before committing them to the development lifecycle.</p>

<p>There's plenty of other optimisations to try… from replacing an embedded YouTube player with a <a href="https://css-tricks.com/lazy-load-embedded-youtube-videos/">lazy-loaded version</a> or adding the lazy-loading attribute to out of viewport images, through to using Cloudflare's image optimisation, and text compression features to reduce payload sizes.</p>

<p>A few clients ask me to evaluate the performance impact of 3rd-party tags before they implement them. As part of this process I typically query the HTTP Archive to find another site that uses the same tag and then test that site with and without the tag. Using a proxy I could inject the tag into the client's site and see what impact it has.</p>

<p>As yet, I've not got as far as rewriting or replacing external scripts and stylesheets, or exploring how Cloudflare's cache and key-value store can be used in the testing process.</p>

<p>But if you'd like some more sophisticated examples of the types of optimisations that can be implemented using Cloudflare's Workers, <a href="https://twitter.com/patmeenan">Pat Meenan</a> has a <a href="https://github.com/pmeenan/cf-workers">collection of examples on GitHub</a>.</p>

<h1>Further Reading</h1>

<p><a href="https://calendar.perfplanet.com/2019/prototyping-optimizations-with-cloudflare-workers-and-webpagetest/">Prototyping optimizations with Cloudflare Workers and WebPageTest</a>, <a href="https://twitter.com/dot_js">Andrew Galloni</a>, Dec 2019</p>

<p><a href="https://twitter.com/patmeenan">Pat Meenan's</a> <a href="https://github.com/pmeenan/cf-workers">collection of Cloudflare Workers</a></p>

<p><a href="https://developers.cloudflare.com/workers/">Cloudflare Workers documentation</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Rel=prefetch and the Importance of Effective HTTP/2 Prioritisation]]></title>
    <link href="https://andydavies.me/blog/2020/07/08/rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/"/>
    <updated>2020-07-08T15:52:24+01:00</updated>
    <id>https://andydavies.me/blog/2020/07/08/rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation</id>
    <content type="html"><![CDATA[<p>Many performance techniques focus on improving the performance of the current page, but there are some that help with the performance of the next page – caching, prefetching, and prerendering for example.</p>

<p>The <a href="https://www.w3.org/TR/resource-hints/#dfn-prefetch">Prefetch Resource Hint</a> allows us to tell the browser about resources we expect to be used in the near future, so they can be fetched ready for the next navigation.</p>

<p>Several of my clients have implemented Prefetch – some are inserting the markup server-side when the page is generated, and others injecting it dynamically in the browser using <a href="https://instant.page/">Instant Page</a> or similar.</p>

<p>A while back I noticed Chrome was making requests for prefetched resources much earlier than I expected and in some cases the prefetched resources were competing with other more important resources for the network.</p>

<p>As the specification makes clear this is something we want to avoid:</p>

<blockquote><p>Resource fetches required for the next navigation SHOULD have lower relative priority and SHOULD NOT block or interfere with resource fetches required by the current navigation context.</p></blockquote>

<p>So how do browsers behave and what are the implications of which server is in use?</p>

<!--more-->


<h1>Test Case</h1>

<p>The tests in this post are based on a modified version of the <a href="https://colorlib.com/wp/template/electro/">Electro ecommerce template from ColorLib</a> with the following prefetch declarations added in the <code>&lt;head&gt;</code> of the document:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prefetch&quot;</span> <span class="na">href=</span><span class="s">&quot;https://www.wikipedia.org/img/Wikipedia-logo-v2.png&quot;</span> <span class="na">as=</span><span class="s">&quot;image&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prefetch&quot;</span> <span class="na">href=</span><span class="s">&quot;dummy-subresources/styles.css&quot;</span> <span class="na">as=</span><span class="s">&quot;style&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prefetch&quot;</span> <span class="na">href=</span><span class="s">&quot;dummy-subresources/scripts.js&quot;</span> <span class="na">as=</span><span class="s">&quot;script&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prefetch&quot;</span> <span class="na">href=</span><span class="s">&quot;dummy-subresources/image.jpg&quot;</span> <span class="na">as=</span><span class="s">&quot;image&quot;</span> <span class="nt">/&gt;</span>
</span></code></pre></div></figure>


<p>The collection of test pages I used is available on <a href="https://github.com/andydavies/test-rel-prefetch">Github</a>.</p>

<p>All the browsers tested – Firefox, Chrome, new Edge and Safari – issue prefetch requests with a low priority, but when the requests are dispatched varies between browsers.</p>

<p>Chrome, new Edge and Safari dispatch the prefetch requests sooner than Firefox and rely on HTTP/2 prioritisation to schedule the requests appropriately against other resources.</p>

<p>As <a href="https://github.com/andydavies/http2-prioritization-issues#current-status">support for HTTP/2 prioritisation varies from very good to non-existent depending on the server</a>, this approach can lead to prefetched resources competing for network capacity.</p>

<h1>Servers with Good HTTP/2 Prioritisation</h1>

<p>The first set of examples are served using <a href="https://github.com/h2o/h2o">h2o</a>, a server that’s known to support effective HTTP/2 prioritisation, running on a $5/month Digital Ocean droplet.</p>

<ul>
<li>Firefox</li>
</ul>


<p>Firefox delays issuing the prefetch requests (37, 38, 39, 40) until the network is quiet. In the example below they’re actually dispatched after the load event but I’ve also seen them dispatched in the middle of page load when the network was quiet.</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/h2o-firefox-prefetch-in-head.png" alt="WebPageTest waterfall showing Firefox defering the request for prefetched resources" />
<a href="https://www.webpagetest.org/result/200708_J4_05e5b2217691557c4828c2aa4d7d6acd/#run3">Resource Hints in head of Page - h2o tested with Firefox / Dulles / Cable</a></p>

<ul>
<li>Chrome and Edge</li>
</ul>


<p>Chrome schedules the requests for prefetched resources alongside the those referenced in the body of the document, as part of it’s ‘second stage load’.</p>

<p>It delays dispatching the prefetch requests (30, 31, 32, 34) until after the other resources referenced in markup but the prefetch requests are still made before those for ‘late-discovered’ resources discovered, such as background images, and fonts.</p>

<p>The server correctly delays the responses for resources prefetched from the test page origin until the other higher priority resources have been served.</p>

<p>As HTTP/2 prioritisation only works across the same connection, if a resource is prefetched from another origin it can’t be prioritised against requests from other origins. And so the request to prefetch an image from Wikipedia (34) gets dispatched, and completes before other content that’s needed to render the page.</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/h2o-chrome-prefetch-in-head.png" alt="WebPageTest waterfall showing Chrome requesting prefetched resources late and relying on HTTP/2 prioritisation to schedule them correctly" />
<a href="https://www.webpagetest.org/result/200708_D0_7410f3d8304c72e7030ef284c775f012/#run8">Resource Hints in head of Page - h2o tested with Chrome / Dulles / Cable</a></p>

<ul>
<li>Safari</li>
</ul>


<p>Prefetch is disabled by default in Safari 13 but can be enabled via Experimental Features.</p>

<p>Safari dispatches the requests as soon as the prefetch directives are discovered.</p>

<p>Resources prefetched from the same origin as the page (3, 4, 5) are correctly delayed by the server’s prioritisation, but as the request to Wikipedia (2) is on a separate connection it may contend with other more important resources (in this case Wikipedia's CDN actually responds before h2o so no contention occurs).</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/h2o-safari-prefetch-in-head.png" alt="WebPageTest waterfall showing Safari requesting prefetched resources early and relying on HTTP/2 prioritisation to schedule them correctly" />
Resource Hints in head of Page - h2o tested with Safari / UK / Broadband</p>

<h1>Servers with Poor HTTP/2 Prioritisation</h1>

<p><a href="https://mobile.twitter.com/patmeenan">Pat Meenan</a> and I have been tracking how well HTTP/2 servers support prioritisation for a while, and the sad truth is that only a few servers and services prioritise effectively.</p>

<p>To test what happens with servers that have poor (or missing) HTTP/2 prioritisation, I hosted the page on both Netlify and Amazon Cloudfront.</p>

<p>The examples below use Netlify but Cloudfront showed similar behaviour.</p>

<ul>
<li>Chrome and Edge</li>
</ul>


<p>Chrome's delay in dispatching the prefetch requests means the prefetched resources don’t contend with the other resources referenced in the page markup.</p>

<p>But as the server doesn’t respect the client-provided priorities, the low priority prefetched resources (30, 31, 32) delay higher priority background images and fonts (33, 34, 34, 36, 37), and the larger the prefetched resources are the longer this delay will be.</p>

<p>In this test only some of the contents of the prefetched resources are recieved before the fonts but in other tests I've seen whole resources download before the fonts.</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/netlify-chrome-prefetch-in-head.png" alt="WebPageTest waterfall showing Chrome requesting prefetched resources early and them competing with other resources due to poor server HTTP/2 prioritisation" />
<a href="https://www.webpagetest.org/result/200708_3Q_3ad2566a9bd471298ac62173cc482e94/#run9">Resource Hints in head of Page - Netlify tested with Chrome / Dulles / Cable</a></p>

<ul>
<li>Safari</li>
</ul>


<p>Unfortunately the outcome in Safari is even worse than Chrome and Edge.</p>

<p>Safari’s choice to dispatch the prefetch requests as soon as they’re discovered, coupled with the poor server prioritisation, leads to the prefetched resources (2, 3, 4, 5) being fetched far too early and  delaying critical content such as stylesheets.</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/netlify-safari-prefetch-in-head.png" alt="WebPageTest waterfall showing Safari requesting prefetched resources early and them competing with other resources due to poor server HTTP/2 prioritisation" />
Resource Hints in head of Page - Netlify tested with Safari / UK / Broadband</p>

<h1>Summary of Behaviour</h1>

<p>Firefox is the only browser that seems to have good behaviour regardless of server support for prioritisation.</p>

<p>When the server used supports effective prioritisation, then resources prefetched from the same origin are scheduled appropriately in Chrome, Edge and Safari and they don’t contend with other higher priority resources.</p>

<p>If the server doesn’t support effective prioritisation then the prefetched resources can have a negative impact on performance, particularly in Safari, but also in Chrome and Edge.</p>

<p>Chrome, Edge and Safari all dispatch prefetch requests to third-party origins too soon and these requests contend for network bandwidth.</p>

<p>Now we have an understanding of how browsers behave with both ‘good’ and ‘bad’ HTTP/2 servers, how we should implement prefetch in our pages?</p>

<h1>Delaying Prefetch Hints</h1>

<p>If you use a server or CDN that has effective HTTP/2 prioritisation (essentially Akamai, Cloudflare or Fastly) then you can rely on the prefetch resources being prioritised correctly and so it doesn’t really matter where you place the prefetch hints for resources from the same origin.</p>

<p>If you use another server or CDN, or you have cross-origin prefetches then you may need to delay when the browser discovers the prefetch hints.</p>

<p>Placing the prefetch hints at the end of the document appears to be one way of achieving this for same origin resources.</p>

<p>In the example below the prefetched resources (36, 37, 38, 39) are dispatched after the late discovered resources and so don't contend for the network with them.</p>

<p><img src="https://andydavies.me/blog/images/2020-07-08-rel-equals-prefetch-and-the-importance-of-effective-http-slash-2-prioritisation/netlify-chrome-prefetch-at-bottom.png" alt="WebPageTest waterfall showing Chrome requesting prefetched resources late to overcome the issues with poor server HTTP/2 prioritisation" />
<a href="https://www.webpagetest.org/result/200708_N8_de98ceedfea4c1ccff39089fd62e9485/#run9">Resource Hints at Bottom of Page - Netlify tested with Chrome / Dulles / Cable</a></p>

<p>Another option is to inject the prefetch hints after the page has rendered, perhaps when the load event fires, or in response to user interaction (as instant.page does).</p>

<h1>Closing Thoughts</h1>

<p>Writing this post made me a little bit sad...</p>

<p>Prefetch is a feature that’s supposed to help make our visitor’s experiences faster but with the wrong combination of browser and CDN / server it can actually make experiences slower!</p>

<p>If Chromium and WebKit followed Firefox’s lead and dispatched prefetches later then the issue of CDNs and servers with ‘flawed’ HTTP/2 prioritisation would have a reduced impact on these test cases as much (there would still be issues with fonts and background images being delayed though).</p>

<p>The relevant browser bugs for Chrome and WebKit are linked in Further Reading below.</p>

<p>If you're using a CDN or server that has ‘flawed’ HTTP/2 prioritisation, then you should consider how to mitigate the issues shown above as, for a variety of reasons, I’m not sure the prioritisation issues are going to be fixed any time soon.</p>

<h1>Further Reading</h1>

<p><a href="https://www.w3.org/TR/resource-hints/">W3C Resource Hints Specification</a></p>

<p><a href="https://github.com/andydavies/http2-prioritization-issues#current-status">HTTP/2 Prioritisation Tests</a></p>

<p><a href="https://github.com/andydavies/test-rel-prefetch">Prioritisation Tests for rel=prefetch</a></p>

<p><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=1031134">Chrome Issue 1031134: Resources prefetched for next navigation contend with resources for current navigation</a></p>

<p><a href="https://bugs.webkit.org/show_bug.cgi?id=49238">Safari Bug 49238 - Link prefetch launches subresource requests too aggressively</a></p>

<p><a href="https://instant.page">Instant Page</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Capturing and Decrypting HTTPS Traffic From iOS Apps Using Frida]]></title>
    <link href="https://andydavies.me/blog/2019/12/12/capturing-and-decrypting-https-traffic-from-ios-apps/"/>
    <updated>2019-12-12T17:20:49+00:00</updated>
    <id>https://andydavies.me/blog/2019/12/12/capturing-and-decrypting-https-traffic-from-ios-apps</id>
    <content type="html"><![CDATA[<p>I often want to examine the web traffic generated by browsers, and other apps.</p>

<p>Sometimes I can use the tools built into browsers, other times proxies, but when I want to take a deeper look and particularly if I’m looking at how a browser is using HTTP/2, I rely on packet captures.</p>

<p>One challenge with analysing HTTP/2 traffic is that it’s encrypted and while Chrome and Firefox support logging TLS keys and tools like Wireshark can then decrypt the traffic.</p>

<p>Safari and iOS doesn’t have this feature natively, and proxies like Charles only communicate to the browser via HTTP/1.x  so I needed to find another solution.</p>

<p>In this post I walk through how I capture iOS apptraffic using <code>tcpdump</code>, and how I use a Frida script to extract the TLS keys during the capture so that I can decrypt the traffic too.</p>

<!--more-->


<h1>Capturing iOS network traffic</h1>

<p>Apple support capturing iOS device network traffic via a Remote Virtual Interface (RVI). This mirrors the network traffic from the device to a virtual interface in MacOS and from  there the traffic can be captured using tools like tcpdump and Wireshark.</p>

<p>You can find more details in <a href="https://developer.apple.com/library/content/qa/qa1176/_index.html">this article from Apple</a> but the basic process is:</p>

<ul>
<li>If they’re not already installed, install xcode’s command line tools:</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>xcode-select --install
</span></code></pre></div></figure>


<p>If you’re unsure whether you have the command line tools installed, run the above command anyway and it will display an error message if they are already installed.</p>

<ul>
<li>Attach an iOS device, and grab it’s UDID</li>
</ul>


<p>I can’t remember where I got the command line below from but it extracts the device’s UDID from the <code>system_profiler</code> output</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>system_profiler SPUSBDataType <span class="p">|</span> sed -n -e <span class="s1">&#39;/iPad/,/Serial/p;/iPhone/,/Serial/p;/iPod/,/Serial/p&#39;</span> <span class="p">|</span> grep <span class="s2">&quot;Serial Number:&quot;</span> <span class="p">|</span> awk -F <span class="s2">&quot;: &quot;</span> <span class="s1">&#39;{print $2}&#39;</span>
</span><span class='line'>
</span><span class='line'>b0e8fe73db17d4993bd549418bfbdba70a4af2b1
</span></code></pre></div></figure>


<ul>
<li>Start the Remote Virtual Interface</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>rvictl -s b0e8fe73db17d4993bd549418bfbdba70a4af2b1
</span><span class='line'>
</span><span class='line'>Starting device b0e8fe73db17d4993bd549418bfbdba70a4af2b1 <span class="o">[</span>SUCCEEDED<span class="o">]</span> with interface rvi0
</span></code></pre></div></figure>


<ul>
<li>Capture traffic using tcpdump (or Wireshark)</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>tcpdump -i rvi0 -w capture.pcap -P
</span></code></pre></div></figure>


<p>The <code>-P</code> option is an Apple extension that captures the traffic in pcap-ng format, and includes metadata such as process name, pid etc. against each packet.</p>

<p>Unfortunately Wireshark can’t display or filter by this data yet but I’m hoping someone might implement support for it soon. Apple’s tcpdump can display it, see the <code>-k</code> option in man pages for more details.</p>

<ul>
<li>Generate some network traffic</li>
</ul>


<p>Open an app on the device e.g. Safari, and generate some network traffic to a site that uses HTTPS e.g. https://www.bbc.co.uk/news</p>

<p>When your done hit <code>Ctrl-C</code> in the terminal to stop tcpdump capturing.</p>

<ul>
<li>Open the packet dump in Wireshark</li>
</ul>


<p>If you don’t already have Wireshark installed, download and install it from <a href="https://www.wireshark.org">https://www.wireshark.org</a>, and then open the pcap:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>open capture.pcap
</span></code></pre></div></figure>


<p>You should see a screen something like this:</p>

<p><img src="https://andydavies.me/blog/images/capturing-and-decrypting-https-traffic-from-ios-apps/encrypted-pcap.png" alt="Wireshark showing an encrypted packet capture for bbc.co.uk/news" /></p>

<p>I’ve filtered the capture to just display the traffic to and from <a href="https://www.bbc.co.uk">www.bbc.co.uk</a>. But as the traffic is encrypted using TLS 1.2 we can’t see the contents of the packets.</p>

<p>To decrypt the packets we need the matching TLS keys, Chrome and Firefox will provide these when the SSLKEYLOGFILE environment variable is set but unfortunately there seems to be no equivalent for Safari.</p>

<p>Fortunately thanks to tools like Frida, we have the ability to implement it ourselves.</p>

<h1>Extracting  TLS Keys and Decrypting iOS Traffic</h1>

<p>Frida describes itself as a “dynamic instrumentation toolkit”, it injects a JavaScript VM into applications and we can write JS code that runs in that VM and can inspect memory and processes, intercept functions, create our own native functions etc.</p>

<p>I’m testing with a Jailbroken iPhone 5S running iOS12.4.3, with Frida installed from Cydia. It's possible to use a non-jailbroken device if you can include Frida’s libraries in the app - either via debugging an app you own, or repackaging someone else’s app and injecting the dylib.</p>

<p>As I want to decrypt Safari’s traffic I’m sticking with the Jailbroken phone.</p>

<ul>
<li>Follow the Frida installation instructions for MacOS and iOS</li>
</ul>


<p>MacOS CLI - <a href="https://frida.re/docs/installation/">https://frida.re/docs/installation/</a></p>

<p>iOS Device - <a href="https://frida.re/docs/ios">https://frida.re/docs/ios</a></p>

<ul>
<li>Check Frida is installed and working by listing the currently running apps</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>frida-ps -Ua
</span></code></pre></div></figure>


<p><code>-U</code> option instructs Frida to attach to a USB device</p>

<ul>
<li>In one terminal window start the frida script:</li>
</ul>


<p>The script for extracting the keys is hosted on Frida Code Share, I’ll walk through the process of how it works later in the post.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>frida -U -n com.apple.WebKit.Networking --codeshare andydavies/ios-tls-keylogger -o bbc-news.keylog
</span></code></pre></div></figure>


<p>The <code>-o</code> option writes the output of the script to a file, but it's also mirrored to the console too so you can see the output as it happens too.</p>

<p>Safari’s networking happens in a separate process so the command above connects to that process rather than Safari itself.</p>

<p>Also the first time you use a script from codeshare you'll be prompted whether to trust it.</p>

<p>If you want to inspect a different app <code>frida-ps -Uai</code> will list many of the apps available, and you can also select the app via Process ID too.</p>

<ul>
<li>In a second window start the TCP capture</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>tcpdump -i rvi0 -w bbc-news.pcap -P
</span></code></pre></div></figure>


<ul>
<li>Open the app you want to decrypt the traffic from and generate some traffic</li>
</ul>


<p>In this example I’m using Safari and <a href="https://www.bbc.co.uk/news">https://www.bbc.co.uk/news</a></p>

<ul>
<li>Terminate the tcpdump, and frida commands, and then use <code>exit</code> to quit the Frida REPL</li>
</ul>


<p>You should have two files, in this example they will be <code>bbc-news.pcap</code> and <code>bbc-news.keylog</code></p>

<ul>
<li>Open the packet capture and provide the keylog as an option</li>
</ul>


<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>wireshark -r bbc-news.pcap -o tls:keylog_file:bbc-news.keylog
</span></code></pre></div></figure>


<p>You can also launch Wireshark, open the packet capture, and then specify the keylog in <code>Preferences &gt; Protocols &gt; TLS &gt; (Pre)-Master-Secret log filename</code></p>

<p>You should see a screen something like this:</p>

<p><img src="https://andydavies.me/blog/images/capturing-and-decrypting-https-traffic-from-ios-apps/decrypted-pcap.png" alt="Wireshark showing a decrypted packet capture for bbc.co.uk/news" /></p>

<p>I’ve filtered the capture to just display HTTP and HTTPS traffic and highlighted the start of one of the decrypted HTTP/2 connections.</p>

<h1>How the Frida Script Works</h1>

<p>I first came across Frida a few years when someone shared Tom Curran and Marat Nigmatullin's paper on <a href="http://www.delaat.net/rp/2015-2016/p52/report.pdf">TLS Session Key Extraction from Memory on iOS Devices</a>.</p>

<p>Tom and Marat used Frida to hook the CoreTLS function (<code>tls_handshake_internal_prf</code>) that generated key material and dump the relevant TLS keys.</p>

<p>Although I got a <a href="https://gist.github.com/andydavies/451c038c2f45cf3397cd17d9273b500e">modified version of their code</a> working well enough to inspect some apps, and <a href="https://www.slideshare.net/AndyDavies/inspecting-ios-app-traffic-with-javascript-jsoxford-jan-2018">talk about it at JSOxford</a> I never quite managed to address extracting keys for resumed TLS sessions and some other cases where it didn’t work.</p>

<p>In iOS11, Apple migrated from OpenSSL to Google’s <a href="https://boringssl.googlesource.com">BoringSSL</a> so Tom and Marat’s code stopped working but the ideas it introduced remained valid and compared to OpenSSL the BoringSSL code easier to understand.</p>

<p>And as Chrome supports TLS key logging so BoringSSL already contains code to log TLS keys.</p>

<p>Searching the code for the <a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">labels that are defined in key log format</a> finds examples like this one in handshake.cc where the CLIENT_RANDOM values are being logged:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='c'><span class='line'>  <span class="c1">// Log the master secret, if logging is enabled.</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">ssl_log_secret</span><span class="p">(</span><span class="n">ssl</span><span class="p">,</span> <span class="s">&quot;CLIENT_RANDOM&quot;</span><span class="p">,</span> <span class="n">session</span><span class="o">-&gt;</span><span class="n">master_key</span><span class="p">,</span>
</span><span class='line'>                      <span class="n">session</span><span class="o">-&gt;</span><span class="n">master_key_length</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span></code></pre></div></figure>


<p>ssl_log_secret is defined in ssl_lib.c – it checks whether a callback function for logging is defined and if it is, builds the log line and calls the callback with log line.</p>

<figure class='code'><figcaption><span>ssl_lib.c</span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='c'><span class='line'><span class="kt">int</span> <span class="nf">ssl_log_secret</span><span class="p">(</span><span class="k">const</span> <span class="n">SSL</span> <span class="o">*</span><span class="n">ssl</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">label</span><span class="p">,</span> <span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">secret</span><span class="p">,</span>
</span><span class='line'>                   <span class="kt">size_t</span> <span class="n">secret_len</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="n">ssl</span><span class="o">-&gt;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">keylog_callback</span> <span class="o">==</span> <span class="nb">NULL</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>    <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">ScopedCBB</span> <span class="n">cbb</span><span class="p">;</span>
</span><span class='line'>  <span class="kt">uint8_t</span> <span class="o">*</span><span class="n">out</span><span class="p">;</span>
</span><span class='line'>  <span class="kt">size_t</span> <span class="n">out_len</span><span class="p">;</span>
</span><span class='line'>  <span class="k">if</span> <span class="p">(</span><span class="o">!</span><span class="n">CBB_init</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">strlen</span><span class="p">(</span><span class="n">label</span><span class="p">)</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">+</span> <span class="n">SSL3_RANDOM_SIZE</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span> <span class="o">+</span>
</span><span class='line'>                          <span class="n">secret_len</span> <span class="o">*</span> <span class="mi">2</span> <span class="o">+</span> <span class="mi">1</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">CBB_add_bytes</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span><span class="n">label</span><span class="p">,</span> <span class="n">strlen</span><span class="p">(</span><span class="n">label</span><span class="p">))</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">CBB_add_bytes</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span><span class="s">&quot; &quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">cbb_add_hex</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">ssl</span><span class="o">-&gt;</span><span class="n">s3</span><span class="o">-&gt;</span><span class="n">client_random</span><span class="p">,</span> <span class="n">SSL3_RANDOM_SIZE</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">CBB_add_bytes</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="p">(</span><span class="k">const</span> <span class="kt">uint8_t</span> <span class="o">*</span><span class="p">)</span><span class="s">&quot; &quot;</span><span class="p">,</span> <span class="mi">1</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">cbb_add_hex</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="n">secret</span><span class="p">,</span> <span class="n">secret_len</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">CBB_add_u8</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="mi">0</span> <span class="cm">/* NUL */</span><span class="p">)</span> <span class="o">||</span>
</span><span class='line'>      <span class="o">!</span><span class="n">CBB_finish</span><span class="p">(</span><span class="n">cbb</span><span class="p">.</span><span class="n">get</span><span class="p">(),</span> <span class="o">&amp;</span><span class="n">out</span><span class="p">,</span> <span class="o">&amp;</span><span class="n">out_len</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>
</span><span class='line'>    <span class="k">return</span> <span class="mi">0</span><span class="p">;</span>
</span><span class='line'>  <span class="p">}</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">ssl</span><span class="o">-&gt;</span><span class="n">ctx</span><span class="o">-&gt;</span><span class="n">keylog_callback</span><span class="p">(</span><span class="n">ssl</span><span class="p">,</span> <span class="p">(</span><span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="p">)</span><span class="n">out</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="n">OPENSSL_free</span><span class="p">(</span><span class="n">out</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>  <span class="k">return</span> <span class="mi">1</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<p>To enable TLS key logging, it appears we just need to be able to set a logging callback function.</p>

<p>The logging callback is set via <code>SSL_CTX_set_keylog_callback</code> but unfortunately this doesn’t seem to be included in Apple’s version of BoringSSL – to check, I extracted libboringssl.dylib from the shared dylib cache and disassembled it using <a href="https://www.hopperapp.com">Hopper</a> but couldn’t find the function.</p>

<figure class='code'><figcaption><span>ssl_lib.c \</span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='c'><span class='line'><span class="kt">void</span> <span class="nf">SSL_CTX_set_keylog_callback</span><span class="p">(</span><span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span>
</span><span class='line'>                                 <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">cb</span><span class="p">)(</span><span class="k">const</span> <span class="n">SSL</span> <span class="o">*</span><span class="n">ssl</span><span class="p">,</span> <span class="k">const</span> <span class="kt">char</span> <span class="o">*</span><span class="n">line</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>  <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">keylog_callback</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<p>Reading the source code and tracing the execution of <code>com.apple.WebKit.Networking</code> using frida-trace, I came across <code>SSL_CTX_set_info_callback</code></p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='bash'><span class='line'>frida-trace -U com.apple.WebKit.Networking -I <span class="s1">&#39;libboringssl.dylib&#39;</span>
</span></code></pre></div></figure>


<p><code>SSL_CTX_set_info_callback</code> appears to be called once per task and gets passed the address of the struct containing the pointer to logging callback function:</p>

<figure class='code'><figcaption><span>ssl_session.c</span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='c'><span class='line'><span class="kt">void</span> <span class="nf">SSL_CTX_set_info_callback</span><span class="p">(</span>
</span><span class='line'>    <span class="n">SSL_CTX</span> <span class="o">*</span><span class="n">ctx</span><span class="p">,</span> <span class="kt">void</span> <span class="p">(</span><span class="o">*</span><span class="n">cb</span><span class="p">)(</span><span class="k">const</span> <span class="n">SSL</span> <span class="o">*</span><span class="n">ssl</span><span class="p">,</span> <span class="kt">int</span> <span class="n">type</span><span class="p">,</span> <span class="kt">int</span> <span class="n">value</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>  <span class="n">ctx</span><span class="o">-&gt;</span><span class="n">info_callback</span> <span class="o">=</span> <span class="n">cb</span><span class="p">;</span>
</span><span class='line'><span class="p">}</span>
</span></code></pre></div></figure>


<p>If we create our own logging function, and wrap it in a native callback</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="c1">// Logging function, reads null terminated string from address in line</span>
</span><span class='line'><span class="kd">function</span> <span class="nx">key_logger</span><span class="p">(</span><span class="nx">ssl</span><span class="p">,</span> <span class="nx">line</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>   <span class="nx">console</span><span class="p">.</span><span class="nx">log</span><span class="p">(</span><span class="k">new</span> <span class="nx">NativePointer</span><span class="p">(</span><span class="nx">line</span><span class="p">).</span><span class="nx">readCString</span><span class="p">());</span>
</span><span class='line'><span class="p">}</span>
</span><span class='line'>
</span><span class='line'><span class="c1">// Wrap key_logger JS function in NativeCallback</span>
</span><span class='line'><span class="kd">var</span> <span class="nx">key_log_callback</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">NativeCallback</span><span class="p">(</span><span class="nx">key_logger</span><span class="p">,</span> <span class="s1">&#39;void&#39;</span><span class="p">,</span> <span class="p">[</span><span class="s1">&#39;pointer&#39;</span><span class="p">,</span> <span class="s1">&#39;pointer&#39;</span><span class="p">]);</span>
</span></code></pre></div></figure>


<p>We can then intercept calls to ‘SSL_CTX_set_info_callback` and write the address of the native callback created above into the relevant entry in the SSL struct:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="kd">var</span> <span class="nx">CALLBACK_OFFSET</span> <span class="o">=</span> <span class="mh">0x2A8</span><span class="p">;</span>
</span><span class='line'>
</span><span class='line'><span class="kd">var</span> <span class="nx">SSL_CTX_set_info_callback</span> <span class="o">=</span> <span class="nx">Module</span><span class="p">.</span><span class="nx">findExportByName</span><span class="p">(</span><span class="s2">&quot;libboringssl.dylib&quot;</span><span class="p">,</span> <span class="s2">&quot;SSL_CTX_set_info_callback&quot;</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'><span class="nx">Interceptor</span><span class="p">.</span><span class="nx">attach</span><span class="p">(</span><span class="nx">SSL_CTX_set_info_callback</span><span class="p">,</span> <span class="p">{</span>
</span><span class='line'>   <span class="nx">onEnter</span><span class="o">:</span> <span class="kd">function</span> <span class="p">(</span><span class="nx">args</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="kd">var</span> <span class="nx">ssl</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">NativePointer</span><span class="p">(</span><span class="nx">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]);</span>
</span><span class='line'>       <span class="kd">var</span> <span class="nx">callback</span> <span class="o">=</span> <span class="k">new</span> <span class="nx">NativePointer</span><span class="p">(</span><span class="nx">ssl</span><span class="p">).</span><span class="nx">add</span><span class="p">(</span><span class="nx">CALLBACK_OFFSET</span><span class="p">);</span>
</span><span class='line'>
</span><span class='line'>       <span class="nx">callback</span><span class="p">.</span><span class="nx">writePointer</span><span class="p">(</span><span class="nx">key_log_callback</span><span class="p">);</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'><span class="p">});</span>
</span></code></pre></div></figure>


<p><code>CALLBACK_OFFSET</code> was determined by disassembling libboringssl.dylib, and like all magic numbers is fragile as it may change if the struct changes in future versions, or on different CPU architectures.</p>

<p>The completed code (TBH it’s more comments than code) is available from <a href="https://codeshare.frida.re/@andydavies/ios-tls-keylogger/">https://codeshare.frida.re/@andydavies/ios-tls-keylogger/</a> under an MIT License so feel free to build on it, incorporate it into other utilities etc.</p>

<h1>Further Reading</h1>

<p><a href="http://www.moserware.com/2009/06/first-few-milliseconds-of-https.html">The First Few Milliseconds of an HTTPS Connection</a></p>

<p><a href="https://developer.apple.com/library/content/qa/qa1176/_index.html">Technical Q&amp;A QA1176 Getting a Packet Trace</a></p>

<p><a href="https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSS/Key_Log_Format">NSS Key Log Format</a></p>

<p><a href="http://www.delaat.net/rp/2015-2016/p52/report.pdf">TLS Session Key Extraction from Memory on iOS Devices</a></p>

<p><a href="https://www.slideshare.net/AndyDavies/inspecting-ios-app-traffic-with-javascript-jsoxford-jan-2018">Inspecting iOS App Traffic with JavaScript - JSOxford - Jan 2018</a></p>

<p><a href="https://boringssl.googlesource.com">BoringSSL</a></p>

<p><a href="https://www.frida.re/">Frida</a></p>

<p><a href="https://www.hopperapp.com">Hopper Disassembler</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Experimenting With Link Rel=preconnect Using Custom Script Injection in WebPageTest]]></title>
    <link href="https://andydavies.me/blog/2019/08/07/experimenting-with-link-rel-equals-preconnect-using-custom-script-injection-in-webpagetest/"/>
    <updated>2019-08-07T12:23:44+01:00</updated>
    <id>https://andydavies.me/blog/2019/08/07/experimenting-with-link-rel-equals-preconnect-using-custom-script-injection-in-webpagetest</id>
    <content type="html"><![CDATA[<p>The preconnect <a href="https://www.w3.org/TR/resource-hints/">Resource Hint</a> is a great way to speed up content that comes from third-party origins – it’s got relatively low overhead (though it’s not completely free) and is generally easy to implement.</p>

<p>Sometimes it produces small improvements, and <a href="https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header/">sometimes more dramatic ones</a>!</p>

<p>Browsers typically only make a connection to an origin just before they request a resource from it, so when resources are discovered late such as CSS background images, fonts, or script injected resources, or resources are considered a low priority, as Chrome does with images, the delay in making the connection becomes part of the critical path for that resource.</p>

<!--more-->


<p>Preconnect enables us to create the connection in advance – removing it from the critical path for a resource, allowing the resource to be loaded sooner and hopefully improving the overall performance of a page too.</p>

<p>Implementing preconnect is often one of the first improvements I get clients to action but I’ve never been completely happy with my process…</p>

<p><a href="https://developers.google.com/web/tools/lighthouse/">Lighthouse</a> offers some recommendations on which origins to preconnect to, but I tend to use a combination of WebPageTest and DevTools to identify candidates.</p>

<p>I make recommendations to the client I’m working with, their development team implement the preconnect directives and then we typically check the effect in a pre-production environment and adjust as necessary.</p>

<p>If I’m sat with the development team these cycles can be quick, but if the client’s using an external development team, or the team’s offshore they can be long.</p>

<p>What I wanted was a way to experiment, evaluate the options and demonstrate the gains before a client commits my recommendations to code.</p>

<p>Then I remembered WebPageTest has the ability to inject a custom script into the page being tested… I could create a script that adds preconnect directives and see what effect different options have on page speed.</p>

<h1>Injecting the Script</h1>

<p>At the bottom of the <strong>Advanced Tab</strong> there’s a text box labelled <strong>Inject Script</strong>, any script placed in here will be injected into the page shortly after it starts loading.</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/wpt-advanced-settings.png" alt="WebPageTest's Advanced Settings Panel showing the Inject Script text box" /></p>

<p>The script I use to create the <code>&lt;link rel=preconnect</code> elements loops around an array of origins, adds a link element for each to a document fragment, and then adds the fragment to the DOM.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'><span class="p">(</span><span class="kd">function</span> <span class="p">()</span> <span class="p">{</span>
</span><span class='line'>   <span class="kd">var</span> <span class="nx">entries</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://res.cloudinary.com&#39;</span><span class="p">}</span>
</span><span class='line'>   <span class="p">];</span>
</span><span class='line'>
</span><span class='line'>   <span class="kd">var</span> <span class="nx">fragment</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createDocumentFragment</span><span class="p">();</span>
</span><span class='line'>   <span class="k">for</span><span class="p">(</span><span class="nx">entry</span> <span class="nx">of</span> <span class="nx">entries</span><span class="p">)</span> <span class="p">{</span>
</span><span class='line'>       <span class="kd">var</span> <span class="nx">link</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">createElement</span><span class="p">(</span><span class="s1">&#39;link&#39;</span><span class="p">);</span>
</span><span class='line'>       <span class="nx">link</span><span class="p">.</span><span class="nx">rel</span> <span class="o">=</span> <span class="s1">&#39;preconnect&#39;</span><span class="p">;</span>
</span><span class='line'>       <span class="nx">link</span><span class="p">.</span><span class="nx">href</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">href</span><span class="p">;</span>
</span><span class='line'>       <span class="k">if</span><span class="p">(</span><span class="nx">entry</span><span class="p">.</span><span class="nx">hasOwnProperty</span><span class="p">(</span><span class="s1">&#39;crossOrigin&#39;</span><span class="p">))</span> <span class="p">{</span>
</span><span class='line'>               <span class="nx">link</span><span class="p">.</span><span class="nx">crossOrigin</span> <span class="o">=</span> <span class="nx">entry</span><span class="p">.</span><span class="nx">crossOrigin</span><span class="p">;</span>
</span><span class='line'>       <span class="p">}</span>
</span><span class='line'>       <span class="nx">fragment</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">link</span><span class="p">);</span>
</span><span class='line'>   <span class="p">}</span>
</span><span class='line'>   <span class="nb">document</span><span class="p">.</span><span class="nx">head</span><span class="p">.</span><span class="nx">appendChild</span><span class="p">(</span><span class="nx">fragment</span><span class="p">);</span>
</span><span class='line'>   <span class="nx">performance</span><span class="p">.</span><span class="nx">mark</span><span class="p">(</span><span class="s1">&#39;wpt.injectEnd&#39;</span><span class="p">);</span> <span class="c1">// Not essential </span>
</span><span class='line'><span class="p">})();</span>
</span></code></pre></div></figure>


<p>More origins can be added to the entries array as needed, and if an origin serves fonts the <code>crossOrigin: 'anonymous'</code> property should be added too.</p>

<p>For example, if both your images and fonts were hosted on Cloudinary, the first entry creates a connection for the images, and the second entry a connection for the fonts.</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'>   <span class="kd">var</span> <span class="nx">entries</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://res.cloudinary.com&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://res.cloudinary.com&#39;</span><span class="p">,</span> <span class="s1">&#39;crossOrigin&#39;</span><span class="o">:</span> <span class="s1">&#39;anonymous&#39;</span><span class="p">}</span>
</span><span class='line'>   <span class="p">];</span>
</span></code></pre></div></figure>


<p>I’ve wrapped the script in an IIFE to limit the scope of its variables, and avoid clashes with any existing ones in the page.</p>

<p>When Pat introduced script injection he described it as ‘a bit racey’ i.e. you can’t exactly be sure when it’s going to execute, so I include a performance mark to record when the script finishes execution.</p>

<p>Also when there’s no evidence of preconnects improving performance, the mark is a good sanity check that I actually remembered to include the custom script!</p>

<h1>Putting it into Action!</h1>

<p>So what difference can preconnect make?</p>

<p>I used the HTTP Archive to find a couple of sites that use Cloudinary for their images, and tested them unchanged, and then with the preconnect script injected.</p>

<p>Each test consisted of nine runs, using Chrome emulating a mobile device, and the Cable network profile.</p>

<p>There’s a noticeable visual improvement in the first site (<a href="https://www.digitaladventures.com/">https://www.digitaladventures.com/</a>), with the main background image loading over half a second sooner (top) than on the unchanged site (bottom).</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/digitaladventures-filmstrip.png" alt="Filmstrip comparing the perfomance of Digital Adventures, with (top) and without (bottom) a preconnect to Cloudinary" />
Top row site with preconnect, bottom row site without</p>

<p>Comparing the waterfalls, the gap between the end of the creating the network connection and the start of request for the image shows the preconnect has encouraged Chrome to connect sooner than it would have by default.</p>

<p>And by removing the network connection setup from the critical path Chrome can start to fetch the images sooner too.</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/digitaladventures-preconnect.png" alt="Waterfall showing preconnect to Cloudinary" />
With Preconnect to Cloudinary</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/digitaladventures-before.png" alt="Waterfall without preconnect to Cloudinary" />
Without Preconnect to Cloudinary</p>

<p>You may also notice the connection setup is faster in the test with the preconnect, this is something I often see and suspect it’s due to network contention being lower at the start of the page load.</p>

<p>If you’d like to examine the results yourself, here’s a link to the comparison view, clicking on the labels to the left of the filmstrip will take you to the median run for each test:</p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=190807_MR_41322698cc9e8666e96fe0a856204dbe,190807_AD_d1e711f0158521f9f872f86890508711">https://www.webpagetest.org/video/compare.php?tests=190807_MR_41322698cc9e8666e96fe0a856204dbe,190807_AD_d1e711f0158521f9f872f86890508711</a></p>

<p>For the second site (<a href="https://hydeparkpicturehouse.co.uk/">https://hydeparkpicturehouse.co.uk/</a>), there’s very little difference between the two tests – there's a small difference near the start of the filmstrip due to the server responding faster in the preconnect test (the menu bar at the bottom of the page appears sooner).</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/hydeparkpicturehouse-filmstrip.png" alt="Filmstrip comparing the perfomance of Hyde Park Picturehouse" />
Top row site with preconnect, bottom row site without</p>

<p>But the overall preconnect isn’t having any effect as once Chrome has discovered the image it’s immediately prioritising the request for it.</p>

<p>In the preconnect waterfall (top) you’ll see the Chrome starts creating the connection before the script to inject the preconnect has even finished executing – the purple vertical bar for the timing mark is after the DNS lookup for Cloudinary.</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/hydeparkpicturehouse-preconnect.png" alt="Waterfall showing preconnect to Cloudinary" />
With Preconnect to Cloudinary</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/hydeparkpicturehouse-before.png" alt="Waterfall without preconnect to Cloudinary" />
Without Preconnect to Cloudinary</p>

<p>Again, here’s the comparison between the two tests if you want to explore further:
<a href="https://www.webpagetest.org/video/compare.php?tests=190807_CP_c4385658104d23097b2084d15f7b3903,190807_0N_6d3eed6797303b7a4243e00335159125">https://www.webpagetest.org/video/compare.php?tests=190807_CP_c4385658104d23097b2084d15f7b3903,190807_0N_6d3eed6797303b7a4243e00335159125</a></p>

<p>There other options to improve the performance of both the sites tested and if these were implemented the preconnect may become more or less important – testing is the key thing!</p>

<h1>Use Preconnect Selectively</h1>

<p>Given the half a second improvement in the first test, you might be tempted to "preconnect all the things!", but I’d encourage you not to – well at least without testing thoroughly.</p>

<p>Browsers have limits on the number of concurrent DNS requests they can make, and creating a new HTTP connection may require certificates to be fetched, and these certificates will compete with other perhaps more critical resources for the network connection</p>

<p>Even without the TLS certificate overhead, adding extra preconnects may actually make things slower as it can change the order in which resources are retrieved, which can increase  competition for the network or the browser’s main thread.</p>

<p>I re-ran the test for <a href="https://www.digitaladventures.com/">https://www.digitaladventures.com/</a> with a few more preconnects added:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='js'><span class='line'>   <span class="kd">var</span> <span class="nx">entries</span> <span class="o">=</span> <span class="p">[</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://res.cloudinary.com&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://connect.facebook.net&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://www.google-analytics.com&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://cdnjs.cloudflare.com&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://client.crisp.chat&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://www.googleadservices.com&#39;</span><span class="p">},</span>
</span><span class='line'>       <span class="p">{</span><span class="s1">&#39;href&#39;</span><span class="o">:</span> <span class="s1">&#39;https://www.gstatic.com&#39;</span><span class="p">}</span>
</span><span class='line'>   <span class="p">];</span>
</span></code></pre></div></figure>


<p>And the resulting test (top) was marginally slower than the test with a single preconnect (bottom) – in this case across multiple tests the background image was about 100ms slower rendering but I’ve seen worse examples.</p>

<p><img src="https://andydavies.me/blog/images/measuring-the-benefits-of-link-rel-preconnect-using-webpagetests-custom-script-injection/digitaladventures-toomanypreconnects-filmstrip.png" alt="Filmstrip showing effect of too many preconnects" />
Top row site with many preconnects, bottom row site with only one</p>

<p>Again, here’s the comparison between the two tests if you want to explore further: <a href="https://www.webpagetest.org/video/compare.php?tests=190807_2B_7f2d2f22147f6207e64bf04ce7e06f1a,190807_MR_41322698cc9e8666e96fe0a856204dbe">https://www.webpagetest.org/video/compare.php?tests=190807_2B_7f2d2f22147f6207e64bf04ce7e06f1a,190807_MR_41322698cc9e8666e96fe0a856204dbe</a></p>

<p>We could refine the preconnect list to identify which one actually make the page faster, and which make it slower but I’ll leave that as an exercise if you want to have a play.</p>

<h1>Closing Thoughts</h1>

<p>In this post I’ve focused on two sites that use Cloudinary but I’d expect to see similar results with any site that hosts their images using a third-party such as Imigix, Kraken.io, Cloudfront etc.</p>

<p>And from what I’ve seen so far, I think many sites that host their images on third-party services would see improved performance if <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=317774#c23">browsers automatically connected earlier</a> but I’ve got to finish analysing the data from the 2,000 tests I ran over the weekend before I can put more detail on that.</p>

<p>The case for automatically pre-connecting for non-image resources is probably a little more fuzzy – render blocking resources tend to be requested at high priority so there’s no delay in creating the connection, and even for lower priority resources such as async scripts, downloading them sooner means the browser will try to execute them sooner which may or may not be a good thing.</p>

<p>Preconnect is a great feature but due to the complexities of how browsers prioritise resources, and our habit of building pages where resources are split across origins it’s not always easy to get right.</p>

<p>Using script injection in WebPageTest enables me to experiment, to explore the complexities of which origins to preconnect too and which ones to avoid, and evaluate the results before I get clients to start changing their code.</p>

<p>And that’s a good thing!</p>

<h1>Further Reading</h1>

<p><a href="https://www.w3.org/TR/resource-hints/">Resource Hints</a></p>

<p><a href="https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header/">Improving Perceived Performance With the Link Rel=preconnect HTTP Header</a></p>

<p><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=317774#c23">Issue 317774: Chrome should pre-connect for resources discovered by the preload scanner</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Three Ways of Checking Rel=preconnect Resource Hints Are Working]]></title>
    <link href="https://andydavies.me/blog/2019/04/17/three-ways-of-checking-your-rel-equals-preconnect-resource-hints-are-working/"/>
    <updated>2019-04-17T15:29:54+01:00</updated>
    <id>https://andydavies.me/blog/2019/04/17/three-ways-of-checking-your-rel-equals-preconnect-resource-hints-are-working</id>
    <content type="html"><![CDATA[<p>After explaining the preconnect Resource Hint to clients or workshop attendees, I often get asked “How can I check it’s working?”</p>

<p>Here's a few ways of checking:</p>

<!--more-->


<h1>Safari Inspector</h1>

<p>Safari’s Inspector displays a console message when it successfully preconnects to another origin. I find this the fastest and simplest way of checking whether preconnects are working:</p>

<ul>
<li><p>Navigate to a page that uses preconnect e.g. https://andydavies.github.io/test-rel-preconnect/tests/preconnect.html</p></li>
<li><p>Open Safari Inspector</p></li>
<li><p>Switch to Console Tab, ensure the option for <em>All</em> messages is selected (top right)</p></li>
<li><p>If the preconnect worked then there should be a message, in this case <em>Successfully connected to "https://www.wikipedia.org/"</em></p></li>
</ul>


<p><img src="https://andydavies.me/blog/images/testing-rel-preconnect/safari-inspector.png" alt="Console Tab in Safari Inspector showing &quot;Successfully connected to https://www.wikipedia.org/" /></p>

<h1>WebPageTest</h1>

<p>For other browsers WebPageTest is next on my list of choices:</p>

<ul>
<li><p>Enter the page to be tested as the URL e.g. https://andydavies.github.io/test-rel-preconnect/tests/preconnect.html</p></li>
<li><p>Select the location / browser you want use for the test</p></li>
<li><p>Hit Submit (and wait for the result)</p></li>
</ul>


<h2>Chrome</h2>

<p>In the waterfall below, request #3 has two distinct parts – the section with DNS resolution, TCP connection and TLS negotiation, and the section with the request and response.</p>

<p>This is preconnect at work, Chrome has made the connection to Wikipedia before it's discovered it needs to fetch the image from Wikipedia.</p>

<p><img src="https://andydavies.me/blog/images/testing-rel-preconnect/chrome-waterfall.png" alt="WebPageTest waterfall illustrating Chrome preconnecting to https://www.wikipedia.org/" /></p>

<h2>iOS Safari</h2>

<p>The iOS Safari waterfall is slightly different as WebPageTest can't  gather the same level of detail from iOS Safari as it can from Chrome.</p>

<p>In this waterfall the whole DNS resolution, TCP connection and TLS negotiation segment are missing from request #3, and that's our clue that preconnect worked in this case.</p>

<p><img src="https://andydavies.me/blog/images/testing-rel-preconnect/ios-waterfall.png" alt="WebPageTest waterfall illustrating iOS Safari preconnecting to https://www.wikipedia.org/" /></p>

<h1>Chrome's NetLog</h1>

<p>If you’re not on a Mac and the pages you want to check aren’t publicly accessible (or the WebPageTest wait is too long) then Chrome’s netlog is the third option - it's slightly more involved and so I tend to use this option the least.</p>

<ul>
<li><p>Open a new Chrome Window (I tend to use Canary for this)</p></li>
<li><p>Enter <em>chrome://net-export</em> in the URL bar, Click <em>Start Logging to Disk</em>, and select the name / location for the netlog to be saved (mine defaults to chrome-net-export-log.json in Downloads)</p></li>
<li><p>Open a new Tab, and load the page you're interested in e.g. https://andydavies.github.io/test-rel-preconnect/tests/preconnect.html</p></li>
<li><p>Switch back to the <em>chrome://net-export tab</em>, and click <em>Stop Logging</em></p></li>
<li><p>Navigate to https://netlog-viewer.appspot.com/</p></li>
<li><p>Choose <em>Import</em>, and select the file you specified above. Once the netlog has loaded and you'll see a page with a some metadata describing the capture</p></li>
<li><p>Switch to the <em>Events</em> view and you should see a list of events similar to the one below</p></li>
</ul>


<p><img src="https://andydavies.me/blog/images/testing-rel-preconnect/netlog-viewer-1.png" alt="Chrome Netlog viewer showing events" /></p>

<ul>
<li>Scroll down, until you'll see a Source Type of HTTP_STREAM_JOB_CONTROLLER for the origin you were expecting the Chrome to preconnect to.</li>
</ul>


<p>In my experience there's often a block of four events with the source types HTTP_STREAM_JOB_CONTROLLER, HTTP_STREAM_JOB, SSL_CONNECT_JOB (if HTTPS), and SOCKET.</p>

<p><img src="https://andydavies.me/blog/images/testing-rel-preconnect/netlog-viewer-2.png" alt="Chrome Netlog viewer showing events" /></p>

<ul>
<li>Selecting the event with source type of HTTP_STREAM_JOB_CONTROLLER brings up a longer description in the right hand panel, it should be similar to the one below</li>
</ul>


<p>Line #5 is the signal that it was the preconnect</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>243: HTTP_STREAM_JOB_CONTROLLER
</span><span class='line'>https://www.wikipedia.org/
</span><span class='line'>Start Time: 2019-04-17 14:59:20.511
</span><span class='line'>t=3523 [st=0] +HTTP_STREAM_JOB_CONTROLLER  [dt=1]
</span><span class='line'>               --&gt; is_preconnect = true
</span><span class='line'>               --&gt; url = "https://www.wikipedia.org/"
</span><span class='line'>t=3523 [st=0]   +PROXY_RESOLUTION_SERVICE  [dt=0]
</span><span class='line'>t=3523 [st=0]      PROXY_RESOLUTION_SERVICE_RESOLVED_PROXY_LIST
</span><span class='line'>                   --&gt; pac_string = "DIRECT"
</span><span class='line'>t=3523 [st=0]   -PROXY_RESOLUTION_SERVICE
</span><span class='line'>t=3523 [st=0]    HTTP_STREAM_JOB_CONTROLLER_PROXY_SERVER_RESOLVED
</span><span class='line'>                 --&gt; proxy_server = "DIRECT"
</span><span class='line'>t=3523 [st=0]    HTTP_STREAM_REQUEST_STARTED_JOB
</span><span class='line'>                --&gt; source_dependency = 244 (HTTP_STREAM_JOB)
</span><span class='line'>t=3524 [st=1] -HTTP_STREAM_JOB_CONTROLLER</span></code></pre></div></figure>


<p>And examining the HTTP_STREAM_JOB below it we can see it had an IDLE priority meaning there wasn't a request waiting for that connection.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>244: HTTP_STREAM_JOB
</span><span class='line'>https://www.wikipedia.org/
</span><span class='line'>Start Time: 2019-04-17 14:59:20.511
</span><span class='line'>t=3523 [st=0] +HTTP_STREAM_JOB  [dt=1]
</span><span class='line'>               --&gt; expect_spdy = false
</span><span class='line'>               --&gt; original_url = "https://www.wikipedia.org/"
</span><span class='line'>               --&gt; priority = "IDLE"
</span><span class='line'>              --&gt; source_dependency = 243 (HTTP_STREAM_JOB_CONTROLLER)
</span><span class='line'>               --&gt; url = "https://www.wikipedia.org/"
</span><span class='line'>               --&gt; using_quic = false
</span><span class='line'>t=3523 [st=0]    HTTP_STREAM_JOB_WAITING  [dt=1]
</span><span class='line'>                 --&gt; should_wait = false
</span><span class='line'>t=3524 [st=1]   +HTTP_STREAM_JOB_INIT_CONNECTION  [dt=0]
</span><span class='line'>t=3524 [st=1]     +HOST_RESOLVER_IMPL_REQUEST  [dt=0]
</span><span class='line'>                   --&gt; address_family = 0
</span><span class='line'>                   --&gt; allow_cached_response = true
</span><span class='line'>                   --&gt; host = "www.wikipedia.org:443"
</span><span class='line'>                   --&gt; is_speculative = false
</span><span class='line'>t=3524 [st=1]        HOST_RESOLVER_IMPL_IPV6_REACHABILITY_CHECK
</span><span class='line'>                     --&gt; cached = true
</span><span class='line'>                     --&gt; ipv6_available = false
</span><span class='line'>t=3524 [st=1]        HOST_RESOLVER_IMPL_CACHE_HIT
</span><span class='line'>                     --&gt; addresses = ["91.198.174.192"]
</span><span class='line'>                     --&gt; expiration = "13199983755512496"
</span><span class='line'>t=3524 [st=1]     -HOST_RESOLVER_IMPL_REQUEST
</span><span class='line'>t=3524 [st=1]      TCP_CLIENT_SOCKET_POOL_REQUESTED_SOCKETS
</span><span class='line'>                   --&gt; group_id = "ssl/www.wikipedia.org:443"
</span><span class='line'>t=3524 [st=1]      SOCKET_POOL_CONNECTING_N_SOCKETS  [dt=0]
</span><span class='line'>                   --&gt; num_sockets = 1
</span><span class='line'>t=3524 [st=1]   -HTTP_STREAM_JOB_INIT_CONNECTION
</span><span class='line'>t=3524 [st=1] -HTTP_STREAM_JOB</span></code></pre></div></figure>


<p>Using  netlog requires a bit of practice, it exposes what's happening in Chrome's network layer so there's a lot of information and often seemingly duplicate entries where Chrome is racing connections.</p>

<h1>Closing Thoughts</h1>

<p>Using Safari is by far the simplest way to check preconnect is working and it would be great if other browser makers made it as easy!</p>

<p>I also came across a few bumps in my tests…</p>

<ul>
<li><p>It appears that preconnect doesn't currently work in Firefox even though it's supposed to</p></li>
<li><p>I like to use a <code>rel="preconnect dns-prefetch"</code> pattern so there's a fallback for browsers that don't support preconnect. Unfortunately it appears this breaks preconnect in Safari, so I either need to drop the dns-prefetch fallback or switch to two separate statements.</p></li>
</ul>


<h1>Further Reading</h1>

<p><a href="https://www.w3.org/TR/resource-hints/">Resource Hints</a></p>

<p><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1543990">Firefox failing to preconnect</a></p>

<p><a href="https://bugs.webkit.org/show_bug.cgi?id=197010">Safari failing to preconnect when preconnect and dns-prefetch specified in same rel attribute</a></p>

<p><a href="https://github.com/andydavies/test-rel-preconnect">My collection of rel="preload" test pages</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Improving Perceived Performance With the Link Rel=preconnect HTTP Header]]></title>
    <link href="https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header/"/>
    <updated>2019-03-22T19:53:17+00:00</updated>
    <id>https://andydavies.me/blog/2019/03/22/improving-perceived-performance-with-a-link-rel-equals-preconnect-http-header</id>
    <content type="html"><![CDATA[<p>Sometimes a small change can have a huge effect…</p>

<p>Recently, a client switched from serving their product images through AWS S3 and Cloudfront to Cloudinary.</p>

<p>Although Cloudinary was delivering optimally sized and better compressed images, there was a noticeable delay before the images arrived, and some of this delay was due to the overhead of creating a connection to another origin (the delay existed with the S3 / Cloudfront combination too).</p>

<!--more-->


<p>This excerpt from a WebPageTest waterfall illustrates the problem – Chrome starts to request the product image at 900ms into the page load, and there's a further wait of 600ms for DNS to be resolved, a TCP connection to be setup and TLS to be negotiated before the HTTP GET request for the image is actually sent.</p>

<p><img src="https://andydavies.me/blog/images/rel-preload-http-header/before.png" alt="Delay caused by time to establish connection to Cloudinary" />
(WebPageTest: Chrome, Cable, London)</p>

<p>I'm not quite sure why Chrome waits so long before initiating the request – it's a normal image element so easily discoverable by <a href="https://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/">the preloader</a> – but from memory, Chrome throttles requests for resources in the body until the those in the head have been completed so perhaps that's the case here.</p>

<p>Waiting 1.7s for the main product image to appear is far from ideal so how can make the  image display sooner?</p>

<h1>Preload?</h1>

<p>The <code>rel=preload</code> Resource Hint is the first option that might spring to mind, but as <a href="https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities/">Chrome prioritises preloads above all other content</a>, and we didn't want to delay the stylesheets and scripts in the head it was discounted. (As an aside, <a href="https://twitter.com/csswizardry">Harry</a> and I eventually decided to remove the font preloads on this site too).</p>

<p>Another challenge with <code>rel=preload</code> is that the exact resource has to be specified. For resources that are common across multiple pages (stylesheets, scripts etc) that's fairly easy to implement, but where the content changes by page, and may not be consistent e.g. search results, landing pages etc., it's a bit more involved.</p>

<p>As we didn't want the overhead of <code>rel=preload</code> we looked at how the <code>rel=preconnect</code> hint might help instead.</p>

<h1>Preconnect?</h1>

<p><code>rel=preconnect</code> is often recommended for origins that have important resources but can't be discovered by the browser's preloader e.g. third-party tags that get injected via scripts, fonts from other origins etc., but as we'd already implemented preconnects for the six most important origins using the <code>link</code> element in the page I wasn't keen to add more.</p>

<p>(Generally I don't use more than six <code>rel='preconnect dns-prefetch'</code> declarations as Chrome has a limit on parallel DNS lookups – the <a href="https://twitter.com/yoavweiss/status/943507444524900357">limit was six</a>, but <a href="https://twitter.com/igrigorik/status/1024015675583389696">might be slightly different now</a>)</p>

<p>Most examples of Resource Hints show the markup based syntax but Resource Hints can also be specified via the <code>link</code> HTTP header (<a href="https://caniuse.com/#feat=link-rel-preconnect">Edge actually only supports rel=preconnect as an HTTP header</a>)</p>

<p>Implementing the hint as a header allows it to be applied across the whole site with a single configuration change, and as the hint is received in the headers the browser doesn't even need to start parsing the HTML to discover it.</p>

<p>So that's how we chose to implement preconnect:</p>

<p><code>link: &lt;https://res.cloudinary.com&gt;; rel=preconnect</code></p>

<p>The impact on the waterfall is pretty dramatic – Chrome starts establishing the connection as soon as it receives the initial chunk of the response for the page. This removes the connection overhead from the critical path for the image, and even though the request for the image still isn't made until 900ms has elapsed, it completes at 1.2s, a whole 500ms improvement!</p>

<p><img src="https://andydavies.me/blog/images/rel-preload-http-header/after.png" alt="Using rel=&quot;preconnect&quot; to remove the connection delay" />
(WebPageTest: Chrome, Cable, London)</p>

<p>The visual improvement is just as dramatic (and we've since improved the rendering of the product images by another 300ms)</p>

<p><img src="https://andydavies.me/blog/images/rel-preload-http-header/filmstrip.png" alt="Comparison demonstrating product images loading sooner with preconnect" /></p>

<h1>Real-World Performance</h1>

<p>WebPageTest is of course a stable test environment and it  might not represent what happens in the complexity of the real-world where there are different browsers, fast and slow devices, and varying levels of network quality.</p>

<p>Fortunately this client uses SpeedCurve LUX, and we'd already implemented a User Timing mark to <a href="https://speedcurve.com/blog/user-timing-and-custom-metrics/">track when the first product image loaded</a>. The real-world metrics showed a 400ms improvement at the median and greater than 1s improvement at the 95th percentile.</p>

<p>All-in-all a substantial improvement!</p>

<h1>Closing Thoughts</h1>

<p>There's no doubt that <code>rel=preconnect</code> makes a huge difference to how soon the product images are being displayed, and I suspect (though didn't test) similar gains could be made using its markup based variant too.</p>

<p>Looking at the complete waterfall (not included it here) it looks like there's scope for further gains if the browser had a better understanding of which images to prioritise and perhaps in the future when Priority Hints have wider support we'll experiment with an <code>importance="high"</code> hint.</p>

<p>Using <code>rel=preconnect</code> as a HTTP header offers some other interesting opportunities for improving performance – as it doesn't rely on markup being parsed preconnect can be triggered by requests for stylesheets, scripts and more.</p>

<p>Google Fonts sometimes (but not always) preconnects to fonts.gstatic.com by including <code>link: &lt;https://fonts.gstatic.com&gt;; rel=preconnect; crossorigin</code> in the stylesheet response.</p>

<p><img src="https://andydavies.me/blog/images/rel-preload-http-header/google-fonts.png" alt="Waterfall illustrating how Google Fonts uses preconnect" />
Stylesheet from fonts.googleapis.com preconnecting to fonts.gstatic.com</p>

<p>As many third-parties tags connect to other domains there are opportunities for this technique to be more widely used (though reducing the number of domains by fronting them with a single CDN domain is probably the more preferable option)</p>

<p>If you've got important content coming from another domain I'd certainly recommend experimenting with preconnect to see what benefits you can gain.</p>

<h1>References / Further Reading</h1>

<p><a href="https://caniuse.com/#feat=link-rel-preconnect">CanIUse Link: rel=preconnect</a></p>

<p><a href="https://www.w3.org/TR/resource-hints/">Resource Hints</a></p>

<p><a href="https://wicg.github.io/priority-hints/">Priority Hints proposal</a></p>

<p>Limits on number of parallel DNS requests in Chrome - <a href="https://twitter.com/yoavweiss/status/943507444524900357">Yoav</a>, <a href="https://twitter.com/igrigorik/status/1024015675583389696">Ilya</a>, <a href="https://twitter.com/addyosmani/status/1091206049623724032">Addy</a></p>

<p><a href="https://speedcurve.com/blog/user-timing-and-custom-metrics/">User Timing and Custom Metrics</a></p>

<p><a href="https://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/">How the Browser Pre-loader Makes Pages Load Faster</a></p>

<p><a href="https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities/">Preloading Fonts and the Puzzle of Priorities</a></p>

<h1>Thanks</h1>

<p>Finally I'd like to thank <a href="https://twitter.com/charliecarlton_">Charlie</a> who encouraged me to share what we learned.</p>

<p>And if you'd like some help improving the performance of your site feel free to <a href="mailto:hello@andydavies.me">get in-touch</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Preloading Fonts and the Puzzle of Priorities]]></title>
    <link href="https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities/"/>
    <updated>2019-02-12T10:15:53+00:00</updated>
    <id>https://andydavies.me/blog/2019/02/12/preloading-fonts-and-the-puzzle-of-priorities</id>
    <content type="html"><![CDATA[<p>"Consider using <code>&lt;link rel=preload&gt;</code> to prioritize fetching resources that are currently requested later in page load" is the seemingly simple advice Lighthouse gives.</p>

<p>Preload is a trade-off – when we explicitly increase the priority of one resource we implicitly decrease the priority of others – and so to work effectively preload requires authors to identify the optimal resources to preload, browsers to request them with optimal priorities and servers to deliver them in optimal order.</p>

<p>Some resources are discovered later than others, for example resources injected by a script, or background images and fonts that are only discovered when the render tree is built.</p>

<p>Preload instructs the browser to download a resource even before it discovers it but so there may be performance gains by using <code>&lt;link rel=preload</code> to download resources earlier than normal.</p>

<p>Preload also allows us to decouple download and execution, for example the <a href="https://www.filamentgroup.com/lab/async-css.html">Filament Group suggest using it to load CSS asynchronously</a></p>

<p>I’ve being using preload with clients over the last few years but I have never been completely satisfied with the results. I’ve also seen some things I hadn’t quite expected in page load waterfalls so decided to dig deeper.</p>

<!--more-->


<h1>When Should Fonts be Loaded?</h1>

<p>As most browsers delay rendering text until the relevant font is available, loading fonts sooner has become one of commonly suggested use cases for preload.</p>

<p>And based on my exploration of the HTTP Archive data it's the most frequently used case too.</p>

<p>By default fonts are loaded late as the browser only discovers them when it's building the render tree i.e. after the styles have been downloaded and any blocking scripts in the head have been downloaded and executed too.</p>

<p>The waterfall below shows the default behaviour with fonts discovered late and then having to compete with the images.</p>

<p>Ideally we want the fonts downloaded much sooner, most likely after the render blocking elements in the head – this is the point where the browser starts adding the body contents to the DOM and where the fonts will be needed.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/waterfall-default-font-behaviour.png" alt="WebPageTest waterfall showing default and ideal prioritisation of fonts loading" /></p>

<p><a href="https://www.webpagetest.org/result/190208_QH_257a4da90873f7bfb2322ea67142b76e/5/details/">Default font loading behaviour - WebPageTest, Dulles, Chrome, 3G Fast</a></p>

<p>Preloading fonts isn't the only option when it comes to reducing the 'Flash of Invisible Text', the CSS <code>font-display</code> property can be used to change the blocking behaviour but reducing the time it takes for the web fonts to replace the fallback font should still improve visual performance.</p>

<p>Although I'm focusing on fonts, many of the observations apply to the preload of other resource types too.</p>

<h1>Creating a Test Page</h1>

<p>To explore further I need a test case, something that approximates some of the pages retailers and other sites might have.</p>

<p>Rather than start from scratch I based the test case on the <a href="https://colorlib.com/wp/template/electro/">Electro ecommerce template from ColorLib</a> with a few changes:</p>

<ul>
<li>switched from Google Fonts to self-hosted ones (containing just Latin glyphs).</li>
<li>removed unused glyphs to reduce fontawesome’s size
added a performance mark to record when the fonts were loaded (using <code>document.fonts.ready</code>).</li>
<li>moved some blocking scripts from the foot of the page into the <code>head</code> element and added a script loaded with async, and another with defer attributes.</li>
</ul>


<p>The page has (at least) a few shortcomings when compared to some of the real world pages I see:</p>

<ul>
<li>only makes 35 requests:</li>
<li>is only around 1,500 lines long - which is pretty short compared to most retailers sites</li>
<li>doesn’t contain any third-party content</li>
<li>there are no long running scripts that affect interactivity</li>
</ul>


<p>The test case has four fonts – three weights of Montserrat, and an icon font – all weighing in at just under 100kB, which is in-line with the <a href="https://httparchive.org/reports/page-weight#bytesFont">median size and number of requests for fonts based on HTTP Archive data</a></p>

<p>I created several variants of the test page, and if you want to take a deeper look they’re all on GitHub complete with descriptions – <a href="https://github.com/andydavies/test-rel-preload">https://github.com/andydavies/test-rel-preload</a></p>

<p>The test pages were hosted on Github, Cloudfront, and AWS (using h2o as a server), and tested in Chrome and Canary, using WebPageTest in Dulles on both 3G Fast and Cable connections.</p>

<p>There are links to all the results on GitHub too.</p>

<h1>Default Behaviour vs Preloaded Fonts</h1>

<p>For the first test I compared fonts loaded using the default browser behaviour with a variant that uses <code>&lt;link rel=preload…</code> at the top of the head to preload each font.</p>

<p>As the filmstrip below shows, the page with default behaviour (top) starts rendering first but suffers from text that doesn’t become visible until 4.9s.</p>

<p>The page that preloads the fonts (bottom) starts rendering 0.4s later, but text is visible as soon as it starts rendering</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/filmstrip-default-vs-preload.png" alt="WebPageTest filmstrip illustrating delay caused by preloading fonts" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=190208_QH_257a4da90873f7bfb2322ea67142b76e%2C190208_SF_f13d67a1fb2a8d126cf983f5307b7865&amp;thumbSize=200&amp;ival=100&amp;end=visual">Comparison with and without preloaded fonts, WebPageTest, Dulles, Chrome, 3G Fast</a></p>

<p>The waterfall for the page with preloaded fonts illustrates why it’s slower to start rendering – although the fonts are given a medium priority, the requests for them are dispatched immediately, before the requests for the higher priority stylesheets and then the responses for the fonts delay the stylesheets.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/waterfall-preload-fonts-blocking-css.png" alt="WebPageTest waterfall illustrating preloading fonts delaying CSS" /></p>

<p>WebPageTest's connection view condenses all the waterfall rows to give a simpler picture showing the fonts (red) are received before the styles (green).</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/connection-view-preload-fonts-blocking-css.png" alt="WebPageTest connection view illustrating preloading fonts delaying CSS" /></p>

<p>As the browser can’t do anything with the fonts until it has the stylesheets this really isn’t the behaviour we want, so why is it happening?</p>

<p>Firstly, Chrome sends the requests for the resource to be preload immediately, while the requests for the other resources wait for AppCache to be initialised – <a href="https://bugs.chromium.org/p/chromium/issues/detail?id=788757">https://bugs.chromium.org/p/chromium/issues/detail?id=788757</a> – so even moving the preload directives to the bottom of the header or into the body don’t help.</p>

<p>Then GitHub’s servers don’t reprioritise the higher priority requests above the medium priority ones and so carry on serving the fonts even when the requests for the stylesheets are received.</p>

<p>To be fair to GitHub, prioritising early requests is hard – if the connection is idle a server is going to start responding as soon as it receives a request, and it may not need interrupt that response until a request with a higher priority is received.</p>

<p>Hopefully the Chrome team will fix the AppCache delay soon but in the meantime there are some other things we can try.</p>

<h1>Switching HTTP/2 Servers</h1>

<p>Recently <a href="https://twitter.com/patmeenan">Pat Meenan</a> and I started tracking how well servers, CDNs etc., support HTTP/2 priorisation (<a href="https://github.com/andydavies/http2-prioritization-issues">https://github.com/andydavies/http2-prioritization-issues</a>) so what if we switched to a server (h2o) that’s known to prioritise well?</p>

<p>I installed h2o 2.3.0-beta1 on a t2.micro instance in AWS US-East running Ubuntu 18.04 using BBR for congestion control and qdisc set to fq.</p>

<p>Although the improvement in prioritisation now results in the stylesheets being sent before the fonts, both fonts and render blocking scripts are requested with a medium priority so the  scripts still get stuck behind the fonts.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/waterfall-preload-fonts-h2o.png" alt="WebPageTest waterfall illustrating preloading fonts delaying JS" /></p>

<p>The connection view shows the fonts (red) being loaded after styles (green) but before scripts (orange)</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/connection-view-preload-fonts-h2o.png" alt="WebPageTest connection view illustrating preloading fonts delaying JS" /></p>

<p>And a visual comparison shows no improvement, as rendering won't begin until the render blocking scripts in the head have executed.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/filmstrip-default-vs-preload-h2o.png" alt="WebPageTest filmstrip illustrating delay caused by preloading fonts" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=190208_E2_06604b7c0ec7bac81eee269b9e2fe1fc,190208_A3_383fa9a55c8152a108969593759ff081">Comparison of default font loading behaviour and preloading front using h2o as a server - WebPageTest, Dulles, Chrome, 3G Fast</a></p>

<p>NOTE: In general, the delay to rendering was smaller with a faster network connection – 100ms on Cable for the pages hosted on GitHub. But using h2o, even on a cable connection, the page with preloaded fonts was noticeably slower (0.3s) to start rendering – the bandwidth chart showed throughput stalling after the resources in the head have been delivered and at the moment I'm unsure whether this is a Chrome issue, a h2o issue, or a combination of both.</p>

<h1>Priority Hints to the Rescue?</h1>

<p>Priority Hints is recent proposal that allows authors to hint how important a resource is and it’s available behind a flag in Chrome Canary.</p>

<p>If we added <code>importance=”low”</code> to the preload elements, would it encourage Canary to prioritise the fonts at a lower priority than the blocking scripts?</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>&lt;link rel="preload" importance="low" href="fonts/Montserrat-Regular.woff2" as="font" crossorigin/&gt;</span></code></pre></div></figure>


<p>So close… and yet so far…</p>

<p>Interestingly the two earlier fonts are given a priority of lowest, and the later ones a priority of highest – maybe the browser has built enough of the DOM to discover it needs them and elevates their priority?</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/waterfall-preload-fonts-priority-hints-h2o.png" alt="WebPageTest waterfall illustrating preloading fonts and priority hints" /></p>

<p>The connection view re-enforces the importance of sending high priority requests first so they don't get stuck behind lower priority ones.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/connection-view-preload-fonts-priority-hints-h2o.png" alt="WebPageTest connection view illustrating preloading fonts delaying JS" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=190208_A6_ccff5003761644d2e70e5d0fba2b17f5%2C190211_MC_29f28778ed5cf6a8a7f8a02380dc5759&amp;thumbSize=200&amp;ival=100&amp;end=visual">Comparison of default font loading behaviour and declarative preloading using Priority Hints - WebPageTest, Dulles, Canary, 3G Fast</a></p>

<p>But as priority hints are still an experiment it's behind a flag, so even though it shows promise most visitors won't be able to take advantage of it yet.</p>

<h1>Delaying the Preload Hint</h1>

<p>As mentioned earlier, resources injected using a script are hidden from the browser until the script executes, and we can use this behaviour to delay when the browser discovers the preload hint.</p>

<p>Using the snippet below I injected the rel=preload elements into the DOM and tested with the snippet positioned both before, and after the external scripts.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>&lt;script&gt;
</span><span class='line'>  (function () {
</span><span class='line'>      var fontsToPreload = [
</span><span class='line'>          {"href": "fonts/fontawesome-webfont.woff2?v=4.7.0", "type": "font/woff2"},
</span><span class='line'>          {"href": "fonts/Montserrat-Medium.woff2", "type": "font/woff2"},
</span><span class='line'>          {"href": "fonts/Montserrat-Regular.woff2", "type": "font/woff2"},
</span><span class='line'>          {"href": "fonts/Montserrat-Bold.woff2", "type": "font/woff2"}
</span><span class='line'>      ];
</span><span class='line'>  
</span><span class='line'>      var fragment = document.createDocumentFragment();
</span><span class='line'>      for(font of fontsToPreload) {
</span><span class='line'>          var preload = document.createElement('link');
</span><span class='line'>          preload.rel = "preload";
</span><span class='line'>          preload.href = font.href;
</span><span class='line'>          preload.type = font.type;
</span><span class='line'>          preload.as = "font";
</span><span class='line'>          preload.crossOrigin = "anonymous";
</span><span class='line'>          fragment.appendChild(preload)
</span><span class='line'>      }
</span><span class='line'>      document.head.appendChild(fragment);
</span><span class='line'>  })();
</span><span class='line'>&lt;/script&gt;</span></code></pre></div></figure>


<p>(Yes… perhaps the script could do with a bit of a tidy up)</p>

<p>And finally, we have a filmstrip that hints at preload's promise!</p>

<p>The page in the bottom row has the above snippet just before the external blocking scripts, it starts rendering at the same time the default page (top), and renders text 0.3s sooner than the page the uses declarative preload statements (middle).</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/filmstrip-default-vs-preload-vs-script.png" alt="WebPageTest filmstrip comparing default font loading, with preloaded fonts and script injected preload elementillustrating delay caused by preloading fonts" /></p>

<p>The waterfall shows the fonts being loaded later.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/waterfall-js-inserted-preload.png" alt="WebPageTest waterfall illustrating inserting preload element using a script" /></p>

<p>And the connection view confirms the styles and scripts are downloaded before the fonts.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/connection-view-js-inserted-preload.png" alt="WebPageTest connection view illustrating inserting preload element using a script" /></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=190208_E2_06604b7c0ec7bac81eee269b9e2fe1fc%2C190208_A3_383fa9a55c8152a108969593759ff081%2C190212_57_1c500fb9b91c99c7a836a2f8adcd5d72&amp;thumbSize=200&amp;ival=100&amp;end=visual">Comparison of default font loading behaviour, declarative preloading and script injected preloading - WebPageTest, Dulles, Chrome, 3G Fast</a></p>

<p>It's not a perfect result, ideally I'd like to see the fonts retrieved before scripts that are async, or deferred but perhaps we have a winner?</p>

<h1>What About Other Browsers?</h1>

<p>Only Chrome and Safari currently support <code>&lt;link rel=preload…</code> and so far, all my examples have used Chrome (or Chrome Canary), so what about Safari?</p>

<p>As Inspector shows, it appears Safari prioritises the declaratively preloaded fonts appropriately – they are loaded after the stylesheets and scripts in the <code>&lt;head&gt;</code> so they’re not delaying these critical resources.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/safari-preloaded-fonts.png" alt="Safari Inspector showing prioritisation of preloaded fonts" /></p>

<p>(I checked the behaviour separately using WebPageTest, Resource Timing, and server logs too.)</p>

<p>But if as highlighted earlier, Chrome's behaviour leads to declarative preloads being downloaded too early, what about the scripted approach that worked so well in Chrome?</p>

<p>Unfortunately, inserting the preload elements using a script frequently leads to Safari 'double downloading' the specified resources.</p>

<p>The requests also get dispatched after the lower priority images so we are reliant on the server to prioritise them effectively too.</p>

<p><img src="https://andydavies.me/blog/images/preloading-fonts-and-the-puzzle-of-priorities/safari-js-preload-double-downloads.png" alt="Safari Inspector showing double download when preloaded elements inserted via script" /></p>

<p>When it comes to examining network traffic, Safari isn’t as helpful as Chrome – there’s no equivalent of Chrome’s netlog, and we can’t capture TLS keys and then use Wireshark or <a href="https://github.com/DaanDeMeyer/h2vis">Dan DeMeyer’s h2vis</a> to explore the traffic – and I really miss these options.</p>

<p>Theses options really are useful to independently check what's happening at the network layer, for example, in this case even though Inspector shows a double download, Resource Timing data doesn't and I eventually resorted to server logs to verify the double downloads.</p>

<p>If declarative preloading is 'broken' in Chrome, and inserting the preload elements via a script results in double downloads in Safari, what approach should we take?</p>

<h1>Closing Thoughts</h1>

<p>It will be interesting to revisit these tests once hopefully both Chrome fixes the AppCache delay, and Safari fixes the double download issue.</p>

<p>I'm particularly interested in whether the fixes will change the importance of HTTP/2 prioritisation for these use-cases.</p>

<p>There's no doubt preload has the danger to be a 'foot gun' and Chrome's AppCache issue combined with HTTP/2 servers priority challenges really don't help.</p>

<p>But does that mean we should avoid it?</p>

<h2>Fonts</h2>

<p>Before considering preload for fonts we should go back to basics, if you can't (or don't want to) switch to system fonts there are often opportunities to reduce the size of self-hosted fonts:</p>

<ul>
<li>reduce the number of web fonts in use.</li>
<li>remove glyphs that aren't going to be needed via subsetting</li>
<li>subsetting is really important if you're using an icon font, you probably don't need to ship all 75kB of fontawesome to your visitors</li>
<li>encode fonts as woff2, woff for older browsers (and maybe ttf or eot for really old browsers if you can't just rely on a default font for them)</li>
</ul>


<p><a href="https://everythingfonts.com/subsetter">Everything Fonts Subsetter</a> is handy for manipulating text fonts (I also use Glyphs Mini), and I tend to use the <a href="https://github.com/bramstein/homebrew-webfonttools">font tools that Bram Stein curates</a> for encoding.</p>

<ul>
<li>use the CSS <code>font-display</code> property with a value of <code>swap</code> to enable the browser to start rendering text sooner</li>
<li>include a unicode range in the @font-face declaration</li>
</ul>


<p><a href="https://twitter.com/zachleat">Zach Leatherman's</a> written more on font optimisation than I probably ever will, so I'd suggest reading his <a href="https://www.zachleat.com/web/comprehensive-webfonts/">Comprehensive Guide to Font Loading</a> too.</p>

<h2>Preloading Fonts</h2>

<p>Given the issues I outlined earlier, should we even consider preloading fonts?</p>

<p>I suspect even with Chrome's current sub-optimal behaviour there's a case for preloading one or maybe two critical fonts.</p>

<p>But don't take my word for it, test it for yourself as your traffic mix e.g. Safari vs Chrome, the choice of server and your page make up will influence the outcome:</p>

<ul>
<li>if you've got a high proportion of Safari visitors, then perhaps Chrome's behaviour isn't important</li>
<li>if you're using servers with poor support for HTTP/2 optimisation such as CloudFront or IIS then the outcome might be very different to using Akamai or Cloudflare.</li>
<li>my test pages had multiple external styles, and blocking scripts – pages with more or less render blocking resources may behave differently.</li>
</ul>


<p>I skipped some optimisation opportunities in my tests, for example what if I'd just preloaded Montserrat Regular, and left the other fonts to load as normal?</p>

<p>What if I had fewer stylesheets or blocking scripts in the head how would these have affected the outcomes?</p>

<p>Preloading is also a first impression optimisation – it should only apply to the first hit in a visitor's session, after that the fonts should come from the local browser cache.</p>

<p>Given first impressions tend to be uncached (and so slower) perhaps there's an opportunity to avoid preload and speed up the first view using  <code>font-display:optional</code> or 'font-display: fallback`.</p>

<h2>Preloading Other Resource Types</h2>

<p>Querying the HTTP Archive data shows varied use cases for preload including font loading, asynchronous CSS loading, scripts, images, right though to a site that seemed to preload every resource (40ish!!!)</p>

<p>As I stated at the start preload is a trade off and given the high priority (at least in Chrome) preloaded resources are implicitly given I worry about what other high priority resources are being delayed.</p>

<p>I think the worst case I've seen is a site where stylesheets were blocked for EIGHT seconds while a video was being preloaded (the video took 20 seconds to load)</p>

<p>Although there's some interesting differences between Chrome and Safari's prioritisation of async, deferred, and foot of the page scripts, browsers are pretty good at prioritising resources they can easily discover.</p>

<p>I doubt there's any benefit to preloading the easily discoverable resources but wonder if some scripts inserted using a tag manager might benefit from it.</p>

<p>Given the issues I've seen with fonts, and some of the tests based on HTTP Archive data I'm pretty cautious about using preload, I think there's a danger it does more harm that good.</p>

<h1>Further Research</h1>

<p>I've only scratched the surface, there's further opportunities to understand how the number of number of preloaded fonts (and the order they're loaded in) affects the visitor experience.</p>

<p>Other questions include when should we (if at all) preload  images, stylesheets scripts etc., and how will priority hints interact with them.</p>

<p>Preload as a HTTP header is often mentioned but doesn't seem to be greatly used, I've concerns that it's an even bigger foot gun than the link element but I can still see use cases e.g. bootstrapping an SPA, or third-parties using a scout script might benefit from the scout script using preload headers to load other resources early (as Google Fonts does with a preconnect hint for example)</p>

<p>Chrome and Safari's prioritisation of async, deferred and foot of the page scripts varies but which one is best and when?</p>

<p>These are some of the research cases I thought of while writing this but I'm sure there's more out there!</p>

<h1>References / Further Reading</h1>

<p><a href="https://github.com/andydavies/test-rel-preload">Test pages and results used in this post</a></p>

<p><a href="https://github.com/andydavies/http2-prioritization-issues">H2 Prioritisation Tracker</a></p>

<p><a href="https://github.com/DaanDeMeyer/h2vis">H2vis</a></p>

<p><a href="https://bugs.chromium.org/p/chromium/issues/detail?id=788757">Chrome AppCache / Prioritisation issue</a></p>

<p><a href="https://www.w3.org/TR/resource-hints/">Resource Hints</a></p>

<p><a href="https://wicg.github.io/priority-hints/">Priority Hints proposal</a></p>

<p><a href="https://httparchive.org/reports/page-weight#bytesFont">Median size and number of requests for fonts based on HTTP Archive data</a></p>

<p><a href="https://www.zachleat.com/web/comprehensive-webfonts/">Zach Leatherman's A Comprehensive Guide to Font Loading</a></p>

<p>(EDIT: 13th Feb 2019 - Fixed typos, and added point on page construction to conclusions)</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Safari, Caching and Third-Party Resources]]></title>
    <link href="https://andydavies.me/blog/2018/09/06/safari-caching-and-3rd-party-resources/"/>
    <updated>2018-09-06T14:39:34+01:00</updated>
    <id>https://andydavies.me/blog/2018/09/06/safari-caching-and-3rd-party-resources</id>
    <content type="html"><![CDATA[<p>"Note that WebKit already partitions caches and HTML5 storage for all third-party domains." - <a href="https://webkit.org/blog/7675/intelligent-tracking-prevention/">Intelligent Tracking Prevention</a></p>

<p>Seems a pretty innocuous note but…</p>

<p>What this means is Safari caches content from third-party origins separately for each document origin, so for example if two sites, say <code>a.com</code> and <code>b.com</code> both use a common library,  <code>third-party.com/script.js</code>, then <code>script.js</code> will be cached separately for both sites.</p>

<p>And if someone has an 'empty' cache and visits the first site and then the other, <code>script.js</code> will be downloaded twice.</p>

<p>Malte Ubl was the first person I saw mention this <a href="https://twitter.com/cramforce/status/849621456111624192">back in April 2017</a> but it appears this has been Safari's behaviour <a href="https://bugs.webkit.org/show_bug.cgi?id=110269">since 2013</a></p>

<p>So how much should we worry about Safari's behaviour from a performance perspective?</p>

<!--more-->


<h2>Checking Safari's Behaviour</h2>

<p>Apart from the note in the WebKit post there's little documented detail on Safari's behaviour.</p>

<p>So I decided to check it for myself…</p>

<p>Using the <a href="https://httparchive.org/faq#how-do-i-use-bigquery-to-write-custom-queries-over-the-data">HTTP Archive</a> I found two sites that included the same resource from one of the public JavaScript Content Deliver Networks (CDN).</p>

<p>In this case I chose <a href="http://www.cornell.edu/">Cornell University</a> and <a href="http://www.walgreensbootsalliance.com">Walgreens Boots Alliance</a> as they both include jQuery 1.10.2 over HTTP from ajax.googleapis.com</p>

<p>Then I visited the sites one after the other using Safari on iOS 11.4.1 and <a href="https://developer.apple.com/library/content/qa/qa1176/_index.html#//apple_ref/doc/uid/DTS10001707-CH1-SECRVI">captured the network traffic with tcpdump</a></p>

<p>Sure enough when the packet capture is viewed in Wireshark it's clear that even though the pages were loaded immediately after each other, jQuery is requested over the network for each page load.</p>

<p><img src="https://andydavies.me/blog/images/safari-cache-3rd-parties/cornell.edu.png" title="'www.cornell.edu loading jQuery from ajax.googleapis.com'" ><br/>
www.cornell.edu loading jQuery from ajax.googleapis.com</p>

<p><img src="https://andydavies.me/blog/images/safari-cache-3rd-parties/walgreensbootsalliance.com.png" title="'www.walgreensbootsalliance.com loading jQuery from ajax.googleapis.com'" ><br/>
www.walgreensbootsalliance.com loading jQuery from ajax.googleapis.com</p>

<p>Repeating the tests with two sites that used HTTPS – <a href="https://www.feelcycle.com/">https://www.feelcycle.com/</a> and <a href="https://www.mazda.no/">https://www.mazda.no/</a> produced similar results.</p>

<p>Cache partitioning is aimed at defending against third-parties tracking visitors across multiple sites e.g. via cookies, another mechanism that can also be used to track visitors is TLS Session resumption – see <a href="https://svs.informatik.uni-hamburg.de/publications/2018/2018-12-06-Sy-ACSAC-Tracking_Users_across_the_Web_via_TLS_Session_Resumption.pdf">Tracking Users across the Web via TLS Session Resumption</a> for more detail.</p>

<p>And it appears when loading the second site (Mazda) the TLS connection to ajax.googleapis.com was resumed using information from the first so perhaps there are limits to Intelligent Tracking Protection's current capabilities and further enhancements to come.</p>

<p>(I used Wireshark as I wanted to see the raw network traffic but you can repeat my tests in Safari DevTools)</p>

<h2>What's the Performance Impact?</h2>

<p>In theory using common libraries, fonts etc. from a public CDN provides several benefits:</p>

<ul>
<li><p>reduced hosting costs for sites on a tight budget – <a href="https://twitter.com/troyhunt">Troy Hunt </a> highlights how it reduces the cost of running ';--have i been pwned? in <a href="https://www.troyhunt.com/10-things-i-learned-about-rapidly/">10 things I learned about rapidly scaling websites with Azure</a>.</p></li>
<li><p>improved performance as the resource is hosted on a CDN (closer to the visitor), and in theory there's the possibility the resource may already be cached from an earlier visit to another site that used the same resource.</p></li>
</ul>


<p>For a while I've been skeptical about shared caching (in the browser) and particularly whether it occurs often enough to deliver benefits.</p>

<h3>Shared Caching</h3>

<p>In 2011, <a href="https://twitter.com/spjwebster">Steve Webster</a> <a href="http://statichtml.com/2011/google-ajax-libraries-caching.html">questioned whether enough sites shared the same libraries for the caching benefits to exist</a> and current HTTP Archive data shows sites are still using diverse versions of common libraries.</p>

<p>The March 2018 HTTP Archive (desktop) run has data for approximately 466,000 pages and the most popular public library, jQuery 1.11.3 from ajax.googleapis.com (served over HTTPS), is used by just over 1% of them.</p>

<p>I'm not sure what level adoption needs to reach for shared caching to achieve critical mass but 1% certainly seems unlikely to be high enough and even Google's most popular font – OpenSans – is only requested by around 9% of pages in the HTTP Archive.</p>

<p>Of course if more sites use the same version of a library, from the same public CDN and over the same scheme then the probability of the library being in cache increases.</p>

<p>But even if the third-party resource is used across a critical mass of sites it still has to stay in the cache long enough for it to be there when the next site requests it.</p>

<p>And research by both <a href="https://yuiblog.com/blog/2007/01/04/performance-research-part-2/">Yahoo</a> and <a href="https://code.facebook.com/posts/964122680272229/web-performance-cache-efficiency-exercise/">Facebook</a> demonstrated that resources don't live for as long as we might expect in the browser’s cache.</p>

<p>So if content from some of the most popular sites only lives in the browser cache for a short time what hope do the rest of us have?</p>

<h3>Benefits of Content Delivery Networks (CDNs)</h3>

<p>The other performance aspect a public CDN brings is the reduction in latency – by moving the resource closer to the visitor there should be less time spent waiting for it to download.</p>

<p>Of course the overhead of creating a connection to a new origin (TCP connection / TLS negotiation etc.) needs to be balanced against the potentially faster download times due to reduced latency.</p>

<p>Most of the clients I deal with already use a CDN so they're already gaining the benefits of the reduced latency.</p>

<p>Self-hosting a library has some other advantages too – it removes the dependency on someone else's infrastructure from both reliablity and security perspectives, if your site is using HTTP/2 then the request can be prioritised against the other resources from the origin, or if your site is still using HTTP/1.x then the TCP connection can reused for other requests (reducing the overall connection overhead, and taking advantage of a growing congestion window).</p>

<p>Overall I'm still skeptical that the shared caching delivers a meaningful benefit for sites already using a CDN and encourage clients to host libraries themselves rather than use a public CDN.</p>

<h2>Test it for Yourself</h2>

<p>As ever with performance related changes it's worth testing the difference between self-hosting a library vs using it from a public CDN, and there are several approaches for this.</p>

<h3>Page Level</h3>

<p>A combination of split testing – serving a portion of visitors the library from a public CDN, and others the self-host version – is perhaps the simplest method for determining which approach is faster.</p>

<p>Coupled with Real User Monitoring (RUM) to measure page performance, we can explore how the two different approaches affects the key milestones – FirstMeaningfulPaint, DOMContentLoaded, onLoad, or a custom milestone (using User Timing API) as appropriate – across a whole visitor base.</p>

<h3>Resource Level</h3>

<p>To explore performance in more depth we can use the Resource Timing API to measure the actual times of the third-party library across all visitors and then beacon the timings to RUM or another service for analysis.</p>

<h3>Cached or Not?</h3>

<p>For browsers that support the <a href="https://www.w3.org/TR/resource-timing-2/">Resource Timing (Level 2) API</a> i.e. Chrome &amp; Firefox, it's possible to determine whether a resource was requested over the network or whether a cached copy was used.</p>

<p>Resource Timing (Level 2) includes three attributes describing the size of a resource – <code>transferSize</code>, <code>encodedBodySize</code> and <code>decodedBodySize</code> and Ben Maurer's <a href="https://groups.google.com/a/chromium.org/forum/#!msg/net-dev/DGVo2J4GKzc/pH1U2gOdAQAJ">post to Chromium net-dev</a> illustrates how these attributes can be used to understand whether a resource is cached (or not).</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>if (transferSize == 0)
</span><span class='line'>  retrieved from cached
</span><span class='line'>else if (transferSize &lt; encodedBodySize)
</span><span class='line'>  cached but revalidated
</span><span class='line'>else 
</span><span class='line'>  uncached</span></code></pre></div></figure>


<p>By default Resource Timing has restrictions on the attributes that are populated when the resource is retrieved from a third-party origin, and the size attributes will only be available if the third-party grants access via the <code>Timing-Allow-Origin</code> header.</p>

<p>So the example above needs updating to account for third-parties that don't allow access to the size information and luckily Nic Jansma <a href="https://nicj.net/resourcetiming-in-practice/#content-sizes">has already suggested some approaches for tackling this</a>.</p>

<h3>First Page in a Session vs Later Ones</h3>

<p>Shared caching should have the most impact on the first page in a session (with subsequent pages benefiting from the first page retrieving and caching common resources) so ideally we want to be able to differentiate data for the initial page from the later pages in a session.</p>

<p>One way of doing this might be store a timestamp in local storage, or a session cookie, updating the timestamp on each page view - when the cookie doesn't exist, or the timestamp is too old consider the session to be new, otherwise consider it to be the continuation of an existing session.</p>

<p>This isn't a perfect approach for separating identifying the first page in a session but it's probably close enough.</p>

<h2>Final Thoughts</h2>

<p>Given the growth in 3rd parties tracking us across the web, mechanisms that improve our privacy are to be applauded even if they lead to a theoretical decrease in performance.</p>

<p>It's unclear how real the decrease in performance is - I'm pretty skeptical that any common library from one of the public CDNs is used widely enough for the shared caching benefits to be seen, but as with all things performance we should 'measure it, not guess it'.</p>

<p>(Thanks to <a href="https://dougsillars.com">Doug</a> and <a href="https://blog.yoav.ws">Yoav</a> for reviewing drafts of this post and suggesting improvements)</p>

<h2>Further Reading</h2>

<p><a href="https://webkit.org/blog/7675/intelligent-tracking-prevention/">Intelligent Tracking Prevention</a>, WebKit, 2017</p>

<p><a href="https://bugs.webkit.org/show_bug.cgi?id=110269">Optionally partition cache to prevent using cache for tracking</a>, WebKit, 2013</p>

<p><a href="https://developer.apple.com/library/content/qa/qa1176/_index.html#//apple_ref/doc/uid/DTS10001707-CH1-SECRVI">Getting a Packet Trace</a>, Apple, 2016</p>

<p><a href="https://svs.informatik.uni-hamburg.de/publications/2018/2018-12-06-Sy-ACSAC-Tracking_Users_across_the_Web_via_TLS_Session_Resumption.pdf">Tracking Users across the Web via TLS Session Resumption</a>, Erik Sy, Christian Burkert, Hannes Federrath, Mathias Fischer, 2018</p>

<p><a href="http://statichtml.com/2011/google-ajax-libraries-caching.html">Caching and the Google AJAX Libraries</a>, Steve Webster, 2011</p>

<p><a href="https://code.facebook.com/posts/964122680272229/web-performance-cache-efficiency-exercise/">Web performance: Cache efficiency exercise</a>, Facebook, 2015</p>

<p><a href="https://yuiblog.com/blog/2007/01/04/performance-research-part-2/">Performance Research, Part 2: Browser Cache Usage - Exposed!</a>, Yahoo, 2007</p>

<p><a href="https://www.w3.org/TR/user-timing/">User Timing API</a>, W3C, 2013</p>

<p><a href="https://www.w3.org/TR/resource-timing-2/">Resource Timing Level 2 API</a>, W3C, 2018 (Working Draft)</p>

<p><a href="https://groups.google.com/a/chromium.org/forum/#!msg/net-dev/DGVo2J4GKzc/pH1U2gOdAQAJ">Local cache performance</a>, Ben Maurer - Facebook, 2016</p>

<p><a href="https://nicj.net/resourcetiming-in-practice/">ResourceTiming in Practice</a>, Nic Jansma, 2015 updated 2018</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Measuring the Impact of 3rd-Party Tags With WebPageTest]]></title>
    <link href="https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags/"/>
    <updated>2018-02-19T12:00:00+00:00</updated>
    <id>https://andydavies.me/blog/2018/02/19/using-webpagetest-to-measure-the-impact-of-3rd-party-tags</id>
    <content type="html"><![CDATA[<p>There's been an explosion in 3rd-Party Tags…</p>

<p>And as <a href="https://twitter.com/tkadlec">Tim Kadlec</a> once said "Everything should have a value, because everything has a cost".</p>

<p>So how do we measure the performance cost of all the 3rd-party tags we keep adding to our sites?</p>

<!--more-->


<p>Although Chrome supports blocking individual requests, my favourite approach still uses WebPageTest.</p>

<p>If you're interested in the Chrome approach <a href="https://twitter/umaar">Umar</a> covered <a href="https://umaar.com/dev-tips/68-block-requests/">how to use DevTools to block a network request</a> just after  it was released in Chrome Canary – it still works the same way today and now you can even block whole domains too.</p>

<p>One of the reasons I prefer WebPageTest is its side-by-side comparison of the filmstrip is a really powerful way to communicate the impact of tags to co-workers, bosses and clients.</p>

<p><img src="https://andydavies.me/blog/images/wpt-3rd-parties/ms-comparison-filmstrip.png" title="'Comparison of Marks and Spencer with and without 3rd-Party Tags'" ></p>

<h2>Creating the Comparison</h2>

<p>The first step is to test a page with nothing blocked i.e. in it's default state.</p>

<p><img src="https://andydavies.me/blog/images/wpt-3rd-parties/wpt-test-config.png" title="'Configuring a test in WebPageTest'" ></p>

<p>Remember to enable video capture, and labelling the tests will make it easier to differentiate them when viewing the filmstrip. In the spirit of being obvious I often label this step 'Original' or 'Default'!</p>

<h2>Blocking Requests</h2>

<p>Next we need to create a test with some requests blocked – I tend to label this one '3rd-Parties Blocked' or similar depending on what's actually blocked</p>

<p>There are a few ways to block requests:</p>

<ol>
<li><p>Using the Block Field</p>

<p> The easiest way to block requests is to add entries to the block field:</p>

<p> <img src="https://andydavies.me/blog/images/wpt-3rd-parties/wpt-block-requests.png" title="'Selecting which requests to block'" ></p>

<p> The block field takes a space separated list of values and like its name suggests blocks any requests containing one of the values.</p>

<p> As it's a substring match it's pretty generous with what it accepts – anything from a single letter to a full URL – so if you add <code>.com</code> WebPageTest will block all requests with .com anywhere in the URL.</p>

<p> I use the block field approach more often than the script based approaches below.</p></li>
<li><p>Using a Script</p></li>
</ol>


<p>Alternatively there are three script commands that can be used to block requests - they're straightforward and self-explanatory.</p>

<ul>
<li><code>block</code> – followed by a space separated list of substrings</li>
</ul>


<p>This works in the same way as the block field in the previous example.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>  block    .com
</span><span class='line'>  navigate    https://andydavies.me</span></code></pre></div></figure>


<p><a href="http://www.webpagetest.org/result/180213_21_6959181a791655089c06740c3acddebc/">Example of test using block command</a></p>

<p><strong>Note:</strong> the script also needs the command to navigate to the page you want to test too.</p>

<ul>
<li><code>blockDomains</code> - followed by a space separated list of domains to block</li>
</ul>


<p>Blocks all the domains specified.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>blockDomains   fonts.googleapis.com
</span><span class='line'>navigate  https://andydavies.me</span></code></pre></div></figure>


<p><a href="http://www.webpagetest.org/result/180213_Z7_77d2b2c4c3ba42a37a181fc5e3760ed8/">Example of test using blockDomains command</a></p>

<ul>
<li><code>blockDomainsExcept</code> - followed by a space separated list of domains to block</li>
</ul>


<p>Blocks all the domains except those specified.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>blockDomainsExcept andydavies.me
</span><span class='line'>navigate  https://andydavies.me</span></code></pre></div></figure>


<p><a href="http://www.webpagetest.org/result/180212_BM_e672aacda7d5e7e3447c0fb4412439ac/">Example of test using blockDomainsExcept command</a></p>

<p><a href="https://twitter.com/souders">Steve Souders</a> covers using <code>blockDomainsExcept</code> for <a href="https://speedcurve.com/blog/pwa-performance/">testing Service Workers</a>, and it's a great example of where it can be really useful.</p>

<p><strong>Note:</strong> One thing to watch for when viewing waterfalls from scripts that used <code>blockDomains</code> and <code>blockDomainsExcept</code> is the requests are blocked at the network level so you'll see the request in the waterfall with a -1 result code.</p>

<h2>Comparing Results</h2>

<p>Once both tests have completed we can hop over to the <a href="https://www.webpagetest.org/testlog/1/">Test History</a> tab, select the tests we want to compare and view the filmstrip.</p>

<p><img src="https://andydavies.me/blog/images/wpt-3rd-parties/wpt-selecting-results.png" title="'Choosing results to compare'" ></p>

<h2>Choosing which Requests to Block</h2>

<p>I generally use one of two strategies for determining which  requests to block depending on whether I want to raise awareness that 3rd-parties tag are a problem in general, or whether I want to determine the cost of a single tag.</p>

<ol>
<li><p>Block Everything</p>

<p> The option that requires the least thought is 'block all the things'.</p>

<p> One way to do this is to this is by copying the list domains from the domains view of a completed test and using this as a block list.</p>

<p> It's a bit of manual labour so I tend to use a shell command to create the list.</p>

<p> The domains view can also produce a JSON formatted response and one way I build the list of domains to block is to pipe the JSON output through <a href="https://stedolan.github.io/jq/"><code>jq</code></a> and then copy it to the clipboard using <code>pbcopy</code>.</p>

<p> <code>curl 'http://www.webpagetest.org/domains.php?test=180130_NH_4c83de2dfe315f19e5367b91b6ac4a37&amp;run=1&amp;cached=0&amp;f=json' | jq -rj '.domains.firstView[].domain + " "' | pbcopy</code></p>

<p> Then I paste the list of domains into the block field and remove any that shouldn't be blocked – domain being tested, and any domains it relies on, for example, static or media assets domains.</p>

<p> <strong>Note:</strong> Of course <code>pbcopy</code> is macOS only but there are <a href="https://garywoodfine.com/use-pbcopy-on-ubuntu/">alternatives such as xclip on Ubuntu etc.</a></p></li>
<li><p>Block a Subset of Requests</p></li>
</ol>


<p>Blocking all 3rd-parties is a dramatic way to show their combined cost, but it's slightly less useful when it comes to  discussions about the impact or value of an single 3rd-party tag.</p>

<p>Blocking a single tag, perhaps one that delays the page from rendering, or slows other critical content from displaying, is a powerful way of understanding just its impact.</p>

<p>And if you have a Real User Monitoring product (such as NCC Group's RUM, or Soasta's mPulse) that models the impact of speed on conversions and other business metrics, you can use the time differences to get a real indication of what the 3rd-party is actually costing in lost conversions, for example.</p>

<h2>Gotchas</h2>

<p>Blocking 3rd-parties isn't a foolproof process and sometimes blocking a 3rd-party doesn't have the expected impact or introduces side effects that make the 'slimmer' page slower.</p>

<p>In this <a href="http://www.webpagetest.org/video/compare.php?tests=180130_R4_4b95a18e31338331e9e642cb3872bfcb,180130_MF_e4c2bf21f0e4edaaf6217133bd7ac23f">comparison of debenhams.com with and without 3rd-party tags</a> the test with 3rd-party tags blocked is actually slower than the one with tags present (you might notice the base page gets re-requested as request #12 in the tests with the tags blocked)</p>

<p>So don't be surprised if occasionally blocking 3rd-parties doesn't have quite the effect you expect.</p>

<p>In these cases, I tend to reduce the list of 3rd-parties being blocked until I isolate the one that's causing the unexpected behaviour.</p>

<h2>Closing Thoughts</h2>

<p>Filmstrip comparisons are a great way to illustrate how 3rd-parties affect the visual experience and they're easy for everyone to understand too.</p>

<p>Although filmstrips only help a little in understanding whether a page is in a state where the visitor can interact with it (or not), WebPageTest has an estimation of when a page is interactive at the bottom of the waterfall.</p>

<p>Comparing the charts from two runs give us some indication of whether the page with 3rd-parties removed becomes usable sooner.</p>

<p><img src="https://andydavies.me/blog/images/wpt-3rd-parties/ms-comparison-tti.png" title="'Time-to-Iteractive Comparison'" ></p>

<p>One of the performance challenges we face is helping people to understand the cost of 3rd-party tags in terms of user experience and WebPageTest's ability to block requests offers a great way to help with that.</p>

<h2>Further Reading</h2>

<p><a href="https://umaar.com/dev-tips/68-block-requests/">dev-tips: 'Chrome DevTools: Block certain requests from a web page, see how a page works without CSS or Javascript'</a></p>

<p><a href="https://sites.google.com/a/webpagetest.org/docs/using-webpagetest/scripting#TOC-block">WebPageTest Docs: Scripting commands for blocking requests</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Adding Public Locations to a Private WebPageTest Instance]]></title>
    <link href="https://andydavies.me/blog/2016/09/20/adding-public-locations-to-a-private-webpagetest-instance/"/>
    <updated>2016-09-20T19:53:11+01:00</updated>
    <id>https://andydavies.me/blog/2016/09/20/adding-public-locations-to-a-private-webpagetest-instance</id>
    <content type="html"><![CDATA[<p>If you’ve got your own Private WebPageTest instance, and you want to add extra locations without setting up the test agents yourself then you can add agents from another instance.</p>

<p>Once configured your local WebPageTest instance submits tests and retrieves results via the API of the remote WebPageTest instance (aka Relay Server), and the pages are tested on the remote instance’s agents.</p>

<p>Using a Relay Server is also handy if you want to do local development on the server code without needing to setup Windows (or mobile) test agents.</p>

<!--more-->


<h2>Configuration</h2>

<p>Assuming you’ve already got a WebPageTest server set up, then adding relay server agents just involves adding some extra entries to <code>locations.ini</code>.</p>

<p>A simple <code>locations.ini</code> that just has the public Singapore test agents as a location looks like this:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>[locations]
</span><span class='line'>1=Singapore
</span><span class='line'>default=Singapore
</span><span class='line'>
</span><span class='line'>[Singapore]
</span><span class='line'>1=WPT_Singapore
</span><span class='line'>label=Singapore
</span><span class='line'>group=WebPageTest Public
</span><span class='line'>
</span><span class='line'>[WPT_Singapore]
</span><span class='line'>browser=Chrome, Firefox, IE 11
</span><span class='line'>label=Singapore
</span><span class='line'>relayServer=https://www.webpagetest.org/
</span><span class='line'>relayKey=(API key for relay server)
</span><span class='line'>relayLocation=ec2-ap-southeast-1</span></code></pre></div></figure>


<p>The section for each public agent (WPT_Singapore in this case) has a collection of key / value pairs.</p>

<p>So where do the values for each of these keys come from?</p>

<ul>
<li>relayServer</li>
</ul>


<p>The URL of the WebPageTest instance hosting the agents you want to use.</p>

<p>In this example we’re using the public WebPageTest instance - https://www.webpagetest.org</p>

<ul>
<li>relayKey</li>
</ul>


<p>The API key (if needed) for the WebPageTest instance with the agents.</p>

<p>If you want to use a public agent and don’t already have an API key, thanks to Akamai you can get a key that’s good for 200 tests per day - <a href="https://www.webpagetest.org/getkey.php">https://www.webpagetest.org/getkey.php</a></p>

<p>The Akamai keys only work with a subset of the public locations and a list of those locations is included when the key is emailed to you.</p>

<ul>
<li>relayLocation</li>
</ul>


<p>Each WebPageTest server has a number of agents, you can see these, their browsers and work queues via <code>/getLocations.php</code></p>

<p>For example on the public instance <a href="https://www.webpagetest.org/getLocations.php">https://www.webpagetest.org/getLocations.php</a> will show you a list of the locations and browsers at each location.</p>

<p>At the start of each line is a location and browser pair (followed by counts of the number of agents at the location, how many are busy, how many jobs are waiting in each priority queue etc.)</p>

<p>The <code>relayLocation</code> value  should be set to the first part of the location:browser pair</p>

<p>For example, the entries for Singapore look something like:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>ec2-ap-southeast-1:Chrome   2   0   0   0   0   0   0   0   0   0   0   0   0
</span><span class='line'>ec2-ap-southeast-1:IE 11    2   0   0   0   0   0   0   0   0   0   0   0   0
</span><span class='line'>ec2-ap-southeast-1:Firefox  2   0   0   0   0   0   0   0   0   0   0   0   0
</span><span class='line'>ec2-ap-southeast-1:Safari   2   0   0   0   0   0   0   0   0   0   0   0   0
</span></code></pre></div></figure>


<p>So to add Singapore, the <code>relayLocation</code> should be set to <code>ec2-ap-southeast-1</code></p>

<ul>
<li>browser</li>
</ul>


<p>The browser key is a comma separated list of the browsers that are available at your relay location.</p>

<p>So again using the Singapore location from above, the <code>browser</code> key value can be set to <code>Chrome, IE 11, Firefox</code></p>

<p>(As the Windows version of Safari is so old I never configure it on my instances)</p>

<h2>Limitations</h2>

<p>There’s a few limitations you want to be aware of when using a relay server:</p>

<ul>
<li><p>If you’re using the public agents then your tests will be run at   a lower priority than pages submitted via the public web UI. Your tests are competing with all the other public tests and so are likely to queue.</p></li>
<li><p>If multiple locations use the same relay server then they’ll be duplication in your <code>locations.ini</code> as each location needs to configured separately.</p></li>
<li><p>Requests to the local servers <code>/getLocations.php</code> and <code>getTesters.php</code> won’t show any real detail for the remote test agents.</p></li>
</ul>


<h2>Further Reading</h2>

<p>If you want to know more about WebPageTest Relay Servers there's some docs available - <a href="https://sites.google.com/a/webpagetest.org/docs/system-design/webpagetest-relay">https://sites.google.com/a/webpagetest.org/docs/system-design/webpagetest-relay</a></p>

<p>And if you want to learn more on how to get the most out of WebPageTest, Rick Viscomi, Marcel Duran and myself wrote <a href="http://shop.oreilly.com/product/0636920033592.do">'Using WebPageTest'</a> just for you!</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Accelerated Mobile Pages - I’ve Got More Questions Than Answers]]></title>
    <link href="https://andydavies.me/blog/2015/10/13/accelerated-mobile-pages-ive-more-questions-than-answers/"/>
    <updated>2015-10-13T14:29:48+01:00</updated>
    <id>https://andydavies.me/blog/2015/10/13/accelerated-mobile-pages-ive-more-questions-than-answers</id>
    <content type="html"><![CDATA[<p>So Google, a group of publishers and others have launched <a href="https://www.ampproject.org/">Accelerated Mobile Pages (AMP)</a></p>

<p>AMP promotes the goal of a faster mobile web which is something I think we’d all like to see.</p>

<p>If you visit <a href="https://g.co/ampdemo">g.co/ampdemo</a> on a mobile, and search for ‘Obama’ there’s no doubt the stories in the carousel come up fast, and moving between them is slick too. The demo isn’t available in all regions yet so <a href="https://twitter.com/addyosmani">Addy Osmani</a> posted a <a href="https://www.youtube.com/watch?v=i2_lAEzmOPo&amp;feature=youtu.be">demo to YouTube.</a></p>

<p>Similar to Google’s <a href="https://googlewebmastercentral.blogspot.co.uk/2011/06/announcing-instant-pages.html">Instant Pages</a>, AMP relies on pre-rendering and caching to make the pages load instantly.</p>

<!--more-->


<p>Where AMP differs from Instant Pages is that it forces developers to use custom elements for images, audio and video, and limits some of the other web features a page can use (only AMP supplied JS, limited CSS features and with exception of button no form elements etc.).</p>

<p>There’s plenty in the documentation about what technologies are allowed or not allowed, but there’s less information on why these choices were made.</p>

<p>And I wonder how much this lack of information and the project’s relative immaturity, coupled with being backed by Google contributes to the unease we feel with it at the moment.</p>

<p><a href="https://twitter.com/tkadlec">Tim Kadlec</a> has already discussed <a href="https://timkadlec.com/2015/10/amp-and-incentives/">whether the incentives publishers have to adopt AMP conflict with an open web</a> and I’d really recommend reading his post.</p>

<p>After a few days of testing AMP based pages, reading the <a href="https://github.com/ampproject/amphtml">GitHub Repo</a> and other docs it’s clear AMP aims to be a set of custom elements and rules to enable pages to be pre-rendered quickly and easily – a set of constraints to protect us from our own excesses.</p>

<p>Some of the AMP components such as img, audio and video, are replacements for their HTML equivalents but control asset loading more finely (and of course dispense with <a href="https://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/">optimisations like the browser pre-loader</a>).</p>

<p>Others provide new features such as carousels or wrap 3rd party services. Let’s face it there are plenty of poorly implemented carousels and 3rd party scripts out there so applying an external quality control over them is welcome.</p>

<p>But does AMP really deliver in performance terms?</p>

<h2>Is it really faster?</h2>

<p>The AMP Project reports speed improvements of 15-85% (using SpeedIndex as their measure), but as we don’t have their data or methodology it’s unclear how much these improvements rely on pre-rendering and caching.</p>

<p>Testing AMP pages (without the benefit of pre-rendering) vs their existing equivalents shows something of a mixed picture.</p>

<ul>
<li>The Guardian</li>
</ul>


<p>(The top set of images is the current Guardian site, and the bottom the AMP equivalent)</p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_1E_5221aacdc476b7168c22110c4f26d70f,151011_48_91c1899477e71b7e6493ff99c86892aa"><strong>3G</strong></a>
<img src="https://andydavies.me/blog/images/guardian-3g.png" title="'The Guardian - 3G'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_C0_e83e42628e85427098a2dae346094a8f,151011_E4_d8fd51096f01531e1291495f191304f1"><strong>3G Fast</strong></a>
<img src="https://andydavies.me/blog/images/guardian-3g-fast.png" title="'The Guardian - Fast 3G'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_A2_3605e4f164631ce0190babe37d71bdb4,151011_2Z_702b2ab194c211208598efec9dc13cb0"><strong>Cable</strong></a>
<img src="https://andydavies.me/blog/images/guardian-cable.png" title="'The Guardian - Cable'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_GM_3e31fe4668e807fdc4f3dc7d4f79b146,151011_9Z_ada6d1c47518be1a757372ebc8e78650"><strong>No network shaping</strong></a>
<img src="https://andydavies.me/blog/images/guardian-no-shaping.png" title="'The Guardian - No Network Shaping'" ></p>

<ul>
<li>BuzzFeed</li>
</ul>


<p>(The top set of images is the current Buzzfeed site, and the bottom the AMP equivalent)</p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_TP_43d156bcce27572e26c172bacf86628f,151011_7J_1d80de4375da693729d1918040c0bce8"><strong>3G</strong></a>
<img src="https://andydavies.me/blog/images/buzzfeed-3g.png" title="'Buzzfeed - 3G'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_10_b7859848cb83fe31e2b762b1bfa522c3,151011_HT_936e94ffb04dffef85171ddc388a7605"><strong>3G Fast</strong></a>
<img src="https://andydavies.me/blog/images/buzzfeed-3g-fast.png" title="'Buzzfeed - Fast 3G'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_00_b354f0102dac06dfb276c41745997288,C151011_DF_86d6a4b43e4a2fc57c14ea9187206816"><strong>Cable</strong></a>
<img src="https://andydavies.me/blog/images/buzzfeed-cable.png" title="'Buzzfeed - Cable'" ></p>

<p><a href="https://www.webpagetest.org/video/compare.php?tests=151011_AB_197b2169b5b98d4eb851c218da3e0d15,151011_18_c09cefc99cf800959b5eb2f32853d9b4"><strong>No network shaping</strong></a>
<img src="https://andydavies.me/blog/images/buzzfeed-no-shaping.png" title="'Buzzfeed - No Network Shaping'" ></p>

<p>In the BuzzFeed examples the AMP version is generally faster with the exception of the test that involved no network shaping.</p>

<p>The picture for The Guardian is reversed, over slower networks the current site starts rendering sooner but generally always finishes after the AMP version. The exception is the test that involved no network shaping where the AMP version is noticeably faster.</p>

<p>The current Guardian site makes over 100 HTTP requests per page, and the AMP version only 9. The effort The Guardian's developers put into performance clearly is reflected in the results.</p>

<p>Note: The current Guardian site uses HTTP, whereas the AMP version uses HTTPS which will affect the result with some TLS overhead.</p>

<h2>What about progressive enhancement?</h2>

<p>From a browser support perspective the pages work in every browser I’ve tried - Chrome on Android, Safari on iOS and even Opera Mini (with a few minor quirks).</p>

<p>One thing you might notice from many of the AMP tests is that with the exception of media there’s little progressive rendering; the content just appears to pop onto the page, this is by design…</p>

<p>The head of each page contains a style block that sets the whole page to be transparent</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>&lt;style&gt;body {opacity: 0}&lt;/style&gt;&lt;noscript&gt;&lt;style&gt;body {opacity: 1}&lt;/style&gt;&lt;/noscript&gt;</span></code></pre></div></figure>


<p>And the page contents don’t become visible until the AMP script has executed</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>&lt;script async src=“https://cdn.ampproject.org/v0.js”&gt;&lt;/script&gt;</span></code></pre></div></figure>


<p>So although the pages use an asynchronous script, if it fails for any reason then the visitor sees a blank screen.</p>

<p>It’s not completely clear whether AMP is designed to just to be a format for content embedded in apps or whether it’s designed for more general use replacing the plain old HTML versions of publishers pages too.</p>

<p>If it is just for embedding in apps then there’s always the option of bundling the scripts with the viewer which guards against failure and will improve render speeds, but I still have a nervousness about pages being so dependent on JavaScript for rendering (even when the blocking 3rd party scripts we use now fail, the browser will eventually allow the page to render).</p>

<p>And do we really want to rely on JavaScript for ‘plain old’ document rendering as Jeff Attwood notes <a href="https://meta.discourse.org/t/the-state-of-javascript-on-android-in-2015-is-poor/33889">JavaScript performance on Android isn’t really improving</a>.</p>

<h2>The componentisation of the web</h2>

<p>Web components have been talked about for several years and perhaps by providing a set of well defined, quality controlled and performant (maybe) components AMP represents the real start of the componentisation of the web.</p>

<p>There are of course questions about how the range of components gets extended, who decides what’s acceptable and how we innovate in a ecosystem of tightly controlled components.</p>

<p>Would BBC News or The Guardian have been able to experiment, learning how to create flexible and fast experiences in an AMP-like environment?</p>

<p>The current constraints still allow some performance issues to slip through - The Atlantic demo is a 447KB page of which 347KB is fonts!</p>

<p>I’d also like to understand more about why AMP bypasses the pre-loader for its images, and whether some of the AMP decisions to lazy-load images could be incorporated directly into browsers.</p>

<h2>Wrap up</h2>

<p>After a few days of exploring, experimenting with and testing AMP I do understand more, but I’m not sure if I’m more comfortable with it.</p>

<p>I see cases where it offers some performance improvements but then there are others where it appears slower.</p>

<p>Despite the launch partners, AMP is still a developer preview and as the GitHub Issues list shows it's still early days.</p>

<p>I wonder if what AMP really does is remind us how we’ve failed to build a performant web… we know how to, but all too often we just choose not to (or lose the argument) and fill our sites with cruft that kills performance, and with it our visitors’ experience.</p>

<p>Perhaps it is time to press the reset button.</p>

<p>Only time will tell if AMP is that reset button…</p>

<h2>Further Reading</h2>

<p><a href="https://plus.google.com/+MalteUbl/posts/Lc1jJ7XVnki">Malte Ubl’s G+ Post</a></p>

<p><a href="https://www.ampproject.org/">https://www.ampproject.org/</a></p>

<p><a href="https://github.com/ampproject/amphtml">AMP Project on Github</a></p>

<p><a href="https://docs.google.com/document/d/1WdNj3qNFDmtI--c2PqyRYrPrxSg2a-93z5iX0SzoQS0/">Life of an AMP</a></p>

<p><a href="https://docs.google.com/document/d/1YjFk_B6r97CCaQJf2nXRVuBOuNi_3Fn87Zyf1U7Xoz4/">AMP Layout System</a></p>

<p><a href="https://groups.google.com/forum/#!topic/amphtml-discuss">AMP HTML Discuss</a></p>

<p><a href="http://www.niemanlab.org/2015/10/get-ampd-heres-what-publishers-need-to-know-about-googles-new-plan-to-speed-up-your-website/">Get AMP’d: Here’s what publishers need to know about Google’s new plan to speed up your website</a></p>

<p><a href="http://marketingland.com/google-open-mobile-web-145588">Accelerated Mobile Pages Project, Backed By Google, Promises Faster Pages</a></p>

<p><a href="https://meta.discourse.org/t/the-state-of-javascript-on-android-in-2015-is-poor/33889">The State of JavaScript on Android in 2015 is… poor</a></p>

<h2>Example AMP Based Pages and their ‘vanilla’ equivalents</h2>

<p><strong>New York Times</strong></p>

<ul>
<li><a href="https://mobile.nytimes.com/2015/10/08/us/reassurances-end-in-flint-after-months-of-concern.html">Regular</a></li>
<li><a href="https://mobile.nytimes.com/2015/10/08/us/reassurances-end-in-flint-after-months-of-concern.amp.html?_r=0">AMP</a></li>
</ul>


<p><strong>The Guardian</strong></p>

<ul>
<li><a href="https://www.theguardian.com/science/2015/oct/07/lindahl-modrich-and-sancar-win-nobel-chemistry-prize-for-dna-research">Regular</a></li>
<li><a href="https://www.theguardian.com/science/2015/oct/07/lindahl-modrich-and-sancar-win-nobel-chemistry-prize-for-dna-research/amp">AMP</a></li>
</ul>


<p><strong>BuzzFeed</strong></p>

<ul>
<li><a href="https://www.buzzfeed.com/salvadorhernandez/rupert-murdoch-tweets-dig-at-obama-asks-for-a-real-black-pre#.rr7NnO7vrM">Regular</a></li>
<li><a href="https://www.buzzfeed.com/amphtml/salvadorhernandez/rupert-murdoch-tweets-dig-at-obama-asks-for-a-real-black-pre?amp_js_v=0">AMP</a></li>
</ul>


<p><strong>The Atlantic</strong></p>

<ul>
<li><a href="https://www.theatlantic.com/politics/archive/2015/10/a-short-history-of-whether-obama-is-black-enough-featuring-rupert-murdoch/409642/">Regular</a></li>
<li><a href="https://amp.gstatic.com/v/the-atlantic-amphtml.googleusercontent.com/amphtml/pages/CAIiEGZTcFZtopP7g7u5imKTZxYqFAgEKg0IACoGCAowm_EEMKAiMPtZ?amp_js_v=0#development=1">AMP</a></li>
</ul>

]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[How the Browser Pre-loader Makes Pages Load Faster]]></title>
    <link href="https://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster/"/>
    <updated>2013-10-22T19:25:00+01:00</updated>
    <id>https://andydavies.me/blog/2013/10/22/how-the-browser-pre-loader-makes-pages-load-faster</id>
    <content type="html"><![CDATA[<p>The pre-loader (also known as the speculative or look-ahead pre-parser) may be the single biggest improvement ever made to browser performance.</p>

<p>During their implementation <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=364315#c38">Mozilla reported a 19% improvement in load times</a>, and in a test against the Alexa top 2,000 sites <a href="https://plus.google.com/+IlyaGrigorik/posts/8AwRUE7wqAE">Google found around a 20% improvement</a>.</p>

<p>It’s not a new browser feature but some seem to believe it’s Chrome only and yet others suggest it’s “the most destructive ‘performance enhancement’ there’s ever been”!</p>

<p>So what is the pre-loader and how does it improve performance?</p>

<!-- MORE -->


<h1>How browsers used to load web pages</h1>

<p>Web pages are full of dependencies – a page can’t start rendering until the relevant CSS has downloaded, then when a script is encountered the HTML parser pauses until the script has executed (of course if the script is external it needs to be downloaded too).</p>

<p>Let’s consider how a browser might load a page:</p>

<ul>
<li><p>First the HTML is downloaded and the browser starts parsing it. It finds a reference to an external CSS resource and fires off a request to download it.</p></li>
<li><p>The browser can carry on parsing the HTML while the CSS is downloading but then it finds a script tag with an external URL, now (unless the script has <code>async</code> or <code>defer</code> attributes) it must wait until the script has downloaded and executed.</p></li>
<li><p>Once the script has downloaded and executed, the browser can continue parsing the HTML, when it finds non-blocking resources such as images it will request them and carry on parsing, but when it finds a script it must stop and wait for the script to be retrieved and executed.</p></li>
</ul>


<p>Although a browser is capable of making multiple requests in parallel, one that behaved like this often wouldn't be downloading any resources in parallel with a script.</p>

<p>This is how browsers used to behave and using <a href="http://stevesouders.com/cuzillion/">Curzillion</a> by <a href="http://twitter.com/souders">Steve Sounders</a> we can create a test page that demonstrates this in IE7.</p>

<p>The <a href="http://man.gl/1cPmbyI">test page</a> has two stylesheets followed by two scripts in the head, then in the body it has two images, a script and finally another image.</p>

<p>The waterfall makes it easy to see parallel downloads stop while a script is being downloaded.</p>

<p><img src="https://andydavies.me/blog/images/no-pre-loader-waterfall-ie7.png" title="'Pre-loader priority issue in IE9'" > <br>
Waterfall of Cuzillion generated <a href="http://man.gl/1cPmbyI">test page</a> in IE7</p>

<p>If browsers still worked like this then pages would be slower to load as every time a script was encountered the browser would need to wait for the script to be downloaded and executed before it could discover more resources.</p>

<h1>How the pre-loader improves network utilisation</h1>

<p>Internet Explorer, WebKit and Mozilla all implemented pre-loaders in 2008 as a way of overcoming the low network utilisation while waiting for scripts to download and execute.</p>

<p>When the browser is blocked on a script, a second lightweight parser scans the rest of the markup looking for other resources e.g. stylesheets, scripts, images etc., that also need to be retrieved.</p>

<p>The pre-loader then starts retrieving these resources in the background with the aim that by the time the main HTML parser reaches them they may have already been downloaded and so reduce blocking later in the page.</p>

<p> (Of course if the resource is already in the cache then the browser won’t need download it)</p>

<p>Repeating the previous test with IE8 shows other resources are now downloaded in parallel with scripts, delivering a huge performance improvement for this test case: 7s vs 14s.</p>

<p><img src="https://andydavies.me/blog/images/pre-loader-waterfall-ie8.png" title="'Pre-loader priority issue in IE8'" > <br>
Waterfall of Cuzillion generated <a href="http://man.gl/1cPmbyI">test page</a> in IE8</p>

<p>Pre-loader behaviour varies between browsers and is still an area of experimentation, some browsers seem to have naive implementations where they download the resources in order of discovery but other browsers prioritise the downloads, for example Safari gives stylesheets that don’t apply to the current viewport a low priority, Chrome schedules scripts (even those at the foot of a page) with a higher priority than most of the images on the page.</p>

<p>The prioritisation mechanisms aren’t well documented (you can read the source for some browsers!) but if you want to get a better understanding of what they can do, James Simonsen wrote some excellent <a href="https://docs.google.com/document/d/1JQZXrONw1RrjrdD_Z9jq1ZKsHguh8UVGHY_MZgE63II/preview?hl=en-GB&amp;forcehl=1">notes about the approaches they’re trying in Chrome</a>.</p>

<h1>Pre-Loader Gotchas</h1>

<p>Pre-loaders extract URLs from markup and don’t / cannot execute javascript so any URLs inserted using javascript aren’t visible to it and the download of these resources will be delayed until the HTML parser discovers and executes the javascript that loads them.</p>

<p>There are cases where inserting resources using javascript can also trip up some pre-loaders.</p>

<p>I came across an answer on Stack Overflow suggesting javascript should be used to insert a link to either a mobile or desktop stylesheet depending on browser width:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;html&gt;</span>
</span><span class='line'><span class="nt">&lt;head&gt;</span>
</span><span class='line'>  <span class="nt">&lt;script&gt;</span>
</span><span class='line'>      <span class="kd">var</span> <span class="nx">file</span> <span class="o">=</span> <span class="nb">window</span><span class="p">.</span><span class="nx">innerWidth</span> <span class="o">&lt;</span> <span class="mi">1000</span> <span class="o">?</span> <span class="s2">&quot;mobile.css&quot;</span> <span class="o">:</span> <span class="s2">&quot;desktop.css&quot;</span><span class="p">;</span>
</span><span class='line'>      <span class="nb">document</span><span class="p">.</span><span class="nx">write</span><span class="p">(</span><span class="s1">&#39;&lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;css/&#39;</span> <span class="o">+</span> <span class="nx">file</span> <span class="o">+</span> <span class="s1">&#39;&quot;/&gt;&#39;</span><span class="p">);</span>
</span><span class='line'>  <span class="nt">&lt;/script&gt;</span>
</span><span class='line'><span class="nt">&lt;/head&gt;</span>
</span><span class='line'><span class="nt">&lt;body&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img1.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img2.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img3.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img4.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img5.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;img</span> <span class="na">src=</span><span class="s">&quot;img/gallery-img6.jpg&quot;</span> <span class="nt">/&gt;</span>
</span><span class='line'><span class="nt">&lt;/body&gt;</span>
</span><span class='line'><span class="nt">&lt;/html&gt;</span>
</span></code></pre></div></figure>


<p>There are several reasons why I wouldn’t use this approach but even this simple example is enough to trip up IE9’s pre-loader – notice how the images grab all the connections and the CSS is delayed until one of the images completes and a connection becomes available.</p>

<p><img src="https://andydavies.me/blog/images/pre-loader-issue-ie9.png" title="'Pre-loader priority issue in IE9'" > <br>
Test page loaded in IE9</p>

<p>Some of the responsive image approaches use a fallback image and the pre-loader will often initiate the fallback image download before the javascript to select the appropriate image has executed leading to extra downloads.</p>

<h1>Influencing the pre-loader</h1>

<p>Currently there are limited ways we can influence the pre-loader's priorities (hiding resources using javascript is one), but the <a href="https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/ResourcePriorities/Overview.html">W3C Resource Priorities</a> spec proposes two attributes to help signal our intent.</p>

<p> <code>lazyload</code> : resource should not be downloaded until other resources that aren’t marked lazyload have started downloading</p>

<p> <code>postpone</code> : resource must not be downloaded until it’s visible to the user i.e. within the viewport and display is not none.</p>

<p>Although I’m not sure how easy it is to polyfill, perhaps postpone might enable a simple way of implementing responsive images?</p>

<h1>Pre-loading vs Pre-fetching</h1>

<p>Pre-fetching is a way of hinting to the browser about resources that are definitely going to or might be used in the future, some hints apply to the current page, others to possible future pages.</p>

<p>At the simplest level we can tell the browser to resolve the DNS for another hostname that we will access later on the page:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;dns-prefetch&quot;</span> <span class="na">href=</span><span class="s">&quot;other.hostname.com&quot;</span><span class="nt">&gt;</span>
</span></code></pre></div></figure>


<p>Chrome also allows us to hint that we’re going to use another resource later in the current page and so it should be downloaded as a high priority:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;subresource&quot;</span>  <span class="na">href=</span><span class="s">&quot;/some_other_resource.js&quot;</span><span class="nt">&gt;</span>
</span></code></pre></div></figure>


<p>(Chromium’s source code suggests it’s actually downloaded as a lower priority than stylesheets/scripts and fonts but at an equal or higher priority than images)</p>

<p>There are two more link types that allow us to speculatively hint about what comes next and will be downloaded at a lower priority than the resources on the current page.</p>

<p>Prefetch an individual resource that might be on the next page:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prefetch&quot;</span>  <span class="na">href=</span><span class="s">&quot;/some_other_resource.jpeg&quot;</span><span class="nt">&gt;</span>
</span></code></pre></div></figure>


<p>Prefetch and render a whole page in a background tab:</p>

<figure class='code'><figcaption><span></span></figcaption><div class="highlight"></pre></td><td class='code'><pre><code class='html'><span class='line'><span class="nt">&lt;link</span> <span class="na">rel=</span><span class="s">&quot;prerender&quot;</span>  <span class="na">href=</span><span class="s">&quot;//domain.com/next_page.html&quot;</span><span class="nt">&gt;</span>
</span></code></pre></div></figure>


<p>Ilya Grigorik’s <a href="https://docs.google.com/presentation/d/18zlAdKAxnc51y_kj-6sWLmnjl6TLnaru_WH0LJTjP-o/present">Preconnect, prefetch, prerender… talk</a> from WebPerfDays New York is a good place to start if you want to learn more about pre-fetching.</p>

<h1>Summary</h1>

<p>The pre-loader isn’t new, it delivers a significant performance boost and as authors we don’t need to do anything special to take advantage of it.</p>

<p>It’s widely implemented - I tested the following browsers to confirm they had a pre-loader:</p>

<ul>
<li>IE8 / 9 / 10</li>
<li>Firefox</li>
<li>Chrome (inc Android)</li>
<li>Safari (inc iOS)</li>
<li>Android 2.2.2 / 2.3 (2.2.2 tested 18 May 2014)</li>
</ul>


<p>Bruce Lawson also confirmed Opera Mini uses the Presto engine which has a pre-loader.</p>

<p>Resource Priorities (and perhaps <code>&lt;link rel=subresource...</code>) will give us some ways to indicate our priorities to it.</p>

<p>If you spot any typos, or have and questions add them in the comments and I'll do my best to fix and answer.</p>

<h1>References / Further Reading:</h1>

<p>If you’re interested in digging further here’s some presentations, posts, bug reports etc. I read while writing this:</p>

<p><a href="http://gent.ilcore.com/2011/01/webkit-preloadscanner.html">The WebKit PreloadScanner</a></p>

<p><a href="http://gent.ilcore.com/2011/05/how-web-page-loads.html">How a web page loads</a></p>

<p><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=364315">Speculatively load referenced files while "real" parsing is blocked on a &lt;script src=&gt; load</a></p>

<p><a href="https://bugs.webkit.org/show_bug.cgi?id=17480">Implement speculative preloading</a></p>

<p><a href="http://blogs.msdn.com/b/ieinternals/archive/2011/07/18/optimal-html-head-ordering-to-avoid-parser-restarts-redownloads-and-improve-performance.aspx">Best Practice: Get your HEAD in order</a></p>

<p><a href="http://blogs.msdn.com/b/ieinternals/archive/2009/07/27/bugs-in-the-ie8-lookahead-downloader.aspx">Bugs in IE8's Lookahead Downloader</a></p>

<p><a href="https://dvcs.w3.org/hg/webperf/raw-file/tip/specs/ResourcePriorities/Overview.html">W3C Resource Priorities</a></p>

<p><a href="https://docs.google.com/document/d/1JQZXrONw1RrjrdD_Z9jq1ZKsHguh8UVGHY_MZgE63II/preview?hl=en-GB&amp;forcehl=1#heading=h.5tq224qeoth4">Chrome PLT Improvements Q1 2013</a></p>

<p><a href="https://docs.google.com/spreadsheet/ccc?key=0As3TLupYw2RedG50WW9hNldQaERDTlFHMEc2S2FBTXc#gid=4">Chrome Pre-Loader Test</a></p>

<p><a href="https://docs.google.com/presentation/d/18zlAdKAxnc51y_kj-6sWLmnjl6TLnaru_WH0LJTjP-o/present#slide=id.p19">Preconnect, prefetch, prerender</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Using a Private Instance of the HTTP Archive to Explore Site Performance]]></title>
    <link href="https://andydavies.me/blog/2013/09/01/using-a-private-instance-of-the-http-archive-to-explore-site-performance/"/>
    <updated>2013-09-01T21:25:00+01:00</updated>
    <id>https://andydavies.me/blog/2013/09/01/using-a-private-instance-of-the-http-archive-to-explore-site-performance</id>
    <content type="html"><![CDATA[<p>With our current tools it’s relatively easy to examine the performance of a single page, or the performance of a journey a visitor takes through a series of pages but when I examine a client’s site for the first time I often want to get a broad view of performance across the whole site.</p>

<p>There are a few tools that can crawl a site and produce performance reports, <a href="http://sitespeed.io/">SiteSpeed.io</a> from <a href="https://twitter.com/soulislove">Peter Hedenskog</a> and NCC Group’s (was Site Confidence) <a href="http://www.nccgroup.com/en/our-services/website-performance-software-testing/website-performance-optimisation/real-browser-performance-analyser/">Performance Analyser</a> are two I use regularly.</p>

<p>Sometimes I want more than these tools offer - I might want to test from the USA or Japan, or want some measurements they don’t provide - that’s when I use my own instance of the HTTP Archive.</p>

<!-- MORE -->


<h1>HTTP Archive</h1>

<p>I run a customised version of the HTTP Archive using my own instance of WebPageTest (WPT) using test agents at various locations.</p>

<p>Getting the HTTP Archive up and running is a bit fiddly but not too hard.</p>

<p>I didn’t make any notes when I got my own instance up and running but <a href="https://twitter.com/bbinto">Barbara Bermes</a> wrote a pretty good guide - <a href="http://bbinto.wordpress.com/2013/03/25/setup-your-own-http-archive-to-track-and-query-your-site-trends/">Setup your own HTTP Archive to track and query your site trends</a></p>

<p>My own instance is slightly different from the ‘out of the box’ version:</p>

<p>The batch process that submits jobs to WebPageTest, monitors them and then collects the results is split into two separate processes:</p>

<ul>
<li>Submit the tests (I monitor the jobs via WebPageTest until they’ve completed)</li>
<li>Collects the results for completed tests, parses the results and inserts into DB</li>
</ul>


<p>I’ve also introduced some new tables, one which maps WPT locations to friendly names, and another which groups URLs to be tested so that I can test subsets of pages, a page across multiple locations, multiple browsers etc.</p>

<p>These changes will be open sourced at some point later this year (some of the changes were part of a client engagement and they’ve agreed they can be released - probably in the Autumn)</p>

<h1>Exploring a site</h1>

<p>To gather the  URLs to be tested I often crawl a site with sitespeed.io or another crawler before inserting the URLs manually into the HTTP Archive DB (spot the automation opportunity).</p>

<p>Once the URLs are in the DB, I schedule the tests with WPT, and collect the data when the tests complete.</p>

<p>Although I use the HTTP Archive for data collection and storage, I don’t actually use the web interface to examine the data.</p>

<h1>SQL</h1>

<p>Small images that might be suitable for techniques like spriting, or larger images that should be optimised further are really easy to identify with simple SQL queries, such as:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>select distinct url, respsize from requests where url like 'http://www.domain.com%' and mimetype like 'image/%' order by respsize desc;</span></code></pre></div></figure>


<h1>R</h1>

<p>People are pattern matchers so I like to draw charts from the raw data using <a href="http://www.r-project.org/">R</a> (via <a href="http://www.rstudio.com/">R Studio</a>) to extract and visualise it.</p>

<p>To start I typically plot the distribution of page size and requests per page, I also plot the load time of all pages split by the various loading phases.</p>

<p>Before plotting any charts we need to connect to the MySQL DB and extract the data we want as follows:</p>

<p>Install MySQL package (only once)</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>install.packages("RMySQL")</span></code></pre></div></figure>


<p>Connect to the database, and create a data frame with the relevant data</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>library(RMySQL)
</span><span class='line'>
</span><span class='line'>drv = dbDriver('MySQL')
</span><span class='line'>
</span><span class='line'>con = dbConnect(drv, user='user’, password='password', dbname='httparchive', host='127.0.0.1')
</span><span class='line'>
</span><span class='line'>results = dbGetQuery(con,statement='select url, wptid, ttfb, renderstart, visualcomplete, onload, fullyloaded, reqtotal, bytestotal, gzipsavings from pages where url like "http://www.domain.com%" order by bytestotal desc;')</span></code></pre></div></figure>


<p>R Studio now has data frame named results that contains the data extracted from the DB and charts can be plotted from this.</p>

<h1>Requests / Page Histogram</h1>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>hist(results$reqtotal, xlim=c(0,200), ylim=c(0,375),breaks=seq(0,200,by=5), main="", xlab="Number of Requests",col="steelblue", border="white", axes=FALSE)
</span><span class='line'>axis(1, at = seq(0, 225, by = 25))
</span><span class='line'>axis(2, at = seq(0, 400, by = 25))</span></code></pre></div></figure>


<p><img src="https://andydavies.me/blog/images/requests.png" title="'Distribution of Requests / Page'" > <br></p>

<h1>Page Size Histogram</h1>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>hist(results$bytestotal / 1000, col="steelblue", border="white", breaks=seq(0,1800,by=100), xlim=c(0,1800), axes=FALSE, plot=TRUE, main="", xlab="Page Size (KB)")
</span><span class='line'>axis(1, at = seq(0, 1800, by = 250))
</span><span class='line'>axis(2, at = seq(0, 600, by = 25))</span></code></pre></div></figure>


<p><img src="https://andydavies.me/blog/images/size.png" title="'Distribution of Page Size'" > <br></p>

<h1>Timing breakdown</h1>

<p>Creating a chart to display the load time is a little more complicated, first we extract just the columns we're interested in, then sort them by the fully loaded time, then calculate the size of each bar segment before plotting it.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>timings &lt;- results[, c("ttfb", "renderstart", "visualcomplete", "fullyloaded")]
</span><span class='line'>timings &lt;-timings[order(-timings$fullyloaded),]
</span><span class='line'>
</span><span class='line'>diff &lt;- data.frame(ttfb=timings$ttfb, renderstart=timings$renderstart - timings$ttfb, visualcomplete=timings$visualcomplete - timings$renderstart, fullyloaded=timings$fullyloaded - timings$visualcomplete)
</span><span class='line'>
</span><span class='line'>barplot(t(diff), legend=c("TTFB", "Start Render", "Visually Complete", "Fully Loaded"), col=c("yellow3", "darkorange", "chartreuse3", "steelblue"), border=NA, width=1, ylab="ms", bty="n", args.legend = list(border=NA, bty="n"))</span></code></pre></div></figure>


<p><img src="https://andydavies.me/blog/images/time-breakdown.png" title="'Breakdown of Page Load by Phase '" > <br></p>

<p>In this chart there's a clear pattern for TTFB - the pages with low times are all product pages, and the pages with longer TTFBs are pages such as category pages  a visitor would need to drill through to reach a product page.</p>

<p>Some of the charts don't display as clearly as I would like e.g. the timing breakdown has bands and the charts axes normally need some fiddling with. Brushing up my R so I can automate this process further and create better charts is on my todo list!</p>

<p>I know <a href="https://twitter.com/souders">Steve</a> didn't intend the HTTP Archive to be something everyone hosted for themselves but it makes a pretty handy tool for bulk testing, and tools like R make it easy to visualise the data to get an understanding of site performance.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Creating Page Load Waterfalls for Older Mobile Devices]]></title>
    <link href="https://andydavies.me/blog/2013/08/19/creating-page-load-waterfalls-for-older-mobile-devices/"/>
    <updated>2013-08-19T00:23:00+01:00</updated>
    <id>https://andydavies.me/blog/2013/08/19/creating-page-load-waterfalls-for-older-mobile-devices</id>
    <content type="html"><![CDATA[<p>Like most people involved in web performance I spend hours looking at page load waterfalls, each one tells it's own story and the patterns hint at where the issues are.</p>

<p>With tools like Mobitest, WebPageTest, remote debugging in Chrome and Safari I can get a good level of detail for modern mobile browsers but I often want to test on older devices where the dev tools support isn't as good or more commonly non-existent.</p>

<p>A proxy like <a href="http://www.charlesproxy.com/">Charles</a> is one way of generating HARs from old devices but using a proxy can alter the waterfall so I prefer to generate waterwalls from a TCP packet capture.</p>

<!-- MORE -->


<p>There are a couple of ways to generate the waterfall but first we need to capture the traffic.</p>

<h1>Capturing the traffic</h1>

<p>I'm on OSX but you should be able to achieve a similar setup on other platforms. I've also been playing with a travel router running OpenWRT, as I want to see if I can get traffic shaping working on it.</p>

<ul>
<li>Connect to a wired network</li>
</ul>


<p>I'm going to use the OSX to share the wired network via wireless so first we need an actual physical network connection.</p>

<ul>
<li>Create an adhoc wireless network</li>
</ul>


<p>Click on the WiFi icon in the OSX menu bar and choose "Create Network"</p>

<p><img src="https://andydavies.me/blog/images/adhoc-network.png" title="'Creating an adhoc network in OSX'" > <br></p>

<p>In this example I've not secured the network but you may want to.</p>

<ul>
<li>Share the connection</li>
</ul>


<p>Pick Sharing in System Preferences and share the internet over WiFi.</p>

<p><img src="https://andydavies.me/blog/images/internet-sharing.png" title="'Sharing an internet connection in OSX'" > <br></p>

<ul>
<li>Connect to mobile device the adhoc network</li>
</ul>


<p>On the mobile device connect to the adhoc WiFi network you created above.</p>

<ul>
<li>Capture the traffic</li>
</ul>


<p>From a terminal window, start the packet capture:</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>tcpdump -i en1 -w test.pcap</span></code></pre></div></figure>


<p>(This command captures the traffic from the wireless interface - en1, and writes the capture to the file - test.pcap)</p>

<p>On the phone, load the pages you want a waterfall for and then hit <em>Ctrl-C</em> to stop the capture when you're done.</p>

<p>Then once we've got a capture we need something to convert it to a waterfall.</p>

<h1>Viewing the Waterfall</h1>

<p>Andrew Fleenor's <a href="https://github.com/andrewf/pcap2har">pcap2har</a>, or the hosted version <a href="http://pcapperf.appspot.com/">http://pcapperf.appspot.com/</a> are two ways of converting the packet capture to a waterfall but lately I've been playing with AT&amp;T's <a href="http://developer.att.com/developer/forward.jsp?passedItemId=9700312">Application Resource Optimizer (ARO)</a></p>

<p>You'll need to signup to AT&amp;T's developer program (it's free) to download ARO but once ARO is installed it's pretty easy to use - just open the pcap and view the waterfall (this one is for <a href="http://lynn.ru/examples/svg/en.html">http://lynn.ru/examples/svg/en.html</a> on an Android 2.3 phone).</p>

<p><img src="https://andydavies.me/blog/images/waterfall-in-aro.png" title="'Waterfall generated in ARO'" > <br></p>

<p>ARO has other features that I've yet to explore such as diagnosing issues estimating 3G radio states and energy usage.</p>

<p>This approach isn't just limited to just old devices, if you want to study the behaviour of a UIWebView in an iOS app this works too.</p>

<p>If you want to try Charles then <a href="https://twitter.com/grigs">Jason Grigsby</a> wrote a pretty thorough guide <a href="http://blog.cloudfour.com/using-charles-proxy-to-examine-ios-apps/">Using Charles Proxy to examine iOS apps</a></p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[From a Logfile to a Histogram With a Few Lines of R]]></title>
    <link href="https://andydavies.me/blog/2013/06/06/from-logfile-to-histogram-with-a-few-lines-of-r/"/>
    <updated>2013-06-06T20:28:00+01:00</updated>
    <id>https://andydavies.me/blog/2013/06/06/from-logfile-to-histogram-with-a-few-lines-of-r</id>
    <content type="html"><![CDATA[<p>I've been helping a client identify some performance issues with a new hosting platform they're in the process of commissioning.</p>

<p>The new platform has New Relic running but unfortunately it only provides an average for response times. Averages can hide all manner of sins so I prefer to look at the distribution of response times, I also wanted a way to compare against the existing platform which has no monitoring on it.</p>

<p>The method I chose was to add <a href="http://support.microsoft.com/kb/944884">time taken</a> to the IIS logfiles and plot histograms using R.</p>

<!-- MORE -->


<p>(Time taken includes network time which may be an issue in some scenarios)</p>

<p>R is a tool for statistical computing that makes crunching numbers and turning them into graphs relatively easy. When I first started using R I found it had a bit of a learning curve and I still have to work had to do anything that's not trivial but that's probably a mixture of all the statistical knowledge I've forgotten and the language / libraries.</p>

<p>IIS logfiles start with a multi-line header (sometimes they can have one part way through too!) that looks like this</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>#Software: Microsoft Internet Information Services 7.5
</span><span class='line'>#Version: 1.0
</span><span class='line'>#Date: 2013-05-31 00:00:02
</span><span class='line'>#Fields: date time s-sitename s-computername s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs-version cs(User-Agent) cs(Referer) cs-host sc-status sc-substatus sc-win32-status sc-bytes cs-bytes time-taken
</span><span class='line'>2013-05-31 00:00:13 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 545
</span><span class='line'>2013-05-31 00:00:18 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 592
</span><span class='line'>2013-05-31 00:00:23 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 561
</span><span class='line'>2013-05-31 00:00:28 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 577
</span><span class='line'>   .
</span><span class='line'>   .</span></code></pre></div></figure>


<p>Most of the header needs stripping off so we're just left with a tab separated set of field headers</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>date time s-sitename s-computername s-ip cs-method cs-uri-stem cs-uri-query s-port cs-username c-ip cs-version cs(User-Agent) cs(Referer) cs-host sc-status sc-substatus sc-win32-status sc-bytes cs-bytes time-taken
</span><span class='line'>2013-05-31 00:00:13 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 545
</span><span class='line'>2013-05-31 00:00:18 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 592
</span><span class='line'>2013-05-31 00:00:23 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 561
</span><span class='line'>2013-05-31 00:00:28 W3SVC1 WEB1 171.12.3.100 GET / - 80 - 172.16.3.254 HTTP/1.0 - - 171.12.3.100 200 0 0 81455 56 577
</span><span class='line'>   .
</span><span class='line'>   .</span></code></pre></div></figure>


<p>Once the data is in a format we can use, it needs to be imported into R, filtered and plotted as a histogram.</p>

<p><a href="http://www.r-project.org/">R</a> has a command shell but I prefer to use <a href="http://www.rstudio.com/">R Studio</a> when I'm exploring data.</p>

<ul>
<li>Import logfile</li>
</ul>


<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>data = read.table("path.to.logfile/logfile.name", header=T, sep="")</span></code></pre></div></figure>


<ul>
<li>Filter data</li>
</ul>


<p>Here I'm only including GET requests for the root that took less than a second (there are a few over a second that skewed the chart, of course the outliers may be interesting in their own right so be careful about removing them)</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>subset &lt;- subset(data, cs.method=="GET" & cs.uri.stem=="/"  & time.taken &lt; 1000)</span></code></pre></div></figure>


<ul>
<li>Plot Histogram</li>
</ul>


<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>hist(subset$time.taken, xlab="time.taken (ms)", col="lightblue", main="Web 1")</span></code></pre></div></figure>


<p><img src="https://andydavies.me/blog/images/histogram.png" title="'Response times by frequency'" > <br></p>

<p>This may be a very simple example but hopefully it goes some way to showing the power of R.</p>

<p>If you need any convincing that you'd benefit from learning some statistics, <a href="https://twitter.com/zedshaw">@zedshaw's</a> <a href="http://zedshaw.com/essays/programmer_stats.html">Programmers Need To Learn Statistics Or I Will Kill Them All</a> might help you.</p>
]]></content>
  </entry>
  
  <entry>
    <title type="html"><![CDATA[Adding iOS Agents to a WebPagetest Instance]]></title>
    <link href="https://andydavies.me/blog/2013/03/05/adding-ios-test-agents-to-a-webpagetest-instance/"/>
    <updated>2013-03-05T07:49:00+00:00</updated>
    <id>https://andydavies.me/blog/2013/03/05/adding-ios-test-agents-to-a-webpagetest-instance</id>
    <content type="html"><![CDATA[<p>Back in September I explained how to create a private instance of Web Page Test running IE, Firefox and Chrome on Windows 7.</p>

<p>Recently I needed to add some iOS agents, after a bit of trial and error this is the approach I used.</p>

<!--more-->


<p>I've added the mobile agents to my existing 'all-in-one' WebPageTest instance so if you've not read the original post it's worth having a quick scan first: <a href="https://andydavies.me/blog/2012/09/18/how-to-create-an-all-in-one-webpagetest-private-instance/">Configuring an ‘all-in-one’ WebPageTest Private Instance</a></p>

<p>At a minimum you'll need access to a Mac with Xcode and iOS Simulator installed. If you want to use a real device as a test agent you will need an iPhone or iPad and an iOS developer account.</p>

<h1>Building the Mobitest Agent</h1>

<p>Confusingly there's some source code for a mobile agent in the WebPageTest SVN respository but as I discovered that's not the source we want!</p>

<p>We want the code for the Mobiletest agent that <a href="https://twitter.com/guypod">Guy Podjarny</a> &amp; Co created at Blaze (now part of Akamai).</p>

<ul>
<li>Download the Mobitest source <code>https://code.google.com/p/mobitest-agent/source/checkout</code> and build it using Xcode.</li>
</ul>


<h1>Install and Configure the Agent</h1>

<ul>
<li>Using Xcode launch the Mobitest App on the iOS Simulator or a physical device.</li>
</ul>


<p>Mobitest has a set of default settings that need updating to match the WebPageTest instance, for example:</p>

<p><img src="https://andydavies.me/blog/images/mobitest-settings.png" title="'Default and configured Mobitest settings'" ><br>
Default settings on left, settings for my install on right.</p>

<ul>
<li><p>Update <em>Jobs URL 1</em> to match the URL of your WebPageTest instance, and set the <em>Unique Agent Name</em> and <em>Location</em> (location must match the location you configure in WebPageTest).</p></li>
<li><p>Unless you are using using keys you'll need to remove the default <em>Location Key</em> too.</p></li>
<li><p>I also enable <em>Restart After Each Job</em> and <em>Auto-Poll</em> (scroll down for these settings)</p></li>
<li><p>Go back to the iOS home screen, launch Mobitest, press <em>Poll Now</em> and the agent should now poll the server for jobs.</p></li>
</ul>


<h1>Add the agent to WebPageTest</h1>

<p>Once the test agent is up and running we need to update WebPageTest's configuration so jobs can be scheduled for the new agent.</p>

<ul>
<li>Edit <code>settings\locations.ini</code></li>
</ul>


<p>Add the new agent under the relevant location section (<code>[Local]</code> and <code>3=Local-iPhone</code> in the example below)</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>[Local]
</span><span class='line'>1=Local-URLBlast
</span><span class='line'>2=Local-WPTDriver
</span><span class='line'>3=Local-iPhone
</span><span class='line'>label=Local</span></code></pre></div></figure>


<ul>
<li>Add a section for the agent itself.</li>
</ul>


<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>[Local-iPhone]
</span><span class='line'>browser=iPhone
</span><span class='line'>connectivity=3G
</span><span class='line'>label="Local"</span></code></pre></div></figure>


<p><strong>N.B. Section name must match the name set in <code>[Local]</code> and the Location field of Mobitest Agent settings</strong></p>

<ul>
<li>Save <code>locations.ini</code></li>
</ul>


<h1>Check it's working</h1>

<p>I find getLocations.php to be the easiest way of checking agents are configured correctly and requesting jobs.</p>

<ul>
<li>Go to <code>http://192.168.0.12/getLocations.php</code> (replace IP address with your WebPageTest URL) in your browser and you should see a list of all the current test agents, their status, and the number of jobs in their queues e.g.</li>
</ul>


<p><img src="https://andydavies.me/blog/images/getlocations.png" title="'Status of WebPageTest agents'" ><br>
List of test agents and their current status (priorities 2-8 omitted from table)</p>

<p>If either <em>Idle Testers</em> or <em>Being Tested</em> columns are greater than zero then the test agent is successfully communicating with the WebPageTest instance.</p>

<p>You should now be able to use the normal WebPageTest interface to run tests on the device you've just configured.</p>

<h1>Shaping the Connection</h1>

<p>On <a href="https://webpagetest.org">https://webpagetest.org</a>, the mobile agents connect via WiFi to a Linux bridge that shapes the network connection.</p>

<p>For my testing I use the Network Link Conditioner that's available on iOS devices registered for development (<em>Settings</em> > <em>Developer</em> > <em>Network Link Conditioner</em>).</p>

<p>If you're using the iOS Simulator for testing then unfortunately the Network Link Conditioner isn't installed. You can use the OSX version of the Network Link Condition instead but that will shape the connection for the whole of OSX.</p>

<p>In either situation you can force the WebPageTest connection dropdown to only show a single label by adding a <code>connectivity</code> entry to the location in <code>settings.ini</code></p>

<p>In the example below I've added a label that matches the Network Link Conditioner settings for 3G.</p>

<figure class='code'><div class="highlight"></pre></td><td class='code'><pre><code class=''><span class='line'>[Local-iPhone]
</span><span class='line'>browser=iPhone
</span><span class='line'>connectivity=3G (780/330 Kbps 100ms RTT)
</span><span class='line'>label="Local"</span></code></pre></div></figure>


<p>Unlike the PC based agents this setting doesn't have any influence over the actual connection settings used and they will always need to be configured separately.</p>

<h1>Some other things to note</h1>

<p>The Mobitest Agent uses a UIWebView rather than Safari, so any JavaScript on the page won't use the JIT and this may be an issue on Javascript heavy pages. This blog gets roughly the same number of visitors inApp on iOS i.e. UIWebView, as it does from iOS Safari so the UIWebView experience matters.</p>

<p>The Mobitest Agent ignores the physical orientation of the device and works in portrait only.</p>

<p>Pat Meenan reports that an Airport Express seems to be the most reliable base station for iOS devices. He also has a monitor reboots the Mobitest App via SSH when it stops responding.</p>

<h1>Questions or Comments?</h1>

<p>If you’ve got any questions or spot any mistakes feel free to leave a comment or drop me an email.</p>

<p>If you want to discuss private instances further or need help WebPageTest <a href="https://www.webpagetest.org/forums/forumdisplay.php?fid=12">Forum for Private Instances</a> is the place to go.</p>
]]></content>
  </entry>
  
</feed>
